Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 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: sfPluginManager.class.php 21908 2009-09-11 12:06:21Z fabien $
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
}