archive.class.inc.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. <?php
  2. /**
  3. * archive.class.inc.php
  4. * Utility to import/export the DB from/to a ZIP file
  5. *
  6. * @package iTopORM
  7. * @author Romain Quetiez <romainquetiez@yahoo.fr>
  8. * @author Denis Flaven <denisflave@free.fr>
  9. * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
  10. * @link www.itop.com
  11. * @since 1.0
  12. * @version 1.1.1.1 $
  13. */
  14. /**
  15. * iTopArchive a class to manipulate (read/write) iTop archives with their catalog
  16. * Each iTop archive is a zip file that contains (at the root of the archive)
  17. * a file called catalog.xml holding the description of the archive
  18. */
  19. class iTopArchive
  20. {
  21. const read = 0;
  22. const create = ZipArchive::CREATE;
  23. protected $m_sZipPath;
  24. protected $m_oZip;
  25. protected $m_sVersion;
  26. protected $m_sTitle;
  27. protected $m_sDescription;
  28. protected $m_aPackages;
  29. protected $m_aErrorMessages;
  30. /**
  31. * Construct an iTopArchive object
  32. * @param $sArchivePath string The full path the archive file
  33. * @param $iMode integrer Either iTopArchive::read for reading an existing archive or iTopArchive::create for creating a new one. Updating is not supported (yet)
  34. */
  35. public function __construct($sArchivePath, $iMode = iTopArchive::read)
  36. {
  37. $this->m_sZipPath = $sArchivePath;
  38. $this->m_oZip = new ZipArchive();
  39. $this->m_oZip->open($this->m_sZipPath, $iMode);
  40. $this->m_aErrorMessages = array();
  41. $this->m_sVersion = '1.0';
  42. $this->m_sTitle = '';
  43. $this->m_sDescription = '';
  44. $this->m_aPackages = array();
  45. }
  46. public function SetTitle($sTitle)
  47. {
  48. $this->m_sTitle = $sTitle;
  49. }
  50. public function SetDescription($sDescription)
  51. {
  52. $this->m_sDescription = $sDescription;
  53. }
  54. public function GetTitle()
  55. {
  56. return $this->m_sTitle;
  57. }
  58. public function GetDescription()
  59. {
  60. return $this->m_sDescription;
  61. }
  62. public function GetPackages()
  63. {
  64. return $this->m_aPackages;
  65. }
  66. public function __destruct()
  67. {
  68. $this->m_oZip->close();
  69. }
  70. /**
  71. * Get the error message explaining the latest error encountered
  72. * @return array All the error messages encountered during the validation
  73. */
  74. public function GetErrors()
  75. {
  76. return $this->m_aErrorMessages;
  77. }
  78. /**
  79. * Read the catalog from the archive (zip) file
  80. * @param sPath string Path the the zip file
  81. * @return boolean True in case of success, false otherwise
  82. */
  83. public function ReadCatalog()
  84. {
  85. if ($this->IsValid())
  86. {
  87. $sXmlCatalog = $this->m_oZip->getFromName('catalog.xml');
  88. $oParser = xml_parser_create();
  89. xml_parse_into_struct($oParser, $sXmlCatalog, $aValues, $aIndexes);
  90. xml_parser_free($oParser);
  91. $iIndex = $aIndexes['ARCHIVE'][0];
  92. $this->m_sVersion = $aValues[$iIndex]['attributes']['VERSION'];
  93. $iIndex = $aIndexes['TITLE'][0];
  94. $this->m_sTitle = $aValues[$iIndex]['value'];
  95. $iIndex = $aIndexes['DESCRIPTION'][0];
  96. if (array_key_exists('value', $aValues[$iIndex]))
  97. {
  98. // #@# implement a get_array_value(array, key, default) ?
  99. $this->m_sDescription = $aValues[$iIndex]['value'];
  100. }
  101. foreach($aIndexes['PACKAGE'] as $iIndex)
  102. {
  103. $this->m_aPackages[$aValues[$iIndex]['attributes']['HREF']] = array( 'type' => $aValues[$iIndex]['attributes']['TYPE'], 'title'=> $aValues[$iIndex]['attributes']['TITLE'], 'description' => $aValues[$iIndex]['value']);
  104. }
  105. //echo "Archive path: {$this->m_sZipPath}<br/>\n";
  106. //echo "Archive format version: {$this->m_sVersion}<br/>\n";
  107. //echo "Title: {$this->m_sTitle}<br/>\n";
  108. //echo "Description: {$this->m_sDescription}<br/>\n";
  109. //foreach($this->m_aPackages as $aFile)
  110. //{
  111. // echo "{$aFile['title']} ({$aFile['type']}): {$aFile['description']}<br/>\n";
  112. //}
  113. }
  114. return true;
  115. }
  116. public function WriteCatalog()
  117. {
  118. $sXml = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?".">\n"; // split the XML closing tag that disturbs PSPad's syntax coloring
  119. $sXml .= "<archive version=\"1.0\">\n";
  120. $sXml .= "<title>{$this->m_sTitle}</title>\n";
  121. $sXml .= "<description>{$this->m_sDescription}</description>\n";
  122. foreach($this->m_aPackages as $sFileName => $aFile)
  123. {
  124. $sXml .= "<package title=\"{$aFile['title']}\" type=\"{$aFile['type']}\" href=\"$sFileName\">{$aFile['description']}</package>\n";
  125. }
  126. $sXml .= "</archive>";
  127. $this->m_oZip->addFromString('catalog.xml', $sXml);
  128. }
  129. /**
  130. * Add a package to the archive
  131. * @param string $sExternalFilePath The path to the file to be added to the archive as a package (directories are not yet implemented)
  132. * @param string $sFilePath The name of the file inside the archive
  133. * @param string $sTitle A short title for this package
  134. * @param string $sType Type of the package. SQL scripts must be of type 'text/sql'
  135. * @param string $sDescription A longer description of the purpose of this package
  136. * @return none
  137. */
  138. public function AddPackage($sExternalFilePath, $sFilePath, $sTitle, $sType, $sDescription)
  139. {
  140. $this->m_aPackages[$sFilePath] = array('title' => $sTitle, 'type' => $sType, 'description' => $sDescription);
  141. $this->m_oZip->addFile($sExternalFilePath, $sFilePath);
  142. }
  143. /**
  144. * Reads the contents of the given file from the archive
  145. * @param string $sFileName The path to the file inside the archive
  146. * @return string The content of the file read from the archive
  147. */
  148. public function GetFileContents($sFileName)
  149. {
  150. return $this->m_oZip->getFromName($sFileName);
  151. }
  152. /**
  153. * Extracts the contents of the given file from the archive
  154. * @param string $sFileName The path to the file inside the archive
  155. * @param string $sDestinationFileName The path of the file to write
  156. * @return none
  157. */
  158. public function ExtractToFile($sFileName, $sDestinationFileName)
  159. {
  160. $iBufferSize = 64 * 1024; // Read 64K at a time
  161. $oZipStream = $this->m_oZip->getStream($sFileName);
  162. $oDestinationStream = fopen($sDestinationFileName, 'wb');
  163. while (!feof($oZipStream)) {
  164. $sContents = fread($oZipStream, $iBufferSize);
  165. fwrite($oDestinationStream, $sContents);
  166. }
  167. fclose($oZipStream);
  168. fclose($oDestinationStream);
  169. }
  170. /**
  171. * Apply a SQL script taken from the archive. The package must be listed in the catalog and of type text/sql
  172. * @param string $sFileName The path to the SQL package inside the archive
  173. * @return boolean false in case of error, true otherwise
  174. */
  175. public function ImportSql($sFileName, $sDatabase = 'itop')
  176. {
  177. if ( ($this->m_oZip->locateName($sFileName) == false) || (!isset($this->m_aPackages[$sFileName])) || ($this->m_aPackages[$sFileName]['type'] != 'text/sql'))
  178. {
  179. // invalid type or not listed in the catalog
  180. return false;
  181. }
  182. $sTempName = tempnam("../tmp/", "sql");
  183. //echo "Extracting to: '$sTempName'<br/>\n";
  184. $this->ExtractToFile($sFileName, $sTempName);
  185. // Note: the command line below works on Windows with the right path to mysql !!!
  186. $sCommandLine = 'type "'.$sTempName.'" | "/iTop/MySQL Server 5.0/bin/mysql.exe" -u root '.$sDatabase;
  187. //echo "Executing: '$sCommandLine'<br/>\n";
  188. exec($sCommandLine, $aOutput, $iRet);
  189. //echo "Return code: $iRet<br/>\n";
  190. //echo "Output:<br/><pre>\n";
  191. //print_r($aOutput);
  192. //echo "</pre><br/>\n";
  193. unlink($sTempName);
  194. return ($iRet == 0);
  195. }
  196. /**
  197. * Dumps some part of the specified MySQL database into the archive as a text/sql package
  198. * @param $sTitle string A short title for this SQL script
  199. * @param $sDescription string A longer description of the purpose of this SQL script
  200. * @param $sFileName string The name of the package inside the archive
  201. * @param $sDatabase string name of the database
  202. * @param $aTables array array or table names. If empty, all tables are dumped
  203. * @param $bStructureOnly boolean Whether or not to dump the data or just the schema
  204. * @return boolean False in case of error, true otherwise
  205. */
  206. public function AddDatabaseDump($sTitle, $sDescription, $sFileName, $sDatabase = 'itop', $aTables = array(), $bStructureOnly = true)
  207. {
  208. $sTempName = tempnam("../tmp/", "sql");
  209. $sNoData = $bStructureOnly ? "--no-data" : "";
  210. $sCommandLine = "\"/iTop/MySQL Server 5.0/bin/mysqldump.exe\" --user=root --opt $sNoData --result-file=$sTempName $sDatabase ".implode(" ", $aTables);
  211. //echo "Executing command: '$sCommandLine'<br/>\n";
  212. exec($sCommandLine, $aOutput, $iRet);
  213. //echo "Return code: $iRet<br/>\n";
  214. //echo "Output:<br/><pre>\n";
  215. //print_r($aOutput);
  216. //echo "</pre><br/>\n";
  217. if ($iRet == 0)
  218. {
  219. $this->AddPackage($sTempName, $sFileName, $sTitle, 'text/sql', $sDescription);
  220. }
  221. //unlink($sTempName);
  222. return ($iRet == 0);
  223. }
  224. /**
  225. * Check the consistency of the archive
  226. * @return boolean True if the archive file is consistent
  227. */
  228. public function IsValid()
  229. {
  230. // TO DO: use a DTD to validate the XML instead of this hand-made validation
  231. $bResult = true;
  232. $aMandatoryTags = array('ARCHIVE' => array('VERSION'),
  233. 'TITLE' => array(),
  234. 'DESCRIPTION' => array(),
  235. 'PACKAGE' => array('TYPE', 'HREF', 'TITLE'));
  236. $sXmlCatalog = $this->m_oZip->getFromName('catalog.xml');
  237. $oParser = xml_parser_create();
  238. xml_parse_into_struct($oParser, $sXmlCatalog, $aValues, $aIndexes);
  239. xml_parser_free($oParser);
  240. foreach($aMandatoryTags as $sTag => $aAttributes)
  241. {
  242. // Check that all the required tags are present
  243. if (!isset($aIndexes[$sTag]))
  244. {
  245. $this->m_aErrorMessages[] = "The XML catalog does not contain the mandatory tag $sTag.";
  246. $bResult = false;
  247. }
  248. else
  249. {
  250. foreach($aIndexes[$sTag] as $iIndex)
  251. {
  252. switch($aValues[$iIndex]['type'])
  253. {
  254. case 'complete':
  255. case 'open':
  256. // Check that all the required attributes are present
  257. foreach($aAttributes as $sAttribute)
  258. {
  259. if (!isset($aValues[$iIndex]['attributes'][$sAttribute]))
  260. {
  261. $this->m_aErrorMessages[] = "The tag $sTag ($iIndex) does not contain the required attribute $sAttribute.";
  262. }
  263. }
  264. break;
  265. default:
  266. // ignore other type of tags: close or cdata
  267. }
  268. }
  269. }
  270. }
  271. return $bResult;
  272. }
  273. }
  274. /*
  275. // Unit test - reading an archive
  276. $sArchivePath = '../tmp/archive.zip';
  277. $oArchive = new iTopArchive($sArchivePath, iTopArchive::read);
  278. $oArchive->ReadCatalog();
  279. $oArchive->ImportSql('full_backup.sql');
  280. // Writing an archive --
  281. $sArchivePath = '../tmp/archive2.zip';
  282. $oArchive = new iTopArchive($sArchivePath, iTopArchive::create);
  283. $oArchive->SetTitle('First Archive !');
  284. $oArchive->SetDescription('This is just a test. Does not contain a lot of useful data.');
  285. $oArchive->AddPackage('../tmp/schema.sql', 'test.sql', 'this is just a test', 'text/sql', 'My first attempt at creating an archive from PHP...');
  286. $oArchive->WriteCatalog();
  287. $sArchivePath = '../tmp/archive2.zip';
  288. $oArchive = new iTopArchive($sArchivePath, iTopArchive::create);
  289. $oArchive->SetTitle('First Archive !');
  290. $oArchive->SetDescription('This is just a test. Does not contain a lot of useful data.');
  291. $oArchive->AddDatabaseDump('Test', 'This is my first automatic dump', 'schema.sql', 'itop', array('objects'));
  292. $oArchive->WriteCatalog();
  293. */
  294. ?>