PageRenderTime 66ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/joomla/installer/installer.php

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