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