Development

/branches/1.1/lib/plugin/sfPluginManager.class.php

You must first sign up to be able to contribute.

root/branches/1.1/lib/plugin/sfPluginManager.class.php

Revision 18030, 15.0 kB (checked in by fabien, 5 years ago)

[1.1, 1.2, 1.3] added more information to the error message when a plugin dependency is not installed

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