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