PageRenderTime 56ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/joomla/installer/adapters/module.php

https://github.com/joebushi/joomla
PHP | 689 lines | 420 code | 58 blank | 211 comment | 54 complexity | 01f9f442085e5cd70f023af934b9c7c9 MD5 | raw file
Possible License(s): LGPL-2.1, Apache-2.0
  1. <?php
  2. /**
  3. * @version $Id$
  4. * @copyright Copyright (C) 2005 - 2010 Open Source Matters, Inc. All rights reserved.
  5. * @license GNU General Public License version 2 or later; see LICENSE.txt
  6. */
  7. // No direct access
  8. defined('JPATH_BASE') or die;
  9. jimport('joomla.base.adapterinstance');
  10. /**
  11. * Module installer
  12. *
  13. * @package Joomla.Framework
  14. * @subpackage Installer
  15. * @since 1.5
  16. */
  17. class JInstallerModule extends JAdapterInstance
  18. {
  19. /**
  20. * @var string install function routing
  21. */
  22. protected $route = 'Install';
  23. protected $manifest = null;
  24. protected $manifest_script = null;
  25. protected $name = null;
  26. protected $element = null;
  27. protected $scriptElement = null;
  28. /**
  29. * Custom install method
  30. *
  31. * @access public
  32. * @return boolean True on success
  33. * @since 1.5
  34. */
  35. public function install()
  36. {
  37. // if this is an update, set the route accordingly
  38. /*if ($this->parent->getUpgrade()) {
  39. $this->route = 'Update';
  40. }*/
  41. // Get a database connector object
  42. $db = &$this->parent->getDbo();
  43. // Get the extension manifest object
  44. $this->manifest = $this->parent->getManifest();
  45. /**
  46. * ---------------------------------------------------------------------------------------------
  47. * Manifest Document Setup Section
  48. * ---------------------------------------------------------------------------------------------
  49. */
  50. // Set the extensions name
  51. $name = (string)$this->manifest->name;
  52. $name = JFilterInput::getInstance()->clean($name, 'string');
  53. $this->set('name', $name);
  54. // Get the component description
  55. $description = (string)$this->manifest->description;
  56. if ($description) {
  57. $this->parent->set('message', $description);
  58. }
  59. else {
  60. $this->parent->set('message', '');
  61. }
  62. /**
  63. * ---------------------------------------------------------------------------------------------
  64. * Target Application Section
  65. * ---------------------------------------------------------------------------------------------
  66. */
  67. // Get the target application
  68. if ($cname = (string)$this->manifest->attributes()->client)
  69. {
  70. // Attempt to map the client to a base path
  71. jimport('joomla.application.helper');
  72. $client = &JApplicationHelper::getClientInfo($cname, true);
  73. if ($client === false)
  74. {
  75. $this->parent->abort(JText::_('Module').' '.JText::_($this->route).': '.JText::_('Unknown client type').' ['.$client->name.']');
  76. return false;
  77. }
  78. $basePath = $client->path;
  79. $clientId = $client->id;
  80. }
  81. else
  82. {
  83. // No client attribute was found so we assume the site as the client
  84. $cname = 'site';
  85. $basePath = JPATH_SITE;
  86. $clientId = 0;
  87. }
  88. // Set the installation path
  89. $element = '';
  90. if (count($this->manifest->files->children()))
  91. {
  92. foreach ($this->manifest->files->children() as $file)
  93. {
  94. if ((string)$file->attributes()->module)
  95. {
  96. $element = (string)$file->attributes()->module;
  97. $this->set('element',$element);
  98. break;
  99. }
  100. }
  101. }
  102. if (!empty ($element)) {
  103. $this->parent->setPath('extension_root', $basePath.DS.'modules'.DS.$element);
  104. }
  105. else {
  106. $this->parent->abort(JText::_('Module').' '.JText::_($this->route).': '.JText::_('No module file specified'));
  107. return false;
  108. }
  109. // Check to see if a module by the same name is already installed
  110. // If it is, then update the table because if the files aren't there
  111. // we can assume that it was (badly) uninstalled
  112. // If it isn't, add an entry to extensions
  113. $query = 'SELECT `extension_id`' .
  114. ' FROM `#__extensions` ' .
  115. ' WHERE element = '.$db->Quote($element) .
  116. ' AND client_id = '.(int)$clientId;
  117. $db->setQuery($query);
  118. try {
  119. $db->Query();
  120. }
  121. catch(JException $e)
  122. {
  123. // Install failed, roll back changes
  124. $this->parent->abort(JText::_('Module').' '.JText::_($this->route).': '.$db->stderr(true));
  125. return false;
  126. }
  127. $id = $db->loadResult();
  128. /*
  129. * If the module directory already exists, then we will assume that the
  130. * module is already installed or another module is using that
  131. * directory.
  132. * Check that this is either an issue where its not overwriting or it is
  133. * set to upgrade anyway
  134. */
  135. if (file_exists($this->parent->getPath('extension_root')) && (!$this->parent->getOverwrite() || $this->parent->getUpgrade()))
  136. {
  137. // look for an update function or update tag
  138. $updateElement = $this->manifest->update;
  139. // upgrade manually set
  140. // update function available
  141. // update tag detected
  142. if ($this->parent->getUpgrade() || ($this->parent->manifestClass && method_exists($this->parent->manifestClass,'update')) || is_a($updateElement, 'JXMLElement'))
  143. {
  144. // force these one
  145. $this->parent->setOverwrite(true);
  146. $this->parent->setUpgrade(true);
  147. if ($id) { // if there is a matching extension mark this as an update; semantics really
  148. $this->route = 'Update';
  149. }
  150. }
  151. else if (!$this->parent->getOverwrite())
  152. {
  153. // overwrite is set
  154. // we didn't have overwrite set, find an udpate function or find an update tag so lets call it safe
  155. $this->parent->abort(JText::_('Module').' '.JText::_($this->route).': '.JText::_('Another module is already using directory').': "'.$this->parent->getPath('extension_root').'"');
  156. return false;
  157. }
  158. }
  159. /**
  160. * ---------------------------------------------------------------------------------------------
  161. * Installer Trigger Loading
  162. * ---------------------------------------------------------------------------------------------
  163. */
  164. // If there is an manifest class file, lets load it; we'll copy it later (don't have dest yet)
  165. $this->scriptElement = $this->manifest->scriptfile;
  166. $manifestScript = (string)$this->manifest->scriptfile;
  167. if ($manifestScript)
  168. {
  169. $manifestScriptFile = $this->parent->getPath('source').DS.$manifestScript;
  170. if (is_file($manifestScriptFile))
  171. {
  172. // load the file
  173. include_once $manifestScriptFile;
  174. }
  175. // Set the class name
  176. $classname = $element.'InstallerScript';
  177. if (class_exists($classname))
  178. {
  179. // create a new instance
  180. $this->parent->manifestClass = new $classname($this);
  181. // and set this so we can copy it later
  182. $this->set('manifest_script', $manifestScript);
  183. // Note: if we don't find the class, don't bother to copy the file
  184. }
  185. }
  186. // run preflight if possible (since we know we're not an update)
  187. ob_start();
  188. ob_implicit_flush(false);
  189. if ($this->parent->manifestClass && method_exists($this->parent->manifestClass,'preflight')) {
  190. $this->parent->manifestClass->preflight($this->route, $this);
  191. }
  192. $msg = ob_get_contents(); // create msg object; first use here
  193. ob_end_clean();
  194. /**
  195. * ---------------------------------------------------------------------------------------------
  196. * Filesystem Processing Section
  197. * ---------------------------------------------------------------------------------------------
  198. */
  199. // If the module directory does not exist, lets create it
  200. $created = false;
  201. if (!file_exists($this->parent->getPath('extension_root')))
  202. {
  203. if (!$created = JFolder::create($this->parent->getPath('extension_root')))
  204. {
  205. $this->parent->abort(JText::_('Module').' '.JText::_($this->route).': '.JText::_('FAILED_TO_CREATE_DIRECTORY').': "'.$this->parent->getPath('extension_root').'"');
  206. return false;
  207. }
  208. }
  209. /*
  210. * Since we created the module directory and will want to remove it if
  211. * we have to roll back the installation, lets add it to the
  212. * installation step stack
  213. */
  214. if ($created) {
  215. $this->parent->pushStep(array ('type' => 'folder', 'path' => $this->parent->getPath('extension_root')));
  216. }
  217. // Copy all necessary files
  218. if ($this->parent->parseFiles($this->manifest->files, -1) === false)
  219. {
  220. // Install failed, roll back changes
  221. $this->parent->abort();
  222. return false;
  223. }
  224. // Parse optional tags
  225. $this->parent->parseMedia($this->manifest->media, $clientId);
  226. $this->parent->parseLanguages($this->manifest->languages, $clientId);
  227. // Parse deprecated tags
  228. $this->parent->parseFiles($this->manifest->images, -1);
  229. /**
  230. * ---------------------------------------------------------------------------------------------
  231. * Database Processing Section
  232. * ---------------------------------------------------------------------------------------------
  233. */
  234. // Was there a module already installed with the same name?
  235. if ($id)
  236. {
  237. // load the entry and update the manifest_cache
  238. $row = &JTable::getInstance('extension');
  239. $row->load($id);
  240. $row->name = $this->get('name'); // update name
  241. $row->manifest_cache = $this->parent->generateManifestCache(); // update manifest
  242. if (!$row->store()) {
  243. // Install failed, roll back changes
  244. $this->parent->abort(JText::_('Module').' '.JText::_($this->route).': '.$db->stderr(true));
  245. return false;
  246. }
  247. }
  248. else
  249. {
  250. $row = & JTable::getInstance('extension');
  251. $row->set('name', $this->get('name'));
  252. $row->set('type', 'module');
  253. $row->set('element', $this->get('element'));
  254. $row->set('folder', ''); // There is no folder for modules
  255. $row->set('enabled', 1);
  256. $row->set('protected', 0);
  257. $row->set('access', $clientId == 1 ? 2 : 0);
  258. $row->set('client_id', $clientId);
  259. $row->set('params', $this->parent->getParams());
  260. $row->set('custom_data', ''); // custom data
  261. $row->set('manifest_cache', $this->parent->generateManifestCache());
  262. if (!$row->store())
  263. {
  264. // Install failed, roll back changes
  265. $this->parent->abort(JText::_('Module').' '.JText::_($this->route).': '.$db->stderr(true));
  266. return false;
  267. }
  268. // Since we have created a module item, we add it to the installation step stack
  269. // so that if we have to rollback the changes we can undo it.
  270. $this->parent->pushStep(array ('type' => 'extension', 'extension_id' => $row->extension_id));
  271. }
  272. /*
  273. * Let's run the queries for the module
  274. * If Joomla 1.5 compatible, with discreet sql files - execute appropriate
  275. * file for utf-8 support or non-utf-8 support
  276. */
  277. // try for Joomla 1.5 type queries
  278. // second argument is the utf compatible version attribute
  279. $utfresult = $this->parent->parseSQLFiles($this->manifest->{strtolower($this->route)}->sql);
  280. if ($utfresult === false)
  281. {
  282. // Install failed, rollback changes
  283. $this->parent->abort(JText::_('Module').' '.JText::_($this->route).': '.JText::_('SQLERRORORFILE')." ".$db->stderr(true));
  284. return false;
  285. }
  286. // Start Joomla! 1.6
  287. ob_start();
  288. ob_implicit_flush(false);
  289. if ($this->parent->manifestClass && method_exists($this->parent->manifestClass,$this->route)) $this->parent->manifestClass->{$this->route}($this);
  290. $msg .= ob_get_contents(); // append messages
  291. ob_end_clean();
  292. /**
  293. * ---------------------------------------------------------------------------------------------
  294. * Finalization and Cleanup Section
  295. * ---------------------------------------------------------------------------------------------
  296. */
  297. // Lastly, we will copy the manifest file to its appropriate place.
  298. if (!$this->parent->copyManifest(-1))
  299. {
  300. // Install failed, rollback changes
  301. $this->parent->abort(JText::_('Module').' '.JText::_($this->route).': '.JText::_('COULD_NOT_COPY_SETUP_FILE'));
  302. return false;
  303. }
  304. // And now we run the postflight
  305. ob_start();
  306. ob_implicit_flush(false);
  307. if ($this->parent->manifestClass && method_exists($this->parent->manifestClass,'postflight')) {
  308. $this->parent->manifestClass->postflight($this->route, $this);
  309. }
  310. $msg .= ob_get_contents(); // append messages
  311. ob_end_clean();
  312. if ($msg != '') {
  313. $this->parent->set('extension_message', $msg);
  314. }
  315. return $row->get('extension_id');
  316. }
  317. /**
  318. * Custom update method
  319. * This is really a shell for the install system
  320. *
  321. * @access public
  322. * @return boolean True on success
  323. * @since 1.6
  324. */
  325. function update()
  326. {
  327. // set the overwrite setting
  328. $this->parent->setOverwrite(true);
  329. $this->parent->setUpgrade(true);
  330. // set the route for the install
  331. $this->route = 'Update';
  332. // go to install which handles updates properly
  333. return $this->install();
  334. }
  335. /**
  336. * Custom discover method
  337. *
  338. * @access public
  339. * @return array(JExtension) list of extensions available
  340. * @since 1.6
  341. */
  342. function discover()
  343. {
  344. $results = Array();
  345. $site_list = JFolder::folders(JPATH_SITE.DS.'modules');
  346. $admin_list = JFolder::folders(JPATH_ADMINISTRATOR.DS.'modules');
  347. $site_info = JApplicationHelper::getClientInfo('site', true);
  348. $admin_info = JApplicationHelper::getClientInfo('administrator', true);
  349. foreach ($site_list as $module)
  350. {
  351. $extension = &JTable::getInstance('extension');
  352. $extension->set('type', 'module');
  353. $extension->set('client_id', $site_info->id);
  354. $extension->set('element', $module);
  355. $extension->set('name', $module);
  356. $extension->set('state', -1);
  357. $results[] = clone $extension;
  358. }
  359. foreach ($admin_list as $module)
  360. {
  361. $extension = &JTable::getInstance('extension');
  362. $extension->set('type', 'module');
  363. $extension->set('client_id', $admin_info->id);
  364. $extension->set('element', $module);
  365. $extension->set('name', $module);
  366. $extension->set('state', -1);
  367. $results[] = clone $extension;
  368. }
  369. return $results;
  370. }
  371. /**
  372. * Custom discover_install method
  373. *
  374. * @access public
  375. * @param int $id The id of the extension to install (from #__discoveredextensions)
  376. * @return void
  377. * @since 1.6
  378. */
  379. function discover_install()
  380. {
  381. // Modules are like templates, and are one of the easiest
  382. // If its not in the extensions table we just add it
  383. $client = JApplicationHelper::getClientInfo($this->parent->extension->client_id);
  384. $manifestPath = $client->path . DS . 'modules'. DS . $this->parent->extension->element . DS . $this->parent->extension->element . '.xml';
  385. $this->parent->manifest = $this->parent->isManifest($manifestPath);
  386. $this->parent->setPath('manifest', $manifestPath);
  387. $manifest_details = JApplicationHelper::parseXMLInstallFile($this->parent->getPath('manifest'));
  388. // TODO: Re-evaluate this; should we run installation triggers? postflight perhaps?
  389. $this->parent->extension->manifest_cache = serialize($manifest_details);
  390. $this->parent->extension->state = 0;
  391. $this->parent->extension->name = $manifest_details['name'];
  392. $this->parent->extension->enabled = 1;
  393. $this->parent->extension->params = $this->parent->getParams();
  394. if ($this->parent->extension->store()) {
  395. return $this->parent->extension->get('extension_id');
  396. }
  397. else
  398. {
  399. JError::raiseWarning(101, JText::_('Module').' '.JText::_('Discover Install').': '.JText::_('Failed to store extension details'));
  400. return false;
  401. }
  402. }
  403. function refreshManifestCache()
  404. {
  405. $client = JApplicationHelper::getClientInfo($this->parent->extension->client_id);
  406. $manifestPath = $client->path . DS . 'modules'. DS . $this->parent->extension->element . DS . $this->parent->extension->element . '.xml';
  407. $this->parent->manifest = $this->parent->isManifest($manifestPath);
  408. $this->parent->setPath('manifest', $manifestPath);
  409. $manifest_details = JApplicationHelper::parseXMLInstallFile($this->parent->getPath('manifest'));
  410. $this->parent->extension->manifest_cache = serialize($manifest_details);
  411. $this->parent->extension->name = $manifest_details['name'];
  412. if ($this->parent->extension->store()) {
  413. return true;
  414. }
  415. else
  416. {
  417. JError::raiseWarning(101, JText::_('Module').' '.JText::_('Refresh Manifest Cache').': '.JText::_('Failed to store extension details'));
  418. return false;
  419. }
  420. }
  421. /**
  422. * Custom uninstall method
  423. *
  424. * @access public
  425. * @param int $id The id of the module to uninstall
  426. * @return boolean True on success
  427. * @since 1.5
  428. */
  429. public function uninstall($id)
  430. {
  431. // Initialise variables.
  432. $row = null;
  433. $retval = true;
  434. $db = &$this->parent->getDbo();
  435. // First order of business will be to load the module object table from the database.
  436. // This should give us the necessary information to proceed.
  437. $row = & JTable::getInstance('extension');
  438. if (!$row->load((int) $id) || !strlen($row->element))
  439. {
  440. JError::raiseWarning(100, JText::_('ERRORUNKOWNEXTENSION'));
  441. return false;
  442. }
  443. // Is the module we are trying to uninstall a core one?
  444. // Because that is not a good idea...
  445. if ($row->protected)
  446. {
  447. JError::raiseWarning(100, JText::_('Module').' '.JText::_('Uninstall').': '.JText::sprintf('WARNCOREMODULE', $row->name)."<br />".JText::_('WARNCOREMODULE2'));
  448. return false;
  449. }
  450. // Get the extension root path
  451. jimport('joomla.application.helper');
  452. $element = $row->element;
  453. $client = &JApplicationHelper::getClientInfo($row->client_id);
  454. if ($client === false)
  455. {
  456. $this->parent->abort(JText::_('Module').' '.JText::_('Uninstall').': '.JText::_('Unknown client type').' ['.$row->client_id.']');
  457. return false;
  458. }
  459. $this->parent->setPath('extension_root', $client->path.DS.'modules'.DS.$element);
  460. $this->parent->setPath('source', $this->parent->getPath('extension_root'));
  461. // Get the package manifest objecct
  462. $this->manifest = $this->parent->getManifest();
  463. // If there is an manifest class file, lets load it
  464. $this->scriptElement = $this->manifest->scriptfile;
  465. $manifestScript = (string)$this->manifest->scriptfile;
  466. if ($manifestScript)
  467. {
  468. $manifestScriptFile = $this->parent->getPath('extension_root').DS.$manifestScript;
  469. if (is_file($manifestScriptFile))
  470. {
  471. // load the file
  472. include_once $manifestScriptFile;
  473. }
  474. // Set the class name
  475. $classname = $element.'InstallerScript';
  476. if (class_exists($classname))
  477. {
  478. // create a new instance
  479. $this->parent->manifestClass = new $classname($this);
  480. // and set this so we can copy it later
  481. $this->set('manifest_script', $manifestScript);
  482. // Note: if we don't find the class, don't bother to copy the file
  483. }
  484. }
  485. ob_start();
  486. ob_implicit_flush(false);
  487. // run uninstall if possible
  488. if ($this->parent->manifestClass && method_exists($this->parent->manifestClass,'uninstall')) {
  489. $this->parent->manifestClass->uninstall($this);
  490. }
  491. $msg = ob_get_contents();
  492. ob_end_clean();
  493. if (!$this->manifest INSTANCEOF JXMLElement)
  494. {
  495. // Make sure we delete the folders
  496. JFolder::delete($this->parent->getPath('extension_root'));
  497. JError::raiseWarning(100, 'Module Uninstall: Package manifest file invalid or not found');
  498. return false;
  499. }
  500. /*
  501. * Let's run the uninstall queries for the component
  502. * If Joomla 1.5 compatible, with discreet sql files - execute appropriate
  503. * file for utf-8 support or non-utf support
  504. */
  505. // try for Joomla 1.5 type queries
  506. // second argument is the utf compatible version attribute
  507. $utfresult = $this->parent->parseSQLFiles($this->manifest->uninstall->sql);
  508. if ($utfresult === false)
  509. {
  510. // Install failed, rollback changes
  511. JError::raiseWarning(100, JText::_('Module').' '.JText::_('Uninstall').': '.JText::_('SQLERRORORFILE')." ".$db->stderr(true));
  512. $retval = false;
  513. }
  514. // Remove other files
  515. $this->parent->removeFiles($this->manifest->media);
  516. $this->parent->removeFiles($this->manifest->languages, $row->client_id);
  517. // Lets delete all the module copies for the type we are uninstalling
  518. $query = 'SELECT `id`' .
  519. ' FROM `#__modules`' .
  520. ' WHERE module = '.$db->Quote($row->element) .
  521. ' AND client_id = '.(int)$row->client_id;
  522. $db->setQuery($query);
  523. try {
  524. $modules = $db->loadResultArray();
  525. }
  526. catch(JException $e) {
  527. $modules = array();
  528. }
  529. // Do we have any module copies?
  530. if (count($modules))
  531. {
  532. // Ensure the list is sane
  533. JArrayHelper::toInteger($modules);
  534. $modID = implode(',', $modules);
  535. // Wipe out any items assigned to menus
  536. $query = 'DELETE' .
  537. ' FROM #__modules_menu' .
  538. ' WHERE moduleid IN ('.$modID.')';
  539. $db->setQuery($query);
  540. try {
  541. $db->query();
  542. }
  543. catch(JException $e)
  544. {
  545. JError::raiseWarning(100, JText::_('Module').' '.JText::_('Uninstall').': '.$db->stderr(true));
  546. $retval = false;
  547. }
  548. // Wipe out any instances in the modules table
  549. $query = 'DELETE' .
  550. ' FROM #__modules' .
  551. ' WHERE id IN ('.$modID.')';
  552. $db->setQuery($query);
  553. try {
  554. $db->query();
  555. }
  556. catch (JException $e)
  557. {
  558. JError::raiseWarning(100, JText::_('Module').' '.JText::_('Uninstall').': '.$db->stderr(true));
  559. $retval = false;
  560. }
  561. }
  562. // Now we will no longer need the module object, so lets delete it and free up memory
  563. $row->delete($row->extension_id);
  564. $query = 'DELETE FROM `#__modules` WHERE module = '.$db->Quote($row->element) . ' AND client_id = ' . $row->client_id;
  565. $db->setQuery($query);
  566. try {
  567. $db->Query(); // clean up any other ones that might exist as well
  568. }
  569. catch(JException $e) {
  570. //Ignore the error...
  571. }
  572. unset ($row);
  573. // Remove the installation folder
  574. if (!JFolder::delete($this->parent->getPath('extension_root')))
  575. {
  576. // JFolder should raise an error
  577. $retval = false;
  578. }
  579. return $retval;
  580. }
  581. /**
  582. * Custom rollback method
  583. * - Roll back the menu item
  584. *
  585. * @access public
  586. * @param array $arg Installation step to rollback
  587. * @return boolean True on success
  588. * @since 1.5
  589. */
  590. protected function _rollback_menu($arg)
  591. {
  592. // Get database connector object
  593. $db = &$this->parent->getDbo();
  594. // Remove the entry from the #__modules_menu table
  595. $query = 'DELETE' .
  596. ' FROM `#__modules_menu`' .
  597. ' WHERE moduleid='.(int)$arg['id'];
  598. $db->setQuery($query);
  599. try {
  600. return $db->query();
  601. }
  602. catch(JException $e) {
  603. return false;
  604. }
  605. }
  606. /**
  607. * Custom rollback method
  608. * - Roll back the module item
  609. *
  610. * @access public
  611. * @param array $arg Installation step to rollback
  612. * @return boolean True on success
  613. * @since 1.5
  614. */
  615. protected function _rollback_module($arg)
  616. {
  617. // Get database connector object
  618. $db = &$this->parent->getDbo();
  619. // Remove the entry from the #__modules table
  620. $query = 'DELETE' .
  621. ' FROM `#__modules`' .
  622. ' WHERE id='.(int)$arg['id'];
  623. $db->setQuery($query);
  624. try {
  625. return $db->query();
  626. }
  627. catch(JException $e) {
  628. return false;
  629. }
  630. }
  631. }