fac91ac121ceaacec3d0654f06ef273b0f4a437f
[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,
151             // for now let's pretend it is ok.
152             // TODO: return false once we require version in each plugin.
153             return true;
154         }
156         return ((float)$this->versiondb === (float)$this->versiondisk);
157     }
159     /**
160      * Sets {@link $displayname} property to a localized name of the plugin
161      */
162     public function init_display_name() {
163         if (!get_string_manager()->string_exists('pluginname', $this->component)) {
164             $this->displayname = '[pluginname,' . $this->component . ']';
165         } else {
166             $this->displayname = get_string('pluginname', $this->component);
167         }
168     }
170     /**
171      * Magic method getter, redirects to read only values.
172      *
173      * @param string $name
174      * @return mixed
175      */
176     public function __get($name) {
177         switch ($name) {
178             case 'component': return $this->type . '_' . $this->name;
180             default:
181                 debugging('Invalid plugin property accessed! '.$name);
182                 return null;
183         }
184     }
186     /**
187      * Return the full path name of a file within the plugin.
188      *
189      * No check is made to see if the file exists.
190      *
191      * @param string $relativepath e.g. 'version.php'.
192      * @return string e.g. $CFG->dirroot . '/mod/quiz/version.php'.
193      */
194     public function full_path($relativepath) {
195         if (empty($this->rootdir)) {
196             return '';
197         }
198         return $this->rootdir . '/' . $relativepath;
199     }
201     /**
202      * Sets {@link $versiondisk} property to a numerical value representing the
203      * version of the plugin's source code.
204      *
205      * If the value is null after calling this method, either the plugin
206      * does not use versioning (typically does not have any database
207      * data) or is missing from disk.
208      */
209     public function load_disk_version() {
210         $versions = core_plugin_manager::instance()->get_present_plugins($this->type);
212         $this->versiondisk = null;
213         $this->versionrequires = null;
214         $this->dependencies = array();
216         if (!isset($versions[$this->name])) {
217             return;
218         }
220         $plugin = $versions[$this->name];
222         if (isset($plugin->version)) {
223             $this->versiondisk = $plugin->version;
224         }
225         if (isset($plugin->requires)) {
226             $this->versionrequires = $plugin->requires;
227         }
228         if (isset($plugin->release)) {
229             $this->release = $plugin->release;
230         }
231         if (isset($plugin->dependencies)) {
232             $this->dependencies = $plugin->dependencies;
233         }
234     }
236     /**
237      * Get the list of other plugins that this plugin requires to be installed.
238      *
239      * @return array with keys the frankenstyle plugin name, and values either
240      *      a version string (like '2011101700') or the constant ANY_VERSION.
241      */
242     public function get_other_required_plugins() {
243         if (is_null($this->dependencies)) {
244             $this->load_disk_version();
245         }
246         return $this->dependencies;
247     }
249     /**
250      * Is this is a subplugin?
251      *
252      * @return boolean
253      */
254     public function is_subplugin() {
255         return ($this->get_parent_plugin() !== false);
256     }
258     /**
259      * If I am a subplugin, return the name of my parent plugin.
260      *
261      * @return string|bool false if not a subplugin, name of the parent otherwise
262      */
263     public function get_parent_plugin() {
264         return $this->get_plugin_manager()->get_parent_of_subplugin($this->type);
265     }
267     /**
268      * Sets {@link $versiondb} property to a numerical value representing the
269      * currently installed version of the plugin.
270      *
271      * If the value is null after calling this method, either the plugin
272      * does not use versioning (typically does not have any database
273      * data) or has not been installed yet.
274      */
275     public function load_db_version() {
276         $versions = core_plugin_manager::instance()->get_installed_plugins($this->type);
278         if (isset($versions[$this->name])) {
279             $this->versiondb = $versions[$this->name];
280         } else {
281             $this->versiondb = null;
282         }
283     }
285     /**
286      * Sets {@link $source} property to one of core_plugin_manager::PLUGIN_SOURCE_xxx
287      * constants.
288      *
289      * If the property's value is null after calling this method, then
290      * the type of the plugin has not been recognized and you should throw
291      * an exception.
292      */
293     public function init_is_standard() {
295         $standard = core_plugin_manager::standard_plugins_list($this->type);
297         if ($standard !== false) {
298             $standard = array_flip($standard);
299             if (isset($standard[$this->name])) {
300                 $this->source = core_plugin_manager::PLUGIN_SOURCE_STANDARD;
301             } else if (!is_null($this->versiondb) and is_null($this->versiondisk)
302                 and core_plugin_manager::is_deleted_standard_plugin($this->type, $this->name)) {
303                 $this->source = core_plugin_manager::PLUGIN_SOURCE_STANDARD; // To be deleted.
304             } else {
305                 $this->source = core_plugin_manager::PLUGIN_SOURCE_EXTENSION;
306             }
307         }
308     }
310     /**
311      * Returns true if the plugin is shipped with the official distribution
312      * of the current Moodle version, false otherwise.
313      *
314      * @return bool
315      */
316     public function is_standard() {
317         return $this->source === core_plugin_manager::PLUGIN_SOURCE_STANDARD;
318     }
320     /**
321      * Returns true if the the given Moodle version is enough to run this plugin
322      *
323      * @param string|int|double $moodleversion
324      * @return bool
325      */
326     public function is_core_dependency_satisfied($moodleversion) {
328         if (empty($this->versionrequires)) {
329             return true;
331         } else {
332             return (double)$this->versionrequires <= (double)$moodleversion;
333         }
334     }
336     /**
337      * Returns the status of the plugin
338      *
339      * @return string one of core_plugin_manager::PLUGIN_STATUS_xxx constants
340      */
341     public function get_status() {
343         if (is_null($this->versiondb) and is_null($this->versiondisk)) {
344             return core_plugin_manager::PLUGIN_STATUS_NODB;
346         } else if (is_null($this->versiondb) and !is_null($this->versiondisk)) {
347             return core_plugin_manager::PLUGIN_STATUS_NEW;
349         } else if (!is_null($this->versiondb) and is_null($this->versiondisk)) {
350             if (core_plugin_manager::is_deleted_standard_plugin($this->type, $this->name)) {
351                 return core_plugin_manager::PLUGIN_STATUS_DELETE;
352             } else {
353                 return core_plugin_manager::PLUGIN_STATUS_MISSING;
354             }
356         } else if ((float)$this->versiondb === (float)$this->versiondisk) {
357             // Note: the float comparison should work fine here
358             //       because there are no arithmetic operations with the numbers.
359             return core_plugin_manager::PLUGIN_STATUS_UPTODATE;
361         } else if ($this->versiondb < $this->versiondisk) {
362             return core_plugin_manager::PLUGIN_STATUS_UPGRADE;
364         } else if ($this->versiondb > $this->versiondisk) {
365             return core_plugin_manager::PLUGIN_STATUS_DOWNGRADE;
367         } else {
368             // $version = pi(); and similar funny jokes - hopefully Donald E. Knuth will never contribute to Moodle ;-)
369             throw new coding_exception('Unable to determine plugin state, check the plugin versions');
370         }
371     }
373     /**
374      * Returns the information about plugin availability
375      *
376      * True means that the plugin is enabled. False means that the plugin is
377      * disabled. Null means that the information is not available, or the
378      * plugin does not support configurable availability or the availability
379      * can not be changed.
380      *
381      * @return null|bool
382      */
383     public function is_enabled() {
384         if (!$this->rootdir) {
385             // Plugin missing.
386             return false;
387         }
389         $enabled = core_plugin_manager::instance()->get_enabled_plugins($this->type);
391         if (!is_array($enabled)) {
392             return null;
393         }
395         return isset($enabled[$this->name]);
396     }
398     /**
399      * Populates the property {@link $availableupdates} with the information provided by
400      * available update checker
401      *
402      * @param \core\update\checker $provider the class providing the available update info
403      */
404     public function check_available_updates(\core\update\checker $provider) {
405         global $CFG;
407         if (isset($CFG->updateminmaturity)) {
408             $minmaturity = $CFG->updateminmaturity;
409         } else {
410             // This can happen during the very first upgrade to 2.3 .
411             $minmaturity = MATURITY_STABLE;
412         }
414         $this->availableupdates = $provider->get_update_info($this->component,
415             array('minmaturity' => $minmaturity));
416     }
418     /**
419      * If there are updates for this plugin available, returns them.
420      *
421      * Returns array of {@link \core\update\info} objects, if some update
422      * is available. Returns null if there is no update available or if the update
423      * availability is unknown.
424      *
425      * @return array|null
426      */
427     public function available_updates() {
429         if (empty($this->availableupdates) or !is_array($this->availableupdates)) {
430             return null;
431         }
433         $updates = array();
435         foreach ($this->availableupdates as $availableupdate) {
436             if ($availableupdate->version > $this->versiondisk) {
437                 $updates[] = $availableupdate;
438             }
439         }
441         if (empty($updates)) {
442             return null;
443         }
445         return $updates;
446     }
448     /**
449      * Returns the node name used in admin settings menu for this plugin settings (if applicable)
450      *
451      * @return null|string node name or null if plugin does not create settings node (default)
452      */
453     public function get_settings_section_name() {
454         return null;
455     }
457     /**
458      * Returns the URL of the plugin settings screen
459      *
460      * Null value means that the plugin either does not have the settings screen
461      * or its location is not available via this library.
462      *
463      * @return null|moodle_url
464      */
465     public function get_settings_url() {
466         $section = $this->get_settings_section_name();
467         if ($section === null) {
468             return null;
469         }
470         $settings = admin_get_root()->locate($section);
471         if ($settings && $settings instanceof \admin_settingpage) {
472             return new moodle_url('/admin/settings.php', array('section' => $section));
473         } else if ($settings && $settings instanceof \admin_externalpage) {
474             return new moodle_url($settings->url);
475         } else {
476             return null;
477         }
478     }
480     /**
481      * Loads plugin settings to the settings tree
482      *
483      * This function usually includes settings.php file in plugins folder.
484      * Alternatively it can create a link to some settings page (instance of admin_externalpage)
485      *
486      * @param \part_of_admin_tree $adminroot
487      * @param string $parentnodename
488      * @param bool $hassiteconfig whether the current user has moodle/site:config capability
489      */
490     public function load_settings(\part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
491     }
493     /**
494      * Should there be a way to uninstall the plugin via the administration UI.
495      *
496      * By default uninstallation is not allowed, plugin developers must enable it explicitly!
497      *
498      * @return bool
499      */
500     public function is_uninstall_allowed() {
501         return false;
502     }
504     /**
505      * Optional extra warning before uninstallation, for example number of uses in courses.
506      *
507      * @return string
508      */
509     public function get_uninstall_extra_warning() {
510         return '';
511     }
513     /**
514      * Pre-uninstall hook.
515      *
516      * This is intended for disabling of plugin, some DB table purging, etc.
517      *
518      * NOTE: to be called from uninstall_plugin() only.
519      * @private
520      */
521     public function uninstall_cleanup() {
522         // Override when extending class,
523         // do not forget to call parent::pre_uninstall_cleanup() at the end.
524     }
526     /**
527      * Returns relative directory of the plugin with heading '/'
528      *
529      * @return string
530      */
531     public function get_dir() {
532         global $CFG;
534         return substr($this->rootdir, strlen($CFG->dirroot));
535     }
537     /**
538      * Hook method to implement certain steps when uninstalling the plugin.
539      *
540      * This hook is called by {@link core_plugin_manager::uninstall_plugin()} so
541      * it is basically usable only for those plugin types that use the default
542      * uninstall tool provided by {@link self::get_default_uninstall_url()}.
543      *
544      * @param \progress_trace $progress traces the process
545      * @return bool true on success, false on failure
546      */
547     public function uninstall(\progress_trace $progress) {
548         return true;
549     }
551     /**
552      * Where should we return after plugin of this type is uninstalled?
553      * @param string $return
554      * @return moodle_url
555      */
556     public function get_return_url_after_uninstall($return) {
557         if ($return === 'manage') {
558             if ($url = $this->get_manage_url()) {
559                 return $url;
560             }
561         }
562         return new moodle_url('/admin/plugins.php#plugin_type_cell_'.$this->type);
563     }
565     /**
566      * Return URL used for management of plugins of this type.
567      * @return moodle_url
568      */
569     public static function get_manage_url() {
570         return null;
571     }
573     /**
574      * Returns URL to a script that handles common plugin uninstall procedure.
575      *
576      * This URL is intended for all plugin uninstallations.
577      *
578      * @param string $return either 'overview' or 'manage'
579      * @return moodle_url
580      */
581     public final function get_default_uninstall_url($return = 'overview') {
582         return new moodle_url('/admin/plugins.php', array(
583             'sesskey' => sesskey(),
584             'uninstall' => $this->component,
585             'confirm' => 0,
586             'return' => $return,
587         ));
588     }
590     /**
591      * Provides access to the core_plugin_manager singleton.
592      *
593      * @return core_plugin_manager
594      */
595     protected function get_plugin_manager() {
596         return core_plugin_manager::instance();
597     }