MDL-20438 dropping get_core_update_info() 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 *
834 * @param null|array $old
835 * @param null|array $new
836 * @throws available_update_checker_exception
837 * @return array parts of $new['updates'] that have changed
838 */
839 protected function compare_responses($old, $new) {
840
841 if (is_null($new)) {
842 return array();
843 }
844
845 if (!array_key_exists('updates', $new)) {
846 throw new available_update_checker_exception('err_response_format');
847 }
848
849 if (is_null($old)) {
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;
1084 $changes = $this->compare_responses($previous, $current);
be378880 1085 }
cd0bb55f
DM
1086}
1087
1088
7d8de6d8
DM
1089/**
1090 * Defines the structure of objects returned by {@link available_update_checker::get_update_info()}
1091 */
1092class available_update_info {
1093
1094 /** @var string frankenstyle component name */
1095 public $component;
1096 /** @var int the available version of the component */
1097 public $version;
1098 /** @var string|null optional release name */
1099 public $release = null;
1100 /** @var int|null optional maturity info, eg {@link MATURITY_STABLE} */
1101 public $maturity = null;
1102 /** @var string|null optional URL of a page with more info about the update */
1103 public $url = null;
1104 /** @var string|null optional URL of a ZIP package that can be downloaded and installed */
1105 public $download = null;
1106
1107 /**
1108 * Creates new instance of the class
1109 *
1110 * The $info array must provide at least the 'version' value and optionally all other
1111 * values to populate the object's properties.
1112 *
1113 * @param string $name the frankenstyle component name
1114 * @param array $info associative array with other properties
1115 */
1116 public function __construct($name, array $info) {
1117 $this->component = $name;
1118 foreach ($info as $k => $v) {
1119 if (property_exists('available_update_info', $k) and $k != 'component') {
1120 $this->$k = $v;
1121 }
1122 }
1123 }
1124}
1125
1126
00ef3c3e
DM
1127/**
1128 * Factory class producing required subclasses of {@link plugininfo_base}
1129 */
1130class plugininfo_default_factory {
1131
1132 /**
1133 * Makes a new instance of the plugininfo class
1134 *
1135 * @param string $type the plugin type, eg. 'mod'
1136 * @param string $typerootdir full path to the location of all the plugins of this type
1137 * @param string $name the plugin name, eg. 'workshop'
1138 * @param string $namerootdir full path to the location of the plugin
1139 * @param string $typeclass the name of class that holds the info about the plugin
1140 * @return plugininfo_base the instance of $typeclass
1141 */
1142 public static function make($type, $typerootdir, $name, $namerootdir, $typeclass) {
1143 $plugin = new $typeclass();
1144 $plugin->type = $type;
1145 $plugin->typerootdir = $typerootdir;
1146 $plugin->name = $name;
1147 $plugin->rootdir = $namerootdir;
1148
1149 $plugin->init_display_name();
1150 $plugin->load_disk_version();
1151 $plugin->load_db_version();
1152 $plugin->load_required_main_version();
1153 $plugin->init_is_standard();
1154
1155 return $plugin;
1156 }
1157}
1158
1159
b9934a17 1160/**
b6ad8594 1161 * Base class providing access to the information about a plugin
828788f0
TH
1162 *
1163 * @property-read string component the component name, type_name
b9934a17 1164 */
b6ad8594 1165abstract class plugininfo_base {
b9934a17
DM
1166
1167 /** @var string the plugintype name, eg. mod, auth or workshopform */
1168 public $type;
1169 /** @var string full path to the location of all the plugins of this type */
1170 public $typerootdir;
1171 /** @var string the plugin name, eg. assignment, ldap */
1172 public $name;
1173 /** @var string the localized plugin name */
1174 public $displayname;
1175 /** @var string the plugin source, one of plugin_manager::PLUGIN_SOURCE_xxx constants */
1176 public $source;
1177 /** @var fullpath to the location of this plugin */
1178 public $rootdir;
1179 /** @var int|string the version of the plugin's source code */
1180 public $versiondisk;
1181 /** @var int|string the version of the installed plugin */
1182 public $versiondb;
1183 /** @var int|float|string required version of Moodle core */
1184 public $versionrequires;
b6ad8594
DM
1185 /** @var array other plugins that this one depends on, lazy-loaded by {@link get_other_required_plugins()} */
1186 public $dependencies;
b9934a17
DM
1187 /** @var int number of instances of the plugin - not supported yet */
1188 public $instances;
1189 /** @var int order of the plugin among other plugins of the same type - not supported yet */
1190 public $sortorder;
7d8de6d8
DM
1191 /** @var array|null array of {@link available_update_info} for this plugin */
1192 public $availableupdates;
b9934a17
DM
1193
1194 /**
b6ad8594
DM
1195 * Gathers and returns the information about all plugins of the given type
1196 *
b6ad8594
DM
1197 * @param string $type the name of the plugintype, eg. mod, auth or workshopform
1198 * @param string $typerootdir full path to the location of the plugin dir
1199 * @param string $typeclass the name of the actually called class
1200 * @return array of plugintype classes, indexed by the plugin name
b9934a17
DM
1201 */
1202 public static function get_plugins($type, $typerootdir, $typeclass) {
1203
1204 // get the information about plugins at the disk
1205 $plugins = get_plugin_list($type);
1206 $ondisk = array();
1207 foreach ($plugins as $pluginname => $pluginrootdir) {
00ef3c3e
DM
1208 $ondisk[$pluginname] = plugininfo_default_factory::make($type, $typerootdir,
1209 $pluginname, $pluginrootdir, $typeclass);
b9934a17
DM
1210 }
1211 return $ondisk;
1212 }
1213
1214 /**
b6ad8594 1215 * Sets {@link $displayname} property to a localized name of the plugin
b9934a17 1216 */
b8343e68 1217 public function init_display_name() {
828788f0
TH
1218 if (!get_string_manager()->string_exists('pluginname', $this->component)) {
1219 $this->displayname = '[pluginname,' . $this->component . ']';
b9934a17 1220 } else {
828788f0
TH
1221 $this->displayname = get_string('pluginname', $this->component);
1222 }
1223 }
1224
1225 /**
1226 * Magic method getter, redirects to read only values.
b6ad8594 1227 *
828788f0
TH
1228 * @param string $name
1229 * @return mixed
1230 */
1231 public function __get($name) {
1232 switch ($name) {
1233 case 'component': return $this->type . '_' . $this->name;
1234
1235 default:
1236 debugging('Invalid plugin property accessed! '.$name);
1237 return null;
b9934a17
DM
1238 }
1239 }
1240
1241 /**
b6ad8594
DM
1242 * Return the full path name of a file within the plugin.
1243 *
1244 * No check is made to see if the file exists.
1245 *
1246 * @param string $relativepath e.g. 'version.php'.
1247 * @return string e.g. $CFG->dirroot . '/mod/quiz/version.php'.
b9934a17 1248 */
473289a0 1249 public function full_path($relativepath) {
b9934a17 1250 if (empty($this->rootdir)) {
473289a0 1251 return '';
b9934a17 1252 }
473289a0
TH
1253 return $this->rootdir . '/' . $relativepath;
1254 }
b9934a17 1255
473289a0
TH
1256 /**
1257 * Load the data from version.php.
b6ad8594
DM
1258 *
1259 * @return stdClass the object called $plugin defined in version.php
473289a0
TH
1260 */
1261 protected function load_version_php() {
1262 $versionfile = $this->full_path('version.php');
b9934a17 1263
473289a0 1264 $plugin = new stdClass();
b9934a17
DM
1265 if (is_readable($versionfile)) {
1266 include($versionfile);
b9934a17 1267 }
473289a0 1268 return $plugin;
b9934a17
DM
1269 }
1270
1271 /**
b6ad8594
DM
1272 * Sets {@link $versiondisk} property to a numerical value representing the
1273 * version of the plugin's source code.
1274 *
1275 * If the value is null after calling this method, either the plugin
1276 * does not use versioning (typically does not have any database
1277 * data) or is missing from disk.
b9934a17 1278 */
473289a0
TH
1279 public function load_disk_version() {
1280 $plugin = $this->load_version_php();
1281 if (isset($plugin->version)) {
1282 $this->versiondisk = $plugin->version;
b9934a17
DM
1283 }
1284 }
1285
1286 /**
b6ad8594
DM
1287 * Sets {@link $versionrequires} property to a numerical value representing
1288 * the version of Moodle core that this plugin requires.
b9934a17 1289 */
b8343e68 1290 public function load_required_main_version() {
473289a0
TH
1291 $plugin = $this->load_version_php();
1292 if (isset($plugin->requires)) {
1293 $this->versionrequires = $plugin->requires;
b9934a17 1294 }
473289a0 1295 }
b9934a17 1296
0242bdc7 1297 /**
777781d1 1298 * Initialise {@link $dependencies} to the list of other plugins (in any)
0242bdc7
TH
1299 * that this one requires to be installed.
1300 */
1301 protected function load_other_required_plugins() {
1302 $plugin = $this->load_version_php();
777781d1
TH
1303 if (!empty($plugin->dependencies)) {
1304 $this->dependencies = $plugin->dependencies;
0242bdc7 1305 } else {
777781d1 1306 $this->dependencies = array(); // By default, no dependencies.
0242bdc7
TH
1307 }
1308 }
1309
1310 /**
b6ad8594
DM
1311 * Get the list of other plugins that this plugin requires to be installed.
1312 *
1313 * @return array with keys the frankenstyle plugin name, and values either
1314 * a version string (like '2011101700') or the constant ANY_VERSION.
0242bdc7
TH
1315 */
1316 public function get_other_required_plugins() {
777781d1 1317 if (is_null($this->dependencies)) {
0242bdc7
TH
1318 $this->load_other_required_plugins();
1319 }
777781d1 1320 return $this->dependencies;
0242bdc7
TH
1321 }
1322
473289a0 1323 /**
b6ad8594
DM
1324 * Sets {@link $versiondb} property to a numerical value representing the
1325 * currently installed version of the plugin.
1326 *
1327 * If the value is null after calling this method, either the plugin
1328 * does not use versioning (typically does not have any database
1329 * data) or has not been installed yet.
473289a0
TH
1330 */
1331 public function load_db_version() {
828788f0 1332 if ($ver = self::get_version_from_config_plugins($this->component)) {
473289a0 1333 $this->versiondb = $ver;
b9934a17
DM
1334 }
1335 }
1336
1337 /**
b6ad8594
DM
1338 * Sets {@link $source} property to one of plugin_manager::PLUGIN_SOURCE_xxx
1339 * constants.
1340 *
1341 * If the property's value is null after calling this method, then
1342 * the type of the plugin has not been recognized and you should throw
1343 * an exception.
b9934a17 1344 */
b8343e68 1345 public function init_is_standard() {
b9934a17
DM
1346
1347 $standard = plugin_manager::standard_plugins_list($this->type);
1348
1349 if ($standard !== false) {
1350 $standard = array_flip($standard);
1351 if (isset($standard[$this->name])) {
1352 $this->source = plugin_manager::PLUGIN_SOURCE_STANDARD;
ec8935f5
PS
1353 } else if (!is_null($this->versiondb) and is_null($this->versiondisk)
1354 and plugin_manager::is_deleted_standard_plugin($this->type, $this->name)) {
1355 $this->source = plugin_manager::PLUGIN_SOURCE_STANDARD; // to be deleted
b9934a17
DM
1356 } else {
1357 $this->source = plugin_manager::PLUGIN_SOURCE_EXTENSION;
1358 }
1359 }
1360 }
1361
1362 /**
b6ad8594
DM
1363 * Returns true if the plugin is shipped with the official distribution
1364 * of the current Moodle version, false otherwise.
1365 *
1366 * @return bool
b9934a17
DM
1367 */
1368 public function is_standard() {
1369 return $this->source === plugin_manager::PLUGIN_SOURCE_STANDARD;
1370 }
1371
1372 /**
b6ad8594
DM
1373 * Returns the status of the plugin
1374 *
1375 * @return string one of plugin_manager::PLUGIN_STATUS_xxx constants
b9934a17
DM
1376 */
1377 public function get_status() {
1378
1379 if (is_null($this->versiondb) and is_null($this->versiondisk)) {
1380 return plugin_manager::PLUGIN_STATUS_NODB;
1381
1382 } else if (is_null($this->versiondb) and !is_null($this->versiondisk)) {
1383 return plugin_manager::PLUGIN_STATUS_NEW;
1384
1385 } else if (!is_null($this->versiondb) and is_null($this->versiondisk)) {
ec8935f5
PS
1386 if (plugin_manager::is_deleted_standard_plugin($this->type, $this->name)) {
1387 return plugin_manager::PLUGIN_STATUS_DELETE;
1388 } else {
1389 return plugin_manager::PLUGIN_STATUS_MISSING;
1390 }
b9934a17
DM
1391
1392 } else if ((string)$this->versiondb === (string)$this->versiondisk) {
1393 return plugin_manager::PLUGIN_STATUS_UPTODATE;
1394
1395 } else if ($this->versiondb < $this->versiondisk) {
1396 return plugin_manager::PLUGIN_STATUS_UPGRADE;
1397
1398 } else if ($this->versiondb > $this->versiondisk) {
1399 return plugin_manager::PLUGIN_STATUS_DOWNGRADE;
1400
1401 } else {
1402 // $version = pi(); and similar funny jokes - hopefully Donald E. Knuth will never contribute to Moodle ;-)
1403 throw new coding_exception('Unable to determine plugin state, check the plugin versions');
1404 }
1405 }
1406
1407 /**
b6ad8594
DM
1408 * Returns the information about plugin availability
1409 *
1410 * True means that the plugin is enabled. False means that the plugin is
1411 * disabled. Null means that the information is not available, or the
1412 * plugin does not support configurable availability or the availability
1413 * can not be changed.
1414 *
1415 * @return null|bool
b9934a17
DM
1416 */
1417 public function is_enabled() {
1418 return null;
1419 }
1420
dd119e21 1421 /**
7d8de6d8 1422 * Populates the property {@link $availableupdates} with the information provided by
dd119e21
DM
1423 * available update checker
1424 *
1425 * @param available_update_checker $provider the class providing the available update info
1426 */
7d8de6d8 1427 public function check_available_updates(available_update_checker $provider) {
c6f008e7
DM
1428 global $CFG;
1429
1430 if (isset($CFG->updateminmaturity)) {
1431 $minmaturity = $CFG->updateminmaturity;
1432 } else {
1433 // this can happen during the very first upgrade to 2.3
1434 $minmaturity = MATURITY_STABLE;
1435 }
1436
1437 $this->availableupdates = $provider->get_update_info($this->component,
1438 array('minmaturity' => $minmaturity));
dd119e21
DM
1439 }
1440
d26f3ddd 1441 /**
7d8de6d8 1442 * If there are updates for this plugin available, returns them.
d26f3ddd 1443 *
7d8de6d8
DM
1444 * Returns array of {@link available_update_info} objects, if some update
1445 * is available. Returns null if there is no update available or if the update
1446 * availability is unknown.
d26f3ddd 1447 *
7d8de6d8 1448 * @return array|null
d26f3ddd 1449 */
7d8de6d8 1450 public function available_updates() {
dd119e21 1451
7d8de6d8 1452 if (empty($this->availableupdates) or !is_array($this->availableupdates)) {
dd119e21
DM
1453 return null;
1454 }
1455
7d8de6d8
DM
1456 $updates = array();
1457
1458 foreach ($this->availableupdates as $availableupdate) {
1459 if ($availableupdate->version > $this->versiondisk) {
1460 $updates[] = $availableupdate;
1461 }
1462 }
1463
1464 if (empty($updates)) {
1465 return null;
dd119e21
DM
1466 }
1467
7d8de6d8 1468 return $updates;
d26f3ddd
DM
1469 }
1470
b9934a17 1471 /**
b6ad8594
DM
1472 * Returns the URL of the plugin settings screen
1473 *
1474 * Null value means that the plugin either does not have the settings screen
1475 * or its location is not available via this library.
1476 *
1477 * @return null|moodle_url
b9934a17
DM
1478 */
1479 public function get_settings_url() {
1480 return null;
1481 }
1482
1483 /**
b6ad8594
DM
1484 * Returns the URL of the screen where this plugin can be uninstalled
1485 *
1486 * Visiting that URL must be safe, that is a manual confirmation is needed
1487 * for actual uninstallation of the plugin. Null value means that the
1488 * plugin either does not support uninstallation, or does not require any
1489 * database cleanup or the location of the screen is not available via this
1490 * library.
1491 *
1492 * @return null|moodle_url
b9934a17
DM
1493 */
1494 public function get_uninstall_url() {
1495 return null;
1496 }
1497
1498 /**
b6ad8594
DM
1499 * Returns relative directory of the plugin with heading '/'
1500 *
1501 * @return string
b9934a17
DM
1502 */
1503 public function get_dir() {
1504 global $CFG;
1505
1506 return substr($this->rootdir, strlen($CFG->dirroot));
1507 }
1508
1509 /**
1510 * Provides access to plugin versions from {config_plugins}
1511 *
1512 * @param string $plugin plugin name
1513 * @param double $disablecache optional, defaults to false
1514 * @return int|false the stored value or false if not found
1515 */
1516 protected function get_version_from_config_plugins($plugin, $disablecache=false) {
1517 global $DB;
1518 static $pluginversions = null;
1519
1520 if (is_null($pluginversions) or $disablecache) {
f433088d
PS
1521 try {
1522 $pluginversions = $DB->get_records_menu('config_plugins', array('name' => 'version'), 'plugin', 'plugin,value');
1523 } catch (dml_exception $e) {
1524 // before install
1525 $pluginversions = array();
1526 }
b9934a17
DM
1527 }
1528
1529 if (!array_key_exists($plugin, $pluginversions)) {
1530 return false;
1531 }
1532
1533 return $pluginversions[$plugin];
1534 }
1535}
1536
b6ad8594 1537
b9934a17
DM
1538/**
1539 * General class for all plugin types that do not have their own class
1540 */
b6ad8594 1541class plugininfo_general extends plugininfo_base {
b9934a17
DM
1542}
1543
b6ad8594 1544
b9934a17
DM
1545/**
1546 * Class for page side blocks
1547 */
b6ad8594 1548class plugininfo_block extends plugininfo_base {
b9934a17 1549
b9934a17
DM
1550 public static function get_plugins($type, $typerootdir, $typeclass) {
1551
1552 // get the information about blocks at the disk
1553 $blocks = parent::get_plugins($type, $typerootdir, $typeclass);
1554
1555 // add blocks missing from disk
1556 $blocksinfo = self::get_blocks_info();
1557 foreach ($blocksinfo as $blockname => $blockinfo) {
1558 if (isset($blocks[$blockname])) {
1559 continue;
1560 }
1561 $plugin = new $typeclass();
1562 $plugin->type = $type;
1563 $plugin->typerootdir = $typerootdir;
1564 $plugin->name = $blockname;
1565 $plugin->rootdir = null;
1566 $plugin->displayname = $blockname;
1567 $plugin->versiondb = $blockinfo->version;
b8343e68 1568 $plugin->init_is_standard();
b9934a17
DM
1569
1570 $blocks[$blockname] = $plugin;
1571 }
1572
1573 return $blocks;
1574 }
1575
b8343e68 1576 public function init_display_name() {
b9934a17
DM
1577
1578 if (get_string_manager()->string_exists('pluginname', 'block_' . $this->name)) {
1579 $this->displayname = get_string('pluginname', 'block_' . $this->name);
1580
1581 } else if (($block = block_instance($this->name)) !== false) {
1582 $this->displayname = $block->get_title();
1583
1584 } else {
b8343e68 1585 parent::init_display_name();
b9934a17
DM
1586 }
1587 }
1588
b8343e68 1589 public function load_db_version() {
b9934a17
DM
1590 global $DB;
1591
1592 $blocksinfo = self::get_blocks_info();
1593 if (isset($blocksinfo[$this->name]->version)) {
1594 $this->versiondb = $blocksinfo[$this->name]->version;
1595 }
1596 }
1597
b9934a17
DM
1598 public function is_enabled() {
1599
1600 $blocksinfo = self::get_blocks_info();
1601 if (isset($blocksinfo[$this->name]->visible)) {
1602 if ($blocksinfo[$this->name]->visible) {
1603 return true;
1604 } else {
1605 return false;
1606 }
1607 } else {
1608 return parent::is_enabled();
1609 }
1610 }
1611
b9934a17
DM
1612 public function get_settings_url() {
1613
1614 if (($block = block_instance($this->name)) === false) {
1615 return parent::get_settings_url();
1616
1617 } else if ($block->has_config()) {
6740c605 1618 if (file_exists($this->full_path('settings.php'))) {
b9934a17
DM
1619 return new moodle_url('/admin/settings.php', array('section' => 'blocksetting' . $this->name));
1620 } else {
1621 $blocksinfo = self::get_blocks_info();
1622 return new moodle_url('/admin/block.php', array('block' => $blocksinfo[$this->name]->id));
1623 }
1624
1625 } else {
1626 return parent::get_settings_url();
1627 }
1628 }
1629
b9934a17
DM
1630 public function get_uninstall_url() {
1631
1632 $blocksinfo = self::get_blocks_info();
1633 return new moodle_url('/admin/blocks.php', array('delete' => $blocksinfo[$this->name]->id, 'sesskey' => sesskey()));
1634 }
1635
1636 /**
1637 * Provides access to the records in {block} table
1638 *
1639 * @param bool $disablecache do not use internal static cache
1640 * @return array array of stdClasses
1641 */
1642 protected static function get_blocks_info($disablecache=false) {
1643 global $DB;
1644 static $blocksinfocache = null;
1645
1646 if (is_null($blocksinfocache) or $disablecache) {
f433088d
PS
1647 try {
1648 $blocksinfocache = $DB->get_records('block', null, 'name', 'name,id,version,visible');
1649 } catch (dml_exception $e) {
1650 // before install
1651 $blocksinfocache = array();
1652 }
b9934a17
DM
1653 }
1654
1655 return $blocksinfocache;
1656 }
1657}
1658
b6ad8594 1659
b9934a17
DM
1660/**
1661 * Class for text filters
1662 */
b6ad8594 1663class plugininfo_filter extends plugininfo_base {
b9934a17 1664
b9934a17 1665 public static function get_plugins($type, $typerootdir, $typeclass) {
7c9b837e 1666 global $CFG, $DB;
b9934a17
DM
1667
1668 $filters = array();
1669
1670 // get the list of filters from both /filter and /mod location
1671 $installed = filter_get_all_installed();
1672
1673 foreach ($installed as $filterlegacyname => $displayname) {
1674 $plugin = new $typeclass();
1675 $plugin->type = $type;
1676 $plugin->typerootdir = $typerootdir;
1677 $plugin->name = self::normalize_legacy_name($filterlegacyname);
1678 $plugin->rootdir = $CFG->dirroot . '/' . $filterlegacyname;
1679 $plugin->displayname = $displayname;
1680
b8343e68
TH
1681 $plugin->load_disk_version();
1682 $plugin->load_db_version();
1683 $plugin->load_required_main_version();
1684 $plugin->init_is_standard();
b9934a17
DM
1685
1686 $filters[$plugin->name] = $plugin;
1687 }
1688
b9934a17 1689 $globalstates = self::get_global_states();
7c9b837e
DM
1690
1691 if ($DB->get_manager()->table_exists('filter_active')) {
1692 // if we're upgrading from 1.9, the table does not exist yet
1693 // if it does, make sure that all installed filters are registered
1694 $needsreload = false;
1695 foreach (array_keys($installed) as $filterlegacyname) {
1696 if (!isset($globalstates[self::normalize_legacy_name($filterlegacyname)])) {
1697 filter_set_global_state($filterlegacyname, TEXTFILTER_DISABLED);
1698 $needsreload = true;
1699 }
1700 }
1701 if ($needsreload) {
1702 $globalstates = self::get_global_states(true);
b9934a17 1703 }
b9934a17
DM
1704 }
1705
1706 // make sure that all registered filters are installed, just in case
1707 foreach ($globalstates as $name => $info) {
1708 if (!isset($filters[$name])) {
1709 // oops, there is a record in filter_active but the filter is not installed
1710 $plugin = new $typeclass();
1711 $plugin->type = $type;
1712 $plugin->typerootdir = $typerootdir;
1713 $plugin->name = $name;
1714 $plugin->rootdir = $CFG->dirroot . '/' . $info->legacyname;
1715 $plugin->displayname = $info->legacyname;
1716
b8343e68 1717 $plugin->load_db_version();
b9934a17
DM
1718
1719 if (is_null($plugin->versiondb)) {
1720 // this is a hack to stimulate 'Missing from disk' error
1721 // because $plugin->versiondisk will be null !== false
1722 $plugin->versiondb = false;
1723 }
1724
1725 $filters[$plugin->name] = $plugin;
1726 }
1727 }
1728
1729 return $filters;
1730 }
1731
b8343e68 1732 public function init_display_name() {
b9934a17
DM
1733 // do nothing, the name is set in self::get_plugins()
1734 }
1735
1736 /**
b6ad8594 1737 * @see load_version_php()
b9934a17 1738 */
473289a0 1739 protected function load_version_php() {
b9934a17 1740 if (strpos($this->name, 'mod_') === 0) {
473289a0
TH
1741 // filters bundled with modules do not have a version.php and so
1742 // do not provide their own versioning information.
1743 return new stdClass();
b9934a17 1744 }
473289a0 1745 return parent::load_version_php();
b9934a17
DM
1746 }
1747
b9934a17
DM
1748 public function is_enabled() {
1749
1750 $globalstates = self::get_global_states();
1751
1752 foreach ($globalstates as $filterlegacyname => $info) {
1753 $name = self::normalize_legacy_name($filterlegacyname);
1754 if ($name === $this->name) {
1755 if ($info->active == TEXTFILTER_DISABLED) {
1756 return false;
1757 } else {
1758 // it may be 'On' or 'Off, but available'
1759 return null;
1760 }
1761 }
1762 }
1763
1764 return null;
1765 }
1766
b9934a17
DM
1767 public function get_settings_url() {
1768
1769 $globalstates = self::get_global_states();
1770 $legacyname = $globalstates[$this->name]->legacyname;
1771 if (filter_has_global_settings($legacyname)) {
1772 return new moodle_url('/admin/settings.php', array('section' => 'filtersetting' . str_replace('/', '', $legacyname)));
1773 } else {
1774 return null;
1775 }
1776 }
1777
b9934a17
DM
1778 public function get_uninstall_url() {
1779
1780 if (strpos($this->name, 'mod_') === 0) {
1781 return null;
1782 } else {
1783 $globalstates = self::get_global_states();
1784 $legacyname = $globalstates[$this->name]->legacyname;
1785 return new moodle_url('/admin/filters.php', array('sesskey' => sesskey(), 'filterpath' => $legacyname, 'action' => 'delete'));
1786 }
1787 }
1788
1789 /**
1790 * Convert legacy filter names like 'filter/foo' or 'mod/bar' into frankenstyle
1791 *
1792 * @param string $legacyfiltername legacy filter name
1793 * @return string frankenstyle-like name
1794 */
1795 protected static function normalize_legacy_name($legacyfiltername) {
1796
1797 $name = str_replace('/', '_', $legacyfiltername);
1798 if (strpos($name, 'filter_') === 0) {
1799 $name = substr($name, 7);
1800 if (empty($name)) {
1801 throw new coding_exception('Unable to determine filter name: ' . $legacyfiltername);
1802 }
1803 }
1804
1805 return $name;
1806 }
1807
1808 /**
1809 * Provides access to the results of {@link filter_get_global_states()}
1810 * but indexed by the normalized filter name
1811 *
1812 * The legacy filter name is available as ->legacyname property.
1813 *
1814 * @param bool $disablecache
1815 * @return array
1816 */
1817 protected static function get_global_states($disablecache=false) {
1818 global $DB;
1819 static $globalstatescache = null;
1820
1821 if ($disablecache or is_null($globalstatescache)) {
1822
1823 if (!$DB->get_manager()->table_exists('filter_active')) {
1824 // we're upgrading from 1.9 and the table used by {@link filter_get_global_states()}
1825 // does not exist yet
1826 $globalstatescache = array();
1827
1828 } else {
1829 foreach (filter_get_global_states() as $legacyname => $info) {
1830 $name = self::normalize_legacy_name($legacyname);
1831 $filterinfo = new stdClass();
1832 $filterinfo->legacyname = $legacyname;
1833 $filterinfo->active = $info->active;
1834 $filterinfo->sortorder = $info->sortorder;
1835 $globalstatescache[$name] = $filterinfo;
1836 }
1837 }
1838 }
1839
1840 return $globalstatescache;
1841 }
1842}
1843
b6ad8594 1844
b9934a17
DM
1845/**
1846 * Class for activity modules
1847 */
b6ad8594 1848class plugininfo_mod extends plugininfo_base {
b9934a17 1849
b9934a17
DM
1850 public static function get_plugins($type, $typerootdir, $typeclass) {
1851
1852 // get the information about plugins at the disk
1853 $modules = parent::get_plugins($type, $typerootdir, $typeclass);
1854
1855 // add modules missing from disk
1856 $modulesinfo = self::get_modules_info();
1857 foreach ($modulesinfo as $modulename => $moduleinfo) {
1858 if (isset($modules[$modulename])) {
1859 continue;
1860 }
1861 $plugin = new $typeclass();
1862 $plugin->type = $type;
1863 $plugin->typerootdir = $typerootdir;
1864 $plugin->name = $modulename;
1865 $plugin->rootdir = null;
1866 $plugin->displayname = $modulename;
1867 $plugin->versiondb = $moduleinfo->version;
b8343e68 1868 $plugin->init_is_standard();
b9934a17
DM
1869
1870 $modules[$modulename] = $plugin;
1871 }
1872
1873 return $modules;
1874 }
1875
b8343e68 1876 public function init_display_name() {
828788f0
TH
1877 if (get_string_manager()->string_exists('pluginname', $this->component)) {
1878 $this->displayname = get_string('pluginname', $this->component);
b9934a17 1879 } else {
828788f0 1880 $this->displayname = get_string('modulename', $this->component);
b9934a17
DM
1881 }
1882 }
1883
1884 /**
473289a0
TH
1885 * Load the data from version.php.
1886 * @return object the data object defined in version.php.
b9934a17 1887 */
473289a0
TH
1888 protected function load_version_php() {
1889 $versionfile = $this->full_path('version.php');
b9934a17 1890
473289a0 1891 $module = new stdClass();
b9934a17
DM
1892 if (is_readable($versionfile)) {
1893 include($versionfile);
b9934a17 1894 }
473289a0 1895 return $module;
b9934a17
DM
1896 }
1897
b8343e68 1898 public function load_db_version() {
b9934a17
DM
1899 global $DB;
1900
1901 $modulesinfo = self::get_modules_info();
1902 if (isset($modulesinfo[$this->name]->version)) {
1903 $this->versiondb = $modulesinfo[$this->name]->version;
1904 }
1905 }
1906
b9934a17
DM
1907 public function is_enabled() {
1908
1909 $modulesinfo = self::get_modules_info();
1910 if (isset($modulesinfo[$this->name]->visible)) {
1911 if ($modulesinfo[$this->name]->visible) {
1912 return true;
1913 } else {
1914 return false;
1915 }
1916 } else {
1917 return parent::is_enabled();
1918 }
1919 }
1920
b9934a17
DM
1921 public function get_settings_url() {
1922
6740c605 1923 if (file_exists($this->full_path('settings.php')) or file_exists($this->full_path('settingstree.php'))) {
b9934a17
DM
1924 return new moodle_url('/admin/settings.php', array('section' => 'modsetting' . $this->name));
1925 } else {
1926 return parent::get_settings_url();
1927 }
1928 }
1929
b9934a17
DM
1930 public function get_uninstall_url() {
1931
1932 if ($this->name !== 'forum') {
1933 return new moodle_url('/admin/modules.php', array('delete' => $this->name, 'sesskey' => sesskey()));
1934 } else {
1935 return null;
1936 }
1937 }
1938
1939 /**
1940 * Provides access to the records in {modules} table
1941 *
1942 * @param bool $disablecache do not use internal static cache
1943 * @return array array of stdClasses
1944 */
1945 protected static function get_modules_info($disablecache=false) {
1946 global $DB;
1947 static $modulesinfocache = null;
1948
1949 if (is_null($modulesinfocache) or $disablecache) {
f433088d
PS
1950 try {
1951 $modulesinfocache = $DB->get_records('modules', null, 'name', 'name,id,version,visible');
1952 } catch (dml_exception $e) {
1953 // before install
1954 $modulesinfocache = array();
1955 }
b9934a17
DM
1956 }
1957
1958 return $modulesinfocache;
1959 }
1960}
1961
0242bdc7
TH
1962
1963/**
1964 * Class for question behaviours.
1965 */
b6ad8594
DM
1966class plugininfo_qbehaviour extends plugininfo_base {
1967
828788f0
TH
1968 public function get_uninstall_url() {
1969 return new moodle_url('/admin/qbehaviours.php',
1970 array('delete' => $this->name, 'sesskey' => sesskey()));
1971 }
0242bdc7
TH
1972}
1973
1974
b9934a17
DM
1975/**
1976 * Class for question types
1977 */
b6ad8594
DM
1978class plugininfo_qtype extends plugininfo_base {
1979
828788f0
TH
1980 public function get_uninstall_url() {
1981 return new moodle_url('/admin/qtypes.php',
1982 array('delete' => $this->name, 'sesskey' => sesskey()));
1983 }
b9934a17
DM
1984}
1985
b9934a17
DM
1986
1987/**
1988 * Class for authentication plugins
1989 */
b6ad8594 1990class plugininfo_auth extends plugininfo_base {
b9934a17 1991
b9934a17
DM
1992 public function is_enabled() {
1993 global $CFG;
1994 /** @var null|array list of enabled authentication plugins */
1995 static $enabled = null;
1996
1997 if (in_array($this->name, array('nologin', 'manual'))) {
1998 // these two are always enabled and can't be disabled
1999 return null;
2000 }
2001
2002 if (is_null($enabled)) {
d5d181f5 2003 $enabled = array_flip(explode(',', $CFG->auth));
b9934a17
DM
2004 }
2005
2006 return isset($enabled[$this->name]);
2007 }
2008
b9934a17 2009 public function get_settings_url() {
6740c605 2010 if (file_exists($this->full_path('settings.php'))) {
b9934a17
DM
2011 return new moodle_url('/admin/settings.php', array('section' => 'authsetting' . $this->name));
2012 } else {
2013 return new moodle_url('/admin/auth_config.php', array('auth' => $this->name));
2014 }
2015 }
2016}
2017
b6ad8594 2018
b9934a17
DM
2019/**
2020 * Class for enrolment plugins
2021 */
b6ad8594 2022class plugininfo_enrol extends plugininfo_base {
b9934a17 2023
b9934a17
DM
2024 public function is_enabled() {
2025 global $CFG;
2026 /** @var null|array list of enabled enrolment plugins */
2027 static $enabled = null;
2028
b6ad8594
DM
2029 // We do not actually need whole enrolment classes here so we do not call
2030 // {@link enrol_get_plugins()}. Note that this may produce slightly different
2031 // results, for example if the enrolment plugin does not contain lib.php
2032 // but it is listed in $CFG->enrol_plugins_enabled
2033
b9934a17 2034 if (is_null($enabled)) {
d5d181f5 2035 $enabled = array_flip(explode(',', $CFG->enrol_plugins_enabled));
b9934a17
DM
2036 }
2037
2038 return isset($enabled[$this->name]);
2039 }
2040
b9934a17
DM
2041 public function get_settings_url() {
2042
6740c605 2043 if ($this->is_enabled() or file_exists($this->full_path('settings.php'))) {
b9934a17
DM
2044 return new moodle_url('/admin/settings.php', array('section' => 'enrolsettings' . $this->name));
2045 } else {
2046 return parent::get_settings_url();
2047 }
2048 }
2049
b9934a17
DM
2050 public function get_uninstall_url() {
2051 return new moodle_url('/admin/enrol.php', array('action' => 'uninstall', 'enrol' => $this->name, 'sesskey' => sesskey()));
2052 }
2053}
2054
b6ad8594 2055
b9934a17
DM
2056/**
2057 * Class for messaging processors
2058 */
b6ad8594 2059class plugininfo_message extends plugininfo_base {
b9934a17 2060
b9934a17
DM
2061 public function get_settings_url() {
2062
6740c605
TH
2063 if (file_exists($this->full_path('settings.php')) or file_exists($this->full_path('settingstree.php'))) {
2064 return new moodle_url('/admin/settings.php', array('section' => 'messagesetting' . $this->name));
2065 } else {
2066 return parent::get_settings_url();
b9934a17 2067 }
b9934a17
DM
2068 }
2069}
2070
b6ad8594 2071
b9934a17
DM
2072/**
2073 * Class for repositories
2074 */
b6ad8594 2075class plugininfo_repository extends plugininfo_base {
b9934a17 2076
b9934a17
DM
2077 public function is_enabled() {
2078
2079 $enabled = self::get_enabled_repositories();
2080
2081 return isset($enabled[$this->name]);
2082 }
2083
b9934a17
DM
2084 public function get_settings_url() {
2085
2086 if ($this->is_enabled()) {
2087 return new moodle_url('/admin/repository.php', array('sesskey' => sesskey(), 'action' => 'edit', 'repos' => $this->name));
2088 } else {
2089 return parent::get_settings_url();
2090 }
2091 }
2092
2093 /**
2094 * Provides access to the records in {repository} table
2095 *
2096 * @param bool $disablecache do not use internal static cache
2097 * @return array array of stdClasses
2098 */
2099 protected static function get_enabled_repositories($disablecache=false) {
2100 global $DB;
2101 static $repositories = null;
2102
2103 if (is_null($repositories) or $disablecache) {
2104 $repositories = $DB->get_records('repository', null, 'type', 'type,visible,sortorder');
2105 }
2106
2107 return $repositories;
2108 }
2109}
2110
b6ad8594 2111
b9934a17
DM
2112/**
2113 * Class for portfolios
2114 */
b6ad8594 2115class plugininfo_portfolio extends plugininfo_base {
b9934a17 2116
b9934a17
DM
2117 public function is_enabled() {
2118
2119 $enabled = self::get_enabled_portfolios();
2120
2121 return isset($enabled[$this->name]);
2122 }
2123
2124 /**
2125 * Provides access to the records in {portfolio_instance} table
2126 *
2127 * @param bool $disablecache do not use internal static cache
2128 * @return array array of stdClasses
2129 */
2130 protected static function get_enabled_portfolios($disablecache=false) {
2131 global $DB;
2132 static $portfolios = null;
2133
2134 if (is_null($portfolios) or $disablecache) {
2135 $portfolios = array();
2136 $instances = $DB->get_recordset('portfolio_instance', null, 'plugin');
2137 foreach ($instances as $instance) {
2138 if (isset($portfolios[$instance->plugin])) {
2139 if ($instance->visible) {
2140 $portfolios[$instance->plugin]->visible = $instance->visible;
2141 }
2142 } else {
2143 $portfolios[$instance->plugin] = $instance;
2144 }
2145 }
2146 }
2147
2148 return $portfolios;
2149 }
2150}
2151
b6ad8594 2152
b9934a17
DM
2153/**
2154 * Class for themes
2155 */
b6ad8594 2156class plugininfo_theme extends plugininfo_base {
b9934a17 2157
b9934a17
DM
2158 public function is_enabled() {
2159 global $CFG;
2160
2161 if ((!empty($CFG->theme) and $CFG->theme === $this->name) or
2162 (!empty($CFG->themelegacy) and $CFG->themelegacy === $this->name)) {
2163 return true;
2164 } else {
2165 return parent::is_enabled();
2166 }
2167 }
2168}
2169
b6ad8594 2170
b9934a17
DM
2171/**
2172 * Class representing an MNet service
2173 */
b6ad8594 2174class plugininfo_mnetservice extends plugininfo_base {
b9934a17 2175
b9934a17
DM
2176 public function is_enabled() {
2177 global $CFG;
2178
2179 if (empty($CFG->mnet_dispatcher_mode) || $CFG->mnet_dispatcher_mode !== 'strict') {
2180 return false;
2181 } else {
2182 return parent::is_enabled();
2183 }
2184 }
2185}
3cdfaeef 2186
b6ad8594 2187
3cdfaeef
PS
2188/**
2189 * Class for admin tool plugins
2190 */
b6ad8594 2191class plugininfo_tool extends plugininfo_base {
3cdfaeef
PS
2192
2193 public function get_uninstall_url() {
2194 return new moodle_url('/admin/tools.php', array('delete' => $this->name, 'sesskey' => sesskey()));
2195 }
2196}
4f6bba20 2197
b6ad8594 2198
4f6bba20
PS
2199/**
2200 * Class for admin tool plugins
2201 */
b6ad8594 2202class plugininfo_report extends plugininfo_base {
4f6bba20
PS
2203
2204 public function get_uninstall_url() {
2205 return new moodle_url('/admin/reports.php', array('delete' => $this->name, 'sesskey' => sesskey()));
2206 }
2207}