MDL-35661 Loading of plugin settings for repositories plugins (plugininfo_repository)
[moodle.git] / lib / pluginlib.php
CommitLineData
b9934a17 1<?php
b6ad8594 2
b9934a17
DM
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.
14//
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/>.
17
18/**
19 * Defines classes used for plugins management
20 *
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.
24 *
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
29 */
30
31defined('MOODLE_INTERNAL') || die();
32
33/**
34 * Singleton class providing general plugins management functionality
35 */
36class plugin_manager {
37
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';
42
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';
ec8935f5
PS
51 /** the standard plugin is about to be deleted */
52 const PLUGIN_STATUS_DELETE = 'delete';
b9934a17
DM
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';
57
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;
64
65 /**
66 * Direct initiation not allowed, use the factory method {@link self::instance()}
b9934a17
DM
67 */
68 protected function __construct() {
b9934a17
DM
69 }
70
71 /**
72 * Sorry, this is singleton
73 */
74 protected function __clone() {
75 }
76
77 /**
78 * Factory method for this class
79 *
80 * @return plugin_manager the singleton instance
81 */
82 public static function instance() {
b9934a17
DM
83 if (is_null(self::$singletoninstance)) {
84 self::$singletoninstance = new self();
85 }
86 return self::$singletoninstance;
87 }
88
89 /**
90 * Returns a tree of known plugins and information about them
91 *
92 * @param bool $disablecache force reload, cache can be used otherwise
e61aaece
TH
93 * @return array 2D array. The first keys are plugin type names (e.g. qtype);
94 * the second keys are the plugin local name (e.g. multichoice); and
b6ad8594 95 * the values are the corresponding objects extending {@link plugininfo_base}
b9934a17
DM
96 */
97 public function get_plugins($disablecache=false) {
7716057f 98 global $CFG;
b9934a17
DM
99
100 if ($disablecache or is_null($this->pluginsinfo)) {
7d59d8da
PS
101 // Hack: include mod and editor subplugin management classes first,
102 // the adminlib.php is supposed to contain extra admin settings too.
103 require_once($CFG->libdir.'/adminlib.php');
104 foreach(array('mod', 'editor') as $type) {
105 foreach (get_plugin_list($type) as $dir) {
106 if (file_exists("$dir/adminlib.php")) {
107 include_once("$dir/adminlib.php");
108 }
109 }
110 }
b9934a17
DM
111 $this->pluginsinfo = array();
112 $plugintypes = get_plugin_types();
4ed26680 113 $plugintypes = $this->reorder_plugin_types($plugintypes);
b9934a17
DM
114 foreach ($plugintypes as $plugintype => $plugintyperootdir) {
115 if (in_array($plugintype, array('base', 'general'))) {
116 throw new coding_exception('Illegal usage of reserved word for plugin type');
117 }
b6ad8594
DM
118 if (class_exists('plugininfo_' . $plugintype)) {
119 $plugintypeclass = 'plugininfo_' . $plugintype;
b9934a17 120 } else {
b6ad8594 121 $plugintypeclass = 'plugininfo_general';
b9934a17 122 }
b6ad8594
DM
123 if (!in_array('plugininfo_base', class_parents($plugintypeclass))) {
124 throw new coding_exception('Class ' . $plugintypeclass . ' must extend plugininfo_base');
b9934a17
DM
125 }
126 $plugins = call_user_func(array($plugintypeclass, 'get_plugins'), $plugintype, $plugintyperootdir, $plugintypeclass);
127 $this->pluginsinfo[$plugintype] = $plugins;
128 }
dd119e21 129
7716057f 130 if (empty($CFG->disableupdatenotifications) and !during_initial_install()) {
8411c24e
DP
131 // append the information about available updates provided by {@link available_update_checker()}
132 $provider = available_update_checker::instance();
133 foreach ($this->pluginsinfo as $plugintype => $plugins) {
134 foreach ($plugins as $plugininfoholder) {
135 $plugininfoholder->check_available_updates($provider);
136 }
dd119e21
DM
137 }
138 }
b9934a17
DM
139 }
140
141 return $this->pluginsinfo;
142 }
143
144 /**
0242bdc7
TH
145 * Returns list of plugins that define their subplugins and the information
146 * about them from the db/subplugins.php file.
b9934a17 147 *
c57fc98b 148 * At the moment, only activity modules and editors can define subplugins.
b9934a17 149 *
0242bdc7
TH
150 * @param bool $disablecache force reload, cache can be used otherwise
151 * @return array with keys like 'mod_quiz', and values the data from the
152 * corresponding db/subplugins.php file.
b9934a17
DM
153 */
154 public function get_subplugins($disablecache=false) {
155
156 if ($disablecache or is_null($this->subpluginsinfo)) {
157 $this->subpluginsinfo = array();
c57fc98b 158 foreach (array('mod', 'editor') as $type) {
e197d9a4 159 $owners = get_plugin_list($type);
c57fc98b 160 foreach ($owners as $component => $ownerdir) {
161 $componentsubplugins = array();
162 if (file_exists($ownerdir . '/db/subplugins.php')) {
975311d3 163 $subplugins = array();
c57fc98b 164 include($ownerdir . '/db/subplugins.php');
165 foreach ($subplugins as $subplugintype => $subplugintyperootdir) {
166 $subplugin = new stdClass();
167 $subplugin->type = $subplugintype;
168 $subplugin->typerootdir = $subplugintyperootdir;
169 $componentsubplugins[$subplugintype] = $subplugin;
170 }
171 $this->subpluginsinfo[$type . '_' . $component] = $componentsubplugins;
b9934a17 172 }
b9934a17
DM
173 }
174 }
175 }
176
177 return $this->subpluginsinfo;
178 }
179
180 /**
181 * Returns the name of the plugin that defines the given subplugin type
182 *
183 * If the given subplugin type is not actually a subplugin, returns false.
184 *
185 * @param string $subplugintype the name of subplugin type, eg. workshopform or quiz
186 * @return false|string the name of the parent plugin, eg. mod_workshop
187 */
188 public function get_parent_of_subplugin($subplugintype) {
189
190 $parent = false;
191 foreach ($this->get_subplugins() as $pluginname => $subplugintypes) {
192 if (isset($subplugintypes[$subplugintype])) {
193 $parent = $pluginname;
194 break;
195 }
196 }
197
198 return $parent;
199 }
200
201 /**
202 * Returns a localized name of a given plugin
203 *
204 * @param string $plugin name of the plugin, eg mod_workshop or auth_ldap
205 * @return string
206 */
207 public function plugin_name($plugin) {
208 list($type, $name) = normalize_component($plugin);
209 return $this->pluginsinfo[$type][$name]->displayname;
210 }
211
212 /**
213 * Returns a localized name of a plugin type in plural form
214 *
215 * Most plugin types define their names in core_plugin lang file. In case of subplugins,
216 * we try to ask the parent plugin for the name. In the worst case, we will return
217 * the value of the passed $type parameter.
218 *
219 * @param string $type the type of the plugin, e.g. mod or workshopform
220 * @return string
221 */
222 public function plugintype_name_plural($type) {
223
224 if (get_string_manager()->string_exists('type_' . $type . '_plural', 'core_plugin')) {
225 // for most plugin types, their names are defined in core_plugin lang file
226 return get_string('type_' . $type . '_plural', 'core_plugin');
227
228 } else if ($parent = $this->get_parent_of_subplugin($type)) {
229 // if this is a subplugin, try to ask the parent plugin for the name
230 if (get_string_manager()->string_exists('subplugintype_' . $type . '_plural', $parent)) {
231 return $this->plugin_name($parent) . ' / ' . get_string('subplugintype_' . $type . '_plural', $parent);
232 } else {
233 return $this->plugin_name($parent) . ' / ' . $type;
234 }
235
236 } else {
237 return $type;
238 }
239 }
240
e61aaece
TH
241 /**
242 * @param string $component frankenstyle component name.
b6ad8594 243 * @return plugininfo_base|null the corresponding plugin information.
e61aaece
TH
244 */
245 public function get_plugin_info($component) {
246 list($type, $name) = normalize_component($component);
247 $plugins = $this->get_plugins();
248 if (isset($plugins[$type][$name])) {
249 return $plugins[$type][$name];
250 } else {
251 return null;
252 }
253 }
254
828788f0 255 /**
b6ad8594 256 * Get a list of any other plugins that require this one.
828788f0
TH
257 * @param string $component frankenstyle component name.
258 * @return array of frankensyle component names that require this one.
259 */
260 public function other_plugins_that_require($component) {
261 $others = array();
262 foreach ($this->get_plugins() as $type => $plugins) {
263 foreach ($plugins as $plugin) {
264 $required = $plugin->get_other_required_plugins();
265 if (isset($required[$component])) {
266 $others[] = $plugin->component;
267 }
268 }
269 }
270 return $others;
271 }
272
e61aaece 273 /**
777781d1
TH
274 * Check a dependencies list against the list of installed plugins.
275 * @param array $dependencies compenent name to required version or ANY_VERSION.
276 * @return bool true if all the dependencies are satisfied.
e61aaece 277 */
777781d1
TH
278 public function are_dependencies_satisfied($dependencies) {
279 foreach ($dependencies as $component => $requiredversion) {
e61aaece
TH
280 $otherplugin = $this->get_plugin_info($component);
281 if (is_null($otherplugin)) {
0242bdc7
TH
282 return false;
283 }
284
3f123d92 285 if ($requiredversion != ANY_VERSION and $otherplugin->versiondisk < $requiredversion) {
0242bdc7
TH
286 return false;
287 }
288 }
289
290 return true;
291 }
292
faadd326 293 /**
927cb511
DM
294 * Checks all dependencies for all installed plugins
295 *
296 * This is used by install and upgrade. The array passed by reference as the second
297 * argument is populated with the list of plugins that have failed dependencies (note that
298 * a single plugin can appear multiple times in the $failedplugins).
299 *
faadd326 300 * @param int $moodleversion the version from version.php.
927cb511 301 * @param array $failedplugins to return the list of plugins with non-satisfied dependencies
777781d1 302 * @return bool true if all the dependencies are satisfied for all plugins.
faadd326 303 */
927cb511
DM
304 public function all_plugins_ok($moodleversion, &$failedplugins = array()) {
305
306 $return = true;
faadd326
TH
307 foreach ($this->get_plugins() as $type => $plugins) {
308 foreach ($plugins as $plugin) {
309
3a2300f5 310 if (!$plugin->is_core_dependency_satisfied($moodleversion)) {
927cb511
DM
311 $return = false;
312 $failedplugins[] = $plugin->component;
faadd326
TH
313 }
314
777781d1 315 if (!$this->are_dependencies_satisfied($plugin->get_other_required_plugins())) {
927cb511
DM
316 $return = false;
317 $failedplugins[] = $plugin->component;
faadd326
TH
318 }
319 }
320 }
321
927cb511 322 return $return;
faadd326
TH
323 }
324
5344ddd1
DM
325 /**
326 * Checks if there are some plugins with a known available update
327 *
328 * @return bool true if there is at least one available update
329 */
330 public function some_plugins_updatable() {
331 foreach ($this->get_plugins() as $type => $plugins) {
332 foreach ($plugins as $plugin) {
333 if ($plugin->available_updates()) {
334 return true;
335 }
336 }
337 }
338
339 return false;
340 }
341
ec8935f5
PS
342 /**
343 * Defines a list of all plugins that were originally shipped in the standard Moodle distribution,
344 * but are not anymore and are deleted during upgrades.
345 *
346 * The main purpose of this list is to hide missing plugins during upgrade.
347 *
348 * @param string $type plugin type
349 * @param string $name plugin name
350 * @return bool
351 */
352 public static function is_deleted_standard_plugin($type, $name) {
353 static $plugins = array(
34c72803 354 // do not add 1.9-2.2 plugin removals here
ec8935f5
PS
355 );
356
357 if (!isset($plugins[$type])) {
358 return false;
359 }
360 return in_array($name, $plugins[$type]);
361 }
362
b9934a17
DM
363 /**
364 * Defines a white list of all plugins shipped in the standard Moodle distribution
365 *
ec8935f5 366 * @param string $type
b9934a17
DM
367 * @return false|array array of standard plugins or false if the type is unknown
368 */
369 public static function standard_plugins_list($type) {
370 static $standard_plugins = array(
371
372 'assignment' => array(
373 'offline', 'online', 'upload', 'uploadsingle'
374 ),
375
1619a38b
DP
376 'assignsubmission' => array(
377 'comments', 'file', 'onlinetext'
378 ),
379
380 'assignfeedback' => array(
fcae4a0c 381 'comments', 'file', 'offline'
1619a38b
DP
382 ),
383
b9934a17
DM
384 'auth' => array(
385 'cas', 'db', 'email', 'fc', 'imap', 'ldap', 'manual', 'mnet',
386 'nntp', 'nologin', 'none', 'pam', 'pop3', 'radius',
387 'shibboleth', 'webservice'
388 ),
389
390 'block' => array(
391 'activity_modules', 'admin_bookmarks', 'blog_menu',
392 'blog_recent', 'blog_tags', 'calendar_month',
393 'calendar_upcoming', 'comments', 'community',
394 'completionstatus', 'course_list', 'course_overview',
395 'course_summary', 'feedback', 'glossary_random', 'html',
396 'login', 'mentees', 'messages', 'mnet_hosts', 'myprofile',
397 'navigation', 'news_items', 'online_users', 'participants',
398 'private_files', 'quiz_results', 'recent_activity',
f68cef22 399 'rss_client', 'search_forums', 'section_links',
b9934a17
DM
400 'selfcompletion', 'settings', 'site_main_menu',
401 'social_activities', 'tag_flickr', 'tag_youtube', 'tags'
402 ),
403
f7e6dd4d
EL
404 'booktool' => array(
405 'exportimscp', 'importhtml', 'print'
406 ),
407
b9934a17 408 'coursereport' => array(
a2a444ab 409 //deprecated!
b9934a17
DM
410 ),
411
412 'datafield' => array(
413 'checkbox', 'date', 'file', 'latlong', 'menu', 'multimenu',
414 'number', 'picture', 'radiobutton', 'text', 'textarea', 'url'
415 ),
416
417 'datapreset' => array(
418 'imagegallery'
419 ),
420
421 'editor' => array(
422 'textarea', 'tinymce'
423 ),
424
425 'enrol' => array(
426 'authorize', 'category', 'cohort', 'database', 'flatfile',
427 'guest', 'imsenterprise', 'ldap', 'manual', 'meta', 'mnet',
428 'paypal', 'self'
429 ),
430
431 'filter' => array(
432 'activitynames', 'algebra', 'censor', 'emailprotect',
433 'emoticon', 'mediaplugin', 'multilang', 'tex', 'tidy',
87783982 434 'urltolink', 'data', 'glossary'
b9934a17
DM
435 ),
436
437 'format' => array(
438 'scorm', 'social', 'topics', 'weeks'
439 ),
440
441 'gradeexport' => array(
442 'ods', 'txt', 'xls', 'xml'
443 ),
444
445 'gradeimport' => array(
446 'csv', 'xml'
447 ),
448
449 'gradereport' => array(
450 'grader', 'outcomes', 'overview', 'user'
451 ),
452
f59f488a 453 'gradingform' => array(
77143217 454 'rubric', 'guide'
f59f488a
DM
455 ),
456
b9934a17
DM
457 'local' => array(
458 ),
459
460 'message' => array(
461 'email', 'jabber', 'popup'
462 ),
463
464 'mnetservice' => array(
465 'enrol'
466 ),
467
468 'mod' => array(
f7e6dd4d 469 'assign', 'assignment', 'book', 'chat', 'choice', 'data', 'feedback', 'folder',
7fdee5b6 470 'forum', 'glossary', 'imscp', 'label', 'lesson', 'lti', 'page',
b9934a17
DM
471 'quiz', 'resource', 'scorm', 'survey', 'url', 'wiki', 'workshop'
472 ),
473
474 'plagiarism' => array(
475 ),
476
477 'portfolio' => array(
478 'boxnet', 'download', 'flickr', 'googledocs', 'mahara', 'picasa'
479 ),
480
481 'profilefield' => array(
482 'checkbox', 'datetime', 'menu', 'text', 'textarea'
483 ),
484
d1c77ac3
DM
485 'qbehaviour' => array(
486 'adaptive', 'adaptivenopenalty', 'deferredcbm',
487 'deferredfeedback', 'immediatecbm', 'immediatefeedback',
488 'informationitem', 'interactive', 'interactivecountback',
489 'manualgraded', 'missing'
490 ),
491
b9934a17
DM
492 'qformat' => array(
493 'aiken', 'blackboard', 'blackboard_six', 'examview', 'gift',
2dc54611 494 'learnwise', 'missingword', 'multianswer', 'webct',
b9934a17
DM
495 'xhtml', 'xml'
496 ),
497
498 'qtype' => array(
499 'calculated', 'calculatedmulti', 'calculatedsimple',
500 'description', 'essay', 'match', 'missingtype', 'multianswer',
501 'multichoice', 'numerical', 'random', 'randomsamatch',
502 'shortanswer', 'truefalse'
503 ),
504
505 'quiz' => array(
506 'grading', 'overview', 'responses', 'statistics'
507 ),
508
c999d841
TH
509 'quizaccess' => array(
510 'delaybetweenattempts', 'ipaddress', 'numattempts', 'openclosedate',
511 'password', 'safebrowser', 'securewindow', 'timelimit'
512 ),
513
b9934a17 514 'report' => array(
13fdaaac 515 'backups', 'completion', 'configlog', 'courseoverview',
8a8f29c2 516 'log', 'loglive', 'outline', 'participation', 'progress', 'questioninstances', 'security', 'stats'
b9934a17
DM
517 ),
518
519 'repository' => array(
daf28d86 520 'alfresco', 'boxnet', 'coursefiles', 'dropbox', 'equella', 'filesystem',
b9934a17
DM
521 'flickr', 'flickr_public', 'googledocs', 'local', 'merlot',
522 'picasa', 'recent', 's3', 'upload', 'url', 'user', 'webdav',
523 'wikimedia', 'youtube'
524 ),
525
99e86561 526 'scormreport' => array(
8f1a0d21 527 'basic',
e61a7137
AKA
528 'interactions',
529 'graphs'
99e86561
PS
530 ),
531
29e03690
PS
532 'tinymce' => array(
533 'dragmath', 'moodleemoticon', 'moodleimage', 'moodlemedia', 'moodlenolink', 'spellchecker',
534 ),
535
b9934a17 536 'theme' => array(
bef9ad95
DM
537 'afterburner', 'anomaly', 'arialist', 'base', 'binarius',
538 'boxxie', 'brick', 'canvas', 'formal_white', 'formfactor',
98ca9e84
EL
539 'fusion', 'leatherbound', 'magazine', 'mymobile', 'nimble',
540 'nonzero', 'overlay', 'serenity', 'sky_high', 'splash',
541 'standard', 'standardold'
b9934a17
DM
542 ),
543
11b24ce7 544 'tool' => array(
db9d7be6 545 'assignmentupgrade', 'capability', 'customlang', 'dbtransfer', 'generator',
a3d5830a 546 'health', 'innodb', 'langimport', 'multilangupgrade', 'phpunit', 'profiling',
fab6f7b7 547 'qeupgradehelper', 'replace', 'spamcleaner', 'timezoneimport', 'unittest',
9597e00b 548 'uploaduser', 'unsuproles', 'xmldb'
11b24ce7
PS
549 ),
550
b9934a17
DM
551 'webservice' => array(
552 'amf', 'rest', 'soap', 'xmlrpc'
553 ),
554
555 'workshopallocation' => array(
98621280 556 'manual', 'random', 'scheduled'
b9934a17
DM
557 ),
558
559 'workshopeval' => array(
560 'best'
561 ),
562
563 'workshopform' => array(
564 'accumulative', 'comments', 'numerrors', 'rubric'
565 )
566 );
567
568 if (isset($standard_plugins[$type])) {
569 return $standard_plugins[$type];
b9934a17
DM
570 } else {
571 return false;
572 }
573 }
4ed26680
DM
574
575 /**
660c4d46 576 * Reorders plugin types into a sequence to be displayed
4ed26680
DM
577 *
578 * For technical reasons, plugin types returned by {@link get_plugin_types()} are
579 * in a certain order that does not need to fit the expected order for the display.
580 * Particularly, activity modules should be displayed first as they represent the
581 * real heart of Moodle. They should be followed by other plugin types that are
582 * used to build the courses (as that is what one expects from LMS). After that,
583 * other supportive plugin types follow.
584 *
585 * @param array $types associative array
586 * @return array same array with altered order of items
587 */
588 protected function reorder_plugin_types(array $types) {
589 $fix = array(
590 'mod' => $types['mod'],
591 'block' => $types['block'],
592 'qtype' => $types['qtype'],
593 'qbehaviour' => $types['qbehaviour'],
594 'qformat' => $types['qformat'],
595 'filter' => $types['filter'],
596 'enrol' => $types['enrol'],
597 );
598 foreach ($types as $type => $path) {
599 if (!isset($fix[$type])) {
600 $fix[$type] = $path;
601 }
602 }
603 return $fix;
604 }
b9934a17
DM
605}
606
b9934a17 607
b9934a17 608/**
cd0bb55f 609 * General exception thrown by the {@link available_update_checker} class
b9934a17 610 */
cd0bb55f 611class available_update_checker_exception extends moodle_exception {
b9934a17
DM
612
613 /**
cd0bb55f
DM
614 * @param string $errorcode exception description identifier
615 * @param mixed $debuginfo debugging data to display
616 */
617 public function __construct($errorcode, $debuginfo=null) {
618 parent::__construct($errorcode, 'core_plugin', '', null, print_r($debuginfo, true));
619 }
620}
621
622
623/**
624 * Singleton class that handles checking for available updates
625 */
626class available_update_checker {
627
628 /** @var available_update_checker holds the singleton instance */
629 protected static $singletoninstance;
7d8de6d8
DM
630 /** @var null|int the timestamp of when the most recent response was fetched */
631 protected $recentfetch = null;
632 /** @var null|array the recent response from the update notification provider */
633 protected $recentresponse = null;
55585f3a
DM
634 /** @var null|string the numerical version of the local Moodle code */
635 protected $currentversion = null;
4442cc80
DM
636 /** @var null|string the release info of the local Moodle code */
637 protected $currentrelease = null;
55585f3a
DM
638 /** @var null|string branch of the local Moodle code */
639 protected $currentbranch = null;
640 /** @var array of (string)frankestyle => (string)version list of additional plugins deployed at this site */
641 protected $currentplugins = array();
cd0bb55f
DM
642
643 /**
644 * Direct initiation not allowed, use the factory method {@link self::instance()}
645 */
646 protected function __construct() {
cd0bb55f
DM
647 }
648
649 /**
650 * Sorry, this is singleton
651 */
652 protected function __clone() {
653 }
654
655 /**
656 * Factory method for this class
b9934a17 657 *
cd0bb55f
DM
658 * @return available_update_checker the singleton instance
659 */
660 public static function instance() {
661 if (is_null(self::$singletoninstance)) {
662 self::$singletoninstance = new self();
663 }
664 return self::$singletoninstance;
665 }
666
cd0bb55f
DM
667 /**
668 * Returns the timestamp of the last execution of {@link fetch()}
b9934a17 669 *
cd0bb55f 670 * @return int|null null if it has never been executed or we don't known
b9934a17 671 */
cd0bb55f 672 public function get_last_timefetched() {
7d8de6d8
DM
673
674 $this->restore_response();
675
676 if (!empty($this->recentfetch)) {
677 return $this->recentfetch;
678
cd0bb55f 679 } else {
7d8de6d8 680 return null;
cd0bb55f
DM
681 }
682 }
b9934a17
DM
683
684 /**
cd0bb55f 685 * Fetches the available update status from the remote site
b9934a17 686 *
cd0bb55f 687 * @throws available_update_checker_exception
b9934a17 688 */
cd0bb55f 689 public function fetch() {
7d8de6d8 690 $response = $this->get_response();
cd0bb55f 691 $this->validate_response($response);
7d8de6d8 692 $this->store_response($response);
cd0bb55f 693 }
b9934a17
DM
694
695 /**
cd0bb55f 696 * Returns the available update information for the given component
b9934a17 697 *
cd0bb55f 698 * This method returns null if the most recent response does not contain any information
7d8de6d8
DM
699 * about it. The returned structure is an array of available updates for the given
700 * component. Each update info is an object with at least one property called
701 * 'version'. Other possible properties are 'release', 'maturity', 'url' and 'downloadurl'.
cd0bb55f 702 *
c6f008e7
DM
703 * For the 'core' component, the method returns real updates only (those with higher version).
704 * For all other components, the list of all known remote updates is returned and the caller
705 * (usually the {@link plugin_manager}) is supposed to make the actual comparison of versions.
b9934a17 706 *
cd0bb55f 707 * @param string $component frankenstyle
c6f008e7
DM
708 * @param array $options with supported keys 'minmaturity' and/or 'notifybuilds'
709 * @return null|array null or array of available_update_info objects
b9934a17 710 */
c6f008e7
DM
711 public function get_update_info($component, array $options = array()) {
712
713 if (!isset($options['minmaturity'])) {
714 $options['minmaturity'] = 0;
715 }
716
717 if (!isset($options['notifybuilds'])) {
718 $options['notifybuilds'] = false;
719 }
720
721 if ($component == 'core') {
722 $this->load_current_environment();
723 }
cd0bb55f 724
7d8de6d8 725 $this->restore_response();
cd0bb55f 726
c6f008e7
DM
727 if (empty($this->recentresponse['updates'][$component])) {
728 return null;
729 }
730
731 $updates = array();
732 foreach ($this->recentresponse['updates'][$component] as $info) {
733 $update = new available_update_info($component, $info);
734 if (isset($update->maturity) and ($update->maturity < $options['minmaturity'])) {
735 continue;
7d8de6d8 736 }
c6f008e7
DM
737 if ($component == 'core') {
738 if ($update->version <= $this->currentversion) {
739 continue;
740 }
4442cc80
DM
741 if (empty($options['notifybuilds']) and $this->is_same_release($update->release)) {
742 continue;
743 }
c6f008e7
DM
744 }
745 $updates[] = $update;
746 }
747
748 if (empty($updates)) {
cd0bb55f
DM
749 return null;
750 }
c6f008e7
DM
751
752 return $updates;
cd0bb55f 753 }
b9934a17
DM
754
755 /**
be378880
DM
756 * The method being run via cron.php
757 */
758 public function cron() {
759 global $CFG;
760
761 if (!$this->cron_autocheck_enabled()) {
762 $this->cron_mtrace('Automatic check for available updates not enabled, skipping.');
763 return;
764 }
765
766 $now = $this->cron_current_timestamp();
767
768 if ($this->cron_has_fresh_fetch($now)) {
769 $this->cron_mtrace('Recently fetched info about available updates is still fresh enough, skipping.');
770 return;
771 }
772
773 if ($this->cron_has_outdated_fetch($now)) {
774 $this->cron_mtrace('Outdated or missing info about available updates, forced fetching ... ', '');
775 $this->cron_execute();
776 return;
777 }
778
779 $offset = $this->cron_execution_offset();
780 $start = mktime(1, 0, 0, date('n', $now), date('j', $now), date('Y', $now)); // 01:00 AM today local time
781 if ($now > $start + $offset) {
782 $this->cron_mtrace('Regular daily check for available updates ... ', '');
783 $this->cron_execute();
784 return;
785 }
786 }
787
788 /// end of public API //////////////////////////////////////////////////////
789
cd0bb55f 790 /**
7d8de6d8 791 * Makes cURL request to get data from the remote site
b9934a17 792 *
7d8de6d8 793 * @return string raw request result
cd0bb55f
DM
794 * @throws available_update_checker_exception
795 */
7d8de6d8 796 protected function get_response() {
b4bfdf5a
PS
797 global $CFG;
798 require_once($CFG->libdir.'/filelib.php');
799
cd0bb55f
DM
800 $curl = new curl(array('proxy' => true));
801 $response = $curl->post($this->prepare_request_url(), $this->prepare_request_params());
802 $curlinfo = $curl->get_info();
803 if ($curlinfo['http_code'] != 200) {
804 throw new available_update_checker_exception('err_response_http_code', $curlinfo['http_code']);
805 }
cd0bb55f
DM
806 return $response;
807 }
808
809 /**
810 * Makes sure the response is valid, has correct API format etc.
811 *
7d8de6d8 812 * @param string $response raw response as returned by the {@link self::get_response()}
cd0bb55f
DM
813 * @throws available_update_checker_exception
814 */
7d8de6d8
DM
815 protected function validate_response($response) {
816
817 $response = $this->decode_response($response);
cd0bb55f
DM
818
819 if (empty($response)) {
820 throw new available_update_checker_exception('err_response_empty');
821 }
822
7d8de6d8
DM
823 if (empty($response['status']) or $response['status'] !== 'OK') {
824 throw new available_update_checker_exception('err_response_status', $response['status']);
825 }
826
827 if (empty($response['apiver']) or $response['apiver'] !== '1.0') {
828 throw new available_update_checker_exception('err_response_format_version', $response['apiver']);
cd0bb55f
DM
829 }
830
7d8de6d8 831 if (empty($response['forbranch']) or $response['forbranch'] !== moodle_major_version(true)) {
d5d2e353 832 throw new available_update_checker_exception('err_response_target_version', $response['forbranch']);
cd0bb55f
DM
833 }
834 }
835
836 /**
7d8de6d8 837 * Decodes the raw string response from the update notifications provider
b9934a17 838 *
7d8de6d8
DM
839 * @param string $response as returned by {@link self::get_response()}
840 * @return array decoded response structure
b9934a17 841 */
7d8de6d8
DM
842 protected function decode_response($response) {
843 return json_decode($response, true);
cd0bb55f 844 }
b9934a17
DM
845
846 /**
7d8de6d8
DM
847 * Stores the valid fetched response for later usage
848 *
849 * This implementation uses the config_plugins table as the permanent storage.
b9934a17 850 *
7d8de6d8 851 * @param string $response raw valid data returned by {@link self::get_response()}
b9934a17 852 */
7d8de6d8
DM
853 protected function store_response($response) {
854
855 set_config('recentfetch', time(), 'core_plugin');
856 set_config('recentresponse', $response, 'core_plugin');
857
858 $this->restore_response(true);
cd0bb55f 859 }
b9934a17
DM
860
861 /**
7d8de6d8 862 * Loads the most recent raw response record we have fetched
b9934a17 863 *
c62580b9
DM
864 * After this method is called, $this->recentresponse is set to an array. If the
865 * array is empty, then either no data have been fetched yet or the fetched data
866 * do not have expected format (and thence they are ignored and a debugging
867 * message is displayed).
868 *
7d8de6d8 869 * This implementation uses the config_plugins table as the permanent storage.
b9934a17 870 *
7d8de6d8 871 * @param bool $forcereload reload even if it was already loaded
b9934a17 872 */
7d8de6d8
DM
873 protected function restore_response($forcereload = false) {
874
875 if (!$forcereload and !is_null($this->recentresponse)) {
876 // we already have it, nothing to do
877 return;
cd0bb55f
DM
878 }
879
7d8de6d8
DM
880 $config = get_config('core_plugin');
881
882 if (!empty($config->recentresponse) and !empty($config->recentfetch)) {
883 try {
884 $this->validate_response($config->recentresponse);
885 $this->recentfetch = $config->recentfetch;
886 $this->recentresponse = $this->decode_response($config->recentresponse);
660c4d46 887 } catch (available_update_checker_exception $e) {
c62580b9
DM
888 debugging('Invalid info about available updates detected and will be ignored: '.$e->getMessage(), DEBUG_ALL);
889 $this->recentresponse = array();
7d8de6d8
DM
890 }
891
cd0bb55f 892 } else {
7d8de6d8 893 $this->recentresponse = array();
cd0bb55f
DM
894 }
895 }
896
7b35553b
DM
897 /**
898 * Compares two raw {@link $recentresponse} records and returns the list of changed updates
899 *
900 * This method is used to populate potential update info to be sent to site admins.
901 *
19d11b3b
DM
902 * @param array $old
903 * @param array $new
7b35553b
DM
904 * @throws available_update_checker_exception
905 * @return array parts of $new['updates'] that have changed
906 */
19d11b3b 907 protected function compare_responses(array $old, array $new) {
7b35553b 908
19d11b3b 909 if (empty($new)) {
7b35553b
DM
910 return array();
911 }
912
913 if (!array_key_exists('updates', $new)) {
914 throw new available_update_checker_exception('err_response_format');
915 }
916
19d11b3b 917 if (empty($old)) {
7b35553b
DM
918 return $new['updates'];
919 }
920
921 if (!array_key_exists('updates', $old)) {
922 throw new available_update_checker_exception('err_response_format');
923 }
924
925 $changes = array();
926
927 foreach ($new['updates'] as $newcomponent => $newcomponentupdates) {
928 if (empty($old['updates'][$newcomponent])) {
929 $changes[$newcomponent] = $newcomponentupdates;
930 continue;
931 }
932 foreach ($newcomponentupdates as $newcomponentupdate) {
933 $inold = false;
934 foreach ($old['updates'][$newcomponent] as $oldcomponentupdate) {
935 if ($newcomponentupdate['version'] == $oldcomponentupdate['version']) {
936 $inold = true;
937 }
938 }
939 if (!$inold) {
940 if (!isset($changes[$newcomponent])) {
941 $changes[$newcomponent] = array();
942 }
943 $changes[$newcomponent][] = $newcomponentupdate;
944 }
945 }
946 }
947
948 return $changes;
949 }
950
cd0bb55f
DM
951 /**
952 * Returns the URL to send update requests to
953 *
954 * During the development or testing, you can set $CFG->alternativeupdateproviderurl
955 * to a custom URL that will be used. Otherwise the standard URL will be returned.
956 *
957 * @return string URL
958 */
959 protected function prepare_request_url() {
960 global $CFG;
961
962 if (!empty($CFG->alternativeupdateproviderurl)) {
963 return $CFG->alternativeupdateproviderurl;
964 } else {
965 return 'http://download.moodle.org/api/1.0/updates.php';
966 }
967 }
968
55585f3a 969 /**
4442cc80 970 * Sets the properties currentversion, currentrelease, currentbranch and currentplugins
55585f3a
DM
971 *
972 * @param bool $forcereload
973 */
974 protected function load_current_environment($forcereload=false) {
975 global $CFG;
976
977 if (!is_null($this->currentversion) and !$forcereload) {
978 // nothing to do
979 return;
980 }
981
975311d3
PS
982 $version = null;
983 $release = null;
984
55585f3a
DM
985 require($CFG->dirroot.'/version.php');
986 $this->currentversion = $version;
4442cc80 987 $this->currentrelease = $release;
55585f3a
DM
988 $this->currentbranch = moodle_major_version(true);
989
990 $pluginman = plugin_manager::instance();
991 foreach ($pluginman->get_plugins() as $type => $plugins) {
992 foreach ($plugins as $plugin) {
993 if (!$plugin->is_standard()) {
994 $this->currentplugins[$plugin->component] = $plugin->versiondisk;
995 }
996 }
997 }
998 }
999
cd0bb55f
DM
1000 /**
1001 * Returns the list of HTTP params to be sent to the updates provider URL
1002 *
1003 * @return array of (string)param => (string)value
1004 */
1005 protected function prepare_request_params() {
1006 global $CFG;
1007
55585f3a 1008 $this->load_current_environment();
7d8de6d8
DM
1009 $this->restore_response();
1010
cd0bb55f
DM
1011 $params = array();
1012 $params['format'] = 'json';
1013
7d8de6d8
DM
1014 if (isset($this->recentresponse['ticket'])) {
1015 $params['ticket'] = $this->recentresponse['ticket'];
cd0bb55f
DM
1016 }
1017
55585f3a
DM
1018 if (isset($this->currentversion)) {
1019 $params['version'] = $this->currentversion;
1020 } else {
1021 throw new coding_exception('Main Moodle version must be already known here');
cd0bb55f
DM
1022 }
1023
55585f3a
DM
1024 if (isset($this->currentbranch)) {
1025 $params['branch'] = $this->currentbranch;
1026 } else {
1027 throw new coding_exception('Moodle release must be already known here');
1028 }
1029
1030 $plugins = array();
1031 foreach ($this->currentplugins as $plugin => $version) {
1032 $plugins[] = $plugin.'@'.$version;
1033 }
1034 if (!empty($plugins)) {
1035 $params['plugins'] = implode(',', $plugins);
cd0bb55f
DM
1036 }
1037
cd0bb55f
DM
1038 return $params;
1039 }
be378880
DM
1040
1041 /**
1042 * Returns the current timestamp
1043 *
1044 * @return int the timestamp
1045 */
1046 protected function cron_current_timestamp() {
1047 return time();
1048 }
1049
1050 /**
1051 * Output cron debugging info
1052 *
1053 * @see mtrace()
1054 * @param string $msg output message
1055 * @param string $eol end of line
1056 */
1057 protected function cron_mtrace($msg, $eol = PHP_EOL) {
1058 mtrace($msg, $eol);
1059 }
1060
1061 /**
1062 * Decide if the autocheck feature is disabled in the server setting
1063 *
1064 * @return bool true if autocheck enabled, false if disabled
1065 */
1066 protected function cron_autocheck_enabled() {
718eb2a5
DM
1067 global $CFG;
1068
be378880
DM
1069 if (empty($CFG->updateautocheck)) {
1070 return false;
1071 } else {
1072 return true;
1073 }
1074 }
1075
1076 /**
1077 * Decide if the recently fetched data are still fresh enough
1078 *
1079 * @param int $now current timestamp
1080 * @return bool true if no need to re-fetch, false otherwise
1081 */
1082 protected function cron_has_fresh_fetch($now) {
1083 $recent = $this->get_last_timefetched();
1084
1085 if (empty($recent)) {
1086 return false;
1087 }
1088
1089 if ($now < $recent) {
1090 $this->cron_mtrace('The most recent fetch is reported to be in the future, this is weird!');
1091 return true;
1092 }
1093
7092ea5d 1094 if ($now - $recent > 24 * HOURSECS) {
be378880
DM
1095 return false;
1096 }
1097
1098 return true;
1099 }
1100
1101 /**
1102 * Decide if the fetch is outadated or even missing
1103 *
1104 * @param int $now current timestamp
1105 * @return bool false if no need to re-fetch, true otherwise
1106 */
1107 protected function cron_has_outdated_fetch($now) {
1108 $recent = $this->get_last_timefetched();
1109
1110 if (empty($recent)) {
1111 return true;
1112 }
1113
1114 if ($now < $recent) {
1115 $this->cron_mtrace('The most recent fetch is reported to be in the future, this is weird!');
1116 return false;
1117 }
1118
1119 if ($now - $recent > 48 * HOURSECS) {
1120 return true;
1121 }
1122
1123 return false;
1124 }
1125
1126 /**
1127 * Returns the cron execution offset for this site
1128 *
1129 * The main {@link self::cron()} is supposed to run every night in some random time
1130 * between 01:00 and 06:00 AM (local time). The exact moment is defined by so called
1131 * execution offset, that is the amount of time after 01:00 AM. The offset value is
1132 * initially generated randomly and then used consistently at the site. This way, the
1133 * regular checks against the download.moodle.org server are spread in time.
1134 *
1135 * @return int the offset number of seconds from range 1 sec to 5 hours
1136 */
1137 protected function cron_execution_offset() {
1138 global $CFG;
1139
1140 if (empty($CFG->updatecronoffset)) {
1141 set_config('updatecronoffset', rand(1, 5 * HOURSECS));
1142 }
1143
1144 return $CFG->updatecronoffset;
1145 }
1146
1147 /**
1148 * Fetch available updates info and eventually send notification to site admins
1149 */
1150 protected function cron_execute() {
7b35553b 1151
19d11b3b 1152 try {
fd87d0bf
AB
1153 $this->restore_response();
1154 $previous = $this->recentresponse;
1155 $this->fetch();
1156 $this->restore_response(true);
1157 $current = $this->recentresponse;
19d11b3b
DM
1158 $changes = $this->compare_responses($previous, $current);
1159 $notifications = $this->cron_notifications($changes);
1160 $this->cron_notify($notifications);
a77141a7 1161 $this->cron_mtrace('done');
19d11b3b
DM
1162 } catch (available_update_checker_exception $e) {
1163 $this->cron_mtrace('FAILED!');
1164 }
1165 }
1166
1167 /**
1168 * Given the list of changes in available updates, pick those to send to site admins
1169 *
1170 * @param array $changes as returned by {@link self::compare_responses()}
1171 * @return array of available_update_info objects to send to site admins
1172 */
1173 protected function cron_notifications(array $changes) {
1174 global $CFG;
1175
1176 $notifications = array();
1177 $pluginman = plugin_manager::instance();
1178 $plugins = $pluginman->get_plugins(true);
1179
1180 foreach ($changes as $component => $componentchanges) {
718eb2a5
DM
1181 if (empty($componentchanges)) {
1182 continue;
1183 }
19d11b3b
DM
1184 $componentupdates = $this->get_update_info($component,
1185 array('minmaturity' => $CFG->updateminmaturity, 'notifybuilds' => $CFG->updatenotifybuilds));
718eb2a5
DM
1186 if (empty($componentupdates)) {
1187 continue;
1188 }
19d11b3b
DM
1189 // notify only about those $componentchanges that are present in $componentupdates
1190 // to respect the preferences
1191 foreach ($componentchanges as $componentchange) {
1192 foreach ($componentupdates as $componentupdate) {
1193 if ($componentupdate->version == $componentchange['version']) {
1194 if ($component == 'core') {
1195 // in case of 'core' this is enough, we already know that the
1196 // $componentupdate is a real update with higher version
1197 $notifications[] = $componentupdate;
1198 } else {
1199 // use the plugin_manager to check if the reported $componentchange
1200 // is a real update with higher version. such a real update must be
1201 // present in the 'availableupdates' property of one of the component's
1202 // available_update_info object
1203 list($plugintype, $pluginname) = normalize_component($component);
1204 if (!empty($plugins[$plugintype][$pluginname]->availableupdates)) {
1205 foreach ($plugins[$plugintype][$pluginname]->availableupdates as $availableupdate) {
1206 if ($availableupdate->version == $componentchange['version']) {
1207 $notifications[] = $componentupdate;
1208 }
1209 }
1210 }
1211 }
1212 }
1213 }
1214 }
1215 }
1216
1217 return $notifications;
be378880 1218 }
a77141a7
DM
1219
1220 /**
1221 * Sends the given notifications to site admins via messaging API
1222 *
1223 * @param array $notifications array of available_update_info objects to send
1224 */
1225 protected function cron_notify(array $notifications) {
1226 global $CFG;
1227
1228 if (empty($notifications)) {
1229 return;
1230 }
1231
1232 $admins = get_admins();
1233
1234 if (empty($admins)) {
1235 return;
1236 }
1237
1238 $this->cron_mtrace('sending notifications ... ', '');
1239
1240 $text = get_string('updatenotifications', 'core_admin') . PHP_EOL;
1241 $html = html_writer::tag('h1', get_string('updatenotifications', 'core_admin')) . PHP_EOL;
1242
1243 $coreupdates = array();
1244 $pluginupdates = array();
1245
660c4d46 1246 foreach ($notifications as $notification) {
a77141a7
DM
1247 if ($notification->component == 'core') {
1248 $coreupdates[] = $notification;
1249 } else {
1250 $pluginupdates[] = $notification;
1251 }
1252 }
1253
1254 if (!empty($coreupdates)) {
1255 $text .= PHP_EOL . get_string('updateavailable', 'core_admin') . PHP_EOL;
1256 $html .= html_writer::tag('h2', get_string('updateavailable', 'core_admin')) . PHP_EOL;
1257 $html .= html_writer::start_tag('ul') . PHP_EOL;
1258 foreach ($coreupdates as $coreupdate) {
1259 $html .= html_writer::start_tag('li');
1260 if (isset($coreupdate->release)) {
1261 $text .= get_string('updateavailable_release', 'core_admin', $coreupdate->release);
1262 $html .= html_writer::tag('strong', get_string('updateavailable_release', 'core_admin', $coreupdate->release));
1263 }
1264 if (isset($coreupdate->version)) {
1265 $text .= ' '.get_string('updateavailable_version', 'core_admin', $coreupdate->version);
1266 $html .= ' '.get_string('updateavailable_version', 'core_admin', $coreupdate->version);
1267 }
1268 if (isset($coreupdate->maturity)) {
1269 $text .= ' ('.get_string('maturity'.$coreupdate->maturity, 'core_admin').')';
1270 $html .= ' ('.get_string('maturity'.$coreupdate->maturity, 'core_admin').')';
1271 }
1272 $text .= PHP_EOL;
1273 $html .= html_writer::end_tag('li') . PHP_EOL;
1274 }
1275 $text .= PHP_EOL;
1276 $html .= html_writer::end_tag('ul') . PHP_EOL;
1277
1278 $a = array('url' => $CFG->wwwroot.'/'.$CFG->admin.'/index.php');
1279 $text .= get_string('updateavailabledetailslink', 'core_admin', $a) . PHP_EOL;
1280 $a = array('url' => html_writer::link($CFG->wwwroot.'/'.$CFG->admin.'/index.php', $CFG->wwwroot.'/'.$CFG->admin.'/index.php'));
1281 $html .= html_writer::tag('p', get_string('updateavailabledetailslink', 'core_admin', $a)) . PHP_EOL;
1282 }
1283
1284 if (!empty($pluginupdates)) {
1285 $text .= PHP_EOL . get_string('updateavailableforplugin', 'core_admin') . PHP_EOL;
1286 $html .= html_writer::tag('h2', get_string('updateavailableforplugin', 'core_admin')) . PHP_EOL;
1287
1288 $html .= html_writer::start_tag('ul') . PHP_EOL;
1289 foreach ($pluginupdates as $pluginupdate) {
1290 $html .= html_writer::start_tag('li');
1291 $text .= get_string('pluginname', $pluginupdate->component);
1292 $html .= html_writer::tag('strong', get_string('pluginname', $pluginupdate->component));
1293
1294 $text .= ' ('.$pluginupdate->component.')';
1295 $html .= ' ('.$pluginupdate->component.')';
1296
1297 $text .= ' '.get_string('updateavailable', 'core_plugin', $pluginupdate->version);
1298 $html .= ' '.get_string('updateavailable', 'core_plugin', $pluginupdate->version);
1299
1300 $text .= PHP_EOL;
1301 $html .= html_writer::end_tag('li') . PHP_EOL;
1302 }
1303 $text .= PHP_EOL;
1304 $html .= html_writer::end_tag('ul') . PHP_EOL;
b9934a17 1305
a77141a7
DM
1306 $a = array('url' => $CFG->wwwroot.'/'.$CFG->admin.'/plugins.php');
1307 $text .= get_string('updateavailabledetailslink', 'core_admin', $a) . PHP_EOL;
1308 $a = array('url' => html_writer::link($CFG->wwwroot.'/'.$CFG->admin.'/plugins.php', $CFG->wwwroot.'/'.$CFG->admin.'/plugins.php'));
1309 $html .= html_writer::tag('p', get_string('updateavailabledetailslink', 'core_admin', $a)) . PHP_EOL;
1310 }
1311
1312 $a = array('siteurl' => $CFG->wwwroot);
1313 $text .= get_string('updatenotificationfooter', 'core_admin', $a) . PHP_EOL;
1314 $a = array('siteurl' => html_writer::link($CFG->wwwroot, $CFG->wwwroot));
1315 $html .= html_writer::tag('footer', html_writer::tag('p', get_string('updatenotificationfooter', 'core_admin', $a),
1316 array('style' => 'font-size:smaller; color:#333;')));
1317
1318 $mainadmin = reset($admins);
1319
1320 foreach ($admins as $admin) {
1321 $message = new stdClass();
1322 $message->component = 'moodle';
1323 $message->name = 'availableupdate';
1324 $message->userfrom = $mainadmin;
1325 $message->userto = $admin;
1326 $message->subject = get_string('updatenotifications', 'core_admin');
1327 $message->fullmessage = $text;
1328 $message->fullmessageformat = FORMAT_PLAIN;
1329 $message->fullmessagehtml = $html;
cd89994d
DM
1330 $message->smallmessage = get_string('updatenotifications', 'core_admin');
1331 $message->notification = 1;
a77141a7
DM
1332 message_send($message);
1333 }
1334 }
b9934a17
DM
1335
1336 /**
4442cc80 1337 * Compare two release labels and decide if they are the same
b9934a17 1338 *
4442cc80
DM
1339 * @param string $remote release info of the available update
1340 * @param null|string $local release info of the local code, defaults to $release defined in version.php
1341 * @return boolean true if the releases declare the same minor+major version
b9934a17 1342 */
4442cc80 1343 protected function is_same_release($remote, $local=null) {
b9934a17 1344
4442cc80
DM
1345 if (is_null($local)) {
1346 $this->load_current_environment();
1347 $local = $this->currentrelease;
1348 }
0242bdc7 1349
4442cc80 1350 $pattern = '/^([0-9\.\+]+)([^(]*)/';
b9934a17 1351
4442cc80
DM
1352 preg_match($pattern, $remote, $remotematches);
1353 preg_match($pattern, $local, $localmatches);
b9934a17 1354
4442cc80
DM
1355 $remotematches[1] = str_replace('+', '', $remotematches[1]);
1356 $localmatches[1] = str_replace('+', '', $localmatches[1]);
1357
1358 if ($remotematches[1] === $localmatches[1] and rtrim($remotematches[2]) === rtrim($localmatches[2])) {
1359 return true;
1360 } else {
1361 return false;
1362 }
1363 }
cd0bb55f
DM
1364}
1365
1366
7d8de6d8
DM
1367/**
1368 * Defines the structure of objects returned by {@link available_update_checker::get_update_info()}
1369 */
1370class available_update_info {
1371
1372 /** @var string frankenstyle component name */
1373 public $component;
1374 /** @var int the available version of the component */
1375 public $version;
1376 /** @var string|null optional release name */
1377 public $release = null;
1378 /** @var int|null optional maturity info, eg {@link MATURITY_STABLE} */
1379 public $maturity = null;
1380 /** @var string|null optional URL of a page with more info about the update */
1381 public $url = null;
1382 /** @var string|null optional URL of a ZIP package that can be downloaded and installed */
1383 public $download = null;
1384
1385 /**
1386 * Creates new instance of the class
b9934a17 1387 *
7d8de6d8
DM
1388 * The $info array must provide at least the 'version' value and optionally all other
1389 * values to populate the object's properties.
b9934a17 1390 *
7d8de6d8
DM
1391 * @param string $name the frankenstyle component name
1392 * @param array $info associative array with other properties
1393 */
1394 public function __construct($name, array $info) {
1395 $this->component = $name;
1396 foreach ($info as $k => $v) {
1397 if (property_exists('available_update_info', $k) and $k != 'component') {
1398 $this->$k = $v;
1399 }
1400 }
1401 }
1402}
1403
1404
00ef3c3e
DM
1405/**
1406 * Factory class producing required subclasses of {@link plugininfo_base}
1407 */
1408class plugininfo_default_factory {
b9934a17
DM
1409
1410 /**
00ef3c3e 1411 * Makes a new instance of the plugininfo class
b9934a17 1412 *
00ef3c3e
DM
1413 * @param string $type the plugin type, eg. 'mod'
1414 * @param string $typerootdir full path to the location of all the plugins of this type
1415 * @param string $name the plugin name, eg. 'workshop'
1416 * @param string $namerootdir full path to the location of the plugin
1417 * @param string $typeclass the name of class that holds the info about the plugin
1418 * @return plugininfo_base the instance of $typeclass
1419 */
1420 public static function make($type, $typerootdir, $name, $namerootdir, $typeclass) {
1421 $plugin = new $typeclass();
1422 $plugin->type = $type;
1423 $plugin->typerootdir = $typerootdir;
1424 $plugin->name = $name;
1425 $plugin->rootdir = $namerootdir;
1426
1427 $plugin->init_display_name();
1428 $plugin->load_disk_version();
1429 $plugin->load_db_version();
1430 $plugin->load_required_main_version();
1431 $plugin->init_is_standard();
473289a0 1432
00ef3c3e
DM
1433 return $plugin;
1434 }
b9934a17
DM
1435}
1436
00ef3c3e 1437
b9934a17 1438/**
b6ad8594 1439 * Base class providing access to the information about a plugin
828788f0
TH
1440 *
1441 * @property-read string component the component name, type_name
b9934a17 1442 */
b6ad8594 1443abstract class plugininfo_base {
b9934a17
DM
1444
1445 /** @var string the plugintype name, eg. mod, auth or workshopform */
1446 public $type;
1447 /** @var string full path to the location of all the plugins of this type */
1448 public $typerootdir;
1449 /** @var string the plugin name, eg. assignment, ldap */
1450 public $name;
1451 /** @var string the localized plugin name */
1452 public $displayname;
1453 /** @var string the plugin source, one of plugin_manager::PLUGIN_SOURCE_xxx constants */
1454 public $source;
1455 /** @var fullpath to the location of this plugin */
1456 public $rootdir;
1457 /** @var int|string the version of the plugin's source code */
1458 public $versiondisk;
1459 /** @var int|string the version of the installed plugin */
1460 public $versiondb;
1461 /** @var int|float|string required version of Moodle core */
1462 public $versionrequires;
b6ad8594
DM
1463 /** @var array other plugins that this one depends on, lazy-loaded by {@link get_other_required_plugins()} */
1464 public $dependencies;
b9934a17
DM
1465 /** @var int number of instances of the plugin - not supported yet */
1466 public $instances;
1467 /** @var int order of the plugin among other plugins of the same type - not supported yet */
1468 public $sortorder;
7d8de6d8
DM
1469 /** @var array|null array of {@link available_update_info} for this plugin */
1470 public $availableupdates;
b9934a17
DM
1471
1472 /**
b6ad8594
DM
1473 * Gathers and returns the information about all plugins of the given type
1474 *
b6ad8594
DM
1475 * @param string $type the name of the plugintype, eg. mod, auth or workshopform
1476 * @param string $typerootdir full path to the location of the plugin dir
1477 * @param string $typeclass the name of the actually called class
1478 * @return array of plugintype classes, indexed by the plugin name
b9934a17
DM
1479 */
1480 public static function get_plugins($type, $typerootdir, $typeclass) {
1481
1482 // get the information about plugins at the disk
1483 $plugins = get_plugin_list($type);
1484 $ondisk = array();
1485 foreach ($plugins as $pluginname => $pluginrootdir) {
00ef3c3e
DM
1486 $ondisk[$pluginname] = plugininfo_default_factory::make($type, $typerootdir,
1487 $pluginname, $pluginrootdir, $typeclass);
b9934a17
DM
1488 }
1489 return $ondisk;
1490 }
1491
1492 /**
b6ad8594 1493 * Sets {@link $displayname} property to a localized name of the plugin
b9934a17 1494 */
b8343e68 1495 public function init_display_name() {
828788f0
TH
1496 if (!get_string_manager()->string_exists('pluginname', $this->component)) {
1497 $this->displayname = '[pluginname,' . $this->component . ']';
b9934a17 1498 } else {
828788f0
TH
1499 $this->displayname = get_string('pluginname', $this->component);
1500 }
1501 }
1502
1503 /**
1504 * Magic method getter, redirects to read only values.
b6ad8594 1505 *
828788f0
TH
1506 * @param string $name
1507 * @return mixed
1508 */
1509 public function __get($name) {
1510 switch ($name) {
1511 case 'component': return $this->type . '_' . $this->name;
1512
1513 default:
1514 debugging('Invalid plugin property accessed! '.$name);
1515 return null;
b9934a17
DM
1516 }
1517 }
1518
1519 /**
b6ad8594
DM
1520 * Return the full path name of a file within the plugin.
1521 *
1522 * No check is made to see if the file exists.
1523 *
1524 * @param string $relativepath e.g. 'version.php'.
1525 * @return string e.g. $CFG->dirroot . '/mod/quiz/version.php'.
b9934a17 1526 */
473289a0 1527 public function full_path($relativepath) {
b9934a17 1528 if (empty($this->rootdir)) {
473289a0 1529 return '';
b9934a17 1530 }
473289a0
TH
1531 return $this->rootdir . '/' . $relativepath;
1532 }
b9934a17 1533
473289a0
TH
1534 /**
1535 * Load the data from version.php.
b6ad8594
DM
1536 *
1537 * @return stdClass the object called $plugin defined in version.php
473289a0
TH
1538 */
1539 protected function load_version_php() {
1540 $versionfile = $this->full_path('version.php');
b9934a17 1541
473289a0 1542 $plugin = new stdClass();
b9934a17
DM
1543 if (is_readable($versionfile)) {
1544 include($versionfile);
b9934a17 1545 }
473289a0 1546 return $plugin;
b9934a17
DM
1547 }
1548
1549 /**
b6ad8594
DM
1550 * Sets {@link $versiondisk} property to a numerical value representing the
1551 * version of the plugin's source code.
1552 *
1553 * If the value is null after calling this method, either the plugin
1554 * does not use versioning (typically does not have any database
1555 * data) or is missing from disk.
b9934a17 1556 */
473289a0
TH
1557 public function load_disk_version() {
1558 $plugin = $this->load_version_php();
1559 if (isset($plugin->version)) {
1560 $this->versiondisk = $plugin->version;
b9934a17
DM
1561 }
1562 }
1563
1564 /**
b6ad8594
DM
1565 * Sets {@link $versionrequires} property to a numerical value representing
1566 * the version of Moodle core that this plugin requires.
b9934a17 1567 */
b8343e68 1568 public function load_required_main_version() {
473289a0
TH
1569 $plugin = $this->load_version_php();
1570 if (isset($plugin->requires)) {
1571 $this->versionrequires = $plugin->requires;
b9934a17 1572 }
473289a0 1573 }
b9934a17 1574
0242bdc7 1575 /**
777781d1 1576 * Initialise {@link $dependencies} to the list of other plugins (in any)
0242bdc7
TH
1577 * that this one requires to be installed.
1578 */
1579 protected function load_other_required_plugins() {
1580 $plugin = $this->load_version_php();
777781d1
TH
1581 if (!empty($plugin->dependencies)) {
1582 $this->dependencies = $plugin->dependencies;
0242bdc7 1583 } else {
777781d1 1584 $this->dependencies = array(); // By default, no dependencies.
0242bdc7
TH
1585 }
1586 }
1587
1588 /**
b6ad8594
DM
1589 * Get the list of other plugins that this plugin requires to be installed.
1590 *
1591 * @return array with keys the frankenstyle plugin name, and values either
1592 * a version string (like '2011101700') or the constant ANY_VERSION.
0242bdc7
TH
1593 */
1594 public function get_other_required_plugins() {
777781d1 1595 if (is_null($this->dependencies)) {
0242bdc7
TH
1596 $this->load_other_required_plugins();
1597 }
777781d1 1598 return $this->dependencies;
0242bdc7
TH
1599 }
1600
473289a0 1601 /**
b6ad8594
DM
1602 * Sets {@link $versiondb} property to a numerical value representing the
1603 * currently installed version of the plugin.
1604 *
1605 * If the value is null after calling this method, either the plugin
1606 * does not use versioning (typically does not have any database
1607 * data) or has not been installed yet.
473289a0
TH
1608 */
1609 public function load_db_version() {
828788f0 1610 if ($ver = self::get_version_from_config_plugins($this->component)) {
473289a0 1611 $this->versiondb = $ver;
b9934a17
DM
1612 }
1613 }
1614
1615 /**
b6ad8594
DM
1616 * Sets {@link $source} property to one of plugin_manager::PLUGIN_SOURCE_xxx
1617 * constants.
1618 *
1619 * If the property's value is null after calling this method, then
1620 * the type of the plugin has not been recognized and you should throw
1621 * an exception.
b9934a17 1622 */
b8343e68 1623 public function init_is_standard() {
b9934a17
DM
1624
1625 $standard = plugin_manager::standard_plugins_list($this->type);
1626
1627 if ($standard !== false) {
1628 $standard = array_flip($standard);
1629 if (isset($standard[$this->name])) {
1630 $this->source = plugin_manager::PLUGIN_SOURCE_STANDARD;
ec8935f5
PS
1631 } else if (!is_null($this->versiondb) and is_null($this->versiondisk)
1632 and plugin_manager::is_deleted_standard_plugin($this->type, $this->name)) {
1633 $this->source = plugin_manager::PLUGIN_SOURCE_STANDARD; // to be deleted
b9934a17
DM
1634 } else {
1635 $this->source = plugin_manager::PLUGIN_SOURCE_EXTENSION;
1636 }
1637 }
1638 }
1639
1640 /**
b6ad8594
DM
1641 * Returns true if the plugin is shipped with the official distribution
1642 * of the current Moodle version, false otherwise.
1643 *
1644 * @return bool
b9934a17
DM
1645 */
1646 public function is_standard() {
1647 return $this->source === plugin_manager::PLUGIN_SOURCE_STANDARD;
1648 }
1649
3a2300f5
DM
1650 /**
1651 * Returns true if the the given Moodle version is enough to run this plugin
1652 *
1653 * @param string|int|double $moodleversion
1654 * @return bool
1655 */
1656 public function is_core_dependency_satisfied($moodleversion) {
1657
1658 if (empty($this->versionrequires)) {
1659 return true;
1660
1661 } else {
1662 return (double)$this->versionrequires <= (double)$moodleversion;
1663 }
1664 }
1665
b9934a17 1666 /**
b6ad8594
DM
1667 * Returns the status of the plugin
1668 *
1669 * @return string one of plugin_manager::PLUGIN_STATUS_xxx constants
b9934a17
DM
1670 */
1671 public function get_status() {
1672
1673 if (is_null($this->versiondb) and is_null($this->versiondisk)) {
1674 return plugin_manager::PLUGIN_STATUS_NODB;
1675
1676 } else if (is_null($this->versiondb) and !is_null($this->versiondisk)) {
1677 return plugin_manager::PLUGIN_STATUS_NEW;
1678
1679 } else if (!is_null($this->versiondb) and is_null($this->versiondisk)) {
ec8935f5
PS
1680 if (plugin_manager::is_deleted_standard_plugin($this->type, $this->name)) {
1681 return plugin_manager::PLUGIN_STATUS_DELETE;
1682 } else {
1683 return plugin_manager::PLUGIN_STATUS_MISSING;
1684 }
b9934a17
DM
1685
1686 } else if ((string)$this->versiondb === (string)$this->versiondisk) {
1687 return plugin_manager::PLUGIN_STATUS_UPTODATE;
1688
1689 } else if ($this->versiondb < $this->versiondisk) {
1690 return plugin_manager::PLUGIN_STATUS_UPGRADE;
1691
1692 } else if ($this->versiondb > $this->versiondisk) {
1693 return plugin_manager::PLUGIN_STATUS_DOWNGRADE;
1694
1695 } else {
1696 // $version = pi(); and similar funny jokes - hopefully Donald E. Knuth will never contribute to Moodle ;-)
1697 throw new coding_exception('Unable to determine plugin state, check the plugin versions');
1698 }
1699 }
1700
1701 /**
b6ad8594
DM
1702 * Returns the information about plugin availability
1703 *
1704 * True means that the plugin is enabled. False means that the plugin is
1705 * disabled. Null means that the information is not available, or the
1706 * plugin does not support configurable availability or the availability
1707 * can not be changed.
1708 *
1709 * @return null|bool
b9934a17
DM
1710 */
1711 public function is_enabled() {
1712 return null;
1713 }
1714
1715 /**
7d8de6d8 1716 * Populates the property {@link $availableupdates} with the information provided by
dd119e21
DM
1717 * available update checker
1718 *
1719 * @param available_update_checker $provider the class providing the available update info
1720 */
7d8de6d8 1721 public function check_available_updates(available_update_checker $provider) {
c6f008e7
DM
1722 global $CFG;
1723
1724 if (isset($CFG->updateminmaturity)) {
1725 $minmaturity = $CFG->updateminmaturity;
1726 } else {
1727 // this can happen during the very first upgrade to 2.3
1728 $minmaturity = MATURITY_STABLE;
1729 }
1730
1731 $this->availableupdates = $provider->get_update_info($this->component,
1732 array('minmaturity' => $minmaturity));
dd119e21
DM
1733 }
1734
d26f3ddd 1735 /**
7d8de6d8 1736 * If there are updates for this plugin available, returns them.
d26f3ddd 1737 *
7d8de6d8
DM
1738 * Returns array of {@link available_update_info} objects, if some update
1739 * is available. Returns null if there is no update available or if the update
1740 * availability is unknown.
d26f3ddd 1741 *
7d8de6d8 1742 * @return array|null
d26f3ddd 1743 */
7d8de6d8 1744 public function available_updates() {
dd119e21 1745
7d8de6d8 1746 if (empty($this->availableupdates) or !is_array($this->availableupdates)) {
dd119e21
DM
1747 return null;
1748 }
1749
7d8de6d8
DM
1750 $updates = array();
1751
1752 foreach ($this->availableupdates as $availableupdate) {
1753 if ($availableupdate->version > $this->versiondisk) {
1754 $updates[] = $availableupdate;
1755 }
1756 }
1757
1758 if (empty($updates)) {
1759 return null;
dd119e21
DM
1760 }
1761
7d8de6d8 1762 return $updates;
d26f3ddd
DM
1763 }
1764
5cdb1893
MG
1765 /**
1766 * Returns the node name used in admin settings menu for this plugin settings (if applicable)
1767 *
1768 * @return null|string node name or null if plugin does not create settings node (default)
1769 */
1770 public function get_settings_section_name() {
1771 return null;
1772 }
1773
b9934a17 1774 /**
b6ad8594
DM
1775 * Returns the URL of the plugin settings screen
1776 *
1777 * Null value means that the plugin either does not have the settings screen
1778 * or its location is not available via this library.
1779 *
1780 * @return null|moodle_url
b9934a17
DM
1781 */
1782 public function get_settings_url() {
5cdb1893
MG
1783 $section = $this->get_settings_section_name();
1784 if ($section === null) {
1785 return null;
1786 }
1787 $settings = admin_get_root()->locate($section);
1788 if ($settings && $settings instanceof admin_settingpage) {
1789 return new moodle_url('/admin/settings.php', array('section' => $section));
1790 } else if ($settings && $settings instanceof admin_externalpage) {
1791 return new moodle_url($settings->url);
1792 } else {
1793 return null;
1794 }
1795 }
1796
1797 /**
1798 * Loads plugin settings to the settings tree
1799 *
1800 * This function usually includes settings.php file in plugins folder.
1801 * Alternatively it can create a link to some settings page (instance of admin_externalpage)
1802 *
1803 * @param part_of_admin_tree $adminroot
1804 * @param string $parentnodename
1805 * @param bool $hassiteconfig whether the current user has moodle/site:config capability
1806 */
1807 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
b9934a17
DM
1808 }
1809
1810 /**
b6ad8594
DM
1811 * Returns the URL of the screen where this plugin can be uninstalled
1812 *
1813 * Visiting that URL must be safe, that is a manual confirmation is needed
1814 * for actual uninstallation of the plugin. Null value means that the
1815 * plugin either does not support uninstallation, or does not require any
1816 * database cleanup or the location of the screen is not available via this
1817 * library.
1818 *
1819 * @return null|moodle_url
b9934a17
DM
1820 */
1821 public function get_uninstall_url() {
1822 return null;
1823 }
1824
1825 /**
b6ad8594
DM
1826 * Returns relative directory of the plugin with heading '/'
1827 *
1828 * @return string
b9934a17
DM
1829 */
1830 public function get_dir() {
1831 global $CFG;
1832
1833 return substr($this->rootdir, strlen($CFG->dirroot));
1834 }
1835
1836 /**
1837 * Provides access to plugin versions from {config_plugins}
1838 *
1839 * @param string $plugin plugin name
1840 * @param double $disablecache optional, defaults to false
1841 * @return int|false the stored value or false if not found
1842 */
1843 protected function get_version_from_config_plugins($plugin, $disablecache=false) {
1844 global $DB;
1845 static $pluginversions = null;
1846
1847 if (is_null($pluginversions) or $disablecache) {
f433088d
PS
1848 try {
1849 $pluginversions = $DB->get_records_menu('config_plugins', array('name' => 'version'), 'plugin', 'plugin,value');
1850 } catch (dml_exception $e) {
1851 // before install
1852 $pluginversions = array();
1853 }
b9934a17
DM
1854 }
1855
1856 if (!array_key_exists($plugin, $pluginversions)) {
1857 return false;
1858 }
1859
1860 return $pluginversions[$plugin];
1861 }
1862}
1863
b6ad8594 1864
b9934a17
DM
1865/**
1866 * General class for all plugin types that do not have their own class
1867 */
b6ad8594 1868class plugininfo_general extends plugininfo_base {
b9934a17
DM
1869}
1870
b6ad8594 1871
b9934a17
DM
1872/**
1873 * Class for page side blocks
1874 */
b6ad8594 1875class plugininfo_block extends plugininfo_base {
b9934a17 1876
b9934a17
DM
1877 public static function get_plugins($type, $typerootdir, $typeclass) {
1878
1879 // get the information about blocks at the disk
1880 $blocks = parent::get_plugins($type, $typerootdir, $typeclass);
1881
1882 // add blocks missing from disk
1883 $blocksinfo = self::get_blocks_info();
1884 foreach ($blocksinfo as $blockname => $blockinfo) {
1885 if (isset($blocks[$blockname])) {
1886 continue;
1887 }
1888 $plugin = new $typeclass();
1889 $plugin->type = $type;
1890 $plugin->typerootdir = $typerootdir;
1891 $plugin->name = $blockname;
1892 $plugin->rootdir = null;
1893 $plugin->displayname = $blockname;
1894 $plugin->versiondb = $blockinfo->version;
b8343e68 1895 $plugin->init_is_standard();
b9934a17
DM
1896
1897 $blocks[$blockname] = $plugin;
1898 }
1899
1900 return $blocks;
1901 }
1902
870d4280
MG
1903 /**
1904 * Magic method getter, redirects to read only values.
1905 *
1906 * For block plugins pretends the object has 'visible' property for compatibility
1907 * with plugins developed for Moodle version below 2.4
1908 *
1909 * @param string $name
1910 * @return mixed
1911 */
1912 public function __get($name) {
1913 if ($name === 'visible') {
1914 debugging('This is now an instance of plugininfo_block, please use $block->is_enabled() instead of $block->visible', DEBUG_DEVELOPER);
1915 return ($this->is_enabled() !== false);
1916 }
1917 return parent::__get($name);
1918 }
1919
b8343e68 1920 public function init_display_name() {
b9934a17
DM
1921
1922 if (get_string_manager()->string_exists('pluginname', 'block_' . $this->name)) {
1923 $this->displayname = get_string('pluginname', 'block_' . $this->name);
1924
1925 } else if (($block = block_instance($this->name)) !== false) {
1926 $this->displayname = $block->get_title();
1927
1928 } else {
b8343e68 1929 parent::init_display_name();
b9934a17
DM
1930 }
1931 }
1932
b8343e68 1933 public function load_db_version() {
b9934a17
DM
1934 global $DB;
1935
1936 $blocksinfo = self::get_blocks_info();
1937 if (isset($blocksinfo[$this->name]->version)) {
1938 $this->versiondb = $blocksinfo[$this->name]->version;
1939 }
1940 }
1941
b9934a17
DM
1942 public function is_enabled() {
1943
1944 $blocksinfo = self::get_blocks_info();
1945 if (isset($blocksinfo[$this->name]->visible)) {
1946 if ($blocksinfo[$this->name]->visible) {
1947 return true;
1948 } else {
1949 return false;
1950 }
1951 } else {
1952 return parent::is_enabled();
1953 }
1954 }
1955
870d4280
MG
1956 public function get_settings_section_name() {
1957 return 'blocksetting' . $this->name;
1958 }
b9934a17 1959
870d4280
MG
1960 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
1961 global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
1962 $ADMIN = $adminroot; // may be used in settings.php
1963 $block = $this; // also can be used inside settings.php
1964 $section = $this->get_settings_section_name();
b9934a17 1965
870d4280
MG
1966 if (!$hassiteconfig || (($blockinstance = block_instance($this->name)) === false)) {
1967 return;
1968 }
1969
1970 $settings = null;
1971 if ($blockinstance->has_config()) {
6740c605 1972 if (file_exists($this->full_path('settings.php'))) {
870d4280
MG
1973 $settings = new admin_settingpage($section, $this->displayname,
1974 'moodle/site:config', $this->is_enabled() === false);
1975 include($this->full_path('settings.php')); // this may also set $settings to null
b9934a17
DM
1976 } else {
1977 $blocksinfo = self::get_blocks_info();
870d4280
MG
1978 $settingsurl = new moodle_url('/admin/block.php', array('block' => $blocksinfo[$this->name]->id));
1979 $settings = new admin_externalpage($section, $this->displayname,
1980 $settingsurl, 'moodle/site:config', $this->is_enabled() === false);
b9934a17 1981 }
870d4280
MG
1982 }
1983 if ($settings) {
1984 $ADMIN->add($parentnodename, $settings);
b9934a17
DM
1985 }
1986 }
1987
b9934a17
DM
1988 public function get_uninstall_url() {
1989
1990 $blocksinfo = self::get_blocks_info();
1991 return new moodle_url('/admin/blocks.php', array('delete' => $blocksinfo[$this->name]->id, 'sesskey' => sesskey()));
1992 }
1993
1994 /**
1995 * Provides access to the records in {block} table
1996 *
1997 * @param bool $disablecache do not use internal static cache
1998 * @return array array of stdClasses
1999 */
2000 protected static function get_blocks_info($disablecache=false) {
2001 global $DB;
2002 static $blocksinfocache = null;
2003
2004 if (is_null($blocksinfocache) or $disablecache) {
f433088d
PS
2005 try {
2006 $blocksinfocache = $DB->get_records('block', null, 'name', 'name,id,version,visible');
2007 } catch (dml_exception $e) {
2008 // before install
2009 $blocksinfocache = array();
2010 }
b9934a17
DM
2011 }
2012
2013 return $blocksinfocache;
2014 }
2015}
2016
b6ad8594 2017
b9934a17
DM
2018/**
2019 * Class for text filters
2020 */
b6ad8594 2021class plugininfo_filter extends plugininfo_base {
b9934a17 2022
b9934a17 2023 public static function get_plugins($type, $typerootdir, $typeclass) {
7c9b837e 2024 global $CFG, $DB;
b9934a17
DM
2025
2026 $filters = array();
2027
2028 // get the list of filters from both /filter and /mod location
2029 $installed = filter_get_all_installed();
2030
2031 foreach ($installed as $filterlegacyname => $displayname) {
2032 $plugin = new $typeclass();
2033 $plugin->type = $type;
2034 $plugin->typerootdir = $typerootdir;
2035 $plugin->name = self::normalize_legacy_name($filterlegacyname);
2036 $plugin->rootdir = $CFG->dirroot . '/' . $filterlegacyname;
2037 $plugin->displayname = $displayname;
2038
b8343e68
TH
2039 $plugin->load_disk_version();
2040 $plugin->load_db_version();
2041 $plugin->load_required_main_version();
2042 $plugin->init_is_standard();
b9934a17
DM
2043
2044 $filters[$plugin->name] = $plugin;
2045 }
2046
b9934a17 2047 $globalstates = self::get_global_states();
7c9b837e
DM
2048
2049 if ($DB->get_manager()->table_exists('filter_active')) {
2050 // if we're upgrading from 1.9, the table does not exist yet
2051 // if it does, make sure that all installed filters are registered
2052 $needsreload = false;
2053 foreach (array_keys($installed) as $filterlegacyname) {
2054 if (!isset($globalstates[self::normalize_legacy_name($filterlegacyname)])) {
2055 filter_set_global_state($filterlegacyname, TEXTFILTER_DISABLED);
2056 $needsreload = true;
2057 }
2058 }
2059 if ($needsreload) {
2060 $globalstates = self::get_global_states(true);
b9934a17 2061 }
b9934a17
DM
2062 }
2063
2064 // make sure that all registered filters are installed, just in case
2065 foreach ($globalstates as $name => $info) {
2066 if (!isset($filters[$name])) {
2067 // oops, there is a record in filter_active but the filter is not installed
2068 $plugin = new $typeclass();
2069 $plugin->type = $type;
2070 $plugin->typerootdir = $typerootdir;
2071 $plugin->name = $name;
2072 $plugin->rootdir = $CFG->dirroot . '/' . $info->legacyname;
2073 $plugin->displayname = $info->legacyname;
2074
b8343e68 2075 $plugin->load_db_version();
b9934a17
DM
2076
2077 if (is_null($plugin->versiondb)) {
2078 // this is a hack to stimulate 'Missing from disk' error
2079 // because $plugin->versiondisk will be null !== false
2080 $plugin->versiondb = false;
2081 }
2082
2083 $filters[$plugin->name] = $plugin;
2084 }
2085 }
2086
2087 return $filters;
2088 }
2089
b8343e68 2090 public function init_display_name() {
b9934a17
DM
2091 // do nothing, the name is set in self::get_plugins()
2092 }
2093
2094 /**
b6ad8594 2095 * @see load_version_php()
b9934a17 2096 */
473289a0 2097 protected function load_version_php() {
b9934a17 2098 if (strpos($this->name, 'mod_') === 0) {
473289a0
TH
2099 // filters bundled with modules do not have a version.php and so
2100 // do not provide their own versioning information.
2101 return new stdClass();
b9934a17 2102 }
473289a0 2103 return parent::load_version_php();
b9934a17
DM
2104 }
2105
b9934a17
DM
2106 public function is_enabled() {
2107
2108 $globalstates = self::get_global_states();
2109
2110 foreach ($globalstates as $filterlegacyname => $info) {
2111 $name = self::normalize_legacy_name($filterlegacyname);
2112 if ($name === $this->name) {
2113 if ($info->active == TEXTFILTER_DISABLED) {
2114 return false;
2115 } else {
2116 // it may be 'On' or 'Off, but available'
2117 return null;
2118 }
2119 }
2120 }
2121
2122 return null;
2123 }
2124
1de1a666 2125 public function get_settings_section_name() {
b9934a17
DM
2126 $globalstates = self::get_global_states();
2127 $legacyname = $globalstates[$this->name]->legacyname;
1de1a666
MG
2128 return 'filtersetting' . str_replace('/', '', $legacyname);
2129 }
2130
2131 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
2132 global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
2133 $ADMIN = $adminroot; // may be used in settings.php
2134 $filter = $this; // also can be used inside settings.php
2135
2136 $settings = null;
2137 if ($hassiteconfig && file_exists($this->full_path('filtersettings.php'))) {
2138 $section = $this->get_settings_section_name();
2139 $settings = new admin_settingpage($section, $this->displayname,
2140 'moodle/site:config', $this->is_enabled() === false);
2141 include($this->full_path('filtersettings.php')); // this may also set $settings to null
2142 }
2143 if ($settings) {
2144 $ADMIN->add($parentnodename, $settings);
b9934a17
DM
2145 }
2146 }
2147
b9934a17
DM
2148 public function get_uninstall_url() {
2149
2150 if (strpos($this->name, 'mod_') === 0) {
2151 return null;
2152 } else {
2153 $globalstates = self::get_global_states();
2154 $legacyname = $globalstates[$this->name]->legacyname;
2155 return new moodle_url('/admin/filters.php', array('sesskey' => sesskey(), 'filterpath' => $legacyname, 'action' => 'delete'));
2156 }
2157 }
2158
2159 /**
2160 * Convert legacy filter names like 'filter/foo' or 'mod/bar' into frankenstyle
2161 *
2162 * @param string $legacyfiltername legacy filter name
2163 * @return string frankenstyle-like name
2164 */
2165 protected static function normalize_legacy_name($legacyfiltername) {
2166
2167 $name = str_replace('/', '_', $legacyfiltername);
2168 if (strpos($name, 'filter_') === 0) {
2169 $name = substr($name, 7);
2170 if (empty($name)) {
2171 throw new coding_exception('Unable to determine filter name: ' . $legacyfiltername);
2172 }
2173 }
2174
2175 return $name;
2176 }
2177
2178 /**
2179 * Provides access to the results of {@link filter_get_global_states()}
2180 * but indexed by the normalized filter name
2181 *
2182 * The legacy filter name is available as ->legacyname property.
2183 *
2184 * @param bool $disablecache
2185 * @return array
2186 */
2187 protected static function get_global_states($disablecache=false) {
2188 global $DB;
2189 static $globalstatescache = null;
2190
2191 if ($disablecache or is_null($globalstatescache)) {
2192
2193 if (!$DB->get_manager()->table_exists('filter_active')) {
2194 // we're upgrading from 1.9 and the table used by {@link filter_get_global_states()}
2195 // does not exist yet
2196 $globalstatescache = array();
2197
2198 } else {
2199 foreach (filter_get_global_states() as $legacyname => $info) {
2200 $name = self::normalize_legacy_name($legacyname);
2201 $filterinfo = new stdClass();
2202 $filterinfo->legacyname = $legacyname;
2203 $filterinfo->active = $info->active;
2204 $filterinfo->sortorder = $info->sortorder;
2205 $globalstatescache[$name] = $filterinfo;
2206 }
2207 }
2208 }
2209
2210 return $globalstatescache;
2211 }
2212}
2213
b6ad8594 2214
b9934a17
DM
2215/**
2216 * Class for activity modules
2217 */
b6ad8594 2218class plugininfo_mod extends plugininfo_base {
b9934a17 2219
b9934a17
DM
2220 public static function get_plugins($type, $typerootdir, $typeclass) {
2221
2222 // get the information about plugins at the disk
2223 $modules = parent::get_plugins($type, $typerootdir, $typeclass);
2224
2225 // add modules missing from disk
2226 $modulesinfo = self::get_modules_info();
2227 foreach ($modulesinfo as $modulename => $moduleinfo) {
2228 if (isset($modules[$modulename])) {
2229 continue;
2230 }
2231 $plugin = new $typeclass();
2232 $plugin->type = $type;
2233 $plugin->typerootdir = $typerootdir;
2234 $plugin->name = $modulename;
2235 $plugin->rootdir = null;
2236 $plugin->displayname = $modulename;
2237 $plugin->versiondb = $moduleinfo->version;
b8343e68 2238 $plugin->init_is_standard();
b9934a17
DM
2239
2240 $modules[$modulename] = $plugin;
2241 }
2242
2243 return $modules;
2244 }
2245
fde6f79f
MG
2246 /**
2247 * Magic method getter, redirects to read only values.
2248 *
2249 * For module plugins we pretend the object has 'visible' property for compatibility
2250 * with plugins developed for Moodle version below 2.4
2251 *
2252 * @param string $name
2253 * @return mixed
2254 */
2255 public function __get($name) {
2256 if ($name === 'visible') {
2257 debugging('This is now an instance of plugininfo_mod, please use $module->is_enabled() instead of $module->visible', DEBUG_DEVELOPER);
2258 return ($this->is_enabled() !== false);
2259 }
2260 return parent::__get($name);
2261 }
2262
b8343e68 2263 public function init_display_name() {
828788f0
TH
2264 if (get_string_manager()->string_exists('pluginname', $this->component)) {
2265 $this->displayname = get_string('pluginname', $this->component);
b9934a17 2266 } else {
828788f0 2267 $this->displayname = get_string('modulename', $this->component);
b9934a17
DM
2268 }
2269 }
2270
2271 /**
473289a0
TH
2272 * Load the data from version.php.
2273 * @return object the data object defined in version.php.
b9934a17 2274 */
473289a0
TH
2275 protected function load_version_php() {
2276 $versionfile = $this->full_path('version.php');
b9934a17 2277
473289a0 2278 $module = new stdClass();
b9934a17
DM
2279 if (is_readable($versionfile)) {
2280 include($versionfile);
b9934a17 2281 }
473289a0 2282 return $module;
b9934a17
DM
2283 }
2284
b8343e68 2285 public function load_db_version() {
b9934a17
DM
2286 global $DB;
2287
2288 $modulesinfo = self::get_modules_info();
2289 if (isset($modulesinfo[$this->name]->version)) {
2290 $this->versiondb = $modulesinfo[$this->name]->version;
2291 }
2292 }
2293
b9934a17
DM
2294 public function is_enabled() {
2295
2296 $modulesinfo = self::get_modules_info();
2297 if (isset($modulesinfo[$this->name]->visible)) {
2298 if ($modulesinfo[$this->name]->visible) {
2299 return true;
2300 } else {
2301 return false;
2302 }
2303 } else {
2304 return parent::is_enabled();
2305 }
2306 }
2307
fde6f79f
MG
2308 public function get_settings_section_name() {
2309 return 'modsetting' . $this->name;
2310 }
b9934a17 2311
fde6f79f
MG
2312 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
2313 global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
2314 $ADMIN = $adminroot; // may be used in settings.php
2315 $module = $this; // also can be used inside settings.php
2316 $section = $this->get_settings_section_name();
2317
2318 $settings = null;
2319 if ($hassiteconfig && file_exists($this->full_path('settings.php'))) {
2320 $settings = new admin_settingpage($section, $this->displayname,
2321 'moodle/site:config', $this->is_enabled() === false);
2322 include($this->full_path('settings.php')); // this may also set $settings to null
2323 }
2324 if ($settings) {
2325 $ADMIN->add($parentnodename, $settings);
b9934a17
DM
2326 }
2327 }
2328
b9934a17
DM
2329 public function get_uninstall_url() {
2330
2331 if ($this->name !== 'forum') {
2332 return new moodle_url('/admin/modules.php', array('delete' => $this->name, 'sesskey' => sesskey()));
2333 } else {
2334 return null;
2335 }
2336 }
2337
2338 /**
2339 * Provides access to the records in {modules} table
2340 *
2341 * @param bool $disablecache do not use internal static cache
2342 * @return array array of stdClasses
2343 */
2344 protected static function get_modules_info($disablecache=false) {
2345 global $DB;
2346 static $modulesinfocache = null;
2347
2348 if (is_null($modulesinfocache) or $disablecache) {
f433088d
PS
2349 try {
2350 $modulesinfocache = $DB->get_records('modules', null, 'name', 'name,id,version,visible');
2351 } catch (dml_exception $e) {
2352 // before install
2353 $modulesinfocache = array();
2354 }
b9934a17
DM
2355 }
2356
2357 return $modulesinfocache;
2358 }
2359}
2360
0242bdc7
TH
2361
2362/**
2363 * Class for question behaviours.
2364 */
b6ad8594
DM
2365class plugininfo_qbehaviour extends plugininfo_base {
2366
828788f0
TH
2367 public function get_uninstall_url() {
2368 return new moodle_url('/admin/qbehaviours.php',
2369 array('delete' => $this->name, 'sesskey' => sesskey()));
2370 }
0242bdc7
TH
2371}
2372
2373
b9934a17
DM
2374/**
2375 * Class for question types
2376 */
b6ad8594
DM
2377class plugininfo_qtype extends plugininfo_base {
2378
828788f0
TH
2379 public function get_uninstall_url() {
2380 return new moodle_url('/admin/qtypes.php',
2381 array('delete' => $this->name, 'sesskey' => sesskey()));
2382 }
66f3684a
MG
2383
2384 public function get_settings_section_name() {
2385 return 'qtypesetting' . $this->name;
2386 }
2387
2388 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
2389 global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
2390 $ADMIN = $adminroot; // may be used in settings.php
2391 $qtype = $this; // also can be used inside settings.php
2392 $section = $this->get_settings_section_name();
2393
2394 $settings = null;
2395 if ($hassiteconfig && file_exists($this->full_path('settings.php'))) {
2396 $settings = new admin_settingpage($section, $this->displayname,
2397 'moodle/site:config', $this->is_enabled() === false);
2398 include($this->full_path('settings.php')); // this may also set $settings to null
2399 }
2400 if ($settings) {
2401 $ADMIN->add($parentnodename, $settings);
2402 }
2403 }
b9934a17
DM
2404}
2405
b9934a17
DM
2406
2407/**
2408 * Class for authentication plugins
2409 */
b6ad8594 2410class plugininfo_auth extends plugininfo_base {
b9934a17 2411
b9934a17
DM
2412 public function is_enabled() {
2413 global $CFG;
2414 /** @var null|array list of enabled authentication plugins */
2415 static $enabled = null;
2416
2417 if (in_array($this->name, array('nologin', 'manual'))) {
2418 // these two are always enabled and can't be disabled
2419 return null;
2420 }
2421
2422 if (is_null($enabled)) {
d5d181f5 2423 $enabled = array_flip(explode(',', $CFG->auth));
b9934a17
DM
2424 }
2425
2426 return isset($enabled[$this->name]);
2427 }
2428
cbe9f609
MG
2429 public function get_settings_section_name() {
2430 return 'authsetting' . $this->name;
2431 }
2432
2433 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
2434 global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
2435 $ADMIN = $adminroot; // may be used in settings.php
2436 $auth = $this; // also to be used inside settings.php
2437 $section = $this->get_settings_section_name();
2438
2439 $settings = null;
2440 if ($hassiteconfig) {
2441 if (file_exists($this->full_path('settings.php'))) {
2442 // TODO: finish implementation of common settings - locking, etc.
2443 $settings = new admin_settingpage($section, $this->displayname,
2444 'moodle/site:config', $this->is_enabled() === false);
2445 include($this->full_path('settings.php')); // this may also set $settings to null
2446 } else {
2447 $settingsurl = new moodle_url('/admin/auth_config.php', array('auth' => $this->name));
2448 $settings = new admin_externalpage($section, $this->displayname,
2449 $settingsurl, 'moodle/site:config', $this->is_enabled() === false);
2450 }
2451 }
2452 if ($settings) {
2453 $ADMIN->add($parentnodename, $settings);
b9934a17
DM
2454 }
2455 }
2456}
2457
b6ad8594 2458
b9934a17
DM
2459/**
2460 * Class for enrolment plugins
2461 */
b6ad8594 2462class plugininfo_enrol extends plugininfo_base {
b9934a17 2463
b9934a17
DM
2464 public function is_enabled() {
2465 global $CFG;
2466 /** @var null|array list of enabled enrolment plugins */
2467 static $enabled = null;
2468
b6ad8594
DM
2469 // We do not actually need whole enrolment classes here so we do not call
2470 // {@link enrol_get_plugins()}. Note that this may produce slightly different
2471 // results, for example if the enrolment plugin does not contain lib.php
2472 // but it is listed in $CFG->enrol_plugins_enabled
2473
b9934a17 2474 if (is_null($enabled)) {
d5d181f5 2475 $enabled = array_flip(explode(',', $CFG->enrol_plugins_enabled));
b9934a17
DM
2476 }
2477
2478 return isset($enabled[$this->name]);
2479 }
2480
79c5c3fa
MG
2481 public function get_settings_section_name() {
2482 return 'enrolsettings' . $this->name;
2483 }
b9934a17 2484
79c5c3fa
MG
2485 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
2486 global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
2487 $ADMIN = $adminroot; // may be used in settings.php
2488 $enrol = $this; // also can be used inside settings.php
2489 $section = $this->get_settings_section_name();
2490
2491 $settings = null;
2492 if ($hassiteconfig && file_exists($this->full_path('settings.php'))) {
2493 $settings = new admin_settingpage($section, $this->displayname,
2494 'moodle/site:config', $this->is_enabled() === false);
2495 include($this->full_path('settings.php')); // this may also set $settings to null
2496 }
2497 if ($settings) {
2498 $ADMIN->add($parentnodename, $settings);
b9934a17
DM
2499 }
2500 }
2501
b9934a17
DM
2502 public function get_uninstall_url() {
2503 return new moodle_url('/admin/enrol.php', array('action' => 'uninstall', 'enrol' => $this->name, 'sesskey' => sesskey()));
2504 }
2505}
2506
b6ad8594 2507
b9934a17
DM
2508/**
2509 * Class for messaging processors
2510 */
b6ad8594 2511class plugininfo_message extends plugininfo_base {
b9934a17 2512
e8d16932
MG
2513 public function get_settings_section_name() {
2514 return 'messagesetting' . $this->name;
2515 }
2516
2517 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
2518 global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
2519 $ADMIN = $adminroot; // may be used in settings.php
2520 if (!$hassiteconfig) {
2521 return;
2522 }
2523 $section = $this->get_settings_section_name();
2524
2525 $settings = null;
bc795b98
RK
2526 $processors = get_message_processors();
2527 if (isset($processors[$this->name])) {
2528 $processor = $processors[$this->name];
2529 if ($processor->available && $processor->hassettings) {
e8d16932
MG
2530 $settings = new admin_settingpage($section, $this->displayname,
2531 'moodle/site:config', $this->is_enabled() === false);
2532 include($this->full_path('settings.php')); // this may also set $settings to null
bc795b98 2533 }
0210ce10 2534 }
e8d16932
MG
2535 if ($settings) {
2536 $ADMIN->add($parentnodename, $settings);
2537 }
b9934a17 2538 }
b9934a17 2539
bede23f7
RK
2540 /**
2541 * @see plugintype_interface::is_enabled()
2542 */
2543 public function is_enabled() {
2544 $processors = get_message_processors();
2545 if (isset($processors[$this->name])) {
2546 return $processors[$this->name]->configured && $processors[$this->name]->enabled;
0210ce10 2547 } else {
bede23f7
RK
2548 return parent::is_enabled();
2549 }
2550 }
3f9d9e28
RK
2551
2552 /**
2553 * @see plugintype_interface::get_uninstall_url()
2554 */
2555 public function get_uninstall_url() {
2556 $processors = get_message_processors();
2557 if (isset($processors[$this->name])) {
e8d16932 2558 return new moodle_url('/admin/message.php', array('uninstall' => $processors[$this->name]->id, 'sesskey' => sesskey()));
3f9d9e28
RK
2559 } else {
2560 return parent::get_uninstall_url();
0210ce10 2561 }
b9934a17
DM
2562 }
2563}
2564
b6ad8594 2565
b9934a17
DM
2566/**
2567 * Class for repositories
2568 */
b6ad8594 2569class plugininfo_repository extends plugininfo_base {
b9934a17 2570
b9934a17
DM
2571 public function is_enabled() {
2572
2573 $enabled = self::get_enabled_repositories();
2574
2575 return isset($enabled[$this->name]);
2576 }
2577
c517dd68
MG
2578 public function get_settings_section_name() {
2579 return 'repositorysettings'.$this->name;
2580 }
b9934a17 2581
c517dd68
MG
2582 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
2583 if ($hassiteconfig && $this->is_enabled()) {
2584 // completely no access to repository setting when it is not enabled
2585 $sectionname = $this->get_settings_section_name();
2586 $settingsurl = new moodle_url('/admin/repository.php',
2587 array('sesskey' => sesskey(), 'action' => 'edit', 'repos' => $this->name));
2588 $settings = new admin_externalpage($sectionname, $this->displayname,
2589 $settingsurl, 'moodle/site:config', false);
2590 $adminroot->add($parentnodename, $settings);
b9934a17
DM
2591 }
2592 }
2593
2594 /**
2595 * Provides access to the records in {repository} table
2596 *
2597 * @param bool $disablecache do not use internal static cache
2598 * @return array array of stdClasses
2599 */
2600 protected static function get_enabled_repositories($disablecache=false) {
2601 global $DB;
2602 static $repositories = null;
2603
2604 if (is_null($repositories) or $disablecache) {
2605 $repositories = $DB->get_records('repository', null, 'type', 'type,visible,sortorder');
2606 }
2607
2608 return $repositories;
2609 }
2610}
2611
b6ad8594 2612
b9934a17
DM
2613/**
2614 * Class for portfolios
2615 */
b6ad8594 2616class plugininfo_portfolio extends plugininfo_base {
b9934a17 2617
b9934a17
DM
2618 public function is_enabled() {
2619
2620 $enabled = self::get_enabled_portfolios();
2621
2622 return isset($enabled[$this->name]);
2623 }
2624
2625 /**
2626 * Provides access to the records in {portfolio_instance} table
2627 *
2628 * @param bool $disablecache do not use internal static cache
2629 * @return array array of stdClasses
2630 */
2631 protected static function get_enabled_portfolios($disablecache=false) {
2632 global $DB;
2633 static $portfolios = null;
2634
2635 if (is_null($portfolios) or $disablecache) {
2636 $portfolios = array();
2637 $instances = $DB->get_recordset('portfolio_instance', null, 'plugin');
2638 foreach ($instances as $instance) {
2639 if (isset($portfolios[$instance->plugin])) {
2640 if ($instance->visible) {
2641 $portfolios[$instance->plugin]->visible = $instance->visible;
2642 }
2643 } else {
2644 $portfolios[$instance->plugin] = $instance;
2645 }
2646 }
2647 }
2648
2649 return $portfolios;
2650 }
2651}
2652
b6ad8594 2653
b9934a17
DM
2654/**
2655 * Class for themes
2656 */
b6ad8594 2657class plugininfo_theme extends plugininfo_base {
b9934a17 2658
b9934a17
DM
2659 public function is_enabled() {
2660 global $CFG;
2661
2662 if ((!empty($CFG->theme) and $CFG->theme === $this->name) or
2663 (!empty($CFG->themelegacy) and $CFG->themelegacy === $this->name)) {
2664 return true;
2665 } else {
2666 return parent::is_enabled();
2667 }
2668 }
2669}
2670
b6ad8594 2671
b9934a17
DM
2672/**
2673 * Class representing an MNet service
2674 */
b6ad8594 2675class plugininfo_mnetservice extends plugininfo_base {
b9934a17 2676
b9934a17
DM
2677 public function is_enabled() {
2678 global $CFG;
2679
2680 if (empty($CFG->mnet_dispatcher_mode) || $CFG->mnet_dispatcher_mode !== 'strict') {
2681 return false;
2682 } else {
2683 return parent::is_enabled();
2684 }
2685 }
2686}
3cdfaeef 2687
b6ad8594 2688
3cdfaeef
PS
2689/**
2690 * Class for admin tool plugins
2691 */
b6ad8594 2692class plugininfo_tool extends plugininfo_base {
3cdfaeef
PS
2693
2694 public function get_uninstall_url() {
2695 return new moodle_url('/admin/tools.php', array('delete' => $this->name, 'sesskey' => sesskey()));
2696 }
2697}
4f6bba20 2698
b6ad8594 2699
4f6bba20
PS
2700/**
2701 * Class for admin tool plugins
2702 */
b6ad8594 2703class plugininfo_report extends plugininfo_base {
4f6bba20
PS
2704
2705 public function get_uninstall_url() {
2706 return new moodle_url('/admin/reports.php', array('delete' => $this->name, 'sesskey' => sesskey()));
2707 }
2708}
888ce02a
RK
2709
2710
2711/**
2712 * Class for local plugins
2713 */
2714class plugininfo_local extends plugininfo_base {
2715
2716 public function get_uninstall_url() {
2717 return new moodle_url('/admin/localplugins.php', array('delete' => $this->name, 'sesskey' => sesskey()));
2718 }
888ce02a 2719}
087001ee
MG
2720
2721/**
2722 * Class for HTML editors
2723 */
2724class plugininfo_editor extends plugininfo_base {
2725
2726 public function get_settings_section_name() {
2727 return 'editorsettings' . $this->name;
2728 }
2729
2730 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
2731 global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
2732 $ADMIN = $adminroot; // may be used in settings.php
2733 $editor = $this; // also can be used inside settings.php
2734 $section = $this->get_settings_section_name();
2735
2736 $settings = null;
2737 if ($hassiteconfig && file_exists($this->full_path('settings.php'))) {
2738 $settings = new admin_settingpage($section, $this->displayname,
2739 'moodle/site:config', $this->is_enabled() === false);
2740 include($this->full_path('settings.php')); // this may also set $settings to null
2741 }
2742 if ($settings) {
2743 $ADMIN->add($parentnodename, $settings);
2744 }
2745 }
2746
2747 /**
2748 * Returns the information about plugin availability
2749 *
2750 * True means that the plugin is enabled. False means that the plugin is
2751 * disabled. Null means that the information is not available, or the
2752 * plugin does not support configurable availability or the availability
2753 * can not be changed.
2754 *
2755 * @return null|bool
2756 */
2757 public function is_enabled() {
2758 global $CFG;
2759 if (empty($CFG->texteditors)) {
2760 $CFG->texteditors = 'tinymce,textarea';
2761 }
2762 if (in_array($this->name, explode(',', $CFG->texteditors))) {
2763 return true;
2764 }
2765 return false;
2766 }
2767}
d98305bd
MG
2768
2769/**
2770 * Class for plagiarism plugins
2771 */
2772class plugininfo_plagiarism extends plugininfo_base {
2773
2774 public function get_settings_section_name() {
2775 return 'plagiarism'. $this->name;
2776 }
2777
2778 public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
2779 // plagiarism plugin just redirect to settings.php in the plugins directory
2780 if ($hassiteconfig && file_exists($this->full_path('settings.php'))) {
2781 $section = $this->get_settings_section_name();
2782 $settingsurl = new moodle_url($this->get_dir().'/settings.php');
2783 $settings = new admin_externalpage($section, $this->displayname,
2784 $settingsurl, 'moodle/site:config', $this->is_enabled() === false);
2785 $adminroot->add($parentnodename, $settings);
2786 }
2787 }
2788}