Merge branch 'MDL-23219_22' of git://github.com/timhunt/moodle into MOODLE_22_STABLE
[moodle.git] / lib / pluginlib.php
blobb5ace5c898b233776ae870f7d042c53c4f87e79e
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.
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/>.
17 /**
18 * Defines classes used for plugins management
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.
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
30 defined('MOODLE_INTERNAL') || die();
32 /**
33 * Singleton class providing general plugins management functionality
35 class plugin_manager {
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';
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 standard plugin is about to be deleted */
51 const PLUGIN_STATUS_DELETE = 'delete';
52 /** the version at the disk is lower than the one already installed */
53 const PLUGIN_STATUS_DOWNGRADE = 'downgrade';
54 /** the plugin is installed but missing from disk */
55 const PLUGIN_STATUS_MISSING = 'missing';
57 /** @var plugin_manager holds the singleton instance */
58 protected static $singletoninstance;
59 /** @var array of raw plugins information */
60 protected $pluginsinfo = null;
61 /** @var array of raw subplugins information */
62 protected $subpluginsinfo = null;
64 /**
65 * Direct initiation not allowed, use the factory method {@link self::instance()}
67 * @todo we might want to specify just a single plugin type to work with
69 protected function __construct() {
70 $this->get_plugins(true);
73 /**
74 * Sorry, this is singleton
76 protected function __clone() {
79 /**
80 * Factory method for this class
82 * @return plugin_manager the singleton instance
84 public static function instance() {
85 global $CFG;
87 if (is_null(self::$singletoninstance)) {
88 self::$singletoninstance = new self();
90 return self::$singletoninstance;
93 /**
94 * Returns a tree of known plugins and information about them
96 * @param bool $disablecache force reload, cache can be used otherwise
97 * @return array 2D array. The first keys are plugin type names (e.g. qtype);
98 * the second keys are the plugin local name (e.g. multichoice); and
99 * the values are the corresponding {@link plugin_information} objects.
101 public function get_plugins($disablecache=false) {
103 if ($disablecache or is_null($this->pluginsinfo)) {
104 $this->pluginsinfo = array();
105 $plugintypes = get_plugin_types();
106 foreach ($plugintypes as $plugintype => $plugintyperootdir) {
107 if (in_array($plugintype, array('base', 'general'))) {
108 throw new coding_exception('Illegal usage of reserved word for plugin type');
110 if (class_exists('plugintype_' . $plugintype)) {
111 $plugintypeclass = 'plugintype_' . $plugintype;
112 } else {
113 $plugintypeclass = 'plugintype_general';
115 if (!in_array('plugin_information', class_implements($plugintypeclass))) {
116 throw new coding_exception('Class ' . $plugintypeclass . ' must implement plugin_information');
118 $plugins = call_user_func(array($plugintypeclass, 'get_plugins'), $plugintype, $plugintyperootdir, $plugintypeclass);
119 $this->pluginsinfo[$plugintype] = $plugins;
123 return $this->pluginsinfo;
127 * Returns list of plugins that define their subplugins and the information
128 * about them from the db/subplugins.php file.
130 * At the moment, only activity modules can define subplugins.
132 * @param bool $disablecache force reload, cache can be used otherwise
133 * @return array with keys like 'mod_quiz', and values the data from the
134 * corresponding db/subplugins.php file.
136 public function get_subplugins($disablecache=false) {
138 if ($disablecache or is_null($this->subpluginsinfo)) {
139 $this->subpluginsinfo = array();
140 $mods = get_plugin_list('mod');
141 foreach ($mods as $mod => $moddir) {
142 $modsubplugins = array();
143 if (file_exists($moddir . '/db/subplugins.php')) {
144 include($moddir . '/db/subplugins.php');
145 foreach ($subplugins as $subplugintype => $subplugintyperootdir) {
146 $subplugin = new stdClass();
147 $subplugin->type = $subplugintype;
148 $subplugin->typerootdir = $subplugintyperootdir;
149 $modsubplugins[$subplugintype] = $subplugin;
151 $this->subpluginsinfo['mod_' . $mod] = $modsubplugins;
156 return $this->subpluginsinfo;
160 * Returns the name of the plugin that defines the given subplugin type
162 * If the given subplugin type is not actually a subplugin, returns false.
164 * @param string $subplugintype the name of subplugin type, eg. workshopform or quiz
165 * @return false|string the name of the parent plugin, eg. mod_workshop
167 public function get_parent_of_subplugin($subplugintype) {
169 $parent = false;
170 foreach ($this->get_subplugins() as $pluginname => $subplugintypes) {
171 if (isset($subplugintypes[$subplugintype])) {
172 $parent = $pluginname;
173 break;
177 return $parent;
181 * Returns a localized name of a given plugin
183 * @param string $plugin name of the plugin, eg mod_workshop or auth_ldap
184 * @return string
186 public function plugin_name($plugin) {
187 list($type, $name) = normalize_component($plugin);
188 return $this->pluginsinfo[$type][$name]->displayname;
192 * Returns a localized name of a plugin type in plural form
194 * Most plugin types define their names in core_plugin lang file. In case of subplugins,
195 * we try to ask the parent plugin for the name. In the worst case, we will return
196 * the value of the passed $type parameter.
198 * @param string $type the type of the plugin, e.g. mod or workshopform
199 * @return string
201 public function plugintype_name_plural($type) {
203 if (get_string_manager()->string_exists('type_' . $type . '_plural', 'core_plugin')) {
204 // for most plugin types, their names are defined in core_plugin lang file
205 return get_string('type_' . $type . '_plural', 'core_plugin');
207 } else if ($parent = $this->get_parent_of_subplugin($type)) {
208 // if this is a subplugin, try to ask the parent plugin for the name
209 if (get_string_manager()->string_exists('subplugintype_' . $type . '_plural', $parent)) {
210 return $this->plugin_name($parent) . ' / ' . get_string('subplugintype_' . $type . '_plural', $parent);
211 } else {
212 return $this->plugin_name($parent) . ' / ' . $type;
215 } else {
216 return $type;
221 * @param string $component frankenstyle component name.
222 * @return plugin_information|null the corresponding plugin information.
224 public function get_plugin_info($component) {
225 list($type, $name) = normalize_component($component);
226 $plugins = $this->get_plugins();
227 if (isset($plugins[$type][$name])) {
228 return $plugins[$type][$name];
229 } else {
230 return null;
235 * Get a list of any other pluings that require this one.
236 * @param string $component frankenstyle component name.
237 * @return array of frankensyle component names that require this one.
239 public function other_plugins_that_require($component) {
240 $others = array();
241 foreach ($this->get_plugins() as $type => $plugins) {
242 foreach ($plugins as $plugin) {
243 $required = $plugin->get_other_required_plugins();
244 if (isset($required[$component])) {
245 $others[] = $plugin->component;
249 return $others;
253 * Check a dependencies list against the list of installed plugins.
254 * @param array $dependencies compenent name to required version or ANY_VERSION.
255 * @return bool true if all the dependencies are satisfied.
257 public function are_dependencies_satisfied($dependencies) {
258 foreach ($dependencies as $component => $requiredversion) {
259 $otherplugin = $this->get_plugin_info($component);
260 if (is_null($otherplugin)) {
261 return false;
264 if ($requiredversion != ANY_VERSION and $otherplugin->versiondisk < $requiredversion) {
265 return false;
269 return true;
273 * Checks all dependencies for all installed plugins. Used by install and upgrade.
274 * @param int $moodleversion the version from version.php.
275 * @return bool true if all the dependencies are satisfied for all plugins.
277 public function all_plugins_ok($moodleversion) {
278 foreach ($this->get_plugins() as $type => $plugins) {
279 foreach ($plugins as $plugin) {
281 if (!empty($plugin->versionrequires) && $plugin->versionrequires > $moodleversion) {
282 return false;
285 if (!$this->are_dependencies_satisfied($plugin->get_other_required_plugins())) {
286 return false;
291 return true;
295 * Defines a list of all plugins that were originally shipped in the standard Moodle distribution,
296 * but are not anymore and are deleted during upgrades.
298 * The main purpose of this list is to hide missing plugins during upgrade.
300 * @param string $type plugin type
301 * @param string $name plugin name
302 * @return bool
304 public static function is_deleted_standard_plugin($type, $name) {
305 static $plugins = array(
306 'block' => array('admin', 'admin_tree', 'loancalc', 'search'),
307 'filter' => array('mod_data', 'mod_glossary'),
310 if (!isset($plugins[$type])) {
311 return false;
313 return in_array($name, $plugins[$type]);
317 * Defines a white list of all plugins shipped in the standard Moodle distribution
319 * @param string $type
320 * @return false|array array of standard plugins or false if the type is unknown
322 public static function standard_plugins_list($type) {
323 static $standard_plugins = array(
325 'assignment' => array(
326 'offline', 'online', 'upload', 'uploadsingle'
329 'auth' => array(
330 'cas', 'db', 'email', 'fc', 'imap', 'ldap', 'manual', 'mnet',
331 'nntp', 'nologin', 'none', 'pam', 'pop3', 'radius',
332 'shibboleth', 'webservice'
335 'block' => array(
336 'activity_modules', 'admin_bookmarks', 'blog_menu',
337 'blog_recent', 'blog_tags', 'calendar_month',
338 'calendar_upcoming', 'comments', 'community',
339 'completionstatus', 'course_list', 'course_overview',
340 'course_summary', 'feedback', 'glossary_random', 'html',
341 'login', 'mentees', 'messages', 'mnet_hosts', 'myprofile',
342 'navigation', 'news_items', 'online_users', 'participants',
343 'private_files', 'quiz_results', 'recent_activity',
344 'rss_client', 'search_forums', 'section_links',
345 'selfcompletion', 'settings', 'site_main_menu',
346 'social_activities', 'tag_flickr', 'tag_youtube', 'tags'
349 'coursereport' => array(
350 //deprecated!
353 'datafield' => array(
354 'checkbox', 'date', 'file', 'latlong', 'menu', 'multimenu',
355 'number', 'picture', 'radiobutton', 'text', 'textarea', 'url'
358 'datapreset' => array(
359 'imagegallery'
362 'editor' => array(
363 'textarea', 'tinymce'
366 'enrol' => array(
367 'authorize', 'category', 'cohort', 'database', 'flatfile',
368 'guest', 'imsenterprise', 'ldap', 'manual', 'meta', 'mnet',
369 'paypal', 'self'
372 'filter' => array(
373 'activitynames', 'algebra', 'censor', 'emailprotect',
374 'emoticon', 'mediaplugin', 'multilang', 'tex', 'tidy',
375 'urltolink', 'data', 'glossary'
378 'format' => array(
379 'scorm', 'social', 'topics', 'weeks'
382 'gradeexport' => array(
383 'ods', 'txt', 'xls', 'xml'
386 'gradeimport' => array(
387 'csv', 'xml'
390 'gradereport' => array(
391 'grader', 'outcomes', 'overview', 'user'
394 'gradingform' => array(
395 'rubric'
398 'local' => array(
401 'message' => array(
402 'email', 'jabber', 'popup'
405 'mnetservice' => array(
406 'enrol'
409 'mod' => array(
410 'assignment', 'chat', 'choice', 'data', 'feedback', 'folder',
411 'forum', 'glossary', 'imscp', 'label', 'lesson', 'lti', 'page',
412 'quiz', 'resource', 'scorm', 'survey', 'url', 'wiki', 'workshop'
415 'plagiarism' => array(
418 'portfolio' => array(
419 'boxnet', 'download', 'flickr', 'googledocs', 'mahara', 'picasa'
422 'profilefield' => array(
423 'checkbox', 'datetime', 'menu', 'text', 'textarea'
426 'qbehaviour' => array(
427 'adaptive', 'adaptivenopenalty', 'deferredcbm',
428 'deferredfeedback', 'immediatecbm', 'immediatefeedback',
429 'informationitem', 'interactive', 'interactivecountback',
430 'manualgraded', 'missing'
433 'qformat' => array(
434 'aiken', 'blackboard', 'blackboard_six', 'examview', 'gift',
435 'learnwise', 'missingword', 'multianswer', 'webct',
436 'xhtml', 'xml'
439 'qtype' => array(
440 'calculated', 'calculatedmulti', 'calculatedsimple',
441 'description', 'essay', 'match', 'missingtype', 'multianswer',
442 'multichoice', 'numerical', 'random', 'randomsamatch',
443 'shortanswer', 'truefalse'
446 'quiz' => array(
447 'grading', 'overview', 'responses', 'statistics'
450 'quizaccess' => array(
451 'delaybetweenattempts', 'ipaddress', 'numattempts', 'openclosedate',
452 'password', 'safebrowser', 'securewindow', 'timelimit'
455 'report' => array(
456 'backups', 'completion', 'configlog', 'courseoverview',
457 'log', 'loglive', 'outline', 'participation', 'progress', 'questioninstances', 'security', 'stats'
460 'repository' => array(
461 'alfresco', 'boxnet', 'coursefiles', 'dropbox', 'filesystem',
462 'flickr', 'flickr_public', 'googledocs', 'local', 'merlot',
463 'picasa', 'recent', 's3', 'upload', 'url', 'user', 'webdav',
464 'wikimedia', 'youtube'
467 'scormreport' => array(
468 'basic',
469 'interactions'
472 'theme' => array(
473 'afterburner', 'anomaly', 'arialist', 'base', 'binarius',
474 'boxxie', 'brick', 'canvas', 'formal_white', 'formfactor',
475 'fusion', 'leatherbound', 'magazine', 'mymobile', 'nimble',
476 'nonzero', 'overlay', 'serenity', 'sky_high', 'splash',
477 'standard', 'standardold'
480 'tool' => array(
481 'bloglevelupgrade', 'capability', 'customlang', 'dbtransfer', 'generator',
482 'health', 'innodb', 'langimport', 'multilangupgrade', 'profiling',
483 'qeupgradehelper', 'replace', 'spamcleaner', 'timezoneimport', 'unittest',
484 'uploaduser', 'unsuproles', 'xmldb'
487 'webservice' => array(
488 'amf', 'rest', 'soap', 'xmlrpc'
491 'workshopallocation' => array(
492 'manual', 'random'
495 'workshopeval' => array(
496 'best'
499 'workshopform' => array(
500 'accumulative', 'comments', 'numerrors', 'rubric'
504 if (isset($standard_plugins[$type])) {
505 return $standard_plugins[$type];
507 } else {
508 return false;
514 * Interface for making information about a plugin available.
516 * Note that most of the useful information is made available in pubic fields,
517 * which cannot be documented in this interface. See the field definitions on
518 * {@link plugintype_base} to find out what information is available.
520 * @property-read string component the component name, type_name
522 interface plugin_information {
525 * Gathers and returns the information about all plugins of the given type
527 * Passing the parameter $typeclass allows us to reach the same effect as with the
528 * late binding in PHP 5.3. Once PHP 5.3 is required, we can refactor this to use
529 * {@example $plugin = new static();} instead of {@example $plugin = new $typeclass()}
531 * @param string $type the name of the plugintype, eg. mod, auth or workshopform
532 * @param string $typerootdir full path to the location of the plugin dir
533 * @param string $typeclass the name of the actually called class
534 * @return array of plugintype classes, indexed by the plugin name
536 public static function get_plugins($type, $typerootdir, $typeclass);
539 * Sets $displayname property to a localized name of the plugin
541 * @return void
543 public function init_display_name();
546 * Sets $versiondisk property to a numerical value representing the
547 * version of the plugin's source code.
549 * If the value is null after calling this method, either the plugin
550 * does not use versioning (typically does not have any database
551 * data) or is missing from disk.
553 * @return void
555 public function load_disk_version();
558 * Sets $versiondb property to a numerical value representing the
559 * currently installed version of the plugin.
561 * If the value is null after calling this method, either the plugin
562 * does not use versioning (typically does not have any database
563 * data) or has not been installed yet.
565 * @return void
567 public function load_db_version();
570 * Sets $versionrequires property to a numerical value representing
571 * the version of Moodle core that this plugin requires.
573 * @return void
575 public function load_required_main_version();
578 * Sets $source property to one of plugin_manager::PLUGIN_SOURCE_xxx
579 * constants.
581 * If the property's value is null after calling this method, then
582 * the type of the plugin has not been recognized and you should throw
583 * an exception.
585 * @return void
587 public function init_is_standard();
590 * Returns true if the plugin is shipped with the official distribution
591 * of the current Moodle version, false otherwise.
593 * @return bool
595 public function is_standard();
598 * Returns the status of the plugin
600 * @return string one of plugin_manager::PLUGIN_STATUS_xxx constants
602 public function get_status();
605 * Get the list of other plugins that this plugin requires ot be installed.
606 * @return array with keys the frankenstyle plugin name, and values either
607 * a version string (like '2011101700') or the constant ANY_VERSION.
609 public function get_other_required_plugins();
612 * Returns the information about plugin availability
614 * True means that the plugin is enabled. False means that the plugin is
615 * disabled. Null means that the information is not available, or the
616 * plugin does not support configurable availability or the availability
617 * can not be changed.
619 * @return null|bool
621 public function is_enabled();
624 * Returns the URL of the plugin settings screen
626 * Null value means that the plugin either does not have the settings screen
627 * or its location is not available via this library.
629 * @return null|moodle_url
631 public function get_settings_url();
634 * Returns the URL of the screen where this plugin can be uninstalled
636 * Visiting that URL must be safe, that is a manual confirmation is needed
637 * for actual uninstallation of the plugin. Null value means that the
638 * plugin either does not support uninstallation, or does not require any
639 * database cleanup or the location of the screen is not available via this
640 * library.
642 * @return null|moodle_url
644 public function get_uninstall_url();
647 * Returns relative directory of the plugin with heading '/'
649 * @example /mod/workshop
650 * @return string
652 public function get_dir();
655 * Return the full path name of a file within the plugin.
656 * No check is made to see if the file exists.
657 * @param string $relativepath e.g. 'version.php'.
658 * @return string e.g. $CFG->dirroot . '/mod/quiz/version.php'.
660 public function full_path($relativepath);
664 * Defines public properties that all plugintype classes must have
665 * and provides default implementation of required methods.
667 * @property-read string component the component name, type_name
669 abstract class plugintype_base {
671 /** @var string the plugintype name, eg. mod, auth or workshopform */
672 public $type;
673 /** @var string full path to the location of all the plugins of this type */
674 public $typerootdir;
675 /** @var string the plugin name, eg. assignment, ldap */
676 public $name;
677 /** @var string the localized plugin name */
678 public $displayname;
679 /** @var string the plugin source, one of plugin_manager::PLUGIN_SOURCE_xxx constants */
680 public $source;
681 /** @var fullpath to the location of this plugin */
682 public $rootdir;
683 /** @var int|string the version of the plugin's source code */
684 public $versiondisk;
685 /** @var int|string the version of the installed plugin */
686 public $versiondb;
687 /** @var int|float|string required version of Moodle core */
688 public $versionrequires;
689 /** @var array other plugins that this one depends on.
690 * Lazy-loaded by {@link get_other_required_plugins()} */
691 public $dependencies = null;
692 /** @var int number of instances of the plugin - not supported yet */
693 public $instances;
694 /** @var int order of the plugin among other plugins of the same type - not supported yet */
695 public $sortorder;
698 * @see plugin_information::get_plugins()
700 public static function get_plugins($type, $typerootdir, $typeclass) {
702 // get the information about plugins at the disk
703 $plugins = get_plugin_list($type);
704 $ondisk = array();
705 foreach ($plugins as $pluginname => $pluginrootdir) {
706 $plugin = new $typeclass();
707 $plugin->type = $type;
708 $plugin->typerootdir = $typerootdir;
709 $plugin->name = $pluginname;
710 $plugin->rootdir = $pluginrootdir;
712 $plugin->init_display_name();
713 $plugin->load_disk_version();
714 $plugin->load_db_version();
715 $plugin->load_required_main_version();
716 $plugin->init_is_standard();
718 $ondisk[$pluginname] = $plugin;
720 return $ondisk;
724 * @see plugin_information::init_display_name()
726 public function init_display_name() {
727 if (!get_string_manager()->string_exists('pluginname', $this->component)) {
728 $this->displayname = '[pluginname,' . $this->component . ']';
729 } else {
730 $this->displayname = get_string('pluginname', $this->component);
735 * Magic method getter, redirects to read only values.
736 * @param string $name
737 * @return mixed
739 public function __get($name) {
740 switch ($name) {
741 case 'component': return $this->type . '_' . $this->name;
743 default:
744 debugging('Invalid plugin property accessed! '.$name);
745 return null;
750 * @see plugin_information::full_path()
752 public function full_path($relativepath) {
753 if (empty($this->rootdir)) {
754 return '';
756 return $this->rootdir . '/' . $relativepath;
760 * Load the data from version.php.
761 * @return object the data object defined in version.php.
763 protected function load_version_php() {
764 $versionfile = $this->full_path('version.php');
766 $plugin = new stdClass();
767 if (is_readable($versionfile)) {
768 include($versionfile);
770 return $plugin;
774 * @see plugin_information::load_disk_version()
776 public function load_disk_version() {
777 $plugin = $this->load_version_php();
778 if (isset($plugin->version)) {
779 $this->versiondisk = $plugin->version;
784 * @see plugin_information::load_required_main_version()
786 public function load_required_main_version() {
787 $plugin = $this->load_version_php();
788 if (isset($plugin->requires)) {
789 $this->versionrequires = $plugin->requires;
794 * Initialise {@link $dependencies} to the list of other plugins (in any)
795 * that this one requires to be installed.
797 protected function load_other_required_plugins() {
798 $plugin = $this->load_version_php();
799 if (!empty($plugin->dependencies)) {
800 $this->dependencies = $plugin->dependencies;
801 } else {
802 $this->dependencies = array(); // By default, no dependencies.
807 * @see plugin_information::get_other_required_plugins()
809 public function get_other_required_plugins() {
810 if (is_null($this->dependencies)) {
811 $this->load_other_required_plugins();
813 return $this->dependencies;
817 * @see plugin_information::load_db_version()
819 public function load_db_version() {
821 if ($ver = self::get_version_from_config_plugins($this->component)) {
822 $this->versiondb = $ver;
827 * @see plugin_information::init_is_standard()
829 public function init_is_standard() {
831 $standard = plugin_manager::standard_plugins_list($this->type);
833 if ($standard !== false) {
834 $standard = array_flip($standard);
835 if (isset($standard[$this->name])) {
836 $this->source = plugin_manager::PLUGIN_SOURCE_STANDARD;
837 } else if (!is_null($this->versiondb) and is_null($this->versiondisk)
838 and plugin_manager::is_deleted_standard_plugin($this->type, $this->name)) {
839 $this->source = plugin_manager::PLUGIN_SOURCE_STANDARD; // to be deleted
840 } else {
841 $this->source = plugin_manager::PLUGIN_SOURCE_EXTENSION;
847 * @see plugin_information::is_standard()
849 public function is_standard() {
850 return $this->source === plugin_manager::PLUGIN_SOURCE_STANDARD;
854 * @see plugin_information::get_status()
856 public function get_status() {
858 if (is_null($this->versiondb) and is_null($this->versiondisk)) {
859 return plugin_manager::PLUGIN_STATUS_NODB;
861 } else if (is_null($this->versiondb) and !is_null($this->versiondisk)) {
862 return plugin_manager::PLUGIN_STATUS_NEW;
864 } else if (!is_null($this->versiondb) and is_null($this->versiondisk)) {
865 if (plugin_manager::is_deleted_standard_plugin($this->type, $this->name)) {
866 return plugin_manager::PLUGIN_STATUS_DELETE;
867 } else {
868 return plugin_manager::PLUGIN_STATUS_MISSING;
871 } else if ((string)$this->versiondb === (string)$this->versiondisk) {
872 return plugin_manager::PLUGIN_STATUS_UPTODATE;
874 } else if ($this->versiondb < $this->versiondisk) {
875 return plugin_manager::PLUGIN_STATUS_UPGRADE;
877 } else if ($this->versiondb > $this->versiondisk) {
878 return plugin_manager::PLUGIN_STATUS_DOWNGRADE;
880 } else {
881 // $version = pi(); and similar funny jokes - hopefully Donald E. Knuth will never contribute to Moodle ;-)
882 throw new coding_exception('Unable to determine plugin state, check the plugin versions');
887 * @see plugin_information::is_enabled()
889 public function is_enabled() {
890 return null;
894 * @see plugin_information::get_settings_url()
896 public function get_settings_url() {
897 return null;
901 * @see plugin_information::get_uninstall_url()
903 public function get_uninstall_url() {
904 return null;
908 * @see plugin_information::get_dir()
910 public function get_dir() {
911 global $CFG;
913 return substr($this->rootdir, strlen($CFG->dirroot));
917 * Provides access to plugin versions from {config_plugins}
919 * @param string $plugin plugin name
920 * @param double $disablecache optional, defaults to false
921 * @return int|false the stored value or false if not found
923 protected function get_version_from_config_plugins($plugin, $disablecache=false) {
924 global $DB;
925 static $pluginversions = null;
927 if (is_null($pluginversions) or $disablecache) {
928 try {
929 $pluginversions = $DB->get_records_menu('config_plugins', array('name' => 'version'), 'plugin', 'plugin,value');
930 } catch (dml_exception $e) {
931 // before install
932 $pluginversions = array();
936 if (!array_key_exists($plugin, $pluginversions)) {
937 return false;
940 return $pluginversions[$plugin];
945 * General class for all plugin types that do not have their own class
947 class plugintype_general extends plugintype_base implements plugin_information {
952 * Class for page side blocks
954 class plugintype_block extends plugintype_base implements plugin_information {
957 * @see plugin_information::get_plugins()
959 public static function get_plugins($type, $typerootdir, $typeclass) {
961 // get the information about blocks at the disk
962 $blocks = parent::get_plugins($type, $typerootdir, $typeclass);
964 // add blocks missing from disk
965 $blocksinfo = self::get_blocks_info();
966 foreach ($blocksinfo as $blockname => $blockinfo) {
967 if (isset($blocks[$blockname])) {
968 continue;
970 $plugin = new $typeclass();
971 $plugin->type = $type;
972 $plugin->typerootdir = $typerootdir;
973 $plugin->name = $blockname;
974 $plugin->rootdir = null;
975 $plugin->displayname = $blockname;
976 $plugin->versiondb = $blockinfo->version;
977 $plugin->init_is_standard();
979 $blocks[$blockname] = $plugin;
982 return $blocks;
986 * @see plugin_information::init_display_name()
988 public function init_display_name() {
990 if (get_string_manager()->string_exists('pluginname', 'block_' . $this->name)) {
991 $this->displayname = get_string('pluginname', 'block_' . $this->name);
993 } else if (($block = block_instance($this->name)) !== false) {
994 $this->displayname = $block->get_title();
996 } else {
997 parent::init_display_name();
1002 * @see plugin_information::load_db_version()
1004 public function load_db_version() {
1005 global $DB;
1007 $blocksinfo = self::get_blocks_info();
1008 if (isset($blocksinfo[$this->name]->version)) {
1009 $this->versiondb = $blocksinfo[$this->name]->version;
1014 * @see plugin_information::is_enabled()
1016 public function is_enabled() {
1018 $blocksinfo = self::get_blocks_info();
1019 if (isset($blocksinfo[$this->name]->visible)) {
1020 if ($blocksinfo[$this->name]->visible) {
1021 return true;
1022 } else {
1023 return false;
1025 } else {
1026 return parent::is_enabled();
1031 * @see plugin_information::get_settings_url()
1033 public function get_settings_url() {
1035 if (($block = block_instance($this->name)) === false) {
1036 return parent::get_settings_url();
1038 } else if ($block->has_config()) {
1039 if (file_exists($this->full_path('settings.php'))) {
1040 return new moodle_url('/admin/settings.php', array('section' => 'blocksetting' . $this->name));
1041 } else {
1042 $blocksinfo = self::get_blocks_info();
1043 return new moodle_url('/admin/block.php', array('block' => $blocksinfo[$this->name]->id));
1046 } else {
1047 return parent::get_settings_url();
1052 * @see plugin_information::get_uninstall_url()
1054 public function get_uninstall_url() {
1056 $blocksinfo = self::get_blocks_info();
1057 return new moodle_url('/admin/blocks.php', array('delete' => $blocksinfo[$this->name]->id, 'sesskey' => sesskey()));
1061 * Provides access to the records in {block} table
1063 * @param bool $disablecache do not use internal static cache
1064 * @return array array of stdClasses
1066 protected static function get_blocks_info($disablecache=false) {
1067 global $DB;
1068 static $blocksinfocache = null;
1070 if (is_null($blocksinfocache) or $disablecache) {
1071 try {
1072 $blocksinfocache = $DB->get_records('block', null, 'name', 'name,id,version,visible');
1073 } catch (dml_exception $e) {
1074 // before install
1075 $blocksinfocache = array();
1079 return $blocksinfocache;
1084 * Class for text filters
1086 class plugintype_filter extends plugintype_base implements plugin_information {
1089 * @see plugin_information::get_plugins()
1091 public static function get_plugins($type, $typerootdir, $typeclass) {
1092 global $CFG, $DB;
1094 $filters = array();
1096 // get the list of filters from both /filter and /mod location
1097 $installed = filter_get_all_installed();
1099 foreach ($installed as $filterlegacyname => $displayname) {
1100 $plugin = new $typeclass();
1101 $plugin->type = $type;
1102 $plugin->typerootdir = $typerootdir;
1103 $plugin->name = self::normalize_legacy_name($filterlegacyname);
1104 $plugin->rootdir = $CFG->dirroot . '/' . $filterlegacyname;
1105 $plugin->displayname = $displayname;
1107 $plugin->load_disk_version();
1108 $plugin->load_db_version();
1109 $plugin->load_required_main_version();
1110 $plugin->init_is_standard();
1112 $filters[$plugin->name] = $plugin;
1115 $globalstates = self::get_global_states();
1117 if ($DB->get_manager()->table_exists('filter_active')) {
1118 // if we're upgrading from 1.9, the table does not exist yet
1119 // if it does, make sure that all installed filters are registered
1120 $needsreload = false;
1121 foreach (array_keys($installed) as $filterlegacyname) {
1122 if (!isset($globalstates[self::normalize_legacy_name($filterlegacyname)])) {
1123 filter_set_global_state($filterlegacyname, TEXTFILTER_DISABLED);
1124 $needsreload = true;
1127 if ($needsreload) {
1128 $globalstates = self::get_global_states(true);
1132 // make sure that all registered filters are installed, just in case
1133 foreach ($globalstates as $name => $info) {
1134 if (!isset($filters[$name])) {
1135 // oops, there is a record in filter_active but the filter is not installed
1136 $plugin = new $typeclass();
1137 $plugin->type = $type;
1138 $plugin->typerootdir = $typerootdir;
1139 $plugin->name = $name;
1140 $plugin->rootdir = $CFG->dirroot . '/' . $info->legacyname;
1141 $plugin->displayname = $info->legacyname;
1143 $plugin->load_db_version();
1145 if (is_null($plugin->versiondb)) {
1146 // this is a hack to stimulate 'Missing from disk' error
1147 // because $plugin->versiondisk will be null !== false
1148 $plugin->versiondb = false;
1151 $filters[$plugin->name] = $plugin;
1155 return $filters;
1159 * @see plugin_information::init_display_name()
1161 public function init_display_name() {
1162 // do nothing, the name is set in self::get_plugins()
1166 * @see plugintype_base::load_version_php().
1168 protected function load_version_php() {
1169 if (strpos($this->name, 'mod_') === 0) {
1170 // filters bundled with modules do not have a version.php and so
1171 // do not provide their own versioning information.
1172 return new stdClass();
1174 return parent::load_version_php();
1178 * @see plugin_information::is_enabled()
1180 public function is_enabled() {
1182 $globalstates = self::get_global_states();
1184 foreach ($globalstates as $filterlegacyname => $info) {
1185 $name = self::normalize_legacy_name($filterlegacyname);
1186 if ($name === $this->name) {
1187 if ($info->active == TEXTFILTER_DISABLED) {
1188 return false;
1189 } else {
1190 // it may be 'On' or 'Off, but available'
1191 return null;
1196 return null;
1200 * @see plugin_information::get_settings_url()
1202 public function get_settings_url() {
1204 $globalstates = self::get_global_states();
1205 $legacyname = $globalstates[$this->name]->legacyname;
1206 if (filter_has_global_settings($legacyname)) {
1207 return new moodle_url('/admin/settings.php', array('section' => 'filtersetting' . str_replace('/', '', $legacyname)));
1208 } else {
1209 return null;
1214 * @see plugin_information::get_uninstall_url()
1216 public function get_uninstall_url() {
1218 if (strpos($this->name, 'mod_') === 0) {
1219 return null;
1220 } else {
1221 $globalstates = self::get_global_states();
1222 $legacyname = $globalstates[$this->name]->legacyname;
1223 return new moodle_url('/admin/filters.php', array('sesskey' => sesskey(), 'filterpath' => $legacyname, 'action' => 'delete'));
1228 * Convert legacy filter names like 'filter/foo' or 'mod/bar' into frankenstyle
1230 * @param string $legacyfiltername legacy filter name
1231 * @return string frankenstyle-like name
1233 protected static function normalize_legacy_name($legacyfiltername) {
1235 $name = str_replace('/', '_', $legacyfiltername);
1236 if (strpos($name, 'filter_') === 0) {
1237 $name = substr($name, 7);
1238 if (empty($name)) {
1239 throw new coding_exception('Unable to determine filter name: ' . $legacyfiltername);
1243 return $name;
1247 * Provides access to the results of {@link filter_get_global_states()}
1248 * but indexed by the normalized filter name
1250 * The legacy filter name is available as ->legacyname property.
1252 * @param bool $disablecache
1253 * @return array
1255 protected static function get_global_states($disablecache=false) {
1256 global $DB;
1257 static $globalstatescache = null;
1259 if ($disablecache or is_null($globalstatescache)) {
1261 if (!$DB->get_manager()->table_exists('filter_active')) {
1262 // we're upgrading from 1.9 and the table used by {@link filter_get_global_states()}
1263 // does not exist yet
1264 $globalstatescache = array();
1266 } else {
1267 foreach (filter_get_global_states() as $legacyname => $info) {
1268 $name = self::normalize_legacy_name($legacyname);
1269 $filterinfo = new stdClass();
1270 $filterinfo->legacyname = $legacyname;
1271 $filterinfo->active = $info->active;
1272 $filterinfo->sortorder = $info->sortorder;
1273 $globalstatescache[$name] = $filterinfo;
1278 return $globalstatescache;
1283 * Class for activity modules
1285 class plugintype_mod extends plugintype_base implements plugin_information {
1288 * @see plugin_information::get_plugins()
1290 public static function get_plugins($type, $typerootdir, $typeclass) {
1292 // get the information about plugins at the disk
1293 $modules = parent::get_plugins($type, $typerootdir, $typeclass);
1295 // add modules missing from disk
1296 $modulesinfo = self::get_modules_info();
1297 foreach ($modulesinfo as $modulename => $moduleinfo) {
1298 if (isset($modules[$modulename])) {
1299 continue;
1301 $plugin = new $typeclass();
1302 $plugin->type = $type;
1303 $plugin->typerootdir = $typerootdir;
1304 $plugin->name = $modulename;
1305 $plugin->rootdir = null;
1306 $plugin->displayname = $modulename;
1307 $plugin->versiondb = $moduleinfo->version;
1308 $plugin->init_is_standard();
1310 $modules[$modulename] = $plugin;
1313 return $modules;
1317 * @see plugin_information::init_display_name()
1319 public function init_display_name() {
1320 if (get_string_manager()->string_exists('pluginname', $this->component)) {
1321 $this->displayname = get_string('pluginname', $this->component);
1322 } else {
1323 $this->displayname = get_string('modulename', $this->component);
1328 * Load the data from version.php.
1329 * @return object the data object defined in version.php.
1331 protected function load_version_php() {
1332 $versionfile = $this->full_path('version.php');
1334 $module = new stdClass();
1335 if (is_readable($versionfile)) {
1336 include($versionfile);
1338 return $module;
1342 * @see plugin_information::load_db_version()
1344 public function load_db_version() {
1345 global $DB;
1347 $modulesinfo = self::get_modules_info();
1348 if (isset($modulesinfo[$this->name]->version)) {
1349 $this->versiondb = $modulesinfo[$this->name]->version;
1354 * @see plugin_information::is_enabled()
1356 public function is_enabled() {
1358 $modulesinfo = self::get_modules_info();
1359 if (isset($modulesinfo[$this->name]->visible)) {
1360 if ($modulesinfo[$this->name]->visible) {
1361 return true;
1362 } else {
1363 return false;
1365 } else {
1366 return parent::is_enabled();
1371 * @see plugin_information::get_settings_url()
1373 public function get_settings_url() {
1375 if (file_exists($this->full_path('settings.php')) or file_exists($this->full_path('settingstree.php'))) {
1376 return new moodle_url('/admin/settings.php', array('section' => 'modsetting' . $this->name));
1377 } else {
1378 return parent::get_settings_url();
1383 * @see plugin_information::get_uninstall_url()
1385 public function get_uninstall_url() {
1387 if ($this->name !== 'forum') {
1388 return new moodle_url('/admin/modules.php', array('delete' => $this->name, 'sesskey' => sesskey()));
1389 } else {
1390 return null;
1395 * Provides access to the records in {modules} table
1397 * @param bool $disablecache do not use internal static cache
1398 * @return array array of stdClasses
1400 protected static function get_modules_info($disablecache=false) {
1401 global $DB;
1402 static $modulesinfocache = null;
1404 if (is_null($modulesinfocache) or $disablecache) {
1405 try {
1406 $modulesinfocache = $DB->get_records('modules', null, 'name', 'name,id,version,visible');
1407 } catch (dml_exception $e) {
1408 // before install
1409 $modulesinfocache = array();
1413 return $modulesinfocache;
1419 * Class for question behaviours.
1421 class plugintype_qbehaviour extends plugintype_base implements plugin_information {
1423 * @see plugin_information::get_uninstall_url()
1425 public function get_uninstall_url() {
1426 return new moodle_url('/admin/qbehaviours.php',
1427 array('delete' => $this->name, 'sesskey' => sesskey()));
1433 * Class for question types
1435 class plugintype_qtype extends plugintype_base implements plugin_information {
1437 * @see plugin_information::get_uninstall_url()
1439 public function get_uninstall_url() {
1440 return new moodle_url('/admin/qtypes.php',
1441 array('delete' => $this->name, 'sesskey' => sesskey()));
1447 * Class for authentication plugins
1449 class plugintype_auth extends plugintype_base implements plugin_information {
1452 * @see plugin_information::is_enabled()
1454 public function is_enabled() {
1455 global $CFG;
1456 /** @var null|array list of enabled authentication plugins */
1457 static $enabled = null;
1459 if (in_array($this->name, array('nologin', 'manual'))) {
1460 // these two are always enabled and can't be disabled
1461 return null;
1464 if (is_null($enabled)) {
1465 $enabled = array_flip(explode(',', $CFG->auth));
1468 return isset($enabled[$this->name]);
1472 * @see plugin_information::get_settings_url()
1474 public function get_settings_url() {
1475 if (file_exists($this->full_path('settings.php'))) {
1476 return new moodle_url('/admin/settings.php', array('section' => 'authsetting' . $this->name));
1477 } else {
1478 return new moodle_url('/admin/auth_config.php', array('auth' => $this->name));
1484 * Class for enrolment plugins
1486 class plugintype_enrol extends plugintype_base implements plugin_information {
1489 * We do not actually need whole enrolment classes here so we do not call
1490 * {@link enrol_get_plugins()}. Note that this may produce slightly different
1491 * results, for example if the enrolment plugin does not contain lib.php
1492 * but it is listed in $CFG->enrol_plugins_enabled
1494 * @see plugin_information::is_enabled()
1496 public function is_enabled() {
1497 global $CFG;
1498 /** @var null|array list of enabled enrolment plugins */
1499 static $enabled = null;
1501 if (is_null($enabled)) {
1502 $enabled = array_flip(explode(',', $CFG->enrol_plugins_enabled));
1505 return isset($enabled[$this->name]);
1509 * @see plugin_information::get_settings_url()
1511 public function get_settings_url() {
1513 if ($this->is_enabled() or file_exists($this->full_path('settings.php'))) {
1514 return new moodle_url('/admin/settings.php', array('section' => 'enrolsettings' . $this->name));
1515 } else {
1516 return parent::get_settings_url();
1521 * @see plugin_information::get_uninstall_url()
1523 public function get_uninstall_url() {
1524 return new moodle_url('/admin/enrol.php', array('action' => 'uninstall', 'enrol' => $this->name, 'sesskey' => sesskey()));
1529 * Class for messaging processors
1531 class plugintype_message extends plugintype_base implements plugin_information {
1534 * @see plugin_information::get_settings_url()
1536 public function get_settings_url() {
1538 if (file_exists($this->full_path('settings.php')) or file_exists($this->full_path('settingstree.php'))) {
1539 return new moodle_url('/admin/settings.php', array('section' => 'messagesetting' . $this->name));
1540 } else {
1541 return parent::get_settings_url();
1547 * Class for repositories
1549 class plugintype_repository extends plugintype_base implements plugin_information {
1552 * @see plugin_information::is_enabled()
1554 public function is_enabled() {
1556 $enabled = self::get_enabled_repositories();
1558 return isset($enabled[$this->name]);
1562 * @see plugin_information::get_settings_url()
1564 public function get_settings_url() {
1566 if ($this->is_enabled()) {
1567 return new moodle_url('/admin/repository.php', array('sesskey' => sesskey(), 'action' => 'edit', 'repos' => $this->name));
1568 } else {
1569 return parent::get_settings_url();
1574 * Provides access to the records in {repository} table
1576 * @param bool $disablecache do not use internal static cache
1577 * @return array array of stdClasses
1579 protected static function get_enabled_repositories($disablecache=false) {
1580 global $DB;
1581 static $repositories = null;
1583 if (is_null($repositories) or $disablecache) {
1584 $repositories = $DB->get_records('repository', null, 'type', 'type,visible,sortorder');
1587 return $repositories;
1592 * Class for portfolios
1594 class plugintype_portfolio extends plugintype_base implements plugin_information {
1597 * @see plugin_information::is_enabled()
1599 public function is_enabled() {
1601 $enabled = self::get_enabled_portfolios();
1603 return isset($enabled[$this->name]);
1607 * Provides access to the records in {portfolio_instance} table
1609 * @param bool $disablecache do not use internal static cache
1610 * @return array array of stdClasses
1612 protected static function get_enabled_portfolios($disablecache=false) {
1613 global $DB;
1614 static $portfolios = null;
1616 if (is_null($portfolios) or $disablecache) {
1617 $portfolios = array();
1618 $instances = $DB->get_recordset('portfolio_instance', null, 'plugin');
1619 foreach ($instances as $instance) {
1620 if (isset($portfolios[$instance->plugin])) {
1621 if ($instance->visible) {
1622 $portfolios[$instance->plugin]->visible = $instance->visible;
1624 } else {
1625 $portfolios[$instance->plugin] = $instance;
1630 return $portfolios;
1635 * Class for themes
1637 class plugintype_theme extends plugintype_base implements plugin_information {
1640 * @see plugin_information::is_enabled()
1642 public function is_enabled() {
1643 global $CFG;
1645 if ((!empty($CFG->theme) and $CFG->theme === $this->name) or
1646 (!empty($CFG->themelegacy) and $CFG->themelegacy === $this->name)) {
1647 return true;
1648 } else {
1649 return parent::is_enabled();
1655 * Class representing an MNet service
1657 class plugintype_mnetservice extends plugintype_base implements plugin_information {
1660 * @see plugin_information::is_enabled()
1662 public function is_enabled() {
1663 global $CFG;
1665 if (empty($CFG->mnet_dispatcher_mode) || $CFG->mnet_dispatcher_mode !== 'strict') {
1666 return false;
1667 } else {
1668 return parent::is_enabled();
1674 * Class for admin tool plugins
1676 class plugintype_tool extends plugintype_base implements plugin_information {
1678 public function get_uninstall_url() {
1679 return new moodle_url('/admin/tools.php', array('delete' => $this->name, 'sesskey' => sesskey()));
1684 * Class for admin tool plugins
1686 class plugintype_report extends plugintype_base implements plugin_information {
1688 public function get_uninstall_url() {
1689 return new moodle_url('/admin/reports.php', array('delete' => $this->name, 'sesskey' => sesskey()));