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