MDL-20438 Introducing new compare_responses() 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
55585f3a
DM
637 /**
638 * Returns the available update information for Moodle core
639 *
640 * The returned structure is an array of available_update_info objects or null
641 * if there no such info available.
642 *
643 * @param int $minmaturity minimal maturity level to return, returns all by default
644 * @return null|stdClass null or array of available_update_info objects
645 */
646 public function get_core_update_info($minmaturity = 0) {
647
648 $this->load_current_environment();
649
650 $updates = $this->get_update_info('core');
651 if (empty($updates)) {
652 return null;
653 }
654 $return = array();
655 foreach ($updates as $update) {
656 if (isset($update->maturity) and ($update->maturity < $minmaturity)) {
657 continue;
658 }
659 if ($update->version > $this->currentversion) {
660 $return[] = $update;
661 }
662 }
663
664 if (empty($return)) {
665 return null;
666 }
667
668 return $return;
669 }
670
cd0bb55f
DM
671 /**
672 * Returns the available update information for the given component
673 *
674 * This method returns null if the most recent response does not contain any information
7d8de6d8
DM
675 * about it. The returned structure is an array of available updates for the given
676 * component. Each update info is an object with at least one property called
677 * 'version'. Other possible properties are 'release', 'maturity', 'url' and 'downloadurl'.
cd0bb55f
DM
678 *
679 * @param string $component frankenstyle
55585f3a 680 * @return null|stdClass null or array of available_update_info objects
cd0bb55f
DM
681 */
682 public function get_update_info($component) {
683
7d8de6d8 684 $this->restore_response();
cd0bb55f 685
7d8de6d8
DM
686 if (!empty($this->recentresponse['updates'][$component])) {
687 $updates = array();
688 foreach ($this->recentresponse['updates'][$component] as $info) {
689 $updates[] = new available_update_info($component, $info);
690 }
691 return $updates;
cd0bb55f
DM
692 } else {
693 return null;
694 }
695 }
696
be378880
DM
697 /**
698 * The method being run via cron.php
699 */
700 public function cron() {
701 global $CFG;
702
703 if (!$this->cron_autocheck_enabled()) {
704 $this->cron_mtrace('Automatic check for available updates not enabled, skipping.');
705 return;
706 }
707
708 $now = $this->cron_current_timestamp();
709
710 if ($this->cron_has_fresh_fetch($now)) {
711 $this->cron_mtrace('Recently fetched info about available updates is still fresh enough, skipping.');
712 return;
713 }
714
715 if ($this->cron_has_outdated_fetch($now)) {
716 $this->cron_mtrace('Outdated or missing info about available updates, forced fetching ... ', '');
717 $this->cron_execute();
718 return;
719 }
720
721 $offset = $this->cron_execution_offset();
722 $start = mktime(1, 0, 0, date('n', $now), date('j', $now), date('Y', $now)); // 01:00 AM today local time
723 if ($now > $start + $offset) {
724 $this->cron_mtrace('Regular daily check for available updates ... ', '');
725 $this->cron_execute();
726 return;
727 }
728 }
729
730 /// end of public API //////////////////////////////////////////////////////
731
cd0bb55f 732 /**
7d8de6d8 733 * Makes cURL request to get data from the remote site
cd0bb55f 734 *
7d8de6d8 735 * @return string raw request result
cd0bb55f
DM
736 * @throws available_update_checker_exception
737 */
7d8de6d8 738 protected function get_response() {
cd0bb55f
DM
739 $curl = new curl(array('proxy' => true));
740 $response = $curl->post($this->prepare_request_url(), $this->prepare_request_params());
741 $curlinfo = $curl->get_info();
742 if ($curlinfo['http_code'] != 200) {
743 throw new available_update_checker_exception('err_response_http_code', $curlinfo['http_code']);
744 }
cd0bb55f
DM
745 return $response;
746 }
747
748 /**
749 * Makes sure the response is valid, has correct API format etc.
750 *
7d8de6d8 751 * @param string $response raw response as returned by the {@link self::get_response()}
cd0bb55f
DM
752 * @throws available_update_checker_exception
753 */
7d8de6d8
DM
754 protected function validate_response($response) {
755
756 $response = $this->decode_response($response);
cd0bb55f
DM
757
758 if (empty($response)) {
759 throw new available_update_checker_exception('err_response_empty');
760 }
761
7d8de6d8
DM
762 if (empty($response['status']) or $response['status'] !== 'OK') {
763 throw new available_update_checker_exception('err_response_status', $response['status']);
764 }
765
766 if (empty($response['apiver']) or $response['apiver'] !== '1.0') {
767 throw new available_update_checker_exception('err_response_format_version', $response['apiver']);
cd0bb55f
DM
768 }
769
7d8de6d8
DM
770 if (empty($response['forbranch']) or $response['forbranch'] !== moodle_major_version(true)) {
771 throw new available_update_checker_exception('err_response_target_version', $response['target']);
cd0bb55f
DM
772 }
773 }
774
775 /**
7d8de6d8 776 * Decodes the raw string response from the update notifications provider
cd0bb55f 777 *
7d8de6d8
DM
778 * @param string $response as returned by {@link self::get_response()}
779 * @return array decoded response structure
cd0bb55f 780 */
7d8de6d8
DM
781 protected function decode_response($response) {
782 return json_decode($response, true);
cd0bb55f
DM
783 }
784
785 /**
7d8de6d8
DM
786 * Stores the valid fetched response for later usage
787 *
788 * This implementation uses the config_plugins table as the permanent storage.
cd0bb55f 789 *
7d8de6d8 790 * @param string $response raw valid data returned by {@link self::get_response()}
cd0bb55f 791 */
7d8de6d8
DM
792 protected function store_response($response) {
793
794 set_config('recentfetch', time(), 'core_plugin');
795 set_config('recentresponse', $response, 'core_plugin');
796
797 $this->restore_response(true);
cd0bb55f
DM
798 }
799
800 /**
7d8de6d8
DM
801 * Loads the most recent raw response record we have fetched
802 *
803 * This implementation uses the config_plugins table as the permanent storage.
cd0bb55f 804 *
7d8de6d8 805 * @param bool $forcereload reload even if it was already loaded
cd0bb55f 806 */
7d8de6d8
DM
807 protected function restore_response($forcereload = false) {
808
809 if (!$forcereload and !is_null($this->recentresponse)) {
810 // we already have it, nothing to do
811 return;
cd0bb55f
DM
812 }
813
7d8de6d8
DM
814 $config = get_config('core_plugin');
815
816 if (!empty($config->recentresponse) and !empty($config->recentfetch)) {
817 try {
818 $this->validate_response($config->recentresponse);
819 $this->recentfetch = $config->recentfetch;
820 $this->recentresponse = $this->decode_response($config->recentresponse);
821 }
822 catch (available_update_checker_exception $e) {
823 // do not set recentresponse if the validation fails
824 }
825
cd0bb55f 826 } else {
7d8de6d8 827 $this->recentresponse = array();
cd0bb55f
DM
828 }
829 }
830
7b35553b
DM
831 /**
832 * Compares two raw {@link $recentresponse} records and returns the list of changed updates
833 *
834 * This method is used to populate potential update info to be sent to site admins.
835 *
836 * @param null|array $old
837 * @param null|array $new
838 * @throws available_update_checker_exception
839 * @return array parts of $new['updates'] that have changed
840 */
841 protected function compare_responses($old, $new) {
842
843 if (is_null($new)) {
844 return array();
845 }
846
847 if (!array_key_exists('updates', $new)) {
848 throw new available_update_checker_exception('err_response_format');
849 }
850
851 if (is_null($old)) {
852 return $new['updates'];
853 }
854
855 if (!array_key_exists('updates', $old)) {
856 throw new available_update_checker_exception('err_response_format');
857 }
858
859 $changes = array();
860
861 foreach ($new['updates'] as $newcomponent => $newcomponentupdates) {
862 if (empty($old['updates'][$newcomponent])) {
863 $changes[$newcomponent] = $newcomponentupdates;
864 continue;
865 }
866 foreach ($newcomponentupdates as $newcomponentupdate) {
867 $inold = false;
868 foreach ($old['updates'][$newcomponent] as $oldcomponentupdate) {
869 if ($newcomponentupdate['version'] == $oldcomponentupdate['version']) {
870 $inold = true;
871 }
872 }
873 if (!$inold) {
874 if (!isset($changes[$newcomponent])) {
875 $changes[$newcomponent] = array();
876 }
877 $changes[$newcomponent][] = $newcomponentupdate;
878 }
879 }
880 }
881
882 return $changes;
883 }
884
cd0bb55f
DM
885 /**
886 * Returns the URL to send update requests to
887 *
888 * During the development or testing, you can set $CFG->alternativeupdateproviderurl
889 * to a custom URL that will be used. Otherwise the standard URL will be returned.
890 *
891 * @return string URL
892 */
893 protected function prepare_request_url() {
894 global $CFG;
895
896 if (!empty($CFG->alternativeupdateproviderurl)) {
897 return $CFG->alternativeupdateproviderurl;
898 } else {
899 return 'http://download.moodle.org/api/1.0/updates.php';
900 }
901 }
902
55585f3a
DM
903 /**
904 * Sets the properties currentversion, currentbranch and currentplugins
905 *
906 * @param bool $forcereload
907 */
908 protected function load_current_environment($forcereload=false) {
909 global $CFG;
910
911 if (!is_null($this->currentversion) and !$forcereload) {
912 // nothing to do
913 return;
914 }
915
916 require($CFG->dirroot.'/version.php');
917 $this->currentversion = $version;
918
919 $this->currentbranch = moodle_major_version(true);
920
921 $pluginman = plugin_manager::instance();
922 foreach ($pluginman->get_plugins() as $type => $plugins) {
923 foreach ($plugins as $plugin) {
924 if (!$plugin->is_standard()) {
925 $this->currentplugins[$plugin->component] = $plugin->versiondisk;
926 }
927 }
928 }
929 }
930
cd0bb55f
DM
931 /**
932 * Returns the list of HTTP params to be sent to the updates provider URL
933 *
934 * @return array of (string)param => (string)value
935 */
936 protected function prepare_request_params() {
937 global $CFG;
938
55585f3a 939 $this->load_current_environment();
7d8de6d8
DM
940 $this->restore_response();
941
cd0bb55f
DM
942 $params = array();
943 $params['format'] = 'json';
944
7d8de6d8
DM
945 if (isset($this->recentresponse['ticket'])) {
946 $params['ticket'] = $this->recentresponse['ticket'];
cd0bb55f
DM
947 }
948
55585f3a
DM
949 if (isset($this->currentversion)) {
950 $params['version'] = $this->currentversion;
951 } else {
952 throw new coding_exception('Main Moodle version must be already known here');
cd0bb55f
DM
953 }
954
55585f3a
DM
955 if (isset($this->currentbranch)) {
956 $params['branch'] = $this->currentbranch;
957 } else {
958 throw new coding_exception('Moodle release must be already known here');
959 }
960
961 $plugins = array();
962 foreach ($this->currentplugins as $plugin => $version) {
963 $plugins[] = $plugin.'@'.$version;
964 }
965 if (!empty($plugins)) {
966 $params['plugins'] = implode(',', $plugins);
cd0bb55f
DM
967 }
968
cd0bb55f
DM
969 return $params;
970 }
be378880
DM
971
972 /**
973 * Returns the current timestamp
974 *
975 * @return int the timestamp
976 */
977 protected function cron_current_timestamp() {
978 return time();
979 }
980
981 /**
982 * Output cron debugging info
983 *
984 * @see mtrace()
985 * @param string $msg output message
986 * @param string $eol end of line
987 */
988 protected function cron_mtrace($msg, $eol = PHP_EOL) {
989 mtrace($msg, $eol);
990 }
991
992 /**
993 * Decide if the autocheck feature is disabled in the server setting
994 *
995 * @return bool true if autocheck enabled, false if disabled
996 */
997 protected function cron_autocheck_enabled() {
998 if (empty($CFG->updateautocheck)) {
999 return false;
1000 } else {
1001 return true;
1002 }
1003 }
1004
1005 /**
1006 * Decide if the recently fetched data are still fresh enough
1007 *
1008 * @param int $now current timestamp
1009 * @return bool true if no need to re-fetch, false otherwise
1010 */
1011 protected function cron_has_fresh_fetch($now) {
1012 $recent = $this->get_last_timefetched();
1013
1014 if (empty($recent)) {
1015 return false;
1016 }
1017
1018 if ($now < $recent) {
1019 $this->cron_mtrace('The most recent fetch is reported to be in the future, this is weird!');
1020 return true;
1021 }
1022
1023 if ($now - $recent > HOURSECS) {
1024 return false;
1025 }
1026
1027 return true;
1028 }
1029
1030 /**
1031 * Decide if the fetch is outadated or even missing
1032 *
1033 * @param int $now current timestamp
1034 * @return bool false if no need to re-fetch, true otherwise
1035 */
1036 protected function cron_has_outdated_fetch($now) {
1037 $recent = $this->get_last_timefetched();
1038
1039 if (empty($recent)) {
1040 return true;
1041 }
1042
1043 if ($now < $recent) {
1044 $this->cron_mtrace('The most recent fetch is reported to be in the future, this is weird!');
1045 return false;
1046 }
1047
1048 if ($now - $recent > 48 * HOURSECS) {
1049 return true;
1050 }
1051
1052 return false;
1053 }
1054
1055 /**
1056 * Returns the cron execution offset for this site
1057 *
1058 * The main {@link self::cron()} is supposed to run every night in some random time
1059 * between 01:00 and 06:00 AM (local time). The exact moment is defined by so called
1060 * execution offset, that is the amount of time after 01:00 AM. The offset value is
1061 * initially generated randomly and then used consistently at the site. This way, the
1062 * regular checks against the download.moodle.org server are spread in time.
1063 *
1064 * @return int the offset number of seconds from range 1 sec to 5 hours
1065 */
1066 protected function cron_execution_offset() {
1067 global $CFG;
1068
1069 if (empty($CFG->updatecronoffset)) {
1070 set_config('updatecronoffset', rand(1, 5 * HOURSECS));
1071 }
1072
1073 return $CFG->updatecronoffset;
1074 }
1075
1076 /**
1077 * Fetch available updates info and eventually send notification to site admins
1078 */
1079 protected function cron_execute() {
7b35553b
DM
1080
1081 $this->restore_response();
1082 $previous = $this->recentresponse;
1083 $this->fetch();
1084 $this->restore_response(true);
1085 $current = $this->recentresponse;
1086 $changes = $this->compare_responses($previous, $current);
be378880 1087 }
cd0bb55f
DM
1088}
1089
1090
7d8de6d8
DM
1091/**
1092 * Defines the structure of objects returned by {@link available_update_checker::get_update_info()}
1093 */
1094class available_update_info {
1095
1096 /** @var string frankenstyle component name */
1097 public $component;
1098 /** @var int the available version of the component */
1099 public $version;
1100 /** @var string|null optional release name */
1101 public $release = null;
1102 /** @var int|null optional maturity info, eg {@link MATURITY_STABLE} */
1103 public $maturity = null;
1104 /** @var string|null optional URL of a page with more info about the update */
1105 public $url = null;
1106 /** @var string|null optional URL of a ZIP package that can be downloaded and installed */
1107 public $download = null;
1108
1109 /**
1110 * Creates new instance of the class
1111 *
1112 * The $info array must provide at least the 'version' value and optionally all other
1113 * values to populate the object's properties.
1114 *
1115 * @param string $name the frankenstyle component name
1116 * @param array $info associative array with other properties
1117 */
1118 public function __construct($name, array $info) {
1119 $this->component = $name;
1120 foreach ($info as $k => $v) {
1121 if (property_exists('available_update_info', $k) and $k != 'component') {
1122 $this->$k = $v;
1123 }
1124 }
1125 }
1126}
1127
1128
00ef3c3e
DM
1129/**
1130 * Factory class producing required subclasses of {@link plugininfo_base}
1131 */
1132class plugininfo_default_factory {
1133
1134 /**
1135 * Makes a new instance of the plugininfo class
1136 *
1137 * @param string $type the plugin type, eg. 'mod'
1138 * @param string $typerootdir full path to the location of all the plugins of this type
1139 * @param string $name the plugin name, eg. 'workshop'
1140 * @param string $namerootdir full path to the location of the plugin
1141 * @param string $typeclass the name of class that holds the info about the plugin
1142 * @return plugininfo_base the instance of $typeclass
1143 */
1144 public static function make($type, $typerootdir, $name, $namerootdir, $typeclass) {
1145 $plugin = new $typeclass();
1146 $plugin->type = $type;
1147 $plugin->typerootdir = $typerootdir;
1148 $plugin->name = $name;
1149 $plugin->rootdir = $namerootdir;
1150
1151 $plugin->init_display_name();
1152 $plugin->load_disk_version();
1153 $plugin->load_db_version();
1154 $plugin->load_required_main_version();
1155 $plugin->init_is_standard();
1156
1157 return $plugin;
1158 }
1159}
1160
1161
b9934a17 1162/**
b6ad8594 1163 * Base class providing access to the information about a plugin
828788f0
TH
1164 *
1165 * @property-read string component the component name, type_name
b9934a17 1166 */
b6ad8594 1167abstract class plugininfo_base {
b9934a17
DM
1168
1169 /** @var string the plugintype name, eg. mod, auth or workshopform */
1170 public $type;
1171 /** @var string full path to the location of all the plugins of this type */
1172 public $typerootdir;
1173 /** @var string the plugin name, eg. assignment, ldap */
1174 public $name;
1175 /** @var string the localized plugin name */
1176 public $displayname;
1177 /** @var string the plugin source, one of plugin_manager::PLUGIN_SOURCE_xxx constants */
1178 public $source;
1179 /** @var fullpath to the location of this plugin */
1180 public $rootdir;
1181 /** @var int|string the version of the plugin's source code */
1182 public $versiondisk;
1183 /** @var int|string the version of the installed plugin */
1184 public $versiondb;
1185 /** @var int|float|string required version of Moodle core */
1186 public $versionrequires;
b6ad8594
DM
1187 /** @var array other plugins that this one depends on, lazy-loaded by {@link get_other_required_plugins()} */
1188 public $dependencies;
b9934a17
DM
1189 /** @var int number of instances of the plugin - not supported yet */
1190 public $instances;
1191 /** @var int order of the plugin among other plugins of the same type - not supported yet */
1192 public $sortorder;
7d8de6d8
DM
1193 /** @var array|null array of {@link available_update_info} for this plugin */
1194 public $availableupdates;
b9934a17
DM
1195
1196 /**
b6ad8594
DM
1197 * Gathers and returns the information about all plugins of the given type
1198 *
b6ad8594
DM
1199 * @param string $type the name of the plugintype, eg. mod, auth or workshopform
1200 * @param string $typerootdir full path to the location of the plugin dir
1201 * @param string $typeclass the name of the actually called class
1202 * @return array of plugintype classes, indexed by the plugin name
b9934a17
DM
1203 */
1204 public static function get_plugins($type, $typerootdir, $typeclass) {
1205
1206 // get the information about plugins at the disk
1207 $plugins = get_plugin_list($type);
1208 $ondisk = array();
1209 foreach ($plugins as $pluginname => $pluginrootdir) {
00ef3c3e
DM
1210 $ondisk[$pluginname] = plugininfo_default_factory::make($type, $typerootdir,
1211 $pluginname, $pluginrootdir, $typeclass);
b9934a17
DM
1212 }
1213 return $ondisk;
1214 }
1215
1216 /**
b6ad8594 1217 * Sets {@link $displayname} property to a localized name of the plugin
b9934a17 1218 */
b8343e68 1219 public function init_display_name() {
828788f0
TH
1220 if (!get_string_manager()->string_exists('pluginname', $this->component)) {
1221 $this->displayname = '[pluginname,' . $this->component . ']';
b9934a17 1222 } else {
828788f0
TH
1223 $this->displayname = get_string('pluginname', $this->component);
1224 }
1225 }
1226
1227 /**
1228 * Magic method getter, redirects to read only values.
b6ad8594 1229 *
828788f0
TH
1230 * @param string $name
1231 * @return mixed
1232 */
1233 public function __get($name) {
1234 switch ($name) {
1235 case 'component': return $this->type . '_' . $this->name;
1236
1237 default:
1238 debugging('Invalid plugin property accessed! '.$name);
1239 return null;
b9934a17
DM
1240 }
1241 }
1242
1243 /**
b6ad8594
DM
1244 * Return the full path name of a file within the plugin.
1245 *
1246 * No check is made to see if the file exists.
1247 *
1248 * @param string $relativepath e.g. 'version.php'.
1249 * @return string e.g. $CFG->dirroot . '/mod/quiz/version.php'.
b9934a17 1250 */
473289a0 1251 public function full_path($relativepath) {
b9934a17 1252 if (empty($this->rootdir)) {
473289a0 1253 return '';
b9934a17 1254 }
473289a0
TH
1255 return $this->rootdir . '/' . $relativepath;
1256 }
b9934a17 1257
473289a0
TH
1258 /**
1259 * Load the data from version.php.
b6ad8594
DM
1260 *
1261 * @return stdClass the object called $plugin defined in version.php
473289a0
TH
1262 */
1263 protected function load_version_php() {
1264 $versionfile = $this->full_path('version.php');
b9934a17 1265
473289a0 1266 $plugin = new stdClass();
b9934a17
DM
1267 if (is_readable($versionfile)) {
1268 include($versionfile);
b9934a17 1269 }
473289a0 1270 return $plugin;
b9934a17
DM
1271 }
1272
1273 /**
b6ad8594
DM
1274 * Sets {@link $versiondisk} property to a numerical value representing the
1275 * version of the plugin's source code.
1276 *
1277 * If the value is null after calling this method, either the plugin
1278 * does not use versioning (typically does not have any database
1279 * data) or is missing from disk.
b9934a17 1280 */
473289a0
TH
1281 public function load_disk_version() {
1282 $plugin = $this->load_version_php();
1283 if (isset($plugin->version)) {
1284 $this->versiondisk = $plugin->version;
b9934a17
DM
1285 }
1286 }
1287
1288 /**
b6ad8594
DM
1289 * Sets {@link $versionrequires} property to a numerical value representing
1290 * the version of Moodle core that this plugin requires.
b9934a17 1291 */
b8343e68 1292 public function load_required_main_version() {
473289a0
TH
1293 $plugin = $this->load_version_php();
1294 if (isset($plugin->requires)) {
1295 $this->versionrequires = $plugin->requires;
b9934a17 1296 }
473289a0 1297 }
b9934a17 1298
0242bdc7 1299 /**
777781d1 1300 * Initialise {@link $dependencies} to the list of other plugins (in any)
0242bdc7
TH
1301 * that this one requires to be installed.
1302 */
1303 protected function load_other_required_plugins() {
1304 $plugin = $this->load_version_php();
777781d1
TH
1305 if (!empty($plugin->dependencies)) {
1306 $this->dependencies = $plugin->dependencies;
0242bdc7 1307 } else {
777781d1 1308 $this->dependencies = array(); // By default, no dependencies.
0242bdc7
TH
1309 }
1310 }
1311
1312 /**
b6ad8594
DM
1313 * Get the list of other plugins that this plugin requires to be installed.
1314 *
1315 * @return array with keys the frankenstyle plugin name, and values either
1316 * a version string (like '2011101700') or the constant ANY_VERSION.
0242bdc7
TH
1317 */
1318 public function get_other_required_plugins() {
777781d1 1319 if (is_null($this->dependencies)) {
0242bdc7
TH
1320 $this->load_other_required_plugins();
1321 }
777781d1 1322 return $this->dependencies;
0242bdc7
TH
1323 }
1324
473289a0 1325 /**
b6ad8594
DM
1326 * Sets {@link $versiondb} property to a numerical value representing the
1327 * currently installed version of the plugin.
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 has not been installed yet.
473289a0
TH
1332 */
1333 public function load_db_version() {
828788f0 1334 if ($ver = self::get_version_from_config_plugins($this->component)) {
473289a0 1335 $this->versiondb = $ver;
b9934a17
DM
1336 }
1337 }
1338
1339 /**
b6ad8594
DM
1340 * Sets {@link $source} property to one of plugin_manager::PLUGIN_SOURCE_xxx
1341 * constants.
1342 *
1343 * If the property's value is null after calling this method, then
1344 * the type of the plugin has not been recognized and you should throw
1345 * an exception.
b9934a17 1346 */
b8343e68 1347 public function init_is_standard() {
b9934a17
DM
1348
1349 $standard = plugin_manager::standard_plugins_list($this->type);
1350
1351 if ($standard !== false) {
1352 $standard = array_flip($standard);
1353 if (isset($standard[$this->name])) {
1354 $this->source = plugin_manager::PLUGIN_SOURCE_STANDARD;
ec8935f5
PS
1355 } else if (!is_null($this->versiondb) and is_null($this->versiondisk)
1356 and plugin_manager::is_deleted_standard_plugin($this->type, $this->name)) {
1357 $this->source = plugin_manager::PLUGIN_SOURCE_STANDARD; // to be deleted
b9934a17
DM
1358 } else {
1359 $this->source = plugin_manager::PLUGIN_SOURCE_EXTENSION;
1360 }
1361 }
1362 }
1363
1364 /**
b6ad8594
DM
1365 * Returns true if the plugin is shipped with the official distribution
1366 * of the current Moodle version, false otherwise.
1367 *
1368 * @return bool
b9934a17
DM
1369 */
1370 public function is_standard() {
1371 return $this->source === plugin_manager::PLUGIN_SOURCE_STANDARD;
1372 }
1373
1374 /**
b6ad8594
DM
1375 * Returns the status of the plugin
1376 *
1377 * @return string one of plugin_manager::PLUGIN_STATUS_xxx constants
b9934a17
DM
1378 */
1379 public function get_status() {
1380
1381 if (is_null($this->versiondb) and is_null($this->versiondisk)) {
1382 return plugin_manager::PLUGIN_STATUS_NODB;
1383
1384 } else if (is_null($this->versiondb) and !is_null($this->versiondisk)) {
1385 return plugin_manager::PLUGIN_STATUS_NEW;
1386
1387 } else if (!is_null($this->versiondb) and is_null($this->versiondisk)) {
ec8935f5
PS
1388 if (plugin_manager::is_deleted_standard_plugin($this->type, $this->name)) {
1389 return plugin_manager::PLUGIN_STATUS_DELETE;
1390 } else {
1391 return plugin_manager::PLUGIN_STATUS_MISSING;
1392 }
b9934a17
DM
1393
1394 } else if ((string)$this->versiondb === (string)$this->versiondisk) {
1395 return plugin_manager::PLUGIN_STATUS_UPTODATE;
1396
1397 } else if ($this->versiondb < $this->versiondisk) {
1398 return plugin_manager::PLUGIN_STATUS_UPGRADE;
1399
1400 } else if ($this->versiondb > $this->versiondisk) {
1401 return plugin_manager::PLUGIN_STATUS_DOWNGRADE;
1402
1403 } else {
1404 // $version = pi(); and similar funny jokes - hopefully Donald E. Knuth will never contribute to Moodle ;-)
1405 throw new coding_exception('Unable to determine plugin state, check the plugin versions');
1406 }
1407 }
1408
1409 /**
b6ad8594
DM
1410 * Returns the information about plugin availability
1411 *
1412 * True means that the plugin is enabled. False means that the plugin is
1413 * disabled. Null means that the information is not available, or the
1414 * plugin does not support configurable availability or the availability
1415 * can not be changed.
1416 *
1417 * @return null|bool
b9934a17
DM
1418 */
1419 public function is_enabled() {
1420 return null;
1421 }
1422
dd119e21 1423 /**
7d8de6d8 1424 * Populates the property {@link $availableupdates} with the information provided by
dd119e21
DM
1425 * available update checker
1426 *
1427 * @param available_update_checker $provider the class providing the available update info
1428 */
7d8de6d8
DM
1429 public function check_available_updates(available_update_checker $provider) {
1430 $this->availableupdates = $provider->get_update_info($this->component);
dd119e21
DM
1431 }
1432
d26f3ddd 1433 /**
7d8de6d8 1434 * If there are updates for this plugin available, returns them.
d26f3ddd 1435 *
7d8de6d8
DM
1436 * Returns array of {@link available_update_info} objects, if some update
1437 * is available. Returns null if there is no update available or if the update
1438 * availability is unknown.
d26f3ddd 1439 *
7d8de6d8 1440 * @return array|null
d26f3ddd 1441 */
7d8de6d8 1442 public function available_updates() {
9bdedf32 1443 global $CFG;
dd119e21 1444
7d8de6d8 1445 if (empty($this->availableupdates) or !is_array($this->availableupdates)) {
dd119e21
DM
1446 return null;
1447 }
1448
9bdedf32
DM
1449 if (!isset($CFG->updateminmaturity)) {
1450 // this may happen during the very first upgrade to 2.3
1451 $CFG->updateminmaturity = MATURITY_STABLE;
1452 }
1453
7d8de6d8
DM
1454 $updates = array();
1455
1456 foreach ($this->availableupdates as $availableupdate) {
9bdedf32
DM
1457 if (isset($availableupdate->maturity) and $availableupdate->maturity < $CFG->updateminmaturity) {
1458 continue;
1459 }
7d8de6d8
DM
1460 if ($availableupdate->version > $this->versiondisk) {
1461 $updates[] = $availableupdate;
1462 }
1463 }
1464
1465 if (empty($updates)) {
1466 return null;
dd119e21
DM
1467 }
1468
7d8de6d8 1469 return $updates;
d26f3ddd
DM
1470 }
1471
b9934a17 1472 /**
b6ad8594
DM
1473 * Returns the URL of the plugin settings screen
1474 *
1475 * Null value means that the plugin either does not have the settings screen
1476 * or its location is not available via this library.
1477 *
1478 * @return null|moodle_url
b9934a17
DM
1479 */
1480 public function get_settings_url() {
1481 return null;
1482 }
1483
1484 /**
b6ad8594
DM
1485 * Returns the URL of the screen where this plugin can be uninstalled
1486 *
1487 * Visiting that URL must be safe, that is a manual confirmation is needed
1488 * for actual uninstallation of the plugin. Null value means that the
1489 * plugin either does not support uninstallation, or does not require any
1490 * database cleanup or the location of the screen is not available via this
1491 * library.
1492 *
1493 * @return null|moodle_url
b9934a17
DM
1494 */
1495 public function get_uninstall_url() {
1496 return null;
1497 }
1498
1499 /**
b6ad8594
DM
1500 * Returns relative directory of the plugin with heading '/'
1501 *
1502 * @return string
b9934a17
DM
1503 */
1504 public function get_dir() {
1505 global $CFG;
1506
1507 return substr($this->rootdir, strlen($CFG->dirroot));
1508 }
1509
1510 /**
1511 * Provides access to plugin versions from {config_plugins}
1512 *
1513 * @param string $plugin plugin name
1514 * @param double $disablecache optional, defaults to false
1515 * @return int|false the stored value or false if not found
1516 */
1517 protected function get_version_from_config_plugins($plugin, $disablecache=false) {
1518 global $DB;
1519 static $pluginversions = null;
1520
1521 if (is_null($pluginversions) or $disablecache) {
f433088d
PS
1522 try {
1523 $pluginversions = $DB->get_records_menu('config_plugins', array('name' => 'version'), 'plugin', 'plugin,value');
1524 } catch (dml_exception $e) {
1525 // before install
1526 $pluginversions = array();
1527 }
b9934a17
DM
1528 }
1529
1530 if (!array_key_exists($plugin, $pluginversions)) {
1531 return false;
1532 }
1533
1534 return $pluginversions[$plugin];
1535 }
1536}
1537
b6ad8594 1538
b9934a17
DM
1539/**
1540 * General class for all plugin types that do not have their own class
1541 */
b6ad8594 1542class plugininfo_general extends plugininfo_base {
b9934a17
DM
1543}
1544
b6ad8594 1545
b9934a17
DM
1546/**
1547 * Class for page side blocks
1548 */
b6ad8594 1549class plugininfo_block extends plugininfo_base {
b9934a17 1550
b9934a17
DM
1551 public static function get_plugins($type, $typerootdir, $typeclass) {
1552
1553 // get the information about blocks at the disk
1554 $blocks = parent::get_plugins($type, $typerootdir, $typeclass);
1555
1556 // add blocks missing from disk
1557 $blocksinfo = self::get_blocks_info();
1558 foreach ($blocksinfo as $blockname => $blockinfo) {
1559 if (isset($blocks[$blockname])) {
1560 continue;
1561 }
1562 $plugin = new $typeclass();
1563 $plugin->type = $type;
1564 $plugin->typerootdir = $typerootdir;
1565 $plugin->name = $blockname;
1566 $plugin->rootdir = null;
1567 $plugin->displayname = $blockname;
1568 $plugin->versiondb = $blockinfo->version;
b8343e68 1569 $plugin->init_is_standard();
b9934a17
DM
1570
1571 $blocks[$blockname] = $plugin;
1572 }
1573
1574 return $blocks;
1575 }
1576
b8343e68 1577 public function init_display_name() {
b9934a17
DM
1578
1579 if (get_string_manager()->string_exists('pluginname', 'block_' . $this->name)) {
1580 $this->displayname = get_string('pluginname', 'block_' . $this->name);
1581
1582 } else if (($block = block_instance($this->name)) !== false) {
1583 $this->displayname = $block->get_title();
1584
1585 } else {
b8343e68 1586 parent::init_display_name();
b9934a17
DM
1587 }
1588 }
1589
b8343e68 1590 public function load_db_version() {
b9934a17
DM
1591 global $DB;
1592
1593 $blocksinfo = self::get_blocks_info();
1594 if (isset($blocksinfo[$this->name]->version)) {
1595 $this->versiondb = $blocksinfo[$this->name]->version;
1596 }
1597 }
1598
b9934a17
DM
1599 public function is_enabled() {
1600
1601 $blocksinfo = self::get_blocks_info();
1602 if (isset($blocksinfo[$this->name]->visible)) {
1603 if ($blocksinfo[$this->name]->visible) {
1604 return true;
1605 } else {
1606 return false;
1607 }
1608 } else {
1609 return parent::is_enabled();
1610 }
1611 }
1612
b9934a17
DM
1613 public function get_settings_url() {
1614
1615 if (($block = block_instance($this->name)) === false) {
1616 return parent::get_settings_url();
1617
1618 } else if ($block->has_config()) {
6740c605 1619 if (file_exists($this->full_path('settings.php'))) {
b9934a17
DM
1620 return new moodle_url('/admin/settings.php', array('section' => 'blocksetting' . $this->name));
1621 } else {
1622 $blocksinfo = self::get_blocks_info();
1623 return new moodle_url('/admin/block.php', array('block' => $blocksinfo[$this->name]->id));
1624 }
1625
1626 } else {
1627 return parent::get_settings_url();
1628 }
1629 }
1630
b9934a17
DM
1631 public function get_uninstall_url() {
1632
1633 $blocksinfo = self::get_blocks_info();
1634 return new moodle_url('/admin/blocks.php', array('delete' => $blocksinfo[$this->name]->id, 'sesskey' => sesskey()));
1635 }
1636
1637 /**
1638 * Provides access to the records in {block} table
1639 *
1640 * @param bool $disablecache do not use internal static cache
1641 * @return array array of stdClasses
1642 */
1643 protected static function get_blocks_info($disablecache=false) {
1644 global $DB;
1645 static $blocksinfocache = null;
1646
1647 if (is_null($blocksinfocache) or $disablecache) {
f433088d
PS
1648 try {
1649 $blocksinfocache = $DB->get_records('block', null, 'name', 'name,id,version,visible');
1650 } catch (dml_exception $e) {
1651 // before install
1652 $blocksinfocache = array();
1653 }
b9934a17
DM
1654 }
1655
1656 return $blocksinfocache;
1657 }
1658}
1659
b6ad8594 1660
b9934a17
DM
1661/**
1662 * Class for text filters
1663 */
b6ad8594 1664class plugininfo_filter extends plugininfo_base {
b9934a17 1665
b9934a17 1666 public static function get_plugins($type, $typerootdir, $typeclass) {
7c9b837e 1667 global $CFG, $DB;
b9934a17
DM
1668
1669 $filters = array();
1670
1671 // get the list of filters from both /filter and /mod location
1672 $installed = filter_get_all_installed();
1673
1674 foreach ($installed as $filterlegacyname => $displayname) {
1675 $plugin = new $typeclass();
1676 $plugin->type = $type;
1677 $plugin->typerootdir = $typerootdir;
1678 $plugin->name = self::normalize_legacy_name($filterlegacyname);
1679 $plugin->rootdir = $CFG->dirroot . '/' . $filterlegacyname;
1680 $plugin->displayname = $displayname;
1681
b8343e68
TH
1682 $plugin->load_disk_version();
1683 $plugin->load_db_version();
1684 $plugin->load_required_main_version();
1685 $plugin->init_is_standard();
b9934a17
DM
1686
1687 $filters[$plugin->name] = $plugin;
1688 }
1689
b9934a17 1690 $globalstates = self::get_global_states();
7c9b837e
DM
1691
1692 if ($DB->get_manager()->table_exists('filter_active')) {
1693 // if we're upgrading from 1.9, the table does not exist yet
1694 // if it does, make sure that all installed filters are registered
1695 $needsreload = false;
1696 foreach (array_keys($installed) as $filterlegacyname) {
1697 if (!isset($globalstates[self::normalize_legacy_name($filterlegacyname)])) {
1698 filter_set_global_state($filterlegacyname, TEXTFILTER_DISABLED);
1699 $needsreload = true;
1700 }
1701 }
1702 if ($needsreload) {
1703 $globalstates = self::get_global_states(true);
b9934a17 1704 }
b9934a17
DM
1705 }
1706
1707 // make sure that all registered filters are installed, just in case
1708 foreach ($globalstates as $name => $info) {
1709 if (!isset($filters[$name])) {
1710 // oops, there is a record in filter_active but the filter is not installed
1711 $plugin = new $typeclass();
1712 $plugin->type = $type;
1713 $plugin->typerootdir = $typerootdir;
1714 $plugin->name = $name;
1715 $plugin->rootdir = $CFG->dirroot . '/' . $info->legacyname;
1716 $plugin->displayname = $info->legacyname;
1717
b8343e68 1718 $plugin->load_db_version();
b9934a17
DM
1719
1720 if (is_null($plugin->versiondb)) {
1721 // this is a hack to stimulate 'Missing from disk' error
1722 // because $plugin->versiondisk will be null !== false
1723 $plugin->versiondb = false;
1724 }
1725
1726 $filters[$plugin->name] = $plugin;
1727 }
1728 }
1729
1730 return $filters;
1731 }
1732
b8343e68 1733 public function init_display_name() {
b9934a17
DM
1734 // do nothing, the name is set in self::get_plugins()
1735 }
1736
1737 /**
b6ad8594 1738 * @see load_version_php()
b9934a17 1739 */
473289a0 1740 protected function load_version_php() {
b9934a17 1741 if (strpos($this->name, 'mod_') === 0) {
473289a0
TH
1742 // filters bundled with modules do not have a version.php and so
1743 // do not provide their own versioning information.
1744 return new stdClass();
b9934a17 1745 }
473289a0 1746 return parent::load_version_php();
b9934a17
DM
1747 }
1748
b9934a17
DM
1749 public function is_enabled() {
1750
1751 $globalstates = self::get_global_states();
1752
1753 foreach ($globalstates as $filterlegacyname => $info) {
1754 $name = self::normalize_legacy_name($filterlegacyname);
1755 if ($name === $this->name) {
1756 if ($info->active == TEXTFILTER_DISABLED) {
1757 return false;
1758 } else {
1759 // it may be 'On' or 'Off, but available'
1760 return null;
1761 }
1762 }
1763 }
1764
1765 return null;
1766 }
1767
b9934a17
DM
1768 public function get_settings_url() {
1769
1770 $globalstates = self::get_global_states();
1771 $legacyname = $globalstates[$this->name]->legacyname;
1772 if (filter_has_global_settings($legacyname)) {
1773 return new moodle_url('/admin/settings.php', array('section' => 'filtersetting' . str_replace('/', '', $legacyname)));
1774 } else {
1775 return null;
1776 }
1777 }
1778
b9934a17
DM
1779 public function get_uninstall_url() {
1780
1781 if (strpos($this->name, 'mod_') === 0) {
1782 return null;
1783 } else {
1784 $globalstates = self::get_global_states();
1785 $legacyname = $globalstates[$this->name]->legacyname;
1786 return new moodle_url('/admin/filters.php', array('sesskey' => sesskey(), 'filterpath' => $legacyname, 'action' => 'delete'));
1787 }
1788 }
1789
1790 /**
1791 * Convert legacy filter names like 'filter/foo' or 'mod/bar' into frankenstyle
1792 *
1793 * @param string $legacyfiltername legacy filter name
1794 * @return string frankenstyle-like name
1795 */
1796 protected static function normalize_legacy_name($legacyfiltername) {
1797
1798 $name = str_replace('/', '_', $legacyfiltername);
1799 if (strpos($name, 'filter_') === 0) {
1800 $name = substr($name, 7);
1801 if (empty($name)) {
1802 throw new coding_exception('Unable to determine filter name: ' . $legacyfiltername);
1803 }
1804 }
1805
1806 return $name;
1807 }
1808
1809 /**
1810 * Provides access to the results of {@link filter_get_global_states()}
1811 * but indexed by the normalized filter name
1812 *
1813 * The legacy filter name is available as ->legacyname property.
1814 *
1815 * @param bool $disablecache
1816 * @return array
1817 */
1818 protected static function get_global_states($disablecache=false) {
1819 global $DB;
1820 static $globalstatescache = null;
1821
1822 if ($disablecache or is_null($globalstatescache)) {
1823
1824 if (!$DB->get_manager()->table_exists('filter_active')) {
1825 // we're upgrading from 1.9 and the table used by {@link filter_get_global_states()}
1826 // does not exist yet
1827 $globalstatescache = array();
1828
1829 } else {
1830 foreach (filter_get_global_states() as $legacyname => $info) {
1831 $name = self::normalize_legacy_name($legacyname);
1832 $filterinfo = new stdClass();
1833 $filterinfo->legacyname = $legacyname;
1834 $filterinfo->active = $info->active;
1835 $filterinfo->sortorder = $info->sortorder;
1836 $globalstatescache[$name] = $filterinfo;
1837 }
1838 }
1839 }
1840
1841 return $globalstatescache;
1842 }
1843}
1844
b6ad8594 1845
b9934a17
DM
1846/**
1847 * Class for activity modules
1848 */
b6ad8594 1849class plugininfo_mod extends plugininfo_base {
b9934a17 1850
b9934a17
DM
1851 public static function get_plugins($type, $typerootdir, $typeclass) {
1852
1853 // get the information about plugins at the disk
1854 $modules = parent::get_plugins($type, $typerootdir, $typeclass);
1855
1856 // add modules missing from disk
1857 $modulesinfo = self::get_modules_info();
1858 foreach ($modulesinfo as $modulename => $moduleinfo) {
1859 if (isset($modules[$modulename])) {
1860 continue;
1861 }
1862 $plugin = new $typeclass();
1863 $plugin->type = $type;
1864 $plugin->typerootdir = $typerootdir;
1865 $plugin->name = $modulename;
1866 $plugin->rootdir = null;
1867 $plugin->displayname = $modulename;
1868 $plugin->versiondb = $moduleinfo->version;
b8343e68 1869 $plugin->init_is_standard();
b9934a17
DM
1870
1871 $modules[$modulename] = $plugin;
1872 }
1873
1874 return $modules;
1875 }
1876
b8343e68 1877 public function init_display_name() {
828788f0
TH
1878 if (get_string_manager()->string_exists('pluginname', $this->component)) {
1879 $this->displayname = get_string('pluginname', $this->component);
b9934a17 1880 } else {
828788f0 1881 $this->displayname = get_string('modulename', $this->component);
b9934a17
DM
1882 }
1883 }
1884
1885 /**
473289a0
TH
1886 * Load the data from version.php.
1887 * @return object the data object defined in version.php.
b9934a17 1888 */
473289a0
TH
1889 protected function load_version_php() {
1890 $versionfile = $this->full_path('version.php');
b9934a17 1891
473289a0 1892 $module = new stdClass();
b9934a17
DM
1893 if (is_readable($versionfile)) {
1894 include($versionfile);
b9934a17 1895 }
473289a0 1896 return $module;
b9934a17
DM
1897 }
1898
b8343e68 1899 public function load_db_version() {
b9934a17
DM
1900 global $DB;
1901
1902 $modulesinfo = self::get_modules_info();
1903 if (isset($modulesinfo[$this->name]->version)) {
1904 $this->versiondb = $modulesinfo[$this->name]->version;
1905 }
1906 }
1907
b9934a17
DM
1908 public function is_enabled() {
1909
1910 $modulesinfo = self::get_modules_info();
1911 if (isset($modulesinfo[$this->name]->visible)) {
1912 if ($modulesinfo[$this->name]->visible) {
1913 return true;
1914 } else {
1915 return false;
1916 }
1917 } else {
1918 return parent::is_enabled();
1919 }
1920 }
1921
b9934a17
DM
1922 public function get_settings_url() {
1923
6740c605 1924 if (file_exists($this->full_path('settings.php')) or file_exists($this->full_path('settingstree.php'))) {
b9934a17
DM
1925 return new moodle_url('/admin/settings.php', array('section' => 'modsetting' . $this->name));
1926 } else {
1927 return parent::get_settings_url();
1928 }
1929 }
1930
b9934a17
DM
1931 public function get_uninstall_url() {
1932
1933 if ($this->name !== 'forum') {
1934 return new moodle_url('/admin/modules.php', array('delete' => $this->name, 'sesskey' => sesskey()));
1935 } else {
1936 return null;
1937 }
1938 }
1939
1940 /**
1941 * Provides access to the records in {modules} table
1942 *
1943 * @param bool $disablecache do not use internal static cache
1944 * @return array array of stdClasses
1945 */
1946 protected static function get_modules_info($disablecache=false) {
1947 global $DB;
1948 static $modulesinfocache = null;
1949
1950 if (is_null($modulesinfocache) or $disablecache) {
f433088d
PS
1951 try {
1952 $modulesinfocache = $DB->get_records('modules', null, 'name', 'name,id,version,visible');
1953 } catch (dml_exception $e) {
1954 // before install
1955 $modulesinfocache = array();
1956 }
b9934a17
DM
1957 }
1958
1959 return $modulesinfocache;
1960 }
1961}
1962
0242bdc7
TH
1963
1964/**
1965 * Class for question behaviours.
1966 */
b6ad8594
DM
1967class plugininfo_qbehaviour extends plugininfo_base {
1968
828788f0
TH
1969 public function get_uninstall_url() {
1970 return new moodle_url('/admin/qbehaviours.php',
1971 array('delete' => $this->name, 'sesskey' => sesskey()));
1972 }
0242bdc7
TH
1973}
1974
1975
b9934a17
DM
1976/**
1977 * Class for question types
1978 */
b6ad8594
DM
1979class plugininfo_qtype extends plugininfo_base {
1980
828788f0
TH
1981 public function get_uninstall_url() {
1982 return new moodle_url('/admin/qtypes.php',
1983 array('delete' => $this->name, 'sesskey' => sesskey()));
1984 }
b9934a17
DM
1985}
1986
b9934a17
DM
1987
1988/**
1989 * Class for authentication plugins
1990 */
b6ad8594 1991class plugininfo_auth extends plugininfo_base {
b9934a17 1992
b9934a17
DM
1993 public function is_enabled() {
1994 global $CFG;
1995 /** @var null|array list of enabled authentication plugins */
1996 static $enabled = null;
1997
1998 if (in_array($this->name, array('nologin', 'manual'))) {
1999 // these two are always enabled and can't be disabled
2000 return null;
2001 }
2002
2003 if (is_null($enabled)) {
d5d181f5 2004 $enabled = array_flip(explode(',', $CFG->auth));
b9934a17
DM
2005 }
2006
2007 return isset($enabled[$this->name]);
2008 }
2009
b9934a17 2010 public function get_settings_url() {
6740c605 2011 if (file_exists($this->full_path('settings.php'))) {
b9934a17
DM
2012 return new moodle_url('/admin/settings.php', array('section' => 'authsetting' . $this->name));
2013 } else {
2014 return new moodle_url('/admin/auth_config.php', array('auth' => $this->name));
2015 }
2016 }
2017}
2018
b6ad8594 2019
b9934a17
DM
2020/**
2021 * Class for enrolment plugins
2022 */
b6ad8594 2023class plugininfo_enrol extends plugininfo_base {
b9934a17 2024
b9934a17
DM
2025 public function is_enabled() {
2026 global $CFG;
2027 /** @var null|array list of enabled enrolment plugins */
2028 static $enabled = null;
2029
b6ad8594
DM
2030 // We do not actually need whole enrolment classes here so we do not call
2031 // {@link enrol_get_plugins()}. Note that this may produce slightly different
2032 // results, for example if the enrolment plugin does not contain lib.php
2033 // but it is listed in $CFG->enrol_plugins_enabled
2034
b9934a17 2035 if (is_null($enabled)) {
d5d181f5 2036 $enabled = array_flip(explode(',', $CFG->enrol_plugins_enabled));
b9934a17
DM
2037 }
2038
2039 return isset($enabled[$this->name]);
2040 }
2041
b9934a17
DM
2042 public function get_settings_url() {
2043
6740c605 2044 if ($this->is_enabled() or file_exists($this->full_path('settings.php'))) {
b9934a17
DM
2045 return new moodle_url('/admin/settings.php', array('section' => 'enrolsettings' . $this->name));
2046 } else {
2047 return parent::get_settings_url();
2048 }
2049 }
2050
b9934a17
DM
2051 public function get_uninstall_url() {
2052 return new moodle_url('/admin/enrol.php', array('action' => 'uninstall', 'enrol' => $this->name, 'sesskey' => sesskey()));
2053 }
2054}
2055
b6ad8594 2056
b9934a17
DM
2057/**
2058 * Class for messaging processors
2059 */
b6ad8594 2060class plugininfo_message extends plugininfo_base {
b9934a17 2061
b9934a17
DM
2062 public function get_settings_url() {
2063
6740c605
TH
2064 if (file_exists($this->full_path('settings.php')) or file_exists($this->full_path('settingstree.php'))) {
2065 return new moodle_url('/admin/settings.php', array('section' => 'messagesetting' . $this->name));
2066 } else {
2067 return parent::get_settings_url();
b9934a17 2068 }
b9934a17
DM
2069 }
2070}
2071
b6ad8594 2072
b9934a17
DM
2073/**
2074 * Class for repositories
2075 */
b6ad8594 2076class plugininfo_repository extends plugininfo_base {
b9934a17 2077
b9934a17
DM
2078 public function is_enabled() {
2079
2080 $enabled = self::get_enabled_repositories();
2081
2082 return isset($enabled[$this->name]);
2083 }
2084
b9934a17
DM
2085 public function get_settings_url() {
2086
2087 if ($this->is_enabled()) {
2088 return new moodle_url('/admin/repository.php', array('sesskey' => sesskey(), 'action' => 'edit', 'repos' => $this->name));
2089 } else {
2090 return parent::get_settings_url();
2091 }
2092 }
2093
2094 /**
2095 * Provides access to the records in {repository} table
2096 *
2097 * @param bool $disablecache do not use internal static cache
2098 * @return array array of stdClasses
2099 */
2100 protected static function get_enabled_repositories($disablecache=false) {
2101 global $DB;
2102 static $repositories = null;
2103
2104 if (is_null($repositories) or $disablecache) {
2105 $repositories = $DB->get_records('repository', null, 'type', 'type,visible,sortorder');
2106 }
2107
2108 return $repositories;
2109 }
2110}
2111
b6ad8594 2112
b9934a17
DM
2113/**
2114 * Class for portfolios
2115 */
b6ad8594 2116class plugininfo_portfolio extends plugininfo_base {
b9934a17 2117
b9934a17
DM
2118 public function is_enabled() {
2119
2120 $enabled = self::get_enabled_portfolios();
2121
2122 return isset($enabled[$this->name]);
2123 }
2124
2125 /**
2126 * Provides access to the records in {portfolio_instance} table
2127 *
2128 * @param bool $disablecache do not use internal static cache
2129 * @return array array of stdClasses
2130 */
2131 protected static function get_enabled_portfolios($disablecache=false) {
2132 global $DB;
2133 static $portfolios = null;
2134
2135 if (is_null($portfolios) or $disablecache) {
2136 $portfolios = array();
2137 $instances = $DB->get_recordset('portfolio_instance', null, 'plugin');
2138 foreach ($instances as $instance) {
2139 if (isset($portfolios[$instance->plugin])) {
2140 if ($instance->visible) {
2141 $portfolios[$instance->plugin]->visible = $instance->visible;
2142 }
2143 } else {
2144 $portfolios[$instance->plugin] = $instance;
2145 }
2146 }
2147 }
2148
2149 return $portfolios;
2150 }
2151}
2152
b6ad8594 2153
b9934a17
DM
2154/**
2155 * Class for themes
2156 */
b6ad8594 2157class plugininfo_theme extends plugininfo_base {
b9934a17 2158
b9934a17
DM
2159 public function is_enabled() {
2160 global $CFG;
2161
2162 if ((!empty($CFG->theme) and $CFG->theme === $this->name) or
2163 (!empty($CFG->themelegacy) and $CFG->themelegacy === $this->name)) {
2164 return true;
2165 } else {
2166 return parent::is_enabled();
2167 }
2168 }
2169}
2170
b6ad8594 2171
b9934a17
DM
2172/**
2173 * Class representing an MNet service
2174 */
b6ad8594 2175class plugininfo_mnetservice extends plugininfo_base {
b9934a17 2176
b9934a17
DM
2177 public function is_enabled() {
2178 global $CFG;
2179
2180 if (empty($CFG->mnet_dispatcher_mode) || $CFG->mnet_dispatcher_mode !== 'strict') {
2181 return false;
2182 } else {
2183 return parent::is_enabled();
2184 }
2185 }
2186}
3cdfaeef 2187
b6ad8594 2188
3cdfaeef
PS
2189/**
2190 * Class for admin tool plugins
2191 */
b6ad8594 2192class plugininfo_tool extends plugininfo_base {
3cdfaeef
PS
2193
2194 public function get_uninstall_url() {
2195 return new moodle_url('/admin/tools.php', array('delete' => $this->name, 'sesskey' => sesskey()));
2196 }
2197}
4f6bba20 2198
b6ad8594 2199
4f6bba20
PS
2200/**
2201 * Class for admin tool plugins
2202 */
b6ad8594 2203class plugininfo_report extends plugininfo_base {
4f6bba20
PS
2204
2205 public function get_uninstall_url() {
2206 return new moodle_url('/admin/reports.php', array('delete' => $this->name, 'sesskey' => sesskey()));
2207 }
2208}