PageRenderTime 52ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/joomla/installer/installer.php

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