PageRenderTime 52ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 1ms

/libraries/joomla/installer/installer.php

https://bitbucket.org/organicdevelopment/joomla-2.5
PHP | 2159 lines | 1205 code | 302 blank | 652 comment | 219 complexity | dbf15f512adb9fd41a6cdd184a8993fd MD5 | raw file
Possible License(s): LGPL-3.0, GPL-2.0, MIT, BSD-3-Clause, LGPL-2.1

Large files files are truncated, but you can click here to view the full file

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

Large files files are truncated, but you can click here to view the full file