PageRenderTime 468ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/joomla/installer/adapters/plugin.php

https://github.com/joebushi/joomla
PHP | 635 lines | 386 code | 62 blank | 187 comment | 71 complexity | 8adc7aeb43b0ae743bb31054db4a062f 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. * Plugin installer
  12. *
  13. * @package Joomla.Framework
  14. * @subpackage Installer
  15. * @since 1.5
  16. */
  17. class JInstallerPlugin extends JAdapterInstance
  18. {
  19. /** @var string install function routing */
  20. var $route = 'Install';
  21. protected $manifest = null;
  22. protected $manifest_script = null;
  23. protected $name = null;
  24. protected $scriptElement = null;
  25. /**
  26. * Custom install method
  27. *
  28. * @access public
  29. * @return boolean True on success
  30. * @since 1.5
  31. */
  32. public function install()
  33. {
  34. // Get a database connector object
  35. $db = &$this->parent->getDbo();
  36. // Get the extension manifest object
  37. $this->manifest = $this->parent->getManifest();
  38. $xml = $this->manifest;
  39. /**
  40. * ---------------------------------------------------------------------------------------------
  41. * Manifest Document Setup Section
  42. * ---------------------------------------------------------------------------------------------
  43. */
  44. // Set the extensions name
  45. $name = (string)$xml->name;
  46. $name = JFilterInput::getInstance()->clean($name, 'string');
  47. $this->set('name', $name);
  48. // Get the component description
  49. $description = (string)$xml->description;
  50. if ($description) {
  51. $this->parent->set('message', $description);
  52. }
  53. else {
  54. $this->parent->set('message', '');
  55. }
  56. /*
  57. * Backward Compatability
  58. * @todo Deprecate in future version
  59. */
  60. $type = (string)$xml->attributes()->type;
  61. // Set the installation path
  62. if (count($xml->files->children()))
  63. {
  64. foreach ($xml->files->children() as $file)
  65. {
  66. if ((string)$file->attributes()->$type)
  67. {
  68. $element = (string)$file->attributes()->$type;
  69. break;
  70. }
  71. }
  72. }
  73. $group = (string)$xml->attributes()->group;
  74. if (!empty ($element) && !empty($group)) {
  75. $this->parent->setPath('extension_root', JPATH_ROOT.DS.'plugins'.DS.$group.DS.$element);
  76. }
  77. else
  78. {
  79. $this->parent->abort(JText::_('Plugin').' '.JText::_($this->route).': '.JText::_('No plugin file specified'));
  80. return false;
  81. }
  82. /*
  83. * Check if we should enable overwrite settings
  84. */
  85. // Check to see if a plugin by the same name is already installed
  86. $query = 'SELECT `extension_id`' .
  87. ' FROM `#__extensions`' .
  88. ' WHERE folder = '.$db->Quote($group) .
  89. ' AND element = '.$db->Quote($element);
  90. $db->setQuery($query);
  91. try {
  92. $db->Query();
  93. }
  94. catch(JException $e)
  95. {
  96. // Install failed, roll back changes
  97. $this->parent->abort(JText::_('Plugin').' '.JText::_($this->route).': '.$db->stderr(true));
  98. return false;
  99. }
  100. $id = $db->loadResult();
  101. // if its on the fs...
  102. if (file_exists($this->parent->getPath('extension_root')) && (!$this->parent->getOverwrite() || $this->parent->getUpgrade()))
  103. {
  104. $updateElement = $xml->update;
  105. // upgrade manually set
  106. // update function available
  107. // update tag detected
  108. if ($this->parent->getUpgrade() || ($this->parent->manifestClass && method_exists($this->parent->manifestClass,'update')) || is_a($updateElement, 'JXMLElement'))
  109. {
  110. // force these one
  111. $this->parent->setOverwrite(true);
  112. $this->parent->setUpgrade(true);
  113. if ($id) { // if there is a matching extension mark this as an update; semantics really
  114. $this->route = 'Update';
  115. }
  116. }
  117. else if (!$this->parent->getOverwrite())
  118. {
  119. // overwrite is set
  120. // we didn't have overwrite set, find an udpate function or find an update tag so lets call it safe
  121. $this->parent->abort(JText::_('Plugin').' '.JText::_($this->route).': '.JText::_('Another extension is already using directory').': "'.$this->parent->getPath('extension_root').'"');
  122. return false;
  123. }
  124. }
  125. /**
  126. * ---------------------------------------------------------------------------------------------
  127. * Installer Trigger Loading
  128. * ---------------------------------------------------------------------------------------------
  129. */
  130. // If there is an manifest class file, lets load it; we'll copy it later (don't have dest yet)
  131. if ((string)$xml->scriptfile)
  132. {
  133. $manifestScript = (string)$xml->scriptfile;
  134. $manifestScriptFile = $this->parent->getPath('source').DS.$manifestScript;
  135. if (is_file($manifestScriptFile))
  136. {
  137. // load the file
  138. include_once $manifestScriptFile;
  139. }
  140. // Set the class name
  141. $classname = 'plg'.$group.$element.'InstallerScript';
  142. if (class_exists($classname))
  143. {
  144. // create a new instance
  145. $this->parent->manifestClass = new $classname($this);
  146. // and set this so we can copy it later
  147. $this->set('manifest_script', $manifestScript);
  148. // Note: if we don't find the class, don't bother to copy the file
  149. }
  150. }
  151. // run preflight if possible (since we know we're not an update)
  152. ob_start();
  153. ob_implicit_flush(false);
  154. if ($this->parent->manifestClass && method_exists($this->parent->manifestClass,'preflight')) {
  155. $this->parent->manifestClass->preflight($this->route, $this);
  156. }
  157. $msg = ob_get_contents(); // create msg object; first use here
  158. ob_end_clean();
  159. /**
  160. * ---------------------------------------------------------------------------------------------
  161. * Filesystem Processing Section
  162. * ---------------------------------------------------------------------------------------------
  163. */
  164. // If the plugin directory does not exist, lets create it
  165. $created = false;
  166. if (!file_exists($this->parent->getPath('extension_root')))
  167. {
  168. if (!$created = JFolder::create($this->parent->getPath('extension_root')))
  169. {
  170. $this->parent->abort(JText::_('Plugin').' '.JText::_($this->route).': '.JText::_('FAILED_TO_CREATE_DIRECTORY').': "'.$this->parent->getPath('extension_root').'"');
  171. return false;
  172. }
  173. }
  174. /*
  175. * If we created the plugin directory and will want to remove it if we
  176. * have to roll back the installation, lets add it to the installation
  177. * step stack
  178. */
  179. if ($created) {
  180. $this->parent->pushStep(array ('type' => 'folder', 'path' => $this->parent->getPath('extension_root')));
  181. }
  182. // Copy all necessary files
  183. if ($this->parent->parseFiles($xml->files, -1) === false)
  184. {
  185. // Install failed, roll back changes
  186. $this->parent->abort();
  187. return false;
  188. }
  189. // Parse optional tags -- media and language files for plugins go in admin app
  190. $this->parent->parseMedia($xml->media, 1);
  191. $this->parent->parseLanguages($xml->languages);
  192. $this->parent->parseLanguages($xml->administration->languages, 1);
  193. // If there is a manifest script, lets copy it.
  194. if ($this->get('manifest_script'))
  195. {
  196. $path['src'] = $this->parent->getPath('source').DS.$this->get('manifest_script');
  197. $path['dest'] = $this->parent->getPath('extension_root').DS.$this->get('manifest_script');
  198. if (!file_exists($path['dest']))
  199. {
  200. if (!$this->parent->copyFiles(array ($path)))
  201. {
  202. // Install failed, rollback changes
  203. $this->parent->abort(JText::_('Plugin').' '.JText::_($this->route).': '.JText::_('Could not copy PHP manifest file.'));
  204. return false;
  205. }
  206. }
  207. }
  208. /**
  209. * ---------------------------------------------------------------------------------------------
  210. * Database Processing Section
  211. * ---------------------------------------------------------------------------------------------
  212. */
  213. // Was there a plugin already installed with the same name?
  214. if ($id)
  215. {
  216. if (!$this->parent->getOverwrite())
  217. {
  218. // Install failed, roll back changes
  219. $this->parent->abort(JText::_('Plugin').' '.JText::_($this->route).': '.JText::_('Plugin').' "'. $this->get('name') .'" '.JText::_('ALREADY_EXISTS'));
  220. return false;
  221. }
  222. }
  223. else
  224. {
  225. // Store in the extensions table (1.6)
  226. $row = & JTable::getInstance('extension');
  227. $row->name = $this->get('name');
  228. $row->type = 'plugin';
  229. $row->ordering = 0;
  230. $row->element = $element;
  231. $row->folder = $group;
  232. $row->enabled = 0;
  233. $row->protected = 0;
  234. $row->access = 1;
  235. $row->client_id = 0;
  236. $row->params = $this->parent->getParams();
  237. $row->custom_data = ''; // custom data
  238. $row->system_data = ''; // system data
  239. $row->manifest_cache = $this->parent->generateManifestCache();
  240. // Editor plugins are published by default
  241. if ($group == 'editors') {
  242. $row->enabled = 1;
  243. }
  244. if (!$row->store())
  245. {
  246. // Install failed, roll back changes
  247. $this->parent->abort(JText::_('Plugin').' '.JText::_($this->route).': '.$db->stderr(true));
  248. return false;
  249. }
  250. // Since we have created a plugin item, we add it to the installation step stack
  251. // so that if we have to rollback the changes we can undo it.
  252. $this->parent->pushStep(array ('type' => 'extension', 'id' => $row->extension_id));
  253. $id = $row->extension_id;
  254. }
  255. /*
  256. * Let's run the queries for the module
  257. * If Joomla 1.5 compatible, with discreet sql files - execute appropriate
  258. * file for utf-8 support or non-utf-8 support
  259. */
  260. // try for Joomla 1.5 type queries
  261. // second argument is the utf compatible version attribute
  262. $utfresult = $this->parent->parseSQLFiles($xml->{strtolower($this->route)}->sql);
  263. if ($utfresult === false)
  264. {
  265. // Install failed, rollback changes
  266. $this->parent->abort(JText::_('Module').' '.JText::_($this->route).': '.JText::_('SQLERRORORFILE')." ".$db->stderr(true));
  267. return false;
  268. }
  269. // Start Joomla! 1.6
  270. ob_start();
  271. ob_implicit_flush(false);
  272. if ($this->parent->manifestClass && method_exists($this->parent->manifestClass,$this->route)) {
  273. $this->parent->manifestClass->{$this->route}($this);
  274. }
  275. $msg .= ob_get_contents(); // append messages
  276. ob_end_clean();
  277. /**
  278. * ---------------------------------------------------------------------------------------------
  279. * Finalization and Cleanup Section
  280. * ---------------------------------------------------------------------------------------------
  281. */
  282. // Lastly, we will copy the manifest file to its appropriate place.
  283. if (!$this->parent->copyManifest(-1))
  284. {
  285. // Install failed, rollback changes
  286. $this->parent->abort(JText::_('Plugin').' '.JText::_($this->route).': '.JText::_('COULD_NOT_COPY_SETUP_FILE'));
  287. return false;
  288. }
  289. // And now we run the postflight
  290. ob_start();
  291. ob_implicit_flush(false);
  292. if ($this->parent->manifestClass && method_exists($this->parent->manifestClass,'postflight')) {
  293. $this->parent->manifestClass->postflight($this->route, $this);
  294. }
  295. $msg .= ob_get_contents(); // append messages
  296. ob_end_clean();
  297. if ($msg != '') {
  298. $this->parent->set('extension_message', $msg);
  299. }
  300. return $id;
  301. }
  302. /**
  303. * Custom update method
  304. *
  305. * @access public
  306. * @return boolean True on success
  307. * @since 1.6
  308. */
  309. function update()
  310. {
  311. // set the overwrite setting
  312. $this->parent->setOverwrite(true);
  313. $this->parent->setUpgrade(true);
  314. // set the route for the install
  315. $this->route = 'Update';
  316. // go to install which handles updates properly
  317. return $this->install();
  318. }
  319. /**
  320. * Custom uninstall method
  321. *
  322. * @access public
  323. * @param int $cid The id of the plugin to uninstall
  324. * @param int $clientId The id of the client (unused)
  325. * @return boolean True on success
  326. * @since 1.5
  327. */
  328. public function uninstall($id)
  329. {
  330. // Initialise variables.
  331. $row = null;
  332. $retval = true;
  333. $db = &$this->parent->getDbo();
  334. // First order of business will be to load the module object table from the database.
  335. // This should give us the necessary information to proceed.
  336. $row = & JTable::getInstance('extension');
  337. if (!$row->load((int) $id))
  338. {
  339. JError::raiseWarning(100, JText::_('ERRORUNKOWNEXTENSION'));
  340. return false;
  341. }
  342. // Is the plugin we are trying to uninstall a core one?
  343. // Because that is not a good idea...
  344. if ($row->protected)
  345. {
  346. JError::raiseWarning(100, JText::_('Plugin').' '.JText::_('Uninstall').': '.JText::sprintf('WARNCOREPLUGIN', $row->name)."<br />".JText::_('WARNCOREPLUGIN2'));
  347. return false;
  348. }
  349. // Get the plugin folder so we can properly build the plugin path
  350. if (trim($row->folder) == '')
  351. {
  352. JError::raiseWarning(100, JText::_('Plugin').' '.JText::_('Uninstall').': '.JText::_('Folder field empty, cannot remove files'));
  353. return false;
  354. }
  355. // Set the plugin root path
  356. if (is_dir(JPATH_ROOT.DS.'plugins'.DS.$row->folder.DS.$row->element)) {
  357. // Use 1.6 plugins
  358. $this->parent->setPath('extension_root', JPATH_ROOT.DS.'plugins'.DS.$row->folder.DS.$row->element);
  359. }
  360. else {
  361. // Use Legacy 1.5 plugins
  362. $this->parent->setPath('extension_root', JPATH_ROOT.DS.'plugins'.DS.$row->folder);
  363. }
  364. // Because plugins don't have their own folders we cannot use the standard method of finding an installation manifest
  365. // Since 1.6 they do, however until we move to 1.7 and remove 1.6 legacy we still need to use this method
  366. // when we get there it'll be something like "$manifest = &$this->parent->getManifest();"
  367. $manifestFile = $this->parent->getPath('extension_root').DS.$row->element.'.xml';
  368. if ( ! file_exists($manifestFile))
  369. {
  370. JError::raiseWarning(100, 'Plugin Uninstall: Manifest File invalid or not found');
  371. return false;
  372. }
  373. $xml = JFactory::getXML($manifestFile);
  374. $this->manifest = $xml;
  375. // If we cannot load the xml file return null
  376. if (!$xml)
  377. {
  378. JError::raiseWarning(100, JText::_('Plugin').' '.JText::_('Uninstall').': '.JText::_('Could not load manifest file'));
  379. return false;
  380. }
  381. /*
  382. * Check for a valid XML root tag.
  383. * @todo: Remove backwards compatability in a future version
  384. * Should be 'extension', but for backward compatability we will accept 'install'.
  385. */
  386. if ($xml->getName() != 'install' && $xml->getName() != 'extension')
  387. {
  388. JError::raiseWarning(100, JText::_('Plugin').' '.JText::_('Uninstall').': '.JText::_('Invalid manifest file'));
  389. return false;
  390. }
  391. /**
  392. * ---------------------------------------------------------------------------------------------
  393. * Installer Trigger Loading
  394. * ---------------------------------------------------------------------------------------------
  395. */
  396. // If there is an manifest class file, lets load it; we'll copy it later (don't have dest yet)
  397. $manifestScript = (string)$xml->scriptfile;
  398. if ($manifestScript)
  399. {
  400. $manifestScriptFile = $this->parent->getPath('source').DS.$manifestScript;
  401. if (is_file($manifestScriptFile)) {
  402. // load the file
  403. include_once $manifestScriptFile;
  404. }
  405. // Set the class name
  406. $classname = 'plg'.$row->folder.$row->element.'InstallerScript';
  407. if (class_exists($classname))
  408. {
  409. // create a new instance
  410. $this->parent->manifestClass = new $classname($this);
  411. // and set this so we can copy it later
  412. $this->set('manifest_script', $manifestScript);
  413. // Note: if we don't find the class, don't bother to copy the file
  414. }
  415. }
  416. // run preflight if possible (since we know we're not an update)
  417. ob_start();
  418. ob_implicit_flush(false);
  419. if ($this->parent->manifestClass && method_exists($this->parent->manifestClass,'preflight')) {
  420. $this->parent->manifestClass->preflight($this->route, $this);
  421. }
  422. $msg = ob_get_contents(); // create msg object; first use here
  423. ob_end_clean();
  424. /*
  425. * Let's run the queries for the module
  426. * If Joomla 1.5 compatible, with discreet sql files - execute appropriate
  427. * file for utf-8 support or non-utf-8 support
  428. */
  429. // try for Joomla 1.5 type queries
  430. // second argument is the utf compatible version attribute
  431. $utfresult = $this->parent->parseSQLFiles($xml->{strtolower($this->route)}->sql);
  432. if ($utfresult === false)
  433. {
  434. // Install failed, rollback changes
  435. $this->parent->abort(JText::_('Plugin').' '.JText::_('Uninstall').': '.JText::_('SQLERRORORFILE')." ".$db->stderr(true));
  436. return false;
  437. }
  438. // Start Joomla! 1.6
  439. ob_start();
  440. ob_implicit_flush(false);
  441. if ($this->parent->manifestClass && method_exists($this->parent->manifestClass,'uninstall')) {
  442. $this->parent->manifestClass->uninstall($this);
  443. }
  444. $msg = ob_get_contents(); // append messages
  445. ob_end_clean();
  446. // Remove the plugin files
  447. $this->parent->removeFiles($xml->images, -1);
  448. $this->parent->removeFiles($xml->files, -1);
  449. JFile::delete($manifestFile);
  450. // Remove all media and languages as well
  451. $this->parent->removeFiles($xml->media);
  452. $this->parent->parseLanguages($xml->languages);
  453. $this->parent->parseLanguages($xml->administration->languages, 1);
  454. // Now we will no longer need the plugin object, so lets delete it
  455. $row->delete($row->extension_id);
  456. unset ($row);
  457. // If the folder is empty, let's delete it
  458. $files = JFolder::files($this->parent->getPath('extension_root'));
  459. if (!count($files)) {
  460. JFolder::delete($this->parent->getPath('extension_root'));
  461. }
  462. if ($msg) {
  463. $this->parent->set('extension_message',$msg);
  464. }
  465. return $retval;
  466. }
  467. /**
  468. * Custom discover method
  469. *
  470. * @access public
  471. * @return array(JExtension) list of extensions available
  472. * @since 1.6
  473. */
  474. function discover()
  475. {
  476. $results = Array();
  477. $folder_list = JFolder::folders(JPATH_SITE.DS.'plugins');
  478. foreach ($folder_list as $folder)
  479. {
  480. $file_list = JFolder::files(JPATH_SITE.DS.'plugins'.DS.$folder,'\.xml$');
  481. foreach ($file_list as $file)
  482. {
  483. $file = JFile::stripExt($file);
  484. if ($file == 'example') continue; // ignore example plugins
  485. $extension = &JTable::getInstance('extension');
  486. $extension->set('type', 'plugin');
  487. $extension->set('client_id', 0);
  488. $extension->set('element', $file);
  489. $extension->set('folder', $folder);
  490. $extension->set('name', $file);
  491. $extension->set('state', -1);
  492. $results[] = $extension;
  493. }
  494. $folder_list = JFolder::folders(JPATH_SITE.DS.'plugins'.DS.$folder);
  495. foreach ($folder_list as $plugin_folder)
  496. {
  497. $file_list = JFolder::files(JPATH_SITE.DS.'plugins'.DS.$folder.DS.$plugin_folder,'\.xml$');
  498. foreach ($file_list as $file)
  499. {
  500. $file = JFile::stripExt($file);
  501. if ($file == 'example') continue; // ignore example plugins
  502. $extension = &JTable::getInstance('extension');
  503. $extension->set('type', 'plugin');
  504. $extension->set('client_id', 0);
  505. $extension->set('element', $file);
  506. $extension->set('folder', $folder);
  507. $extension->set('name', $file);
  508. $extension->set('state', -1);
  509. $results[] = $extension;
  510. }
  511. }
  512. }
  513. return $results;
  514. }
  515. /**
  516. * Custom discover_install method
  517. *
  518. * @access public
  519. * @param int $id The id of the extension to install (from #__discoveredextensions)
  520. * @return void
  521. * @since 1.6
  522. */
  523. function discover_install()
  524. {
  525. // Plugins use the extensions table as their primary store
  526. // Similar to modules and templates, rather easy
  527. // If its not in the extensions table we just add it
  528. $client = JApplicationHelper::getClientInfo($this->parent->extension->client_id);
  529. if (is_dir($client->path . DS . 'plugins'. DS . $this->parent->extension->folder . DS . $this->parent->extension->element)) {
  530. $manifestPath = $client->path . DS . 'plugins'. DS . $this->parent->extension->folder . DS . $this->parent->extension->element . DS . $this->parent->extension->element . '.xml';
  531. }
  532. else {
  533. $manifestPath = $client->path . DS . 'plugins'. DS . $this->parent->extension->folder . DS . $this->parent->extension->element . '.xml';
  534. }
  535. $this->parent->manifest = $this->parent->isManifest($manifestPath);
  536. $this->parent->setPath('manifest', $manifestPath);
  537. $manifest_details = JApplicationHelper::parseXMLInstallFile($this->parent->getPath('manifest'));
  538. $this->parent->extension->manifest_cache = serialize($manifest_details);
  539. $this->parent->extension->state = 0;
  540. $this->parent->extension->name = $manifest_details['name'];
  541. $this->parent->extension->enabled = 1;
  542. $this->parent->extension->params = $this->parent->getParams();
  543. if ($this->parent->extension->store()) {
  544. return $this->parent->extension->get('extension_id');
  545. }
  546. else
  547. {
  548. JError::raiseWarning(101, JText::_('Plugin').' '.JText::_('Discover Install').': '.JText::_('Failed to store extension details'));
  549. return false;
  550. }
  551. }
  552. function refreshManifestCache()
  553. {
  554. // Plugins use the extensions table as their primary store
  555. // Similar to modules and templates, rather easy
  556. // If its not in the extensions table we just add it
  557. $client = JApplicationHelper::getClientInfo($this->parent->extension->client_id);
  558. $manifestPath = $client->path . DS . 'plugins'. DS . $this->parent->extension->folder . DS . $this->parent->extension->element . '.xml';
  559. $this->parent->manifest = $this->parent->isManifest($manifestPath);
  560. $this->parent->setPath('manifest', $manifestPath);
  561. $manifest_details = JApplicationHelper::parseXMLInstallFile($this->parent->getPath('manifest'));
  562. $this->parent->extension->manifest_cache = serialize($manifest_details);
  563. $this->parent->extension->name = $manifest_details['name'];
  564. if ($this->parent->extension->store()) {
  565. return true;
  566. }
  567. else
  568. {
  569. JError::raiseWarning(101, JText::_('Plugin').' '.JText::_('Refresh Manifest Cache').': '.JText::_('Failed to store extension details'));
  570. return false;
  571. }
  572. }
  573. }