weekly release 4.5dev
[moodle.git] / lib / classes / plugininfo / base.php
blobf26bb591c9aa9d14386115af790a29e28c114190
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.
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/>.
17 /**
18 * Defines classes used for plugin info.
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
24 namespace core\plugininfo;
26 use coding_exception;
27 use core_component;
28 use core_plugin_manager;
29 use moodle_url;
31 /**
32 * Base class providing access to the information about a plugin
34 * @property-read string component the component name, type_name
36 abstract class base {
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;
71 /** @var array|null array of {@link \core\update\info} for this plugin */
72 protected $availableupdates;
74 /** @var int Move a plugin up in the plugin order */
75 public const MOVE_UP = -1;
77 /** @var int Move a plugin down in the plugin order */
78 public const MOVE_DOWN = 1;
80 /** @var array hold $plugin->supported in version.php */
81 public $supported;
83 /** @var int hold $plugin->incompatible in version.php */
84 public $incompatible;
86 /** @var string Name of the plugin */
87 public $component = '';
89 /**
90 * Whether this plugintype supports its plugins being disabled.
92 * @return bool
94 public static function plugintype_supports_disabling(): bool {
95 return false;
98 /**
99 * Finds all enabled plugins, the result may include missing plugins.
100 * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown
102 public static function get_enabled_plugins() {
103 return null;
107 * Enable or disable a plugin.
108 * When possible, the change will be stored into the config_log table, to let admins check when/who has modified it.
110 * @param string $pluginname The plugin name to enable/disable.
111 * @param int $enabled Whether the pluginname should be enabled (1) or not (0). This is an integer because some plugins, such
112 * as filters or repositories, might support more statuses than just enabled/disabled.
114 * @return bool Whether $pluginname has been updated or not.
116 public static function enable_plugin(string $pluginname, int $enabled): bool {
117 return false;
121 * Returns current status for a pluginname.
123 * @param string $pluginname The plugin name to check.
124 * @return int The current status (enabled, disabled...) of $pluginname.
126 public static function get_enabled_plugin(string $pluginname): int {
127 $enabledplugins = static::get_enabled_plugins();
128 $value = $enabledplugins && array_key_exists($pluginname, $enabledplugins);
129 return (int) $value;
133 * Gathers and returns the information about all plugins of the given type,
134 * either on disk or previously installed.
136 * This is supposed to be used exclusively by the plugin manager when it is
137 * populating its tree of plugins.
139 * @param string $type the name of the plugintype, eg. mod, auth or workshopform
140 * @param string $typerootdir full path to the location of the plugin dir
141 * @param string $typeclass the name of the actually called class
142 * @param core_plugin_manager $pluginman the plugin manager calling this method
143 * @return array of plugintype classes, indexed by the plugin name
145 public static function get_plugins($type, $typerootdir, $typeclass, $pluginman) {
146 // Get the information about plugins at the disk.
147 $plugins = core_component::get_plugin_list($type);
148 $return = array();
149 foreach ($plugins as $pluginname => $pluginrootdir) {
150 $return[$pluginname] = self::make_plugin_instance($type, $typerootdir,
151 $pluginname, $pluginrootdir, $typeclass, $pluginman);
154 // Fetch missing incorrectly uninstalled plugins.
155 $plugins = $pluginman->get_installed_plugins($type);
157 foreach ($plugins as $name => $version) {
158 if (isset($return[$name])) {
159 continue;
161 $plugin = new $typeclass();
162 $plugin->type = $type;
163 $plugin->typerootdir = $typerootdir;
164 $plugin->name = $name;
165 $plugin->component = $plugin->type.'_'.$plugin->name;
166 $plugin->rootdir = null;
167 $plugin->displayname = $name;
168 $plugin->versiondb = $version;
169 $plugin->pluginman = $pluginman;
170 $plugin->init_is_standard();
172 $return[$name] = $plugin;
175 return $return;
179 * Makes a new instance of the plugininfo class
181 * @param string $type the plugin type, eg. 'mod'
182 * @param string $typerootdir full path to the location of all the plugins of this type
183 * @param string $name the plugin name, eg. 'workshop'
184 * @param string $namerootdir full path to the location of the plugin
185 * @param string $typeclass the name of class that holds the info about the plugin
186 * @param core_plugin_manager $pluginman the plugin manager of the new instance
187 * @return base the instance of $typeclass
189 protected static function make_plugin_instance($type, $typerootdir, $name, $namerootdir, $typeclass, $pluginman) {
190 $plugin = new $typeclass();
191 $plugin->type = $type;
192 $plugin->typerootdir = $typerootdir;
193 $plugin->name = $name;
194 $plugin->rootdir = $namerootdir;
195 $plugin->pluginman = $pluginman;
196 $plugin->component = $plugin->type.'_'.$plugin->name;
198 $plugin->init_display_name();
199 $plugin->load_disk_version();
200 $plugin->load_db_version();
201 $plugin->init_is_standard();
203 return $plugin;
207 * Is this plugin already installed and updated?
208 * @return bool true if plugin installed and upgraded.
210 public function is_installed_and_upgraded() {
211 if (!$this->rootdir) {
212 return false;
214 if ($this->versiondb === null and $this->versiondisk === null) {
215 // There is no version.php or version info inside it.
216 return false;
219 return ((float)$this->versiondb === (float)$this->versiondisk);
223 * Sets {@link $displayname} property to a localized name of the plugin
225 public function init_display_name() {
226 if (!get_string_manager()->string_exists('pluginname', $this->component)) {
227 $this->displayname = '[pluginname,' . $this->component . ']';
228 } else {
229 $this->displayname = get_string('pluginname', $this->component);
234 * Magic method getter, redirects to read only values.
236 * @param string $name
237 * @return mixed
239 public function __get($name) {
240 switch ($name) {
241 case 'component': return $this->type . '_' . $this->name;
243 default:
244 debugging('Invalid plugin property accessed! '.$name);
245 return null;
250 * Return the full path name of a file within the plugin.
252 * No check is made to see if the file exists.
254 * @param string $relativepath e.g. 'version.php'.
255 * @return string e.g. $CFG->dirroot . '/mod/quiz/version.php'.
257 public function full_path($relativepath) {
258 if (empty($this->rootdir)) {
259 return '';
261 return $this->rootdir . '/' . $relativepath;
265 * Sets {@link $versiondisk} property to a numerical value representing the
266 * version of the plugin's source code.
268 * If the value is null after calling this method, either the plugin
269 * does not use versioning (typically does not have any database
270 * data) or is missing from disk.
272 public function load_disk_version() {
273 $versions = $this->pluginman->get_present_plugins($this->type);
275 $this->versiondisk = null;
276 $this->versionrequires = null;
277 $this->pluginsupported = null;
278 $this->pluginincompatible = null;
279 $this->dependencies = array();
281 if (!isset($versions[$this->name])) {
282 return;
285 $plugin = $versions[$this->name];
287 if (isset($plugin->version)) {
288 $this->versiondisk = $plugin->version;
290 if (isset($plugin->requires)) {
291 $this->versionrequires = $plugin->requires;
293 if (isset($plugin->release)) {
294 $this->release = $plugin->release;
296 if (isset($plugin->dependencies)) {
297 $this->dependencies = $plugin->dependencies;
300 // Check that supports and incompatible are wellformed, exception otherwise.
301 if (isset($plugin->supported)) {
302 // Checks for structure of supported.
303 $isint = (is_int($plugin->supported[0]) && is_int($plugin->supported[1]));
304 $isrange = ($plugin->supported[0] <= $plugin->supported[1] && count($plugin->supported) == 2);
306 if (is_array($plugin->supported) && $isint && $isrange) {
307 $this->pluginsupported = $plugin->supported;
308 } else {
309 throw new coding_exception('Incorrect syntax in plugin supported declaration in '."$this->name");
313 if (isset($plugin->incompatible) && $plugin->incompatible !== null) {
314 if (((is_string($plugin->incompatible) && ctype_digit($plugin->incompatible)) || is_int($plugin->incompatible))
315 && (int) $plugin->incompatible > 0) {
316 $this->pluginincompatible = intval($plugin->incompatible);
317 } else {
318 throw new coding_exception('Incorrect syntax in plugin incompatible declaration in '."$this->name");
325 * Get the list of other plugins that this plugin requires to be installed.
327 * @return array with keys the frankenstyle plugin name, and values either
328 * a version string (like '2011101700') or the constant ANY_VERSION.
330 public function get_other_required_plugins() {
331 if (is_null($this->dependencies)) {
332 $this->load_disk_version();
334 return $this->dependencies;
338 * Is this is a subplugin?
340 * @return boolean
342 public function is_subplugin() {
343 return ($this->get_parent_plugin() !== false);
347 * If I am a subplugin, return the name of my parent plugin.
349 * @return string|bool false if not a subplugin, name of the parent otherwise
351 public function get_parent_plugin() {
352 return $this->pluginman->get_parent_of_subplugin($this->type);
356 * Sets {@link $versiondb} property to a numerical value representing the
357 * currently installed version of the plugin.
359 * If the value is null after calling this method, either the plugin
360 * does not use versioning (typically does not have any database
361 * data) or has not been installed yet.
363 public function load_db_version() {
364 $versions = $this->pluginman->get_installed_plugins($this->type);
366 if (isset($versions[$this->name])) {
367 $this->versiondb = $versions[$this->name];
368 } else {
369 $this->versiondb = null;
374 * Sets {@link $source} property to one of core_plugin_manager::PLUGIN_SOURCE_xxx
375 * constants.
377 * If the property's value is null after calling this method, then
378 * the type of the plugin has not been recognized and you should throw
379 * an exception.
381 public function init_is_standard() {
383 $pluginman = $this->pluginman;
384 $standard = $pluginman::standard_plugins_list($this->type);
386 if ($standard !== false) {
387 $standard = array_flip($standard);
388 if (isset($standard[$this->name])) {
389 $this->source = core_plugin_manager::PLUGIN_SOURCE_STANDARD;
390 } else if (!is_null($this->versiondb) and is_null($this->versiondisk)
391 and $pluginman::is_deleted_standard_plugin($this->type, $this->name)) {
392 $this->source = core_plugin_manager::PLUGIN_SOURCE_STANDARD; // To be deleted.
393 } else {
394 $this->source = core_plugin_manager::PLUGIN_SOURCE_EXTENSION;
400 * Returns true if the plugin is shipped with the official distribution
401 * of the current Moodle version, false otherwise.
403 * @return bool
405 public function is_standard() {
406 return $this->source === core_plugin_manager::PLUGIN_SOURCE_STANDARD;
410 * Returns true if the the given Moodle version is enough to run this plugin
412 * @param string|int|double $moodleversion
413 * @return bool
415 public function is_core_dependency_satisfied($moodleversion) {
417 if (empty($this->versionrequires)) {
418 return true;
420 } else {
421 return (double)$this->versionrequires <= (double)$moodleversion;
426 * Returns true if the the given moodle branch is not stated incompatible with the plugin
428 * @param int $branch the moodle branch number
429 * @return bool true if not incompatible with moodle branch
431 public function is_core_compatible_satisfied(int $branch): bool {
432 if (!empty($this->pluginincompatible) && ($branch >= $this->pluginincompatible)) {
433 return false;
434 } else {
435 return true;
440 * Returns the status of the plugin
442 * @return string one of core_plugin_manager::PLUGIN_STATUS_xxx constants
444 public function get_status() {
446 $pluginman = $this->pluginman;
448 if (is_null($this->versiondb) and is_null($this->versiondisk)) {
449 return core_plugin_manager::PLUGIN_STATUS_NODB;
451 } else if (is_null($this->versiondb) and !is_null($this->versiondisk)) {
452 return core_plugin_manager::PLUGIN_STATUS_NEW;
454 } else if (!is_null($this->versiondb) and is_null($this->versiondisk)) {
455 if ($pluginman::is_deleted_standard_plugin($this->type, $this->name)) {
456 return core_plugin_manager::PLUGIN_STATUS_DELETE;
457 } else {
458 return core_plugin_manager::PLUGIN_STATUS_MISSING;
461 } else if ((float)$this->versiondb === (float)$this->versiondisk) {
462 // Note: the float comparison should work fine here
463 // because there are no arithmetic operations with the numbers.
464 return core_plugin_manager::PLUGIN_STATUS_UPTODATE;
466 } else if ($this->versiondb < $this->versiondisk) {
467 return core_plugin_manager::PLUGIN_STATUS_UPGRADE;
469 } else if ($this->versiondb > $this->versiondisk) {
470 return core_plugin_manager::PLUGIN_STATUS_DOWNGRADE;
472 } else {
473 // $version = pi(); and similar funny jokes - hopefully Donald E. Knuth will never contribute to Moodle ;-)
474 throw new coding_exception('Unable to determine plugin state, check the plugin versions');
479 * Returns the information about plugin availability
481 * True means that the plugin is enabled. False means that the plugin is
482 * disabled. Null means that the information is not available, or the
483 * plugin does not support configurable availability or the availability
484 * can not be changed.
486 * @return null|bool
488 public function is_enabled() {
489 if (!$this->rootdir) {
490 // Plugin missing.
491 return false;
494 $enabled = $this->pluginman->get_enabled_plugins($this->type);
496 if (!is_array($enabled)) {
497 return null;
500 return isset($enabled[$this->name]);
504 * If there are updates for this plugin available, returns them.
506 * Returns array of {@link \core\update\info} objects, if some update
507 * is available. Returns null if there is no update available or if the update
508 * availability is unknown.
510 * Populates the property {@link $availableupdates} on first call (lazy
511 * loading).
513 * @return array|null
515 public function available_updates() {
517 if ($this->availableupdates === null) {
518 // Lazy load the information about available updates.
519 $this->availableupdates = $this->pluginman->load_available_updates_for_plugin($this->component);
522 if (empty($this->availableupdates) or !is_array($this->availableupdates)) {
523 $this->availableupdates = array();
524 return null;
527 $updates = array();
529 foreach ($this->availableupdates as $availableupdate) {
530 if ($availableupdate->version > $this->versiondisk) {
531 $updates[] = $availableupdate;
535 if (empty($updates)) {
536 return null;
539 return $updates;
543 * Returns the node name used in admin settings menu for this plugin settings (if applicable)
545 * @return null|string node name or null if plugin does not create settings node (default)
547 public function get_settings_section_name() {
548 return null;
552 * Returns the URL of the plugin settings screen
554 * Null value means that the plugin either does not have the settings screen
555 * or its location is not available via this library.
557 * @return null|moodle_url
559 public function get_settings_url(): ?moodle_url {
560 $section = $this->get_settings_section_name();
561 if ($section === null) {
562 return null;
565 $settings = admin_get_root()->locate($section);
566 if ($settings && $settings instanceof \core_admin\local\settings\linkable_settings_page) {
567 return $settings->get_settings_page_url();
570 return null;
574 * Loads plugin settings to the settings tree
576 * This function usually includes settings.php file in plugins folder.
577 * Alternatively it can create a link to some settings page (instance of admin_externalpage)
579 * @param \part_of_admin_tree $adminroot
580 * @param string $parentnodename
581 * @param bool $hassiteconfig whether the current user has moodle/site:config capability
583 public function load_settings(\part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
587 * Should there be a way to uninstall the plugin via the administration UI.
589 * By default uninstallation is not allowed, plugin developers must enable it explicitly!
591 * @return bool
593 public function is_uninstall_allowed() {
594 return false;
598 * Optional extra warning before uninstallation, for example number of uses in courses.
600 * @return string
602 public function get_uninstall_extra_warning() {
603 return '';
607 * Pre-uninstall hook.
609 * This is intended for disabling of plugin, some DB table purging, etc.
611 * NOTE: to be called from uninstall_plugin() only.
612 * @private
614 public function uninstall_cleanup() {
615 // Override when extending class,
616 // do not forget to call parent::pre_uninstall_cleanup() at the end.
620 * Returns relative directory of the plugin with heading '/'
622 * @return string
624 public function get_dir() {
625 global $CFG;
627 if (!isset($this->rootdir)) {
628 return '';
631 return substr($this->rootdir, strlen($CFG->dirroot));
635 * Hook method to implement certain steps when uninstalling the plugin.
637 * This hook is called by {@link core_plugin_manager::uninstall_plugin()} so
638 * it is basically usable only for those plugin types that use the default
639 * uninstall tool provided by {@link self::get_default_uninstall_url()}.
641 * @param \progress_trace $progress traces the process
642 * @return bool true on success, false on failure
644 public function uninstall(\progress_trace $progress) {
645 return true;
649 * Where should we return after plugin of this type is uninstalled?
650 * @param string $return
651 * @return moodle_url
653 public function get_return_url_after_uninstall($return) {
654 if ($return === 'manage') {
655 if ($url = $this->get_manage_url()) {
656 return $url;
659 return new moodle_url('/admin/plugins.php#plugin_type_cell_'.$this->type);
663 * Return URL used for management of plugins of this type.
664 * @return moodle_url
666 public static function get_manage_url() {
667 return null;
671 * Returns URL to a script that handles common plugin uninstall procedure.
673 * This URL is intended for all plugin uninstallations.
675 * @param string $return either 'overview' or 'manage'
676 * @return moodle_url
678 final public function get_default_uninstall_url($return = 'overview') {
679 return new moodle_url('/admin/plugins.php', array(
680 'uninstall' => $this->component,
681 'confirm' => 0,
682 'return' => $return,
687 * Whether this plugintype supports ordering of plugins using native functionality.
689 * Please note that plugintypes which pre-date this native functionality may still support ordering
690 * but will not use the built-in functionality.
692 * @return bool
694 public static function plugintype_supports_ordering(): bool {
695 return false;
699 * Finds all enabled plugins, the result may include missing plugins.
701 * @param bool $enabledonly Show all plugins, or only those which are enabled
702 * @return array|null of sorted plugins $pluginname => $pluginname, null means unknown
704 public static function get_sorted_plugins(bool $enabledonly = false): ?array {
705 return null;
709 * Change the order of the plugin relative to other plugins in the plugintype.
711 * When possible, the change will be stored into the config_log table, to let admins check when/who has modified it.
713 * @param string $pluginname The plugin name to enable/disable.
714 * @param int $direction The direction to move the plugin. Negative numbers mean up, Positive mean down.
715 * @return bool Whether $pluginname has been updated or not.
717 public static function change_plugin_order(string $pluginname, int $direction): bool {
718 return false;