Merge branch 'w22_MDL-39676_m24_whereparam' of git://github.com/skodak/moodle into...
[moodle.git] / lib / pluginlib.php
blobb740093d906f47690b607cf7daf3f97ab2b1bfc2
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
18 /**
19 * Defines classes used for plugins management
21 * This library provides a unified interface to various plugin types in
22 * Moodle. It is mainly used by the plugins management admin page and the
23 * plugins check page during the upgrade.
25 * @package core
26 * @subpackage admin
27 * @copyright 2011 David Mudrak <david@moodle.com>
28 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
31 defined('MOODLE_INTERNAL') || die();
33 /**
34 * Singleton class providing general plugins management functionality
36 class plugin_manager {
38 /** the plugin is shipped with standard Moodle distribution */
39 const PLUGIN_SOURCE_STANDARD = 'std';
40 /** the plugin is added extension */
41 const PLUGIN_SOURCE_EXTENSION = 'ext';
43 /** the plugin uses neither database nor capabilities, no versions */
44 const PLUGIN_STATUS_NODB = 'nodb';
45 /** the plugin is up-to-date */
46 const PLUGIN_STATUS_UPTODATE = 'uptodate';
47 /** the plugin is about to be installed */
48 const PLUGIN_STATUS_NEW = 'new';
49 /** the plugin is about to be upgraded */
50 const PLUGIN_STATUS_UPGRADE = 'upgrade';
51 /** the standard plugin is about to be deleted */
52 const PLUGIN_STATUS_DELETE = 'delete';
53 /** the version at the disk is lower than the one already installed */
54 const PLUGIN_STATUS_DOWNGRADE = 'downgrade';
55 /** the plugin is installed but missing from disk */
56 const PLUGIN_STATUS_MISSING = 'missing';
58 /** @var plugin_manager holds the singleton instance */
59 protected static $singletoninstance;
60 /** @var array of raw plugins information */
61 protected $pluginsinfo = null;
62 /** @var array of raw subplugins information */
63 protected $subpluginsinfo = null;
65 /**
66 * Direct initiation not allowed, use the factory method {@link self::instance()}
68 protected function __construct() {
71 /**
72 * Sorry, this is singleton
74 protected function __clone() {
77 /**
78 * Factory method for this class
80 * @return plugin_manager the singleton instance
82 public static function instance() {
83 if (is_null(self::$singletoninstance)) {
84 self::$singletoninstance = new self();
86 return self::$singletoninstance;
89 /**
90 * Reset any caches
91 * @param bool $phpunitreset
93 public static function reset_caches($phpunitreset = false) {
94 if ($phpunitreset) {
95 self::$singletoninstance = null;
99 /**
100 * Returns a tree of known plugins and information about them
102 * @param bool $disablecache force reload, cache can be used otherwise
103 * @return array 2D array. The first keys are plugin type names (e.g. qtype);
104 * the second keys are the plugin local name (e.g. multichoice); and
105 * the values are the corresponding objects extending {@link plugininfo_base}
107 public function get_plugins($disablecache=false) {
108 global $CFG;
110 if ($disablecache or is_null($this->pluginsinfo)) {
111 // Hack: include mod and editor subplugin management classes first,
112 // the adminlib.php is supposed to contain extra admin settings too.
113 require_once($CFG->libdir.'/adminlib.php');
114 foreach(array('mod', 'editor') as $type) {
115 foreach (get_plugin_list($type) as $dir) {
116 if (file_exists("$dir/adminlib.php")) {
117 include_once("$dir/adminlib.php");
121 $this->pluginsinfo = array();
122 $plugintypes = get_plugin_types();
123 $plugintypes = $this->reorder_plugin_types($plugintypes);
124 foreach ($plugintypes as $plugintype => $plugintyperootdir) {
125 if (in_array($plugintype, array('base', 'general'))) {
126 throw new coding_exception('Illegal usage of reserved word for plugin type');
128 if (class_exists('plugininfo_' . $plugintype)) {
129 $plugintypeclass = 'plugininfo_' . $plugintype;
130 } else {
131 $plugintypeclass = 'plugininfo_general';
133 if (!in_array('plugininfo_base', class_parents($plugintypeclass))) {
134 throw new coding_exception('Class ' . $plugintypeclass . ' must extend plugininfo_base');
136 $plugins = call_user_func(array($plugintypeclass, 'get_plugins'), $plugintype, $plugintyperootdir, $plugintypeclass);
137 $this->pluginsinfo[$plugintype] = $plugins;
140 if (empty($CFG->disableupdatenotifications) and !during_initial_install()) {
141 // append the information about available updates provided by {@link available_update_checker()}
142 $provider = available_update_checker::instance();
143 foreach ($this->pluginsinfo as $plugintype => $plugins) {
144 foreach ($plugins as $plugininfoholder) {
145 $plugininfoholder->check_available_updates($provider);
151 return $this->pluginsinfo;
155 * Returns list of plugins that define their subplugins and the information
156 * about them from the db/subplugins.php file.
158 * At the moment, only activity modules and editors can define subplugins.
160 * @param bool $disablecache force reload, cache can be used otherwise
161 * @return array with keys like 'mod_quiz', and values the data from the
162 * corresponding db/subplugins.php file.
164 public function get_subplugins($disablecache=false) {
166 if ($disablecache or is_null($this->subpluginsinfo)) {
167 $this->subpluginsinfo = array();
168 foreach (array('mod', 'editor') as $type) {
169 $owners = get_plugin_list($type);
170 foreach ($owners as $component => $ownerdir) {
171 $componentsubplugins = array();
172 if (file_exists($ownerdir . '/db/subplugins.php')) {
173 $subplugins = array();
174 include($ownerdir . '/db/subplugins.php');
175 foreach ($subplugins as $subplugintype => $subplugintyperootdir) {
176 $subplugin = new stdClass();
177 $subplugin->type = $subplugintype;
178 $subplugin->typerootdir = $subplugintyperootdir;
179 $componentsubplugins[$subplugintype] = $subplugin;
181 $this->subpluginsinfo[$type . '_' . $component] = $componentsubplugins;
187 return $this->subpluginsinfo;
191 * Returns the name of the plugin that defines the given subplugin type
193 * If the given subplugin type is not actually a subplugin, returns false.
195 * @param string $subplugintype the name of subplugin type, eg. workshopform or quiz
196 * @return false|string the name of the parent plugin, eg. mod_workshop
198 public function get_parent_of_subplugin($subplugintype) {
200 $parent = false;
201 foreach ($this->get_subplugins() as $pluginname => $subplugintypes) {
202 if (isset($subplugintypes[$subplugintype])) {
203 $parent = $pluginname;
204 break;
208 return $parent;
212 * Returns a localized name of a given plugin
214 * @param string $plugin name of the plugin, eg mod_workshop or auth_ldap
215 * @return string
217 public function plugin_name($plugin) {
218 list($type, $name) = normalize_component($plugin);
219 return $this->pluginsinfo[$type][$name]->displayname;
223 * Returns a localized name of a plugin type in plural form
225 * Most plugin types define their names in core_plugin lang file. In case of subplugins,
226 * we try to ask the parent plugin for the name. In the worst case, we will return
227 * the value of the passed $type parameter.
229 * @param string $type the type of the plugin, e.g. mod or workshopform
230 * @return string
232 public function plugintype_name_plural($type) {
234 if (get_string_manager()->string_exists('type_' . $type . '_plural', 'core_plugin')) {
235 // for most plugin types, their names are defined in core_plugin lang file
236 return get_string('type_' . $type . '_plural', 'core_plugin');
238 } else if ($parent = $this->get_parent_of_subplugin($type)) {
239 // if this is a subplugin, try to ask the parent plugin for the name
240 if (get_string_manager()->string_exists('subplugintype_' . $type . '_plural', $parent)) {
241 return $this->plugin_name($parent) . ' / ' . get_string('subplugintype_' . $type . '_plural', $parent);
242 } else {
243 return $this->plugin_name($parent) . ' / ' . $type;
246 } else {
247 return $type;
252 * @param string $component frankenstyle component name.
253 * @return plugininfo_base|null the corresponding plugin information.
255 public function get_plugin_info($component) {
256 list($type, $name) = normalize_component($component);
257 $plugins = $this->get_plugins();
258 if (isset($plugins[$type][$name])) {
259 return $plugins[$type][$name];
260 } else {
261 return null;
266 * Get a list of any other plugins that require this one.
267 * @param string $component frankenstyle component name.
268 * @return array of frankensyle component names that require this one.
270 public function other_plugins_that_require($component) {
271 $others = array();
272 foreach ($this->get_plugins() as $type => $plugins) {
273 foreach ($plugins as $plugin) {
274 $required = $plugin->get_other_required_plugins();
275 if (isset($required[$component])) {
276 $others[] = $plugin->component;
280 return $others;
284 * Check a dependencies list against the list of installed plugins.
285 * @param array $dependencies compenent name to required version or ANY_VERSION.
286 * @return bool true if all the dependencies are satisfied.
288 public function are_dependencies_satisfied($dependencies) {
289 foreach ($dependencies as $component => $requiredversion) {
290 $otherplugin = $this->get_plugin_info($component);
291 if (is_null($otherplugin)) {
292 return false;
295 if ($requiredversion != ANY_VERSION and $otherplugin->versiondisk < $requiredversion) {
296 return false;
300 return true;
304 * Checks all dependencies for all installed plugins
306 * This is used by install and upgrade. The array passed by reference as the second
307 * argument is populated with the list of plugins that have failed dependencies (note that
308 * a single plugin can appear multiple times in the $failedplugins).
310 * @param int $moodleversion the version from version.php.
311 * @param array $failedplugins to return the list of plugins with non-satisfied dependencies
312 * @return bool true if all the dependencies are satisfied for all plugins.
314 public function all_plugins_ok($moodleversion, &$failedplugins = array()) {
316 $return = true;
317 foreach ($this->get_plugins() as $type => $plugins) {
318 foreach ($plugins as $plugin) {
320 if (!$plugin->is_core_dependency_satisfied($moodleversion)) {
321 $return = false;
322 $failedplugins[] = $plugin->component;
325 if (!$this->are_dependencies_satisfied($plugin->get_other_required_plugins())) {
326 $return = false;
327 $failedplugins[] = $plugin->component;
332 return $return;
336 * Checks if there are some plugins with a known available update
338 * @return bool true if there is at least one available update
340 public function some_plugins_updatable() {
341 foreach ($this->get_plugins() as $type => $plugins) {
342 foreach ($plugins as $plugin) {
343 if ($plugin->available_updates()) {
344 return true;
349 return false;
353 * Defines a list of all plugins that were originally shipped in the standard Moodle distribution,
354 * but are not anymore and are deleted during upgrades.
356 * The main purpose of this list is to hide missing plugins during upgrade.
358 * @param string $type plugin type
359 * @param string $name plugin name
360 * @return bool
362 public static function is_deleted_standard_plugin($type, $name) {
363 static $plugins = array(
364 // do not add 1.9-2.2 plugin removals here
367 if (!isset($plugins[$type])) {
368 return false;
370 return in_array($name, $plugins[$type]);
374 * Defines a white list of all plugins shipped in the standard Moodle distribution
376 * @param string $type
377 * @return false|array array of standard plugins or false if the type is unknown
379 public static function standard_plugins_list($type) {
380 static $standard_plugins = array(
382 'assignment' => array(
383 'offline', 'online', 'upload', 'uploadsingle'
386 'assignsubmission' => array(
387 'comments', 'file', 'onlinetext'
390 'assignfeedback' => array(
391 'comments', 'file', 'offline'
394 'auth' => array(
395 'cas', 'db', 'email', 'fc', 'imap', 'ldap', 'manual', 'mnet',
396 'nntp', 'nologin', 'none', 'pam', 'pop3', 'radius',
397 'shibboleth', 'webservice'
400 'block' => array(
401 'activity_modules', 'admin_bookmarks', 'blog_menu',
402 'blog_recent', 'blog_tags', 'calendar_month',
403 'calendar_upcoming', 'comments', 'community',
404 'completionstatus', 'course_list', 'course_overview',
405 'course_summary', 'feedback', 'glossary_random', 'html',
406 'login', 'mentees', 'messages', 'mnet_hosts', 'myprofile',
407 'navigation', 'news_items', 'online_users', 'participants',
408 'private_files', 'quiz_results', 'recent_activity',
409 'rss_client', 'search_forums', 'section_links',
410 'selfcompletion', 'settings', 'site_main_menu',
411 'social_activities', 'tag_flickr', 'tag_youtube', 'tags'
414 'booktool' => array(
415 'exportimscp', 'importhtml', 'print'
418 'cachelock' => array(
419 'file'
422 'cachestore' => array(
423 'file', 'memcache', 'memcached', 'mongodb', 'session', 'static'
426 'coursereport' => array(
427 //deprecated!
430 'datafield' => array(
431 'checkbox', 'date', 'file', 'latlong', 'menu', 'multimenu',
432 'number', 'picture', 'radiobutton', 'text', 'textarea', 'url'
435 'datapreset' => array(
436 'imagegallery'
439 'editor' => array(
440 'textarea', 'tinymce'
443 'enrol' => array(
444 'authorize', 'category', 'cohort', 'database', 'flatfile',
445 'guest', 'imsenterprise', 'ldap', 'manual', 'meta', 'mnet',
446 'paypal', 'self'
449 'filter' => array(
450 'activitynames', 'algebra', 'censor', 'emailprotect',
451 'emoticon', 'mediaplugin', 'multilang', 'tex', 'tidy',
452 'urltolink', 'data', 'glossary'
455 'format' => array(
456 'scorm', 'social', 'topics', 'weeks'
459 'gradeexport' => array(
460 'ods', 'txt', 'xls', 'xml'
463 'gradeimport' => array(
464 'csv', 'xml'
467 'gradereport' => array(
468 'grader', 'outcomes', 'overview', 'user'
471 'gradingform' => array(
472 'rubric', 'guide'
475 'local' => array(
478 'message' => array(
479 'email', 'jabber', 'popup'
482 'mnetservice' => array(
483 'enrol'
486 'mod' => array(
487 'assign', 'assignment', 'book', 'chat', 'choice', 'data', 'feedback', 'folder',
488 'forum', 'glossary', 'imscp', 'label', 'lesson', 'lti', 'page',
489 'quiz', 'resource', 'scorm', 'survey', 'url', 'wiki', 'workshop'
492 'plagiarism' => array(
495 'portfolio' => array(
496 'boxnet', 'download', 'flickr', 'googledocs', 'mahara', 'picasa'
499 'profilefield' => array(
500 'checkbox', 'datetime', 'menu', 'text', 'textarea'
503 'qbehaviour' => array(
504 'adaptive', 'adaptivenopenalty', 'deferredcbm',
505 'deferredfeedback', 'immediatecbm', 'immediatefeedback',
506 'informationitem', 'interactive', 'interactivecountback',
507 'manualgraded', 'missing'
510 'qformat' => array(
511 'aiken', 'blackboard', 'blackboard_six', 'examview', 'gift',
512 'learnwise', 'missingword', 'multianswer', 'webct',
513 'xhtml', 'xml'
516 'qtype' => array(
517 'calculated', 'calculatedmulti', 'calculatedsimple',
518 'description', 'essay', 'match', 'missingtype', 'multianswer',
519 'multichoice', 'numerical', 'random', 'randomsamatch',
520 'shortanswer', 'truefalse'
523 'quiz' => array(
524 'grading', 'overview', 'responses', 'statistics'
527 'quizaccess' => array(
528 'delaybetweenattempts', 'ipaddress', 'numattempts', 'openclosedate',
529 'password', 'safebrowser', 'securewindow', 'timelimit'
532 'report' => array(
533 'backups', 'completion', 'configlog', 'courseoverview',
534 'log', 'loglive', 'outline', 'participation', 'progress', 'questioninstances', 'security', 'stats'
537 'repository' => array(
538 'alfresco', 'boxnet', 'coursefiles', 'dropbox', 'equella', 'filesystem',
539 'flickr', 'flickr_public', 'googledocs', 'local', 'merlot',
540 'picasa', 'recent', 's3', 'upload', 'url', 'user', 'webdav',
541 'wikimedia', 'youtube'
544 'scormreport' => array(
545 'basic',
546 'interactions',
547 'graphs'
550 'tinymce' => array(
551 'dragmath', 'moodleemoticon', 'moodleimage', 'moodlemedia', 'moodlenolink', 'spellchecker',
554 'theme' => array(
555 'afterburner', 'anomaly', 'arialist', 'base', 'binarius',
556 'boxxie', 'brick', 'canvas', 'formal_white', 'formfactor',
557 'fusion', 'leatherbound', 'magazine', 'mymobile', 'nimble',
558 'nonzero', 'overlay', 'serenity', 'sky_high', 'splash',
559 'standard', 'standardold'
562 'tool' => array(
563 'assignmentupgrade', 'capability', 'customlang', 'dbtransfer', 'generator',
564 'health', 'innodb', 'langimport', 'multilangupgrade', 'phpunit', 'profiling',
565 'qeupgradehelper', 'replace', 'spamcleaner', 'timezoneimport', 'unittest',
566 'uploaduser', 'unsuproles', 'xmldb'
569 'webservice' => array(
570 'amf', 'rest', 'soap', 'xmlrpc'
573 'workshopallocation' => array(
574 'manual', 'random', 'scheduled'
577 'workshopeval' => array(
578 'best'
581 'workshopform' => array(
582 'accumulative', 'comments', 'numerrors', 'rubric'
586 if (isset($standard_plugins[$type])) {
587 return $standard_plugins[$type];
588 } else {
589 return false;
594 * Reorders plugin types into a sequence to be displayed
596 * For technical reasons, plugin types returned by {@link get_plugin_types()} are
597 * in a certain order that does not need to fit the expected order for the display.
598 * Particularly, activity modules should be displayed first as they represent the
599 * real heart of Moodle. They should be followed by other plugin types that are
600 * used to build the courses (as that is what one expects from LMS). After that,
601 * other supportive plugin types follow.
603 * @param array $types associative array
604 * @return array same array with altered order of items
606 protected function reorder_plugin_types(array $types) {
607 $fix = array(
608 'mod' => $types['mod'],
609 'block' => $types['block'],
610 'qtype' => $types['qtype'],
611 'qbehaviour' => $types['qbehaviour'],
612 'qformat' => $types['qformat'],
613 'filter' => $types['filter'],
614 'enrol' => $types['enrol'],
616 foreach ($types as $type => $path) {
617 if (!isset($fix[$type])) {
618 $fix[$type] = $path;
621 return $fix;
627 * General exception thrown by the {@link available_update_checker} class
629 class available_update_checker_exception extends moodle_exception {
632 * @param string $errorcode exception description identifier
633 * @param mixed $debuginfo debugging data to display
635 public function __construct($errorcode, $debuginfo=null) {
636 parent::__construct($errorcode, 'core_plugin', '', null, print_r($debuginfo, true));
642 * Singleton class that handles checking for available updates
644 class available_update_checker {
646 /** @var available_update_checker holds the singleton instance */
647 protected static $singletoninstance;
648 /** @var null|int the timestamp of when the most recent response was fetched */
649 protected $recentfetch = null;
650 /** @var null|array the recent response from the update notification provider */
651 protected $recentresponse = null;
652 /** @var null|string the numerical version of the local Moodle code */
653 protected $currentversion = null;
654 /** @var null|string the release info of the local Moodle code */
655 protected $currentrelease = null;
656 /** @var null|string branch of the local Moodle code */
657 protected $currentbranch = null;
658 /** @var array of (string)frankestyle => (string)version list of additional plugins deployed at this site */
659 protected $currentplugins = array();
662 * Direct initiation not allowed, use the factory method {@link self::instance()}
664 protected function __construct() {
668 * Sorry, this is singleton
670 protected function __clone() {
674 * Factory method for this class
676 * @return available_update_checker the singleton instance
678 public static function instance() {
679 if (is_null(self::$singletoninstance)) {
680 self::$singletoninstance = new self();
682 return self::$singletoninstance;
686 * Reset any caches
687 * @param bool $phpunitreset
689 public static function reset_caches($phpunitreset = false) {
690 if ($phpunitreset) {
691 self::$singletoninstance = null;
696 * Returns the timestamp of the last execution of {@link fetch()}
698 * @return int|null null if it has never been executed or we don't known
700 public function get_last_timefetched() {
702 $this->restore_response();
704 if (!empty($this->recentfetch)) {
705 return $this->recentfetch;
707 } else {
708 return null;
713 * Fetches the available update status from the remote site
715 * @throws available_update_checker_exception
717 public function fetch() {
718 $response = $this->get_response();
719 $this->validate_response($response);
720 $this->store_response($response);
724 * Returns the available update information for the given component
726 * This method returns null if the most recent response does not contain any information
727 * about it. The returned structure is an array of available updates for the given
728 * component. Each update info is an object with at least one property called
729 * 'version'. Other possible properties are 'release', 'maturity', 'url' and 'downloadurl'.
731 * For the 'core' component, the method returns real updates only (those with higher version).
732 * For all other components, the list of all known remote updates is returned and the caller
733 * (usually the {@link plugin_manager}) is supposed to make the actual comparison of versions.
735 * @param string $component frankenstyle
736 * @param array $options with supported keys 'minmaturity' and/or 'notifybuilds'
737 * @return null|array null or array of available_update_info objects
739 public function get_update_info($component, array $options = array()) {
741 if (!isset($options['minmaturity'])) {
742 $options['minmaturity'] = 0;
745 if (!isset($options['notifybuilds'])) {
746 $options['notifybuilds'] = false;
749 if ($component == 'core') {
750 $this->load_current_environment();
753 $this->restore_response();
755 if (empty($this->recentresponse['updates'][$component])) {
756 return null;
759 $updates = array();
760 foreach ($this->recentresponse['updates'][$component] as $info) {
761 $update = new available_update_info($component, $info);
762 if (isset($update->maturity) and ($update->maturity < $options['minmaturity'])) {
763 continue;
765 if ($component == 'core') {
766 if ($update->version <= $this->currentversion) {
767 continue;
769 if (empty($options['notifybuilds']) and $this->is_same_release($update->release)) {
770 continue;
773 $updates[] = $update;
776 if (empty($updates)) {
777 return null;
780 return $updates;
784 * The method being run via cron.php
786 public function cron() {
787 global $CFG;
789 if (!$this->cron_autocheck_enabled()) {
790 $this->cron_mtrace('Automatic check for available updates not enabled, skipping.');
791 return;
794 $now = $this->cron_current_timestamp();
796 if ($this->cron_has_fresh_fetch($now)) {
797 $this->cron_mtrace('Recently fetched info about available updates is still fresh enough, skipping.');
798 return;
801 if ($this->cron_has_outdated_fetch($now)) {
802 $this->cron_mtrace('Outdated or missing info about available updates, forced fetching ... ', '');
803 $this->cron_execute();
804 return;
807 $offset = $this->cron_execution_offset();
808 $start = mktime(1, 0, 0, date('n', $now), date('j', $now), date('Y', $now)); // 01:00 AM today local time
809 if ($now > $start + $offset) {
810 $this->cron_mtrace('Regular daily check for available updates ... ', '');
811 $this->cron_execute();
812 return;
816 /// end of public API //////////////////////////////////////////////////////
819 * Makes cURL request to get data from the remote site
821 * @return string raw request result
822 * @throws available_update_checker_exception
824 protected function get_response() {
825 global $CFG;
826 require_once($CFG->libdir.'/filelib.php');
828 $curl = new curl(array('proxy' => true));
829 $response = $curl->post($this->prepare_request_url(), $this->prepare_request_params(), $this->prepare_request_options());
830 $curlerrno = $curl->get_errno();
831 if (!empty($curlerrno)) {
832 throw new available_update_checker_exception('err_response_curl', 'cURL error '.$curlerrno.': '.$curl->error);
834 $curlinfo = $curl->get_info();
835 if ($curlinfo['http_code'] != 200) {
836 throw new available_update_checker_exception('err_response_http_code', $curlinfo['http_code']);
838 return $response;
842 * Makes sure the response is valid, has correct API format etc.
844 * @param string $response raw response as returned by the {@link self::get_response()}
845 * @throws available_update_checker_exception
847 protected function validate_response($response) {
849 $response = $this->decode_response($response);
851 if (empty($response)) {
852 throw new available_update_checker_exception('err_response_empty');
855 if (empty($response['status']) or $response['status'] !== 'OK') {
856 throw new available_update_checker_exception('err_response_status', $response['status']);
859 if (empty($response['apiver']) or $response['apiver'] !== '1.1') {
860 throw new available_update_checker_exception('err_response_format_version', $response['apiver']);
863 if (empty($response['forbranch']) or $response['forbranch'] !== moodle_major_version(true)) {
864 throw new available_update_checker_exception('err_response_target_version', $response['forbranch']);
869 * Decodes the raw string response from the update notifications provider
871 * @param string $response as returned by {@link self::get_response()}
872 * @return array decoded response structure
874 protected function decode_response($response) {
875 return json_decode($response, true);
879 * Stores the valid fetched response for later usage
881 * This implementation uses the config_plugins table as the permanent storage.
883 * @param string $response raw valid data returned by {@link self::get_response()}
885 protected function store_response($response) {
887 set_config('recentfetch', time(), 'core_plugin');
888 set_config('recentresponse', $response, 'core_plugin');
890 $this->restore_response(true);
894 * Loads the most recent raw response record we have fetched
896 * After this method is called, $this->recentresponse is set to an array. If the
897 * array is empty, then either no data have been fetched yet or the fetched data
898 * do not have expected format (and thence they are ignored and a debugging
899 * message is displayed).
901 * This implementation uses the config_plugins table as the permanent storage.
903 * @param bool $forcereload reload even if it was already loaded
905 protected function restore_response($forcereload = false) {
907 if (!$forcereload and !is_null($this->recentresponse)) {
908 // we already have it, nothing to do
909 return;
912 $config = get_config('core_plugin');
914 if (!empty($config->recentresponse) and !empty($config->recentfetch)) {
915 try {
916 $this->validate_response($config->recentresponse);
917 $this->recentfetch = $config->recentfetch;
918 $this->recentresponse = $this->decode_response($config->recentresponse);
919 } catch (available_update_checker_exception $e) {
920 // The server response is not valid. Behave as if no data were fetched yet.
921 // This may happen when the most recent update info (cached locally) has been
922 // fetched with the previous branch of Moodle (like during an upgrade from 2.x
923 // to 2.y) or when the API of the response has changed.
924 $this->recentresponse = array();
927 } else {
928 $this->recentresponse = array();
933 * Compares two raw {@link $recentresponse} records and returns the list of changed updates
935 * This method is used to populate potential update info to be sent to site admins.
937 * @param array $old
938 * @param array $new
939 * @throws available_update_checker_exception
940 * @return array parts of $new['updates'] that have changed
942 protected function compare_responses(array $old, array $new) {
944 if (empty($new)) {
945 return array();
948 if (!array_key_exists('updates', $new)) {
949 throw new available_update_checker_exception('err_response_format');
952 if (empty($old)) {
953 return $new['updates'];
956 if (!array_key_exists('updates', $old)) {
957 throw new available_update_checker_exception('err_response_format');
960 $changes = array();
962 foreach ($new['updates'] as $newcomponent => $newcomponentupdates) {
963 if (empty($old['updates'][$newcomponent])) {
964 $changes[$newcomponent] = $newcomponentupdates;
965 continue;
967 foreach ($newcomponentupdates as $newcomponentupdate) {
968 $inold = false;
969 foreach ($old['updates'][$newcomponent] as $oldcomponentupdate) {
970 if ($newcomponentupdate['version'] == $oldcomponentupdate['version']) {
971 $inold = true;
974 if (!$inold) {
975 if (!isset($changes[$newcomponent])) {
976 $changes[$newcomponent] = array();
978 $changes[$newcomponent][] = $newcomponentupdate;
983 return $changes;
987 * Returns the URL to send update requests to
989 * During the development or testing, you can set $CFG->alternativeupdateproviderurl
990 * to a custom URL that will be used. Otherwise the standard URL will be returned.
992 * @return string URL
994 protected function prepare_request_url() {
995 global $CFG;
997 if (!empty($CFG->config_php_settings['alternativeupdateproviderurl'])) {
998 return $CFG->config_php_settings['alternativeupdateproviderurl'];
999 } else {
1000 return 'https://download.moodle.org/api/1.1/updates.php';
1005 * Sets the properties currentversion, currentrelease, currentbranch and currentplugins
1007 * @param bool $forcereload
1009 protected function load_current_environment($forcereload=false) {
1010 global $CFG;
1012 if (!is_null($this->currentversion) and !$forcereload) {
1013 // nothing to do
1014 return;
1017 $version = null;
1018 $release = null;
1020 require($CFG->dirroot.'/version.php');
1021 $this->currentversion = $version;
1022 $this->currentrelease = $release;
1023 $this->currentbranch = moodle_major_version(true);
1025 $pluginman = plugin_manager::instance();
1026 foreach ($pluginman->get_plugins() as $type => $plugins) {
1027 foreach ($plugins as $plugin) {
1028 if (!$plugin->is_standard()) {
1029 $this->currentplugins[$plugin->component] = $plugin->versiondisk;
1036 * Returns the list of HTTP params to be sent to the updates provider URL
1038 * @return array of (string)param => (string)value
1040 protected function prepare_request_params() {
1041 global $CFG;
1043 $this->load_current_environment();
1044 $this->restore_response();
1046 $params = array();
1047 $params['format'] = 'json';
1049 if (isset($this->recentresponse['ticket'])) {
1050 $params['ticket'] = $this->recentresponse['ticket'];
1053 if (isset($this->currentversion)) {
1054 $params['version'] = $this->currentversion;
1055 } else {
1056 throw new coding_exception('Main Moodle version must be already known here');
1059 if (isset($this->currentbranch)) {
1060 $params['branch'] = $this->currentbranch;
1061 } else {
1062 throw new coding_exception('Moodle release must be already known here');
1065 $plugins = array();
1066 foreach ($this->currentplugins as $plugin => $version) {
1067 $plugins[] = $plugin.'@'.$version;
1069 if (!empty($plugins)) {
1070 $params['plugins'] = implode(',', $plugins);
1073 return $params;
1077 * Returns the list of cURL options to use when fetching available updates data
1079 * @return array of (string)param => (string)value
1081 protected function prepare_request_options() {
1082 global $CFG;
1084 $options = array(
1085 'CURLOPT_SSL_VERIFYHOST' => 2, // this is the default in {@link curl} class but just in case
1086 'CURLOPT_SSL_VERIFYPEER' => true,
1089 $cacertfile = $CFG->dataroot.'/moodleorgca.crt';
1090 if (is_readable($cacertfile)) {
1091 // Do not use CA certs provided by the operating system. Instead,
1092 // use this CA cert to verify the updates provider.
1093 $options['CURLOPT_CAINFO'] = $cacertfile;
1096 return $options;
1100 * Returns the current timestamp
1102 * @return int the timestamp
1104 protected function cron_current_timestamp() {
1105 return time();
1109 * Output cron debugging info
1111 * @see mtrace()
1112 * @param string $msg output message
1113 * @param string $eol end of line
1115 protected function cron_mtrace($msg, $eol = PHP_EOL) {
1116 mtrace($msg, $eol);
1120 * Decide if the autocheck feature is disabled in the server setting
1122 * @return bool true if autocheck enabled, false if disabled
1124 protected function cron_autocheck_enabled() {
1125 global $CFG;
1127 if (empty($CFG->updateautocheck)) {
1128 return false;
1129 } else {
1130 return true;
1135 * Decide if the recently fetched data are still fresh enough
1137 * @param int $now current timestamp
1138 * @return bool true if no need to re-fetch, false otherwise
1140 protected function cron_has_fresh_fetch($now) {
1141 $recent = $this->get_last_timefetched();
1143 if (empty($recent)) {
1144 return false;
1147 if ($now < $recent) {
1148 $this->cron_mtrace('The most recent fetch is reported to be in the future, this is weird!');
1149 return true;
1152 if ($now - $recent > 24 * HOURSECS) {
1153 return false;
1156 return true;
1160 * Decide if the fetch is outadated or even missing
1162 * @param int $now current timestamp
1163 * @return bool false if no need to re-fetch, true otherwise
1165 protected function cron_has_outdated_fetch($now) {
1166 $recent = $this->get_last_timefetched();
1168 if (empty($recent)) {
1169 return true;
1172 if ($now < $recent) {
1173 $this->cron_mtrace('The most recent fetch is reported to be in the future, this is weird!');
1174 return false;
1177 if ($now - $recent > 48 * HOURSECS) {
1178 return true;
1181 return false;
1185 * Returns the cron execution offset for this site
1187 * The main {@link self::cron()} is supposed to run every night in some random time
1188 * between 01:00 and 06:00 AM (local time). The exact moment is defined by so called
1189 * execution offset, that is the amount of time after 01:00 AM. The offset value is
1190 * initially generated randomly and then used consistently at the site. This way, the
1191 * regular checks against the download.moodle.org server are spread in time.
1193 * @return int the offset number of seconds from range 1 sec to 5 hours
1195 protected function cron_execution_offset() {
1196 global $CFG;
1198 if (empty($CFG->updatecronoffset)) {
1199 set_config('updatecronoffset', rand(1, 5 * HOURSECS));
1202 return $CFG->updatecronoffset;
1206 * Fetch available updates info and eventually send notification to site admins
1208 protected function cron_execute() {
1210 try {
1211 $this->restore_response();
1212 $previous = $this->recentresponse;
1213 $this->fetch();
1214 $this->restore_response(true);
1215 $current = $this->recentresponse;
1216 $changes = $this->compare_responses($previous, $current);
1217 $notifications = $this->cron_notifications($changes);
1218 $this->cron_notify($notifications);
1219 $this->cron_mtrace('done');
1220 } catch (available_update_checker_exception $e) {
1221 $this->cron_mtrace('FAILED!');
1226 * Given the list of changes in available updates, pick those to send to site admins
1228 * @param array $changes as returned by {@link self::compare_responses()}
1229 * @return array of available_update_info objects to send to site admins
1231 protected function cron_notifications(array $changes) {
1232 global $CFG;
1234 $notifications = array();
1235 $pluginman = plugin_manager::instance();
1236 $plugins = $pluginman->get_plugins(true);
1238 foreach ($changes as $component => $componentchanges) {
1239 if (empty($componentchanges)) {
1240 continue;
1242 $componentupdates = $this->get_update_info($component,
1243 array('minmaturity' => $CFG->updateminmaturity, 'notifybuilds' => $CFG->updatenotifybuilds));
1244 if (empty($componentupdates)) {
1245 continue;
1247 // notify only about those $componentchanges that are present in $componentupdates
1248 // to respect the preferences
1249 foreach ($componentchanges as $componentchange) {
1250 foreach ($componentupdates as $componentupdate) {
1251 if ($componentupdate->version == $componentchange['version']) {
1252 if ($component == 'core') {
1253 // In case of 'core', we already know that the $componentupdate
1254 // is a real update with higher version ({@see self::get_update_info()}).
1255 // We just perform additional check for the release property as there
1256 // can be two Moodle releases having the same version (e.g. 2.4.0 and 2.5dev shortly
1257 // after the release). We can do that because we have the release info
1258 // always available for the core.
1259 if ((string)$componentupdate->release === (string)$componentchange['release']) {
1260 $notifications[] = $componentupdate;
1262 } else {
1263 // Use the plugin_manager to check if the detected $componentchange
1264 // is a real update with higher version. That is, the $componentchange
1265 // is present in the array of {@link available_update_info} objects
1266 // returned by the plugin's available_updates() method.
1267 list($plugintype, $pluginname) = normalize_component($component);
1268 if (!empty($plugins[$plugintype][$pluginname])) {
1269 $availableupdates = $plugins[$plugintype][$pluginname]->available_updates();
1270 if (!empty($availableupdates)) {
1271 foreach ($availableupdates as $availableupdate) {
1272 if ($availableupdate->version == $componentchange['version']) {
1273 $notifications[] = $componentupdate;
1284 return $notifications;
1288 * Sends the given notifications to site admins via messaging API
1290 * @param array $notifications array of available_update_info objects to send
1292 protected function cron_notify(array $notifications) {
1293 global $CFG;
1295 if (empty($notifications)) {
1296 return;
1299 $admins = get_admins();
1301 if (empty($admins)) {
1302 return;
1305 $this->cron_mtrace('sending notifications ... ', '');
1307 $text = get_string('updatenotifications', 'core_admin') . PHP_EOL;
1308 $html = html_writer::tag('h1', get_string('updatenotifications', 'core_admin')) . PHP_EOL;
1310 $coreupdates = array();
1311 $pluginupdates = array();
1313 foreach ($notifications as $notification) {
1314 if ($notification->component == 'core') {
1315 $coreupdates[] = $notification;
1316 } else {
1317 $pluginupdates[] = $notification;
1321 if (!empty($coreupdates)) {
1322 $text .= PHP_EOL . get_string('updateavailable', 'core_admin') . PHP_EOL;
1323 $html .= html_writer::tag('h2', get_string('updateavailable', 'core_admin')) . PHP_EOL;
1324 $html .= html_writer::start_tag('ul') . PHP_EOL;
1325 foreach ($coreupdates as $coreupdate) {
1326 $html .= html_writer::start_tag('li');
1327 if (isset($coreupdate->release)) {
1328 $text .= get_string('updateavailable_release', 'core_admin', $coreupdate->release);
1329 $html .= html_writer::tag('strong', get_string('updateavailable_release', 'core_admin', $coreupdate->release));
1331 if (isset($coreupdate->version)) {
1332 $text .= ' '.get_string('updateavailable_version', 'core_admin', $coreupdate->version);
1333 $html .= ' '.get_string('updateavailable_version', 'core_admin', $coreupdate->version);
1335 if (isset($coreupdate->maturity)) {
1336 $text .= ' ('.get_string('maturity'.$coreupdate->maturity, 'core_admin').')';
1337 $html .= ' ('.get_string('maturity'.$coreupdate->maturity, 'core_admin').')';
1339 $text .= PHP_EOL;
1340 $html .= html_writer::end_tag('li') . PHP_EOL;
1342 $text .= PHP_EOL;
1343 $html .= html_writer::end_tag('ul') . PHP_EOL;
1345 $a = array('url' => $CFG->wwwroot.'/'.$CFG->admin.'/index.php');
1346 $text .= get_string('updateavailabledetailslink', 'core_admin', $a) . PHP_EOL;
1347 $a = array('url' => html_writer::link($CFG->wwwroot.'/'.$CFG->admin.'/index.php', $CFG->wwwroot.'/'.$CFG->admin.'/index.php'));
1348 $html .= html_writer::tag('p', get_string('updateavailabledetailslink', 'core_admin', $a)) . PHP_EOL;
1351 if (!empty($pluginupdates)) {
1352 $text .= PHP_EOL . get_string('updateavailableforplugin', 'core_admin') . PHP_EOL;
1353 $html .= html_writer::tag('h2', get_string('updateavailableforplugin', 'core_admin')) . PHP_EOL;
1355 $html .= html_writer::start_tag('ul') . PHP_EOL;
1356 foreach ($pluginupdates as $pluginupdate) {
1357 $html .= html_writer::start_tag('li');
1358 $text .= get_string('pluginname', $pluginupdate->component);
1359 $html .= html_writer::tag('strong', get_string('pluginname', $pluginupdate->component));
1361 $text .= ' ('.$pluginupdate->component.')';
1362 $html .= ' ('.$pluginupdate->component.')';
1364 $text .= ' '.get_string('updateavailable', 'core_plugin', $pluginupdate->version);
1365 $html .= ' '.get_string('updateavailable', 'core_plugin', $pluginupdate->version);
1367 $text .= PHP_EOL;
1368 $html .= html_writer::end_tag('li') . PHP_EOL;
1370 $text .= PHP_EOL;
1371 $html .= html_writer::end_tag('ul') . PHP_EOL;
1373 $a = array('url' => $CFG->wwwroot.'/'.$CFG->admin.'/plugins.php');
1374 $text .= get_string('updateavailabledetailslink', 'core_admin', $a) . PHP_EOL;
1375 $a = array('url' => html_writer::link($CFG->wwwroot.'/'.$CFG->admin.'/plugins.php', $CFG->wwwroot.'/'.$CFG->admin.'/plugins.php'));
1376 $html .= html_writer::tag('p', get_string('updateavailabledetailslink', 'core_admin', $a)) . PHP_EOL;
1379 $a = array('siteurl' => $CFG->wwwroot);
1380 $text .= get_string('updatenotificationfooter', 'core_admin', $a) . PHP_EOL;
1381 $a = array('siteurl' => html_writer::link($CFG->wwwroot, $CFG->wwwroot));
1382 $html .= html_writer::tag('footer', html_writer::tag('p', get_string('updatenotificationfooter', 'core_admin', $a),
1383 array('style' => 'font-size:smaller; color:#333;')));
1385 foreach ($admins as $admin) {
1386 $message = new stdClass();
1387 $message->component = 'moodle';
1388 $message->name = 'availableupdate';
1389 $message->userfrom = get_admin();
1390 $message->userto = $admin;
1391 $message->subject = get_string('updatenotificationsubject', 'core_admin', array('siteurl' => $CFG->wwwroot));
1392 $message->fullmessage = $text;
1393 $message->fullmessageformat = FORMAT_PLAIN;
1394 $message->fullmessagehtml = $html;
1395 $message->smallmessage = get_string('updatenotifications', 'core_admin');
1396 $message->notification = 1;
1397 message_send($message);
1402 * Compare two release labels and decide if they are the same
1404 * @param string $remote release info of the available update
1405 * @param null|string $local release info of the local code, defaults to $release defined in version.php
1406 * @return boolean true if the releases declare the same minor+major version
1408 protected function is_same_release($remote, $local=null) {
1410 if (is_null($local)) {
1411 $this->load_current_environment();
1412 $local = $this->currentrelease;
1415 $pattern = '/^([0-9\.\+]+)([^(]*)/';
1417 preg_match($pattern, $remote, $remotematches);
1418 preg_match($pattern, $local, $localmatches);
1420 $remotematches[1] = str_replace('+', '', $remotematches[1]);
1421 $localmatches[1] = str_replace('+', '', $localmatches[1]);
1423 if ($remotematches[1] === $localmatches[1] and rtrim($remotematches[2]) === rtrim($localmatches[2])) {
1424 return true;
1425 } else {
1426 return false;
1433 * Defines the structure of objects returned by {@link available_update_checker::get_update_info()}
1435 class available_update_info {
1437 /** @var string frankenstyle component name */
1438 public $component;
1439 /** @var int the available version of the component */
1440 public $version;
1441 /** @var string|null optional release name */
1442 public $release = null;
1443 /** @var int|null optional maturity info, eg {@link MATURITY_STABLE} */
1444 public $maturity = null;
1445 /** @var string|null optional URL of a page with more info about the update */
1446 public $url = null;
1447 /** @var string|null optional URL of a ZIP package that can be downloaded and installed */
1448 public $download = null;
1449 /** @var string|null of self::download is set, then this must be the MD5 hash of the ZIP */
1450 public $downloadmd5 = null;
1453 * Creates new instance of the class
1455 * The $info array must provide at least the 'version' value and optionally all other
1456 * values to populate the object's properties.
1458 * @param string $name the frankenstyle component name
1459 * @param array $info associative array with other properties
1461 public function __construct($name, array $info) {
1462 $this->component = $name;
1463 foreach ($info as $k => $v) {
1464 if (property_exists('available_update_info', $k) and $k != 'component') {
1465 $this->$k = $v;
1473 * Implements a communication bridge to the mdeploy.php utility
1475 class available_update_deployer {
1477 const HTTP_PARAM_PREFIX = 'updteautodpldata_'; // Hey, even Google has not heard of such a prefix! So it MUST be safe :-p
1478 const HTTP_PARAM_CHECKER = 'datapackagesize'; // Name of the parameter that holds the number of items in the received data items
1480 /** @var available_update_deployer holds the singleton instance */
1481 protected static $singletoninstance;
1482 /** @var moodle_url URL of a page that includes the deployer UI */
1483 protected $callerurl;
1484 /** @var moodle_url URL to return after the deployment */
1485 protected $returnurl;
1488 * Direct instantiation not allowed, use the factory method {@link self::instance()}
1490 protected function __construct() {
1494 * Sorry, this is singleton
1496 protected function __clone() {
1500 * Factory method for this class
1502 * @return available_update_deployer the singleton instance
1504 public static function instance() {
1505 if (is_null(self::$singletoninstance)) {
1506 self::$singletoninstance = new self();
1508 return self::$singletoninstance;
1512 * Reset caches used by this script
1514 * @param bool $phpunitreset is this called as a part of PHPUnit reset?
1516 public static function reset_caches($phpunitreset = false) {
1517 if ($phpunitreset) {
1518 self::$singletoninstance = null;
1523 * Is automatic deployment enabled?
1525 * @return bool
1527 public function enabled() {
1528 global $CFG;
1530 if (!empty($CFG->disableupdateautodeploy)) {
1531 // The feature is prohibited via config.php
1532 return false;
1535 return get_config('updateautodeploy');
1539 * Sets some base properties of the class to make it usable.
1541 * @param moodle_url $callerurl the base URL of a script that will handle the class'es form data
1542 * @param moodle_url $returnurl the final URL to return to when the deployment is finished
1544 public function initialize(moodle_url $callerurl, moodle_url $returnurl) {
1546 if (!$this->enabled()) {
1547 throw new coding_exception('Unable to initialize the deployer, the feature is not enabled.');
1550 $this->callerurl = $callerurl;
1551 $this->returnurl = $returnurl;
1555 * Has the deployer been initialized?
1557 * Initialized deployer means that the following properties were set:
1558 * callerurl, returnurl
1560 * @return bool
1562 public function initialized() {
1564 if (!$this->enabled()) {
1565 return false;
1568 if (empty($this->callerurl)) {
1569 return false;
1572 if (empty($this->returnurl)) {
1573 return false;
1576 return true;
1580 * Returns a list of reasons why the deployment can not happen
1582 * If the returned array is empty, the deployment seems to be possible. The returned
1583 * structure is an associative array with keys representing individual impediments.
1584 * Possible keys are: missingdownloadurl, missingdownloadmd5, notwritable.
1586 * @param available_update_info $info
1587 * @return array
1589 public function deployment_impediments(available_update_info $info) {
1591 $impediments = array();
1593 if (empty($info->download)) {
1594 $impediments['missingdownloadurl'] = true;
1597 if (empty($info->downloadmd5)) {
1598 $impediments['missingdownloadmd5'] = true;
1601 if (!empty($info->download) and !$this->update_downloadable($info->download)) {
1602 $impediments['notdownloadable'] = true;
1605 if (!$this->component_writable($info->component)) {
1606 $impediments['notwritable'] = true;
1609 return $impediments;
1613 * Check to see if the current version of the plugin seems to be a checkout of an external repository.
1615 * @param available_update_info $info
1616 * @return false|string
1618 public function plugin_external_source(available_update_info $info) {
1620 $paths = get_plugin_types(true);
1621 list($plugintype, $pluginname) = normalize_component($info->component);
1622 $pluginroot = $paths[$plugintype].'/'.$pluginname;
1624 if (is_dir($pluginroot.'/.git')) {
1625 return 'git';
1628 if (is_dir($pluginroot.'/CVS')) {
1629 return 'cvs';
1632 if (is_dir($pluginroot.'/.svn')) {
1633 return 'svn';
1636 return false;
1640 * Prepares a renderable widget to confirm installation of an available update.
1642 * @param available_update_info $info component version to deploy
1643 * @return renderable
1645 public function make_confirm_widget(available_update_info $info) {
1647 if (!$this->initialized()) {
1648 throw new coding_exception('Illegal method call - deployer not initialized.');
1651 $params = $this->data_to_params(array(
1652 'updateinfo' => (array)$info, // see http://www.php.net/manual/en/language.types.array.php#language.types.array.casting
1655 $widget = new single_button(
1656 new moodle_url($this->callerurl, $params),
1657 get_string('updateavailableinstall', 'core_admin'),
1658 'post'
1661 return $widget;
1665 * Prepares a renderable widget to execute installation of an available update.
1667 * @param available_update_info $info component version to deploy
1668 * @return renderable
1670 public function make_execution_widget(available_update_info $info) {
1671 global $CFG;
1673 if (!$this->initialized()) {
1674 throw new coding_exception('Illegal method call - deployer not initialized.');
1677 $pluginrootpaths = get_plugin_types(true);
1679 list($plugintype, $pluginname) = normalize_component($info->component);
1681 if (empty($pluginrootpaths[$plugintype])) {
1682 throw new coding_exception('Unknown plugin type root location', $plugintype);
1685 list($passfile, $password) = $this->prepare_authorization();
1687 $upgradeurl = new moodle_url('/admin');
1689 $params = array(
1690 'upgrade' => true,
1691 'type' => $plugintype,
1692 'name' => $pluginname,
1693 'typeroot' => $pluginrootpaths[$plugintype],
1694 'package' => $info->download,
1695 'md5' => $info->downloadmd5,
1696 'dataroot' => $CFG->dataroot,
1697 'dirroot' => $CFG->dirroot,
1698 'passfile' => $passfile,
1699 'password' => $password,
1700 'returnurl' => $upgradeurl->out(true),
1703 if (!empty($CFG->proxyhost)) {
1704 // MDL-36973 - Beware - we should call just !is_proxybypass() here. But currently, our
1705 // cURL wrapper class does not do it. So, to have consistent behaviour, we pass proxy
1706 // setting regardless the $CFG->proxybypass setting. Once the {@link curl} class is
1707 // fixed, the condition should be amended.
1708 if (true or !is_proxybypass($info->download)) {
1709 if (empty($CFG->proxyport)) {
1710 $params['proxy'] = $CFG->proxyhost;
1711 } else {
1712 $params['proxy'] = $CFG->proxyhost.':'.$CFG->proxyport;
1715 if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {
1716 $params['proxyuserpwd'] = $CFG->proxyuser.':'.$CFG->proxypassword;
1719 if (!empty($CFG->proxytype)) {
1720 $params['proxytype'] = $CFG->proxytype;
1725 $widget = new single_button(
1726 new moodle_url('/mdeploy.php', $params),
1727 get_string('updateavailableinstall', 'core_admin'),
1728 'post'
1731 return $widget;
1735 * Returns array of data objects passed to this tool.
1737 * @return array
1739 public function submitted_data() {
1741 $data = $this->params_to_data($_POST);
1743 if (empty($data) or empty($data[self::HTTP_PARAM_CHECKER])) {
1744 return false;
1747 if (!empty($data['updateinfo']) and is_object($data['updateinfo'])) {
1748 $updateinfo = $data['updateinfo'];
1749 if (!empty($updateinfo->component) and !empty($updateinfo->version)) {
1750 $data['updateinfo'] = new available_update_info($updateinfo->component, (array)$updateinfo);
1754 if (!empty($data['callerurl'])) {
1755 $data['callerurl'] = new moodle_url($data['callerurl']);
1758 if (!empty($data['returnurl'])) {
1759 $data['returnurl'] = new moodle_url($data['returnurl']);
1762 return $data;
1766 * Handles magic getters and setters for protected properties.
1768 * @param string $name method name, e.g. set_returnurl()
1769 * @param array $arguments arguments to be passed to the array
1771 public function __call($name, array $arguments = array()) {
1773 if (substr($name, 0, 4) === 'set_') {
1774 $property = substr($name, 4);
1775 if (empty($property)) {
1776 throw new coding_exception('Invalid property name (empty)');
1778 if (empty($arguments)) {
1779 $arguments = array(true); // Default value for flag-like properties.
1781 // Make sure it is a protected property.
1782 $isprotected = false;
1783 $reflection = new ReflectionObject($this);
1784 foreach ($reflection->getProperties(ReflectionProperty::IS_PROTECTED) as $reflectionproperty) {
1785 if ($reflectionproperty->getName() === $property) {
1786 $isprotected = true;
1787 break;
1790 if (!$isprotected) {
1791 throw new coding_exception('Unable to set property - it does not exist or it is not protected');
1793 $value = reset($arguments);
1794 $this->$property = $value;
1795 return;
1798 if (substr($name, 0, 4) === 'get_') {
1799 $property = substr($name, 4);
1800 if (empty($property)) {
1801 throw new coding_exception('Invalid property name (empty)');
1803 if (!empty($arguments)) {
1804 throw new coding_exception('No parameter expected');
1806 // Make sure it is a protected property.
1807 $isprotected = false;
1808 $reflection = new ReflectionObject($this);
1809 foreach ($reflection->getProperties(ReflectionProperty::IS_PROTECTED) as $reflectionproperty) {
1810 if ($reflectionproperty->getName() === $property) {
1811 $isprotected = true;
1812 break;
1815 if (!$isprotected) {
1816 throw new coding_exception('Unable to get property - it does not exist or it is not protected');
1818 return $this->$property;
1823 * Generates a random token and stores it in a file in moodledata directory.
1825 * @return array of the (string)filename and (string)password in this order
1827 public function prepare_authorization() {
1828 global $CFG;
1830 make_upload_directory('mdeploy/auth/');
1832 $attempts = 0;
1833 $success = false;
1835 while (!$success and $attempts < 5) {
1836 $attempts++;
1838 $passfile = $this->generate_passfile();
1839 $password = $this->generate_password();
1840 $now = time();
1842 $filepath = $CFG->dataroot.'/mdeploy/auth/'.$passfile;
1844 if (!file_exists($filepath)) {
1845 $success = file_put_contents($filepath, $password . PHP_EOL . $now . PHP_EOL, LOCK_EX);
1849 if ($success) {
1850 return array($passfile, $password);
1852 } else {
1853 throw new moodle_exception('unable_prepare_authorization', 'core_plugin');
1857 // End of external API
1860 * Prepares an array of HTTP parameters that can be passed to another page.
1862 * @param array|object $data associative array or an object holding the data, data JSON-able
1863 * @return array suitable as a param for moodle_url
1865 protected function data_to_params($data) {
1867 // Append some our own data
1868 if (!empty($this->callerurl)) {
1869 $data['callerurl'] = $this->callerurl->out(false);
1871 if (!empty($this->callerurl)) {
1872 $data['returnurl'] = $this->returnurl->out(false);
1875 // Finally append the count of items in the package.
1876 $data[self::HTTP_PARAM_CHECKER] = count($data);
1878 // Generate params
1879 $params = array();
1880 foreach ($data as $name => $value) {
1881 $transname = self::HTTP_PARAM_PREFIX.$name;
1882 $transvalue = json_encode($value);
1883 $params[$transname] = $transvalue;
1886 return $params;
1890 * Converts HTTP parameters passed to the script into native PHP data
1892 * @param array $params such as $_REQUEST or $_POST
1893 * @return array data passed for this class
1895 protected function params_to_data(array $params) {
1897 if (empty($params)) {
1898 return array();
1901 $data = array();
1902 foreach ($params as $name => $value) {
1903 if (strpos($name, self::HTTP_PARAM_PREFIX) === 0) {
1904 $realname = substr($name, strlen(self::HTTP_PARAM_PREFIX));
1905 $realvalue = json_decode($value);
1906 $data[$realname] = $realvalue;
1910 return $data;
1914 * Returns a random string to be used as a filename of the password storage.
1916 * @return string
1918 protected function generate_passfile() {
1919 return clean_param(uniqid('mdeploy_', true), PARAM_FILE);
1923 * Returns a random string to be used as the authorization token
1925 * @return string
1927 protected function generate_password() {
1928 return complex_random_string();
1932 * Checks if the given component's directory is writable
1934 * For the purpose of the deployment, the web server process has to have
1935 * write access to all files in the component's directory (recursively) and for the
1936 * directory itself.
1938 * @see worker::move_directory_source_precheck()
1939 * @param string $component normalized component name
1940 * @return boolean
1942 protected function component_writable($component) {
1944 list($plugintype, $pluginname) = normalize_component($component);
1946 $directory = get_plugin_directory($plugintype, $pluginname);
1948 if (is_null($directory)) {
1949 throw new coding_exception('Unknown component location', $component);
1952 return $this->directory_writable($directory);
1956 * Checks if the mdeploy.php will be able to fetch the ZIP from the given URL
1958 * This is mainly supposed to check if the transmission over HTTPS would
1959 * work. That is, if the CA certificates are present at the server.
1961 * @param string $downloadurl the URL of the ZIP package to download
1962 * @return bool
1964 protected function update_downloadable($downloadurl) {
1965 global $CFG;
1967 $curloptions = array(
1968 'CURLOPT_SSL_VERIFYHOST' => 2, // this is the default in {@link curl} class but just in case
1969 'CURLOPT_SSL_VERIFYPEER' => true,
1972 $cacertfile = $CFG->dataroot.'/moodleorgca.crt';
1973 if (is_readable($cacertfile)) {
1974 // Do not use CA certs provided by the operating system. Instead,
1975 // use this CA cert to verify the updates provider.
1976 $curloptions['CURLOPT_CAINFO'] = $cacertfile;
1979 $curl = new curl(array('proxy' => true));
1980 $result = $curl->head($downloadurl, $curloptions);
1981 $errno = $curl->get_errno();
1982 if (empty($errno)) {
1983 return true;
1984 } else {
1985 return false;
1990 * Checks if the directory and all its contents (recursively) is writable
1992 * @param string $path full path to a directory
1993 * @return boolean
1995 private function directory_writable($path) {
1997 if (!is_writable($path)) {
1998 return false;
2001 if (is_dir($path)) {
2002 $handle = opendir($path);
2003 } else {
2004 return false;
2007 $result = true;
2009 while ($filename = readdir($handle)) {
2010 $filepath = $path.'/'.$filename;
2012 if ($filename === '.' or $filename === '..') {
2013 continue;
2016 if (is_dir($filepath)) {
2017 $result = $result && $this->directory_writable($filepath);
2019 } else {
2020 $result = $result && is_writable($filepath);
2024 closedir($handle);
2026 return $result;
2032 * Factory class producing required subclasses of {@link plugininfo_base}
2034 class plugininfo_default_factory {
2037 * Makes a new instance of the plugininfo class
2039 * @param string $type the plugin type, eg. 'mod'
2040 * @param string $typerootdir full path to the location of all the plugins of this type
2041 * @param string $name the plugin name, eg. 'workshop'
2042 * @param string $namerootdir full path to the location of the plugin
2043 * @param string $typeclass the name of class that holds the info about the plugin
2044 * @return plugininfo_base the instance of $typeclass
2046 public static function make($type, $typerootdir, $name, $namerootdir, $typeclass) {
2047 $plugin = new $typeclass();
2048 $plugin->type = $type;
2049 $plugin->typerootdir = $typerootdir;
2050 $plugin->name = $name;
2051 $plugin->rootdir = $namerootdir;
2053 $plugin->init_display_name();
2054 $plugin->load_disk_version();
2055 $plugin->load_db_version();
2056 $plugin->load_required_main_version();
2057 $plugin->init_is_standard();
2059 return $plugin;
2065 * Base class providing access to the information about a plugin
2067 * @property-read string component the component name, type_name
2069 abstract class plugininfo_base {
2071 /** @var string the plugintype name, eg. mod, auth or workshopform */
2072 public $type;
2073 /** @var string full path to the location of all the plugins of this type */
2074 public $typerootdir;
2075 /** @var string the plugin name, eg. assignment, ldap */
2076 public $name;
2077 /** @var string the localized plugin name */
2078 public $displayname;
2079 /** @var string the plugin source, one of plugin_manager::PLUGIN_SOURCE_xxx constants */
2080 public $source;
2081 /** @var fullpath to the location of this plugin */
2082 public $rootdir;
2083 /** @var int|string the version of the plugin's source code */
2084 public $versiondisk;
2085 /** @var int|string the version of the installed plugin */
2086 public $versiondb;
2087 /** @var int|float|string required version of Moodle core */
2088 public $versionrequires;
2089 /** @var array other plugins that this one depends on, lazy-loaded by {@link get_other_required_plugins()} */
2090 public $dependencies;
2091 /** @var int number of instances of the plugin - not supported yet */
2092 public $instances;
2093 /** @var int order of the plugin among other plugins of the same type - not supported yet */
2094 public $sortorder;
2095 /** @var array|null array of {@link available_update_info} for this plugin */
2096 public $availableupdates;
2099 * Gathers and returns the information about all plugins of the given type
2101 * @param string $type the name of the plugintype, eg. mod, auth or workshopform
2102 * @param string $typerootdir full path to the location of the plugin dir
2103 * @param string $typeclass the name of the actually called class
2104 * @return array of plugintype classes, indexed by the plugin name
2106 public static function get_plugins($type, $typerootdir, $typeclass) {
2108 // get the information about plugins at the disk
2109 $plugins = get_plugin_list($type);
2110 $ondisk = array();
2111 foreach ($plugins as $pluginname => $pluginrootdir) {
2112 $ondisk[$pluginname] = plugininfo_default_factory::make($type, $typerootdir,
2113 $pluginname, $pluginrootdir, $typeclass);
2115 return $ondisk;
2119 * Sets {@link $displayname} property to a localized name of the plugin
2121 public function init_display_name() {
2122 if (!get_string_manager()->string_exists('pluginname', $this->component)) {
2123 $this->displayname = '[pluginname,' . $this->component . ']';
2124 } else {
2125 $this->displayname = get_string('pluginname', $this->component);
2130 * Magic method getter, redirects to read only values.
2132 * @param string $name
2133 * @return mixed
2135 public function __get($name) {
2136 switch ($name) {
2137 case 'component': return $this->type . '_' . $this->name;
2139 default:
2140 debugging('Invalid plugin property accessed! '.$name);
2141 return null;
2146 * Return the full path name of a file within the plugin.
2148 * No check is made to see if the file exists.
2150 * @param string $relativepath e.g. 'version.php'.
2151 * @return string e.g. $CFG->dirroot . '/mod/quiz/version.php'.
2153 public function full_path($relativepath) {
2154 if (empty($this->rootdir)) {
2155 return '';
2157 return $this->rootdir . '/' . $relativepath;
2161 * Load the data from version.php.
2163 * @return stdClass the object called $plugin defined in version.php
2165 protected function load_version_php() {
2166 $versionfile = $this->full_path('version.php');
2168 $plugin = new stdClass();
2169 if (is_readable($versionfile)) {
2170 include($versionfile);
2172 return $plugin;
2176 * Sets {@link $versiondisk} property to a numerical value representing the
2177 * version of the plugin's source code.
2179 * If the value is null after calling this method, either the plugin
2180 * does not use versioning (typically does not have any database
2181 * data) or is missing from disk.
2183 public function load_disk_version() {
2184 $plugin = $this->load_version_php();
2185 if (isset($plugin->version)) {
2186 $this->versiondisk = $plugin->version;
2191 * Sets {@link $versionrequires} property to a numerical value representing
2192 * the version of Moodle core that this plugin requires.
2194 public function load_required_main_version() {
2195 $plugin = $this->load_version_php();
2196 if (isset($plugin->requires)) {
2197 $this->versionrequires = $plugin->requires;
2202 * Initialise {@link $dependencies} to the list of other plugins (in any)
2203 * that this one requires to be installed.
2205 protected function load_other_required_plugins() {
2206 $plugin = $this->load_version_php();
2207 if (!empty($plugin->dependencies)) {
2208 $this->dependencies = $plugin->dependencies;
2209 } else {
2210 $this->dependencies = array(); // By default, no dependencies.
2215 * Get the list of other plugins that this plugin requires to be installed.
2217 * @return array with keys the frankenstyle plugin name, and values either
2218 * a version string (like '2011101700') or the constant ANY_VERSION.
2220 public function get_other_required_plugins() {
2221 if (is_null($this->dependencies)) {
2222 $this->load_other_required_plugins();
2224 return $this->dependencies;
2228 * Sets {@link $versiondb} property to a numerical value representing the
2229 * currently installed version of the plugin.
2231 * If the value is null after calling this method, either the plugin
2232 * does not use versioning (typically does not have any database
2233 * data) or has not been installed yet.
2235 public function load_db_version() {
2236 if ($ver = self::get_version_from_config_plugins($this->component)) {
2237 $this->versiondb = $ver;
2242 * Sets {@link $source} property to one of plugin_manager::PLUGIN_SOURCE_xxx
2243 * constants.
2245 * If the property's value is null after calling this method, then
2246 * the type of the plugin has not been recognized and you should throw
2247 * an exception.
2249 public function init_is_standard() {
2251 $standard = plugin_manager::standard_plugins_list($this->type);
2253 if ($standard !== false) {
2254 $standard = array_flip($standard);
2255 if (isset($standard[$this->name])) {
2256 $this->source = plugin_manager::PLUGIN_SOURCE_STANDARD;
2257 } else if (!is_null($this->versiondb) and is_null($this->versiondisk)
2258 and plugin_manager::is_deleted_standard_plugin($this->type, $this->name)) {
2259 $this->source = plugin_manager::PLUGIN_SOURCE_STANDARD; // to be deleted
2260 } else {
2261 $this->source = plugin_manager::PLUGIN_SOURCE_EXTENSION;
2267 * Returns true if the plugin is shipped with the official distribution
2268 * of the current Moodle version, false otherwise.
2270 * @return bool
2272 public function is_standard() {
2273 return $this->source === plugin_manager::PLUGIN_SOURCE_STANDARD;
2277 * Returns true if the the given Moodle version is enough to run this plugin
2279 * @param string|int|double $moodleversion
2280 * @return bool
2282 public function is_core_dependency_satisfied($moodleversion) {
2284 if (empty($this->versionrequires)) {
2285 return true;
2287 } else {
2288 return (double)$this->versionrequires <= (double)$moodleversion;
2293 * Returns the status of the plugin
2295 * @return string one of plugin_manager::PLUGIN_STATUS_xxx constants
2297 public function get_status() {
2299 if (is_null($this->versiondb) and is_null($this->versiondisk)) {
2300 return plugin_manager::PLUGIN_STATUS_NODB;
2302 } else if (is_null($this->versiondb) and !is_null($this->versiondisk)) {
2303 return plugin_manager::PLUGIN_STATUS_NEW;
2305 } else if (!is_null($this->versiondb) and is_null($this->versiondisk)) {
2306 if (plugin_manager::is_deleted_standard_plugin($this->type, $this->name)) {
2307 return plugin_manager::PLUGIN_STATUS_DELETE;
2308 } else {
2309 return plugin_manager::PLUGIN_STATUS_MISSING;
2312 } else if ((string)$this->versiondb === (string)$this->versiondisk) {
2313 return plugin_manager::PLUGIN_STATUS_UPTODATE;
2315 } else if ($this->versiondb < $this->versiondisk) {
2316 return plugin_manager::PLUGIN_STATUS_UPGRADE;
2318 } else if ($this->versiondb > $this->versiondisk) {
2319 return plugin_manager::PLUGIN_STATUS_DOWNGRADE;
2321 } else {
2322 // $version = pi(); and similar funny jokes - hopefully Donald E. Knuth will never contribute to Moodle ;-)
2323 throw new coding_exception('Unable to determine plugin state, check the plugin versions');
2328 * Returns the information about plugin availability
2330 * True means that the plugin is enabled. False means that the plugin is
2331 * disabled. Null means that the information is not available, or the
2332 * plugin does not support configurable availability or the availability
2333 * can not be changed.
2335 * @return null|bool
2337 public function is_enabled() {
2338 return null;
2342 * Populates the property {@link $availableupdates} with the information provided by
2343 * available update checker
2345 * @param available_update_checker $provider the class providing the available update info
2347 public function check_available_updates(available_update_checker $provider) {
2348 global $CFG;
2350 if (isset($CFG->updateminmaturity)) {
2351 $minmaturity = $CFG->updateminmaturity;
2352 } else {
2353 // this can happen during the very first upgrade to 2.3
2354 $minmaturity = MATURITY_STABLE;
2357 $this->availableupdates = $provider->get_update_info($this->component,
2358 array('minmaturity' => $minmaturity));
2362 * If there are updates for this plugin available, returns them.
2364 * Returns array of {@link available_update_info} objects, if some update
2365 * is available. Returns null if there is no update available or if the update
2366 * availability is unknown.
2368 * @return array|null
2370 public function available_updates() {
2372 if (empty($this->availableupdates) or !is_array($this->availableupdates)) {
2373 return null;
2376 $updates = array();
2378 foreach ($this->availableupdates as $availableupdate) {
2379 if ($availableupdate->version > $this->versiondisk) {
2380 $updates[] = $availableupdate;
2384 if (empty($updates)) {
2385 return null;
2388 return $updates;
2392 * Returns the node name used in admin settings menu for this plugin settings (if applicable)
2394 * @return null|string node name or null if plugin does not create settings node (default)
2396 public function get_settings_section_name() {
2397 return null;
2401 * Returns the URL of the plugin settings screen
2403 * Null value means that the plugin either does not have the settings screen
2404 * or its location is not available via this library.
2406 * @return null|moodle_url
2408 public function get_settings_url() {
2409 $section = $this->get_settings_section_name();
2410 if ($section === null) {
2411 return null;
2413 $settings = admin_get_root()->locate($section);
2414 if ($settings && $settings instanceof admin_settingpage) {
2415 return new moodle_url('/admin/settings.php', array('section' => $section));
2416 } else if ($settings && $settings instanceof admin_externalpage) {
2417 return new moodle_url($settings->url);
2418 } else {
2419 return null;
2424 * Loads plugin settings to the settings tree
2426 * This function usually includes settings.php file in plugins folder.
2427 * Alternatively it can create a link to some settings page (instance of admin_externalpage)
2429 * @param part_of_admin_tree $adminroot
2430 * @param string $parentnodename
2431 * @param bool $hassiteconfig whether the current user has moodle/site:config capability
2433 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
2437 * Returns the URL of the screen where this plugin can be uninstalled
2439 * Visiting that URL must be safe, that is a manual confirmation is needed
2440 * for actual uninstallation of the plugin. Null value means that the
2441 * plugin either does not support uninstallation, or does not require any
2442 * database cleanup or the location of the screen is not available via this
2443 * library.
2445 * @return null|moodle_url
2447 public function get_uninstall_url() {
2448 return null;
2452 * Returns relative directory of the plugin with heading '/'
2454 * @return string
2456 public function get_dir() {
2457 global $CFG;
2459 return substr($this->rootdir, strlen($CFG->dirroot));
2463 * Provides access to plugin versions from {config_plugins}
2465 * @param string $plugin plugin name
2466 * @param double $disablecache optional, defaults to false
2467 * @return int|false the stored value or false if not found
2469 protected function get_version_from_config_plugins($plugin, $disablecache=false) {
2470 global $DB;
2471 static $pluginversions = null;
2473 if (is_null($pluginversions) or $disablecache) {
2474 try {
2475 $pluginversions = $DB->get_records_menu('config_plugins', array('name' => 'version'), 'plugin', 'plugin,value');
2476 } catch (dml_exception $e) {
2477 // before install
2478 $pluginversions = array();
2482 if (!array_key_exists($plugin, $pluginversions)) {
2483 return false;
2486 return $pluginversions[$plugin];
2492 * General class for all plugin types that do not have their own class
2494 class plugininfo_general extends plugininfo_base {
2499 * Class for page side blocks
2501 class plugininfo_block extends plugininfo_base {
2503 public static function get_plugins($type, $typerootdir, $typeclass) {
2505 // get the information about blocks at the disk
2506 $blocks = parent::get_plugins($type, $typerootdir, $typeclass);
2508 // add blocks missing from disk
2509 $blocksinfo = self::get_blocks_info();
2510 foreach ($blocksinfo as $blockname => $blockinfo) {
2511 if (isset($blocks[$blockname])) {
2512 continue;
2514 $plugin = new $typeclass();
2515 $plugin->type = $type;
2516 $plugin->typerootdir = $typerootdir;
2517 $plugin->name = $blockname;
2518 $plugin->rootdir = null;
2519 $plugin->displayname = $blockname;
2520 $plugin->versiondb = $blockinfo->version;
2521 $plugin->init_is_standard();
2523 $blocks[$blockname] = $plugin;
2526 return $blocks;
2530 * Magic method getter, redirects to read only values.
2532 * For block plugins pretends the object has 'visible' property for compatibility
2533 * with plugins developed for Moodle version below 2.4
2535 * @param string $name
2536 * @return mixed
2538 public function __get($name) {
2539 if ($name === 'visible') {
2540 debugging('This is now an instance of plugininfo_block, please use $block->is_enabled() instead of $block->visible', DEBUG_DEVELOPER);
2541 return ($this->is_enabled() !== false);
2543 return parent::__get($name);
2546 public function init_display_name() {
2548 if (get_string_manager()->string_exists('pluginname', 'block_' . $this->name)) {
2549 $this->displayname = get_string('pluginname', 'block_' . $this->name);
2551 } else if (($block = block_instance($this->name)) !== false) {
2552 $this->displayname = $block->get_title();
2554 } else {
2555 parent::init_display_name();
2559 public function load_db_version() {
2560 global $DB;
2562 $blocksinfo = self::get_blocks_info();
2563 if (isset($blocksinfo[$this->name]->version)) {
2564 $this->versiondb = $blocksinfo[$this->name]->version;
2568 public function is_enabled() {
2570 $blocksinfo = self::get_blocks_info();
2571 if (isset($blocksinfo[$this->name]->visible)) {
2572 if ($blocksinfo[$this->name]->visible) {
2573 return true;
2574 } else {
2575 return false;
2577 } else {
2578 return parent::is_enabled();
2582 public function get_settings_section_name() {
2583 return 'blocksetting' . $this->name;
2586 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
2587 global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
2588 $ADMIN = $adminroot; // may be used in settings.php
2589 $block = $this; // also can be used inside settings.php
2590 $section = $this->get_settings_section_name();
2592 if (!$hassiteconfig || (($blockinstance = block_instance($this->name)) === false)) {
2593 return;
2596 $settings = null;
2597 if ($blockinstance->has_config()) {
2598 if (file_exists($this->full_path('settings.php'))) {
2599 $settings = new admin_settingpage($section, $this->displayname,
2600 'moodle/site:config', $this->is_enabled() === false);
2601 include($this->full_path('settings.php')); // this may also set $settings to null
2602 } else {
2603 $blocksinfo = self::get_blocks_info();
2604 $settingsurl = new moodle_url('/admin/block.php', array('block' => $blocksinfo[$this->name]->id));
2605 $settings = new admin_externalpage($section, $this->displayname,
2606 $settingsurl, 'moodle/site:config', $this->is_enabled() === false);
2609 if ($settings) {
2610 $ADMIN->add($parentnodename, $settings);
2614 public function get_uninstall_url() {
2616 $blocksinfo = self::get_blocks_info();
2617 return new moodle_url('/admin/blocks.php', array('delete' => $blocksinfo[$this->name]->id, 'sesskey' => sesskey()));
2621 * Provides access to the records in {block} table
2623 * @param bool $disablecache do not use internal static cache
2624 * @return array array of stdClasses
2626 protected static function get_blocks_info($disablecache=false) {
2627 global $DB;
2628 static $blocksinfocache = null;
2630 if (is_null($blocksinfocache) or $disablecache) {
2631 try {
2632 $blocksinfocache = $DB->get_records('block', null, 'name', 'name,id,version,visible');
2633 } catch (dml_exception $e) {
2634 // before install
2635 $blocksinfocache = array();
2639 return $blocksinfocache;
2645 * Class for text filters
2647 class plugininfo_filter extends plugininfo_base {
2649 public static function get_plugins($type, $typerootdir, $typeclass) {
2650 global $CFG, $DB;
2652 $filters = array();
2654 // get the list of filters from both /filter and /mod location
2655 $installed = filter_get_all_installed();
2657 foreach ($installed as $filterlegacyname => $displayname) {
2658 $plugin = new $typeclass();
2659 $plugin->type = $type;
2660 $plugin->typerootdir = $typerootdir;
2661 $plugin->name = self::normalize_legacy_name($filterlegacyname);
2662 $plugin->rootdir = $CFG->dirroot . '/' . $filterlegacyname;
2663 $plugin->displayname = $displayname;
2665 $plugin->load_disk_version();
2666 $plugin->load_db_version();
2667 $plugin->load_required_main_version();
2668 $plugin->init_is_standard();
2670 $filters[$plugin->name] = $plugin;
2673 $globalstates = self::get_global_states();
2675 if ($DB->get_manager()->table_exists('filter_active')) {
2676 // if we're upgrading from 1.9, the table does not exist yet
2677 // if it does, make sure that all installed filters are registered
2678 $needsreload = false;
2679 foreach (array_keys($installed) as $filterlegacyname) {
2680 if (!isset($globalstates[self::normalize_legacy_name($filterlegacyname)])) {
2681 filter_set_global_state($filterlegacyname, TEXTFILTER_DISABLED);
2682 $needsreload = true;
2685 if ($needsreload) {
2686 $globalstates = self::get_global_states(true);
2690 // make sure that all registered filters are installed, just in case
2691 foreach ($globalstates as $name => $info) {
2692 if (!isset($filters[$name])) {
2693 // oops, there is a record in filter_active but the filter is not installed
2694 $plugin = new $typeclass();
2695 $plugin->type = $type;
2696 $plugin->typerootdir = $typerootdir;
2697 $plugin->name = $name;
2698 $plugin->rootdir = $CFG->dirroot . '/' . $info->legacyname;
2699 $plugin->displayname = $info->legacyname;
2701 $plugin->load_db_version();
2703 if (is_null($plugin->versiondb)) {
2704 // this is a hack to stimulate 'Missing from disk' error
2705 // because $plugin->versiondisk will be null !== false
2706 $plugin->versiondb = false;
2709 $filters[$plugin->name] = $plugin;
2713 return $filters;
2716 public function init_display_name() {
2717 // do nothing, the name is set in self::get_plugins()
2721 * @see load_version_php()
2723 protected function load_version_php() {
2724 if (strpos($this->name, 'mod_') === 0) {
2725 // filters bundled with modules do not have a version.php and so
2726 // do not provide their own versioning information.
2727 return new stdClass();
2729 return parent::load_version_php();
2732 public function is_enabled() {
2734 $globalstates = self::get_global_states();
2736 foreach ($globalstates as $filterlegacyname => $info) {
2737 $name = self::normalize_legacy_name($filterlegacyname);
2738 if ($name === $this->name) {
2739 if ($info->active == TEXTFILTER_DISABLED) {
2740 return false;
2741 } else {
2742 // it may be 'On' or 'Off, but available'
2743 return null;
2748 return null;
2751 public function get_settings_section_name() {
2752 $globalstates = self::get_global_states();
2753 if (!isset($globalstates[$this->name])) {
2754 return parent::get_settings_section_name();
2756 $legacyname = $globalstates[$this->name]->legacyname;
2757 return 'filtersetting' . str_replace('/', '', $legacyname);
2760 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
2761 global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
2762 $ADMIN = $adminroot; // may be used in settings.php
2763 $filter = $this; // also can be used inside settings.php
2765 $globalstates = self::get_global_states();
2766 $settings = null;
2767 if ($hassiteconfig && isset($globalstates[$this->name]) && file_exists($this->full_path('filtersettings.php'))) {
2768 $section = $this->get_settings_section_name();
2769 $settings = new admin_settingpage($section, $this->displayname,
2770 'moodle/site:config', $this->is_enabled() === false);
2771 include($this->full_path('filtersettings.php')); // this may also set $settings to null
2773 if ($settings) {
2774 $ADMIN->add($parentnodename, $settings);
2778 public function get_uninstall_url() {
2780 if (strpos($this->name, 'mod_') === 0) {
2781 return null;
2782 } else {
2783 $globalstates = self::get_global_states();
2784 $legacyname = $globalstates[$this->name]->legacyname;
2785 return new moodle_url('/admin/filters.php', array('sesskey' => sesskey(), 'filterpath' => $legacyname, 'action' => 'delete'));
2790 * Convert legacy filter names like 'filter/foo' or 'mod/bar' into frankenstyle
2792 * @param string $legacyfiltername legacy filter name
2793 * @return string frankenstyle-like name
2795 protected static function normalize_legacy_name($legacyfiltername) {
2797 $name = str_replace('/', '_', $legacyfiltername);
2798 if (strpos($name, 'filter_') === 0) {
2799 $name = substr($name, 7);
2800 if (empty($name)) {
2801 throw new coding_exception('Unable to determine filter name: ' . $legacyfiltername);
2805 return $name;
2809 * Provides access to the results of {@link filter_get_global_states()}
2810 * but indexed by the normalized filter name
2812 * The legacy filter name is available as ->legacyname property.
2814 * @param bool $disablecache
2815 * @return array
2817 protected static function get_global_states($disablecache=false) {
2818 global $DB;
2819 static $globalstatescache = null;
2821 if ($disablecache or is_null($globalstatescache)) {
2823 if (!$DB->get_manager()->table_exists('filter_active')) {
2824 // we're upgrading from 1.9 and the table used by {@link filter_get_global_states()}
2825 // does not exist yet
2826 $globalstatescache = array();
2828 } else {
2829 foreach (filter_get_global_states() as $legacyname => $info) {
2830 $name = self::normalize_legacy_name($legacyname);
2831 $filterinfo = new stdClass();
2832 $filterinfo->legacyname = $legacyname;
2833 $filterinfo->active = $info->active;
2834 $filterinfo->sortorder = $info->sortorder;
2835 $globalstatescache[$name] = $filterinfo;
2840 return $globalstatescache;
2846 * Class for activity modules
2848 class plugininfo_mod extends plugininfo_base {
2850 public static function get_plugins($type, $typerootdir, $typeclass) {
2852 // get the information about plugins at the disk
2853 $modules = parent::get_plugins($type, $typerootdir, $typeclass);
2855 // add modules missing from disk
2856 $modulesinfo = self::get_modules_info();
2857 foreach ($modulesinfo as $modulename => $moduleinfo) {
2858 if (isset($modules[$modulename])) {
2859 continue;
2861 $plugin = new $typeclass();
2862 $plugin->type = $type;
2863 $plugin->typerootdir = $typerootdir;
2864 $plugin->name = $modulename;
2865 $plugin->rootdir = null;
2866 $plugin->displayname = $modulename;
2867 $plugin->versiondb = $moduleinfo->version;
2868 $plugin->init_is_standard();
2870 $modules[$modulename] = $plugin;
2873 return $modules;
2877 * Magic method getter, redirects to read only values.
2879 * For module plugins we pretend the object has 'visible' property for compatibility
2880 * with plugins developed for Moodle version below 2.4
2882 * @param string $name
2883 * @return mixed
2885 public function __get($name) {
2886 if ($name === 'visible') {
2887 debugging('This is now an instance of plugininfo_mod, please use $module->is_enabled() instead of $module->visible', DEBUG_DEVELOPER);
2888 return ($this->is_enabled() !== false);
2890 return parent::__get($name);
2893 public function init_display_name() {
2894 if (get_string_manager()->string_exists('pluginname', $this->component)) {
2895 $this->displayname = get_string('pluginname', $this->component);
2896 } else {
2897 $this->displayname = get_string('modulename', $this->component);
2902 * Load the data from version.php.
2903 * @return object the data object defined in version.php.
2905 protected function load_version_php() {
2906 $versionfile = $this->full_path('version.php');
2908 $module = new stdClass();
2909 if (is_readable($versionfile)) {
2910 include($versionfile);
2912 return $module;
2915 public function load_db_version() {
2916 global $DB;
2918 $modulesinfo = self::get_modules_info();
2919 if (isset($modulesinfo[$this->name]->version)) {
2920 $this->versiondb = $modulesinfo[$this->name]->version;
2924 public function is_enabled() {
2926 $modulesinfo = self::get_modules_info();
2927 if (isset($modulesinfo[$this->name]->visible)) {
2928 if ($modulesinfo[$this->name]->visible) {
2929 return true;
2930 } else {
2931 return false;
2933 } else {
2934 return parent::is_enabled();
2938 public function get_settings_section_name() {
2939 return 'modsetting' . $this->name;
2942 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
2943 global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
2944 $ADMIN = $adminroot; // may be used in settings.php
2945 $module = $this; // also can be used inside settings.php
2946 $section = $this->get_settings_section_name();
2948 $modulesinfo = self::get_modules_info();
2949 $settings = null;
2950 if ($hassiteconfig && isset($modulesinfo[$this->name]) && file_exists($this->full_path('settings.php'))) {
2951 $settings = new admin_settingpage($section, $this->displayname,
2952 'moodle/site:config', $this->is_enabled() === false);
2953 include($this->full_path('settings.php')); // this may also set $settings to null
2955 if ($settings) {
2956 $ADMIN->add($parentnodename, $settings);
2960 public function get_uninstall_url() {
2962 if ($this->name !== 'forum') {
2963 return new moodle_url('/admin/modules.php', array('delete' => $this->name, 'sesskey' => sesskey()));
2964 } else {
2965 return null;
2970 * Provides access to the records in {modules} table
2972 * @param bool $disablecache do not use internal static cache
2973 * @return array array of stdClasses
2975 protected static function get_modules_info($disablecache=false) {
2976 global $DB;
2977 static $modulesinfocache = null;
2979 if (is_null($modulesinfocache) or $disablecache) {
2980 try {
2981 $modulesinfocache = $DB->get_records('modules', null, 'name', 'name,id,version,visible');
2982 } catch (dml_exception $e) {
2983 // before install
2984 $modulesinfocache = array();
2988 return $modulesinfocache;
2994 * Class for question behaviours.
2996 class plugininfo_qbehaviour extends plugininfo_base {
2998 public function get_uninstall_url() {
2999 return new moodle_url('/admin/qbehaviours.php',
3000 array('delete' => $this->name, 'sesskey' => sesskey()));
3006 * Class for question types
3008 class plugininfo_qtype extends plugininfo_base {
3010 public function get_uninstall_url() {
3011 return new moodle_url('/admin/qtypes.php',
3012 array('delete' => $this->name, 'sesskey' => sesskey()));
3015 public function get_settings_section_name() {
3016 return 'qtypesetting' . $this->name;
3019 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
3020 global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
3021 $ADMIN = $adminroot; // may be used in settings.php
3022 $qtype = $this; // also can be used inside settings.php
3023 $section = $this->get_settings_section_name();
3025 $settings = null;
3026 $systemcontext = context_system::instance();
3027 if (($hassiteconfig || has_capability('moodle/question:config', $systemcontext)) &&
3028 file_exists($this->full_path('settings.php'))) {
3029 $settings = new admin_settingpage($section, $this->displayname,
3030 'moodle/question:config', $this->is_enabled() === false);
3031 include($this->full_path('settings.php')); // this may also set $settings to null
3033 if ($settings) {
3034 $ADMIN->add($parentnodename, $settings);
3041 * Class for authentication plugins
3043 class plugininfo_auth extends plugininfo_base {
3045 public function is_enabled() {
3046 global $CFG;
3047 /** @var null|array list of enabled authentication plugins */
3048 static $enabled = null;
3050 if (in_array($this->name, array('nologin', 'manual'))) {
3051 // these two are always enabled and can't be disabled
3052 return null;
3055 if (is_null($enabled)) {
3056 $enabled = array_flip(explode(',', $CFG->auth));
3059 return isset($enabled[$this->name]);
3062 public function get_settings_section_name() {
3063 return 'authsetting' . $this->name;
3066 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
3067 global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
3068 $ADMIN = $adminroot; // may be used in settings.php
3069 $auth = $this; // also to be used inside settings.php
3070 $section = $this->get_settings_section_name();
3072 $settings = null;
3073 if ($hassiteconfig) {
3074 if (file_exists($this->full_path('settings.php'))) {
3075 // TODO: finish implementation of common settings - locking, etc.
3076 $settings = new admin_settingpage($section, $this->displayname,
3077 'moodle/site:config', $this->is_enabled() === false);
3078 include($this->full_path('settings.php')); // this may also set $settings to null
3079 } else {
3080 $settingsurl = new moodle_url('/admin/auth_config.php', array('auth' => $this->name));
3081 $settings = new admin_externalpage($section, $this->displayname,
3082 $settingsurl, 'moodle/site:config', $this->is_enabled() === false);
3085 if ($settings) {
3086 $ADMIN->add($parentnodename, $settings);
3093 * Class for enrolment plugins
3095 class plugininfo_enrol extends plugininfo_base {
3097 public function is_enabled() {
3098 global $CFG;
3099 /** @var null|array list of enabled enrolment plugins */
3100 static $enabled = null;
3102 // We do not actually need whole enrolment classes here so we do not call
3103 // {@link enrol_get_plugins()}. Note that this may produce slightly different
3104 // results, for example if the enrolment plugin does not contain lib.php
3105 // but it is listed in $CFG->enrol_plugins_enabled
3107 if (is_null($enabled)) {
3108 $enabled = array_flip(explode(',', $CFG->enrol_plugins_enabled));
3111 return isset($enabled[$this->name]);
3114 public function get_settings_section_name() {
3115 return 'enrolsettings' . $this->name;
3118 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
3119 global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
3120 $ADMIN = $adminroot; // may be used in settings.php
3121 $enrol = $this; // also can be used inside settings.php
3122 $section = $this->get_settings_section_name();
3124 $settings = null;
3125 if ($hassiteconfig && file_exists($this->full_path('settings.php'))) {
3126 $settings = new admin_settingpage($section, $this->displayname,
3127 'moodle/site:config', $this->is_enabled() === false);
3128 include($this->full_path('settings.php')); // this may also set $settings to null
3130 if ($settings) {
3131 $ADMIN->add($parentnodename, $settings);
3135 public function get_uninstall_url() {
3136 return new moodle_url('/admin/enrol.php', array('action' => 'uninstall', 'enrol' => $this->name, 'sesskey' => sesskey()));
3142 * Class for messaging processors
3144 class plugininfo_message extends plugininfo_base {
3146 public function get_settings_section_name() {
3147 return 'messagesetting' . $this->name;
3150 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
3151 global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
3152 $ADMIN = $adminroot; // may be used in settings.php
3153 if (!$hassiteconfig) {
3154 return;
3156 $section = $this->get_settings_section_name();
3158 $settings = null;
3159 $processors = get_message_processors();
3160 if (isset($processors[$this->name])) {
3161 $processor = $processors[$this->name];
3162 if ($processor->available && $processor->hassettings) {
3163 $settings = new admin_settingpage($section, $this->displayname,
3164 'moodle/site:config', $this->is_enabled() === false);
3165 include($this->full_path('settings.php')); // this may also set $settings to null
3168 if ($settings) {
3169 $ADMIN->add($parentnodename, $settings);
3174 * @see plugintype_interface::is_enabled()
3176 public function is_enabled() {
3177 $processors = get_message_processors();
3178 if (isset($processors[$this->name])) {
3179 return $processors[$this->name]->configured && $processors[$this->name]->enabled;
3180 } else {
3181 return parent::is_enabled();
3186 * @see plugintype_interface::get_uninstall_url()
3188 public function get_uninstall_url() {
3189 $processors = get_message_processors();
3190 if (isset($processors[$this->name])) {
3191 return new moodle_url('/admin/message.php', array('uninstall' => $processors[$this->name]->id, 'sesskey' => sesskey()));
3192 } else {
3193 return parent::get_uninstall_url();
3200 * Class for repositories
3202 class plugininfo_repository extends plugininfo_base {
3204 public function is_enabled() {
3206 $enabled = self::get_enabled_repositories();
3208 return isset($enabled[$this->name]);
3211 public function get_settings_section_name() {
3212 return 'repositorysettings'.$this->name;
3215 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
3216 if ($hassiteconfig && $this->is_enabled()) {
3217 // completely no access to repository setting when it is not enabled
3218 $sectionname = $this->get_settings_section_name();
3219 $settingsurl = new moodle_url('/admin/repository.php',
3220 array('sesskey' => sesskey(), 'action' => 'edit', 'repos' => $this->name));
3221 $settings = new admin_externalpage($sectionname, $this->displayname,
3222 $settingsurl, 'moodle/site:config', false);
3223 $adminroot->add($parentnodename, $settings);
3228 * Provides access to the records in {repository} table
3230 * @param bool $disablecache do not use internal static cache
3231 * @return array array of stdClasses
3233 protected static function get_enabled_repositories($disablecache=false) {
3234 global $DB;
3235 static $repositories = null;
3237 if (is_null($repositories) or $disablecache) {
3238 $repositories = $DB->get_records('repository', null, 'type', 'type,visible,sortorder');
3241 return $repositories;
3247 * Class for portfolios
3249 class plugininfo_portfolio extends plugininfo_base {
3251 public function is_enabled() {
3253 $enabled = self::get_enabled_portfolios();
3255 return isset($enabled[$this->name]);
3259 * Provides access to the records in {portfolio_instance} table
3261 * @param bool $disablecache do not use internal static cache
3262 * @return array array of stdClasses
3264 protected static function get_enabled_portfolios($disablecache=false) {
3265 global $DB;
3266 static $portfolios = null;
3268 if (is_null($portfolios) or $disablecache) {
3269 $portfolios = array();
3270 $instances = $DB->get_recordset('portfolio_instance', null, 'plugin');
3271 foreach ($instances as $instance) {
3272 if (isset($portfolios[$instance->plugin])) {
3273 if ($instance->visible) {
3274 $portfolios[$instance->plugin]->visible = $instance->visible;
3276 } else {
3277 $portfolios[$instance->plugin] = $instance;
3282 return $portfolios;
3288 * Class for themes
3290 class plugininfo_theme extends plugininfo_base {
3292 public function is_enabled() {
3293 global $CFG;
3295 if ((!empty($CFG->theme) and $CFG->theme === $this->name) or
3296 (!empty($CFG->themelegacy) and $CFG->themelegacy === $this->name)) {
3297 return true;
3298 } else {
3299 return parent::is_enabled();
3306 * Class representing an MNet service
3308 class plugininfo_mnetservice extends plugininfo_base {
3310 public function is_enabled() {
3311 global $CFG;
3313 if (empty($CFG->mnet_dispatcher_mode) || $CFG->mnet_dispatcher_mode !== 'strict') {
3314 return false;
3315 } else {
3316 return parent::is_enabled();
3323 * Class for admin tool plugins
3325 class plugininfo_tool extends plugininfo_base {
3327 public function get_uninstall_url() {
3328 return new moodle_url('/admin/tools.php', array('delete' => $this->name, 'sesskey' => sesskey()));
3334 * Class for admin tool plugins
3336 class plugininfo_report extends plugininfo_base {
3338 public function get_uninstall_url() {
3339 return new moodle_url('/admin/reports.php', array('delete' => $this->name, 'sesskey' => sesskey()));
3345 * Class for local plugins
3347 class plugininfo_local extends plugininfo_base {
3349 public function get_uninstall_url() {
3350 return new moodle_url('/admin/localplugins.php', array('delete' => $this->name, 'sesskey' => sesskey()));
3355 * Class for HTML editors
3357 class plugininfo_editor extends plugininfo_base {
3359 public function get_settings_section_name() {
3360 return 'editorsettings' . $this->name;
3363 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
3364 global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
3365 $ADMIN = $adminroot; // may be used in settings.php
3366 $editor = $this; // also can be used inside settings.php
3367 $section = $this->get_settings_section_name();
3369 $settings = null;
3370 if ($hassiteconfig && file_exists($this->full_path('settings.php'))) {
3371 $settings = new admin_settingpage($section, $this->displayname,
3372 'moodle/site:config', $this->is_enabled() === false);
3373 include($this->full_path('settings.php')); // this may also set $settings to null
3375 if ($settings) {
3376 $ADMIN->add($parentnodename, $settings);
3381 * Returns the information about plugin availability
3383 * True means that the plugin is enabled. False means that the plugin is
3384 * disabled. Null means that the information is not available, or the
3385 * plugin does not support configurable availability or the availability
3386 * can not be changed.
3388 * @return null|bool
3390 public function is_enabled() {
3391 global $CFG;
3392 if (empty($CFG->texteditors)) {
3393 $CFG->texteditors = 'tinymce,textarea';
3395 if (in_array($this->name, explode(',', $CFG->texteditors))) {
3396 return true;
3398 return false;
3403 * Class for plagiarism plugins
3405 class plugininfo_plagiarism extends plugininfo_base {
3407 public function get_settings_section_name() {
3408 return 'plagiarism'. $this->name;
3411 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
3412 // plagiarism plugin just redirect to settings.php in the plugins directory
3413 if ($hassiteconfig && file_exists($this->full_path('settings.php'))) {
3414 $section = $this->get_settings_section_name();
3415 $settingsurl = new moodle_url($this->get_dir().'/settings.php');
3416 $settings = new admin_externalpage($section, $this->displayname,
3417 $settingsurl, 'moodle/site:config', $this->is_enabled() === false);
3418 $adminroot->add($parentnodename, $settings);
3424 * Class for webservice protocols
3426 class plugininfo_webservice extends plugininfo_base {
3428 public function get_settings_section_name() {
3429 return 'webservicesetting' . $this->name;
3432 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
3433 global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
3434 $ADMIN = $adminroot; // may be used in settings.php
3435 $webservice = $this; // also can be used inside settings.php
3436 $section = $this->get_settings_section_name();
3438 $settings = null;
3439 if ($hassiteconfig && file_exists($this->full_path('settings.php'))) {
3440 $settings = new admin_settingpage($section, $this->displayname,
3441 'moodle/site:config', $this->is_enabled() === false);
3442 include($this->full_path('settings.php')); // this may also set $settings to null
3444 if ($settings) {
3445 $ADMIN->add($parentnodename, $settings);
3449 public function is_enabled() {
3450 global $CFG;
3451 if (empty($CFG->enablewebservices)) {
3452 return false;
3454 $active_webservices = empty($CFG->webserviceprotocols) ? array() : explode(',', $CFG->webserviceprotocols);
3455 if (in_array($this->name, $active_webservices)) {
3456 return true;
3458 return false;
3461 public function get_uninstall_url() {
3462 return new moodle_url('/admin/webservice/protocols.php',
3463 array('sesskey' => sesskey(), 'action' => 'uninstall', 'webservice' => $this->name));
3468 * Class for course formats
3470 class plugininfo_format extends plugininfo_base {
3473 * Gathers and returns the information about all plugins of the given type
3475 * @param string $type the name of the plugintype, eg. mod, auth or workshopform
3476 * @param string $typerootdir full path to the location of the plugin dir
3477 * @param string $typeclass the name of the actually called class
3478 * @return array of plugintype classes, indexed by the plugin name
3480 public static function get_plugins($type, $typerootdir, $typeclass) {
3481 global $CFG;
3482 $formats = parent::get_plugins($type, $typerootdir, $typeclass);
3483 require_once($CFG->dirroot.'/course/lib.php');
3484 $order = get_sorted_course_formats();
3485 $sortedformats = array();
3486 foreach ($order as $formatname) {
3487 $sortedformats[$formatname] = $formats[$formatname];
3489 return $sortedformats;
3492 public function get_settings_section_name() {
3493 return 'formatsetting' . $this->name;
3496 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
3497 global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
3498 $ADMIN = $adminroot; // also may be used in settings.php
3499 $section = $this->get_settings_section_name();
3501 $settings = null;
3502 if ($hassiteconfig && file_exists($this->full_path('settings.php'))) {
3503 $settings = new admin_settingpage($section, $this->displayname,
3504 'moodle/site:config', $this->is_enabled() === false);
3505 include($this->full_path('settings.php')); // this may also set $settings to null
3507 if ($settings) {
3508 $ADMIN->add($parentnodename, $settings);
3512 public function is_enabled() {
3513 return !get_config($this->component, 'disabled');
3516 public function get_uninstall_url() {
3517 if ($this->name !== get_config('moodlecourse', 'format') && $this->name !== 'site') {
3518 return new moodle_url('/admin/courseformats.php',
3519 array('sesskey' => sesskey(), 'action' => 'uninstall', 'format' => $this->name));
3521 return parent::get_uninstall_url();