Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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

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