PageRenderTime 62ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/libraries/joomla/installer/installer.php

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