[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/classes/ -> plugin_manager.php (source)

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Defines classes used for plugins management
  19   *
  20   * This library provides a unified interface to various plugin types in
  21   * Moodle. It is mainly used by the plugins management admin page and the
  22   * plugins check page during the upgrade.
  23   *
  24   * @package    core
  25   * @copyright  2011 David Mudrak <[email protected]>
  26   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  27   */
  28  
  29  defined('MOODLE_INTERNAL') || die();
  30  
  31  /**
  32   * Singleton class providing general plugins management functionality.
  33   */
  34  class core_plugin_manager {
  35  
  36      /** the plugin is shipped with standard Moodle distribution */
  37      const PLUGIN_SOURCE_STANDARD    = 'std';
  38      /** the plugin is added extension */
  39      const PLUGIN_SOURCE_EXTENSION   = 'ext';
  40  
  41      /** the plugin uses neither database nor capabilities, no versions */
  42      const PLUGIN_STATUS_NODB        = 'nodb';
  43      /** the plugin is up-to-date */
  44      const PLUGIN_STATUS_UPTODATE    = 'uptodate';
  45      /** the plugin is about to be installed */
  46      const PLUGIN_STATUS_NEW         = 'new';
  47      /** the plugin is about to be upgraded */
  48      const PLUGIN_STATUS_UPGRADE     = 'upgrade';
  49      /** the standard plugin is about to be deleted */
  50      const PLUGIN_STATUS_DELETE     = 'delete';
  51      /** the version at the disk is lower than the one already installed */
  52      const PLUGIN_STATUS_DOWNGRADE   = 'downgrade';
  53      /** the plugin is installed but missing from disk */
  54      const PLUGIN_STATUS_MISSING     = 'missing';
  55  
  56      /** @var core_plugin_manager holds the singleton instance */
  57      protected static $singletoninstance;
  58      /** @var array of raw plugins information */
  59      protected $pluginsinfo = null;
  60      /** @var array of raw subplugins information */
  61      protected $subpluginsinfo = null;
  62      /** @var array list of installed plugins $name=>$version */
  63      protected $installedplugins = null;
  64      /** @var array list of all enabled plugins $name=>$name */
  65      protected $enabledplugins = null;
  66      /** @var array list of all enabled plugins $name=>$diskversion */
  67      protected $presentplugins = null;
  68      /** @var array reordered list of plugin types */
  69      protected $plugintypes = null;
  70  
  71      /**
  72       * Direct initiation not allowed, use the factory method {@link self::instance()}
  73       */
  74      protected function __construct() {
  75      }
  76  
  77      /**
  78       * Sorry, this is singleton
  79       */
  80      protected function __clone() {
  81      }
  82  
  83      /**
  84       * Factory method for this class
  85       *
  86       * @return core_plugin_manager the singleton instance
  87       */
  88      public static function instance() {
  89          if (is_null(self::$singletoninstance)) {
  90              self::$singletoninstance = new self();
  91          }
  92          return self::$singletoninstance;
  93      }
  94  
  95      /**
  96       * Reset all caches.
  97       * @param bool $phpunitreset
  98       */
  99      public static function reset_caches($phpunitreset = false) {
 100          if ($phpunitreset) {
 101              self::$singletoninstance = null;
 102          } else {
 103              if (self::$singletoninstance) {
 104                  self::$singletoninstance->pluginsinfo = null;
 105                  self::$singletoninstance->subpluginsinfo = null;
 106                  self::$singletoninstance->installedplugins = null;
 107                  self::$singletoninstance->enabledplugins = null;
 108                  self::$singletoninstance->presentplugins = null;
 109                  self::$singletoninstance->plugintypes = null;
 110              }
 111          }
 112          $cache = cache::make('core', 'plugin_manager');
 113          $cache->purge();
 114      }
 115  
 116      /**
 117       * Returns the result of {@link core_component::get_plugin_types()} ordered for humans
 118       *
 119       * @see self::reorder_plugin_types()
 120       * @return array (string)name => (string)location
 121       */
 122      public function get_plugin_types() {
 123          if (func_num_args() > 0) {
 124              if (!func_get_arg(0)) {
 125                  throw coding_exception('core_plugin_manager->get_plugin_types() does not support relative paths.');
 126              }
 127          }
 128          if ($this->plugintypes) {
 129              return $this->plugintypes;
 130          }
 131  
 132          $this->plugintypes = $this->reorder_plugin_types(core_component::get_plugin_types());
 133          return $this->plugintypes;
 134      }
 135  
 136      /**
 137       * Load list of installed plugins,
 138       * always call before using $this->installedplugins.
 139       *
 140       * This method is caching results for all plugins.
 141       */
 142      protected function load_installed_plugins() {
 143          global $DB, $CFG;
 144  
 145          if ($this->installedplugins) {
 146              return;
 147          }
 148  
 149          if (empty($CFG->version)) {
 150              // Nothing installed yet.
 151              $this->installedplugins = array();
 152              return;
 153          }
 154  
 155          $cache = cache::make('core', 'plugin_manager');
 156          $installed = $cache->get('installed');
 157  
 158          if (is_array($installed)) {
 159              $this->installedplugins = $installed;
 160              return;
 161          }
 162  
 163          $this->installedplugins = array();
 164  
 165          // TODO: Delete this block once Moodle 2.6 or later becomes minimum required version to upgrade.
 166          if ($CFG->version < 2013092001.02) {
 167              // We did not upgrade the database yet.
 168              $modules = $DB->get_records('modules', array(), 'name ASC', 'id, name, version');
 169              foreach ($modules as $module) {
 170                  $this->installedplugins['mod'][$module->name] = $module->version;
 171              }
 172              $blocks = $DB->get_records('block', array(), 'name ASC', 'id, name, version');
 173              foreach ($blocks as $block) {
 174                  $this->installedplugins['block'][$block->name] = $block->version;
 175              }
 176          }
 177  
 178          $versions = $DB->get_records('config_plugins', array('name'=>'version'));
 179          foreach ($versions as $version) {
 180              $parts = explode('_', $version->plugin, 2);
 181              if (!isset($parts[1])) {
 182                  // Invalid component, there must be at least one "_".
 183                  continue;
 184              }
 185              // Do not verify here if plugin type and name are valid.
 186              $this->installedplugins[$parts[0]][$parts[1]] = $version->value;
 187          }
 188  
 189          foreach ($this->installedplugins as $key => $value) {
 190              ksort($this->installedplugins[$key]);
 191          }
 192  
 193          $cache->set('installed', $this->installedplugins);
 194      }
 195  
 196      /**
 197       * Return list of installed plugins of given type.
 198       * @param string $type
 199       * @return array $name=>$version
 200       */
 201      public function get_installed_plugins($type) {
 202          $this->load_installed_plugins();
 203          if (isset($this->installedplugins[$type])) {
 204              return $this->installedplugins[$type];
 205          }
 206          return array();
 207      }
 208  
 209      /**
 210       * Load list of all enabled plugins,
 211       * call before using $this->enabledplugins.
 212       *
 213       * This method is caching results from individual plugin info classes.
 214       */
 215      protected function load_enabled_plugins() {
 216          global $CFG;
 217  
 218          if ($this->enabledplugins) {
 219              return;
 220          }
 221  
 222          if (empty($CFG->version)) {
 223              $this->enabledplugins = array();
 224              return;
 225          }
 226  
 227          $cache = cache::make('core', 'plugin_manager');
 228          $enabled = $cache->get('enabled');
 229  
 230          if (is_array($enabled)) {
 231              $this->enabledplugins = $enabled;
 232              return;
 233          }
 234  
 235          $this->enabledplugins = array();
 236  
 237          require_once($CFG->libdir.'/adminlib.php');
 238  
 239          $plugintypes = core_component::get_plugin_types();
 240          foreach ($plugintypes as $plugintype => $fulldir) {
 241              $plugininfoclass = self::resolve_plugininfo_class($plugintype);
 242              if (class_exists($plugininfoclass)) {
 243                  $enabled = $plugininfoclass::get_enabled_plugins();
 244                  if (!is_array($enabled)) {
 245                      continue;
 246                  }
 247                  $this->enabledplugins[$plugintype] = $enabled;
 248              }
 249          }
 250  
 251          $cache->set('enabled', $this->enabledplugins);
 252      }
 253  
 254      /**
 255       * Get list of enabled plugins of given type,
 256       * the result may contain missing plugins.
 257       *
 258       * @param string $type
 259       * @return array|null  list of enabled plugins of this type, null if unknown
 260       */
 261      public function get_enabled_plugins($type) {
 262          $this->load_enabled_plugins();
 263          if (isset($this->enabledplugins[$type])) {
 264              return $this->enabledplugins[$type];
 265          }
 266          return null;
 267      }
 268  
 269      /**
 270       * Load list of all present plugins - call before using $this->presentplugins.
 271       */
 272      protected function load_present_plugins() {
 273          if ($this->presentplugins) {
 274              return;
 275          }
 276  
 277          $cache = cache::make('core', 'plugin_manager');
 278          $present = $cache->get('present');
 279  
 280          if (is_array($present)) {
 281              $this->presentplugins = $present;
 282              return;
 283          }
 284  
 285          $this->presentplugins = array();
 286  
 287          $plugintypes = core_component::get_plugin_types();
 288          foreach ($plugintypes as $type => $typedir) {
 289              $plugs = core_component::get_plugin_list($type);
 290              foreach ($plugs as $plug => $fullplug) {
 291                  $plugin = new stdClass();
 292                  $plugin->version = null;
 293                  $module = $plugin;
 294                  @include ($fullplug.'/version.php');
 295                  $this->presentplugins[$type][$plug] = $plugin;
 296              }
 297          }
 298  
 299          $cache->set('present', $this->presentplugins);
 300      }
 301  
 302      /**
 303       * Get list of present plugins of given type.
 304       *
 305       * @param string $type
 306       * @return array|null  list of presnet plugins $name=>$diskversion, null if unknown
 307       */
 308      public function get_present_plugins($type) {
 309          $this->load_present_plugins();
 310          if (isset($this->presentplugins[$type])) {
 311              return $this->presentplugins[$type];
 312          }
 313          return null;
 314      }
 315  
 316      /**
 317       * Returns a tree of known plugins and information about them
 318       *
 319       * @return array 2D array. The first keys are plugin type names (e.g. qtype);
 320       *      the second keys are the plugin local name (e.g. multichoice); and
 321       *      the values are the corresponding objects extending {@link \core\plugininfo\base}
 322       */
 323      public function get_plugins() {
 324          $this->init_pluginsinfo_property();
 325  
 326          // Make sure all types are initialised.
 327          foreach ($this->pluginsinfo as $plugintype => $list) {
 328              if ($list === null) {
 329                  $this->get_plugins_of_type($plugintype);
 330              }
 331          }
 332  
 333          return $this->pluginsinfo;
 334      }
 335  
 336      /**
 337       * Returns list of known plugins of the given type.
 338       *
 339       * This method returns the subset of the tree returned by {@link self::get_plugins()}.
 340       * If the given type is not known, empty array is returned.
 341       *
 342       * @param string $type plugin type, e.g. 'mod' or 'workshopallocation'
 343       * @return \core\plugininfo\base[] (string)plugin name (e.g. 'workshop') => corresponding subclass of {@link \core\plugininfo\base}
 344       */
 345      public function get_plugins_of_type($type) {
 346          global $CFG;
 347  
 348          $this->init_pluginsinfo_property();
 349  
 350          if (!array_key_exists($type, $this->pluginsinfo)) {
 351              return array();
 352          }
 353  
 354          if (is_array($this->pluginsinfo[$type])) {
 355              return $this->pluginsinfo[$type];
 356          }
 357  
 358          $types = core_component::get_plugin_types();
 359  
 360          if (!isset($types[$type])) {
 361              // Orphaned subplugins!
 362              $plugintypeclass = self::resolve_plugininfo_class($type);
 363              $this->pluginsinfo[$type] = $plugintypeclass::get_plugins($type, null, $plugintypeclass);
 364              return $this->pluginsinfo[$type];
 365          }
 366  
 367          /** @var \core\plugininfo\base $plugintypeclass */
 368          $plugintypeclass = self::resolve_plugininfo_class($type);
 369          $plugins = $plugintypeclass::get_plugins($type, $types[$type], $plugintypeclass);
 370          $this->pluginsinfo[$type] = $plugins;
 371  
 372          if (empty($CFG->disableupdatenotifications) and !during_initial_install()) {
 373              // Append the information about available updates provided by {@link \core\update\checker()}.
 374              $provider = \core\update\checker::instance();
 375              foreach ($plugins as $plugininfoholder) {
 376                  $plugininfoholder->check_available_updates($provider);
 377              }
 378          }
 379  
 380          return $this->pluginsinfo[$type];
 381      }
 382  
 383      /**
 384       * Init placeholder array for plugin infos.
 385       */
 386      protected function init_pluginsinfo_property() {
 387          if (is_array($this->pluginsinfo)) {
 388              return;
 389          }
 390          $this->pluginsinfo = array();
 391  
 392          $plugintypes = $this->get_plugin_types();
 393  
 394          foreach ($plugintypes as $plugintype => $plugintyperootdir) {
 395              $this->pluginsinfo[$plugintype] = null;
 396          }
 397  
 398          // Add orphaned subplugin types.
 399          $this->load_installed_plugins();
 400          foreach ($this->installedplugins as $plugintype => $unused) {
 401              if (!isset($plugintypes[$plugintype])) {
 402                  $this->pluginsinfo[$plugintype] = null;
 403              }
 404          }
 405      }
 406  
 407      /**
 408       * Find the plugin info class for given type.
 409       *
 410       * @param string $type
 411       * @return string name of pluginfo class for give plugin type
 412       */
 413      public static function resolve_plugininfo_class($type) {
 414          $plugintypes = core_component::get_plugin_types();
 415          if (!isset($plugintypes[$type])) {
 416              return '\core\plugininfo\orphaned';
 417          }
 418  
 419          $parent = core_component::get_subtype_parent($type);
 420  
 421          if ($parent) {
 422              $class = '\\'.$parent.'\plugininfo\\' . $type;
 423              if (class_exists($class)) {
 424                  $plugintypeclass = $class;
 425              } else {
 426                  if ($dir = core_component::get_component_directory($parent)) {
 427                      // BC only - use namespace instead!
 428                      if (file_exists("$dir/adminlib.php")) {
 429                          global $CFG;
 430                          include_once("$dir/adminlib.php");
 431                      }
 432                      if (class_exists('plugininfo_' . $type)) {
 433                          $plugintypeclass = 'plugininfo_' . $type;
 434                          debugging('Class "'.$plugintypeclass.'" is deprecated, migrate to "'.$class.'"', DEBUG_DEVELOPER);
 435                      } else {
 436                          debugging('Subplugin type "'.$type.'" should define class "'.$class.'"', DEBUG_DEVELOPER);
 437                          $plugintypeclass = '\core\plugininfo\general';
 438                      }
 439                  } else {
 440                      $plugintypeclass = '\core\plugininfo\general';
 441                  }
 442              }
 443          } else {
 444              $class = '\core\plugininfo\\' . $type;
 445              if (class_exists($class)) {
 446                  $plugintypeclass = $class;
 447              } else {
 448                  debugging('All standard types including "'.$type.'" should have plugininfo class!', DEBUG_DEVELOPER);
 449                  $plugintypeclass = '\core\plugininfo\general';
 450              }
 451          }
 452  
 453          if (!in_array('core\plugininfo\base', class_parents($plugintypeclass))) {
 454              throw new coding_exception('Class ' . $plugintypeclass . ' must extend \core\plugininfo\base');
 455          }
 456  
 457          return $plugintypeclass;
 458      }
 459  
 460      /**
 461       * Returns list of all known subplugins of the given plugin.
 462       *
 463       * For plugins that do not provide subplugins (i.e. there is no support for it),
 464       * empty array is returned.
 465       *
 466       * @param string $component full component name, e.g. 'mod_workshop'
 467       * @return array (string) component name (e.g. 'workshopallocation_random') => subclass of {@link \core\plugininfo\base}
 468       */
 469      public function get_subplugins_of_plugin($component) {
 470  
 471          $pluginfo = $this->get_plugin_info($component);
 472  
 473          if (is_null($pluginfo)) {
 474              return array();
 475          }
 476  
 477          $subplugins = $this->get_subplugins();
 478  
 479          if (!isset($subplugins[$pluginfo->component])) {
 480              return array();
 481          }
 482  
 483          $list = array();
 484  
 485          foreach ($subplugins[$pluginfo->component] as $subdata) {
 486              foreach ($this->get_plugins_of_type($subdata->type) as $subpluginfo) {
 487                  $list[$subpluginfo->component] = $subpluginfo;
 488              }
 489          }
 490  
 491          return $list;
 492      }
 493  
 494      /**
 495       * Returns list of plugins that define their subplugins and the information
 496       * about them from the db/subplugins.php file.
 497       *
 498       * @return array with keys like 'mod_quiz', and values the data from the
 499       *      corresponding db/subplugins.php file.
 500       */
 501      public function get_subplugins() {
 502  
 503          if (is_array($this->subpluginsinfo)) {
 504              return $this->subpluginsinfo;
 505          }
 506  
 507          $plugintypes = core_component::get_plugin_types();
 508  
 509          $this->subpluginsinfo = array();
 510          foreach (core_component::get_plugin_types_with_subplugins() as $type => $ignored) {
 511              foreach (core_component::get_plugin_list($type) as $plugin => $componentdir) {
 512                  $component = $type.'_'.$plugin;
 513                  $subplugins = core_component::get_subplugins($component);
 514                  if (!$subplugins) {
 515                      continue;
 516                  }
 517                  $this->subpluginsinfo[$component] = array();
 518                  foreach ($subplugins as $subplugintype => $ignored) {
 519                      $subplugin = new stdClass();
 520                      $subplugin->type = $subplugintype;
 521                      $subplugin->typerootdir = $plugintypes[$subplugintype];
 522                      $this->subpluginsinfo[$component][$subplugintype] = $subplugin;
 523                  }
 524              }
 525          }
 526          return $this->subpluginsinfo;
 527      }
 528  
 529      /**
 530       * Returns the name of the plugin that defines the given subplugin type
 531       *
 532       * If the given subplugin type is not actually a subplugin, returns false.
 533       *
 534       * @param string $subplugintype the name of subplugin type, eg. workshopform or quiz
 535       * @return false|string the name of the parent plugin, eg. mod_workshop
 536       */
 537      public function get_parent_of_subplugin($subplugintype) {
 538          $parent = core_component::get_subtype_parent($subplugintype);
 539          if (!$parent) {
 540              return false;
 541          }
 542          return $parent;
 543      }
 544  
 545      /**
 546       * Returns a localized name of a given plugin
 547       *
 548       * @param string $component name of the plugin, eg mod_workshop or auth_ldap
 549       * @return string
 550       */
 551      public function plugin_name($component) {
 552  
 553          $pluginfo = $this->get_plugin_info($component);
 554  
 555          if (is_null($pluginfo)) {
 556              throw new moodle_exception('err_unknown_plugin', 'core_plugin', '', array('plugin' => $component));
 557          }
 558  
 559          return $pluginfo->displayname;
 560      }
 561  
 562      /**
 563       * Returns a localized name of a plugin typed in singular form
 564       *
 565       * Most plugin types define their names in core_plugin lang file. In case of subplugins,
 566       * we try to ask the parent plugin for the name. In the worst case, we will return
 567       * the value of the passed $type parameter.
 568       *
 569       * @param string $type the type of the plugin, e.g. mod or workshopform
 570       * @return string
 571       */
 572      public function plugintype_name($type) {
 573  
 574          if (get_string_manager()->string_exists('type_' . $type, 'core_plugin')) {
 575              // For most plugin types, their names are defined in core_plugin lang file.
 576              return get_string('type_' . $type, 'core_plugin');
 577  
 578          } else if ($parent = $this->get_parent_of_subplugin($type)) {
 579              // If this is a subplugin, try to ask the parent plugin for the name.
 580              if (get_string_manager()->string_exists('subplugintype_' . $type, $parent)) {
 581                  return $this->plugin_name($parent) . ' / ' . get_string('subplugintype_' . $type, $parent);
 582              } else {
 583                  return $this->plugin_name($parent) . ' / ' . $type;
 584              }
 585  
 586          } else {
 587              return $type;
 588          }
 589      }
 590  
 591      /**
 592       * Returns a localized name of a plugin type in plural form
 593       *
 594       * Most plugin types define their names in core_plugin lang file. In case of subplugins,
 595       * we try to ask the parent plugin for the name. In the worst case, we will return
 596       * the value of the passed $type parameter.
 597       *
 598       * @param string $type the type of the plugin, e.g. mod or workshopform
 599       * @return string
 600       */
 601      public function plugintype_name_plural($type) {
 602  
 603          if (get_string_manager()->string_exists('type_' . $type . '_plural', 'core_plugin')) {
 604              // For most plugin types, their names are defined in core_plugin lang file.
 605              return get_string('type_' . $type . '_plural', 'core_plugin');
 606  
 607          } else if ($parent = $this->get_parent_of_subplugin($type)) {
 608              // If this is a subplugin, try to ask the parent plugin for the name.
 609              if (get_string_manager()->string_exists('subplugintype_' . $type . '_plural', $parent)) {
 610                  return $this->plugin_name($parent) . ' / ' . get_string('subplugintype_' . $type . '_plural', $parent);
 611              } else {
 612                  return $this->plugin_name($parent) . ' / ' . $type;
 613              }
 614  
 615          } else {
 616              return $type;
 617          }
 618      }
 619  
 620      /**
 621       * Returns information about the known plugin, or null
 622       *
 623       * @param string $component frankenstyle component name.
 624       * @return \core\plugininfo\base|null the corresponding plugin information.
 625       */
 626      public function get_plugin_info($component) {
 627          list($type, $name) = core_component::normalize_component($component);
 628          $plugins = $this->get_plugins_of_type($type);
 629          if (isset($plugins[$name])) {
 630              return $plugins[$name];
 631          } else {
 632              return null;
 633          }
 634      }
 635  
 636      /**
 637       * Check to see if the current version of the plugin seems to be a checkout of an external repository.
 638       *
 639       * @see \core\update\deployer::plugin_external_source()
 640       * @param string $component frankenstyle component name
 641       * @return false|string
 642       */
 643      public function plugin_external_source($component) {
 644  
 645          $plugininfo = $this->get_plugin_info($component);
 646  
 647          if (is_null($plugininfo)) {
 648              return false;
 649          }
 650  
 651          $pluginroot = $plugininfo->rootdir;
 652  
 653          if (is_dir($pluginroot.'/.git')) {
 654              return 'git';
 655          }
 656  
 657          if (is_file($pluginroot.'/.git')) {
 658              return 'git-submodule';
 659          }
 660  
 661          if (is_dir($pluginroot.'/CVS')) {
 662              return 'cvs';
 663          }
 664  
 665          if (is_dir($pluginroot.'/.svn')) {
 666              return 'svn';
 667          }
 668  
 669          if (is_dir($pluginroot.'/.hg')) {
 670              return 'mercurial';
 671          }
 672  
 673          return false;
 674      }
 675  
 676      /**
 677       * Get a list of any other plugins that require this one.
 678       * @param string $component frankenstyle component name.
 679       * @return array of frankensyle component names that require this one.
 680       */
 681      public function other_plugins_that_require($component) {
 682          $others = array();
 683          foreach ($this->get_plugins() as $type => $plugins) {
 684              foreach ($plugins as $plugin) {
 685                  $required = $plugin->get_other_required_plugins();
 686                  if (isset($required[$component])) {
 687                      $others[] = $plugin->component;
 688                  }
 689              }
 690          }
 691          return $others;
 692      }
 693  
 694      /**
 695       * Check a dependencies list against the list of installed plugins.
 696       * @param array $dependencies compenent name to required version or ANY_VERSION.
 697       * @return bool true if all the dependencies are satisfied.
 698       */
 699      public function are_dependencies_satisfied($dependencies) {
 700          foreach ($dependencies as $component => $requiredversion) {
 701              $otherplugin = $this->get_plugin_info($component);
 702              if (is_null($otherplugin)) {
 703                  return false;
 704              }
 705  
 706              if ($requiredversion != ANY_VERSION and $otherplugin->versiondisk < $requiredversion) {
 707                  return false;
 708              }
 709          }
 710  
 711          return true;
 712      }
 713  
 714      /**
 715       * Checks all dependencies for all installed plugins
 716       *
 717       * This is used by install and upgrade. The array passed by reference as the second
 718       * argument is populated with the list of plugins that have failed dependencies (note that
 719       * a single plugin can appear multiple times in the $failedplugins).
 720       *
 721       * @param int $moodleversion the version from version.php.
 722       * @param array $failedplugins to return the list of plugins with non-satisfied dependencies
 723       * @return bool true if all the dependencies are satisfied for all plugins.
 724       */
 725      public function all_plugins_ok($moodleversion, &$failedplugins = array()) {
 726  
 727          $return = true;
 728          foreach ($this->get_plugins() as $type => $plugins) {
 729              foreach ($plugins as $plugin) {
 730  
 731                  if (!$plugin->is_core_dependency_satisfied($moodleversion)) {
 732                      $return = false;
 733                      $failedplugins[] = $plugin->component;
 734                  }
 735  
 736                  if (!$this->are_dependencies_satisfied($plugin->get_other_required_plugins())) {
 737                      $return = false;
 738                      $failedplugins[] = $plugin->component;
 739                  }
 740              }
 741          }
 742  
 743          return $return;
 744      }
 745  
 746      /**
 747       * Is it possible to uninstall the given plugin?
 748       *
 749       * False is returned if the plugininfo subclass declares the uninstall should
 750       * not be allowed via {@link \core\plugininfo\base::is_uninstall_allowed()} or if the
 751       * core vetoes it (e.g. becase the plugin or some of its subplugins is required
 752       * by some other installed plugin).
 753       *
 754       * @param string $component full frankenstyle name, e.g. mod_foobar
 755       * @return bool
 756       */
 757      public function can_uninstall_plugin($component) {
 758  
 759          $pluginfo = $this->get_plugin_info($component);
 760  
 761          if (is_null($pluginfo)) {
 762              return false;
 763          }
 764  
 765          if (!$this->common_uninstall_check($pluginfo)) {
 766              return false;
 767          }
 768  
 769          // Verify only if something else requires the subplugins, do not verify their common_uninstall_check()!
 770          $subplugins = $this->get_subplugins_of_plugin($pluginfo->component);
 771          foreach ($subplugins as $subpluginfo) {
 772              // Check if there are some other plugins requiring this subplugin
 773              // (but the parent and siblings).
 774              foreach ($this->other_plugins_that_require($subpluginfo->component) as $requiresme) {
 775                  $ismyparent = ($pluginfo->component === $requiresme);
 776                  $ismysibling = in_array($requiresme, array_keys($subplugins));
 777                  if (!$ismyparent and !$ismysibling) {
 778                      return false;
 779                  }
 780              }
 781          }
 782  
 783          // Check if there are some other plugins requiring this plugin
 784          // (but its subplugins).
 785          foreach ($this->other_plugins_that_require($pluginfo->component) as $requiresme) {
 786              $ismysubplugin = in_array($requiresme, array_keys($subplugins));
 787              if (!$ismysubplugin) {
 788                  return false;
 789              }
 790          }
 791  
 792          return true;
 793      }
 794  
 795      /**
 796       * Returns uninstall URL if exists.
 797       *
 798       * @param string $component
 799       * @param string $return either 'overview' or 'manage'
 800       * @return moodle_url uninstall URL, null if uninstall not supported
 801       */
 802      public function get_uninstall_url($component, $return = 'overview') {
 803          if (!$this->can_uninstall_plugin($component)) {
 804              return null;
 805          }
 806  
 807          $pluginfo = $this->get_plugin_info($component);
 808  
 809          if (is_null($pluginfo)) {
 810              return null;
 811          }
 812  
 813          if (method_exists($pluginfo, 'get_uninstall_url')) {
 814              debugging('plugininfo method get_uninstall_url() is deprecated, all plugins should be uninstalled via standard URL only.');
 815              return $pluginfo->get_uninstall_url($return);
 816          }
 817  
 818          return $pluginfo->get_default_uninstall_url($return);
 819      }
 820  
 821      /**
 822       * Uninstall the given plugin.
 823       *
 824       * Automatically cleans-up all remaining configuration data, log records, events,
 825       * files from the file pool etc.
 826       *
 827       * In the future, the functionality of {@link uninstall_plugin()} function may be moved
 828       * into this method and all the code should be refactored to use it. At the moment, we
 829       * mimic this future behaviour by wrapping that function call.
 830       *
 831       * @param string $component
 832       * @param progress_trace $progress traces the process
 833       * @return bool true on success, false on errors/problems
 834       */
 835      public function uninstall_plugin($component, progress_trace $progress) {
 836  
 837          $pluginfo = $this->get_plugin_info($component);
 838  
 839          if (is_null($pluginfo)) {
 840              return false;
 841          }
 842  
 843          // Give the pluginfo class a chance to execute some steps.
 844          $result = $pluginfo->uninstall($progress);
 845          if (!$result) {
 846              return false;
 847          }
 848  
 849          // Call the legacy core function to uninstall the plugin.
 850          ob_start();
 851          uninstall_plugin($pluginfo->type, $pluginfo->name);
 852          $progress->output(ob_get_clean());
 853  
 854          return true;
 855      }
 856  
 857      /**
 858       * Checks if there are some plugins with a known available update
 859       *
 860       * @return bool true if there is at least one available update
 861       */
 862      public function some_plugins_updatable() {
 863          foreach ($this->get_plugins() as $type => $plugins) {
 864              foreach ($plugins as $plugin) {
 865                  if ($plugin->available_updates()) {
 866                      return true;
 867                  }
 868              }
 869          }
 870  
 871          return false;
 872      }
 873  
 874      /**
 875       * Check to see if the given plugin folder can be removed by the web server process.
 876       *
 877       * @param string $component full frankenstyle component
 878       * @return bool
 879       */
 880      public function is_plugin_folder_removable($component) {
 881  
 882          $pluginfo = $this->get_plugin_info($component);
 883  
 884          if (is_null($pluginfo)) {
 885              return false;
 886          }
 887  
 888          // To be able to remove the plugin folder, its parent must be writable, too.
 889          if (!is_writable(dirname($pluginfo->rootdir))) {
 890              return false;
 891          }
 892  
 893          // Check that the folder and all its content is writable (thence removable).
 894          return $this->is_directory_removable($pluginfo->rootdir);
 895      }
 896  
 897      /**
 898       * Defines a list of all plugins that were originally shipped in the standard Moodle distribution,
 899       * but are not anymore and are deleted during upgrades.
 900       *
 901       * The main purpose of this list is to hide missing plugins during upgrade.
 902       *
 903       * @param string $type plugin type
 904       * @param string $name plugin name
 905       * @return bool
 906       */
 907      public static function is_deleted_standard_plugin($type, $name) {
 908          // Do not include plugins that were removed during upgrades to versions that are
 909          // not supported as source versions for upgrade any more. For example, at MOODLE_23_STABLE
 910          // branch, listed should be no plugins that were removed at 1.9.x - 2.1.x versions as
 911          // Moodle 2.3 supports upgrades from 2.2.x only.
 912          $plugins = array(
 913              'qformat' => array('blackboard', 'learnwise'),
 914              'enrol' => array('authorize'),
 915              'tinymce' => array('dragmath'),
 916              'tool' => array('bloglevelupgrade', 'qeupgradehelper'),
 917              'theme' => array('mymobile', 'afterburner', 'anomaly', 'arialist', 'binarius', 'boxxie', 'brick', 'formal_white',
 918                  'formfactor', 'fusion', 'leatherbound', 'magazine', 'nimble', 'nonzero', 'overlay', 'serenity', 'sky_high',
 919                  'splash', 'standard', 'standardold'),
 920          );
 921  
 922          if (!isset($plugins[$type])) {
 923              return false;
 924          }
 925          return in_array($name, $plugins[$type]);
 926      }
 927  
 928      /**
 929       * Defines a white list of all plugins shipped in the standard Moodle distribution
 930       *
 931       * @param string $type
 932       * @return false|array array of standard plugins or false if the type is unknown
 933       */
 934      public static function standard_plugins_list($type) {
 935  
 936          $standard_plugins = array(
 937  
 938              'atto' => array(
 939                  'accessibilitychecker', 'accessibilityhelper', 'align',
 940                  'backcolor', 'bold', 'charmap', 'clear', 'collapse', 'emoticon',
 941                  'equation', 'fontcolor', 'html', 'image', 'indent', 'italic',
 942                  'link', 'managefiles', 'media', 'noautolink', 'orderedlist',
 943                  'rtl', 'strike', 'subscript', 'superscript', 'table', 'title',
 944                  'underline', 'undo', 'unorderedlist'
 945              ),
 946  
 947              'assignment' => array(
 948                  'offline', 'online', 'upload', 'uploadsingle'
 949              ),
 950  
 951              'assignsubmission' => array(
 952                  'comments', 'file', 'onlinetext'
 953              ),
 954  
 955              'assignfeedback' => array(
 956                  'comments', 'file', 'offline', 'editpdf'
 957              ),
 958  
 959              'auth' => array(
 960                  'cas', 'db', 'email', 'fc', 'imap', 'ldap', 'manual', 'mnet',
 961                  'nntp', 'nologin', 'none', 'pam', 'pop3', 'radius',
 962                  'shibboleth', 'webservice'
 963              ),
 964  
 965              'availability' => array(
 966                  'completion', 'date', 'grade', 'group', 'grouping', 'profile'
 967              ),
 968  
 969              'block' => array(
 970                  'activity_modules', 'admin_bookmarks', 'badges', 'blog_menu',
 971                  'blog_recent', 'blog_tags', 'calendar_month',
 972                  'calendar_upcoming', 'comments', 'community',
 973                  'completionstatus', 'course_list', 'course_overview',
 974                  'course_summary', 'feedback', 'glossary_random', 'html',
 975                  'login', 'mentees', 'messages', 'mnet_hosts', 'myprofile',
 976                  'navigation', 'news_items', 'online_users', 'participants',
 977                  'private_files', 'quiz_results', 'recent_activity',
 978                  'rss_client', 'search_forums', 'section_links',
 979                  'selfcompletion', 'settings', 'site_main_menu',
 980                  'social_activities', 'tag_flickr', 'tag_youtube', 'tags'
 981              ),
 982  
 983              'booktool' => array(
 984                  'exportimscp', 'importhtml', 'print'
 985              ),
 986  
 987              'cachelock' => array(
 988                  'file'
 989              ),
 990  
 991              'cachestore' => array(
 992                  'file', 'memcache', 'memcached', 'mongodb', 'session', 'static'
 993              ),
 994  
 995              'calendartype' => array(
 996                  'gregorian'
 997              ),
 998  
 999              'coursereport' => array(
1000                  // Deprecated!
1001              ),
1002  
1003              'datafield' => array(
1004                  'checkbox', 'date', 'file', 'latlong', 'menu', 'multimenu',
1005                  'number', 'picture', 'radiobutton', 'text', 'textarea', 'url'
1006              ),
1007  
1008              'datapreset' => array(
1009                  'imagegallery'
1010              ),
1011  
1012              'editor' => array(
1013                  'atto', 'textarea', 'tinymce'
1014              ),
1015  
1016              'enrol' => array(
1017                  'category', 'cohort', 'database', 'flatfile',
1018                  'guest', 'imsenterprise', 'ldap', 'manual', 'meta', 'mnet',
1019                  'paypal', 'self'
1020              ),
1021  
1022              'filter' => array(
1023                  'activitynames', 'algebra', 'censor', 'emailprotect',
1024                  'emoticon', 'mathjaxloader', 'mediaplugin', 'multilang', 'tex', 'tidy',
1025                  'urltolink', 'data', 'glossary'
1026              ),
1027  
1028              'format' => array(
1029                  'singleactivity', 'social', 'topics', 'weeks'
1030              ),
1031  
1032              'gradeexport' => array(
1033                  'ods', 'txt', 'xls', 'xml'
1034              ),
1035  
1036              'gradeimport' => array(
1037                  'csv', 'direct', 'xml'
1038              ),
1039  
1040              'gradereport' => array(
1041                  'grader', 'history', 'outcomes', 'overview', 'user', 'singleview'
1042              ),
1043  
1044              'gradingform' => array(
1045                  'rubric', 'guide'
1046              ),
1047  
1048              'local' => array(
1049              ),
1050  
1051              'logstore' => array(
1052                  'database', 'legacy', 'standard',
1053              ),
1054  
1055              'ltiservice' => array(
1056                  'profile', 'toolproxy', 'toolsettings'
1057              ),
1058  
1059              'message' => array(
1060                  'airnotifier', 'email', 'jabber', 'popup'
1061              ),
1062  
1063              'mnetservice' => array(
1064                  'enrol'
1065              ),
1066  
1067              'mod' => array(
1068                  'assign', 'assignment', 'book', 'chat', 'choice', 'data', 'feedback', 'folder',
1069                  'forum', 'glossary', 'imscp', 'label', 'lesson', 'lti', 'page',
1070                  'quiz', 'resource', 'scorm', 'survey', 'url', 'wiki', 'workshop'
1071              ),
1072  
1073              'plagiarism' => array(
1074              ),
1075  
1076              'portfolio' => array(
1077                  'boxnet', 'download', 'flickr', 'googledocs', 'mahara', 'picasa'
1078              ),
1079  
1080              'profilefield' => array(
1081                  'checkbox', 'datetime', 'menu', 'text', 'textarea'
1082              ),
1083  
1084              'qbehaviour' => array(
1085                  'adaptive', 'adaptivenopenalty', 'deferredcbm',
1086                  'deferredfeedback', 'immediatecbm', 'immediatefeedback',
1087                  'informationitem', 'interactive', 'interactivecountback',
1088                  'manualgraded', 'missing'
1089              ),
1090  
1091              'qformat' => array(
1092                  'aiken', 'blackboard_six', 'examview', 'gift',
1093                  'missingword', 'multianswer', 'webct',
1094                  'xhtml', 'xml'
1095              ),
1096  
1097              'qtype' => array(
1098                  'calculated', 'calculatedmulti', 'calculatedsimple',
1099                  'description', 'essay', 'match', 'missingtype', 'multianswer',
1100                  'multichoice', 'numerical', 'random', 'randomsamatch',
1101                  'shortanswer', 'truefalse'
1102              ),
1103  
1104              'quiz' => array(
1105                  'grading', 'overview', 'responses', 'statistics'
1106              ),
1107  
1108              'quizaccess' => array(
1109                  'delaybetweenattempts', 'ipaddress', 'numattempts', 'openclosedate',
1110                  'password', 'safebrowser', 'securewindow', 'timelimit'
1111              ),
1112  
1113              'report' => array(
1114                  'backups', 'completion', 'configlog', 'courseoverview', 'eventlist',
1115                  'log', 'loglive', 'outline', 'participation', 'progress', 'questioninstances', 'security', 'stats', 'performance'
1116              ),
1117  
1118              'repository' => array(
1119                  'alfresco', 'areafiles', 'boxnet', 'coursefiles', 'dropbox', 'equella', 'filesystem',
1120                  'flickr', 'flickr_public', 'googledocs', 'local', 'merlot',
1121                  'picasa', 'recent', 'skydrive', 's3', 'upload', 'url', 'user', 'webdav',
1122                  'wikimedia', 'youtube'
1123              ),
1124  
1125              'scormreport' => array(
1126                  'basic',
1127                  'interactions',
1128                  'graphs',
1129                  'objectives'
1130              ),
1131  
1132              'tinymce' => array(
1133                  'ctrlhelp', 'managefiles', 'moodleemoticon', 'moodleimage',
1134                  'moodlemedia', 'moodlenolink', 'pdw', 'spellchecker', 'wrap'
1135              ),
1136  
1137              'theme' => array(
1138                  'base', 'bootstrapbase', 'canvas', 'clean', 'more'
1139              ),
1140  
1141              'tool' => array(
1142                  'assignmentupgrade', 'availabilityconditions', 'behat', 'capability', 'customlang',
1143                  'dbtransfer', 'generator', 'health', 'innodb', 'installaddon',
1144                  'langimport', 'log', 'messageinbound', 'multilangupgrade', 'monitor', 'phpunit', 'profiling',
1145                  'replace', 'spamcleaner', 'task', 'timezoneimport',
1146                  'unittest', 'uploadcourse', 'uploaduser', 'unsuproles', 'xmldb'
1147              ),
1148  
1149              'webservice' => array(
1150                  'amf', 'rest', 'soap', 'xmlrpc'
1151              ),
1152  
1153              'workshopallocation' => array(
1154                  'manual', 'random', 'scheduled'
1155              ),
1156  
1157              'workshopeval' => array(
1158                  'best'
1159              ),
1160  
1161              'workshopform' => array(
1162                  'accumulative', 'comments', 'numerrors', 'rubric'
1163              )
1164          );
1165  
1166          if (isset($standard_plugins[$type])) {
1167              return $standard_plugins[$type];
1168          } else {
1169              return false;
1170          }
1171      }
1172  
1173      /**
1174       * Reorders plugin types into a sequence to be displayed
1175       *
1176       * For technical reasons, plugin types returned by {@link core_component::get_plugin_types()} are
1177       * in a certain order that does not need to fit the expected order for the display.
1178       * Particularly, activity modules should be displayed first as they represent the
1179       * real heart of Moodle. They should be followed by other plugin types that are
1180       * used to build the courses (as that is what one expects from LMS). After that,
1181       * other supportive plugin types follow.
1182       *
1183       * @param array $types associative array
1184       * @return array same array with altered order of items
1185       */
1186      protected function reorder_plugin_types(array $types) {
1187          $fix = array('mod' => $types['mod']);
1188          foreach (core_component::get_plugin_list('mod') as $plugin => $fulldir) {
1189              if (!$subtypes = core_component::get_subplugins('mod_'.$plugin)) {
1190                  continue;
1191              }
1192              foreach ($subtypes as $subtype => $ignored) {
1193                  $fix[$subtype] = $types[$subtype];
1194              }
1195          }
1196  
1197          $fix['mod']        = $types['mod'];
1198          $fix['block']      = $types['block'];
1199          $fix['qtype']      = $types['qtype'];
1200          $fix['qbehaviour'] = $types['qbehaviour'];
1201          $fix['qformat']    = $types['qformat'];
1202          $fix['filter']     = $types['filter'];
1203  
1204          $fix['editor']     = $types['editor'];
1205          foreach (core_component::get_plugin_list('editor') as $plugin => $fulldir) {
1206              if (!$subtypes = core_component::get_subplugins('editor_'.$plugin)) {
1207                  continue;
1208              }
1209              foreach ($subtypes as $subtype => $ignored) {
1210                  $fix[$subtype] = $types[$subtype];
1211              }
1212          }
1213  
1214          $fix['enrol'] = $types['enrol'];
1215          $fix['auth']  = $types['auth'];
1216          $fix['tool']  = $types['tool'];
1217          foreach (core_component::get_plugin_list('tool') as $plugin => $fulldir) {
1218              if (!$subtypes = core_component::get_subplugins('tool_'.$plugin)) {
1219                  continue;
1220              }
1221              foreach ($subtypes as $subtype => $ignored) {
1222                  $fix[$subtype] = $types[$subtype];
1223              }
1224          }
1225  
1226          foreach ($types as $type => $path) {
1227              if (!isset($fix[$type])) {
1228                  $fix[$type] = $path;
1229              }
1230          }
1231          return $fix;
1232      }
1233  
1234      /**
1235       * Check if the given directory can be removed by the web server process.
1236       *
1237       * This recursively checks that the given directory and all its contents
1238       * it writable.
1239       *
1240       * @param string $fullpath
1241       * @return boolean
1242       */
1243      protected function is_directory_removable($fullpath) {
1244  
1245          if (!is_writable($fullpath)) {
1246              return false;
1247          }
1248  
1249          if (is_dir($fullpath)) {
1250              $handle = opendir($fullpath);
1251          } else {
1252              return false;
1253          }
1254  
1255          $result = true;
1256  
1257          while ($filename = readdir($handle)) {
1258  
1259              if ($filename === '.' or $filename === '..') {
1260                  continue;
1261              }
1262  
1263              $subfilepath = $fullpath.'/'.$filename;
1264  
1265              if (is_dir($subfilepath)) {
1266                  $result = $result && $this->is_directory_removable($subfilepath);
1267  
1268              } else {
1269                  $result = $result && is_writable($subfilepath);
1270              }
1271          }
1272  
1273          closedir($handle);
1274  
1275          return $result;
1276      }
1277  
1278      /**
1279       * Helper method that implements common uninstall prerequisites
1280       *
1281       * @param \core\plugininfo\base $pluginfo
1282       * @return bool
1283       */
1284      protected function common_uninstall_check(\core\plugininfo\base $pluginfo) {
1285  
1286          if (!$pluginfo->is_uninstall_allowed()) {
1287              // The plugin's plugininfo class declares it should not be uninstalled.
1288              return false;
1289          }
1290  
1291          if ($pluginfo->get_status() === self::PLUGIN_STATUS_NEW) {
1292              // The plugin is not installed. It should be either installed or removed from the disk.
1293              // Relying on this temporary state may be tricky.
1294              return false;
1295          }
1296  
1297          if (method_exists($pluginfo, 'get_uninstall_url') and is_null($pluginfo->get_uninstall_url())) {
1298              // Backwards compatibility.
1299              debugging('\core\plugininfo\base subclasses should use is_uninstall_allowed() instead of returning null in get_uninstall_url()',
1300                  DEBUG_DEVELOPER);
1301              return false;
1302          }
1303  
1304          return true;
1305      }
1306  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1