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