2 // This file is part of Moodle - http://moodle.org/
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.
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/>.
18 * Defines classes used for plugin info.
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
;
28 use core_plugin_manager
;
32 * Base class providing access to the information about a plugin
34 * @property-read string component the component name, type_name
38 /** @var string the plugintype name, eg. mod, auth or workshopform */
40 /** @var string full path to the location of all the plugins of this type */
42 /** @var string the plugin name, eg. assignment, ldap */
44 /** @var string the localized plugin name */
46 /** @var string the plugin source, one of core_plugin_manager::PLUGIN_SOURCE_xxx constants */
48 /** @var string fullpath to the location of this plugin */
50 /** @var int|string the version of the plugin's source code */
52 /** @var int|string the version of the installed plugin */
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 */
62 /** @var array other plugins that this one depends on, lazy-loaded by {@link get_other_required_plugins()} */
64 /** @var int number of instances of the plugin - not supported yet */
66 /** @var int order of the plugin among other plugins of the same type - not supported yet */
68 /** @var core_plugin_manager the plugin manager this plugin info is part of */
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 */
83 /** @var int hold $plugin->incompatible in version.php */
86 /** @var string Name of the plugin */
87 public $component = '';
90 * Whether this plugintype supports its plugins being disabled.
94 public static function plugintype_supports_disabling(): bool {
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() {
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 {
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);
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);
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])) {
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;
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();
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
) {
214 if ($this->versiondb
=== null and $this->versiondisk
=== null) {
215 // There is no version.php or version info inside it.
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
. ']';
229 $this->displayname
= get_string('pluginname', $this->component
);
234 * Magic method getter, redirects to read only values.
236 * @param string $name
239 public function __get($name) {
241 case 'component': return $this->type
. '_' . $this->name
;
244 debugging('Invalid plugin property accessed! '.$name);
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
)) {
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
])) {
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
;
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
);
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?
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
];
369 $this->versiondb
= null;
374 * Sets {@link $source} property to one of core_plugin_manager::PLUGIN_SOURCE_xxx
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
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.
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.
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
415 public function is_core_dependency_satisfied($moodleversion) {
417 if (empty($this->versionrequires
)) {
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
)) {
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
;
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
;
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.
488 public function is_enabled() {
489 if (!$this->rootdir
) {
494 $enabled = $this->pluginman
->get_enabled_plugins($this->type
);
496 if (!is_array($enabled)) {
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
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();
529 foreach ($this->availableupdates
as $availableupdate) {
530 if ($availableupdate->version
> $this->versiondisk
) {
531 $updates[] = $availableupdate;
535 if (empty($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() {
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) {
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();
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!
593 public function is_uninstall_allowed() {
598 * Optional extra warning before uninstallation, for example number of uses in courses.
602 public function get_uninstall_extra_warning() {
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.
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 '/'
624 public function get_dir() {
627 if (!isset($this->rootdir
)) {
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) {
649 * Where should we return after plugin of this type is uninstalled?
650 * @param string $return
653 public function get_return_url_after_uninstall($return) {
654 if ($return === 'manage') {
655 if ($url = $this->get_manage_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.
666 public static function get_manage_url() {
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'
678 final public function get_default_uninstall_url($return = 'overview') {
679 return new moodle_url('/admin/plugins.php', array(
680 'uninstall' => $this->component
,
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.
694 public static function plugintype_supports_ordering(): bool {
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 {
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 {