PageRenderTime 49ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/joomla/installer/adapters/file.php

http://github.com/joomla/joomla-platform
PHP | 750 lines | 448 code | 118 blank | 184 comment | 58 complexity | a1d4549d77ebec19287f9ed00daa8562 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. /**
  3. * @package Joomla.Platform
  4. * @subpackage Installer
  5. *
  6. * @copyright Copyright (C) 2005 - 2012 Open Source Matters, Inc. All rights reserved.
  7. * @license GNU General Public License version 2 or later; see LICENSE
  8. */
  9. defined('JPATH_PLATFORM') or die;
  10. jimport('joomla.installer.filemanifest');
  11. jimport('joomla.base.adapterinstance');
  12. /**
  13. * File installer
  14. *
  15. * @package Joomla.Platform
  16. * @subpackage Installer
  17. * @since 11.1
  18. */
  19. class JInstallerFile extends JAdapterInstance
  20. {
  21. protected $route = 'install';
  22. /**
  23. * Custom loadLanguage method
  24. *
  25. * @param string $path The path on which to find language files.
  26. *
  27. * @return void
  28. *
  29. * @since 11.1
  30. */
  31. public function loadLanguage($path)
  32. {
  33. $this->manifest = $this->parent->getManifest();
  34. $extension = 'files_' . str_replace('files_', '', strtolower(JFilterInput::getInstance()->clean((string) $this->manifest->name, 'cmd')));
  35. $lang = JFactory::getLanguage();
  36. $source = $path;
  37. $lang->load($extension . '.sys', $source, null, false, false)
  38. || $lang->load($extension . '.sys', JPATH_SITE, null, false, false)
  39. || $lang->load($extension . '.sys', $source, $lang->getDefault(), false, false)
  40. || $lang->load($extension . '.sys', JPATH_SITE, $lang->getDefault(), false, false);
  41. }
  42. /**
  43. * Custom install method
  44. *
  45. * @return boolean True on success
  46. *
  47. * @since 11.1
  48. */
  49. public function install()
  50. {
  51. // Get the extension manifest object
  52. $this->manifest = $this->parent->getManifest();
  53. // Manifest Document Setup Section
  54. // Set the extension's name
  55. $name = JFilterInput::getInstance()->clean((string) $this->manifest->name, 'string');
  56. $this->set('name', $name);
  57. // Set element
  58. $manifestPath = JPath::clean($this->parent->getPath('manifest'));
  59. $element = preg_replace('/\.xml/', '', basename($manifestPath));
  60. $this->set('element', $element);
  61. // Get the component description
  62. $description = (string) $this->manifest->description;
  63. if ($description)
  64. {
  65. $this->parent->set('message', JText::_($description));
  66. }
  67. else
  68. {
  69. $this->parent->set('message', '');
  70. }
  71. // Check if the extension by the same name is already installed
  72. if ($this->extensionExistsInSystem($element))
  73. {
  74. // Package with same name already exists
  75. if (!$this->parent->isOverwrite())
  76. {
  77. // We're not overwriting so abort
  78. $this->parent->abort(JText::_('JLIB_INSTALLER_ABORT_FILE_SAME_NAME'));
  79. return false;
  80. }
  81. else
  82. {
  83. // Swap to the update route
  84. $this->route = 'update';
  85. }
  86. }
  87. // Set the file root path
  88. $this->parent->setPath('extension_root', JPATH_ROOT);
  89. /**
  90. * ---------------------------------------------------------------------------------------------
  91. * Installer Trigger Loading
  92. * ---------------------------------------------------------------------------------------------
  93. */
  94. // If there is an manifest class file, lets load it; we'll copy it later (don't have dest yet)
  95. $this->scriptElement = $this->manifest->scriptfile;
  96. $manifestScript = (string) $this->manifest->scriptfile;
  97. if ($manifestScript)
  98. {
  99. $manifestScriptFile = $this->parent->getPath('source') . '/' . $manifestScript;
  100. if (is_file($manifestScriptFile))
  101. {
  102. // Load the file
  103. include_once $manifestScriptFile;
  104. }
  105. // Set the class name
  106. $classname = $element . 'InstallerScript';
  107. if (class_exists($classname))
  108. {
  109. // Create a new instance
  110. $this->parent->manifestClass = new $classname($this);
  111. // And set this so we can copy it later
  112. $this->set('manifest_script', $manifestScript);
  113. // Note: if we don't find the class, don't bother to copy the file
  114. }
  115. }
  116. // Run preflight if possible (since we know we're not an update)
  117. ob_start();
  118. ob_implicit_flush(false);
  119. if ($this->parent->manifestClass && method_exists($this->parent->manifestClass, 'preflight'))
  120. {
  121. if ($this->parent->manifestClass->preflight($this->route, $this) === false)
  122. {
  123. // Install failed, rollback changes
  124. $this->parent->abort(JText::_('JLIB_INSTALLER_ABORT_FILE_INSTALL_CUSTOM_INSTALL_FAILURE'));
  125. return false;
  126. }
  127. }
  128. // Create msg object; first use here
  129. $msg = ob_get_contents();
  130. ob_end_clean();
  131. // Populate File and Folder List to copy
  132. $this->populateFilesAndFolderList();
  133. // Filesystem Processing Section
  134. // Now that we have folder list, lets start creating them
  135. foreach ($this->folderList as $folder)
  136. {
  137. if (!JFolder::exists($folder))
  138. {
  139. if (!$created = JFolder::create($folder))
  140. {
  141. JError::raiseWarning(1, JText::sprintf('JLIB_INSTALLER_ABORT_FILE_INSTALL_FAIL_SOURCE_DIRECTORY', $folder));
  142. // If installation fails, rollback
  143. $this->parent->abort();
  144. return false;
  145. }
  146. // Since we created a directory and will want to remove it if we have to roll back.
  147. // The installation due to some errors, let's add it to the installation step stack.
  148. if ($created)
  149. {
  150. $this->parent->pushStep(array('type' => 'folder', 'path' => $folder));
  151. }
  152. }
  153. }
  154. // Now that we have file list, let's start copying them
  155. $this->parent->copyFiles($this->fileList);
  156. // Parse optional tags
  157. $this->parent->parseLanguages($this->manifest->languages);
  158. // Finalization and Cleanup Section
  159. // Get a database connector object
  160. $db = $this->parent->getDbo();
  161. /*
  162. * Check to see if a module by the same name is already installed
  163. * If it is, then update the table because if the files aren't there
  164. * we can assume that it was (badly) uninstalled
  165. * If it isn't, add an entry to extensions
  166. */
  167. $query = $db->getQuery(true);
  168. $query->select($query->qn('extension_id'))
  169. ->from($query->qn('#__extensions'));
  170. $query->where($query->qn('type') . ' = ' . $query->q('file'))
  171. ->where($query->qn('element') . ' = ' . $query->q($element));
  172. $db->setQuery($query);
  173. try
  174. {
  175. $db->Query();
  176. }
  177. catch (JException $e)
  178. {
  179. // Install failed, roll back changes
  180. $this->parent->abort(
  181. JText::sprintf('JLIB_INSTALLER_ABORT_FILE_ROLLBACK', JText::_('JLIB_INSTALLER_' . $this->route), $db->stderr(true))
  182. );
  183. return false;
  184. }
  185. $id = $db->loadResult();
  186. $row = JTable::getInstance('extension');
  187. if ($id)
  188. {
  189. // Load the entry and update the manifest_cache
  190. $row->load($id);
  191. // Update name
  192. $row->set('name', $this->get('name'));
  193. // Update manifest
  194. $row->manifest_cache = $this->parent->generateManifestCache();
  195. if (!$row->store())
  196. {
  197. // Install failed, roll back changes
  198. $this->parent->abort(
  199. JText::sprintf('JLIB_INSTALLER_ABORT_FILE_ROLLBACK', JText::_('JLIB_INSTALLER_' . $this->route), $db->stderr(true))
  200. );
  201. return false;
  202. }
  203. }
  204. else
  205. {
  206. // Add an entry to the extension table with a whole heap of defaults
  207. $row->set('name', $this->get('name'));
  208. $row->set('type', 'file');
  209. $row->set('element', $this->get('element'));
  210. // There is no folder for files so leave it blank
  211. $row->set('folder', '');
  212. $row->set('enabled', 1);
  213. $row->set('protected', 0);
  214. $row->set('access', 0);
  215. $row->set('client_id', 0);
  216. $row->set('params', '');
  217. $row->set('system_data', '');
  218. $row->set('manifest_cache', $this->parent->generateManifestCache());
  219. if (!$row->store())
  220. {
  221. // Install failed, roll back changes
  222. $this->parent->abort(JText::sprintf('JLIB_INSTALLER_ABORT_FILE_INSTALL_ROLLBACK', $db->stderr(true)));
  223. return false;
  224. }
  225. // Set the insert id
  226. $row->set('extension_id', $db->insertid());
  227. // Since we have created a module item, we add it to the installation step stack
  228. // so that if we have to rollback the changes we can undo it.
  229. $this->parent->pushStep(array('type' => 'extension', 'extension_id' => $row->extension_id));
  230. }
  231. /*
  232. * Let's run the queries for the file
  233. */
  234. // second argument is the utf compatible version attribute
  235. if (strtolower($this->route) == 'install')
  236. {
  237. $utfresult = $this->parent->parseSQLFiles($this->manifest->install->sql);
  238. if ($utfresult === false)
  239. {
  240. // Install failed, rollback changes
  241. $this->parent->abort(
  242. JText::sprintf('JLIB_INSTALLER_ABORT_FILE_INSTALL_SQL_ERROR', JText::_('JLIB_INSTALLER_' . $this->route), $db->stderr(true))
  243. );
  244. return false;
  245. }
  246. // Set the schema version to be the latest update version
  247. if ($this->manifest->update)
  248. {
  249. $this->parent->setSchemaVersion($this->manifest->update->schemas, $row->extension_id);
  250. }
  251. }
  252. elseif (strtolower($this->route) == 'update')
  253. {
  254. if ($this->manifest->update)
  255. {
  256. $result = $this->parent->parseSchemaUpdates($this->manifest->update->schemas, $row->extension_id);
  257. if ($result === false)
  258. {
  259. // Install failed, rollback changes
  260. $this->parent->abort(JText::sprintf('JLIB_INSTALLER_ABORT_FILE_UPDATE_SQL_ERROR', $db->stderr(true)));
  261. return false;
  262. }
  263. }
  264. }
  265. // Start Joomla! 1.6
  266. ob_start();
  267. ob_implicit_flush(false);
  268. if ($this->parent->manifestClass && method_exists($this->parent->manifestClass, $this->route))
  269. {
  270. if ($this->parent->manifestClass->{$this->route}($this) === false)
  271. {
  272. // Install failed, rollback changes
  273. $this->parent->abort(JText::_('JLIB_INSTALLER_ABORT_FILE_INSTALL_CUSTOM_INSTALL_FAILURE'));
  274. return false;
  275. }
  276. }
  277. // Append messages
  278. $msg .= ob_get_contents();
  279. ob_end_clean();
  280. // Lastly, we will copy the manifest file to its appropriate place.
  281. $manifest = array();
  282. $manifest['src'] = $this->parent->getPath('manifest');
  283. $manifest['dest'] = JPATH_MANIFESTS . '/files/' . basename($this->parent->getPath('manifest'));
  284. if (!$this->parent->copyFiles(array($manifest), true))
  285. {
  286. // Install failed, rollback changes
  287. $this->parent->abort(JText::_('JLIB_INSTALLER_ABORT_FILE_INSTALL_COPY_SETUP'));
  288. return false;
  289. }
  290. // Clobber any possible pending updates
  291. $update = JTable::getInstance('update');
  292. $uid = $update->find(
  293. array('element' => $this->get('element'), 'type' => 'file', 'client_id' => '', 'folder' => '')
  294. );
  295. if ($uid)
  296. {
  297. $update->delete($uid);
  298. }
  299. // And now we run the postflight
  300. ob_start();
  301. ob_implicit_flush(false);
  302. if ($this->parent->manifestClass && method_exists($this->parent->manifestClass, 'postflight'))
  303. {
  304. $this->parent->manifestClass->postflight($this->route, $this);
  305. }
  306. // Append messages
  307. $msg .= ob_get_contents();
  308. ob_end_clean();
  309. if ($msg != '')
  310. {
  311. $this->parent->set('extension_message', $msg);
  312. }
  313. return $row->get('extension_id');
  314. }
  315. /**
  316. * Custom update method
  317. *
  318. * @return boolean True on success
  319. *
  320. * @since 11.1
  321. */
  322. public function update()
  323. {
  324. // Set the overwrite setting
  325. $this->parent->setOverwrite(true);
  326. $this->parent->setUpgrade(true);
  327. $this->route = 'update';
  328. // ...and adds new files
  329. return $this->install();
  330. }
  331. /**
  332. * Custom uninstall method
  333. *
  334. * @param string $id The id of the file to uninstall
  335. *
  336. * @return boolean True on success
  337. *
  338. * @since 11.1
  339. */
  340. public function uninstall($id)
  341. {
  342. // Initialise variables.
  343. $row = JTable::getInstance('extension');
  344. if (!$row->load($id))
  345. {
  346. JError::raiseWarning(100, JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_LOAD_ENTRY'));
  347. return false;
  348. }
  349. if ($row->protected)
  350. {
  351. JError::raiseWarning(100, JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_WARNCOREFILE'));
  352. return false;
  353. }
  354. $retval = true;
  355. $manifestFile = JPATH_MANIFESTS . '/files/' . $row->element . '.xml';
  356. // Because files may not have their own folders we cannot use the standard method of finding an installation manifest
  357. if (file_exists($manifestFile))
  358. {
  359. // Set the plugin root path
  360. // @todo remove code: . '/files/' . $manifest->filename);
  361. $this->parent->setPath('extension_root', JPATH_ROOT);
  362. $xml = JFactory::getXML($manifestFile);
  363. // If we cannot load the XML file return null
  364. if (!$xml)
  365. {
  366. JError::raiseWarning(100, JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_LOAD_MANIFEST'));
  367. return false;
  368. }
  369. /*
  370. * Check for a valid XML root tag.
  371. */
  372. if ($xml->getName() != 'extension')
  373. {
  374. JError::raiseWarning(100, JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_INVALID_MANIFEST'));
  375. return false;
  376. }
  377. $this->manifest = $xml;
  378. // If there is an manifest class file, let's load it
  379. $this->scriptElement = $this->manifest->scriptfile;
  380. $manifestScript = (string) $this->manifest->scriptfile;
  381. if ($manifestScript)
  382. {
  383. $manifestScriptFile = $this->parent->getPath('extension_root') . '/' . $manifestScript;
  384. if (is_file($manifestScriptFile))
  385. {
  386. // Load the file
  387. include_once $manifestScriptFile;
  388. }
  389. // Set the class name
  390. $classname = $row->element . 'InstallerScript';
  391. if (class_exists($classname))
  392. {
  393. // Create a new instance
  394. $this->parent->manifestClass = new $classname($this);
  395. // And set this so we can copy it later
  396. $this->set('manifest_script', $manifestScript);
  397. // Note: if we don't find the class, don't bother to copy the file
  398. }
  399. }
  400. ob_start();
  401. ob_implicit_flush(false);
  402. // Run uninstall if possible
  403. if ($this->parent->manifestClass && method_exists($this->parent->manifestClass, 'uninstall'))
  404. {
  405. $this->parent->manifestClass->uninstall($this);
  406. }
  407. $msg = ob_get_contents();
  408. ob_end_clean();
  409. /*
  410. * Let's run the uninstall queries for the component
  411. * If Joomla 1.5 compatible, with discreet sql files - execute appropriate
  412. * file for utf-8 support or non-utf support
  413. */
  414. // Try for Joomla 1.5 type queries
  415. // Second argument is the utf compatible version attribute
  416. $utfresult = $this->parent->parseSQLFiles($this->manifest->uninstall->sql);
  417. if ($utfresult === false)
  418. {
  419. // Install failed, rollback changes
  420. JError::raiseWarning(100, JText::sprintf('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_SQL_ERROR', $db->stderr(true)));
  421. $retval = false;
  422. }
  423. // Remove the schema version
  424. $db = JFactory::getDbo();
  425. $query = $db->getQuery(true);
  426. $query->delete()
  427. ->from('#__schemas')
  428. ->where('extension_id = ' . $row->extension_id);
  429. $db->setQuery($query);
  430. $db->Query();
  431. // Set root folder names
  432. $packagePath = $this->parent->getPath('source');
  433. $jRootPath = JPath::clean(JPATH_ROOT);
  434. // Loop through all elements and get list of files and folders
  435. foreach ($xml->fileset->files as $eFiles)
  436. {
  437. $folder = (string) $eFiles->attributes()->folder;
  438. $target = (string) $eFiles->attributes()->target;
  439. // Create folder path
  440. if (empty($target))
  441. {
  442. $targetFolder = JPATH_ROOT;
  443. }
  444. else
  445. {
  446. $targetFolder = JPATH_ROOT . '/' . $target;
  447. }
  448. $folderList = array();
  449. // Check if all children exists
  450. if (count($eFiles->children()) > 0)
  451. {
  452. // Loop through all filenames elements
  453. foreach ($eFiles->children() as $eFileName)
  454. {
  455. if ($eFileName->getName() == 'folder')
  456. {
  457. $folderList[] = $targetFolder . '/' . $eFileName;
  458. }
  459. else
  460. {
  461. $fileName = $targetFolder . '/' . $eFileName;
  462. JFile::delete($fileName);
  463. }
  464. }
  465. }
  466. // Delete any folders that don't have any content in them.
  467. foreach ($folderList as $folder)
  468. {
  469. $files = JFolder::files($folder);
  470. if (!count($files))
  471. {
  472. JFolder::delete($folder);
  473. }
  474. }
  475. }
  476. JFile::delete($manifestFile);
  477. }
  478. else
  479. {
  480. JError::raiseWarning(100, JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_INVALID_NOTFOUND_MANIFEST'));
  481. // Delete the row because its broken
  482. $row->delete();
  483. return false;
  484. }
  485. $this->parent->removeFiles($xml->languages);
  486. $row->delete();
  487. return $retval;
  488. }
  489. /**
  490. * Function used to check if extension is already installed
  491. *
  492. * @param string $extension The element name of the extension to install
  493. *
  494. * @return boolean True if extension exists
  495. *
  496. * @since 11.1
  497. */
  498. protected function extensionExistsInSystem($extension = null)
  499. {
  500. // Get a database connector object
  501. $db = $this->parent->getDBO();
  502. $query = $db->getQuery(true);
  503. $query->select($query->qn('extension_id'))
  504. ->from($query->qn('#__extensions'));
  505. $query->where($query->qn('type') . ' = ' . $query->q('file'))
  506. ->where($query->qn('element') . ' = ' . $query->q($extension));
  507. $db->setQuery($query);
  508. try
  509. {
  510. $db->Query();
  511. }
  512. catch (JException $e)
  513. {
  514. // Install failed, roll back changes
  515. $this->parent->abort(JText::sprintf('JLIB_INSTALLER_ABORT_FILE_ROLLBACK', $db->stderr(true)));
  516. return false;
  517. }
  518. $id = $db->loadResult();
  519. if (empty($id))
  520. {
  521. return false;
  522. }
  523. return true;
  524. }
  525. /**
  526. * Function used to populate files and folder list
  527. *
  528. * @return boolean none
  529. *
  530. * @since 11.1
  531. */
  532. protected function populateFilesAndFolderList()
  533. {
  534. // Initialise variable
  535. $this->folderList = array();
  536. $this->fileList = array();
  537. // Get fileset
  538. $eFileset = $this->manifest->fileset->files;
  539. // Set root folder names
  540. $packagePath = $this->parent->getPath('source');
  541. $jRootPath = JPath::clean(JPATH_ROOT);
  542. // Loop through all elements and get list of files and folders
  543. foreach ($this->manifest->fileset->files as $eFiles)
  544. {
  545. // Check if the element is files element
  546. $folder = (string) $eFiles->attributes()->folder;
  547. $target = (string) $eFiles->attributes()->target;
  548. // Split folder names into array to get folder names. This will
  549. // help in creating folders
  550. $arrList = preg_split("#/|\\/#", $target);
  551. $folderName = $jRootPath;
  552. foreach ($arrList as $dir)
  553. {
  554. if (empty($dir))
  555. {
  556. continue;
  557. }
  558. $folderName .= '/' . $dir;
  559. // Check if folder exists, if not then add to the array for folder creation
  560. if (!JFolder::exists($folderName))
  561. {
  562. array_push($this->folderList, $folderName);
  563. }
  564. }
  565. // Create folder path
  566. $sourceFolder = empty($folder) ? $packagePath : $packagePath . '/' . $folder;
  567. $targetFolder = empty($target) ? $jRootPath : $jRootPath . '/' . $target;
  568. // Check if source folder exists
  569. if (!JFolder::exists($sourceFolder))
  570. {
  571. JError::raiseWarning(1, JText::sprintf('JLIB_INSTALLER_ABORT_FILE_INSTALL_FAIL_SOURCE_DIRECTORY', $sourceFolder));
  572. // If installation fails, rollback
  573. $this->parent->abort();
  574. return false;
  575. }
  576. // Check if all children exists
  577. if (count($eFiles->children()))
  578. {
  579. // Loop through all filenames elements
  580. foreach ($eFiles->children() as $eFileName)
  581. {
  582. $path['src'] = $sourceFolder . '/' . $eFileName;
  583. $path['dest'] = $targetFolder . '/' . $eFileName;
  584. $path['type'] = 'file';
  585. if ($eFileName->getName() == 'folder')
  586. {
  587. $folderName = $targetFolder . '/' . $eFileName;
  588. array_push($this->folderList, $folderName);
  589. $path['type'] = 'folder';
  590. }
  591. array_push($this->fileList, $path);
  592. }
  593. }
  594. else
  595. {
  596. $files = JFolder::files($sourceFolder);
  597. foreach ($files as $file)
  598. {
  599. $path['src'] = $sourceFolder . '/' . $file;
  600. $path['dest'] = $targetFolder . '/' . $file;
  601. array_push($this->fileList, $path);
  602. }
  603. }
  604. }
  605. }
  606. /**
  607. * Refreshes the extension table cache
  608. *
  609. * @return boolean result of operation, true if updated, false on failure
  610. *
  611. * @since 11.1
  612. */
  613. public function refreshManifestCache()
  614. {
  615. // Need to find to find where the XML file is since we don't store this normally
  616. $manifestPath = JPATH_MANIFESTS . '/files/' . $this->parent->extension->element . '.xml';
  617. $this->parent->manifest = $this->parent->isManifest($manifestPath);
  618. $this->parent->setPath('manifest', $manifestPath);
  619. $manifest_details = JApplicationHelper::parseXMLInstallFile($this->parent->getPath('manifest'));
  620. $this->parent->extension->manifest_cache = json_encode($manifest_details);
  621. $this->parent->extension->name = $manifest_details['name'];
  622. try
  623. {
  624. return $this->parent->extension->store();
  625. }
  626. catch (JException $e)
  627. {
  628. JError::raiseWarning(101, JText::_('JLIB_INSTALLER_ERROR_PACK_REFRESH_MANIFEST_CACHE'));
  629. return false;
  630. }
  631. }
  632. }