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