MDL-29509 migrate DB replace to admin tools
[moodle.git] / lib / pluginlib.php
CommitLineData
b9934a17
DM
1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * Defines classes used for 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 */
29
30defined('MOODLE_INTERNAL') || die();
31
32/**
33 * Singleton class providing general plugins management functionality
34 */
35class plugin_manager {
36
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';
41
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';
54
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;
61
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 }
70
71 /**
72 * Sorry, this is singleton
73 */
74 protected function __clone() {
75 }
76
77 /**
78 * Factory method for this class
79 *
80 * @return plugin_manager the singleton instance
81 */
82 public static function instance() {
83 global $CFG;
84
85 if (is_null(self::$singletoninstance)) {
86 self::$singletoninstance = new self();
87 }
88 return self::$singletoninstance;
89 }
90
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) {
98
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 }
118
119 return $this->pluginsinfo;
120 }
121
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) {
131
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 }
149
150 return $this->subpluginsinfo;
151 }
152
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) {
162
163 $parent = false;
164 foreach ($this->get_subplugins() as $pluginname => $subplugintypes) {
165 if (isset($subplugintypes[$subplugintype])) {
166 $parent = $pluginname;
167 break;
168 }
169 }
170
171 return $parent;
172 }
173
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 }
184
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) {
196
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');
200
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 }
208
209 } else {
210 return $type;
211 }
212 }
213
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(
221
222 'assignment' => array(
223 'offline', 'online', 'upload', 'uploadsingle'
224 ),
225
226 'auth' => array(
227 'cas', 'db', 'email', 'fc', 'imap', 'ldap', 'manual', 'mnet',
228 'nntp', 'nologin', 'none', 'pam', 'pop3', 'radius',
229 'shibboleth', 'webservice'
230 ),
231
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 ),
245
246 'coursereport' => array(
247 'completion', 'log', 'outline', 'participation', 'progress', 'stats'
248 ),
249
250 'datafield' => array(
251 'checkbox', 'date', 'file', 'latlong', 'menu', 'multimenu',
252 'number', 'picture', 'radiobutton', 'text', 'textarea', 'url'
253 ),
254
255 'datapreset' => array(
256 'imagegallery'
257 ),
258
259 'editor' => array(
260 'textarea', 'tinymce'
261 ),
262
263 'enrol' => array(
264 'authorize', 'category', 'cohort', 'database', 'flatfile',
265 'guest', 'imsenterprise', 'ldap', 'manual', 'meta', 'mnet',
266 'paypal', 'self'
267 ),
268
269 'filter' => array(
270 'activitynames', 'algebra', 'censor', 'emailprotect',
271 'emoticon', 'mediaplugin', 'multilang', 'tex', 'tidy',
272 'urltolink', 'mod_data', 'mod_glossary'
273 ),
274
275 'format' => array(
276 'scorm', 'social', 'topics', 'weeks'
277 ),
278
279 'gradeexport' => array(
280 'ods', 'txt', 'xls', 'xml'
281 ),
282
283 'gradeimport' => array(
284 'csv', 'xml'
285 ),
286
287 'gradereport' => array(
288 'grader', 'outcomes', 'overview', 'user'
289 ),
290
291 'local' => array(
292 ),
293
294 'message' => array(
295 'email', 'jabber', 'popup'
296 ),
297
298 'mnetservice' => array(
299 'enrol'
300 ),
301
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 ),
307
308 'plagiarism' => array(
309 ),
310
311 'portfolio' => array(
312 'boxnet', 'download', 'flickr', 'googledocs', 'mahara', 'picasa'
313 ),
314
315 'profilefield' => array(
316 'checkbox', 'datetime', 'menu', 'text', 'textarea'
317 ),
318
d1c77ac3
DM
319 'qbehaviour' => array(
320 'adaptive', 'adaptivenopenalty', 'deferredcbm',
321 'deferredfeedback', 'immediatecbm', 'immediatefeedback',
322 'informationitem', 'interactive', 'interactivecountback',
323 'manualgraded', 'missing'
324 ),
325
b9934a17
DM
326 'qformat' => array(
327 'aiken', 'blackboard', 'blackboard_six', 'examview', 'gift',
328 'learnwise', 'missingword', 'multianswer', 'qti_two', 'webct',
329 'xhtml', 'xml'
330 ),
331
332 'qtype' => array(
333 'calculated', 'calculatedmulti', 'calculatedsimple',
334 'description', 'essay', 'match', 'missingtype', 'multianswer',
335 'multichoice', 'numerical', 'random', 'randomsamatch',
336 'shortanswer', 'truefalse'
337 ),
338
339 'quiz' => array(
340 'grading', 'overview', 'responses', 'statistics'
341 ),
342
343 'report' => array(
4211dfa4 344 'backups', 'configlog', 'courseoverview',
17a14fbd 345 'log', 'questioninstances', 'security', 'stats'
b9934a17
DM
346 ),
347
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 ),
354
99e86561
PS
355 'scormreport' => array(
356 'basic'
357 ),
358
b9934a17 359 'theme' => array(
bef9ad95
DM
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'
b9934a17
DM
365 ),
366
11b24ce7 367 'tool' => array(
b703861f 368 'bloglevelupgrade', 'capability', 'customlang', 'dbtransfer', 'generator',
cff8fc8d 369 'health', 'innodb', 'langimport', 'multilangupgrade', 'profiling',
fab6f7b7 370 'qeupgradehelper', 'replace', 'spamcleaner', 'timezoneimport', 'unittest',
9597e00b 371 'uploaduser', 'unsuproles', 'xmldb'
11b24ce7
PS
372 ),
373
b9934a17
DM
374 'webservice' => array(
375 'amf', 'rest', 'soap', 'xmlrpc'
376 ),
377
378 'workshopallocation' => array(
379 'manual', 'random'
380 ),
381
382 'workshopeval' => array(
383 'best'
384 ),
385
386 'workshopform' => array(
387 'accumulative', 'comments', 'numerrors', 'rubric'
388 )
389 );
390
391 if (isset($standard_plugins[$type])) {
392 return $standard_plugins[$type];
393
394 } else {
395 return false;
396 }
397 }
398}
399
400/**
401 * All classes that represent a plugin of some type must implement this interface
402 */
403interface plugintype_interface {
404
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);
418
419 /**
420 * Sets $displayname property to a localized name of the plugin
421 *
422 * @return void
423 */
424 public function set_display_name();
425
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();
437
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();
449
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();
457
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();
469
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();
477
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();
484
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();
496
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();
506
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();
519
520 /**
521 * Returns relative directory of the plugin with heading '/'
522 *
523 * @example /mod/workshop
524 * @return string
525 */
526 public function get_dir();
527}
528
529/**
530 * Defines public properties that all plugintype classes must have
531 * and provides default implementation of required methods.
532 */
533abstract class plugintype_base {
534
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;
557
558 /**
559 * @see plugintype_interface::get_plugins()
560 */
561 public static function get_plugins($type, $typerootdir, $typeclass) {
562
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;
572
573 $plugin->set_display_name();
574 $plugin->set_version_disk();
575 $plugin->set_version_db();
576 $plugin->set_version_requires();
577 $plugin->set_source();
578
579 $ondisk[$pluginname] = $plugin;
580 }
581 return $ondisk;
582 }
583
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 }
594
595 /**
596 * @see plugintype_interface::set_version_disk()
597 */
598 public function set_version_disk() {
599
600 if (empty($this->rootdir)) {
601 return;
602 }
603
604 $versionfile = $this->rootdir . '/version.php';
605
606 if (is_readable($versionfile)) {
607 include($versionfile);
608 if (isset($plugin->version)) {
609 $this->versiondisk = $plugin->version;
610 }
611 }
612 }
613
614 /**
615 * @see plugintype_interface::set_version_db()
616 */
617 public function set_version_db() {
618
619 if ($ver = self::get_version_from_config_plugins($this->type . '_' . $this->name)) {
620 $this->versiondb = $ver;
621 }
622 }
623
624 /**
625 * @see plugintype_interface::set_version_requires()
626 */
627 public function set_version_requires() {
628
629 if (empty($this->rootdir)) {
630 return;
631 }
632
633 $versionfile = $this->rootdir . '/version.php';
634
635 if (is_readable($versionfile)) {
636 include($versionfile);
637 if (isset($plugin->requires)) {
638 $this->versionrequires = $plugin->requires;
639 }
640 }
641 }
642
643 /**
644 * @see plugintype_interface::set_source()
645 */
646 public function set_source() {
647
648 $standard = plugin_manager::standard_plugins_list($this->type);
649
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 }
659
660 /**
661 * @see plugintype_interface::is_standard()
662 */
663 public function is_standard() {
664 return $this->source === plugin_manager::PLUGIN_SOURCE_STANDARD;
665 }
666
667 /**
668 * @see plugintype_interface::get_status()
669 */
670 public function get_status() {
671
672 if (is_null($this->versiondb) and is_null($this->versiondisk)) {
673 return plugin_manager::PLUGIN_STATUS_NODB;
674
675 } else if (is_null($this->versiondb) and !is_null($this->versiondisk)) {
676 return plugin_manager::PLUGIN_STATUS_NEW;
677
678 } else if (!is_null($this->versiondb) and is_null($this->versiondisk)) {
679 return plugin_manager::PLUGIN_STATUS_MISSING;
680
681 } else if ((string)$this->versiondb === (string)$this->versiondisk) {
682 return plugin_manager::PLUGIN_STATUS_UPTODATE;
683
684 } else if ($this->versiondb < $this->versiondisk) {
685 return plugin_manager::PLUGIN_STATUS_UPGRADE;
686
687 } else if ($this->versiondb > $this->versiondisk) {
688 return plugin_manager::PLUGIN_STATUS_DOWNGRADE;
689
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 }
695
696 /**
697 * @see plugintype_interface::is_enabled()
698 */
699 public function is_enabled() {
700 return null;
701 }
702
703 /**
704 * @see plugintype_interface::get_settings_url()
705 */
706 public function get_settings_url() {
707 return null;
708 }
709
710 /**
711 * @see plugintype_interface::get_uninstall_url()
712 */
713 public function get_uninstall_url() {
714 return null;
715 }
716
717 /**
718 * @see plugintype_interface::get_dir()
719 */
720 public function get_dir() {
721 global $CFG;
722
723 return substr($this->rootdir, strlen($CFG->dirroot));
724 }
725
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;
736
737 if (is_null($pluginversions) or $disablecache) {
738 $pluginversions = $DB->get_records_menu('config_plugins', array('name' => 'version'), 'plugin', 'plugin,value');
739 }
740
741 if (!array_key_exists($plugin, $pluginversions)) {
742 return false;
743 }
744
745 return $pluginversions[$plugin];
746 }
747}
748
749/**
750 * General class for all plugin types that do not have their own class
751 */
752class plugintype_general extends plugintype_base implements plugintype_interface {
753
754}
755
756/**
757 * Class for page side blocks
758 */
759class plugintype_block extends plugintype_base implements plugintype_interface {
760
761 /**
762 * @see plugintype_interface::get_plugins()
763 */
764 public static function get_plugins($type, $typerootdir, $typeclass) {
765
766 // get the information about blocks at the disk
767 $blocks = parent::get_plugins($type, $typerootdir, $typeclass);
768
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();
783
784 $blocks[$blockname] = $plugin;
785 }
786
787 return $blocks;
788 }
789
790 /**
791 * @see plugintype_interface::set_display_name()
792 */
793 public function set_display_name() {
794
795 if (get_string_manager()->string_exists('pluginname', 'block_' . $this->name)) {
796 $this->displayname = get_string('pluginname', 'block_' . $this->name);
797
798 } else if (($block = block_instance($this->name)) !== false) {
799 $this->displayname = $block->get_title();
800
801 } else {
802 parent::set_display_name();
803 }
804 }
805
806 /**
807 * @see plugintype_interface::set_version_db()
808 */
809 public function set_version_db() {
810 global $DB;
811
812 $blocksinfo = self::get_blocks_info();
813 if (isset($blocksinfo[$this->name]->version)) {
814 $this->versiondb = $blocksinfo[$this->name]->version;
815 }
816 }
817
818 /**
819 * @see plugintype_interface::is_enabled()
820 */
821 public function is_enabled() {
822
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 }
834
835 /**
836 * @see plugintype_interface::get_settings_url()
837 */
838 public function get_settings_url() {
839
840 if (($block = block_instance($this->name)) === false) {
841 return parent::get_settings_url();
842
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 }
850
851 } else {
852 return parent::get_settings_url();
853 }
854 }
855
856 /**
857 * @see plugintype_interface::get_uninstall_url()
858 */
859 public function get_uninstall_url() {
860
861 $blocksinfo = self::get_blocks_info();
862 return new moodle_url('/admin/blocks.php', array('delete' => $blocksinfo[$this->name]->id, 'sesskey' => sesskey()));
863 }
864
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;
874
875 if (is_null($blocksinfocache) or $disablecache) {
876 $blocksinfocache = $DB->get_records('block', null, 'name', 'name,id,version,visible');
877 }
878
879 return $blocksinfocache;
880 }
881}
882
883/**
884 * Class for text filters
885 */
886class plugintype_filter extends plugintype_base implements plugintype_interface {
887
888 /**
889 * @see plugintype_interface::get_plugins()
890 */
891 public static function get_plugins($type, $typerootdir, $typeclass) {
7c9b837e 892 global $CFG, $DB;
b9934a17
DM
893
894 $filters = array();
895
896 // get the list of filters from both /filter and /mod location
897 $installed = filter_get_all_installed();
898
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;
906
907 $plugin->set_version_disk();
908 $plugin->set_version_db();
909 $plugin->set_version_requires();
910 $plugin->set_source();
911
912 $filters[$plugin->name] = $plugin;
913 }
914
b9934a17 915 $globalstates = self::get_global_states();
7c9b837e
DM
916
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);
b9934a17 929 }
b9934a17
DM
930 }
931
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;
942
943 $plugin->set_version_db();
944
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 }
950
951 $filters[$plugin->name] = $plugin;
952 }
953 }
954
955 return $filters;
956 }
957
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 }
964
965 /**
966 * @see plugintype_interface::set_version_disk()
967 */
968 public function set_version_disk() {
969
970 if (strpos($this->name, 'mod_') === 0) {
971 // filters bundled with modules do not use versioning
972 return;
973 }
974
975 return parent::set_version_disk();
976 }
977
978 /**
979 * @see plugintype_interface::set_version_requires()
980 */
981 public function set_version_requires() {
982
983 if (strpos($this->name, 'mod_') === 0) {
984 // filters bundled with modules do not use versioning
985 return;
986 }
987
988 return parent::set_version_requires();
989 }
990
991 /**
992 * @see plugintype_interface::is_enabled()
993 */
994 public function is_enabled() {
995
996 $globalstates = self::get_global_states();
997
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 }
1009
1010 return null;
1011 }
1012
1013 /**
1014 * @see plugintype_interface::get_settings_url()
1015 */
1016 public function get_settings_url() {
1017
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 }
1026
1027 /**
1028 * @see plugintype_interface::get_uninstall_url()
1029 */
1030 public function get_uninstall_url() {
1031
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 }
1040
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) {
1048
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 }
1056
1057 return $name;
1058 }
1059
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;
1072
1073 if ($disablecache or is_null($globalstatescache)) {
1074
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();
1079
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 }
1091
1092 return $globalstatescache;
1093 }
1094}
1095
1096/**
1097 * Class for activity modules
1098 */
1099class plugintype_mod extends plugintype_base implements plugintype_interface {
1100
1101 /**
1102 * @see plugintype_interface::get_plugins()
1103 */
1104 public static function get_plugins($type, $typerootdir, $typeclass) {
1105
1106 // get the information about plugins at the disk
1107 $modules = parent::get_plugins($type, $typerootdir, $typeclass);
1108
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();
1123
1124 $modules[$modulename] = $plugin;
1125 }
1126
1127 return $modules;
1128 }
1129
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 }
1140
1141 /**
1142 * @see plugintype_interface::set_version_disk()
1143 */
1144 public function set_version_disk() {
1145
1146 if (empty($this->rootdir)) {
1147 return;
1148 }
1149
1150 $versionfile = $this->rootdir . '/version.php';
1151
1152 if (is_readable($versionfile)) {
1153 include($versionfile);
1154 if (isset($module->version)) {
1155 $this->versiondisk = $module->version;
1156 }
1157 }
1158 }
1159
1160 /**
1161 * @see plugintype_interface::set_version_db()
1162 */
1163 public function set_version_db() {
1164 global $DB;
1165
1166 $modulesinfo = self::get_modules_info();
1167 if (isset($modulesinfo[$this->name]->version)) {
1168 $this->versiondb = $modulesinfo[$this->name]->version;
1169 }
1170 }
1171
1172 /**
1173 * @see plugintype_interface::set_version_requires()
1174 */
1175 public function set_version_requires() {
1176
1177 if (empty($this->rootdir)) {
1178 return;
1179 }
1180
1181 $versionfile = $this->rootdir . '/version.php';
1182
1183 if (is_readable($versionfile)) {
1184 include($versionfile);
1185 if (isset($module->requires)) {
1186 $this->versionrequires = $module->requires;
1187 }
1188 }
1189 }
1190
1191 /**
1192 * @see plugintype_interface::is_enabled()
1193 */
1194 public function is_enabled() {
1195
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 }
1207
1208 /**
1209 * @see plugintype_interface::get_settings_url()
1210 */
1211 public function get_settings_url() {
1212
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 }
1219
1220 /**
1221 * @see plugintype_interface::get_uninstall_url()
1222 */
1223 public function get_uninstall_url() {
1224
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 }
1231
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;
1241
1242 if (is_null($modulesinfocache) or $disablecache) {
1243 $modulesinfocache = $DB->get_records('modules', null, 'name', 'name,id,version,visible');
1244 }
1245
1246 return $modulesinfocache;
1247 }
1248}
1249
1250/**
1251 * Class for question types
1252 */
1253class plugintype_qtype extends plugintype_base implements plugintype_interface {
1254
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 }
1261}
1262
1263/**
1264 * Class for question formats
1265 */
1266class plugintype_qformat extends plugintype_base implements plugintype_interface {
1267
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 }
1274}
1275
1276/**
1277 * Class for authentication plugins
1278 */
1279class plugintype_auth extends plugintype_base implements plugintype_interface {
1280
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;
1288
1289 if (in_array($this->name, array('nologin', 'manual'))) {
1290 // these two are always enabled and can't be disabled
1291 return null;
1292 }
1293
1294 if (is_null($enabled)) {
1295 $enabled = explode(',', $CFG->auth);
1296 }
1297
1298 return isset($enabled[$this->name]);
1299 }
1300
1301 /**
1302 * @see plugintype_interface::get_settings_url()
1303 */
1304 public function get_settings_url() {
1305
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 }
1312}
1313
1314/**
1315 * Class for enrolment plugins
1316 */
1317class plugintype_enrol extends plugintype_base implements plugintype_interface {
1318
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;
1331
1332 if (is_null($enabled)) {
1333 $enabled = explode(',', $CFG->enrol_plugins_enabled);
1334 }
1335
1336 return isset($enabled[$this->name]);
1337 }
1338
1339 /**
1340 * @see plugintype_interface::get_settings_url()
1341 */
1342 public function get_settings_url() {
1343
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 }
1350
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 }
1357}
1358
1359/**
1360 * Class for messaging processors
1361 */
1362class plugintype_message extends plugintype_base implements plugintype_interface {
1363
1364 /**
1365 * @see plugintype_interface::get_settings_url()
1366 */
1367 public function get_settings_url() {
1368
1369 if ($this->name === 'jabber') {
1370 return new moodle_url('/admin/settings.php', array('section' => 'jabber'));
1371 }
1372
1373 if ($this->name === 'email') {
1374 return new moodle_url('/admin/settings.php', array('section' => 'mail'));
1375 }
1376
1377 }
1378}
1379
1380/**
1381 * Class for repositories
1382 */
1383class plugintype_repository extends plugintype_base implements plugintype_interface {
1384
1385 /**
1386 * @see plugintype_interface::is_enabled()
1387 */
1388 public function is_enabled() {
1389
1390 $enabled = self::get_enabled_repositories();
1391
1392 return isset($enabled[$this->name]);
1393 }
1394
1395 /**
1396 * @see plugintype_interface::get_settings_url()
1397 */
1398 public function get_settings_url() {
1399
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 }
1406
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;
1416
1417 if (is_null($repositories) or $disablecache) {
1418 $repositories = $DB->get_records('repository', null, 'type', 'type,visible,sortorder');
1419 }
1420
1421 return $repositories;
1422 }
1423}
1424
1425/**
1426 * Class for portfolios
1427 */
1428class plugintype_portfolio extends plugintype_base implements plugintype_interface {
1429
1430 /**
1431 * @see plugintype_interface::is_enabled()
1432 */
1433 public function is_enabled() {
1434
1435 $enabled = self::get_enabled_portfolios();
1436
1437 return isset($enabled[$this->name]);
1438 }
1439
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;
1449
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 }
1463
1464 return $portfolios;
1465 }
1466}
1467
1468/**
1469 * Class for themes
1470 */
1471class plugintype_theme extends plugintype_base implements plugintype_interface {
1472
1473 /**
1474 * @see plugintype_interface::is_enabled()
1475 */
1476 public function is_enabled() {
1477 global $CFG;
1478
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 }
1486}
1487
1488/**
1489 * Class representing an MNet service
1490 */
1491class plugintype_mnetservice extends plugintype_base implements plugintype_interface {
1492
1493 /**
1494 * @see plugintype_interface::is_enabled()
1495 */
1496 public function is_enabled() {
1497 global $CFG;
1498
1499 if (empty($CFG->mnet_dispatcher_mode) || $CFG->mnet_dispatcher_mode !== 'strict') {
1500 return false;
1501 } else {
1502 return parent::is_enabled();
1503 }
1504 }
1505}
3cdfaeef
PS
1506
1507/**
1508 * Class for admin tool plugins
1509 */
1510class plugintype_tool extends plugintype_base implements plugintype_interface {
1511
1512 public function get_uninstall_url() {
1513 return new moodle_url('/admin/tools.php', array('delete' => $this->name, 'sesskey' => sesskey()));
1514 }
1515}