1. sfPluginManager.class.php
  2. /** * sfPluginManager allows you to manage plugins installation and uninstallation. * * @package symfony * @subpackage plugin * @author Fabien Potencier * @version SVN: $Id: sfPluginManager.class.php 21908 2009-09-11 12:06:21Z fabien $ */
  3. class sfPluginManager
  4. {
  5. protected
  6. $dispatcher = null,
  7. $environment = null,
  8. $installing = array();
  9. /**
  10. * Constructs a new sfPluginManager.
  11. *
  12. * @param sfEventDispatcher $dispatcher An event dispatcher instance
  13. * @param sfPearEnvironment $environment A sfPearEnvironment instance
  14. */
  15. public function __construct(sfEventDispatcher $dispatcher, sfPearEnvironment $environment)
  16. {
  17. $this->initialize($dispatcher, $environment);
  18. }
  19. /**
  20. * Initializes this sfPluginManager instance.
  21. *
  22. * see sfPearEnvironment for available options.
  23. *
  24. * @param sfEventDispatcher $dispatcher An event dispatcher instance
  25. * @param sfPearEnvironment $environment A sfPearEnvironment instance
  26. */
  27. public function initialize(sfEventDispatcher $dispatcher, sfPearEnvironment $environment)
  28. {
  29. $this->dispatcher = $dispatcher;
  30. $this->environment = $environment;
  31. // configure this plugin manager
  32. $this->configure();
  33. }
  34. /**
  35. * Configures this plugin manager.
  36. */
  37. public function configure()
  38. {
  39. }
  40. /**
  41. * Returns the sfPearEnvironment instance.
  42. *
  43. * @return sfPearEnvironment The sfPearEnvironment instance
  44. */
  45. public function getEnvironment()
  46. {
  47. return $this->environment;
  48. }
  49. /**
  50. * Returns a list of installed plugin.
  51. *
  52. * @return array An array of installed plugins
  53. */
  54. public function getInstalledPlugins()
  55. {
  56. $installed = array();
  57. foreach ($this->environment->getRegistry()->packageInfo(null, null, null) as $channel => $packages)
  58. {
  59. foreach ($packages as $package)
  60. {
  61. $installed[] = $this->environment->getRegistry()->getPackage(isset($package['package']) ? $package['package'] : $package['name'], $channel);
  62. }
  63. }
  64. return $installed;
  65. }
  66. /**
  67. * Installs a plugin.
  68. *
  69. * If you don't pass a version, it will install the latest version available
  70. * for the current project symfony version.
  71. *
  72. * Available options:
  73. *
  74. * * channel: The plugin channel name
  75. * * version: The version to install
  76. * * stability: The stability preference
  77. * * install_deps: Whether to automatically install dependencies (default to false)
  78. *
  79. * @param string $plugin The plugin name
  80. * @param array $options An array of options
  81. *
  82. * @return Boolean|string true if the plugin is already installed, the name of the installed plugin otherwise
  83. */
  84. public function installPlugin($plugin, $options = array())
  85. {
  86. $this->installing = array();
  87. return $this->doInstallPlugin($plugin, $options);
  88. }
  89. /**
  90. * Installs a plugin
  91. *
  92. * @see installPlugin()
  93. */
  94. protected function doInstallPlugin($plugin, $options = array())
  95. {
  96. $channel = isset($options['channel']) ? $options['channel'] : $this->environment->getConfig()->get('default_channel');
  97. $stability = isset($options['stability']) ? $options['stability'] : $this->environment->getConfig()->get('preferred_state', null, $channel);
  98. $version = isset($options['version']) ? $options['version'] : null;
  99. $isPackage = true;
  100. if (0 === strpos($plugin, 'http://') || file_exists($plugin))
  101. {
  102. if (0 === strpos($plugin, 'http://plugins.symfony-project.'))
  103. {
  104. throw new sfPluginException("You try to install a symfony 1.0 plugin.\nPlease read the help message of this task to know how to install a plugin for the current version of symfony.");
  105. }
  106. $download = $plugin;
  107. $isPackage = false;
  108. }
  109. else if (false !== strpos($plugin, '/'))
  110. {
  111. list($channel, $plugin) = explode('/', $plugin);
  112. }
  113. $this->dispatcher->notify(new sfEvent($this, 'plugin.pre_install', array('channel' => $channel, 'plugin' => $plugin, 'is_package' => $isPackage)));
  114. if ($isPackage)
  115. {
  116. $this->environment->getRest()->setChannel($channel);
  117. if (!preg_match(PEAR_COMMON_PACKAGE_NAME_PREG, $plugin))
  118. {
  119. throw new sfPluginException(sprintf('Plugin name "%s" is not a valid package name', $plugin));
  120. }
  121. if (!$version)
  122. {
  123. $version = $this->getPluginVersion($plugin, $stability);
  124. }
  125. else
  126. {
  127. if (!$this->isPluginCompatible($plugin, $version))
  128. {
  129. throw new sfPluginDependencyException(sprintf('Plugin "%s" in version "%s" is not compatible with the current application', $plugin, $version));
  130. }
  131. }
  132. if (!preg_match(PEAR_COMMON_PACKAGE_VERSION_PREG, $version))
  133. {
  134. throw new sfPluginException(sprintf('Plugin version "%s" is not a valid version', $version));
  135. }
  136. $existing = $this->environment->getRegistry()->packageInfo($plugin, 'version', $channel);
  137. if (version_compare($existing, $version) === 0)
  138. {
  139. $this->dispatcher->notify(new sfEvent($this, 'application.log', array('Plugin is already installed')));
  140. return true;
  141. }
  142. // skip if the plugin is already installing and we are here through a dependency)
  143. if (isset($this->installing[$channel.'/'.$plugin]))
  144. {
  145. return true;
  146. }
  147. // convert the plugin package into a discrete download URL
  148. $download = $this->environment->getRest()->getPluginDownloadURL($plugin, $version, $stability);
  149. if (PEAR::isError($download))
  150. {
  151. throw new sfPluginException(sprintf('Problem downloading the plugin "%s": %s', $plugin, $download->getMessage()));
  152. }
  153. }
  154. // download the plugin and install
  155. $class = $this->environment->getOption('downloader_base_class');
  156. $downloader = new $class($this, array('upgrade' => true), $this->environment->getConfig());
  157. $this->installing[$channel.'/'.$plugin] = true;
  158. if ($isPackage)
  159. {
  160. $this->checkPluginDependencies($plugin, $version, array(
  161. 'install_deps' => isset($options['install_deps']) ? (bool) $options['install_deps'] : false,
  162. 'stability' => $stability,
  163. ));
  164. }
  165. // download the actual URL to the plugin
  166. $downloaded = $downloader->download(array($download));
  167. if (PEAR::isError($downloaded))
  168. {
  169. throw new sfPluginException(sprintf('Problem when downloading "%s": %s', $download, $downloaded->getMessage()));
  170. }
  171. $errors = $downloader->getErrorMsgs();
  172. if (count($errors))
  173. {
  174. $err = array();
  175. foreach ($errors as $error)
  176. {
  177. $err[] = $error;
  178. }
  179. if (!count($downloaded))
  180. {
  181. throw new sfPluginException(sprintf('Plugin "%s" installation failed: %s', $plugin, implode("\n", $err)));
  182. }
  183. }
  184. $pluginPackage = $downloaded[0];
  185. $installer = new PEAR_Installer($this);
  186. $installer->setOptions(array('upgrade' => true));
  187. $packages = array($pluginPackage);
  188. $installer->sortPackagesForInstall($packages);
  189. PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  190. $err = $installer->setDownloadedPackages($packages);
  191. if (PEAR::isError($err))
  192. {
  193. PEAR::staticPopErrorHandling();
  194. throw new sfPluginException($err->getMessage());
  195. }
  196. $info = $installer->install($pluginPackage, array('upgrade' => true));
  197. PEAR::staticPopErrorHandling();
  198. if (PEAR::isError($info))
  199. {
  200. throw new sfPluginException(sprintf('Installation of "%s" plugin failed: %s', $plugin, $info->getMessage()));
  201. }
  202. if (is_array($info))
  203. {
  204. $this->dispatcher->notify(new sfEvent($this, 'application.log', array(sprintf('Installation successful for plugin "%s"', $plugin))));
  205. $this->dispatcher->notify(new sfEvent($this, 'plugin.post_install', array('channel' => $channel, 'plugin' => $pluginPackage->getPackage())));
  206. unset($this->installing[$channel.'/'.$plugin]);
  207. return $pluginPackage->getPackage();
  208. }
  209. else
  210. {
  211. throw new sfPluginException(sprintf('Installation of "%s" plugin failed', $plugin));
  212. }
  213. }
  214. /**
  215. * Uninstalls a plugin.
  216. *
  217. * @param string $plugin The plugin name
  218. * @param string $channel The channel name
  219. */
  220. public function uninstallPlugin($plugin, $channel = null)
  221. {
  222. if (false !== strpos($plugin, '/'))
  223. {
  224. list($channel, $plugin) = explode('/', $plugin);
  225. }
  226. $channel = null === $channel ? $this->environment->getConfig()->get('default_channel') : $channel;
  227. $existing = $this->environment->getRegistry()->packageInfo($plugin, 'version', $channel);
  228. if (null === $existing)
  229. {
  230. $this->dispatcher->notify(new sfEvent($this, 'application.log', array(sprintf('Plugin "%s" is not installed', $plugin))));
  231. return false;
  232. }
  233. $this->dispatcher->notify(new sfEvent($this, 'plugin.pre_uninstall', array('channel' => $channel, 'plugin' => $plugin)));
  234. $package = $this->environment->getRegistry()->parsePackageName($plugin, $channel);
  235. $installer = new PEAR_Installer($this);
  236. $packages = array($this->environment->getRegistry()->getPackage($plugin, $channel));
  237. $installer->setUninstallPackages($packages);
  238. $ret = $installer->uninstall($package);
  239. if (PEAR::isError($ret))
  240. {
  241. throw new sfPluginException(sprintf('Problem uninstalling plugin "%s": %s', $plugin, $ret->getMessage()));
  242. }
  243. if ($ret)
  244. {
  245. $this->dispatcher->notify(new sfEvent($this, 'application.log', array(sprintf('Uninstallation successful for plugin "%s"', $plugin))));
  246. $this->dispatcher->notify(new sfEvent($this, 'plugin.post_uninstall', array('channel' => $channel, 'plugin' => $plugin)));
  247. }
  248. else
  249. {
  250. throw new sfPluginException(sprintf('Uninstallation of "%s" plugin failed', $plugin));
  251. }
  252. return $ret;
  253. }
  254. /**
  255. * Checks all plugin dependencies.
  256. *
  257. * Available options:
  258. *
  259. * * stability: The stability preference
  260. * * install_deps: Whether to automatically install dependencies (default to false)
  261. *
  262. * @param string $plugin The plugin name
  263. * @param string $version The plugin version
  264. * @param array $options An array of options
  265. */
  266. public function checkPluginDependencies($plugin, $version, $options = false)
  267. {
  268. $dependencies = $this->environment->getRest()->getPluginDependencies($plugin, $version);
  269. if (!isset($dependencies['required']) || !isset($dependencies['required']['package']))
  270. {
  271. return;
  272. }
  273. $deps = $dependencies['required']['package'];
  274. if (!isset($deps[0]))
  275. {
  276. $deps = array($deps);
  277. }
  278. foreach ($deps as $dependency)
  279. {
  280. if (!$this->checkDependency($dependency))
  281. {
  282. $version = (isset($dependency['min']) ? ' >= '.$dependency['min'] : '').(isset($dependency['max']) ? ' <= '.$dependency['max'] : '').(isset($dependency['exclude']) ? ' exclude '.$dependency['exclude'] : '');
  283. if (isset($options['install_deps']) && $options['install_deps'])
  284. {
  285. try
  286. {
  287. $this->doInstallPlugin($dependency['name'], array_merge($options, array('channel' => $dependency['channel'])));
  288. }
  289. catch (sfException $e)
  290. {
  291. throw new sfPluginRecursiveDependencyException(sprintf('Unable to install plugin "%s" (version %s) because it depends on plugin "%s" which cannot be installed automatically: %s', $plugin, $version, $dependency['name'], $e->getMessage()));
  292. }
  293. continue;
  294. }
  295. throw new sfPluginDependencyException(sprintf('Unable to install plugin "%s" (version %s) because it depends on plugin "%s" which is not installed (install dependencies by hand or use the --install_deps option for automatic installation).', $plugin, $version, $dependency['name']));
  296. }
  297. }
  298. }
  299. /**
  300. * Gets the "best" version available for a given plugin.
  301. *
  302. * @param string $plugin The plugin name
  303. * @param string $stability The stability name
  304. *
  305. * @return string The version
  306. */
  307. public function getPluginVersion($plugin, $stability = null)
  308. {
  309. $versions = $this->environment->getRest()->getPluginVersions($plugin, $stability);
  310. foreach ($versions as $version)
  311. {
  312. if (!$this->isPluginCompatible($plugin, $version))
  313. {
  314. continue;
  315. }
  316. return $version;
  317. }
  318. throw new sfPluginDependencyException(sprintf('No release available for plugin "%s" in state "%s" that satisfies the application requirements.', $plugin, $stability));
  319. }
  320. /**
  321. * Returns true if the plugin is comptatible with your environment.
  322. *
  323. * @param string $plugin The plugin name
  324. * @param string $version The plugin version
  325. *
  326. * @return Boolean true if the plugin is compatible, false otherwise
  327. */
  328. public function isPluginCompatible($plugin, $version)
  329. {
  330. $dependencies = $this->environment->getRest()->getPluginDependencies($plugin, $version);
  331. if (!isset($dependencies['required']) || !isset($dependencies['required']['package']))
  332. {
  333. return true;
  334. }
  335. $deps = $dependencies['required']['package'];
  336. if (!isset($deps[0]))
  337. {
  338. $deps = array($deps);
  339. }
  340. foreach ($deps as $dependency)
  341. {
  342. if (!$this->isPluginCompatibleWithDependency($dependency))
  343. {
  344. return false;
  345. }
  346. }
  347. return true;
  348. }
  349. /**
  350. * Returns the license for a given plugin.
  351. *
  352. * @param string $plugin The plugin name
  353. * @param array $options An array of options
  354. *
  355. * @return string The license
  356. *
  357. * @see installPlugin() for available options
  358. */
  359. public function getPluginLicense($plugin, $options = array())
  360. {
  361. $channel = isset($options['channel']) ? $options['channel'] : $this->environment->getConfig()->get('default_channel');
  362. $stability = isset($options['stability']) ? $options['stability'] : $this->environment->getConfig()->get('preferred_state', null, $channel);
  363. $version = isset($options['version']) ? $options['version'] : null;
  364. $rest = $this->environment->getRest();
  365. $rest->setChannel(null === $channel ? $this->environment->getConfig()->get('default_channel') : $channel);
  366. if (null === $version)
  367. {
  368. try
  369. {
  370. $version = $this->getPluginVersion($plugin, $stability);
  371. }
  372. catch (Exception $e)
  373. {
  374. // no release available
  375. return false;
  376. }
  377. }
  378. else
  379. {
  380. if (!$this->isPluginCompatible($plugin, $version))
  381. {
  382. throw new sfPluginDependencyException(sprintf('Plugin "%s" in version "%s" is not compatible with the current application', $plugin, $version));
  383. }
  384. }
  385. return $rest->getPluginLicense($plugin, $version);
  386. }
  387. /**
  388. * Returns true if the plugin is comptatible with the dependency.
  389. *
  390. * @param array $dependency An dependency array
  391. *
  392. * @return Boolean true if the plugin is compatible, false otherwise
  393. */
  394. protected function isPluginCompatibleWithDependency($dependency)
  395. {
  396. return true;
  397. }
  398. /**
  399. * Checks that the dependency is valid.
  400. *
  401. * @param array $dependency A dependency array
  402. *
  403. * @return Boolean true if the dependency is valid, false otherwise
  404. */
  405. protected function checkDependency($dependency)
  406. {
  407. $dependencyChecker = new PEAR_Dependency2($this->environment->getConfig(), array(), array('package' => '', 'channel' => ''));
  408. PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  409. $e = $dependencyChecker->validatePackageDependency($dependency, true, array());
  410. PEAR::staticPopErrorHandling();
  411. if (PEAR::isError($e))
  412. {
  413. return false;
  414. }
  415. return true;
  416. }
  417. }

Debug toolbar