PageRenderTime 56ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/joomla/installer/installer.php

https://bitbucket.org/eternaware/joomus
PHP | 2135 lines | 1178 code | 306 blank | 651 comment | 208 complexity | cb3b60c8ad131b5a47eae5595322e5bf MD5 | raw file
Possible License(s): 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.filesystem.file');
  11. jimport('joomla.filesystem.folder');
  12. jimport('joomla.filesystem.path');
  13. jimport('joomla.base.adapter');
  14. /**
  15. * Joomla base installer class
  16. *
  17. * @package Joomla.Platform
  18. * @subpackage Installer
  19. * @since 11.1
  20. */
  21. class JInstaller extends JAdapter
  22. {
  23. /**
  24. * Array of paths needed by the installer
  25. *
  26. * @var array
  27. * @since 12.1
  28. */
  29. protected $paths = array();
  30. /**
  31. * True if package is an upgrade
  32. *
  33. * @var boolean
  34. * @since 12.1
  35. */
  36. protected $upgrade = null;
  37. /**
  38. * The manifest trigger class
  39. *
  40. * @var object
  41. * @since 11.1
  42. */
  43. public $manifestClass = null;
  44. /**
  45. * True if existing files can be overwritten
  46. * @var boolean
  47. * @since 12.1
  48. */
  49. protected $overwrite = false;
  50. /**
  51. * Stack of installation steps
  52. * - Used for installation rollback
  53. *
  54. * @var array
  55. * @since 12.1
  56. */
  57. protected $stepStack = array();
  58. /**
  59. * Extension Table Entry
  60. *
  61. * @var JTableExtension
  62. * @since 11.1
  63. */
  64. public $extension = null;
  65. /**
  66. * The output from the install/uninstall scripts
  67. *
  68. * @var string
  69. * @since 11.1
  70. * */
  71. public $message = null;
  72. /**
  73. * The installation manifest XML object
  74. *
  75. * @var object
  76. * @since 11.1
  77. */
  78. public $manifest = null;
  79. /**
  80. * The extension message that appears
  81. *
  82. * @var string
  83. * @since 11.1
  84. */
  85. protected $extension_message = null;
  86. /**
  87. * The redirect URL if this extension (can be null if no redirect)
  88. *
  89. * @var string
  90. * @since 11.1
  91. */
  92. protected $redirect_url = null;
  93. /**
  94. * @var JInstaller JInstaller instance container.
  95. * @since 11.3
  96. */
  97. protected static $instance;
  98. /**
  99. * Constructor
  100. *
  101. * @since 11.1
  102. */
  103. public function __construct()
  104. {
  105. parent::__construct(__DIR__, 'JInstaller');
  106. }
  107. /**
  108. * Returns the global Installer object, only creating it
  109. * if it doesn't already exist.
  110. *
  111. * @return JInstaller An installer object
  112. *
  113. * @since 11.1
  114. */
  115. public static function getInstance()
  116. {
  117. if (!isset(self::$instance))
  118. {
  119. self::$instance = new JInstaller;
  120. }
  121. return self::$instance;
  122. }
  123. /**
  124. * Get the allow overwrite switch
  125. *
  126. * @return boolean Allow overwrite switch
  127. *
  128. * @since 11.4
  129. */
  130. public function isOverwrite()
  131. {
  132. return $this->overwrite;
  133. }
  134. /**
  135. * Set the allow overwrite switch
  136. *
  137. * @param boolean $state Overwrite switch state
  138. *
  139. * @return boolean True it state is set, false if it is not
  140. *
  141. * @since 11.1
  142. */
  143. public function setOverwrite($state = false)
  144. {
  145. $tmp = $this->overwrite;
  146. if ($state)
  147. {
  148. $this->overwrite = true;
  149. }
  150. else
  151. {
  152. $this->overwrite = false;
  153. }
  154. return $tmp;
  155. }
  156. /**
  157. * Get the redirect location
  158. *
  159. * @return string Redirect location (or null)
  160. *
  161. * @since 11.1
  162. */
  163. public function getRedirectURL()
  164. {
  165. return $this->redirect_url;
  166. }
  167. /**
  168. * Set the redirect location
  169. *
  170. * @param string $newurl New redirect location
  171. *
  172. * @return void
  173. *
  174. * @since 11.1
  175. */
  176. public function setRedirectURL($newurl)
  177. {
  178. $this->redirect_url = $newurl;
  179. }
  180. /**
  181. * Get the upgrade switch
  182. *
  183. * @return boolean
  184. *
  185. * @since 11.4
  186. */
  187. public function isUpgrade()
  188. {
  189. return $this->upgrade;
  190. }
  191. /**
  192. * Set the upgrade switch
  193. *
  194. * @param boolean $state Upgrade switch state
  195. *
  196. * @return boolean True if upgrade, false otherwise
  197. *
  198. * @since 11.1
  199. */
  200. public function setUpgrade($state = false)
  201. {
  202. $tmp = $this->upgrade;
  203. if ($state)
  204. {
  205. $this->upgrade = true;
  206. }
  207. else
  208. {
  209. $this->upgrade = false;
  210. }
  211. return $tmp;
  212. }
  213. /**
  214. * Get the installation manifest object
  215. *
  216. * @return object Manifest object
  217. *
  218. * @since 11.1
  219. */
  220. public function getManifest()
  221. {
  222. if (!is_object($this->manifest))
  223. {
  224. $this->findManifest();
  225. }
  226. return $this->manifest;
  227. }
  228. /**
  229. * Get an installer path by name
  230. *
  231. * @param string $name Path name
  232. * @param string $default Default value
  233. *
  234. * @return string Path
  235. *
  236. * @since 11.1
  237. */
  238. public function getPath($name, $default = null)
  239. {
  240. return (!empty($this->paths[$name])) ? $this->paths[$name] : $default;
  241. }
  242. /**
  243. * Sets an installer path by name
  244. *
  245. * @param string $name Path name
  246. * @param string $value Path
  247. *
  248. * @return void
  249. *
  250. * @since 11.1
  251. */
  252. public function setPath($name, $value)
  253. {
  254. $this->paths[$name] = $value;
  255. }
  256. /**
  257. * Pushes a step onto the installer stack for rolling back steps
  258. *
  259. * @param array $step Installer step
  260. *
  261. * @return void
  262. *
  263. * @since 11.1
  264. */
  265. public function pushStep($step)
  266. {
  267. $this->stepStack[] = $step;
  268. }
  269. /**
  270. * Installation abort method
  271. *
  272. * @param string $msg Abort message from the installer
  273. * @param string $type Package type if defined
  274. *
  275. * @return boolean True if successful
  276. *
  277. * @since 11.1
  278. * @throws RuntimeException
  279. */
  280. public function abort($msg = null, $type = null)
  281. {
  282. $retval = true;
  283. $step = array_pop($this->stepStack);
  284. // Raise abort warning
  285. if ($msg)
  286. {
  287. JLog::add($msg, JLog::WARNING, 'jerror');
  288. }
  289. while ($step != null)
  290. {
  291. switch ($step['type'])
  292. {
  293. case 'file':
  294. // Remove the file
  295. $stepval = JFile::delete($step['path']);
  296. break;
  297. case 'folder':
  298. // Remove the folder
  299. $stepval = JFolder::delete($step['path']);
  300. break;
  301. case 'query':
  302. // Placeholder in case this is necessary in the future
  303. // $stepval is always false because if this step was called it invariably failed
  304. $stepval = false;
  305. break;
  306. case 'extension':
  307. // Get database connector object
  308. $db = $this->getDBO();
  309. $query = $db->getQuery(true);
  310. // Remove the entry from the #__extensions table
  311. $query->delete($db->quoteName('#__extensions'));
  312. $query->where($db->quoteName('extension_id') . ' = ' . (int) $step['id']);
  313. $db->setQuery($query);
  314. $stepval = $db->execute();
  315. break;
  316. default:
  317. if ($type && is_object($this->_adapters[$type]))
  318. {
  319. // Build the name of the custom rollback method for the type
  320. $method = '_rollback_' . $step['type'];
  321. // Custom rollback method handler
  322. if (method_exists($this->_adapters[$type], $method))
  323. {
  324. $stepval = $this->_adapters[$type]->$method($step);
  325. }
  326. }
  327. else
  328. {
  329. // Set it to false
  330. $stepval = false;
  331. }
  332. break;
  333. }
  334. // Only set the return value if it is false
  335. if ($stepval === false)
  336. {
  337. $retval = false;
  338. }
  339. // Get the next step and continue
  340. $step = array_pop($this->stepStack);
  341. }
  342. $conf = JFactory::getConfig();
  343. $debug = $conf->get('debug');
  344. if ($debug)
  345. {
  346. throw new RuntimeException('Installation unexpectedly terminated: ' . $msg, 500);
  347. }
  348. return $retval;
  349. }
  350. // Adapter functions
  351. /**
  352. * Package installation method
  353. *
  354. * @param string $path Path to package source folder
  355. *
  356. * @return boolean True if successful
  357. *
  358. * @since 11.1
  359. */
  360. public function install($path = null)
  361. {
  362. if ($path && JFolder::exists($path))
  363. {
  364. $this->setPath('source', $path);
  365. }
  366. else
  367. {
  368. $this->abort(JText::_('JLIB_INSTALLER_ABORT_NOINSTALLPATH'));
  369. return false;
  370. }
  371. if (!$this->setupInstall())
  372. {
  373. $this->abort(JText::_('JLIB_INSTALLER_ABORT_DETECTMANIFEST'));
  374. return false;
  375. }
  376. $type = (string) $this->manifest->attributes()->type;
  377. if (is_object($this->_adapters[$type]))
  378. {
  379. // Add the languages from the package itself
  380. if (method_exists($this->_adapters[$type], 'loadLanguage'))
  381. {
  382. $this->_adapters[$type]->loadLanguage($path);
  383. }
  384. // Fire the onExtensionBeforeInstall event.
  385. JPluginHelper::importPlugin('extension');
  386. $dispatcher = JEventDispatcher::getInstance();
  387. $dispatcher->trigger(
  388. 'onExtensionBeforeInstall',
  389. array('method' => 'install', 'type' => $type, 'manifest' => $this->manifest, 'extension' => 0)
  390. );
  391. // Run the install
  392. $result = $this->_adapters[$type]->install();
  393. // Fire the onExtensionAfterInstall
  394. $dispatcher->trigger(
  395. 'onExtensionAfterInstall',
  396. array('installer' => clone $this, 'eid' => $result)
  397. );
  398. if ($result !== false)
  399. {
  400. return true;
  401. }
  402. else
  403. {
  404. return false;
  405. }
  406. }
  407. return false;
  408. }
  409. /**
  410. * Discovered package installation method
  411. *
  412. * @param integer $eid Extension ID
  413. *
  414. * @return boolean True if successful
  415. *
  416. * @since 11.1
  417. */
  418. public function discover_install($eid = null)
  419. {
  420. if ($eid)
  421. {
  422. $this->extension = JTable::getInstance('extension');
  423. if (!$this->extension->load($eid))
  424. {
  425. $this->abort(JText::_('JLIB_INSTALLER_ABORT_LOAD_DETAILS'));
  426. return false;
  427. }
  428. if ($this->extension->state != -1)
  429. {
  430. $this->abort(JText::_('JLIB_INSTALLER_ABORT_ALREADYINSTALLED'));
  431. return false;
  432. }
  433. // Lazy load the adapter
  434. if (!isset($this->_adapters[$this->extension->type]) || !is_object($this->_adapters[$this->extension->type]))
  435. {
  436. if (!$this->setAdapter($this->extension->type))
  437. {
  438. return false;
  439. }
  440. }
  441. if (is_object($this->_adapters[$this->extension->type]))
  442. {
  443. if (method_exists($this->_adapters[$this->extension->type], 'discover_install'))
  444. {
  445. // Add the languages from the package itself
  446. if (method_exists($this->_adapters[$this->extension->type], 'loadLanguage'))
  447. {
  448. $this->_adapters[$this->extension->type]->loadLanguage();
  449. }
  450. // Fire the onExtensionBeforeInstall event.
  451. JPluginHelper::importPlugin('extension');
  452. $dispatcher = JEventDispatcher::getInstance();
  453. $dispatcher->trigger(
  454. 'onExtensionBeforeInstall',
  455. array(
  456. 'method' => 'discover_install',
  457. 'type' => $this->extension->get('type'),
  458. 'manifest' => null,
  459. 'extension' => $this->extension->get('extension_id')
  460. )
  461. );
  462. // Run the install
  463. $result = $this->_adapters[$this->extension->type]->discover_install();
  464. // Fire the onExtensionAfterInstall
  465. $dispatcher->trigger(
  466. 'onExtensionAfterInstall',
  467. array('installer' => clone $this, 'eid' => $result)
  468. );
  469. if ($result !== false)
  470. {
  471. return true;
  472. }
  473. else
  474. {
  475. return false;
  476. }
  477. }
  478. else
  479. {
  480. $this->abort(JText::_('JLIB_INSTALLER_ABORT_METHODNOTSUPPORTED'));
  481. return false;
  482. }
  483. }
  484. return false;
  485. }
  486. else
  487. {
  488. $this->abort(JText::_('JLIB_INSTALLER_ABORT_EXTENSIONNOTVALID'));
  489. return false;
  490. }
  491. }
  492. /**
  493. * Extension discover method
  494. * Asks each adapter to find extensions
  495. *
  496. * @return array JExtension
  497. *
  498. * @since 11.1
  499. */
  500. public function discover()
  501. {
  502. $this->loadAllAdapters();
  503. $results = array();
  504. foreach ($this->_adapters as $adapter)
  505. {
  506. // Joomla! 1.5 installation adapter legacy support
  507. if (method_exists($adapter, 'discover'))
  508. {
  509. $tmp = $adapter->discover();
  510. // If its an array and has entries
  511. if (is_array($tmp) && count($tmp))
  512. {
  513. // Merge it into the system
  514. $results = array_merge($results, $tmp);
  515. }
  516. }
  517. }
  518. return $results;
  519. }
  520. /**
  521. * Package update method
  522. *
  523. * @param string $path Path to package source folder
  524. *
  525. * @return boolean True if successful
  526. *
  527. * @since 11.1
  528. */
  529. public function update($path = null)
  530. {
  531. if ($path && JFolder::exists($path))
  532. {
  533. $this->setPath('source', $path);
  534. }
  535. else
  536. {
  537. $this->abort(JText::_('JLIB_INSTALLER_ABORT_NOUPDATEPATH'));
  538. }
  539. if (!$this->setupInstall())
  540. {
  541. return $this->abort(JText::_('JLIB_INSTALLER_ABORT_DETECTMANIFEST'));
  542. }
  543. $type = (string) $this->manifest->attributes()->type;
  544. if (is_object($this->_adapters[$type]))
  545. {
  546. // Add the languages from the package itself
  547. if (method_exists($this->_adapters[$type], 'loadLanguage'))
  548. {
  549. $this->_adapters[$type]->loadLanguage($path);
  550. }
  551. // Fire the onExtensionBeforeUpdate event.
  552. JPluginHelper::importPlugin('extension');
  553. $dispatcher = JEventDispatcher::getInstance();
  554. $dispatcher->trigger('onExtensionBeforeUpdate', array('type' => $type, 'manifest' => $this->manifest));
  555. // Run the update
  556. $result = $this->_adapters[$type]->update();
  557. // Fire the onExtensionAfterUpdate
  558. $dispatcher->trigger(
  559. 'onExtensionAfterUpdate',
  560. array('installer' => clone $this, 'eid' => $result)
  561. );
  562. if ($result !== false)
  563. {
  564. return true;
  565. }
  566. else
  567. {
  568. return false;
  569. }
  570. }
  571. return false;
  572. }
  573. /**
  574. * Package uninstallation method
  575. *
  576. * @param string $type Package type
  577. * @param mixed $identifier Package identifier for adapter
  578. * @param integer $cid Application ID; deprecated in 1.6
  579. *
  580. * @return boolean True if successful
  581. *
  582. * @since 11.1
  583. */
  584. public function uninstall($type, $identifier, $cid = 0)
  585. {
  586. if (!isset($this->_adapters[$type]) || !is_object($this->_adapters[$type]))
  587. {
  588. if (!$this->setAdapter($type))
  589. {
  590. // We failed to get the right adapter
  591. return false;
  592. }
  593. }
  594. if (is_object($this->_adapters[$type]))
  595. {
  596. // We don't load languages here, we get the extension adapter to work it out
  597. // Fire the onExtensionBeforeUninstall event.
  598. JPluginHelper::importPlugin('extension');
  599. $dispatcher = JEventDispatcher::getInstance();
  600. $dispatcher->trigger('onExtensionBeforeUninstall', array('eid' => $identifier));
  601. // Run the uninstall
  602. $result = $this->_adapters[$type]->uninstall($identifier);
  603. // Fire the onExtensionAfterInstall
  604. $dispatcher->trigger(
  605. 'onExtensionAfterUninstall',
  606. array('installer' => clone $this, 'eid' => $identifier, 'result' => $result)
  607. );
  608. return $result;
  609. }
  610. return false;
  611. }
  612. /**
  613. * Refreshes the manifest cache stored in #__extensions
  614. *
  615. * @param integer $eid Extension ID
  616. *
  617. * @return mixed void on success, false on error @todo missing return value ?
  618. *
  619. * @since 11.1
  620. */
  621. public function refreshManifestCache($eid)
  622. {
  623. if ($eid)
  624. {
  625. $this->extension = JTable::getInstance('extension');
  626. if (!$this->extension->load($eid))
  627. {
  628. $this->abort(JText::_('JLIB_INSTALLER_ABORT_LOAD_DETAILS'));
  629. return false;
  630. }
  631. if ($this->extension->state == -1)
  632. {
  633. $this->abort(JText::_('JLIB_INSTALLER_ABORT_REFRESH_MANIFEST_CACHE'));
  634. return false;
  635. }
  636. // Lazy load the adapter
  637. if (!isset($this->_adapters[$this->extension->type]) || !is_object($this->_adapters[$this->extension->type]))
  638. {
  639. if (!$this->setAdapter($this->extension->type))
  640. {
  641. return false;
  642. }
  643. }
  644. if (is_object($this->_adapters[$this->extension->type]))
  645. {
  646. if (method_exists($this->_adapters[$this->extension->type], 'refreshManifestCache'))
  647. {
  648. $result = $this->_adapters[$this->extension->type]->refreshManifestCache();
  649. if ($result !== false)
  650. {
  651. return true;
  652. }
  653. else
  654. {
  655. return false;
  656. }
  657. }
  658. else
  659. {
  660. $this->abort(JText::sprintf('JLIB_INSTALLER_ABORT_METHODNOTSUPPORTED_TYPE', $this->extension->type));
  661. return false;
  662. }
  663. }
  664. return false;
  665. }
  666. else
  667. {
  668. $this->abort(JText::_('JLIB_INSTALLER_ABORT_REFRESH_MANIFEST_CACHE_VALID'));
  669. return false;
  670. }
  671. }
  672. // Utility functions
  673. /**
  674. * Prepare for installation: this method sets the installation directory, finds
  675. * and checks the installation file and verifies the installation type.
  676. *
  677. * @return boolean True on success
  678. *
  679. * @since 11.1
  680. */
  681. public function setupInstall()
  682. {
  683. // We need to find the installation manifest file
  684. if (!$this->findManifest())
  685. {
  686. return false;
  687. }
  688. // Load the adapter(s) for the install manifest
  689. $type = (string) $this->manifest->attributes()->type;
  690. // Lazy load the adapter
  691. if (!isset($this->_adapters[$type]) || !is_object($this->_adapters[$type]))
  692. {
  693. if (!$this->setAdapter($type))
  694. {
  695. return false;
  696. }
  697. }
  698. return true;
  699. }
  700. /**
  701. * Backward compatible method to parse through a queries element of the
  702. * installation manifest file and take appropriate action.
  703. *
  704. * @param SimpleXMLElement $element The XML node to process
  705. *
  706. * @return mixed Number of queries processed or False on error
  707. *
  708. * @since 11.1
  709. */
  710. public function parseQueries($element)
  711. {
  712. // Get the database connector object
  713. $db = & $this->_db;
  714. if (!$element || !count($element->children()))
  715. {
  716. // Either the tag does not exist or has no children therefore we return zero files processed.
  717. return 0;
  718. }
  719. // Get the array of query nodes to process
  720. $queries = $element->children();
  721. if (count($queries) == 0)
  722. {
  723. // No queries to process
  724. return 0;
  725. }
  726. // Process each query in the $queries array (children of $tagName).
  727. foreach ($queries as $query)
  728. {
  729. $db->setQuery($query->data());
  730. if (!$db->execute())
  731. {
  732. JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR', $db->stderr(true)), JLog::WARNING, 'jerror');
  733. return false;
  734. }
  735. }
  736. return (int) count($queries);
  737. }
  738. /**
  739. * Method to extract the name of a discreet installation sql file from the installation manifest file.
  740. *
  741. * @param object $element The XML node to process
  742. *
  743. * @return mixed Number of queries processed or False on error
  744. *
  745. * @since 11.1
  746. */
  747. public function parseSQLFiles($element)
  748. {
  749. if (!$element || !count($element->children()))
  750. {
  751. // The tag does not exist.
  752. return 0;
  753. }
  754. $queries = array();
  755. $db = & $this->_db;
  756. $dbDriver = strtolower($db->name);
  757. if ($dbDriver == 'mysqli')
  758. {
  759. $dbDriver = 'mysql';
  760. }
  761. // Get the name of the sql file to process
  762. foreach ($element->children() as $file)
  763. {
  764. $fCharset = (strtolower($file->attributes()->charset) == 'utf8') ? 'utf8' : '';
  765. $fDriver = strtolower($file->attributes()->driver);
  766. if ($fDriver == 'mysqli')
  767. {
  768. $fDriver = 'mysql';
  769. }
  770. if ($fCharset == 'utf8' && $fDriver == $dbDriver)
  771. {
  772. $sqlfile = $this->getPath('extension_root') . '/' . $file;
  773. // Check that sql files exists before reading. Otherwise raise error for rollback
  774. if (!file_exists($sqlfile))
  775. {
  776. JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR', $db->stderr(true)), JLog::WARNING, 'jerror');
  777. return false;
  778. }
  779. $buffer = file_get_contents($sqlfile);
  780. // Graceful exit and rollback if read not successful
  781. if ($buffer === false)
  782. {
  783. JLog::add(JText::_('JLIB_INSTALLER_ERROR_SQL_READBUFFER'), JLog::WARNING, 'jerror');
  784. return false;
  785. }
  786. // Create an array of queries from the sql file
  787. $queries = JDatabaseDriver::splitSql($buffer);
  788. if (count($queries) == 0)
  789. {
  790. // No queries to process
  791. return 0;
  792. }
  793. // Process each query in the $queries array (split out of sql file).
  794. foreach ($queries as $query)
  795. {
  796. $query = trim($query);
  797. if ($query != '' && $query{0} != '#')
  798. {
  799. $db->setQuery($query);
  800. if (!$db->execute())
  801. {
  802. JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR', $db->stderr(true)), JLog::WARNING, 'jerror');
  803. return false;
  804. }
  805. }
  806. }
  807. }
  808. }
  809. return (int) count($queries);
  810. }
  811. /**
  812. * Set the schema version for an extension by looking at its latest update
  813. *
  814. * @param SimpleXMLElement $schema Schema Tag
  815. * @param integer $eid Extension ID
  816. *
  817. * @return void
  818. *
  819. * @since 11.1
  820. */
  821. public function setSchemaVersion($schema, $eid)
  822. {
  823. if ($eid && $schema)
  824. {
  825. $db = JFactory::getDBO();
  826. $schemapaths = $schema->children();
  827. if (!$schemapaths)
  828. {
  829. return;
  830. }
  831. if (count($schemapaths))
  832. {
  833. $dbDriver = strtolower($db->name);
  834. if ($dbDriver == 'mysqli')
  835. {
  836. $dbDriver = 'mysql';
  837. }
  838. $schemapath = '';
  839. foreach ($schemapaths as $entry)
  840. {
  841. $attrs = $entry->attributes();
  842. if ($attrs['type'] == $dbDriver)
  843. {
  844. $schemapath = $entry;
  845. break;
  846. }
  847. }
  848. if (strlen($schemapath))
  849. {
  850. $files = str_replace('.sql', '', JFolder::files($this->getPath('extension_root') . '/' . $schemapath, '\.sql$'));
  851. usort($files, 'version_compare');
  852. // Update the database
  853. $query = $db->getQuery(true);
  854. $query->delete()
  855. ->from('#__schemas')
  856. ->where('extension_id = ' . $eid);
  857. $db->setQuery($query);
  858. if ($db->execute())
  859. {
  860. $query->clear();
  861. $query->insert($db->quoteName('#__schemas'));
  862. $query->columns(array($db->quoteName('extension_id'), $db->quoteName('version_id')));
  863. $query->values($eid . ', ' . $db->quote(end($files)));
  864. $db->setQuery($query);
  865. $db->execute();
  866. }
  867. }
  868. }
  869. }
  870. }
  871. /**
  872. * Method to process the updates for an item
  873. *
  874. * @param SimpleXMLElement $schema The XML node to process
  875. * @param integer $eid Extension Identifier
  876. *
  877. * @return boolean Result of the operations
  878. *
  879. * @since 11.1
  880. */
  881. public function parseSchemaUpdates($schema, $eid)
  882. {
  883. $files = array();
  884. $update_count = 0;
  885. // Ensure we have an XML element and a valid extension id
  886. if ($eid && $schema)
  887. {
  888. $db = JFactory::getDBO();
  889. $schemapaths = $schema->children();
  890. if (count($schemapaths))
  891. {
  892. $dbDriver = strtolower($db->name);
  893. if ($dbDriver == 'mysqli')
  894. {
  895. $dbDriver = 'mysql';
  896. }
  897. $schemapath = '';
  898. foreach ($schemapaths as $entry)
  899. {
  900. $attrs = $entry->attributes();
  901. if ($attrs['type'] == $dbDriver)
  902. {
  903. $schemapath = $entry;
  904. break;
  905. }
  906. }
  907. if (strlen($schemapath))
  908. {
  909. $files = str_replace('.sql', '', JFolder::files($this->getPath('extension_root') . '/' . $schemapath, '\.sql$'));
  910. usort($files, 'version_compare');
  911. if (!count($files))
  912. {
  913. return false;
  914. }
  915. $query = $db->getQuery(true);
  916. $query->select('version_id')
  917. ->from('#__schemas')
  918. ->where('extension_id = ' . $eid);
  919. $db->setQuery($query);
  920. $version = $db->loadResult();
  921. if ($version)
  922. {
  923. // We have a version!
  924. foreach ($files as $file)
  925. {
  926. if (version_compare($file, $version) > 0)
  927. {
  928. $buffer = file_get_contents($this->getPath('extension_root') . '/' . $schemapath . '/' . $file . '.sql');
  929. // Graceful exit and rollback if read not successful
  930. if ($buffer === false)
  931. {
  932. JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_SQL_READBUFFER'), JLog::WARNING, 'jerror');
  933. return false;
  934. }
  935. // Create an array of queries from the sql file
  936. $queries = JDatabaseDriver::splitSql($buffer);
  937. if (count($queries) == 0)
  938. {
  939. // No queries to process
  940. continue;
  941. }
  942. // Process each query in the $queries array (split out of sql file).
  943. foreach ($queries as $query)
  944. {
  945. $query = trim($query);
  946. if ($query != '' && $query{0} != '#')
  947. {
  948. $db->setQuery($query);
  949. if (!$db->execute())
  950. {
  951. JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR', $db->stderr(true)), JLog::WARNING, 'jerror');
  952. return false;
  953. }
  954. $update_count++;
  955. }
  956. }
  957. }
  958. }
  959. }
  960. // Update the database
  961. $query = $db->getQuery(true);
  962. $query->delete()
  963. ->from('#__schemas')
  964. ->where('extension_id = ' . $eid);
  965. $db->setQuery($query);
  966. if ($db->execute())
  967. {
  968. $query->clear();
  969. $query->insert($db->quoteName('#__schemas'));
  970. $query->columns(array($db->quoteName('extension_id'), $db->quoteName('version_id')));
  971. $query->values($eid . ', ' . $db->quote(end($files)));
  972. $db->setQuery($query);
  973. $db->execute();
  974. }
  975. }
  976. }
  977. }
  978. return $update_count;
  979. }
  980. /**
  981. * Method to parse through a files element of the installation manifest and take appropriate
  982. * action.
  983. *
  984. * @param SimpleXMLElement $element The XML node to process
  985. * @param integer $cid Application ID of application to install to
  986. * @param array $oldFiles List of old files (SimpleXMLElement's)
  987. * @param array $oldMD5 List of old MD5 sums (indexed by filename with value as MD5)
  988. *
  989. * @return boolean True on success
  990. *
  991. * @since 11.1
  992. */
  993. public function parseFiles($element, $cid = 0, $oldFiles = null, $oldMD5 = null)
  994. {
  995. // Get the array of file nodes to process; we checked whether this had children above.
  996. if (!$element || !count($element->children()))
  997. {
  998. // Either the tag does not exist or has no children (hence no files to process) therefore we return zero files processed.
  999. return 0;
  1000. }
  1001. $copyfiles = array();
  1002. // Get the client info
  1003. $client = JApplicationHelper::getClientInfo($cid);
  1004. /*
  1005. * Here we set the folder we are going to remove the files from.
  1006. */
  1007. if ($client)
  1008. {
  1009. $pathname = 'extension_' . $client->name;
  1010. $destination = $this->getPath($pathname);
  1011. }
  1012. else
  1013. {
  1014. $pathname = 'extension_root';
  1015. $destination = $this->getPath($pathname);
  1016. }
  1017. /*
  1018. * Here we set the folder we are going to copy the files from.
  1019. *
  1020. * Does the element have a folder attribute?
  1021. *
  1022. * If so this indicates that the files are in a subdirectory of the source
  1023. * folder and we should append the folder attribute to the source path when
  1024. * copying files.
  1025. */
  1026. $folder = (string) $element->attributes()->folder;
  1027. if ($folder && file_exists($this->getPath('source') . '/' . $folder))
  1028. {
  1029. $source = $this->getPath('source') . '/' . $folder;
  1030. }
  1031. else
  1032. {
  1033. $source = $this->getPath('source');
  1034. }
  1035. // Work out what files have been deleted
  1036. if ($oldFiles && ($oldFiles instanceof SimpleXMLElement))
  1037. {
  1038. $oldEntries = $oldFiles->children();
  1039. if (count($oldEntries))
  1040. {
  1041. $deletions = $this->findDeletedFiles($oldEntries, $element->children());
  1042. foreach ($deletions['folders'] as $deleted_folder)
  1043. {
  1044. JFolder::delete($destination . '/' . $deleted_folder);
  1045. }
  1046. foreach ($deletions['files'] as $deleted_file)
  1047. {
  1048. JFile::delete($destination . '/' . $deleted_file);
  1049. }
  1050. }
  1051. }
  1052. $path = array();
  1053. // Copy the MD5SUMS file if it exists
  1054. if (file_exists($source . '/MD5SUMS'))
  1055. {
  1056. $path['src'] = $source . '/MD5SUMS';
  1057. $path['dest'] = $destination . '/MD5SUMS';
  1058. $path['type'] = 'file';
  1059. $copyfiles[] = $path;
  1060. }
  1061. // Process each file in the $files array (children of $tagName).
  1062. foreach ($element->children() as $file)
  1063. {
  1064. $path['src'] = $source . '/' . $file;
  1065. $path['dest'] = $destination . '/' . $file;
  1066. // Is this path a file or folder?
  1067. $path['type'] = ($file->getName() == 'folder') ? 'folder' : 'file';
  1068. /*
  1069. * Before we can add a file to the copyfiles array we need to ensure
  1070. * that the folder we are copying our file to exits and if it doesn't,
  1071. * we need to create it.
  1072. */
  1073. if (basename($path['dest']) != $path['dest'])
  1074. {
  1075. $newdir = dirname($path['dest']);
  1076. if (!JFolder::create($newdir))
  1077. {
  1078. JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_CREATE_DIRECTORY', $newdir), JLog::WARNING, 'jerror');
  1079. return false;
  1080. }
  1081. }
  1082. // Add the file to the copyfiles array
  1083. $copyfiles[] = $path;
  1084. }
  1085. return $this->copyFiles($copyfiles);
  1086. }
  1087. /**
  1088. * Method to parse through a languages element of the installation manifest and take appropriate
  1089. * action.
  1090. *
  1091. * @param SimpleXMLElement $element The XML node to process
  1092. * @param integer $cid Application ID of application to install to
  1093. *
  1094. * @return boolean True on success
  1095. *
  1096. * @since 11.1
  1097. */
  1098. public function parseLanguages($element, $cid = 0)
  1099. {
  1100. // TODO: work out why the below line triggers 'node no longer exists' errors with files
  1101. if (!$element || !count($element->children()))
  1102. {
  1103. // Either the tag does not exist or has no children therefore we return zero files processed.
  1104. return 0;
  1105. }
  1106. $copyfiles = array();
  1107. // Get the client info
  1108. $client = JApplicationHelper::getClientInfo($cid);
  1109. // Here we set the folder we are going to copy the files to.
  1110. // 'languages' Files are copied to JPATH_BASE/language/ folder
  1111. $destination = $client->path . '/language';
  1112. /*
  1113. * Here we set the folder we are going to copy the files from.
  1114. *
  1115. * Does the element have a folder attribute?
  1116. *
  1117. * If so this indicates that the files are in a subdirectory of the source
  1118. * folder and we should append the folder attribute to the source path when
  1119. * copying files.
  1120. */
  1121. $folder = (string) $element->attributes()->folder;
  1122. if ($folder && file_exists($this->getPath('source') . '/' . $folder))
  1123. {
  1124. $source = $this->getPath('source') . '/' . $folder;
  1125. }
  1126. else
  1127. {
  1128. $source = $this->getPath('source');
  1129. }
  1130. // Process each file in the $files array (children of $tagName).
  1131. foreach ($element->children() as $file)
  1132. {
  1133. /*
  1134. * Language files go in a subfolder based on the language code, ie.
  1135. * <language tag="en-US">en-US.mycomponent.ini</language>
  1136. * would go in the en-US subdirectory of the language folder.
  1137. */
  1138. // We will only install language files where a core language pack
  1139. // already exists.
  1140. if ((string) $file->attributes()->tag != '')
  1141. {
  1142. $path['src'] = $source . '/' . $file;
  1143. if ((string) $file->attributes()->client != '')
  1144. {
  1145. // Override the client
  1146. $langclient = JApplicationHelper::getClientInfo((string) $file->attributes()->client, true);
  1147. $path['dest'] = $langclient->path . '/language/' . $file->attributes()->tag . '/' . basename((string) $file);
  1148. }
  1149. else
  1150. {
  1151. // Use the default client
  1152. $path['dest'] = $destination . '/' . $file->attributes()->tag . '/' . basename((string) $file);
  1153. }
  1154. // If the language folder is not present, then the core pack hasn't been installed... ignore
  1155. if (!JFolder::exists(dirname($path['dest'])))
  1156. {
  1157. continue;
  1158. }
  1159. }
  1160. else
  1161. {
  1162. $path['src'] = $source . '/' . $file;
  1163. $path['dest'] = $destination . '/' . $file;
  1164. }
  1165. /*
  1166. * Before we can add a file to the copyfiles array we need to ensure
  1167. * that the folder we are copying our file to exits and if it doesn't,
  1168. * we need to create it.
  1169. */
  1170. if (basename($path['dest']) != $path['dest'])
  1171. {
  1172. $newdir = dirname($path['dest']);
  1173. if (!JFolder::create($newdir))
  1174. {
  1175. JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_CREATE_DIRECTORY', $newdir), JLog::WARNING, 'jerror');
  1176. return false;
  1177. }
  1178. }
  1179. // Add the file to the copyfiles array
  1180. $copyfiles[] = $path;
  1181. }
  1182. return $this->copyFiles($copyfiles);
  1183. }
  1184. /**
  1185. * Method to parse through a media element of the installation manifest and take appropriate
  1186. * action.
  1187. *
  1188. * @param SimpleXMLElement $element The XML node to process
  1189. * @param integer $cid Application ID of application to install to
  1190. *
  1191. * @return boolean True on success
  1192. *
  1193. * @since 11.1
  1194. */
  1195. public function parseMedia($element, $cid = 0)
  1196. {
  1197. if (!$element || !count($element->children()))
  1198. {
  1199. // Either the tag does not exist or has no children therefore we return zero files processed.
  1200. return 0;
  1201. }
  1202. $copyfiles = array();
  1203. // Get the client info
  1204. $client = JApplicationHelper::getClientInfo($cid);
  1205. // Here we set the folder we are going to copy the files to.
  1206. // Default 'media' Files are copied to the JPATH_BASE/media folder
  1207. $folder = ((string) $element->attributes()->destination) ? '/' . $element->attributes()->destination : null;
  1208. $destination = JPath::clean(JPATH_ROOT . '/media' . $folder);
  1209. // Here we set the folder we are going to copy the files from.
  1210. /*
  1211. * Does the element have a folder attribute?
  1212. * If so this indicates that the files are in a subdirectory of the source
  1213. * folder and we should append the folder attribute to the source path when
  1214. * copying files.
  1215. */
  1216. $folder = (string) $element->attributes()->folder;
  1217. if ($folder && file_exists($this->getPath('source') . '/' . $folder))
  1218. {
  1219. $source = $this->getPath('source') . '/' . $folder;
  1220. }
  1221. else
  1222. {
  1223. $source = $this->getPath('source');
  1224. }
  1225. // Process each file in the $files array (children of $tagName).
  1226. foreach ($element->children() as $file)
  1227. {
  1228. $path['src'] = $source . '/' . $file;
  1229. $path['dest'] = $destination . '/' . $file;
  1230. // Is this path a file or folder?
  1231. $path['type'] = ($file->getName() == 'folder') ? 'folder' : 'file';
  1232. /*
  1233. * Before we can add a file to the copyfiles array we need to ensure
  1234. * that the folder we are copying our file to exits and if it doesn't,
  1235. * we need to create it.
  1236. */
  1237. if (basename($path['dest']) != $path['dest'])
  1238. {
  1239. $newdir = dirname($path['dest']);
  1240. if (!JFolder::create($newdir))
  1241. {
  1242. JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_CREATE_DIRECTORY', $newdir), JLog::WARNING, 'jerror');
  1243. return false;
  1244. }
  1245. }
  1246. // Add the file to the copyfiles array
  1247. $copyfiles[] = $path;
  1248. }
  1249. return $this->copyFiles($copyfiles);
  1250. }
  1251. /**
  1252. * Method to parse the parameters of an extension, build the INI
  1253. * string for its default parameters, and return the INI string.
  1254. *
  1255. * @return string INI string of parameter values
  1256. *
  1257. * @since 11.1
  1258. */
  1259. public function getParams()
  1260. {
  1261. // Validate that we have a fieldset to use
  1262. if (!isset($this->manifest->config->fields->fieldset))
  1263. {
  1264. return '{}';
  1265. }
  1266. // Getting the fieldset tags
  1267. $fieldsets = $this->manifest->config->fields->fieldset;
  1268. // Creating the data collection variable:
  1269. $ini = array();
  1270. // Iterating through the fieldsets:
  1271. foreach ($fieldsets as $fieldset)
  1272. {
  1273. if (!count($fieldset->children()))
  1274. {
  1275. // Either the tag does not exist or has no children therefore we return zero files processed.
  1276. return null;
  1277. }
  1278. // Iterating through the fields and collecting the name/default values:
  1279. foreach ($fieldset as $field)
  1280. {
  1281. // Check against the null value since otherwise default values like "0"
  1282. // cause entire parameters to be skipped.
  1283. if (($name = $field->attributes()->name) === null)
  1284. {
  1285. continue;
  1286. }
  1287. if (($value = $field->attributes()->default) === null)
  1288. {
  1289. continue;
  1290. }
  1291. $ini[(string) $name] = (string) $value;
  1292. }
  1293. }
  1294. return json_encode($ini);
  1295. }
  1296. /**
  1297. * Copyfiles
  1298. *
  1299. * Copy files from source directory to the target directory
  1300. *
  1301. * @param array $files Array with filenames
  1302. * @param boolean $overwrite True if existing files can be replaced
  1303. *
  1304. * @return boolean True on success
  1305. *
  1306. * @since 11.1
  1307. */
  1308. public function copyFiles($files, $overwrite = null)
  1309. {
  1310. /*
  1311. * To allow for manual override on the overwriting flag, we check to see if
  1312. * the $overwrite flag was set and is a boolean value. If not, use the object
  1313. * allowOverwrite flag.
  1314. */
  1315. if (is_null($overwrite) || !is_bool($overwrite))
  1316. {
  1317. $overwrite = $this->overwrite;
  1318. }
  1319. /*
  1320. * $files must be an array of filenames. Verify that it is an array with
  1321. * at least one file to copy.
  1322. */
  1323. if (is_array($files) && count($files) > 0)
  1324. {
  1325. foreach ($files as $file)
  1326. {
  1327. // Get the source and destination paths
  1328. $filesource = JPath::clean($file['src']);
  1329. $filedest = JPath::clean($file['dest']);
  1330. $filetype = array_key_exists('type', $file) ? $file['type'] : 'file';
  1331. if (!file_exists($filesource))
  1332. {
  1333. /*
  1334. * The source file does not exist. Nothing to copy so set an error
  1335. * and return false.
  1336. */
  1337. JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_NO_FILE', $filesource), JLog::WARNING, 'jerror');
  1338. return false;
  1339. }
  1340. elseif (($exists = file_exists($filedest)) && !$overwrite)
  1341. {
  1342. // It's okay if the manifest already exists
  1343. if ($this->getPath('manifest') == $filesource)
  1344. {
  1345. continue;
  1346. }
  1347. // The destination file already exists and the overwrite flag is false.
  1348. // Set an error and return false.
  1349. JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_FILE_EXISTS', $filedest), JLog::WARNING, 'jerror');
  1350. return false;
  1351. }
  1352. else
  1353. {
  1354. // Copy the folder or file to the new location.
  1355. if ($filetype == 'folder')
  1356. {
  1357. if (!(JFolder::copy($filesource, $filedest, null, $overwrite)))
  1358. {
  1359. JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_FAIL_COPY_FOLDER', $filesource, $filedest), JLog::WARNING, 'jerror');
  1360. return false;
  1361. }
  1362. $step = array('type' => 'folder', 'path' => $filedest);
  1363. }
  1364. else
  1365. {
  1366. if (!(JFile::copy($filesource, $filedest, null)))
  1367. {
  1368. JLog::add(JText::sprintf('JLIB_INSTALLER_ERROR_FAIL_COPY_FILE', $filesource, $filedest), JLog::WARNING, 'jerror');
  1369. return false;
  1370. }
  1371. $step = array('type' => 'file', 'path' => $filedest);
  1372. }
  1373. /*
  1374. * Since we copied a file/folder, we want to add it to the installation step stack so that
  1375. * in case we have to roll back the installation we can remove the files copied.
  1376. */
  1377. if (!$exists)
  1378. {
  1379. $this->stepStack[] = $step;
  1380. }
  1381. }
  1382. }
  1383. }
  1384. else
  1385. {
  1386. // The $files variable was either not an array or an empty array
  1387. return false;
  1388. }
  1389. return count($files);
  1390. }
  1391. /**
  1392. * Method to parse through a files element of the installation manifest and remove
  1393. * the files that were installed
  1394. *
  1395. * @param object $element The XML node to process
  1396. * @param integer $cid Application ID of application to remove from
  1397. *
  1398. * @return boolean True on success
  1399. *
  1400. * @since 11.1
  1401. */
  1402. public function removeFiles($element, $cid = 0)
  1403. {
  1404. if (!$element || !count($element->children()))
  1405. {
  1406. // Either the tag does not exist or has no children therefore we return zero files processed.
  1407. return true;
  1408. }
  1409. $removefiles = array();
  1410. $retval = true;
  1411. $debug = false;
  1412. if (isset($GLOBALS['installerdebug']) && $GLOBALS['installerdebug'])
  1413. {
  1414. $debug = true;
  1415. }
  1416. // Get the client info if we're using a specific client
  1417. if ($cid > -1)
  1418. {
  1419. $client = JApplicationHelper::getClientInfo($cid);
  1420. }
  1421. else
  1422. {
  1423. $client = null;
  1424. }
  1425. // Get the array of file nodes to process
  1426. $files = $element->children();
  1427. if (count($files) == 0)
  1428. {
  1429. // No files to process
  1430. return true;
  1431. }
  1432. $folder = '';
  1433. /*
  1434. * Here we set the folder we are going to remove the files from. There are a few
  1435. * special cases that need to be considered for certain reserved tags.
  1436. */
  1437. switch ($element->getName())
  1438. {
  1439. case 'media':
  1440. if ((string) $element->attributes()->destination)
  1441. {
  1442. $folder = (string) $element->attributes()->destination;
  1443. }
  1444. else
  1445. {
  1446. $folder = '';
  1447. }
  1448. $source = $client->path . '/media/' . $folder;
  1449. break;
  1450. case 'languages':
  1451. $lang_client = (string) $element->attributes()->client;
  1452. if ($lang_client)
  1453. {
  1454. $client = JApplicationHelper::getClientInfo($lang_client, true);
  1455. $source = $client->path . '/language';
  1456. }
  1457. else
  1458. {
  1459. if ($client)
  1460. {
  1461. $source = $client->path . '/language';
  1462. }
  1463. else
  1464. {
  1465. $source = '';
  1466. }
  1467. }
  1468. break;
  1469. default:
  1470. if ($client)
  1471. {
  1472. $pathname = 'extension_' . $client->name;
  1473. $source = $this->getPath($pathname);
  1474. }
  1475. else
  1476. {
  1477. $pathname = 'extension_root';
  1478. $source = $this->getPath($pathname);
  1479. }
  1480. break;
  1481. }
  1482. // Process each file in the $files array (children of $tagName).
  1483. foreach ($files as $file)
  1484. {
  1485. /*
  1486. * If the file is a language, we must handle it differently. Language files
  1487. * go in a subdirectory based on the language code, ie.
  1488. * <language tag="en_US">en_US.mycomponent.ini</language>
  1489. * would go in the en_US subdirectory of the languages directory.
  1490. */
  1491. if ($file->getName() == 'language' && (string) $file->attributes()->tag != '')
  1492. {
  1493. if ($source)
  1494. {
  1495. $path = $source . '/' . $file->attributes()->tag . '/' . basename((string) $file);
  1496. }
  1497. else
  1498. {
  1499. $target_client = JApplicationHelper::getClientInfo((string) $file->attributes()->client, true);
  1500. $path = $target_client->path . '/language/' . $file->attributes()->tag . '/' . basename((string) $file);
  1501. }
  1502. // If the language folder is not present, then the core pack hasn't been installed... ignore
  1503. if (!JFolder::exists(dirname($path)))
  1504. {
  1505. continue;
  1506. }
  1507. }
  1508. else
  1509. {
  1510. $path = $source . '/' . $file;
  1511. }
  1512. // Actually delete the files/folders
  1513. if (is_dir($path))
  1514. {
  1515. $val = JFolder::delete($path);
  1516. }
  1517. else
  1518. {
  1519. $val = JFile::delete($path);
  1520. }
  1521. if ($val === false)
  1522. {
  1523. JLog::add('Failed to delete ' . $path, JLog::WARNING, 'jerror');
  1524. $retval = false;
  1525. }
  1526. }
  1527. if (!empty($folder))
  1528. {
  1529. $val = JFolder::delete($source);
  1530. }
  1531. return $retval;
  1532. }
  1533. /**
  1534. * Copies the installation manifest file to the extension folder in the given client
  1535. *
  1536. * @param integer $cid Where to copy the installfile [optional: defaults to 1 (admin)]
  1537. *
  1538. * @return boolean True on success, False on error
  1539. *
  1540. * @since 11.1
  1541. */
  1542. public function copyManifest($cid = 1)
  1543. {
  1544. // Get the client info
  1545. $client = JApplicationHelper::getClientInfo($cid);
  1546. $path['src'] = $this->getPath('manifest');
  1547. if ($client)
  1548. {
  1549. $pathname = 'extension_' . $client->name;
  1550. $path['dest'] = $this->getPath($pathname) . '/' . basename($this->getPath('manifest'));
  1551. }
  1552. else
  1553. {
  1554. $pathname = 'extension_root';
  1555. $path['dest'] = $this->getPath($pathname) . '/' . basename($this->getPath('manifest'));
  1556. }
  1557. return $this->copyFiles(array($path), true);
  1558. }
  1559. /**
  1560. * Tries to find the package manifest file
  1561. *
  1562. * @return boolean True on success, False on error
  1563. *
  1564. * @since 11.1
  1565. */
  1566. public function findManifest()
  1567. {
  1568. // Get an array of all the XML files from the installation directory
  1569. $xmlfiles = JFolder::files($this->getPath('source'), '.xml$', 1, true);
  1570. // If at least one XML file exists
  1571. if (!empty($xmlfiles))
  1572. {
  1573. foreach ($xmlfiles as $file)
  1574. {
  1575. // Is it a valid Joomla installation manifest file?
  1576. $manifest = $this->isManifest($file);
  1577. if (!is_null($manifest))
  1578. {
  1579. // If the root method attribute is set to upgrade, allow file overwrite
  1580. if ((string) $manifest->attributes()->method == 'upgrade')
  1581. {
  1582. $this->upgrade = true;
  1583. $this->overwrite = true;
  1584. }
  1585. // If the overwrite option is set, allow file overwriting
  1586. if ((string) $manifest->attributes()->overwrite == 'true')
  1587. {
  1588. $this->overwrite = true;
  1589. }
  1590. // Set the manifest object and path
  1591. $this->manifest = $manifest;
  1592. $this->setPath('manifest', $file);
  1593. // Set the installation source path to that of the manifest file
  1594. $this->setPath('source', dirname($file));
  1595. return true;
  1596. }
  1597. }
  1598. // None of the XML files found were valid install files
  1599. JLog::add(JText::_('JLIB_INSTALLER_ERROR_NOTFINDJOOMLAXMLSETUPFILE'), JLog::WARNING, 'jerror');
  1600. return false;
  1601. }
  1602. else
  1603. {
  1604. // No XML files were found in the install folder
  1605. JLog::add(JText::_('JLIB_INSTALLER_ERROR_NOTFINDXMLSETUPFILE'), JLog::WARNING, 'jerror');
  1606. return false;
  1607. }
  1608. }
  1609. /**
  1610. * Is the XML file a valid Joomla installation manifest file.
  1611. *
  1612. * @param string $file An xmlfile path to check
  1613. *
  1614. * @return mixed A SimpleXMLElement, or null if the file failed to parse
  1615. *
  1616. * @since 11.1
  1617. */
  1618. public function isManifest($file)
  1619. {
  1620. $xml = simplexml_load_file($file);
  1621. // If we cannot load the XML file return null
  1622. if (!$xml)
  1623. {
  1624. return null;
  1625. }
  1626. // Check for a valid XML root tag.
  1627. if ($xml->getName() != 'extension')
  1628. {
  1629. return null;
  1630. }
  1631. // Valid manifest file return the object
  1632. return $xml;
  1633. }
  1634. /**
  1635. * Generates a manifest cache
  1636. *
  1637. * @return string serialised manifest data
  1638. *
  1639. * @since 11.1
  1640. */
  1641. public function generateManifestCache()
  1642. {
  1643. return json_encode(self::parseXMLInstallFile($this->getPath('manifest')));
  1644. }
  1645. /**
  1646. * Cleans up discovered extensions if they're being installed some other way
  1647. *
  1648. * @param string $type The type of extension (component, etc)
  1649. * @param string $element Unique element identifier (e.g. com_content)
  1650. * @param string $folder The folder of the extension (plugins; e.g. system)
  1651. * @param integer $client The client application (administrator or site)
  1652. *
  1653. * @return object Result of query
  1654. *
  1655. * @since 11.1
  1656. */
  1657. public function cleanDiscoveredExtension($type, $element, $folder = '', $client = 0)
  1658. {
  1659. $dbo = JFactory::getDBO();
  1660. $query = $dbo->getQuery(true);
  1661. $query->delete($dbo->quoteName('#__extensions'));
  1662. $query->where('type = ' . $dbo->Quote($type));
  1663. $query->where('element = ' . $dbo->Quote($element));
  1664. $query->where('folder = ' . $dbo->Quote($folder));
  1665. $query->where('client_id = ' . (int) $client);
  1666. $query->where('state = -1');
  1667. return $dbo->execute();
  1668. }
  1669. /**
  1670. * Compares two "files" entries to find deleted files/folders
  1671. *
  1672. * @param array $old_files An array of SimpleXMLElement objects that are the old files
  1673. * @param array $new_files An array of SimpleXMLElement objects that are the new files
  1674. *
  1675. * @return array An array with the delete files and folders in findDeletedFiles[files] and findDeletedFiles[folders] respectively
  1676. *
  1677. * @since 11.1
  1678. */
  1679. public function findDeletedFiles($old_files, $new_files)
  1680. {
  1681. // The magic find deleted files function!
  1682. // The files that are new
  1683. $files = array();
  1684. // The folders that are new
  1685. $folders = array();
  1686. // The folders of the files that are new
  1687. $containers = array();
  1688. // A list of files to delete
  1689. $files_deleted = array();
  1690. // A list of folders to delete
  1691. $folders_deleted = array();
  1692. foreach ($new_files as $file)
  1693. {
  1694. switch ($file->getName())
  1695. {
  1696. case 'folder':
  1697. // Add any folders to the list
  1698. $folders[] = (string) $file; // add any folders to the list
  1699. break;
  1700. case 'file':
  1701. default:
  1702. // Add any files to the list
  1703. $files[] = (string) $file;
  1704. // Now handle the folder part of the file to ensure we get any containers
  1705. // Break up the parts of the directory
  1706. $container_parts = explode('/', dirname((string) $file));
  1707. // Make sure this is clean and empty
  1708. $container = '';
  1709. foreach ($container_parts as $part)
  1710. {
  1711. // Iterate through each part
  1712. // Add a slash if its not empty
  1713. if (!empty($container))
  1714. {
  1715. $container .= '/';
  1716. }
  1717. // Aappend the folder part
  1718. $container .= $part;
  1719. if (!in_array($container, $containers))
  1720. {
  1721. // Add the container if it doesn't already exist
  1722. $containers[] = $container;
  1723. }
  1724. }
  1725. break;
  1726. }
  1727. }
  1728. foreach ($old_files as $file)
  1729. {
  1730. switch ($file->getName())
  1731. {
  1732. case 'folder':
  1733. if (!in_array((string) $file, $folders))
  1734. {
  1735. // See whether the folder exists in the new list
  1736. if (!in_array((string) $file, $containers))
  1737. {
  1738. // Check if the folder exists as a container in the new list
  1739. // If it's not in the new list or a container then delete it
  1740. $folders_deleted[] = (string) $file;
  1741. }
  1742. }
  1743. break;
  1744. case 'file':
  1745. default:
  1746. if (!in_array((string) $file, $files))
  1747. {
  1748. // Look if the file exists in the new list
  1749. if (!in_array(dirname((string) $file), $folders))
  1750. {
  1751. // Look if the file is now potentially in a folder
  1752. $files_deleted[] = (string) $file; // not in a folder, doesn't exist, wipe it out!
  1753. }
  1754. }
  1755. break;
  1756. }
  1757. }
  1758. return array('files' => $files_deleted, 'folders' => $folders_deleted);
  1759. }
  1760. /**
  1761. * Loads an MD5SUMS file into an associative array
  1762. *
  1763. * @param string $filename Filename to load
  1764. *
  1765. * @return array Associative array with filenames as the index and the MD5 as the value
  1766. *
  1767. * @since 11.1
  1768. */
  1769. public function loadMD5Sum($filename)
  1770. {
  1771. if (!file_exists($filename))
  1772. {
  1773. // Bail if the file doesn't exist
  1774. return false;
  1775. }
  1776. $data = file($filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
  1777. $retval = array();
  1778. foreach ($data as $row)
  1779. {
  1780. // Split up the data
  1781. $results = explode(' ', $row);
  1782. // Cull any potential prefix
  1783. $results[1] = str_replace('./', '', $results[1]);
  1784. // Throw into the array
  1785. $retval[$results[1]] = $results[0];
  1786. }
  1787. return $retval;
  1788. }
  1789. /**
  1790. * Parse a XML install manifest file.
  1791. *
  1792. * XML Root tag should be 'install' except for languages which use meta file.
  1793. *
  1794. * @param string $path Full path to XML file.
  1795. *
  1796. * @return array XML metadata.
  1797. *
  1798. * @since 12.1
  1799. */
  1800. public static function parseXMLInstallFile($path)
  1801. {
  1802. // Read the file to see if it's a valid component XML file
  1803. $xml = simplexml_load_file($path);
  1804. if (!$xml)
  1805. {
  1806. return false;
  1807. }
  1808. // Check for a valid XML root tag.
  1809. // Extensions use 'extension' as the root tag. Languages use 'metafile' instead
  1810. if ($xml->getName() != 'extension' && $xml->getName() != 'metafile')
  1811. {
  1812. unset($xml);
  1813. return false;
  1814. }
  1815. $data = array();
  1816. $data['name'] = (string) $xml->name;
  1817. // Check if we're a language. If so use metafile.
  1818. $data['type'] = $xml->getName() == 'metafile' ? 'language' : (string) $xml->attributes()->type;
  1819. $data['creationDate'] = ((string) $xml->creationDate) ? (string) $xml->creationDate : JText::_('Unknown');
  1820. $data['author'] = ((string) $xml->author) ? (string) $xml->author : JText::_('Unknown');
  1821. $data['copyright'] = (string) $xml->copyright;
  1822. $data['authorEmail'] = (string) $xml->authorEmail;
  1823. $data['authorUrl'] = (string) $xml->authorUrl;
  1824. $data['version'] = (string) $xml->version;
  1825. $data['description'] = (string) $xml->description;
  1826. $data['group'] = (string) $xml->group;
  1827. return $data;
  1828. }
  1829. }