PageRenderTime 61ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/joomla/installer/adapters/package.php

http://github.com/joomla/joomla-platform
PHP | 570 lines | 349 code | 81 blank | 140 comment | 45 complexity | 60c3cdf096302614ff31e0f4019bc70d MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. /**
  3. * @package Joomla.Platform
  4. * @subpackage Installer
  5. *
  6. * @copyright Copyright (C) 2005 - 2012 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.base.adapterinstance');
  11. jimport('joomla.installer.packagemanifest');
  12. /**
  13. * Package installer
  14. *
  15. * @package Joomla.Platform
  16. * @subpackage Installer
  17. * @since 11.1
  18. */
  19. class JInstallerPackage extends JAdapterInstance
  20. {
  21. /**
  22. * Method of system
  23. *
  24. * @var string
  25. *
  26. * @since 11.1
  27. */
  28. protected $route = 'install';
  29. /**
  30. * Load language from a path
  31. *
  32. * @param string $path The path of the language.
  33. *
  34. * @return void
  35. *
  36. * @since 11.1
  37. */
  38. public function loadLanguage($path)
  39. {
  40. $this->manifest = $this->parent->getManifest();
  41. $extension = 'pkg_' . strtolower(JFilterInput::getInstance()->clean((string) $this->manifest->packagename, 'cmd'));
  42. $lang = JFactory::getLanguage();
  43. $source = $path;
  44. $lang->load($extension . '.sys', $source, null, false, false)
  45. || $lang->load($extension . '.sys', JPATH_SITE, null, false, false)
  46. || $lang->load($extension . '.sys', $source, $lang->getDefault(), false, false)
  47. || $lang->load($extension . '.sys', JPATH_SITE, $lang->getDefault(), false, false);
  48. }
  49. /**
  50. * Custom install method
  51. *
  52. * @return int The extension id
  53. *
  54. * @since 11.1
  55. */
  56. public function install()
  57. {
  58. // Get the extension manifest object
  59. $this->manifest = $this->parent->getManifest();
  60. // Manifest Document Setup Section
  61. // Set the extensions name
  62. $filter = JFilterInput::getInstance();
  63. $name = (string) $this->manifest->packagename;
  64. $name = $filter->clean($name, 'cmd');
  65. $this->set('name', $name);
  66. $element = 'pkg_' . $filter->clean($this->manifest->packagename, 'cmd');
  67. $this->set('element', $element);
  68. // Get the component description
  69. $description = (string) $this->manifest->description;
  70. if ($description)
  71. {
  72. $this->parent->set('message', JText::_($description));
  73. }
  74. else
  75. {
  76. $this->parent->set('message', '');
  77. }
  78. // Set the installation path
  79. $files = $this->manifest->files;
  80. $group = (string) $this->manifest->packagename;
  81. if (!empty($group))
  82. {
  83. $this->parent->setPath('extension_root', JPATH_MANIFESTS . '/packages/' . implode(DIRECTORY_SEPARATOR, explode('/', $group)));
  84. }
  85. else
  86. {
  87. $this->parent->abort(JText::sprintf('JLIB_INSTALLER_ABORT_PACK_INSTALL_NO_PACK', JText::_('JLIB_INSTALLER_' . strtoupper($this->route))));
  88. return false;
  89. }
  90. /**
  91. * ---------------------------------------------------------------------------------------------
  92. * Installer Trigger Loading
  93. * ---------------------------------------------------------------------------------------------
  94. */
  95. // If there is an manifest class file, lets load it; we'll copy it later (don't have dest yet)
  96. $this->scriptElement = $this->manifest->scriptfile;
  97. $manifestScript = (string) $this->manifest->scriptfile;
  98. if ($manifestScript)
  99. {
  100. $manifestScriptFile = $this->parent->getPath('source') . '/' . $manifestScript;
  101. if (is_file($manifestScriptFile))
  102. {
  103. // Load the file
  104. include_once $manifestScriptFile;
  105. }
  106. // Set the class name
  107. $classname = $element . 'InstallerScript';
  108. if (class_exists($classname))
  109. {
  110. // Create a new instance
  111. $this->parent->manifestClass = new $classname($this);
  112. // And set this so we can copy it later
  113. $this->set('manifest_script', $manifestScript);
  114. // Note: if we don't find the class, don't bother to copy the file
  115. }
  116. }
  117. // Run preflight if possible (since we know we're not an update)
  118. ob_start();
  119. ob_implicit_flush(false);
  120. if ($this->parent->manifestClass && method_exists($this->parent->manifestClass, 'preflight'))
  121. {
  122. if ($this->parent->manifestClass->preflight($this->route, $this) === false)
  123. {
  124. // Install failed, rollback changes
  125. $this->parent->abort(JText::_('JLIB_INSTALLER_ABORT_PACKAGE_INSTALL_CUSTOM_INSTALL_FAILURE'));
  126. return false;
  127. }
  128. }
  129. // Create msg object; first use here
  130. $msg = ob_get_contents();
  131. ob_end_clean();
  132. // Filesystem Processing Section
  133. if ($folder = $files->attributes()->folder)
  134. {
  135. $source = $this->parent->getPath('source') . '/' . $folder;
  136. }
  137. else
  138. {
  139. $source = $this->parent->getPath('source');
  140. }
  141. // Install all necessary files
  142. if (count($this->manifest->files->children()))
  143. {
  144. $i = 0;
  145. foreach ($this->manifest->files->children() as $child)
  146. {
  147. $file = $source . '/' . $child;
  148. if (is_dir($file))
  149. {
  150. // If it's actually a directory then fill it up
  151. $package = array();
  152. $package['dir'] = $file;
  153. $package['type'] = JInstallerHelper::detectType($file);
  154. }
  155. else
  156. {
  157. // If it's an archive
  158. $package = JInstallerHelper::unpack($file);
  159. }
  160. $tmpInstaller = new JInstaller;
  161. $installResult = $tmpInstaller->install($package['dir']);
  162. if (!$installResult)
  163. {
  164. $this->parent->abort(
  165. JText::sprintf(
  166. 'JLIB_INSTALLER_ABORT_PACK_INSTALL_ERROR_EXTENSION', JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
  167. basename($file)
  168. )
  169. );
  170. return false;
  171. }
  172. else
  173. {
  174. $results[$i] = array(
  175. 'name' => $tmpInstaller->manifest->name,
  176. 'result' => $installResult
  177. );
  178. }
  179. $i++;
  180. }
  181. }
  182. else
  183. {
  184. $this->parent->abort(JText::sprintf('JLIB_INSTALLER_ABORT_PACK_INSTALL_NO_FILES', JText::_('JLIB_INSTALLER_' . strtoupper($this->route))));
  185. return false;
  186. }
  187. // Parse optional tags
  188. $this->parent->parseLanguages($this->manifest->languages);
  189. // Extension Registration
  190. $row = JTable::getInstance('extension');
  191. $eid = $row->find(array('element' => strtolower($this->get('element')), 'type' => 'package'));
  192. if ($eid)
  193. {
  194. $row->load($eid);
  195. }
  196. else
  197. {
  198. $row->name = $this->get('name');
  199. $row->type = 'package';
  200. $row->element = $this->get('element');
  201. // There is no folder for modules
  202. $row->folder = '';
  203. $row->enabled = 1;
  204. $row->protected = 0;
  205. $row->access = 1;
  206. $row->client_id = 0;
  207. // Custom data
  208. $row->custom_data = '';
  209. $row->params = $this->parent->getParams();
  210. }
  211. // Update the manifest cache for the entry
  212. $row->manifest_cache = $this->parent->generateManifestCache();
  213. if (!$row->store())
  214. {
  215. // Install failed, roll back changes
  216. $this->parent->abort(JText::sprintf('JLIB_INSTALLER_ABORT_PACK_INSTALL_ROLLBACK', $row->getError()));
  217. return false;
  218. }
  219. // Finalization and Cleanup Section
  220. // Start Joomla! 1.6
  221. ob_start();
  222. ob_implicit_flush(false);
  223. if ($this->parent->manifestClass && method_exists($this->parent->manifestClass, $this->route))
  224. {
  225. if ($this->parent->manifestClass->{$this->route}($this) === false)
  226. {
  227. // Install failed, rollback changes
  228. $this->parent->abort(JText::_('JLIB_INSTALLER_ABORT_FILE_INSTALL_CUSTOM_INSTALL_FAILURE'));
  229. return false;
  230. }
  231. }
  232. // Append messages
  233. $msg .= ob_get_contents();
  234. ob_end_clean();
  235. // Lastly, we will copy the manifest file to its appropriate place.
  236. $manifest = array();
  237. $manifest['src'] = $this->parent->getPath('manifest');
  238. $manifest['dest'] = JPATH_MANIFESTS . '/packages/' . basename($this->parent->getPath('manifest'));
  239. if (!$this->parent->copyFiles(array($manifest), true))
  240. {
  241. // Install failed, rollback changes
  242. $this->parent->abort(
  243. JText::sprintf('JLIB_INSTALLER_ABORT_PACK_INSTALL_COPY_SETUP', JText::_('JLIB_INSTALLER_ABORT_PACK_INSTALL_NO_FILES'))
  244. );
  245. return false;
  246. }
  247. // If there is a manifest script, let's copy it.
  248. if ($this->get('manifest_script'))
  249. {
  250. // First, we have to create a folder for the script if one isn't present
  251. $created = false;
  252. if (!file_exists($this->parent->getPath('extension_root')))
  253. {
  254. JFolder::create($this->parent->getPath('extension_root'));
  255. }
  256. $path['src'] = $this->parent->getPath('source') . '/' . $this->get('manifest_script');
  257. $path['dest'] = $this->parent->getPath('extension_root') . '/' . $this->get('manifest_script');
  258. if (!file_exists($path['dest']) || $this->parent->isOverwrite())
  259. {
  260. if (!$this->parent->copyFiles(array($path)))
  261. {
  262. // Install failed, rollback changes
  263. $this->parent->abort(JText::_('JLIB_INSTALLER_ABORT_PACKAGE_INSTALL_MANIFEST'));
  264. return false;
  265. }
  266. }
  267. }
  268. // And now we run the postflight
  269. ob_start();
  270. ob_implicit_flush(false);
  271. if ($this->parent->manifestClass && method_exists($this->parent->manifestClass, 'postflight'))
  272. {
  273. $this->parent->manifestClass->postflight($this->route, $this, $results);
  274. }
  275. // Append messages
  276. $msg .= ob_get_contents();
  277. ob_end_clean();
  278. if ($msg != '')
  279. {
  280. $this->parent->set('extension_message', $msg);
  281. }
  282. return $row->extension_id;
  283. }
  284. /**
  285. * Updates a package
  286. *
  287. * The only difference between an update and a full install
  288. * is how we handle the database
  289. *
  290. * @return void
  291. *
  292. * @since 11.1
  293. */
  294. public function update()
  295. {
  296. $this->route = 'update';
  297. $this->install();
  298. }
  299. /**
  300. * Custom uninstall method
  301. *
  302. * @param integer $id The id of the package to uninstall.
  303. *
  304. * @return boolean True on success
  305. *
  306. * @since 11.1
  307. */
  308. public function uninstall($id)
  309. {
  310. // Initialise variables.
  311. $row = null;
  312. $retval = true;
  313. $row = JTable::getInstance('extension');
  314. $row->load($id);
  315. if ($row->protected)
  316. {
  317. JError::raiseWarning(100, JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_WARNCOREPACK'));
  318. return false;
  319. }
  320. $manifestFile = JPATH_MANIFESTS . '/packages/' . $row->get('element') . '.xml';
  321. $manifest = new JPackageManifest($manifestFile);
  322. // Set the package root path
  323. $this->parent->setPath('extension_root', JPATH_MANIFESTS . '/packages/' . $manifest->packagename);
  324. // Because packages may not have their own folders we cannot use the standard method of finding an installation manifest
  325. if (!file_exists($manifestFile))
  326. {
  327. // TODO: Fail?
  328. JError::raiseWarning(100, JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_MISSINGMANIFEST'));
  329. return false;
  330. }
  331. $xml = JFactory::getXML($manifestFile);
  332. // If we cannot load the XML file return false
  333. if (!$xml)
  334. {
  335. JError::raiseWarning(100, JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_LOAD_MANIFEST'));
  336. return false;
  337. }
  338. /*
  339. * Check for a valid XML root tag.
  340. */
  341. if ($xml->getName() != 'extension')
  342. {
  343. JError::raiseWarning(100, JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_INVALID_MANIFEST'));
  344. return false;
  345. }
  346. // If there is an manifest class file, let's load it
  347. $this->scriptElement = $manifest->scriptfile;
  348. $manifestScript = (string) $manifest->scriptfile;
  349. if ($manifestScript)
  350. {
  351. $manifestScriptFile = $this->parent->getPath('extension_root') . '/' . $manifestScript;
  352. if (is_file($manifestScriptFile))
  353. {
  354. // Load the file
  355. include_once $manifestScriptFile;
  356. }
  357. // Set the class name
  358. $classname = $row->element . 'InstallerScript';
  359. if (class_exists($classname))
  360. {
  361. // Create a new instance
  362. $this->parent->manifestClass = new $classname($this);
  363. // And set this so we can copy it later
  364. $this->set('manifest_script', $manifestScript);
  365. // Note: if we don't find the class, don't bother to copy the file
  366. }
  367. }
  368. ob_start();
  369. ob_implicit_flush(false);
  370. // Run uninstall if possible
  371. if ($this->parent->manifestClass && method_exists($this->parent->manifestClass, 'uninstall'))
  372. {
  373. $this->parent->manifestClass->uninstall($this);
  374. }
  375. $msg = ob_get_contents();
  376. ob_end_clean();
  377. $error = false;
  378. foreach ($manifest->filelist as $extension)
  379. {
  380. $tmpInstaller = new JInstaller;
  381. $id = $this->_getExtensionID($extension->type, $extension->id, $extension->client, $extension->group);
  382. $client = JApplicationHelper::getClientInfo($extension->client, true);
  383. if ($id)
  384. {
  385. if (!$tmpInstaller->uninstall($extension->type, $id, $client->id))
  386. {
  387. $error = true;
  388. JError::raiseWarning(100, JText::sprintf('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_NOT_PROPER', basename($extension->filename)));
  389. }
  390. }
  391. else
  392. {
  393. JError::raiseWarning(100, JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_UNKNOWN_EXTENSION'));
  394. }
  395. }
  396. // Remove any language files
  397. $this->parent->removeFiles($xml->languages);
  398. // Clean up manifest file after we're done if there were no errors
  399. if (!$error)
  400. {
  401. JFile::delete($manifestFile);
  402. $folder = $this->parent->getPath('extension_root');
  403. if (JFolder::exists($folder))
  404. {
  405. JFolder::delete($folder);
  406. }
  407. $row->delete();
  408. }
  409. else
  410. {
  411. JError::raiseWarning(100, JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_MANIFEST_NOT_REMOVED'));
  412. }
  413. // Return the result up the line
  414. return $retval;
  415. }
  416. /**
  417. * Gets the extension id.
  418. *
  419. * @param string $type The extension type.
  420. * @param string $id The name of the extension (the element field).
  421. * @param integer $client The application id (0: Joomla CMS site; 1: Joomla CMS administrator).
  422. * @param string $group The extension group (mainly for plugins).
  423. *
  424. * @return integer
  425. *
  426. * @since 11.1
  427. */
  428. protected function _getExtensionID($type, $id, $client, $group)
  429. {
  430. $db = $this->parent->getDbo();
  431. $result = $id;
  432. $query = $db->getQuery(true);
  433. $query->select('extension_id');
  434. $query->from('#__extensions');
  435. $query->where('type = ' . $db->Quote($type));
  436. $query->where('element = ' . $db->Quote($id));
  437. switch ($type)
  438. {
  439. case 'plugin':
  440. // Plugins have a folder but not a client
  441. $query->where('folder = ' . $db->Quote($group));
  442. break;
  443. case 'library':
  444. case 'package':
  445. case 'component':
  446. // Components, packages and libraries don't have a folder or client.
  447. // Included for completeness.
  448. break;
  449. case 'language':
  450. case 'module':
  451. case 'template':
  452. // Languages, modules and templates have a client but not a folder
  453. $client = JApplicationHelper::getClientInfo($client, true);
  454. $query->where('client_id = ' . (int) $client->id);
  455. break;
  456. }
  457. $db->setQuery($query);
  458. $result = $db->loadResult();
  459. // Note: For templates, libraries and packages their unique name is their key.
  460. // This means they come out the same way they came in.
  461. return $result;
  462. }
  463. /**
  464. * Refreshes the extension table cache
  465. *
  466. * @return boolean Result of operation, true if updated, false on failure
  467. *
  468. * @since 11.1
  469. */
  470. public function refreshManifestCache()
  471. {
  472. // Need to find to find where the XML file is since we don't store this normally
  473. $manifestPath = JPATH_MANIFESTS . '/packages/' . $this->parent->extension->element . '.xml';
  474. $this->parent->manifest = $this->parent->isManifest($manifestPath);
  475. $this->parent->setPath('manifest', $manifestPath);
  476. $manifest_details = JApplicationHelper::parseXMLInstallFile($this->parent->getPath('manifest'));
  477. $this->parent->extension->manifest_cache = json_encode($manifest_details);
  478. $this->parent->extension->name = $manifest_details['name'];
  479. try
  480. {
  481. return $this->parent->extension->store();
  482. }
  483. catch (JException $e)
  484. {
  485. JError::raiseWarning(101, JText::_('JLIB_INSTALLER_ERROR_PACK_REFRESH_MANIFEST_CACHE'));
  486. return false;
  487. }
  488. }
  489. }