MDL-29474 fix qtype and qbehaviour plugins to use proper pluginname strings.
[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
c999d841
TH
343 'quizaccess' => array(
344 'delaybetweenattempts', 'ipaddress', 'numattempts', 'openclosedate',
345 'password', 'safebrowser', 'securewindow', 'timelimit'
346 ),
347
b9934a17 348 'report' => array(
4211dfa4 349 'backups', 'configlog', 'courseoverview',
17a14fbd 350 'log', 'questioninstances', 'security', 'stats'
b9934a17
DM
351 ),
352
353 'repository' => array(
354 'alfresco', 'boxnet', 'coursefiles', 'dropbox', 'filesystem',
355 'flickr', 'flickr_public', 'googledocs', 'local', 'merlot',
356 'picasa', 'recent', 's3', 'upload', 'url', 'user', 'webdav',
357 'wikimedia', 'youtube'
358 ),
359
99e86561
PS
360 'scormreport' => array(
361 'basic'
362 ),
363
b9934a17 364 'theme' => array(
bef9ad95
DM
365 'afterburner', 'anomaly', 'arialist', 'base', 'binarius',
366 'boxxie', 'brick', 'canvas', 'formal_white', 'formfactor',
367 'fusion', 'leatherbound', 'magazine', 'nimble', 'nonzero',
368 'overlay', 'serenity', 'sky_high', 'splash', 'standard',
369 'standardold'
b9934a17
DM
370 ),
371
11b24ce7 372 'tool' => array(
b703861f 373 'bloglevelupgrade', 'capability', 'customlang', 'dbtransfer', 'generator',
cff8fc8d 374 'health', 'innodb', 'langimport', 'multilangupgrade', 'profiling',
fab6f7b7 375 'qeupgradehelper', 'replace', 'spamcleaner', 'timezoneimport', 'unittest',
9597e00b 376 'uploaduser', 'unsuproles', 'xmldb'
11b24ce7
PS
377 ),
378
b9934a17
DM
379 'webservice' => array(
380 'amf', 'rest', 'soap', 'xmlrpc'
381 ),
382
383 'workshopallocation' => array(
384 'manual', 'random'
385 ),
386
387 'workshopeval' => array(
388 'best'
389 ),
390
391 'workshopform' => array(
392 'accumulative', 'comments', 'numerrors', 'rubric'
393 )
394 );
395
396 if (isset($standard_plugins[$type])) {
397 return $standard_plugins[$type];
398
399 } else {
400 return false;
401 }
402 }
403}
404
405/**
406 * All classes that represent a plugin of some type must implement this interface
407 */
408interface plugintype_interface {
409
410 /**
411 * Gathers and returns the information about all plugins of the given type
412 *
413 * Passing the parameter $typeclass allows us to reach the same effect as with the
414 * late binding in PHP 5.3. Once PHP 5.3 is required, we can refactor this to use
415 * {@example $plugin = new static();} instead of {@example $plugin = new $typeclass()}
416 *
417 * @param string $type the name of the plugintype, eg. mod, auth or workshopform
418 * @param string $typerootdir full path to the location of the plugin dir
419 * @param string $typeclass the name of the actually called class
420 * @return array of plugintype classes, indexed by the plugin name
421 */
422 public static function get_plugins($type, $typerootdir, $typeclass);
423
424 /**
425 * Sets $displayname property to a localized name of the plugin
426 *
427 * @return void
428 */
b8343e68 429 public function init_display_name();
b9934a17
DM
430
431 /**
432 * Sets $versiondisk property to a numerical value representing the
433 * version of the plugin's source code.
434 *
435 * If the value is null after calling this method, either the plugin
436 * does not use versioning (typically does not have any database
437 * data) or is missing from disk.
438 *
439 * @return void
440 */
b8343e68 441 public function load_disk_version();
b9934a17
DM
442
443 /**
444 * Sets $versiondb property to a numerical value representing the
445 * currently installed version of the plugin.
446 *
447 * If the value is null after calling this method, either the plugin
448 * does not use versioning (typically does not have any database
449 * data) or has not been installed yet.
450 *
451 * @return void
452 */
b8343e68 453 public function load_db_version();
b9934a17
DM
454
455 /**
456 * Sets $versionrequires property to a numerical value representing
457 * the version of Moodle core that this plugin requires.
458 *
459 * @return void
460 */
b8343e68 461 public function load_required_main_version();
b9934a17
DM
462
463 /**
464 * Sets $source property to one of plugin_manager::PLUGIN_SOURCE_xxx
465 * constants.
466 *
467 * If the property's value is null after calling this method, then
468 * the type of the plugin has not been recognized and you should throw
469 * an exception.
470 *
471 * @return void
472 */
b8343e68 473 public function init_is_standard();
b9934a17
DM
474
475 /**
476 * Returns true if the plugin is shipped with the official distribution
477 * of the current Moodle version, false otherwise.
478 *
479 * @return bool
480 */
481 public function is_standard();
482
483 /**
484 * Returns the status of the plugin
485 *
486 * @return string one of plugin_manager::PLUGIN_STATUS_xxx constants
487 */
488 public function get_status();
489
490 /**
491 * Returns the information about plugin availability
492 *
493 * True means that the plugin is enabled. False means that the plugin is
494 * disabled. Null means that the information is not available, or the
495 * plugin does not support configurable availability or the availability
496 * can not be changed.
497 *
498 * @return null|bool
499 */
500 public function is_enabled();
501
502 /**
503 * Returns the URL of the plugin settings screen
504 *
505 * Null value means that the plugin either does not have the settings screen
506 * or its location is not available via this library.
507 *
508 * @return null|moodle_url
509 */
510 public function get_settings_url();
511
512 /**
513 * Returns the URL of the screen where this plugin can be uninstalled
514 *
515 * Visiting that URL must be safe, that is a manual confirmation is needed
516 * for actual uninstallation of the plugin. Null value means that the
517 * plugin either does not support uninstallation, or does not require any
518 * database cleanup or the location of the screen is not available via this
519 * library.
520 *
521 * @return null|moodle_url
522 */
523 public function get_uninstall_url();
524
525 /**
526 * Returns relative directory of the plugin with heading '/'
527 *
528 * @example /mod/workshop
529 * @return string
530 */
531 public function get_dir();
473289a0
TH
532
533 /**
534 * Return the full path name of a file within the plugin.
535 * No check is made to see if the file exists.
536 * @param string $relativepath e.g. 'version.php'.
537 * @return string e.g. $CFG->dirroot . '/mod/quiz/version.php'.
538 */
539 public function full_path($relativepath);
b9934a17
DM
540}
541
542/**
543 * Defines public properties that all plugintype classes must have
544 * and provides default implementation of required methods.
545 */
546abstract class plugintype_base {
547
548 /** @var string the plugintype name, eg. mod, auth or workshopform */
549 public $type;
550 /** @var string full path to the location of all the plugins of this type */
551 public $typerootdir;
552 /** @var string the plugin name, eg. assignment, ldap */
553 public $name;
554 /** @var string the localized plugin name */
555 public $displayname;
556 /** @var string the plugin source, one of plugin_manager::PLUGIN_SOURCE_xxx constants */
557 public $source;
558 /** @var fullpath to the location of this plugin */
559 public $rootdir;
560 /** @var int|string the version of the plugin's source code */
561 public $versiondisk;
562 /** @var int|string the version of the installed plugin */
563 public $versiondb;
564 /** @var int|float|string required version of Moodle core */
565 public $versionrequires;
566 /** @var int number of instances of the plugin - not supported yet */
567 public $instances;
568 /** @var int order of the plugin among other plugins of the same type - not supported yet */
569 public $sortorder;
570
571 /**
572 * @see plugintype_interface::get_plugins()
573 */
574 public static function get_plugins($type, $typerootdir, $typeclass) {
575
576 // get the information about plugins at the disk
577 $plugins = get_plugin_list($type);
578 $ondisk = array();
579 foreach ($plugins as $pluginname => $pluginrootdir) {
580 $plugin = new $typeclass();
581 $plugin->type = $type;
582 $plugin->typerootdir = $typerootdir;
583 $plugin->name = $pluginname;
584 $plugin->rootdir = $pluginrootdir;
585
b8343e68
TH
586 $plugin->init_display_name();
587 $plugin->load_disk_version();
588 $plugin->load_db_version();
589 $plugin->load_required_main_version();
590 $plugin->init_is_standard();
b9934a17
DM
591
592 $ondisk[$pluginname] = $plugin;
593 }
594 return $ondisk;
595 }
596
597 /**
b8343e68 598 * @see plugintype_interface::init_display_name()
b9934a17 599 */
b8343e68 600 public function init_display_name() {
b9934a17
DM
601 if (! get_string_manager()->string_exists('pluginname', $this->type . '_' . $this->name)) {
602 $this->displayname = '[pluginname,' . $this->type . '_' . $this->name . ']';
603 } else {
604 $this->displayname = get_string('pluginname', $this->type . '_' . $this->name);
605 }
606 }
607
608 /**
473289a0 609 * @see plugintype_interface::full_path()
b9934a17 610 */
473289a0 611 public function full_path($relativepath) {
b9934a17 612 if (empty($this->rootdir)) {
473289a0 613 return '';
b9934a17 614 }
473289a0
TH
615 return $this->rootdir . '/' . $relativepath;
616 }
b9934a17 617
473289a0
TH
618 /**
619 * Load the data from version.php.
620 * @return object the data object defined in version.php.
621 */
622 protected function load_version_php() {
623 $versionfile = $this->full_path('version.php');
b9934a17 624
473289a0 625 $plugin = new stdClass();
b9934a17
DM
626 if (is_readable($versionfile)) {
627 include($versionfile);
b9934a17 628 }
473289a0 629 return $plugin;
b9934a17
DM
630 }
631
632 /**
473289a0 633 * @see plugintype_interface::load_disk_version()
b9934a17 634 */
473289a0
TH
635 public function load_disk_version() {
636 $plugin = $this->load_version_php();
637 if (isset($plugin->version)) {
638 $this->versiondisk = $plugin->version;
b9934a17
DM
639 }
640 }
641
642 /**
b8343e68 643 * @see plugintype_interface::load_required_main_version()
b9934a17 644 */
b8343e68 645 public function load_required_main_version() {
473289a0
TH
646 $plugin = $this->load_version_php();
647 if (isset($plugin->requires)) {
648 $this->versionrequires = $plugin->requires;
b9934a17 649 }
473289a0 650 }
b9934a17 651
473289a0
TH
652 /**
653 * @see plugintype_interface::load_db_version()
654 */
655 public function load_db_version() {
b9934a17 656
473289a0
TH
657 if ($ver = self::get_version_from_config_plugins($this->type . '_' . $this->name)) {
658 $this->versiondb = $ver;
b9934a17
DM
659 }
660 }
661
662 /**
b8343e68 663 * @see plugintype_interface::init_is_standard()
b9934a17 664 */
b8343e68 665 public function init_is_standard() {
b9934a17
DM
666
667 $standard = plugin_manager::standard_plugins_list($this->type);
668
669 if ($standard !== false) {
670 $standard = array_flip($standard);
671 if (isset($standard[$this->name])) {
672 $this->source = plugin_manager::PLUGIN_SOURCE_STANDARD;
673 } else {
674 $this->source = plugin_manager::PLUGIN_SOURCE_EXTENSION;
675 }
676 }
677 }
678
679 /**
680 * @see plugintype_interface::is_standard()
681 */
682 public function is_standard() {
683 return $this->source === plugin_manager::PLUGIN_SOURCE_STANDARD;
684 }
685
686 /**
687 * @see plugintype_interface::get_status()
688 */
689 public function get_status() {
690
691 if (is_null($this->versiondb) and is_null($this->versiondisk)) {
692 return plugin_manager::PLUGIN_STATUS_NODB;
693
694 } else if (is_null($this->versiondb) and !is_null($this->versiondisk)) {
695 return plugin_manager::PLUGIN_STATUS_NEW;
696
697 } else if (!is_null($this->versiondb) and is_null($this->versiondisk)) {
698 return plugin_manager::PLUGIN_STATUS_MISSING;
699
700 } else if ((string)$this->versiondb === (string)$this->versiondisk) {
701 return plugin_manager::PLUGIN_STATUS_UPTODATE;
702
703 } else if ($this->versiondb < $this->versiondisk) {
704 return plugin_manager::PLUGIN_STATUS_UPGRADE;
705
706 } else if ($this->versiondb > $this->versiondisk) {
707 return plugin_manager::PLUGIN_STATUS_DOWNGRADE;
708
709 } else {
710 // $version = pi(); and similar funny jokes - hopefully Donald E. Knuth will never contribute to Moodle ;-)
711 throw new coding_exception('Unable to determine plugin state, check the plugin versions');
712 }
713 }
714
715 /**
716 * @see plugintype_interface::is_enabled()
717 */
718 public function is_enabled() {
719 return null;
720 }
721
722 /**
723 * @see plugintype_interface::get_settings_url()
724 */
725 public function get_settings_url() {
726 return null;
727 }
728
729 /**
730 * @see plugintype_interface::get_uninstall_url()
731 */
732 public function get_uninstall_url() {
733 return null;
734 }
735
736 /**
737 * @see plugintype_interface::get_dir()
738 */
739 public function get_dir() {
740 global $CFG;
741
742 return substr($this->rootdir, strlen($CFG->dirroot));
743 }
744
745 /**
746 * Provides access to plugin versions from {config_plugins}
747 *
748 * @param string $plugin plugin name
749 * @param double $disablecache optional, defaults to false
750 * @return int|false the stored value or false if not found
751 */
752 protected function get_version_from_config_plugins($plugin, $disablecache=false) {
753 global $DB;
754 static $pluginversions = null;
755
756 if (is_null($pluginversions) or $disablecache) {
757 $pluginversions = $DB->get_records_menu('config_plugins', array('name' => 'version'), 'plugin', 'plugin,value');
758 }
759
760 if (!array_key_exists($plugin, $pluginversions)) {
761 return false;
762 }
763
764 return $pluginversions[$plugin];
765 }
766}
767
768/**
769 * General class for all plugin types that do not have their own class
770 */
771class plugintype_general extends plugintype_base implements plugintype_interface {
772
773}
774
775/**
776 * Class for page side blocks
777 */
778class plugintype_block extends plugintype_base implements plugintype_interface {
779
780 /**
781 * @see plugintype_interface::get_plugins()
782 */
783 public static function get_plugins($type, $typerootdir, $typeclass) {
784
785 // get the information about blocks at the disk
786 $blocks = parent::get_plugins($type, $typerootdir, $typeclass);
787
788 // add blocks missing from disk
789 $blocksinfo = self::get_blocks_info();
790 foreach ($blocksinfo as $blockname => $blockinfo) {
791 if (isset($blocks[$blockname])) {
792 continue;
793 }
794 $plugin = new $typeclass();
795 $plugin->type = $type;
796 $plugin->typerootdir = $typerootdir;
797 $plugin->name = $blockname;
798 $plugin->rootdir = null;
799 $plugin->displayname = $blockname;
800 $plugin->versiondb = $blockinfo->version;
b8343e68 801 $plugin->init_is_standard();
b9934a17
DM
802
803 $blocks[$blockname] = $plugin;
804 }
805
806 return $blocks;
807 }
808
809 /**
b8343e68 810 * @see plugintype_interface::init_display_name()
b9934a17 811 */
b8343e68 812 public function init_display_name() {
b9934a17
DM
813
814 if (get_string_manager()->string_exists('pluginname', 'block_' . $this->name)) {
815 $this->displayname = get_string('pluginname', 'block_' . $this->name);
816
817 } else if (($block = block_instance($this->name)) !== false) {
818 $this->displayname = $block->get_title();
819
820 } else {
b8343e68 821 parent::init_display_name();
b9934a17
DM
822 }
823 }
824
825 /**
b8343e68 826 * @see plugintype_interface::load_db_version()
b9934a17 827 */
b8343e68 828 public function load_db_version() {
b9934a17
DM
829 global $DB;
830
831 $blocksinfo = self::get_blocks_info();
832 if (isset($blocksinfo[$this->name]->version)) {
833 $this->versiondb = $blocksinfo[$this->name]->version;
834 }
835 }
836
837 /**
838 * @see plugintype_interface::is_enabled()
839 */
840 public function is_enabled() {
841
842 $blocksinfo = self::get_blocks_info();
843 if (isset($blocksinfo[$this->name]->visible)) {
844 if ($blocksinfo[$this->name]->visible) {
845 return true;
846 } else {
847 return false;
848 }
849 } else {
850 return parent::is_enabled();
851 }
852 }
853
854 /**
855 * @see plugintype_interface::get_settings_url()
856 */
857 public function get_settings_url() {
858
859 if (($block = block_instance($this->name)) === false) {
860 return parent::get_settings_url();
861
862 } else if ($block->has_config()) {
6740c605 863 if (file_exists($this->full_path('settings.php'))) {
b9934a17
DM
864 return new moodle_url('/admin/settings.php', array('section' => 'blocksetting' . $this->name));
865 } else {
866 $blocksinfo = self::get_blocks_info();
867 return new moodle_url('/admin/block.php', array('block' => $blocksinfo[$this->name]->id));
868 }
869
870 } else {
871 return parent::get_settings_url();
872 }
873 }
874
875 /**
876 * @see plugintype_interface::get_uninstall_url()
877 */
878 public function get_uninstall_url() {
879
880 $blocksinfo = self::get_blocks_info();
881 return new moodle_url('/admin/blocks.php', array('delete' => $blocksinfo[$this->name]->id, 'sesskey' => sesskey()));
882 }
883
884 /**
885 * Provides access to the records in {block} table
886 *
887 * @param bool $disablecache do not use internal static cache
888 * @return array array of stdClasses
889 */
890 protected static function get_blocks_info($disablecache=false) {
891 global $DB;
892 static $blocksinfocache = null;
893
894 if (is_null($blocksinfocache) or $disablecache) {
895 $blocksinfocache = $DB->get_records('block', null, 'name', 'name,id,version,visible');
896 }
897
898 return $blocksinfocache;
899 }
900}
901
902/**
903 * Class for text filters
904 */
905class plugintype_filter extends plugintype_base implements plugintype_interface {
906
907 /**
908 * @see plugintype_interface::get_plugins()
909 */
910 public static function get_plugins($type, $typerootdir, $typeclass) {
7c9b837e 911 global $CFG, $DB;
b9934a17
DM
912
913 $filters = array();
914
915 // get the list of filters from both /filter and /mod location
916 $installed = filter_get_all_installed();
917
918 foreach ($installed as $filterlegacyname => $displayname) {
919 $plugin = new $typeclass();
920 $plugin->type = $type;
921 $plugin->typerootdir = $typerootdir;
922 $plugin->name = self::normalize_legacy_name($filterlegacyname);
923 $plugin->rootdir = $CFG->dirroot . '/' . $filterlegacyname;
924 $plugin->displayname = $displayname;
925
b8343e68
TH
926 $plugin->load_disk_version();
927 $plugin->load_db_version();
928 $plugin->load_required_main_version();
929 $plugin->init_is_standard();
b9934a17
DM
930
931 $filters[$plugin->name] = $plugin;
932 }
933
b9934a17 934 $globalstates = self::get_global_states();
7c9b837e
DM
935
936 if ($DB->get_manager()->table_exists('filter_active')) {
937 // if we're upgrading from 1.9, the table does not exist yet
938 // if it does, make sure that all installed filters are registered
939 $needsreload = false;
940 foreach (array_keys($installed) as $filterlegacyname) {
941 if (!isset($globalstates[self::normalize_legacy_name($filterlegacyname)])) {
942 filter_set_global_state($filterlegacyname, TEXTFILTER_DISABLED);
943 $needsreload = true;
944 }
945 }
946 if ($needsreload) {
947 $globalstates = self::get_global_states(true);
b9934a17 948 }
b9934a17
DM
949 }
950
951 // make sure that all registered filters are installed, just in case
952 foreach ($globalstates as $name => $info) {
953 if (!isset($filters[$name])) {
954 // oops, there is a record in filter_active but the filter is not installed
955 $plugin = new $typeclass();
956 $plugin->type = $type;
957 $plugin->typerootdir = $typerootdir;
958 $plugin->name = $name;
959 $plugin->rootdir = $CFG->dirroot . '/' . $info->legacyname;
960 $plugin->displayname = $info->legacyname;
961
b8343e68 962 $plugin->load_db_version();
b9934a17
DM
963
964 if (is_null($plugin->versiondb)) {
965 // this is a hack to stimulate 'Missing from disk' error
966 // because $plugin->versiondisk will be null !== false
967 $plugin->versiondb = false;
968 }
969
970 $filters[$plugin->name] = $plugin;
971 }
972 }
973
974 return $filters;
975 }
976
977 /**
b8343e68 978 * @see plugintype_interface::init_display_name()
b9934a17 979 */
b8343e68 980 public function init_display_name() {
b9934a17
DM
981 // do nothing, the name is set in self::get_plugins()
982 }
983
984 /**
473289a0 985 * @see plugintype_base::load_version_php().
b9934a17 986 */
473289a0 987 protected function load_version_php() {
b9934a17 988 if (strpos($this->name, 'mod_') === 0) {
473289a0
TH
989 // filters bundled with modules do not have a version.php and so
990 // do not provide their own versioning information.
991 return new stdClass();
b9934a17 992 }
473289a0 993 return parent::load_version_php();
b9934a17
DM
994 }
995
996 /**
997 * @see plugintype_interface::is_enabled()
998 */
999 public function is_enabled() {
1000
1001 $globalstates = self::get_global_states();
1002
1003 foreach ($globalstates as $filterlegacyname => $info) {
1004 $name = self::normalize_legacy_name($filterlegacyname);
1005 if ($name === $this->name) {
1006 if ($info->active == TEXTFILTER_DISABLED) {
1007 return false;
1008 } else {
1009 // it may be 'On' or 'Off, but available'
1010 return null;
1011 }
1012 }
1013 }
1014
1015 return null;
1016 }
1017
1018 /**
1019 * @see plugintype_interface::get_settings_url()
1020 */
1021 public function get_settings_url() {
1022
1023 $globalstates = self::get_global_states();
1024 $legacyname = $globalstates[$this->name]->legacyname;
1025 if (filter_has_global_settings($legacyname)) {
1026 return new moodle_url('/admin/settings.php', array('section' => 'filtersetting' . str_replace('/', '', $legacyname)));
1027 } else {
1028 return null;
1029 }
1030 }
1031
1032 /**
1033 * @see plugintype_interface::get_uninstall_url()
1034 */
1035 public function get_uninstall_url() {
1036
1037 if (strpos($this->name, 'mod_') === 0) {
1038 return null;
1039 } else {
1040 $globalstates = self::get_global_states();
1041 $legacyname = $globalstates[$this->name]->legacyname;
1042 return new moodle_url('/admin/filters.php', array('sesskey' => sesskey(), 'filterpath' => $legacyname, 'action' => 'delete'));
1043 }
1044 }
1045
1046 /**
1047 * Convert legacy filter names like 'filter/foo' or 'mod/bar' into frankenstyle
1048 *
1049 * @param string $legacyfiltername legacy filter name
1050 * @return string frankenstyle-like name
1051 */
1052 protected static function normalize_legacy_name($legacyfiltername) {
1053
1054 $name = str_replace('/', '_', $legacyfiltername);
1055 if (strpos($name, 'filter_') === 0) {
1056 $name = substr($name, 7);
1057 if (empty($name)) {
1058 throw new coding_exception('Unable to determine filter name: ' . $legacyfiltername);
1059 }
1060 }
1061
1062 return $name;
1063 }
1064
1065 /**
1066 * Provides access to the results of {@link filter_get_global_states()}
1067 * but indexed by the normalized filter name
1068 *
1069 * The legacy filter name is available as ->legacyname property.
1070 *
1071 * @param bool $disablecache
1072 * @return array
1073 */
1074 protected static function get_global_states($disablecache=false) {
1075 global $DB;
1076 static $globalstatescache = null;
1077
1078 if ($disablecache or is_null($globalstatescache)) {
1079
1080 if (!$DB->get_manager()->table_exists('filter_active')) {
1081 // we're upgrading from 1.9 and the table used by {@link filter_get_global_states()}
1082 // does not exist yet
1083 $globalstatescache = array();
1084
1085 } else {
1086 foreach (filter_get_global_states() as $legacyname => $info) {
1087 $name = self::normalize_legacy_name($legacyname);
1088 $filterinfo = new stdClass();
1089 $filterinfo->legacyname = $legacyname;
1090 $filterinfo->active = $info->active;
1091 $filterinfo->sortorder = $info->sortorder;
1092 $globalstatescache[$name] = $filterinfo;
1093 }
1094 }
1095 }
1096
1097 return $globalstatescache;
1098 }
1099}
1100
1101/**
1102 * Class for activity modules
1103 */
1104class plugintype_mod extends plugintype_base implements plugintype_interface {
1105
1106 /**
1107 * @see plugintype_interface::get_plugins()
1108 */
1109 public static function get_plugins($type, $typerootdir, $typeclass) {
1110
1111 // get the information about plugins at the disk
1112 $modules = parent::get_plugins($type, $typerootdir, $typeclass);
1113
1114 // add modules missing from disk
1115 $modulesinfo = self::get_modules_info();
1116 foreach ($modulesinfo as $modulename => $moduleinfo) {
1117 if (isset($modules[$modulename])) {
1118 continue;
1119 }
1120 $plugin = new $typeclass();
1121 $plugin->type = $type;
1122 $plugin->typerootdir = $typerootdir;
1123 $plugin->name = $modulename;
1124 $plugin->rootdir = null;
1125 $plugin->displayname = $modulename;
1126 $plugin->versiondb = $moduleinfo->version;
b8343e68 1127 $plugin->init_is_standard();
b9934a17
DM
1128
1129 $modules[$modulename] = $plugin;
1130 }
1131
1132 return $modules;
1133 }
1134
1135 /**
b8343e68 1136 * @see plugintype_interface::init_display_name()
b9934a17 1137 */
b8343e68 1138 public function init_display_name() {
b9934a17
DM
1139 if (get_string_manager()->string_exists('pluginname', $this->type . '_' . $this->name)) {
1140 $this->displayname = get_string('pluginname', $this->type . '_' . $this->name);
1141 } else {
1142 $this->displayname = get_string('modulename', $this->type . '_' . $this->name);
1143 }
1144 }
1145
1146 /**
473289a0
TH
1147 * Load the data from version.php.
1148 * @return object the data object defined in version.php.
b9934a17 1149 */
473289a0
TH
1150 protected function load_version_php() {
1151 $versionfile = $this->full_path('version.php');
b9934a17 1152
473289a0 1153 $module = new stdClass();
b9934a17
DM
1154 if (is_readable($versionfile)) {
1155 include($versionfile);
b9934a17 1156 }
473289a0 1157 return $module;
b9934a17
DM
1158 }
1159
1160 /**
b8343e68 1161 * @see plugintype_interface::load_db_version()
b9934a17 1162 */
b8343e68 1163 public function load_db_version() {
b9934a17
DM
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
b9934a17
DM
1172 /**
1173 * @see plugintype_interface::is_enabled()
1174 */
1175 public function is_enabled() {
1176
1177 $modulesinfo = self::get_modules_info();
1178 if (isset($modulesinfo[$this->name]->visible)) {
1179 if ($modulesinfo[$this->name]->visible) {
1180 return true;
1181 } else {
1182 return false;
1183 }
1184 } else {
1185 return parent::is_enabled();
1186 }
1187 }
1188
1189 /**
1190 * @see plugintype_interface::get_settings_url()
1191 */
1192 public function get_settings_url() {
1193
6740c605 1194 if (file_exists($this->full_path('settings.php')) or file_exists($this->full_path('settingstree.php'))) {
b9934a17
DM
1195 return new moodle_url('/admin/settings.php', array('section' => 'modsetting' . $this->name));
1196 } else {
1197 return parent::get_settings_url();
1198 }
1199 }
1200
1201 /**
1202 * @see plugintype_interface::get_uninstall_url()
1203 */
1204 public function get_uninstall_url() {
1205
1206 if ($this->name !== 'forum') {
1207 return new moodle_url('/admin/modules.php', array('delete' => $this->name, 'sesskey' => sesskey()));
1208 } else {
1209 return null;
1210 }
1211 }
1212
1213 /**
1214 * Provides access to the records in {modules} table
1215 *
1216 * @param bool $disablecache do not use internal static cache
1217 * @return array array of stdClasses
1218 */
1219 protected static function get_modules_info($disablecache=false) {
1220 global $DB;
1221 static $modulesinfocache = null;
1222
1223 if (is_null($modulesinfocache) or $disablecache) {
1224 $modulesinfocache = $DB->get_records('modules', null, 'name', 'name,id,version,visible');
1225 }
1226
1227 return $modulesinfocache;
1228 }
1229}
1230
1231/**
1232 * Class for question types
1233 */
1234class plugintype_qtype extends plugintype_base implements plugintype_interface {
1235
1236 /**
b8343e68 1237 * @see plugintype_interface::init_display_name()
b9934a17 1238 */
b8343e68 1239 public function init_display_name() {
44a7f384
TH
1240 if (get_string_manager()->string_exists('pluginname', $this->type . '_' . $this->name)) {
1241 $this->displayname = get_string('pluginname', $this->type . '_' . $this->name);
1242 } else {
1243 $this->displayname = get_string($this->name, 'qtype_' . $this->name);
1244 }
b9934a17
DM
1245 }
1246}
1247
1248/**
1249 * Class for question formats
1250 */
1251class plugintype_qformat extends plugintype_base implements plugintype_interface {
1252
1253 /**
b8343e68 1254 * @see plugintype_interface::init_display_name()
b9934a17 1255 */
b8343e68 1256 public function init_display_name() {
44a7f384
TH
1257 if (get_string_manager()->string_exists('pluginname', $this->type . '_' . $this->name)) {
1258 $this->displayname = get_string('pluginname', $this->type . '_' . $this->name);
1259 } else {
1260 $this->displayname = get_string($this->name, 'qformat_' . $this->name);
1261 }
b9934a17
DM
1262 }
1263}
1264
1265/**
1266 * Class for authentication plugins
1267 */
1268class plugintype_auth extends plugintype_base implements plugintype_interface {
1269
1270 /**
1271 * @see plugintype_interface::is_enabled()
1272 */
1273 public function is_enabled() {
1274 global $CFG;
1275 /** @var null|array list of enabled authentication plugins */
1276 static $enabled = null;
1277
1278 if (in_array($this->name, array('nologin', 'manual'))) {
1279 // these two are always enabled and can't be disabled
1280 return null;
1281 }
1282
1283 if (is_null($enabled)) {
1284 $enabled = explode(',', $CFG->auth);
1285 }
1286
1287 return isset($enabled[$this->name]);
1288 }
1289
1290 /**
1291 * @see plugintype_interface::get_settings_url()
1292 */
1293 public function get_settings_url() {
6740c605 1294 if (file_exists($this->full_path('settings.php'))) {
b9934a17
DM
1295 return new moodle_url('/admin/settings.php', array('section' => 'authsetting' . $this->name));
1296 } else {
1297 return new moodle_url('/admin/auth_config.php', array('auth' => $this->name));
1298 }
1299 }
1300}
1301
1302/**
1303 * Class for enrolment plugins
1304 */
1305class plugintype_enrol extends plugintype_base implements plugintype_interface {
1306
1307 /**
1308 * We do not actually need whole enrolment classes here so we do not call
1309 * {@link enrol_get_plugins()}. Note that this may produce slightly different
1310 * results, for example if the enrolment plugin does not contain lib.php
1311 * but it is listed in $CFG->enrol_plugins_enabled
1312 *
1313 * @see plugintype_interface::is_enabled()
1314 */
1315 public function is_enabled() {
1316 global $CFG;
1317 /** @var null|array list of enabled enrolment plugins */
1318 static $enabled = null;
1319
1320 if (is_null($enabled)) {
1321 $enabled = explode(',', $CFG->enrol_plugins_enabled);
1322 }
1323
1324 return isset($enabled[$this->name]);
1325 }
1326
1327 /**
1328 * @see plugintype_interface::get_settings_url()
1329 */
1330 public function get_settings_url() {
1331
6740c605 1332 if ($this->is_enabled() or file_exists($this->full_path('settings.php'))) {
b9934a17
DM
1333 return new moodle_url('/admin/settings.php', array('section' => 'enrolsettings' . $this->name));
1334 } else {
1335 return parent::get_settings_url();
1336 }
1337 }
1338
1339 /**
1340 * @see plugintype_interface::get_uninstall_url()
1341 */
1342 public function get_uninstall_url() {
1343 return new moodle_url('/admin/enrol.php', array('action' => 'uninstall', 'enrol' => $this->name, 'sesskey' => sesskey()));
1344 }
1345}
1346
1347/**
1348 * Class for messaging processors
1349 */
1350class plugintype_message extends plugintype_base implements plugintype_interface {
1351
1352 /**
1353 * @see plugintype_interface::get_settings_url()
1354 */
1355 public function get_settings_url() {
1356
6740c605
TH
1357 if (file_exists($this->full_path('settings.php')) or file_exists($this->full_path('settingstree.php'))) {
1358 return new moodle_url('/admin/settings.php', array('section' => 'messagesetting' . $this->name));
1359 } else {
1360 return parent::get_settings_url();
b9934a17 1361 }
b9934a17
DM
1362 }
1363}
1364
1365/**
1366 * Class for repositories
1367 */
1368class plugintype_repository extends plugintype_base implements plugintype_interface {
1369
1370 /**
1371 * @see plugintype_interface::is_enabled()
1372 */
1373 public function is_enabled() {
1374
1375 $enabled = self::get_enabled_repositories();
1376
1377 return isset($enabled[$this->name]);
1378 }
1379
1380 /**
1381 * @see plugintype_interface::get_settings_url()
1382 */
1383 public function get_settings_url() {
1384
1385 if ($this->is_enabled()) {
1386 return new moodle_url('/admin/repository.php', array('sesskey' => sesskey(), 'action' => 'edit', 'repos' => $this->name));
1387 } else {
1388 return parent::get_settings_url();
1389 }
1390 }
1391
1392 /**
1393 * Provides access to the records in {repository} table
1394 *
1395 * @param bool $disablecache do not use internal static cache
1396 * @return array array of stdClasses
1397 */
1398 protected static function get_enabled_repositories($disablecache=false) {
1399 global $DB;
1400 static $repositories = null;
1401
1402 if (is_null($repositories) or $disablecache) {
1403 $repositories = $DB->get_records('repository', null, 'type', 'type,visible,sortorder');
1404 }
1405
1406 return $repositories;
1407 }
1408}
1409
1410/**
1411 * Class for portfolios
1412 */
1413class plugintype_portfolio extends plugintype_base implements plugintype_interface {
1414
1415 /**
1416 * @see plugintype_interface::is_enabled()
1417 */
1418 public function is_enabled() {
1419
1420 $enabled = self::get_enabled_portfolios();
1421
1422 return isset($enabled[$this->name]);
1423 }
1424
1425 /**
1426 * Provides access to the records in {portfolio_instance} table
1427 *
1428 * @param bool $disablecache do not use internal static cache
1429 * @return array array of stdClasses
1430 */
1431 protected static function get_enabled_portfolios($disablecache=false) {
1432 global $DB;
1433 static $portfolios = null;
1434
1435 if (is_null($portfolios) or $disablecache) {
1436 $portfolios = array();
1437 $instances = $DB->get_recordset('portfolio_instance', null, 'plugin');
1438 foreach ($instances as $instance) {
1439 if (isset($portfolios[$instance->plugin])) {
1440 if ($instance->visible) {
1441 $portfolios[$instance->plugin]->visible = $instance->visible;
1442 }
1443 } else {
1444 $portfolios[$instance->plugin] = $instance;
1445 }
1446 }
1447 }
1448
1449 return $portfolios;
1450 }
1451}
1452
1453/**
1454 * Class for themes
1455 */
1456class plugintype_theme extends plugintype_base implements plugintype_interface {
1457
1458 /**
1459 * @see plugintype_interface::is_enabled()
1460 */
1461 public function is_enabled() {
1462 global $CFG;
1463
1464 if ((!empty($CFG->theme) and $CFG->theme === $this->name) or
1465 (!empty($CFG->themelegacy) and $CFG->themelegacy === $this->name)) {
1466 return true;
1467 } else {
1468 return parent::is_enabled();
1469 }
1470 }
1471}
1472
1473/**
1474 * Class representing an MNet service
1475 */
1476class plugintype_mnetservice extends plugintype_base implements plugintype_interface {
1477
1478 /**
1479 * @see plugintype_interface::is_enabled()
1480 */
1481 public function is_enabled() {
1482 global $CFG;
1483
1484 if (empty($CFG->mnet_dispatcher_mode) || $CFG->mnet_dispatcher_mode !== 'strict') {
1485 return false;
1486 } else {
1487 return parent::is_enabled();
1488 }
1489 }
1490}
3cdfaeef
PS
1491
1492/**
1493 * Class for admin tool plugins
1494 */
1495class plugintype_tool extends plugintype_base implements plugintype_interface {
1496
1497 public function get_uninstall_url() {
1498 return new moodle_url('/admin/tools.php', array('delete' => $this->name, 'sesskey' => sesskey()));
1499 }
1500}