Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]

   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 plugin info.
  19   *
  20   * @package    core
  21   * @copyright  2011 David Mudrak <david@moodle.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  namespace core\plugininfo;
  25  
  26  use core_component, core_plugin_manager, moodle_url, coding_exception;
  27  
  28  defined('MOODLE_INTERNAL') || die();
  29  
  30  
  31  /**
  32   * Base class providing access to the information about a plugin
  33   *
  34   * @property-read string component the component name, type_name
  35   */
  36  abstract class base {
  37  
  38      /** @var string the plugintype name, eg. mod, auth or workshopform */
  39      public $type;
  40      /** @var string full path to the location of all the plugins of this type */
  41      public $typerootdir;
  42      /** @var string the plugin name, eg. assignment, ldap */
  43      public $name;
  44      /** @var string the localized plugin name */
  45      public $displayname;
  46      /** @var string the plugin source, one of core_plugin_manager::PLUGIN_SOURCE_xxx constants */
  47      public $source;
  48      /** @var string fullpath to the location of this plugin */
  49      public $rootdir;
  50      /** @var int|string the version of the plugin's source code */
  51      public $versiondisk;
  52      /** @var int|string the version of the installed plugin */
  53      public $versiondb;
  54      /** @var int|float|string required version of Moodle core  */
  55      public $versionrequires;
  56      /** @var array explicitly supported branches of Moodle core  */
  57      public $pluginsupported;
  58      /** @var int first incompatible branch of Moodle core  */
  59      public $pluginincompatible;
  60      /** @var mixed human-readable release information */
  61      public $release;
  62      /** @var array other plugins that this one depends on, lazy-loaded by {@link get_other_required_plugins()} */
  63      public $dependencies;
  64      /** @var int number of instances of the plugin - not supported yet */
  65      public $instances;
  66      /** @var int order of the plugin among other plugins of the same type - not supported yet */
  67      public $sortorder;
  68      /** @var core_plugin_manager the plugin manager this plugin info is part of */
  69      public $pluginman;
  70  
  71      /** @var array|null array of {@link \core\update\info} for this plugin */
  72      protected $availableupdates;
  73  
  74      /**
  75       * Finds all enabled plugins, the result may include missing plugins.
  76       * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown
  77       */
  78      public static function get_enabled_plugins() {
  79          return null;
  80      }
  81  
  82      /**
  83       * Gathers and returns the information about all plugins of the given type,
  84       * either on disk or previously installed.
  85       *
  86       * This is supposed to be used exclusively by the plugin manager when it is
  87       * populating its tree of plugins.
  88       *
  89       * @param string $type the name of the plugintype, eg. mod, auth or workshopform
  90       * @param string $typerootdir full path to the location of the plugin dir
  91       * @param string $typeclass the name of the actually called class
  92       * @param core_plugin_manager $pluginman the plugin manager calling this method
  93       * @return array of plugintype classes, indexed by the plugin name
  94       */
  95      public static function get_plugins($type, $typerootdir, $typeclass, $pluginman) {
  96          // Get the information about plugins at the disk.
  97          $plugins = core_component::get_plugin_list($type);
  98          $return = array();
  99          foreach ($plugins as $pluginname => $pluginrootdir) {
 100              $return[$pluginname] = self::make_plugin_instance($type, $typerootdir,
 101                  $pluginname, $pluginrootdir, $typeclass, $pluginman);
 102          }
 103  
 104          // Fetch missing incorrectly uninstalled plugins.
 105          $plugins = $pluginman->get_installed_plugins($type);
 106  
 107          foreach ($plugins as $name => $version) {
 108              if (isset($return[$name])) {
 109                  continue;
 110              }
 111              $plugin              = new $typeclass();
 112              $plugin->type        = $type;
 113              $plugin->typerootdir = $typerootdir;
 114              $plugin->name        = $name;
 115              $plugin->rootdir     = null;
 116              $plugin->displayname = $name;
 117              $plugin->versiondb   = $version;
 118              $plugin->pluginman   = $pluginman;
 119              $plugin->init_is_standard();
 120  
 121              $return[$name] = $plugin;
 122          }
 123  
 124          return $return;
 125      }
 126  
 127      /**
 128       * Makes a new instance of the plugininfo class
 129       *
 130       * @param string $type the plugin type, eg. 'mod'
 131       * @param string $typerootdir full path to the location of all the plugins of this type
 132       * @param string $name the plugin name, eg. 'workshop'
 133       * @param string $namerootdir full path to the location of the plugin
 134       * @param string $typeclass the name of class that holds the info about the plugin
 135       * @param core_plugin_manager $pluginman the plugin manager of the new instance
 136       * @return base the instance of $typeclass
 137       */
 138      protected static function make_plugin_instance($type, $typerootdir, $name, $namerootdir, $typeclass, $pluginman) {
 139          $plugin              = new $typeclass();
 140          $plugin->type        = $type;
 141          $plugin->typerootdir = $typerootdir;
 142          $plugin->name        = $name;
 143          $plugin->rootdir     = $namerootdir;
 144          $plugin->pluginman   = $pluginman;
 145  
 146          $plugin->init_display_name();
 147          $plugin->load_disk_version();
 148          $plugin->load_db_version();
 149          $plugin->init_is_standard();
 150  
 151          return $plugin;
 152      }
 153  
 154      /**
 155       * Is this plugin already installed and updated?
 156       * @return bool true if plugin installed and upgraded.
 157       */
 158      public function is_installed_and_upgraded() {
 159          if (!$this->rootdir) {
 160              return false;
 161          }
 162          if ($this->versiondb === null and $this->versiondisk === null) {
 163              // There is no version.php or version info inside it.
 164              return false;
 165          }
 166  
 167          return ((float)$this->versiondb === (float)$this->versiondisk);
 168      }
 169  
 170      /**
 171       * Sets {@link $displayname} property to a localized name of the plugin
 172       */
 173      public function init_display_name() {
 174          if (!get_string_manager()->string_exists('pluginname', $this->component)) {
 175              $this->displayname = '[pluginname,' . $this->component . ']';
 176          } else {
 177              $this->displayname = get_string('pluginname', $this->component);
 178          }
 179      }
 180  
 181      /**
 182       * Magic method getter, redirects to read only values.
 183       *
 184       * @param string $name
 185       * @return mixed
 186       */
 187      public function __get($name) {
 188          switch ($name) {
 189              case 'component': return $this->type . '_' . $this->name;
 190  
 191              default:
 192                  debugging('Invalid plugin property accessed! '.$name);
 193                  return null;
 194          }
 195      }
 196  
 197      /**
 198       * Return the full path name of a file within the plugin.
 199       *
 200       * No check is made to see if the file exists.
 201       *
 202       * @param string $relativepath e.g. 'version.php'.
 203       * @return string e.g. $CFG->dirroot . '/mod/quiz/version.php'.
 204       */
 205      public function full_path($relativepath) {
 206          if (empty($this->rootdir)) {
 207              return '';
 208          }
 209          return $this->rootdir . '/' . $relativepath;
 210      }
 211  
 212      /**
 213       * Sets {@link $versiondisk} property to a numerical value representing the
 214       * version of the plugin's source code.
 215       *
 216       * If the value is null after calling this method, either the plugin
 217       * does not use versioning (typically does not have any database
 218       * data) or is missing from disk.
 219       */
 220      public function load_disk_version() {
 221          $versions = $this->pluginman->get_present_plugins($this->type);
 222  
 223          $this->versiondisk = null;
 224          $this->versionrequires = null;
 225          $this->pluginsupported = null;
 226          $this->pluginincompatible = null;
 227          $this->dependencies = array();
 228  
 229          if (!isset($versions[$this->name])) {
 230              return;
 231          }
 232  
 233          $plugin = $versions[$this->name];
 234  
 235          if (isset($plugin->version)) {
 236              $this->versiondisk = $plugin->version;
 237          }
 238          if (isset($plugin->requires)) {
 239              $this->versionrequires = $plugin->requires;
 240          }
 241          if (isset($plugin->release)) {
 242              $this->release = $plugin->release;
 243          }
 244          if (isset($plugin->dependencies)) {
 245              $this->dependencies = $plugin->dependencies;
 246          }
 247  
 248          // Check that supports and incompatible are wellformed, exception otherwise.
 249          if (isset($plugin->supported)) {
 250              // Checks for structure of supported.
 251              $isint = (is_int($plugin->supported[0]) && is_int($plugin->supported[1]));
 252              $isrange = ($plugin->supported[0] <= $plugin->supported[1] && count($plugin->supported) == 2);
 253  
 254              if (is_array($plugin->supported) && $isint && $isrange) {
 255                  $this->pluginsupported = $plugin->supported;
 256              } else {
 257                  throw new coding_exception('Incorrect syntax in plugin supported declaration in '."$this->name");
 258              }
 259          }
 260  
 261          if (isset($plugin->incompatible) && $plugin->incompatible !== null) {
 262              if ((ctype_digit($plugin->incompatible) || is_int($plugin->incompatible)) && (int) $plugin->incompatible > 0) {
 263                  $this->pluginincompatible = intval($plugin->incompatible);
 264              } else {
 265                  throw new coding_exception('Incorrect syntax in plugin incompatible declaration in '."$this->name");
 266              }
 267          }
 268  
 269      }
 270  
 271      /**
 272       * Get the list of other plugins that this plugin requires to be installed.
 273       *
 274       * @return array with keys the frankenstyle plugin name, and values either
 275       *      a version string (like '2011101700') or the constant ANY_VERSION.
 276       */
 277      public function get_other_required_plugins() {
 278          if (is_null($this->dependencies)) {
 279              $this->load_disk_version();
 280          }
 281          return $this->dependencies;
 282      }
 283  
 284      /**
 285       * Is this is a subplugin?
 286       *
 287       * @return boolean
 288       */
 289      public function is_subplugin() {
 290          return ($this->get_parent_plugin() !== false);
 291      }
 292  
 293      /**
 294       * If I am a subplugin, return the name of my parent plugin.
 295       *
 296       * @return string|bool false if not a subplugin, name of the parent otherwise
 297       */
 298      public function get_parent_plugin() {
 299          return $this->pluginman->get_parent_of_subplugin($this->type);
 300      }
 301  
 302      /**
 303       * Sets {@link $versiondb} property to a numerical value representing the
 304       * currently installed version of the plugin.
 305       *
 306       * If the value is null after calling this method, either the plugin
 307       * does not use versioning (typically does not have any database
 308       * data) or has not been installed yet.
 309       */
 310      public function load_db_version() {
 311          $versions = $this->pluginman->get_installed_plugins($this->type);
 312  
 313          if (isset($versions[$this->name])) {
 314              $this->versiondb = $versions[$this->name];
 315          } else {
 316              $this->versiondb = null;
 317          }
 318      }
 319  
 320      /**
 321       * Sets {@link $source} property to one of core_plugin_manager::PLUGIN_SOURCE_xxx
 322       * constants.
 323       *
 324       * If the property's value is null after calling this method, then
 325       * the type of the plugin has not been recognized and you should throw
 326       * an exception.
 327       */
 328      public function init_is_standard() {
 329  
 330          $pluginman = $this->pluginman;
 331          $standard = $pluginman::standard_plugins_list($this->type);
 332  
 333          if ($standard !== false) {
 334              $standard = array_flip($standard);
 335              if (isset($standard[$this->name])) {
 336                  $this->source = core_plugin_manager::PLUGIN_SOURCE_STANDARD;
 337              } else if (!is_null($this->versiondb) and is_null($this->versiondisk)
 338                  and $pluginman::is_deleted_standard_plugin($this->type, $this->name)) {
 339                  $this->source = core_plugin_manager::PLUGIN_SOURCE_STANDARD; // To be deleted.
 340              } else {
 341                  $this->source = core_plugin_manager::PLUGIN_SOURCE_EXTENSION;
 342              }
 343          }
 344      }
 345  
 346      /**
 347       * Returns true if the plugin is shipped with the official distribution
 348       * of the current Moodle version, false otherwise.
 349       *
 350       * @return bool
 351       */
 352      public function is_standard() {
 353          return $this->source === core_plugin_manager::PLUGIN_SOURCE_STANDARD;
 354      }
 355  
 356      /**
 357       * Returns true if the the given Moodle version is enough to run this plugin
 358       *
 359       * @param string|int|double $moodleversion
 360       * @return bool
 361       */
 362      public function is_core_dependency_satisfied($moodleversion) {
 363  
 364          if (empty($this->versionrequires)) {
 365              return true;
 366  
 367          } else {
 368              return (double)$this->versionrequires <= (double)$moodleversion;
 369          }
 370      }
 371  
 372      /**
 373       * Returns true if the the given moodle branch is not stated incompatible with the plugin
 374       *
 375       * @param int $branch the moodle branch number
 376       * @return bool true if not incompatible with moodle branch
 377       */
 378      public function is_core_compatible_satisfied(int $branch) : bool {
 379          if (!empty($this->pluginincompatible) && ($branch >= $this->pluginincompatible)) {
 380              return false;
 381          } else {
 382              return true;
 383          }
 384      }
 385  
 386      /**
 387       * Returns the status of the plugin
 388       *
 389       * @return string one of core_plugin_manager::PLUGIN_STATUS_xxx constants
 390       */
 391      public function get_status() {
 392  
 393          $pluginman = $this->pluginman;
 394  
 395          if (is_null($this->versiondb) and is_null($this->versiondisk)) {
 396              return core_plugin_manager::PLUGIN_STATUS_NODB;
 397  
 398          } else if (is_null($this->versiondb) and !is_null($this->versiondisk)) {
 399              return core_plugin_manager::PLUGIN_STATUS_NEW;
 400  
 401          } else if (!is_null($this->versiondb) and is_null($this->versiondisk)) {
 402              if ($pluginman::is_deleted_standard_plugin($this->type, $this->name)) {
 403                  return core_plugin_manager::PLUGIN_STATUS_DELETE;
 404              } else {
 405                  return core_plugin_manager::PLUGIN_STATUS_MISSING;
 406              }
 407  
 408          } else if ((float)$this->versiondb === (float)$this->versiondisk) {
 409              // Note: the float comparison should work fine here
 410              //       because there are no arithmetic operations with the numbers.
 411              return core_plugin_manager::PLUGIN_STATUS_UPTODATE;
 412  
 413          } else if ($this->versiondb < $this->versiondisk) {
 414              return core_plugin_manager::PLUGIN_STATUS_UPGRADE;
 415  
 416          } else if ($this->versiondb > $this->versiondisk) {
 417              return core_plugin_manager::PLUGIN_STATUS_DOWNGRADE;
 418  
 419          } else {
 420              // $version = pi(); and similar funny jokes - hopefully Donald E. Knuth will never contribute to Moodle ;-)
 421              throw new coding_exception('Unable to determine plugin state, check the plugin versions');
 422          }
 423      }
 424  
 425      /**
 426       * Returns the information about plugin availability
 427       *
 428       * True means that the plugin is enabled. False means that the plugin is
 429       * disabled. Null means that the information is not available, or the
 430       * plugin does not support configurable availability or the availability
 431       * can not be changed.
 432       *
 433       * @return null|bool
 434       */
 435      public function is_enabled() {
 436          if (!$this->rootdir) {
 437              // Plugin missing.
 438              return false;
 439          }
 440  
 441          $enabled = $this->pluginman->get_enabled_plugins($this->type);
 442  
 443          if (!is_array($enabled)) {
 444              return null;
 445          }
 446  
 447          return isset($enabled[$this->name]);
 448      }
 449  
 450      /**
 451       * If there are updates for this plugin available, returns them.
 452       *
 453       * Returns array of {@link \core\update\info} objects, if some update
 454       * is available. Returns null if there is no update available or if the update
 455       * availability is unknown.
 456       *
 457       * Populates the property {@link $availableupdates} on first call (lazy
 458       * loading).
 459       *
 460       * @return array|null
 461       */
 462      public function available_updates() {
 463  
 464          if ($this->availableupdates === null) {
 465              // Lazy load the information about available updates.
 466              $this->availableupdates = $this->pluginman->load_available_updates_for_plugin($this->component);
 467          }
 468  
 469          if (empty($this->availableupdates) or !is_array($this->availableupdates)) {
 470              $this->availableupdates = array();
 471              return null;
 472          }
 473  
 474          $updates = array();
 475  
 476          foreach ($this->availableupdates as $availableupdate) {
 477              if ($availableupdate->version > $this->versiondisk) {
 478                  $updates[] = $availableupdate;
 479              }
 480          }
 481  
 482          if (empty($updates)) {
 483              return null;
 484          }
 485  
 486          return $updates;
 487      }
 488  
 489      /**
 490       * Returns the node name used in admin settings menu for this plugin settings (if applicable)
 491       *
 492       * @return null|string node name or null if plugin does not create settings node (default)
 493       */
 494      public function get_settings_section_name() {
 495          return null;
 496      }
 497  
 498      /**
 499       * Returns the URL of the plugin settings screen
 500       *
 501       * Null value means that the plugin either does not have the settings screen
 502       * or its location is not available via this library.
 503       *
 504       * @return null|moodle_url
 505       */
 506      public function get_settings_url() {
 507          $section = $this->get_settings_section_name();
 508          if ($section === null) {
 509              return null;
 510          }
 511          $settings = admin_get_root()->locate($section);
 512          if ($settings) {
 513              if ($settings instanceof \admin_settingpage) {
 514                  return new moodle_url('/admin/settings.php', [
 515                      'section' => $section,
 516                  ]);
 517              }
 518  
 519              if ($settings instanceof \admin_externalpage) {
 520                  return new moodle_url($settings->url);
 521              }
 522  
 523              if ($settings instanceof \admin_category) {
 524                  return $settings->get_settings_page_url();
 525              }
 526          }
 527          return null;
 528      }
 529  
 530      /**
 531       * Loads plugin settings to the settings tree
 532       *
 533       * This function usually includes settings.php file in plugins folder.
 534       * Alternatively it can create a link to some settings page (instance of admin_externalpage)
 535       *
 536       * @param \part_of_admin_tree $adminroot
 537       * @param string $parentnodename
 538       * @param bool $hassiteconfig whether the current user has moodle/site:config capability
 539       */
 540      public function load_settings(\part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
 541      }
 542  
 543      /**
 544       * Should there be a way to uninstall the plugin via the administration UI.
 545       *
 546       * By default uninstallation is not allowed, plugin developers must enable it explicitly!
 547       *
 548       * @return bool
 549       */
 550      public function is_uninstall_allowed() {
 551          return false;
 552      }
 553  
 554      /**
 555       * Optional extra warning before uninstallation, for example number of uses in courses.
 556       *
 557       * @return string
 558       */
 559      public function get_uninstall_extra_warning() {
 560          return '';
 561      }
 562  
 563      /**
 564       * Pre-uninstall hook.
 565       *
 566       * This is intended for disabling of plugin, some DB table purging, etc.
 567       *
 568       * NOTE: to be called from uninstall_plugin() only.
 569       * @private
 570       */
 571      public function uninstall_cleanup() {
 572          // Override when extending class,
 573          // do not forget to call parent::pre_uninstall_cleanup() at the end.
 574      }
 575  
 576      /**
 577       * Returns relative directory of the plugin with heading '/'
 578       *
 579       * @return string
 580       */
 581      public function get_dir() {
 582          global $CFG;
 583  
 584          return substr($this->rootdir, strlen($CFG->dirroot));
 585      }
 586  
 587      /**
 588       * Hook method to implement certain steps when uninstalling the plugin.
 589       *
 590       * This hook is called by {@link core_plugin_manager::uninstall_plugin()} so
 591       * it is basically usable only for those plugin types that use the default
 592       * uninstall tool provided by {@link self::get_default_uninstall_url()}.
 593       *
 594       * @param \progress_trace $progress traces the process
 595       * @return bool true on success, false on failure
 596       */
 597      public function uninstall(\progress_trace $progress) {
 598          return true;
 599      }
 600  
 601      /**
 602       * Where should we return after plugin of this type is uninstalled?
 603       * @param string $return
 604       * @return moodle_url
 605       */
 606      public function get_return_url_after_uninstall($return) {
 607          if ($return === 'manage') {
 608              if ($url = $this->get_manage_url()) {
 609                  return $url;
 610              }
 611          }
 612          return new moodle_url('/admin/plugins.php#plugin_type_cell_'.$this->type);
 613      }
 614  
 615      /**
 616       * Return URL used for management of plugins of this type.
 617       * @return moodle_url
 618       */
 619      public static function get_manage_url() {
 620          return null;
 621      }
 622  
 623      /**
 624       * Returns URL to a script that handles common plugin uninstall procedure.
 625       *
 626       * This URL is intended for all plugin uninstallations.
 627       *
 628       * @param string $return either 'overview' or 'manage'
 629       * @return moodle_url
 630       */
 631      public final function get_default_uninstall_url($return = 'overview') {
 632          return new moodle_url('/admin/plugins.php', array(
 633              'uninstall' => $this->component,
 634              'confirm' => 0,
 635              'return' => $return,
 636          ));
 637      }
 638  }