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