MDL-29509 move spamcleaner to admin tools
[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             ),
294             'message' => array(
295                 'email', 'jabber', 'popup'
296             ),
298             'mnetservice' => array(
299                 'enrol'
300             ),
302             'mod' => array(
303                 'assignment', 'chat', 'choice', 'data', 'feedback', 'folder',
304                 'forum', 'glossary', 'imscp', 'label', 'lesson', 'page',
305                 'quiz', 'resource', 'scorm', 'survey', 'url', 'wiki', 'workshop'
306             ),
308             'plagiarism' => array(
309             ),
311             'portfolio' => array(
312                 'boxnet', 'download', 'flickr', 'googledocs', 'mahara', 'picasa'
313             ),
315             'profilefield' => array(
316                 'checkbox', 'datetime', 'menu', 'text', 'textarea'
317             ),
319             'qbehaviour' => array(
320                 'adaptive', 'adaptivenopenalty', 'deferredcbm',
321                 'deferredfeedback', 'immediatecbm', 'immediatefeedback',
322                 'informationitem', 'interactive', 'interactivecountback',
323                 'manualgraded', 'missing'
324             ),
326             'qformat' => array(
327                 'aiken', 'blackboard', 'blackboard_six', 'examview', 'gift',
328                 'learnwise', 'missingword', 'multianswer', 'qti_two', 'webct',
329                 'xhtml', 'xml'
330             ),
332             'qtype' => array(
333                 'calculated', 'calculatedmulti', 'calculatedsimple',
334                 'description', 'essay', 'match', 'missingtype', 'multianswer',
335                 'multichoice', 'numerical', 'random', 'randomsamatch',
336                 'shortanswer', 'truefalse'
337             ),
339             'quiz' => array(
340                 'grading', 'overview', 'responses', 'statistics'
341             ),
343             'report' => array(
344                 'backups', 'configlog', 'courseoverview',
345                 'log', 'questioninstances', 'security', 'stats'
346             ),
348             'repository' => array(
349                 'alfresco', 'boxnet', 'coursefiles', 'dropbox', 'filesystem',
350                 'flickr', 'flickr_public', 'googledocs', 'local', 'merlot',
351                 'picasa', 'recent', 's3', 'upload', 'url', 'user', 'webdav',
352                 'wikimedia', 'youtube'
353             ),
355             'scormreport' => array(
356                 'basic'
357             ),
359             'theme' => array(
360                 'afterburner', 'anomaly', 'arialist', 'base', 'binarius',
361                 'boxxie', 'brick', 'canvas', 'formal_white', 'formfactor',
362                 'fusion', 'leatherbound', 'magazine', 'nimble', 'nonzero',
363                 'overlay', 'serenity', 'sky_high', 'splash', 'standard',
364                 'standardold'
365             ),
367             'tool' => array(
368                 'bloglevelupgrade', 'capability', 'customlang', 'dbtransfer', 'generator',
369                 'health', 'innodb', 'langimport', 'multilangupgrade', 'profiling',
370                 'qeupgradehelper', 'spamcleaner', 'unittest', 'uploaduser', 'unsuproles',
371                 'xmldb'
372             ),
374             'webservice' => array(
375                 'amf', 'rest', 'soap', 'xmlrpc'
376             ),
378             'workshopallocation' => array(
379                 'manual', 'random'
380             ),
382             'workshopeval' => array(
383                 'best'
384             ),
386             'workshopform' => array(
387                 'accumulative', 'comments', 'numerrors', 'rubric'
388             )
389         );
391         if (isset($standard_plugins[$type])) {
392             return $standard_plugins[$type];
394         } else {
395             return false;
396         }
397     }
400 /**
401  * All classes that represent a plugin of some type must implement this interface
402  */
403 interface plugintype_interface {
405     /**
406      * Gathers and returns the information about all plugins of the given type
407      *
408      * Passing the parameter $typeclass allows us to reach the same effect as with the
409      * late binding in PHP 5.3. Once PHP 5.3 is required, we can refactor this to use
410      * {@example $plugin = new static();} instead of {@example $plugin = new $typeclass()}
411      *
412      * @param string $type the name of the plugintype, eg. mod, auth or workshopform
413      * @param string $typerootdir full path to the location of the plugin dir
414      * @param string $typeclass the name of the actually called class
415      * @return array of plugintype classes, indexed by the plugin name
416      */
417     public static function get_plugins($type, $typerootdir, $typeclass);
419     /**
420      * Sets $displayname property to a localized name of the plugin
421      *
422      * @return void
423      */
424     public function set_display_name();
426     /**
427      * Sets $versiondisk property to a numerical value representing the
428      * version of the plugin's source code.
429      *
430      * If the value is null after calling this method, either the plugin
431      * does not use versioning (typically does not have any database
432      * data) or is missing from disk.
433      *
434      * @return void
435      */
436     public function set_version_disk();
438     /**
439      * Sets $versiondb property to a numerical value representing the
440      * currently installed version of the plugin.
441      *
442      * If the value is null after calling this method, either the plugin
443      * does not use versioning (typically does not have any database
444      * data) or has not been installed yet.
445      *
446      * @return void
447      */
448     public function set_version_db();
450     /**
451      * Sets $versionrequires property to a numerical value representing
452      * the version of Moodle core that this plugin requires.
453      *
454      * @return void
455      */
456     public function set_version_requires();
458     /**
459      * Sets $source property to one of plugin_manager::PLUGIN_SOURCE_xxx
460      * constants.
461      *
462      * If the property's value is null after calling this method, then
463      * the type of the plugin has not been recognized and you should throw
464      * an exception.
465      *
466      * @return void
467      */
468     public function set_source();
470     /**
471      * Returns true if the plugin is shipped with the official distribution
472      * of the current Moodle version, false otherwise.
473      *
474      * @return bool
475      */
476     public function is_standard();
478     /**
479      * Returns the status of the plugin
480      *
481      * @return string one of plugin_manager::PLUGIN_STATUS_xxx constants
482      */
483     public function get_status();
485     /**
486      * Returns the information about plugin availability
487      *
488      * True means that the plugin is enabled. False means that the plugin is
489      * disabled. Null means that the information is not available, or the
490      * plugin does not support configurable availability or the availability
491      * can not be changed.
492      *
493      * @return null|bool
494      */
495     public function is_enabled();
497     /**
498      * Returns the URL of the plugin settings screen
499      *
500      * Null value means that the plugin either does not have the settings screen
501      * or its location is not available via this library.
502      *
503      * @return null|moodle_url
504      */
505     public function get_settings_url();
507     /**
508      * Returns the URL of the screen where this plugin can be uninstalled
509      *
510      * Visiting that URL must be safe, that is a manual confirmation is needed
511      * for actual uninstallation of the plugin. Null value means that the
512      * plugin either does not support uninstallation, or does not require any
513      * database cleanup or the location of the screen is not available via this
514      * library.
515      *
516      * @return null|moodle_url
517      */
518     public function get_uninstall_url();
520     /**
521      * Returns relative directory of the plugin with heading '/'
522      *
523      * @example /mod/workshop
524      * @return string
525      */
526     public function get_dir();
529 /**
530  * Defines public properties that all plugintype classes must have
531  * and provides default implementation of required methods.
532  */
533 abstract class plugintype_base {
535     /** @var string the plugintype name, eg. mod, auth or workshopform */
536     public $type;
537     /** @var string full path to the location of all the plugins of this type */
538     public $typerootdir;
539     /** @var string the plugin name, eg. assignment, ldap */
540     public $name;
541     /** @var string the localized plugin name */
542     public $displayname;
543     /** @var string the plugin source, one of plugin_manager::PLUGIN_SOURCE_xxx constants */
544     public $source;
545     /** @var fullpath to the location of this plugin */
546     public $rootdir;
547     /** @var int|string the version of the plugin's source code */
548     public $versiondisk;
549     /** @var int|string the version of the installed plugin */
550     public $versiondb;
551     /** @var int|float|string required version of Moodle core  */
552     public $versionrequires;
553     /** @var int number of instances of the plugin - not supported yet */
554     public $instances;
555     /** @var int order of the plugin among other plugins of the same type - not supported yet */
556     public $sortorder;
558     /**
559      * @see plugintype_interface::get_plugins()
560      */
561     public static function get_plugins($type, $typerootdir, $typeclass) {
563         // get the information about plugins at the disk
564         $plugins = get_plugin_list($type);
565         $ondisk = array();
566         foreach ($plugins as $pluginname => $pluginrootdir) {
567             $plugin                 = new $typeclass();
568             $plugin->type           = $type;
569             $plugin->typerootdir    = $typerootdir;
570             $plugin->name           = $pluginname;
571             $plugin->rootdir        = $pluginrootdir;
573             $plugin->set_display_name();
574             $plugin->set_version_disk();
575             $plugin->set_version_db();
576             $plugin->set_version_requires();
577             $plugin->set_source();
579             $ondisk[$pluginname] = $plugin;
580         }
581         return $ondisk;
582     }
584     /**
585      * @see plugintype_interface::set_display_name()
586      */
587     public function set_display_name() {
588         if (! get_string_manager()->string_exists('pluginname', $this->type . '_' . $this->name)) {
589             $this->displayname = '[pluginname,' . $this->type . '_' . $this->name . ']';
590         } else {
591             $this->displayname = get_string('pluginname', $this->type . '_' . $this->name);
592         }
593     }
595     /**
596      * @see plugintype_interface::set_version_disk()
597      */
598     public function set_version_disk() {
600         if (empty($this->rootdir)) {
601             return;
602         }
604         $versionfile = $this->rootdir . '/version.php';
606         if (is_readable($versionfile)) {
607             include($versionfile);
608             if (isset($plugin->version)) {
609                 $this->versiondisk = $plugin->version;
610             }
611         }
612     }
614     /**
615      * @see plugintype_interface::set_version_db()
616      */
617     public function set_version_db() {
619         if ($ver = self::get_version_from_config_plugins($this->type . '_' . $this->name)) {
620             $this->versiondb = $ver;
621         }
622     }
624     /**
625      * @see plugintype_interface::set_version_requires()
626      */
627     public function set_version_requires() {
629         if (empty($this->rootdir)) {
630             return;
631         }
633         $versionfile = $this->rootdir . '/version.php';
635         if (is_readable($versionfile)) {
636             include($versionfile);
637             if (isset($plugin->requires)) {
638                 $this->versionrequires = $plugin->requires;
639             }
640         }
641     }
643     /**
644      * @see plugintype_interface::set_source()
645      */
646     public function set_source() {
648         $standard = plugin_manager::standard_plugins_list($this->type);
650         if ($standard !== false) {
651             $standard = array_flip($standard);
652             if (isset($standard[$this->name])) {
653                 $this->source = plugin_manager::PLUGIN_SOURCE_STANDARD;
654             } else {
655                 $this->source = plugin_manager::PLUGIN_SOURCE_EXTENSION;
656             }
657         }
658     }
660     /**
661      * @see plugintype_interface::is_standard()
662      */
663     public function is_standard() {
664         return $this->source === plugin_manager::PLUGIN_SOURCE_STANDARD;
665     }
667     /**
668      * @see plugintype_interface::get_status()
669      */
670     public function get_status() {
672         if (is_null($this->versiondb) and is_null($this->versiondisk)) {
673             return plugin_manager::PLUGIN_STATUS_NODB;
675         } else if (is_null($this->versiondb) and !is_null($this->versiondisk)) {
676             return plugin_manager::PLUGIN_STATUS_NEW;
678         } else if (!is_null($this->versiondb) and is_null($this->versiondisk)) {
679             return plugin_manager::PLUGIN_STATUS_MISSING;
681         } else if ((string)$this->versiondb === (string)$this->versiondisk) {
682             return plugin_manager::PLUGIN_STATUS_UPTODATE;
684         } else if ($this->versiondb < $this->versiondisk) {
685             return plugin_manager::PLUGIN_STATUS_UPGRADE;
687         } else if ($this->versiondb > $this->versiondisk) {
688             return plugin_manager::PLUGIN_STATUS_DOWNGRADE;
690         } else {
691             // $version = pi(); and similar funny jokes - hopefully Donald E. Knuth will never contribute to Moodle ;-)
692             throw new coding_exception('Unable to determine plugin state, check the plugin versions');
693         }
694     }
696     /**
697      * @see plugintype_interface::is_enabled()
698      */
699     public function is_enabled() {
700         return null;
701     }
703     /**
704      * @see plugintype_interface::get_settings_url()
705      */
706     public function get_settings_url() {
707         return null;
708     }
710     /**
711      * @see plugintype_interface::get_uninstall_url()
712      */
713     public function get_uninstall_url() {
714         return null;
715     }
717     /**
718      * @see plugintype_interface::get_dir()
719      */
720     public function get_dir() {
721         global $CFG;
723         return substr($this->rootdir, strlen($CFG->dirroot));
724     }
726     /**
727      * Provides access to plugin versions from {config_plugins}
728      *
729      * @param string $plugin plugin name
730      * @param double $disablecache optional, defaults to false
731      * @return int|false the stored value or false if not found
732      */
733     protected function get_version_from_config_plugins($plugin, $disablecache=false) {
734         global $DB;
735         static $pluginversions = null;
737         if (is_null($pluginversions) or $disablecache) {
738             $pluginversions = $DB->get_records_menu('config_plugins', array('name' => 'version'), 'plugin', 'plugin,value');
739         }
741         if (!array_key_exists($plugin, $pluginversions)) {
742             return false;
743         }
745         return $pluginversions[$plugin];
746     }
749 /**
750  * General class for all plugin types that do not have their own class
751  */
752 class plugintype_general extends plugintype_base implements plugintype_interface {
756 /**
757  * Class for page side blocks
758  */
759 class plugintype_block extends plugintype_base implements plugintype_interface {
761     /**
762      * @see plugintype_interface::get_plugins()
763      */
764     public static function get_plugins($type, $typerootdir, $typeclass) {
766         // get the information about blocks at the disk
767         $blocks = parent::get_plugins($type, $typerootdir, $typeclass);
769         // add blocks missing from disk
770         $blocksinfo = self::get_blocks_info();
771         foreach ($blocksinfo as $blockname => $blockinfo) {
772             if (isset($blocks[$blockname])) {
773                 continue;
774             }
775             $plugin                 = new $typeclass();
776             $plugin->type           = $type;
777             $plugin->typerootdir    = $typerootdir;
778             $plugin->name           = $blockname;
779             $plugin->rootdir        = null;
780             $plugin->displayname    = $blockname;
781             $plugin->versiondb      = $blockinfo->version;
782             $plugin->set_source();
784             $blocks[$blockname]   = $plugin;
785         }
787         return $blocks;
788     }
790     /**
791      * @see plugintype_interface::set_display_name()
792      */
793     public function set_display_name() {
795         if (get_string_manager()->string_exists('pluginname', 'block_' . $this->name)) {
796             $this->displayname = get_string('pluginname', 'block_' . $this->name);
798         } else if (($block = block_instance($this->name)) !== false) {
799             $this->displayname = $block->get_title();
801         } else {
802             parent::set_display_name();
803         }
804     }
806     /**
807      * @see plugintype_interface::set_version_db()
808      */
809     public function set_version_db() {
810         global $DB;
812         $blocksinfo = self::get_blocks_info();
813         if (isset($blocksinfo[$this->name]->version)) {
814             $this->versiondb = $blocksinfo[$this->name]->version;
815         }
816     }
818     /**
819      * @see plugintype_interface::is_enabled()
820      */
821     public function is_enabled() {
823         $blocksinfo = self::get_blocks_info();
824         if (isset($blocksinfo[$this->name]->visible)) {
825             if ($blocksinfo[$this->name]->visible) {
826                 return true;
827             } else {
828                 return false;
829             }
830         } else {
831             return parent::is_enabled();
832         }
833     }
835     /**
836      * @see plugintype_interface::get_settings_url()
837      */
838     public function get_settings_url() {
840         if (($block = block_instance($this->name)) === false) {
841             return parent::get_settings_url();
843         } else if ($block->has_config()) {
844             if (!empty($this->rootdir) and file_exists($this->rootdir . '/settings.php')) {
845                 return new moodle_url('/admin/settings.php', array('section' => 'blocksetting' . $this->name));
846             } else {
847                 $blocksinfo = self::get_blocks_info();
848                 return new moodle_url('/admin/block.php', array('block' => $blocksinfo[$this->name]->id));
849             }
851         } else {
852             return parent::get_settings_url();
853         }
854     }
856     /**
857      * @see plugintype_interface::get_uninstall_url()
858      */
859     public function get_uninstall_url() {
861         $blocksinfo = self::get_blocks_info();
862         return new moodle_url('/admin/blocks.php', array('delete' => $blocksinfo[$this->name]->id, 'sesskey' => sesskey()));
863     }
865     /**
866      * Provides access to the records in {block} table
867      *
868      * @param bool $disablecache do not use internal static cache
869      * @return array array of stdClasses
870      */
871     protected static function get_blocks_info($disablecache=false) {
872         global $DB;
873         static $blocksinfocache = null;
875         if (is_null($blocksinfocache) or $disablecache) {
876             $blocksinfocache = $DB->get_records('block', null, 'name', 'name,id,version,visible');
877         }
879         return $blocksinfocache;
880     }
883 /**
884  * Class for text filters
885  */
886 class plugintype_filter extends plugintype_base implements plugintype_interface {
888     /**
889      * @see plugintype_interface::get_plugins()
890      */
891     public static function get_plugins($type, $typerootdir, $typeclass) {
892         global $CFG, $DB;
894         $filters = array();
896         // get the list of filters from both /filter and /mod location
897         $installed = filter_get_all_installed();
899         foreach ($installed as $filterlegacyname => $displayname) {
900             $plugin                 = new $typeclass();
901             $plugin->type           = $type;
902             $plugin->typerootdir    = $typerootdir;
903             $plugin->name           = self::normalize_legacy_name($filterlegacyname);
904             $plugin->rootdir        = $CFG->dirroot . '/' . $filterlegacyname;
905             $plugin->displayname    = $displayname;
907             $plugin->set_version_disk();
908             $plugin->set_version_db();
909             $plugin->set_version_requires();
910             $plugin->set_source();
912             $filters[$plugin->name] = $plugin;
913         }
915         $globalstates = self::get_global_states();
917         if ($DB->get_manager()->table_exists('filter_active')) {
918             // if we're upgrading from 1.9, the table does not exist yet
919             // if it does, make sure that all installed filters are registered
920             $needsreload  = false;
921             foreach (array_keys($installed) as $filterlegacyname) {
922                 if (!isset($globalstates[self::normalize_legacy_name($filterlegacyname)])) {
923                     filter_set_global_state($filterlegacyname, TEXTFILTER_DISABLED);
924                     $needsreload = true;
925                 }
926             }
927             if ($needsreload) {
928                 $globalstates = self::get_global_states(true);
929             }
930         }
932         // make sure that all registered filters are installed, just in case
933         foreach ($globalstates as $name => $info) {
934             if (!isset($filters[$name])) {
935                 // oops, there is a record in filter_active but the filter is not installed
936                 $plugin                 = new $typeclass();
937                 $plugin->type           = $type;
938                 $plugin->typerootdir    = $typerootdir;
939                 $plugin->name           = $name;
940                 $plugin->rootdir        = $CFG->dirroot . '/' . $info->legacyname;
941                 $plugin->displayname    = $info->legacyname;
943                 $plugin->set_version_db();
945                 if (is_null($plugin->versiondb)) {
946                     // this is a hack to stimulate 'Missing from disk' error
947                     // because $plugin->versiondisk will be null !== false
948                     $plugin->versiondb = false;
949                 }
951                 $filters[$plugin->name] = $plugin;
952             }
953         }
955         return $filters;
956     }
958     /**
959      * @see plugintype_interface::set_display_name()
960      */
961     public function set_display_name() {
962         // do nothing, the name is set in self::get_plugins()
963     }
965     /**
966      * @see plugintype_interface::set_version_disk()
967      */
968     public function set_version_disk() {
970         if (strpos($this->name, 'mod_') === 0) {
971             // filters bundled with modules do not use versioning
972             return;
973         }
975         return parent::set_version_disk();
976     }
978     /**
979      * @see plugintype_interface::set_version_requires()
980      */
981     public function set_version_requires() {
983         if (strpos($this->name, 'mod_') === 0) {
984             // filters bundled with modules do not use versioning
985             return;
986         }
988         return parent::set_version_requires();
989     }
991     /**
992      * @see plugintype_interface::is_enabled()
993      */
994     public function is_enabled() {
996         $globalstates = self::get_global_states();
998         foreach ($globalstates as $filterlegacyname => $info) {
999             $name = self::normalize_legacy_name($filterlegacyname);
1000             if ($name === $this->name) {
1001                 if ($info->active == TEXTFILTER_DISABLED) {
1002                     return false;
1003                 } else {
1004                     // it may be 'On' or 'Off, but available'
1005                     return null;
1006                 }
1007             }
1008         }
1010         return null;
1011     }
1013     /**
1014      * @see plugintype_interface::get_settings_url()
1015      */
1016     public function get_settings_url() {
1018         $globalstates = self::get_global_states();
1019         $legacyname = $globalstates[$this->name]->legacyname;
1020         if (filter_has_global_settings($legacyname)) {
1021             return new moodle_url('/admin/settings.php', array('section' => 'filtersetting' . str_replace('/', '', $legacyname)));
1022         } else {
1023             return null;
1024         }
1025     }
1027     /**
1028      * @see plugintype_interface::get_uninstall_url()
1029      */
1030     public function get_uninstall_url() {
1032         if (strpos($this->name, 'mod_') === 0) {
1033             return null;
1034         } else {
1035             $globalstates = self::get_global_states();
1036             $legacyname = $globalstates[$this->name]->legacyname;
1037             return new moodle_url('/admin/filters.php', array('sesskey' => sesskey(), 'filterpath' => $legacyname, 'action' => 'delete'));
1038         }
1039     }
1041     /**
1042      * Convert legacy filter names like 'filter/foo' or 'mod/bar' into frankenstyle
1043      *
1044      * @param string $legacyfiltername legacy filter name
1045      * @return string frankenstyle-like name
1046      */
1047     protected static function normalize_legacy_name($legacyfiltername) {
1049         $name = str_replace('/', '_', $legacyfiltername);
1050         if (strpos($name, 'filter_') === 0) {
1051             $name = substr($name, 7);
1052             if (empty($name)) {
1053                 throw new coding_exception('Unable to determine filter name: ' . $legacyfiltername);
1054             }
1055         }
1057         return $name;
1058     }
1060     /**
1061      * Provides access to the results of {@link filter_get_global_states()}
1062      * but indexed by the normalized filter name
1063      *
1064      * The legacy filter name is available as ->legacyname property.
1065      *
1066      * @param bool $disablecache
1067      * @return array
1068      */
1069     protected static function get_global_states($disablecache=false) {
1070         global $DB;
1071         static $globalstatescache = null;
1073         if ($disablecache or is_null($globalstatescache)) {
1075             if (!$DB->get_manager()->table_exists('filter_active')) {
1076                 // we're upgrading from 1.9 and the table used by {@link filter_get_global_states()}
1077                 // does not exist yet
1078                 $globalstatescache = array();
1080             } else {
1081                 foreach (filter_get_global_states() as $legacyname => $info) {
1082                     $name                       = self::normalize_legacy_name($legacyname);
1083                     $filterinfo                 = new stdClass();
1084                     $filterinfo->legacyname     = $legacyname;
1085                     $filterinfo->active         = $info->active;
1086                     $filterinfo->sortorder      = $info->sortorder;
1087                     $globalstatescache[$name]   = $filterinfo;
1088                 }
1089             }
1090         }
1092         return $globalstatescache;
1093     }
1096 /**
1097  * Class for activity modules
1098  */
1099 class plugintype_mod extends plugintype_base implements plugintype_interface {
1101     /**
1102      * @see plugintype_interface::get_plugins()
1103      */
1104     public static function get_plugins($type, $typerootdir, $typeclass) {
1106         // get the information about plugins at the disk
1107         $modules = parent::get_plugins($type, $typerootdir, $typeclass);
1109         // add modules missing from disk
1110         $modulesinfo = self::get_modules_info();
1111         foreach ($modulesinfo as $modulename => $moduleinfo) {
1112             if (isset($modules[$modulename])) {
1113                 continue;
1114             }
1115             $plugin                 = new $typeclass();
1116             $plugin->type           = $type;
1117             $plugin->typerootdir    = $typerootdir;
1118             $plugin->name           = $modulename;
1119             $plugin->rootdir        = null;
1120             $plugin->displayname    = $modulename;
1121             $plugin->versiondb      = $moduleinfo->version;
1122             $plugin->set_source();
1124             $modules[$modulename]   = $plugin;
1125         }
1127         return $modules;
1128     }
1130     /**
1131      * @see plugintype_interface::set_display_name()
1132      */
1133     public function set_display_name() {
1134         if (get_string_manager()->string_exists('pluginname', $this->type . '_' . $this->name)) {
1135             $this->displayname = get_string('pluginname', $this->type . '_' . $this->name);
1136         } else {
1137             $this->displayname = get_string('modulename', $this->type . '_' . $this->name);
1138         }
1139     }
1141     /**
1142      * @see plugintype_interface::set_version_disk()
1143      */
1144     public function set_version_disk() {
1146         if (empty($this->rootdir)) {
1147             return;
1148         }
1150         $versionfile = $this->rootdir . '/version.php';
1152         if (is_readable($versionfile)) {
1153             include($versionfile);
1154             if (isset($module->version)) {
1155                 $this->versiondisk = $module->version;
1156             }
1157         }
1158     }
1160     /**
1161      * @see plugintype_interface::set_version_db()
1162      */
1163     public function set_version_db() {
1164         global $DB;
1166         $modulesinfo = self::get_modules_info();
1167         if (isset($modulesinfo[$this->name]->version)) {
1168             $this->versiondb = $modulesinfo[$this->name]->version;
1169         }
1170     }
1172     /**
1173      * @see plugintype_interface::set_version_requires()
1174      */
1175     public function set_version_requires() {
1177         if (empty($this->rootdir)) {
1178             return;
1179         }
1181         $versionfile = $this->rootdir . '/version.php';
1183         if (is_readable($versionfile)) {
1184             include($versionfile);
1185             if (isset($module->requires)) {
1186                 $this->versionrequires = $module->requires;
1187             }
1188         }
1189     }
1191     /**
1192      * @see plugintype_interface::is_enabled()
1193      */
1194     public function is_enabled() {
1196         $modulesinfo = self::get_modules_info();
1197         if (isset($modulesinfo[$this->name]->visible)) {
1198             if ($modulesinfo[$this->name]->visible) {
1199                 return true;
1200             } else {
1201                 return false;
1202             }
1203         } else {
1204             return parent::is_enabled();
1205         }
1206     }
1208     /**
1209      * @see plugintype_interface::get_settings_url()
1210      */
1211     public function get_settings_url() {
1213         if (!empty($this->rootdir) and (file_exists($this->rootdir . '/settings.php') or file_exists($this->rootdir . '/settingstree.php'))) {
1214             return new moodle_url('/admin/settings.php', array('section' => 'modsetting' . $this->name));
1215         } else {
1216             return parent::get_settings_url();
1217         }
1218     }
1220     /**
1221      * @see plugintype_interface::get_uninstall_url()
1222      */
1223     public function get_uninstall_url() {
1225         if ($this->name !== 'forum') {
1226             return new moodle_url('/admin/modules.php', array('delete' => $this->name, 'sesskey' => sesskey()));
1227         } else {
1228             return null;
1229         }
1230     }
1232     /**
1233      * Provides access to the records in {modules} table
1234      *
1235      * @param bool $disablecache do not use internal static cache
1236      * @return array array of stdClasses
1237      */
1238     protected static function get_modules_info($disablecache=false) {
1239         global $DB;
1240         static $modulesinfocache = null;
1242         if (is_null($modulesinfocache) or $disablecache) {
1243             $modulesinfocache = $DB->get_records('modules', null, 'name', 'name,id,version,visible');
1244         }
1246         return $modulesinfocache;
1247     }
1250 /**
1251  * Class for question types
1252  */
1253 class plugintype_qtype extends plugintype_base implements plugintype_interface {
1255     /**
1256      * @see plugintype_interface::set_display_name()
1257      */
1258     public function set_display_name() {
1259         $this->displayname = get_string($this->name, 'qtype_' . $this->name);
1260     }
1263 /**
1264  * Class for question formats
1265  */
1266 class plugintype_qformat extends plugintype_base implements plugintype_interface {
1268     /**
1269      * @see plugintype_interface::set_display_name()
1270      */
1271     public function set_display_name() {
1272         $this->displayname = get_string($this->name, 'qformat_' . $this->name);
1273     }
1276 /**
1277  * Class for authentication plugins
1278  */
1279 class plugintype_auth extends plugintype_base implements plugintype_interface {
1281     /**
1282      * @see plugintype_interface::is_enabled()
1283      */
1284     public function is_enabled() {
1285         global $CFG;
1286         /** @var null|array list of enabled authentication plugins */
1287         static $enabled = null;
1289         if (in_array($this->name, array('nologin', 'manual'))) {
1290             // these two are always enabled and can't be disabled
1291             return null;
1292         }
1294         if (is_null($enabled)) {
1295             $enabled = explode(',', $CFG->auth);
1296         }
1298         return isset($enabled[$this->name]);
1299     }
1301     /**
1302      * @see plugintype_interface::get_settings_url()
1303      */
1304     public function get_settings_url() {
1306         if (!empty($this->rootdir) and file_exists($this->rootdir . '/settings.php')) {
1307             return new moodle_url('/admin/settings.php', array('section' => 'authsetting' . $this->name));
1308         } else {
1309             return new moodle_url('/admin/auth_config.php', array('auth' => $this->name));
1310         }
1311     }
1314 /**
1315  * Class for enrolment plugins
1316  */
1317 class plugintype_enrol extends plugintype_base implements plugintype_interface {
1319     /**
1320      * We do not actually need whole enrolment classes here so we do not call
1321      * {@link enrol_get_plugins()}. Note that this may produce slightly different
1322      * results, for example if the enrolment plugin does not contain lib.php
1323      * but it is listed in $CFG->enrol_plugins_enabled
1324      *
1325      * @see plugintype_interface::is_enabled()
1326      */
1327     public function is_enabled() {
1328         global $CFG;
1329         /** @var null|array list of enabled enrolment plugins */
1330         static $enabled = null;
1332         if (is_null($enabled)) {
1333             $enabled = explode(',', $CFG->enrol_plugins_enabled);
1334         }
1336         return isset($enabled[$this->name]);
1337     }
1339     /**
1340      * @see plugintype_interface::get_settings_url()
1341      */
1342     public function get_settings_url() {
1344         if ($this->is_enabled() or (!empty($this->rootdir) and file_exists($this->rootdir . '/settings.php'))) {
1345             return new moodle_url('/admin/settings.php', array('section' => 'enrolsettings' . $this->name));
1346         } else {
1347             return parent::get_settings_url();
1348         }
1349     }
1351     /**
1352      * @see plugintype_interface::get_uninstall_url()
1353      */
1354     public function get_uninstall_url() {
1355         return new moodle_url('/admin/enrol.php', array('action' => 'uninstall', 'enrol' => $this->name, 'sesskey' => sesskey()));
1356     }
1359 /**
1360  * Class for messaging processors
1361  */
1362 class plugintype_message extends plugintype_base implements plugintype_interface {
1364     /**
1365      * @see plugintype_interface::get_settings_url()
1366      */
1367     public function get_settings_url() {
1369         if ($this->name === 'jabber') {
1370             return new moodle_url('/admin/settings.php', array('section' => 'jabber'));
1371         }
1373         if ($this->name === 'email') {
1374             return new moodle_url('/admin/settings.php', array('section' => 'mail'));
1375         }
1377     }
1380 /**
1381  * Class for repositories
1382  */
1383 class plugintype_repository extends plugintype_base implements plugintype_interface {
1385     /**
1386      * @see plugintype_interface::is_enabled()
1387      */
1388     public function is_enabled() {
1390         $enabled = self::get_enabled_repositories();
1392         return isset($enabled[$this->name]);
1393     }
1395     /**
1396      * @see plugintype_interface::get_settings_url()
1397      */
1398     public function get_settings_url() {
1400         if ($this->is_enabled()) {
1401             return new moodle_url('/admin/repository.php', array('sesskey' => sesskey(), 'action' => 'edit', 'repos' => $this->name));
1402         } else {
1403             return parent::get_settings_url();
1404         }
1405     }
1407     /**
1408      * Provides access to the records in {repository} table
1409      *
1410      * @param bool $disablecache do not use internal static cache
1411      * @return array array of stdClasses
1412      */
1413     protected static function get_enabled_repositories($disablecache=false) {
1414         global $DB;
1415         static $repositories = null;
1417         if (is_null($repositories) or $disablecache) {
1418             $repositories = $DB->get_records('repository', null, 'type', 'type,visible,sortorder');
1419         }
1421         return $repositories;
1422     }
1425 /**
1426  * Class for portfolios
1427  */
1428 class plugintype_portfolio extends plugintype_base implements plugintype_interface {
1430     /**
1431      * @see plugintype_interface::is_enabled()
1432      */
1433     public function is_enabled() {
1435         $enabled = self::get_enabled_portfolios();
1437         return isset($enabled[$this->name]);
1438     }
1440     /**
1441      * Provides access to the records in {portfolio_instance} table
1442      *
1443      * @param bool $disablecache do not use internal static cache
1444      * @return array array of stdClasses
1445      */
1446     protected static function get_enabled_portfolios($disablecache=false) {
1447         global $DB;
1448         static $portfolios = null;
1450         if (is_null($portfolios) or $disablecache) {
1451             $portfolios = array();
1452             $instances  = $DB->get_recordset('portfolio_instance', null, 'plugin');
1453             foreach ($instances as $instance) {
1454                 if (isset($portfolios[$instance->plugin])) {
1455                     if ($instance->visible) {
1456                         $portfolios[$instance->plugin]->visible = $instance->visible;
1457                     }
1458                 } else {
1459                     $portfolios[$instance->plugin] = $instance;
1460                 }
1461             }
1462         }
1464         return $portfolios;
1465     }
1468 /**
1469  * Class for themes
1470  */
1471 class plugintype_theme extends plugintype_base implements plugintype_interface {
1473     /**
1474      * @see plugintype_interface::is_enabled()
1475      */
1476     public function is_enabled() {
1477         global $CFG;
1479         if ((!empty($CFG->theme) and $CFG->theme === $this->name) or
1480             (!empty($CFG->themelegacy) and $CFG->themelegacy === $this->name)) {
1481             return true;
1482         } else {
1483             return parent::is_enabled();
1484         }
1485     }
1488 /**
1489  * Class representing an MNet service
1490  */
1491 class plugintype_mnetservice extends plugintype_base implements plugintype_interface {
1493     /**
1494      * @see plugintype_interface::is_enabled()
1495      */
1496     public function is_enabled() {
1497         global $CFG;
1499         if (empty($CFG->mnet_dispatcher_mode) || $CFG->mnet_dispatcher_mode !== 'strict') {
1500             return false;
1501         } else {
1502             return parent::is_enabled();
1503         }
1504     }