MDL-27789 The new question plugins reported as standard ones
[moodle.git] / lib / pluginlib.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 plugins management
19  *
20  * This library provides a unified interface to various plugin types in
21  * Moodle. It is mainly used by the plugins management admin page and the
22  * plugins check page during the upgrade.
23  *
24  * @package    core
25  * @subpackage admin
26  * @copyright  2011 David Mudrak <david@moodle.com>
27  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28  */
30 defined('MOODLE_INTERNAL') || die();
32 /**
33  * Singleton class providing general plugins management functionality
34  */
35 class plugin_manager {
37     /** the plugin is shipped with standard Moodle distribution */
38     const PLUGIN_SOURCE_STANDARD    = 'std';
39     /** the plugin is added extension */
40     const PLUGIN_SOURCE_EXTENSION   = 'ext';
42     /** the plugin uses neither database nor capabilities, no versions */
43     const PLUGIN_STATUS_NODB        = 'nodb';
44     /** the plugin is up-to-date */
45     const PLUGIN_STATUS_UPTODATE    = 'uptodate';
46     /** the plugin is about to be installed */
47     const PLUGIN_STATUS_NEW         = 'new';
48     /** the plugin is about to be upgraded */
49     const PLUGIN_STATUS_UPGRADE     = 'upgrade';
50     /** the version at the disk is lower than the one already installed */
51     const PLUGIN_STATUS_DOWNGRADE   = 'downgrade';
52     /** the plugin is installed but missing from disk */
53     const PLUGIN_STATUS_MISSING     = 'missing';
55     /** @var plugin_manager holds the singleton instance */
56     protected static $singletoninstance;
57     /** @var array of raw plugins information */
58     protected $pluginsinfo = null;
59     /** @var array of raw subplugins information */
60     protected $subpluginsinfo = null;
62     /**
63      * Direct initiation not allowed, use the factory method {@link self::instance()}
64      *
65      * @todo we might want to specify just a single plugin type to work with
66      */
67     protected function __construct() {
68         $this->get_plugins(true);
69     }
71     /**
72      * Sorry, this is singleton
73      */
74     protected function __clone() {
75     }
77     /**
78      * Factory method for this class
79      *
80      * @return plugin_manager the singleton instance
81      */
82     public static function instance() {
83         global $CFG;
85         if (is_null(self::$singletoninstance)) {
86             self::$singletoninstance = new self();
87         }
88         return self::$singletoninstance;
89     }
91     /**
92      * Returns a tree of known plugins and information about them
93      *
94      * @param bool $disablecache force reload, cache can be used otherwise
95      * @return array
96      */
97     public function get_plugins($disablecache=false) {
99         if ($disablecache or is_null($this->pluginsinfo)) {
100             $this->pluginsinfo = array();
101             $plugintypes = get_plugin_types();
102             foreach ($plugintypes as $plugintype => $plugintyperootdir) {
103                 if (in_array($plugintype, array('base', 'general'))) {
104                     throw new coding_exception('Illegal usage of reserved word for plugin type');
105                 }
106                 if (class_exists('plugintype_' . $plugintype)) {
107                     $plugintypeclass = 'plugintype_' . $plugintype;
108                 } else {
109                     $plugintypeclass = 'plugintype_general';
110                 }
111                 if (!in_array('plugintype_interface', class_implements($plugintypeclass))) {
112                     throw new coding_exception('Class ' . $plugintypeclass . ' must implement plugintype_interface');
113                 }
114                 $plugins = call_user_func(array($plugintypeclass, 'get_plugins'), $plugintype, $plugintyperootdir, $plugintypeclass);
115                 $this->pluginsinfo[$plugintype] = $plugins;
116             }
117         }
119         return $this->pluginsinfo;
120     }
122     /**
123      * Returns list of plugins that define their subplugins and information about them
124      *
125      * At the moment, only activity modules can define subplugins.
126      *
127      * @param double $disablecache force reload, cache can be used otherwise
128      * @return array
129      */
130     public function get_subplugins($disablecache=false) {
132         if ($disablecache or is_null($this->subpluginsinfo)) {
133             $this->subpluginsinfo = array();
134             $mods = get_plugin_list('mod');
135             foreach ($mods as $mod => $moddir) {
136                 $modsubplugins = array();
137                 if (file_exists($moddir . '/db/subplugins.php')) {
138                     include($moddir . '/db/subplugins.php');
139                     foreach ($subplugins as $subplugintype => $subplugintyperootdir) {
140                         $subplugin = new stdClass();
141                         $subplugin->type = $subplugintype;
142                         $subplugin->typerootdir = $subplugintyperootdir;
143                         $modsubplugins[$subplugintype] = $subplugin;
144                     }
145                 $this->subpluginsinfo['mod_' . $mod] = $modsubplugins;
146                 }
147             }
148         }
150         return $this->subpluginsinfo;
151     }
153     /**
154      * Returns the name of the plugin that defines the given subplugin type
155      *
156      * If the given subplugin type is not actually a subplugin, returns false.
157      *
158      * @param string $subplugintype the name of subplugin type, eg. workshopform or quiz
159      * @return false|string the name of the parent plugin, eg. mod_workshop
160      */
161     public function get_parent_of_subplugin($subplugintype) {
163         $parent = false;
164         foreach ($this->get_subplugins() as $pluginname => $subplugintypes) {
165             if (isset($subplugintypes[$subplugintype])) {
166                 $parent = $pluginname;
167                 break;
168             }
169         }
171         return $parent;
172     }
174     /**
175      * Returns a localized name of a given plugin
176      *
177      * @param string $plugin name of the plugin, eg mod_workshop or auth_ldap
178      * @return string
179      */
180     public function plugin_name($plugin) {
181         list($type, $name) = normalize_component($plugin);
182         return $this->pluginsinfo[$type][$name]->displayname;
183     }
185     /**
186      * Returns a localized name of a plugin type in plural form
187      *
188      * Most plugin types define their names in core_plugin lang file. In case of subplugins,
189      * we try to ask the parent plugin for the name. In the worst case, we will return
190      * the value of the passed $type parameter.
191      *
192      * @param string $type the type of the plugin, e.g. mod or workshopform
193      * @return string
194      */
195     public function plugintype_name_plural($type) {
197         if (get_string_manager()->string_exists('type_' . $type . '_plural', 'core_plugin')) {
198             // for most plugin types, their names are defined in core_plugin lang file
199             return get_string('type_' . $type . '_plural', 'core_plugin');
201         } else if ($parent = $this->get_parent_of_subplugin($type)) {
202             // if this is a subplugin, try to ask the parent plugin for the name
203             if (get_string_manager()->string_exists('subplugintype_' . $type . '_plural', $parent)) {
204                 return $this->plugin_name($parent) . ' / ' . get_string('subplugintype_' . $type . '_plural', $parent);
205             } else {
206                 return $this->plugin_name($parent) . ' / ' . $type;
207             }
209         } else {
210             return $type;
211         }
212     }
214     /**
215      * Defines a white list of all plugins shipped in the standard Moodle distribution
216      *
217      * @return false|array array of standard plugins or false if the type is unknown
218      */
219     public static function standard_plugins_list($type) {
220         static $standard_plugins = array(
222             'assignment' => array(
223                 'offline', 'online', 'upload', 'uploadsingle'
224             ),
226             'auth' => array(
227                 'cas', 'db', 'email', 'fc', 'imap', 'ldap', 'manual', 'mnet',
228                 'nntp', 'nologin', 'none', 'pam', 'pop3', 'radius',
229                 'shibboleth', 'webservice'
230             ),
232             'block' => array(
233                 'activity_modules', 'admin_bookmarks', 'blog_menu',
234                 'blog_recent', 'blog_tags', 'calendar_month',
235                 'calendar_upcoming', 'comments', 'community',
236                 'completionstatus', 'course_list', 'course_overview',
237                 'course_summary', 'feedback', 'glossary_random', 'html',
238                 'login', 'mentees', 'messages', 'mnet_hosts', 'myprofile',
239                 'navigation', 'news_items', 'online_users', 'participants',
240                 'private_files', 'quiz_results', 'recent_activity',
241                 'rss_client', 'search', 'search_forums', 'section_links',
242                 'selfcompletion', 'settings', 'site_main_menu',
243                 'social_activities', 'tag_flickr', 'tag_youtube', 'tags'
244             ),
246             'coursereport' => array(
247                 'completion', 'log', 'outline', 'participation', 'progress', 'stats'
248             ),
250             'datafield' => array(
251                 'checkbox', 'date', 'file', 'latlong', 'menu', 'multimenu',
252                 'number', 'picture', 'radiobutton', 'text', 'textarea', 'url'
253             ),
255             'datapreset' => array(
256                 'imagegallery'
257             ),
259             'editor' => array(
260                 'textarea', 'tinymce'
261             ),
263             'enrol' => array(
264                 'authorize', 'category', 'cohort', 'database', 'flatfile',
265                 'guest', 'imsenterprise', 'ldap', 'manual', 'meta', 'mnet',
266                 'paypal', 'self'
267             ),
269             'filter' => array(
270                 'activitynames', 'algebra', 'censor', 'emailprotect',
271                 'emoticon', 'mediaplugin', 'multilang', 'tex', 'tidy',
272                 'urltolink', 'mod_data', 'mod_glossary'
273             ),
275             'format' => array(
276                 'scorm', 'social', 'topics', 'weeks'
277             ),
279             'gradeexport' => array(
280                 'ods', 'txt', 'xls', 'xml'
281             ),
283             'gradeimport' => array(
284                 'csv', 'xml'
285             ),
287             'gradereport' => array(
288                 'grader', 'outcomes', 'overview', 'user'
289             ),
291             'local' => array(
292                 'qeupgradehelper'
293             ),
295             'message' => array(
296                 'email', 'jabber', 'popup'
297             ),
299             'mnetservice' => array(
300                 'enrol'
301             ),
303             'mod' => array(
304                 'assignment', 'chat', 'choice', 'data', 'feedback', 'folder',
305                 'forum', 'glossary', 'imscp', 'label', 'lesson', 'page',
306                 'quiz', 'resource', 'scorm', 'survey', 'url', 'wiki', 'workshop'
307             ),
309             'plagiarism' => array(
310             ),
312             'portfolio' => array(
313                 'boxnet', 'download', 'flickr', 'googledocs', 'mahara', 'picasa'
314             ),
316             'profilefield' => array(
317                 'checkbox', 'datetime', 'menu', 'text', 'textarea'
318             ),
320             'qbehaviour' => array(
321                 'adaptive', 'adaptivenopenalty', 'deferredcbm',
322                 'deferredfeedback', 'immediatecbm', 'immediatefeedback',
323                 'informationitem', 'interactive', 'interactivecountback',
324                 'manualgraded', 'missing'
325             ),
327             'qformat' => array(
328                 'aiken', 'blackboard', 'blackboard_six', 'examview', 'gift',
329                 'learnwise', 'missingword', 'multianswer', 'qti_two', 'webct',
330                 'xhtml', 'xml'
331             ),
333             'qtype' => array(
334                 'calculated', 'calculatedmulti', 'calculatedsimple',
335                 'description', 'essay', 'match', 'missingtype', 'multianswer',
336                 'multichoice', 'numerical', 'random', 'randomsamatch',
337                 'shortanswer', 'truefalse'
338             ),
340             'quiz' => array(
341                 'grading', 'overview', 'responses', 'statistics'
342             ),
344             'report' => array(
345                 'backups', 'capability', 'configlog', 'courseoverview',
346                 'customlang', 'log', 'profiling', 'questioninstances',
347                 'security', 'spamcleaner', 'stats', 'unittest', 'unsuproles'
348             ),
350             'repository' => array(
351                 'alfresco', 'boxnet', 'coursefiles', 'dropbox', 'filesystem',
352                 'flickr', 'flickr_public', 'googledocs', 'local', 'merlot',
353                 'picasa', 'recent', 's3', 'upload', 'url', 'user', 'webdav',
354                 'wikimedia', 'youtube'
355             ),
357             'theme' => array(
358                 'afterburner', 'anomaly', 'arialist', 'base', 'binarius',
359                 'boxxie', 'brick', 'canvas', 'formal_white', 'formfactor',
360                 'fusion', 'leatherbound', 'magazine', 'nimble', 'nonzero',
361                 'overlay', 'serenity', 'sky_high', 'splash', 'standard',
362                 'standardold'
363             ),
365             'webservice' => array(
366                 'amf', 'rest', 'soap', 'xmlrpc'
367             ),
369             'workshopallocation' => array(
370                 'manual', 'random'
371             ),
373             'workshopeval' => array(
374                 'best'
375             ),
377             'workshopform' => array(
378                 'accumulative', 'comments', 'numerrors', 'rubric'
379             )
380         );
382         if (isset($standard_plugins[$type])) {
383             return $standard_plugins[$type];
385         } else {
386             return false;
387         }
388     }
391 /**
392  * All classes that represent a plugin of some type must implement this interface
393  */
394 interface plugintype_interface {
396     /**
397      * Gathers and returns the information about all plugins of the given type
398      *
399      * Passing the parameter $typeclass allows us to reach the same effect as with the
400      * late binding in PHP 5.3. Once PHP 5.3 is required, we can refactor this to use
401      * {@example $plugin = new static();} instead of {@example $plugin = new $typeclass()}
402      *
403      * @param string $type the name of the plugintype, eg. mod, auth or workshopform
404      * @param string $typerootdir full path to the location of the plugin dir
405      * @param string $typeclass the name of the actually called class
406      * @return array of plugintype classes, indexed by the plugin name
407      */
408     public static function get_plugins($type, $typerootdir, $typeclass);
410     /**
411      * Sets $displayname property to a localized name of the plugin
412      *
413      * @return void
414      */
415     public function set_display_name();
417     /**
418      * Sets $versiondisk property to a numerical value representing the
419      * version of the plugin's source code.
420      *
421      * If the value is null after calling this method, either the plugin
422      * does not use versioning (typically does not have any database
423      * data) or is missing from disk.
424      *
425      * @return void
426      */
427     public function set_version_disk();
429     /**
430      * Sets $versiondb property to a numerical value representing the
431      * currently installed version of the plugin.
432      *
433      * If the value is null after calling this method, either the plugin
434      * does not use versioning (typically does not have any database
435      * data) or has not been installed yet.
436      *
437      * @return void
438      */
439     public function set_version_db();
441     /**
442      * Sets $versionrequires property to a numerical value representing
443      * the version of Moodle core that this plugin requires.
444      *
445      * @return void
446      */
447     public function set_version_requires();
449     /**
450      * Sets $source property to one of plugin_manager::PLUGIN_SOURCE_xxx
451      * constants.
452      *
453      * If the property's value is null after calling this method, then
454      * the type of the plugin has not been recognized and you should throw
455      * an exception.
456      *
457      * @return void
458      */
459     public function set_source();
461     /**
462      * Returns true if the plugin is shipped with the official distribution
463      * of the current Moodle version, false otherwise.
464      *
465      * @return bool
466      */
467     public function is_standard();
469     /**
470      * Returns the status of the plugin
471      *
472      * @return string one of plugin_manager::PLUGIN_STATUS_xxx constants
473      */
474     public function get_status();
476     /**
477      * Returns the information about plugin availability
478      *
479      * True means that the plugin is enabled. False means that the plugin is
480      * disabled. Null means that the information is not available, or the
481      * plugin does not support configurable availability or the availability
482      * can not be changed.
483      *
484      * @return null|bool
485      */
486     public function is_enabled();
488     /**
489      * Returns the URL of the plugin settings screen
490      *
491      * Null value means that the plugin either does not have the settings screen
492      * or its location is not available via this library.
493      *
494      * @return null|moodle_url
495      */
496     public function get_settings_url();
498     /**
499      * Returns the URL of the screen where this plugin can be uninstalled
500      *
501      * Visiting that URL must be safe, that is a manual confirmation is needed
502      * for actual uninstallation of the plugin. Null value means that the
503      * plugin either does not support uninstallation, or does not require any
504      * database cleanup or the location of the screen is not available via this
505      * library.
506      *
507      * @return null|moodle_url
508      */
509     public function get_uninstall_url();
511     /**
512      * Returns relative directory of the plugin with heading '/'
513      *
514      * @example /mod/workshop
515      * @return string
516      */
517     public function get_dir();
520 /**
521  * Defines public properties that all plugintype classes must have
522  * and provides default implementation of required methods.
523  */
524 abstract class plugintype_base {
526     /** @var string the plugintype name, eg. mod, auth or workshopform */
527     public $type;
528     /** @var string full path to the location of all the plugins of this type */
529     public $typerootdir;
530     /** @var string the plugin name, eg. assignment, ldap */
531     public $name;
532     /** @var string the localized plugin name */
533     public $displayname;
534     /** @var string the plugin source, one of plugin_manager::PLUGIN_SOURCE_xxx constants */
535     public $source;
536     /** @var fullpath to the location of this plugin */
537     public $rootdir;
538     /** @var int|string the version of the plugin's source code */
539     public $versiondisk;
540     /** @var int|string the version of the installed plugin */
541     public $versiondb;
542     /** @var int|float|string required version of Moodle core  */
543     public $versionrequires;
544     /** @var int number of instances of the plugin - not supported yet */
545     public $instances;
546     /** @var int order of the plugin among other plugins of the same type - not supported yet */
547     public $sortorder;
549     /**
550      * @see plugintype_interface::get_plugins()
551      */
552     public static function get_plugins($type, $typerootdir, $typeclass) {
554         // get the information about plugins at the disk
555         $plugins = get_plugin_list($type);
556         $ondisk = array();
557         foreach ($plugins as $pluginname => $pluginrootdir) {
558             $plugin                 = new $typeclass();
559             $plugin->type           = $type;
560             $plugin->typerootdir    = $typerootdir;
561             $plugin->name           = $pluginname;
562             $plugin->rootdir        = $pluginrootdir;
564             $plugin->set_display_name();
565             $plugin->set_version_disk();
566             $plugin->set_version_db();
567             $plugin->set_version_requires();
568             $plugin->set_source();
570             $ondisk[$pluginname] = $plugin;
571         }
572         return $ondisk;
573     }
575     /**
576      * @see plugintype_interface::set_display_name()
577      */
578     public function set_display_name() {
579         if (! get_string_manager()->string_exists('pluginname', $this->type . '_' . $this->name)) {
580             $this->displayname = '[pluginname,' . $this->type . '_' . $this->name . ']';
581         } else {
582             $this->displayname = get_string('pluginname', $this->type . '_' . $this->name);
583         }
584     }
586     /**
587      * @see plugintype_interface::set_version_disk()
588      */
589     public function set_version_disk() {
591         if (empty($this->rootdir)) {
592             return;
593         }
595         $versionfile = $this->rootdir . '/version.php';
597         if (is_readable($versionfile)) {
598             include($versionfile);
599             if (isset($plugin->version)) {
600                 $this->versiondisk = $plugin->version;
601             }
602         }
603     }
605     /**
606      * @see plugintype_interface::set_version_db()
607      */
608     public function set_version_db() {
610         if ($ver = self::get_version_from_config_plugins($this->type . '_' . $this->name)) {
611             $this->versiondb = $ver;
612         }
613     }
615     /**
616      * @see plugintype_interface::set_version_requires()
617      */
618     public function set_version_requires() {
620         if (empty($this->rootdir)) {
621             return;
622         }
624         $versionfile = $this->rootdir . '/version.php';
626         if (is_readable($versionfile)) {
627             include($versionfile);
628             if (isset($plugin->requires)) {
629                 $this->versionrequires = $plugin->requires;
630             }
631         }
632     }
634     /**
635      * @see plugintype_interface::set_source()
636      */
637     public function set_source() {
639         $standard = plugin_manager::standard_plugins_list($this->type);
641         if ($standard !== false) {
642             $standard = array_flip($standard);
643             if (isset($standard[$this->name])) {
644                 $this->source = plugin_manager::PLUGIN_SOURCE_STANDARD;
645             } else {
646                 $this->source = plugin_manager::PLUGIN_SOURCE_EXTENSION;
647             }
648         }
649     }
651     /**
652      * @see plugintype_interface::is_standard()
653      */
654     public function is_standard() {
655         return $this->source === plugin_manager::PLUGIN_SOURCE_STANDARD;
656     }
658     /**
659      * @see plugintype_interface::get_status()
660      */
661     public function get_status() {
663         if (is_null($this->versiondb) and is_null($this->versiondisk)) {
664             return plugin_manager::PLUGIN_STATUS_NODB;
666         } else if (is_null($this->versiondb) and !is_null($this->versiondisk)) {
667             return plugin_manager::PLUGIN_STATUS_NEW;
669         } else if (!is_null($this->versiondb) and is_null($this->versiondisk)) {
670             return plugin_manager::PLUGIN_STATUS_MISSING;
672         } else if ((string)$this->versiondb === (string)$this->versiondisk) {
673             return plugin_manager::PLUGIN_STATUS_UPTODATE;
675         } else if ($this->versiondb < $this->versiondisk) {
676             return plugin_manager::PLUGIN_STATUS_UPGRADE;
678         } else if ($this->versiondb > $this->versiondisk) {
679             return plugin_manager::PLUGIN_STATUS_DOWNGRADE;
681         } else {
682             // $version = pi(); and similar funny jokes - hopefully Donald E. Knuth will never contribute to Moodle ;-)
683             throw new coding_exception('Unable to determine plugin state, check the plugin versions');
684         }
685     }
687     /**
688      * @see plugintype_interface::is_enabled()
689      */
690     public function is_enabled() {
691         return null;
692     }
694     /**
695      * @see plugintype_interface::get_settings_url()
696      */
697     public function get_settings_url() {
698         return null;
699     }
701     /**
702      * @see plugintype_interface::get_uninstall_url()
703      */
704     public function get_uninstall_url() {
705         return null;
706     }
708     /**
709      * @see plugintype_interface::get_dir()
710      */
711     public function get_dir() {
712         global $CFG;
714         return substr($this->rootdir, strlen($CFG->dirroot));
715     }
717     /**
718      * Provides access to plugin versions from {config_plugins}
719      *
720      * @param string $plugin plugin name
721      * @param double $disablecache optional, defaults to false
722      * @return int|false the stored value or false if not found
723      */
724     protected function get_version_from_config_plugins($plugin, $disablecache=false) {
725         global $DB;
726         static $pluginversions = null;
728         if (is_null($pluginversions) or $disablecache) {
729             $pluginversions = $DB->get_records_menu('config_plugins', array('name' => 'version'), 'plugin', 'plugin,value');
730         }
732         if (!array_key_exists($plugin, $pluginversions)) {
733             return false;
734         }
736         return $pluginversions[$plugin];
737     }
740 /**
741  * General class for all plugin types that do not have their own class
742  */
743 class plugintype_general extends plugintype_base implements plugintype_interface {
747 /**
748  * Class for page side blocks
749  */
750 class plugintype_block extends plugintype_base implements plugintype_interface {
752     /**
753      * @see plugintype_interface::get_plugins()
754      */
755     public static function get_plugins($type, $typerootdir, $typeclass) {
757         // get the information about blocks at the disk
758         $blocks = parent::get_plugins($type, $typerootdir, $typeclass);
760         // add blocks missing from disk
761         $blocksinfo = self::get_blocks_info();
762         foreach ($blocksinfo as $blockname => $blockinfo) {
763             if (isset($blocks[$blockname])) {
764                 continue;
765             }
766             $plugin                 = new $typeclass();
767             $plugin->type           = $type;
768             $plugin->typerootdir    = $typerootdir;
769             $plugin->name           = $blockname;
770             $plugin->rootdir        = null;
771             $plugin->displayname    = $blockname;
772             $plugin->versiondb      = $blockinfo->version;
773             $plugin->set_source();
775             $blocks[$blockname]   = $plugin;
776         }
778         return $blocks;
779     }
781     /**
782      * @see plugintype_interface::set_display_name()
783      */
784     public function set_display_name() {
786         if (get_string_manager()->string_exists('pluginname', 'block_' . $this->name)) {
787             $this->displayname = get_string('pluginname', 'block_' . $this->name);
789         } else if (($block = block_instance($this->name)) !== false) {
790             $this->displayname = $block->get_title();
792         } else {
793             parent::set_display_name();
794         }
795     }
797     /**
798      * @see plugintype_interface::set_version_db()
799      */
800     public function set_version_db() {
801         global $DB;
803         $blocksinfo = self::get_blocks_info();
804         if (isset($blocksinfo[$this->name]->version)) {
805             $this->versiondb = $blocksinfo[$this->name]->version;
806         }
807     }
809     /**
810      * @see plugintype_interface::is_enabled()
811      */
812     public function is_enabled() {
814         $blocksinfo = self::get_blocks_info();
815         if (isset($blocksinfo[$this->name]->visible)) {
816             if ($blocksinfo[$this->name]->visible) {
817                 return true;
818             } else {
819                 return false;
820             }
821         } else {
822             return parent::is_enabled();
823         }
824     }
826     /**
827      * @see plugintype_interface::get_settings_url()
828      */
829     public function get_settings_url() {
831         if (($block = block_instance($this->name)) === false) {
832             return parent::get_settings_url();
834         } else if ($block->has_config()) {
835             if (!empty($this->rootdir) and file_exists($this->rootdir . '/settings.php')) {
836                 return new moodle_url('/admin/settings.php', array('section' => 'blocksetting' . $this->name));
837             } else {
838                 $blocksinfo = self::get_blocks_info();
839                 return new moodle_url('/admin/block.php', array('block' => $blocksinfo[$this->name]->id));
840             }
842         } else {
843             return parent::get_settings_url();
844         }
845     }
847     /**
848      * @see plugintype_interface::get_uninstall_url()
849      */
850     public function get_uninstall_url() {
852         $blocksinfo = self::get_blocks_info();
853         return new moodle_url('/admin/blocks.php', array('delete' => $blocksinfo[$this->name]->id, 'sesskey' => sesskey()));
854     }
856     /**
857      * Provides access to the records in {block} table
858      *
859      * @param bool $disablecache do not use internal static cache
860      * @return array array of stdClasses
861      */
862     protected static function get_blocks_info($disablecache=false) {
863         global $DB;
864         static $blocksinfocache = null;
866         if (is_null($blocksinfocache) or $disablecache) {
867             $blocksinfocache = $DB->get_records('block', null, 'name', 'name,id,version,visible');
868         }
870         return $blocksinfocache;
871     }
874 /**
875  * Class for text filters
876  */
877 class plugintype_filter extends plugintype_base implements plugintype_interface {
879     /**
880      * @see plugintype_interface::get_plugins()
881      */
882     public static function get_plugins($type, $typerootdir, $typeclass) {
883         global $CFG, $DB;
885         $filters = array();
887         // get the list of filters from both /filter and /mod location
888         $installed = filter_get_all_installed();
890         foreach ($installed as $filterlegacyname => $displayname) {
891             $plugin                 = new $typeclass();
892             $plugin->type           = $type;
893             $plugin->typerootdir    = $typerootdir;
894             $plugin->name           = self::normalize_legacy_name($filterlegacyname);
895             $plugin->rootdir        = $CFG->dirroot . '/' . $filterlegacyname;
896             $plugin->displayname    = $displayname;
898             $plugin->set_version_disk();
899             $plugin->set_version_db();
900             $plugin->set_version_requires();
901             $plugin->set_source();
903             $filters[$plugin->name] = $plugin;
904         }
906         $globalstates = self::get_global_states();
908         if ($DB->get_manager()->table_exists('filter_active')) {
909             // if we're upgrading from 1.9, the table does not exist yet
910             // if it does, make sure that all installed filters are registered
911             $needsreload  = false;
912             foreach (array_keys($installed) as $filterlegacyname) {
913                 if (!isset($globalstates[self::normalize_legacy_name($filterlegacyname)])) {
914                     filter_set_global_state($filterlegacyname, TEXTFILTER_DISABLED);
915                     $needsreload = true;
916                 }
917             }
918             if ($needsreload) {
919                 $globalstates = self::get_global_states(true);
920             }
921         }
923         // make sure that all registered filters are installed, just in case
924         foreach ($globalstates as $name => $info) {
925             if (!isset($filters[$name])) {
926                 // oops, there is a record in filter_active but the filter is not installed
927                 $plugin                 = new $typeclass();
928                 $plugin->type           = $type;
929                 $plugin->typerootdir    = $typerootdir;
930                 $plugin->name           = $name;
931                 $plugin->rootdir        = $CFG->dirroot . '/' . $info->legacyname;
932                 $plugin->displayname    = $info->legacyname;
934                 $plugin->set_version_db();
936                 if (is_null($plugin->versiondb)) {
937                     // this is a hack to stimulate 'Missing from disk' error
938                     // because $plugin->versiondisk will be null !== false
939                     $plugin->versiondb = false;
940                 }
942                 $filters[$plugin->name] = $plugin;
943             }
944         }
946         return $filters;
947     }
949     /**
950      * @see plugintype_interface::set_display_name()
951      */
952     public function set_display_name() {
953         // do nothing, the name is set in self::get_plugins()
954     }
956     /**
957      * @see plugintype_interface::set_version_disk()
958      */
959     public function set_version_disk() {
961         if (strpos($this->name, 'mod_') === 0) {
962             // filters bundled with modules do not use versioning
963             return;
964         }
966         return parent::set_version_disk();
967     }
969     /**
970      * @see plugintype_interface::set_version_requires()
971      */
972     public function set_version_requires() {
974         if (strpos($this->name, 'mod_') === 0) {
975             // filters bundled with modules do not use versioning
976             return;
977         }
979         return parent::set_version_requires();
980     }
982     /**
983      * @see plugintype_interface::is_enabled()
984      */
985     public function is_enabled() {
987         $globalstates = self::get_global_states();
989         foreach ($globalstates as $filterlegacyname => $info) {
990             $name = self::normalize_legacy_name($filterlegacyname);
991             if ($name === $this->name) {
992                 if ($info->active == TEXTFILTER_DISABLED) {
993                     return false;
994                 } else {
995                     // it may be 'On' or 'Off, but available'
996                     return null;
997                 }
998             }
999         }
1001         return null;
1002     }
1004     /**
1005      * @see plugintype_interface::get_settings_url()
1006      */
1007     public function get_settings_url() {
1009         $globalstates = self::get_global_states();
1010         $legacyname = $globalstates[$this->name]->legacyname;
1011         if (filter_has_global_settings($legacyname)) {
1012             return new moodle_url('/admin/settings.php', array('section' => 'filtersetting' . str_replace('/', '', $legacyname)));
1013         } else {
1014             return null;
1015         }
1016     }
1018     /**
1019      * @see plugintype_interface::get_uninstall_url()
1020      */
1021     public function get_uninstall_url() {
1023         if (strpos($this->name, 'mod_') === 0) {
1024             return null;
1025         } else {
1026             $globalstates = self::get_global_states();
1027             $legacyname = $globalstates[$this->name]->legacyname;
1028             return new moodle_url('/admin/filters.php', array('sesskey' => sesskey(), 'filterpath' => $legacyname, 'action' => 'delete'));
1029         }
1030     }
1032     /**
1033      * Convert legacy filter names like 'filter/foo' or 'mod/bar' into frankenstyle
1034      *
1035      * @param string $legacyfiltername legacy filter name
1036      * @return string frankenstyle-like name
1037      */
1038     protected static function normalize_legacy_name($legacyfiltername) {
1040         $name = str_replace('/', '_', $legacyfiltername);
1041         if (strpos($name, 'filter_') === 0) {
1042             $name = substr($name, 7);
1043             if (empty($name)) {
1044                 throw new coding_exception('Unable to determine filter name: ' . $legacyfiltername);
1045             }
1046         }
1048         return $name;
1049     }
1051     /**
1052      * Provides access to the results of {@link filter_get_global_states()}
1053      * but indexed by the normalized filter name
1054      *
1055      * The legacy filter name is available as ->legacyname property.
1056      *
1057      * @param bool $disablecache
1058      * @return array
1059      */
1060     protected static function get_global_states($disablecache=false) {
1061         global $DB;
1062         static $globalstatescache = null;
1064         if ($disablecache or is_null($globalstatescache)) {
1066             if (!$DB->get_manager()->table_exists('filter_active')) {
1067                 // we're upgrading from 1.9 and the table used by {@link filter_get_global_states()}
1068                 // does not exist yet
1069                 $globalstatescache = array();
1071             } else {
1072                 foreach (filter_get_global_states() as $legacyname => $info) {
1073                     $name                       = self::normalize_legacy_name($legacyname);
1074                     $filterinfo                 = new stdClass();
1075                     $filterinfo->legacyname     = $legacyname;
1076                     $filterinfo->active         = $info->active;
1077                     $filterinfo->sortorder      = $info->sortorder;
1078                     $globalstatescache[$name]   = $filterinfo;
1079                 }
1080             }
1081         }
1083         return $globalstatescache;
1084     }
1087 /**
1088  * Class for activity modules
1089  */
1090 class plugintype_mod extends plugintype_base implements plugintype_interface {
1092     /**
1093      * @see plugintype_interface::get_plugins()
1094      */
1095     public static function get_plugins($type, $typerootdir, $typeclass) {
1097         // get the information about plugins at the disk
1098         $modules = parent::get_plugins($type, $typerootdir, $typeclass);
1100         // add modules missing from disk
1101         $modulesinfo = self::get_modules_info();
1102         foreach ($modulesinfo as $modulename => $moduleinfo) {
1103             if (isset($modules[$modulename])) {
1104                 continue;
1105             }
1106             $plugin                 = new $typeclass();
1107             $plugin->type           = $type;
1108             $plugin->typerootdir    = $typerootdir;
1109             $plugin->name           = $modulename;
1110             $plugin->rootdir        = null;
1111             $plugin->displayname    = $modulename;
1112             $plugin->versiondb      = $moduleinfo->version;
1113             $plugin->set_source();
1115             $modules[$modulename]   = $plugin;
1116         }
1118         return $modules;
1119     }
1121     /**
1122      * @see plugintype_interface::set_display_name()
1123      */
1124     public function set_display_name() {
1125         if (get_string_manager()->string_exists('pluginname', $this->type . '_' . $this->name)) {
1126             $this->displayname = get_string('pluginname', $this->type . '_' . $this->name);
1127         } else {
1128             $this->displayname = get_string('modulename', $this->type . '_' . $this->name);
1129         }
1130     }
1132     /**
1133      * @see plugintype_interface::set_version_disk()
1134      */
1135     public function set_version_disk() {
1137         if (empty($this->rootdir)) {
1138             return;
1139         }
1141         $versionfile = $this->rootdir . '/version.php';
1143         if (is_readable($versionfile)) {
1144             include($versionfile);
1145             if (isset($module->version)) {
1146                 $this->versiondisk = $module->version;
1147             }
1148         }
1149     }
1151     /**
1152      * @see plugintype_interface::set_version_db()
1153      */
1154     public function set_version_db() {
1155         global $DB;
1157         $modulesinfo = self::get_modules_info();
1158         if (isset($modulesinfo[$this->name]->version)) {
1159             $this->versiondb = $modulesinfo[$this->name]->version;
1160         }
1161     }
1163     /**
1164      * @see plugintype_interface::set_version_requires()
1165      */
1166     public function set_version_requires() {
1168         if (empty($this->rootdir)) {
1169             return;
1170         }
1172         $versionfile = $this->rootdir . '/version.php';
1174         if (is_readable($versionfile)) {
1175             include($versionfile);
1176             if (isset($module->requires)) {
1177                 $this->versionrequires = $module->requires;
1178             }
1179         }
1180     }
1182     /**
1183      * @see plugintype_interface::is_enabled()
1184      */
1185     public function is_enabled() {
1187         $modulesinfo = self::get_modules_info();
1188         if (isset($modulesinfo[$this->name]->visible)) {
1189             if ($modulesinfo[$this->name]->visible) {
1190                 return true;
1191             } else {
1192                 return false;
1193             }
1194         } else {
1195             return parent::is_enabled();
1196         }
1197     }
1199     /**
1200      * @see plugintype_interface::get_settings_url()
1201      */
1202     public function get_settings_url() {
1204         if (!empty($this->rootdir) and (file_exists($this->rootdir . '/settings.php') or file_exists($this->rootdir . '/settingstree.php'))) {
1205             return new moodle_url('/admin/settings.php', array('section' => 'modsetting' . $this->name));
1206         } else {
1207             return parent::get_settings_url();
1208         }
1209     }
1211     /**
1212      * @see plugintype_interface::get_uninstall_url()
1213      */
1214     public function get_uninstall_url() {
1216         if ($this->name !== 'forum') {
1217             return new moodle_url('/admin/modules.php', array('delete' => $this->name, 'sesskey' => sesskey()));
1218         } else {
1219             return null;
1220         }
1221     }
1223     /**
1224      * Provides access to the records in {modules} table
1225      *
1226      * @param bool $disablecache do not use internal static cache
1227      * @return array array of stdClasses
1228      */
1229     protected static function get_modules_info($disablecache=false) {
1230         global $DB;
1231         static $modulesinfocache = null;
1233         if (is_null($modulesinfocache) or $disablecache) {
1234             $modulesinfocache = $DB->get_records('modules', null, 'name', 'name,id,version,visible');
1235         }
1237         return $modulesinfocache;
1238     }
1241 /**
1242  * Class for question types
1243  */
1244 class plugintype_qtype extends plugintype_base implements plugintype_interface {
1246     /**
1247      * @see plugintype_interface::set_display_name()
1248      */
1249     public function set_display_name() {
1250         $this->displayname = get_string($this->name, 'qtype_' . $this->name);
1251     }
1254 /**
1255  * Class for question formats
1256  */
1257 class plugintype_qformat extends plugintype_base implements plugintype_interface {
1259     /**
1260      * @see plugintype_interface::set_display_name()
1261      */
1262     public function set_display_name() {
1263         $this->displayname = get_string($this->name, 'qformat_' . $this->name);
1264     }
1267 /**
1268  * Class for authentication plugins
1269  */
1270 class plugintype_auth extends plugintype_base implements plugintype_interface {
1272     /**
1273      * @see plugintype_interface::is_enabled()
1274      */
1275     public function is_enabled() {
1276         global $CFG;
1277         /** @var null|array list of enabled authentication plugins */
1278         static $enabled = null;
1280         if (in_array($this->name, array('nologin', 'manual'))) {
1281             // these two are always enabled and can't be disabled
1282             return null;
1283         }
1285         if (is_null($enabled)) {
1286             $enabled = explode(',', $CFG->auth);
1287         }
1289         return isset($enabled[$this->name]);
1290     }
1292     /**
1293      * @see plugintype_interface::get_settings_url()
1294      */
1295     public function get_settings_url() {
1297         if (!empty($this->rootdir) and file_exists($this->rootdir . '/settings.php')) {
1298             return new moodle_url('/admin/settings.php', array('section' => 'authsetting' . $this->name));
1299         } else {
1300             return new moodle_url('/admin/auth_config.php', array('auth' => $this->name));
1301         }
1302     }
1305 /**
1306  * Class for enrolment plugins
1307  */
1308 class plugintype_enrol extends plugintype_base implements plugintype_interface {
1310     /**
1311      * We do not actually need whole enrolment classes here so we do not call
1312      * {@link enrol_get_plugins()}. Note that this may produce slightly different
1313      * results, for example if the enrolment plugin does not contain lib.php
1314      * but it is listed in $CFG->enrol_plugins_enabled
1315      *
1316      * @see plugintype_interface::is_enabled()
1317      */
1318     public function is_enabled() {
1319         global $CFG;
1320         /** @var null|array list of enabled enrolment plugins */
1321         static $enabled = null;
1323         if (is_null($enabled)) {
1324             $enabled = explode(',', $CFG->enrol_plugins_enabled);
1325         }
1327         return isset($enabled[$this->name]);
1328     }
1330     /**
1331      * @see plugintype_interface::get_settings_url()
1332      */
1333     public function get_settings_url() {
1335         if ($this->is_enabled() or (!empty($this->rootdir) and file_exists($this->rootdir . '/settings.php'))) {
1336             return new moodle_url('/admin/settings.php', array('section' => 'enrolsettings' . $this->name));
1337         } else {
1338             return parent::get_settings_url();
1339         }
1340     }
1342     /**
1343      * @see plugintype_interface::get_uninstall_url()
1344      */
1345     public function get_uninstall_url() {
1346         return new moodle_url('/admin/enrol.php', array('action' => 'uninstall', 'enrol' => $this->name, 'sesskey' => sesskey()));
1347     }
1350 /**
1351  * Class for messaging processors
1352  */
1353 class plugintype_message extends plugintype_base implements plugintype_interface {
1355     /**
1356      * @see plugintype_interface::get_settings_url()
1357      */
1358     public function get_settings_url() {
1360         if ($this->name === 'jabber') {
1361             return new moodle_url('/admin/settings.php', array('section' => 'jabber'));
1362         }
1364         if ($this->name === 'email') {
1365             return new moodle_url('/admin/settings.php', array('section' => 'mail'));
1366         }
1368     }
1371 /**
1372  * Class for repositories
1373  */
1374 class plugintype_repository extends plugintype_base implements plugintype_interface {
1376     /**
1377      * @see plugintype_interface::is_enabled()
1378      */
1379     public function is_enabled() {
1381         $enabled = self::get_enabled_repositories();
1383         return isset($enabled[$this->name]);
1384     }
1386     /**
1387      * @see plugintype_interface::get_settings_url()
1388      */
1389     public function get_settings_url() {
1391         if ($this->is_enabled()) {
1392             return new moodle_url('/admin/repository.php', array('sesskey' => sesskey(), 'action' => 'edit', 'repos' => $this->name));
1393         } else {
1394             return parent::get_settings_url();
1395         }
1396     }
1398     /**
1399      * Provides access to the records in {repository} table
1400      *
1401      * @param bool $disablecache do not use internal static cache
1402      * @return array array of stdClasses
1403      */
1404     protected static function get_enabled_repositories($disablecache=false) {
1405         global $DB;
1406         static $repositories = null;
1408         if (is_null($repositories) or $disablecache) {
1409             $repositories = $DB->get_records('repository', null, 'type', 'type,visible,sortorder');
1410         }
1412         return $repositories;
1413     }
1416 /**
1417  * Class for portfolios
1418  */
1419 class plugintype_portfolio extends plugintype_base implements plugintype_interface {
1421     /**
1422      * @see plugintype_interface::is_enabled()
1423      */
1424     public function is_enabled() {
1426         $enabled = self::get_enabled_portfolios();
1428         return isset($enabled[$this->name]);
1429     }
1431     /**
1432      * Provides access to the records in {portfolio_instance} table
1433      *
1434      * @param bool $disablecache do not use internal static cache
1435      * @return array array of stdClasses
1436      */
1437     protected static function get_enabled_portfolios($disablecache=false) {
1438         global $DB;
1439         static $portfolios = null;
1441         if (is_null($portfolios) or $disablecache) {
1442             $portfolios = array();
1443             $instances  = $DB->get_recordset('portfolio_instance', null, 'plugin');
1444             foreach ($instances as $instance) {
1445                 if (isset($portfolios[$instance->plugin])) {
1446                     if ($instance->visible) {
1447                         $portfolios[$instance->plugin]->visible = $instance->visible;
1448                     }
1449                 } else {
1450                     $portfolios[$instance->plugin] = $instance;
1451                 }
1452             }
1453         }
1455         return $portfolios;
1456     }
1459 /**
1460  * Class for themes
1461  */
1462 class plugintype_theme extends plugintype_base implements plugintype_interface {
1464     /**
1465      * @see plugintype_interface::is_enabled()
1466      */
1467     public function is_enabled() {
1468         global $CFG;
1470         if ((!empty($CFG->theme) and $CFG->theme === $this->name) or
1471             (!empty($CFG->themelegacy) and $CFG->themelegacy === $this->name)) {
1472             return true;
1473         } else {
1474             return parent::is_enabled();
1475         }
1476     }
1479 /**
1480  * Class representing an MNet service
1481  */
1482 class plugintype_mnetservice extends plugintype_base implements plugintype_interface {
1484     /**
1485      * @see plugintype_interface::is_enabled()
1486      */
1487     public function is_enabled() {
1488         global $CFG;
1490         if (empty($CFG->mnet_dispatcher_mode) || $CFG->mnet_dispatcher_mode !== 'strict') {
1491             return false;
1492         } else {
1493             return parent::is_enabled();
1494         }
1495     }