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