PageRenderTime 55ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/libraries/joomla/installer/installer.php

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