MDL-59562 core: updated type hint of new function
[moodle.git] / lib / classes / plugininfo / base.php
CommitLineData
e87214bd
PS
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 */
24namespace core\plugininfo;
25
26use core_component, core_plugin_manager, moodle_url, coding_exception;
27
28defined('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 */
36abstract 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;
147a895a
PB
56 /** @var array explicitly supported branches of Moodle core */
57 public $pluginsupported;
58 /** @var int first incompatible branch of Moodle core */
59 public $pluginincompatible;
2f98f5d5
PS
60 /** @var mixed human-readable release information */
61 public $release;
e87214bd
PS
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;
2d488c8f
DM
68 /** @var core_plugin_manager the plugin manager this plugin info is part of */
69 public $pluginman;
30d8bc5f 70
e87214bd 71 /** @var array|null array of {@link \core\update\info} for this plugin */
30d8bc5f 72 protected $availableupdates;
e87214bd
PS
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 *
2d488c8f
DM
86 * This is supposed to be used exclusively by the plugin manager when it is
87 * populating its tree of plugins.
88 *
e87214bd
PS
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
2d488c8f 92 * @param core_plugin_manager $pluginman the plugin manager calling this method
e87214bd
PS
93 * @return array of plugintype classes, indexed by the plugin name
94 */
2d488c8f 95 public static function get_plugins($type, $typerootdir, $typeclass, $pluginman) {
e87214bd
PS
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,
2d488c8f 101 $pluginname, $pluginrootdir, $typeclass, $pluginman);
e87214bd
PS
102 }
103
104 // Fetch missing incorrectly uninstalled plugins.
2d488c8f 105 $plugins = $pluginman->get_installed_plugins($type);
e87214bd
PS
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;
2d488c8f 118 $plugin->pluginman = $pluginman;
e87214bd
PS
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
2d488c8f 135 * @param core_plugin_manager $pluginman the plugin manager of the new instance
e87214bd
PS
136 * @return base the instance of $typeclass
137 */
2d488c8f 138 protected static function make_plugin_instance($type, $typerootdir, $name, $namerootdir, $typeclass, $pluginman) {
e87214bd
PS
139 $plugin = new $typeclass();
140 $plugin->type = $type;
141 $plugin->typerootdir = $typerootdir;
142 $plugin->name = $name;
143 $plugin->rootdir = $namerootdir;
2d488c8f 144 $plugin->pluginman = $pluginman;
e87214bd
PS
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) {
4ad70aed
DM
163 // There is no version.php or version info inside it.
164 return false;
e87214bd
PS
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() {
2d488c8f 221 $versions = $this->pluginman->get_present_plugins($this->type);
e87214bd
PS
222
223 $this->versiondisk = null;
224 $this->versionrequires = null;
147a895a
PB
225 $this->pluginsupported = null;
226 $this->pluginincompatible = null;
e87214bd
PS
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 }
2f98f5d5
PS
241 if (isset($plugin->release)) {
242 $this->release = $plugin->release;
243 }
e87214bd
PS
244 if (isset($plugin->dependencies)) {
245 $this->dependencies = $plugin->dependencies;
246 }
147a895a
PB
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 {
72aac063 257 throw new coding_exception('Incorrect syntax in plugin supported declaration in '."$this->name");
147a895a
PB
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 {
72aac063 265 throw new coding_exception('Incorrect syntax in plugin incompatible declaration in '."$this->name");
147a895a
PB
266 }
267 }
268
e87214bd
PS
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() {
2d488c8f 299 return $this->pluginman->get_parent_of_subplugin($this->type);
e87214bd
PS
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() {
2d488c8f 311 $versions = $this->pluginman->get_installed_plugins($this->type);
e87214bd
PS
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
2d488c8f
DM
330 $pluginman = $this->pluginman;
331 $standard = $pluginman::standard_plugins_list($this->type);
e87214bd
PS
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)
2d488c8f 338 and $pluginman::is_deleted_standard_plugin($this->type, $this->name)) {
e87214bd
PS
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
147a895a
PB
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
e87214bd
PS
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
2d488c8f
DM
393 $pluginman = $this->pluginman;
394
e87214bd
PS
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)) {
2d488c8f 402 if ($pluginman::is_deleted_standard_plugin($this->type, $this->name)) {
e87214bd
PS
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
2d488c8f 441 $enabled = $this->pluginman->get_enabled_plugins($this->type);
e87214bd
PS
442
443 if (!is_array($enabled)) {
444 return null;
445 }
446
447 return isset($enabled[$this->name]);
448 }
449
e87214bd
PS
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 *
c44bbe35
DM
457 * Populates the property {@link $availableupdates} on first call (lazy
458 * loading).
459 *
e87214bd
PS
460 * @return array|null
461 */
462 public function available_updates() {
463
30d8bc5f
DM
464 if ($this->availableupdates === null) {
465 // Lazy load the information about available updates.
c44bbe35 466 $this->availableupdates = $this->pluginman->load_available_updates_for_plugin($this->component);
30d8bc5f
DM
467 }
468
e87214bd 469 if (empty($this->availableupdates) or !is_array($this->availableupdates)) {
c44bbe35 470 $this->availableupdates = array();
e87214bd
PS
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 && $settings instanceof \admin_settingpage) {
513 return new moodle_url('/admin/settings.php', array('section' => $section));
514 } else if ($settings && $settings instanceof \admin_externalpage) {
515 return new moodle_url($settings->url);
516 } else {
517 return null;
518 }
519 }
520
521 /**
522 * Loads plugin settings to the settings tree
523 *
524 * This function usually includes settings.php file in plugins folder.
525 * Alternatively it can create a link to some settings page (instance of admin_externalpage)
526 *
527 * @param \part_of_admin_tree $adminroot
528 * @param string $parentnodename
529 * @param bool $hassiteconfig whether the current user has moodle/site:config capability
530 */
531 public function load_settings(\part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
532 }
533
534 /**
535 * Should there be a way to uninstall the plugin via the administration UI.
536 *
537 * By default uninstallation is not allowed, plugin developers must enable it explicitly!
538 *
539 * @return bool
540 */
541 public function is_uninstall_allowed() {
542 return false;
543 }
544
545 /**
546 * Optional extra warning before uninstallation, for example number of uses in courses.
547 *
548 * @return string
549 */
550 public function get_uninstall_extra_warning() {
551 return '';
552 }
553
554 /**
555 * Pre-uninstall hook.
556 *
557 * This is intended for disabling of plugin, some DB table purging, etc.
558 *
559 * NOTE: to be called from uninstall_plugin() only.
560 * @private
561 */
562 public function uninstall_cleanup() {
563 // Override when extending class,
564 // do not forget to call parent::pre_uninstall_cleanup() at the end.
565 }
566
567 /**
568 * Returns relative directory of the plugin with heading '/'
569 *
570 * @return string
571 */
572 public function get_dir() {
573 global $CFG;
574
575 return substr($this->rootdir, strlen($CFG->dirroot));
576 }
577
578 /**
579 * Hook method to implement certain steps when uninstalling the plugin.
580 *
581 * This hook is called by {@link core_plugin_manager::uninstall_plugin()} so
582 * it is basically usable only for those plugin types that use the default
583 * uninstall tool provided by {@link self::get_default_uninstall_url()}.
584 *
585 * @param \progress_trace $progress traces the process
586 * @return bool true on success, false on failure
587 */
588 public function uninstall(\progress_trace $progress) {
589 return true;
590 }
591
592 /**
593 * Where should we return after plugin of this type is uninstalled?
594 * @param string $return
595 * @return moodle_url
596 */
597 public function get_return_url_after_uninstall($return) {
598 if ($return === 'manage') {
599 if ($url = $this->get_manage_url()) {
600 return $url;
601 }
602 }
603 return new moodle_url('/admin/plugins.php#plugin_type_cell_'.$this->type);
604 }
605
606 /**
607 * Return URL used for management of plugins of this type.
608 * @return moodle_url
609 */
610 public static function get_manage_url() {
611 return null;
612 }
613
614 /**
615 * Returns URL to a script that handles common plugin uninstall procedure.
616 *
617 * This URL is intended for all plugin uninstallations.
618 *
619 * @param string $return either 'overview' or 'manage'
620 * @return moodle_url
621 */
622 public final function get_default_uninstall_url($return = 'overview') {
623 return new moodle_url('/admin/plugins.php', array(
624 'sesskey' => sesskey(),
625 'uninstall' => $this->component,
626 'confirm' => 0,
627 'return' => $return,
628 ));
629 }
e87214bd 630}