Development

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

You must first sign up to be able to contribute.

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

Revision 21908, 15.5 kB (checked in by fabien, 5 years ago)

[1.3] made some optimizations by replacing is_null() by null ===

  • 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, sfPearEnvironment $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    * @return Boolean|string true if the plugin is already installed, the name of the installed plugin otherwise
107    */
108   public function installPlugin($plugin, $options = array())
109   {
110     $this->installing = array();
111
112     return $this->doInstallPlugin($plugin, $options);
113   }
114
115   /**
116    * Installs a plugin
117    *
118    * @see installPlugin()
119    */
120   protected function doInstallPlugin($plugin, $options = array())
121   {
122     $channel   = isset($options['channel']) ? $options['channel'] : $this->environment->getConfig()->get('default_channel');
123     $stability = isset($options['stability']) ? $options['stability'] : $this->environment->getConfig()->get('preferred_state', null, $channel);
124     $version   = isset($options['version']) ? $options['version'] : null;
125
126     $isPackage = true;
127     if (0 === strpos($plugin, 'http://') || file_exists($plugin))
128     {
129       if (0 === strpos($plugin, 'http://plugins.symfony-project.'))
130       {
131         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.");
132       }
133
134       $download  = $plugin;
135       $isPackage = false;
136     }
137     else if (false !== strpos($plugin, '/'))
138     {
139       list($channel, $plugin) = explode('/', $plugin);
140     }
141
142     $this->dispatcher->notify(new sfEvent($this, 'plugin.pre_install', array('channel' => $channel, 'plugin' => $plugin, 'is_package' => $isPackage)));
143
144     if ($isPackage)
145     {
146       $this->environment->getRest()->setChannel($channel);
147
148       if (!preg_match(PEAR_COMMON_PACKAGE_NAME_PREG, $plugin))
149       {
150         throw new sfPluginException(sprintf('Plugin name "%s" is not a valid package name', $plugin));
151       }
152
153       if (!$version)
154       {
155         $version = $this->getPluginVersion($plugin, $stability);
156       }
157       else
158       {
159         if (!$this->isPluginCompatible($plugin, $version))
160         {
161           throw new sfPluginDependencyException(sprintf('Plugin "%s" in version "%s" is not compatible with the current application', $plugin, $version));
162         }
163       }
164
165       if (!preg_match(PEAR_COMMON_PACKAGE_VERSION_PREG, $version))
166       {
167         throw new sfPluginException(sprintf('Plugin version "%s" is not a valid version', $version));
168       }
169
170       $existing = $this->environment->getRegistry()->packageInfo($plugin, 'version', $channel);
171       if (version_compare($existing, $version) === 0)
172       {
173         $this->dispatcher->notify(new sfEvent($this, 'application.log', array('Plugin is already installed')));
174
175         return true;
176       }
177
178       // skip if the plugin is already installing and we are here through a dependency)
179       if (isset($this->installing[$channel.'/'.$plugin]))
180       {
181         return true;
182       }
183
184       // convert the plugin package into a discrete download URL
185       $download = $this->environment->getRest()->getPluginDownloadURL($plugin, $version, $stability);
186       if (PEAR::isError($download))
187       {
188         throw new sfPluginException(sprintf('Problem downloading the plugin "%s": %s', $plugin, $download->getMessage()));
189       }
190     }
191
192     // download the plugin and install
193     $class = $this->environment->getOption('downloader_base_class');
194     $downloader = new $class($this, array('upgrade' => true), $this->environment->getConfig());
195
196     $this->installing[$channel.'/'.$plugin] = true;
197
198     if ($isPackage)
199     {
200       $this->checkPluginDependencies($plugin, $version, array(
201         'install_deps' => isset($options['install_deps']) ? (bool) $options['install_deps'] : false,
202         'stability'    => $stability,
203       ));
204     }
205
206     // download the actual URL to the plugin
207     $downloaded = $downloader->download(array($download));
208     if (PEAR::isError($downloaded))
209     {
210       throw new sfPluginException(sprintf('Problem when downloading "%s": %s', $download, $downloaded->getMessage()));
211     }
212     $errors = $downloader->getErrorMsgs();
213     if (count($errors))
214     {
215       $err = array();
216       foreach ($errors as $error)
217       {
218         $err[] = $error;
219       }
220
221       if (!count($downloaded))
222       {
223         throw new sfPluginException(sprintf('Plugin "%s" installation failed: %s', $plugin, implode("\n", $err)));
224       }
225     }
226
227     $pluginPackage = $downloaded[0];
228
229     $installer = new PEAR_Installer($this);
230     $installer->setOptions(array('upgrade' => true));
231     $packages = array($pluginPackage);
232     $installer->sortPackagesForInstall($packages);
233     PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
234     $err = $installer->setDownloadedPackages($packages);
235     if (PEAR::isError($err))
236     {
237       PEAR::staticPopErrorHandling();
238       throw new sfPluginException($err->getMessage());
239     }
240
241     $info = $installer->install($pluginPackage, array('upgrade' => true));
242     PEAR::staticPopErrorHandling();
243     if (PEAR::isError($info))
244     {
245       throw new sfPluginException(sprintf('Installation of "%s" plugin failed: %s', $plugin, $info->getMessage()));
246     }
247
248     if (is_array($info))
249     {
250       $this->dispatcher->notify(new sfEvent($this, 'application.log', array(sprintf('Installation successful for plugin "%s"', $plugin))));
251
252       $this->dispatcher->notify(new sfEvent($this, 'plugin.post_install', array('channel' => $channel, 'plugin' => $pluginPackage->getPackage())));
253
254       unset($this->installing[$channel.'/'.$plugin]);
255
256       return $pluginPackage->getPackage();
257     }
258     else
259     {
260       throw new sfPluginException(sprintf('Installation of "%s" plugin failed', $plugin));
261     }
262   }
263
264   /**
265    * Uninstalls a plugin.
266    *
267    * @param string $plugin  The plugin name
268    * @param string $channel The channel name
269    */
270   public function uninstallPlugin($plugin, $channel = null)
271   {
272     if (false !== strpos($plugin, '/'))
273     {
274       list($channel, $plugin) = explode('/', $plugin);
275     }
276
277     $channel = null === $channel ? $this->environment->getConfig()->get('default_channel') : $channel;
278
279     $existing = $this->environment->getRegistry()->packageInfo($plugin, 'version', $channel);
280     if (null === $existing)
281     {
282       $this->dispatcher->notify(new sfEvent($this, 'application.log', array(sprintf('Plugin "%s" is not installed', $plugin))));
283
284       return false;
285     }
286
287     $this->dispatcher->notify(new sfEvent($this, 'plugin.pre_uninstall', array('channel' => $channel, 'plugin' => $plugin)));
288
289     $package = $this->environment->getRegistry()->parsePackageName($plugin, $channel);
290
291     $installer = new PEAR_Installer($this);
292     $packages = array($this->environment->getRegistry()->getPackage($plugin, $channel));
293     $installer->setUninstallPackages($packages);
294     $ret = $installer->uninstall($package);
295     if (PEAR::isError($ret))
296     {
297       throw new sfPluginException(sprintf('Problem uninstalling plugin "%s": %s', $plugin, $ret->getMessage()));
298     }
299
300     if ($ret)
301     {
302       $this->dispatcher->notify(new sfEvent($this, 'application.log', array(sprintf('Uninstallation successful for plugin "%s"', $plugin))));
303
304       $this->dispatcher->notify(new sfEvent($this, 'plugin.post_uninstall', array('channel' => $channel, 'plugin' => $plugin)));
305     }
306     else
307     {
308       throw new sfPluginException(sprintf('Uninstallation of "%s" plugin failed', $plugin));
309     }
310
311     return $ret;
312   }
313
314   /**
315    * Checks all plugin dependencies.
316    *
317    * Available options:
318    *
319    *  * stability:    The stability preference
320    *  * install_deps: Whether to automatically install dependencies (default to false)
321    *
322    * @param string $plugin  The plugin name
323    * @param string $version The plugin version
324    * @param array  $options An array of options
325    */
326   public function checkPluginDependencies($plugin, $version, $options = false)
327   {
328     $dependencies = $this->environment->getRest()->getPluginDependencies($plugin, $version);
329
330     if (!isset($dependencies['required']) || !isset($dependencies['required']['package']))
331     {
332       return;
333     }
334
335     $deps = $dependencies['required']['package'];
336     if (!isset($deps[0]))
337     {
338       $deps = array($deps);
339     }
340
341     foreach ($deps as $dependency)
342     {
343       if (!$this->checkDependency($dependency))
344       {
345         $version = (isset($dependency['min']) ? ' >= '.$dependency['min'] : '').(isset($dependency['max']) ? ' <= '.$dependency['max'] : '').(isset($dependency['exclude']) ? ' exclude '.$dependency['exclude'] : '');
346
347         if (isset($options['install_deps']) && $options['install_deps'])
348         {
349           try
350           {
351             $this->doInstallPlugin($dependency['name'], array_merge($options, array('channel' => $dependency['channel'])));
352           }
353           catch (sfException $e)
354           {
355             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()));
356           }
357
358           continue;
359         }
360
361         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']));
362       }
363     }
364   }
365
366   /**
367    * Gets the "best" version available for a given plugin.
368    *
369    * @param  string $plugin     The plugin name
370    * @param  string $stability  The stability name
371    *
372    * @return string The version
373    */
374   public function getPluginVersion($plugin, $stability = null)
375   {
376     $versions = $this->environment->getRest()->getPluginVersions($plugin, $stability);
377     foreach ($versions as $version)
378     {
379       if (!$this->isPluginCompatible($plugin, $version))
380       {
381         continue;
382       }
383
384       return $version;
385     }
386
387     throw new sfPluginDependencyException(sprintf('No release available for plugin "%s" in state "%s" that satisfies the application requirements.', $plugin, $stability));
388   }
389
390   /**
391    * Returns true if the plugin is comptatible with your environment.
392    *
393    * @param  string $plugin   The plugin name
394    * @param  string $version  The plugin version
395    *
396    * @return Boolean true if the plugin is compatible, false otherwise
397    */
398   public function isPluginCompatible($plugin, $version)
399   {
400     $dependencies = $this->environment->getRest()->getPluginDependencies($plugin, $version);
401
402     if (!isset($dependencies['required']) || !isset($dependencies['required']['package']))
403     {
404       return true;
405     }
406
407     $deps = $dependencies['required']['package'];
408     if (!isset($deps[0]))
409     {
410       $deps = array($deps);
411     }
412
413     foreach ($deps as $dependency)
414     {
415       if (!$this->isPluginCompatibleWithDependency($dependency))
416       {
417         return false;
418       }
419     }
420
421     return true;
422   }
423
424   /**
425    * Returns the license for a given plugin.
426    *
427    * @param string $plugin    The plugin name
428    * @param array  $options   An array of options
429    *
430    * @return string The license
431    *
432    * @see installPlugin() for available options
433    */
434   public function getPluginLicense($plugin, $options = array())
435   {
436     $channel   = isset($options['channel']) ? $options['channel'] : $this->environment->getConfig()->get('default_channel');
437     $stability = isset($options['stability']) ? $options['stability'] : $this->environment->getConfig()->get('preferred_state', null, $channel);
438     $version   = isset($options['version']) ? $options['version'] : null;
439
440     $rest = $this->environment->getRest();
441     $rest->setChannel(null === $channel ? $this->environment->getConfig()->get('default_channel') : $channel);
442
443     if (null === $version)
444     {
445       try
446       {
447         $version = $this->getPluginVersion($plugin, $stability);
448       }
449       catch (Exception $e)
450       {
451         // no release available
452         return false;
453       }
454     }
455     else
456     {
457       if (!$this->isPluginCompatible($plugin, $version))
458       {
459         throw new sfPluginDependencyException(sprintf('Plugin "%s" in version "%s" is not compatible with the current application', $plugin, $version));
460       }
461     }
462
463     return $rest->getPluginLicense($plugin, $version);
464   }
465
466   /**
467    * Returns true if the plugin is comptatible with the dependency.
468    *
469    * @param  array   $dependency An dependency array
470    *
471    * @return Boolean true if the plugin is compatible, false otherwise
472    */
473   protected function isPluginCompatibleWithDependency($dependency)
474   {
475     return true;
476   }
477
478   /**
479    * Checks that the dependency is valid.
480    *
481    * @param  array   $dependency A dependency array
482    *
483    * @return Boolean true if the dependency is valid, false otherwise
484    */
485   protected function checkDependency($dependency)
486   {
487     $dependencyChecker = new PEAR_Dependency2($this->environment->getConfig(), array(), array('package' => '', 'channel' => ''));
488
489     PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
490     $e = $dependencyChecker->validatePackageDependency($dependency, true, array());
491     PEAR::staticPopErrorHandling();
492     if (PEAR::isError($e))
493     {
494       return false;
495     }
496
497     return true;
498   }
499 }
500
Note: See TracBrowser for help on using the browser.