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