MDL-20438 The Plugins overview admin page now provides a way to check for updates
[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) {
126 $plugininfoholder->check_available_update($provider);
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;
575 /** @var null|string numerical version of the local Moodle site */
576 protected $localversion = null;
577 /** @var null|string release signature of the local Moodle site */
578 protected $localrelease = null;
579 /** @var array of (string)frankestyle => (string)version list of plugins installed at the local Moodle site */
580 protected $localplugins = array();
581 /** @var null|stdClass */
582 protected $config = null;
583
584 /**
585 * Direct initiation not allowed, use the factory method {@link self::instance()}
586 */
587 protected function __construct() {
588 $this->load_config();
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
609 /**
610 * Sets the local version
611 *
612 * If the version is set before the request is done, the version info will
613 * be sent to the remote site as a part of the request. The returned data can
614 * be filtered so that they contain just information relevant to the sent
615 * version.
616 *
617 * @param string $version our local Moodle version, usually $CFG->version
618 */
619 public function set_local_version($version) {
620 $this->localversion = $version;
621 }
622
623 /**
624 * Sets the local release info
625 *
626 * If the release is set before the request is done, the release info will
627 * be sent to the remote site as a part of the request. The returned data can
628 * be filtered so that they contain just information relevant to the sent
629 * release/build.
630 *
631 * @param string $version our local Moodle version, usually $CFG->release
632 */
633 public function set_local_release($release) {
634 $this->localrelease = $release;
635 }
636
637 /**
638 * Sets the list of plugins and their version at the local Moodle site
639 *
640 * The keys of the passed array are frankenstyle component names of plugins. The
641 * values are the on-disk versions of these plugins (allowing the null value where
642 * the version can't be obtained for any reason).
643 * If the plugins are set before the request is done, their list will be sent
644 * to the remote site as a part of the request. The returned data can
645 * be filtered so that they contain just information about the installed plugins.
646 * To obtain a list of all available plugins, do not set this list prior to calling
647 * {@link self::fetch()}
648 *
649 * @param array $plugins of (string)component => (string)version
650 */
651 public function set_local_plugins(array $plugins) {
652 $this->localplugins = $plugins;
653 }
654
655 /**
656 * Returns the timestamp of the last execution of {@link fetch()}
657 *
658 * @return int|null null if it has never been executed or we don't known
659 */
660 public function get_last_timefetched() {
661 if (isset($this->config->timelastfetched)) {
662 return $this->config->timelastfetched;
663 } else {
664 return false;
665 }
666 }
667
668 /**
669 * Fetches the available update status from the remote site
670 *
671 * @throws available_update_checker_exception
672 */
673 public function fetch() {
674 $response = $this->make_request();
675 $this->validate_response($response);
676 $this->process_response($response);
677 }
678
679 /**
680 * Returns the available update information for the given component
681 *
682 * This method returns null if the most recent response does not contain any information
683 * about it. Note that this does not mean that the information is not provided by the
684 * remote site. The recent request might be specific
685 *
686 * @param string $component frankenstyle
687 * @return null|stdClass null if the most recent response does not provide any info
688 */
689 public function get_update_info($component) {
690
691 $branch = moodle_major_version();
692
693 if (!empty($this->config->components->$branch->$component)) {
694 return $this->config->components->$branch->$component;
695 } else {
696 return null;
697 }
698 }
699
700 /**
701 * Executes cURL request to get data from the remote site
702 *
703 * @return stdClass request result
704 * @throws available_update_checker_exception
705 */
706 protected function make_request() {
707 $curl = new curl(array('proxy' => true));
708 $response = $curl->post($this->prepare_request_url(), $this->prepare_request_params());
709 $curlinfo = $curl->get_info();
710 if ($curlinfo['http_code'] != 200) {
711 throw new available_update_checker_exception('err_response_http_code', $curlinfo['http_code']);
712 }
713 $response = json_decode($response);
714 return $response;
715 }
716
717 /**
718 * Makes sure the response is valid, has correct API format etc.
719 *
720 * @param stdClass $response
721 * @throws available_update_checker_exception
722 */
723 protected function validate_response(stdClass $response) {
724
725 if (empty($response)) {
726 throw new available_update_checker_exception('err_response_empty');
727 }
728
729 if (empty($response->status) or $response->status !== 'OK') {
730 throw new available_update_checker_exception('err_response_status', $response->status);
731 }
732
733 if (empty($response->apiver) or $response->apiver != '1.0') {
734 throw new available_update_checker_exception('err_response_format_version', $response->apiver);
735 }
736 }
737
738 /**
739 * Stores the fetched response for later usage
740 *
741 * This implementation uses the config_plugins table as the permanent storage.
742 *
743 * @param stdClass $response the data returned by the updates info provider
744 */
745 protected function process_response(stdClass $response) {
746
747 $components = $this->merge_components_info($this->config->components, $response->components, $response->timegenerated);
748
749 set_config('timelastfetched', time(), 'core_plugin');
750 set_config('ticket', $response->ticket, 'core_plugin');
751 set_config('components', json_encode($components), 'core_plugin');
752
753 $this->load_config(true);
754 }
755
756 /**
757 * Merges the current and the new info about the available updates
758 *
759 * @param stdClass $old
760 * @param stdClass $new
761 * @param int $timegenerated the timestamp of when the $new was generated
762 * @return stdClass merged
763 */
764 protected function merge_components_info(stdClass $old, stdClass $new, $timegenerated=null) {
765 $merged = clone($old);
766 if (is_null($timegenerated)) {
767 $timegenerated = time();
768 }
769 foreach ($new as $branch => $components) {
770 foreach ($components as $component => $info) {
771 $info->timegenerated = $timegenerated;
772 if (isset($info->version)) {
773 $merged->$branch->$component = $info;
774 }
775 }
776 }
777 return $merged;
778 }
779
780 /**
781 * Loads the core_plugin subsystem config
782 *
783 * @param bool $forcereload reload the config even if it was already loaded
784 */
785 protected function load_config($forcereload = false) {
786 if ($forcereload or is_null($this->config)) {
787 $this->config = get_config('core_plugin');
788 }
789
790 if (empty($this->config->components)) {
791 $this->config->components = new stdClass();
792 } else {
793 $this->config->components = json_decode($this->config->components);
794 }
795 }
796
797 /**
798 * Returns the URL to send update requests to
799 *
800 * During the development or testing, you can set $CFG->alternativeupdateproviderurl
801 * to a custom URL that will be used. Otherwise the standard URL will be returned.
802 *
803 * @return string URL
804 */
805 protected function prepare_request_url() {
806 global $CFG;
807
808 if (!empty($CFG->alternativeupdateproviderurl)) {
809 return $CFG->alternativeupdateproviderurl;
810 } else {
811 return 'http://download.moodle.org/api/1.0/updates.php';
812 }
813 }
814
815 /**
816 * Returns the list of HTTP params to be sent to the updates provider URL
817 *
818 * @return array of (string)param => (string)value
819 */
820 protected function prepare_request_params() {
821 global $CFG;
822
823 $params = array();
824 $params['format'] = 'json';
825
826 if (isset($this->config->ticket)) {
827 $params['ticket'] = $this->config->ticket;
828 }
829
830 if (isset($this->localversion)) {
831 $params['version'] = $this->localversion;
832 }
833
834 if (isset($this->localrelease)) {
835 $params['release'] = $this->localrelease;
836 }
837
838 // todo localplugins
839 return $params;
840 }
841}
842
843
00ef3c3e
DM
844/**
845 * Factory class producing required subclasses of {@link plugininfo_base}
846 */
847class plugininfo_default_factory {
848
849 /**
850 * Makes a new instance of the plugininfo class
851 *
852 * @param string $type the plugin type, eg. 'mod'
853 * @param string $typerootdir full path to the location of all the plugins of this type
854 * @param string $name the plugin name, eg. 'workshop'
855 * @param string $namerootdir full path to the location of the plugin
856 * @param string $typeclass the name of class that holds the info about the plugin
857 * @return plugininfo_base the instance of $typeclass
858 */
859 public static function make($type, $typerootdir, $name, $namerootdir, $typeclass) {
860 $plugin = new $typeclass();
861 $plugin->type = $type;
862 $plugin->typerootdir = $typerootdir;
863 $plugin->name = $name;
864 $plugin->rootdir = $namerootdir;
865
866 $plugin->init_display_name();
867 $plugin->load_disk_version();
868 $plugin->load_db_version();
869 $plugin->load_required_main_version();
870 $plugin->init_is_standard();
871
872 return $plugin;
873 }
874}
875
876
b9934a17 877/**
b6ad8594 878 * Base class providing access to the information about a plugin
828788f0
TH
879 *
880 * @property-read string component the component name, type_name
b9934a17 881 */
b6ad8594 882abstract class plugininfo_base {
b9934a17
DM
883
884 /** @var string the plugintype name, eg. mod, auth or workshopform */
885 public $type;
886 /** @var string full path to the location of all the plugins of this type */
887 public $typerootdir;
888 /** @var string the plugin name, eg. assignment, ldap */
889 public $name;
890 /** @var string the localized plugin name */
891 public $displayname;
892 /** @var string the plugin source, one of plugin_manager::PLUGIN_SOURCE_xxx constants */
893 public $source;
894 /** @var fullpath to the location of this plugin */
895 public $rootdir;
896 /** @var int|string the version of the plugin's source code */
897 public $versiondisk;
898 /** @var int|string the version of the installed plugin */
899 public $versiondb;
900 /** @var int|float|string required version of Moodle core */
901 public $versionrequires;
b6ad8594
DM
902 /** @var array other plugins that this one depends on, lazy-loaded by {@link get_other_required_plugins()} */
903 public $dependencies;
b9934a17
DM
904 /** @var int number of instances of the plugin - not supported yet */
905 public $instances;
906 /** @var int order of the plugin among other plugins of the same type - not supported yet */
907 public $sortorder;
dd119e21
DM
908 /** @var null|stdClass holds the information about the remote available update for this plugin */
909 public $availableupdate;
b9934a17
DM
910
911 /**
b6ad8594
DM
912 * Gathers and returns the information about all plugins of the given type
913 *
b6ad8594
DM
914 * @param string $type the name of the plugintype, eg. mod, auth or workshopform
915 * @param string $typerootdir full path to the location of the plugin dir
916 * @param string $typeclass the name of the actually called class
917 * @return array of plugintype classes, indexed by the plugin name
b9934a17
DM
918 */
919 public static function get_plugins($type, $typerootdir, $typeclass) {
920
921 // get the information about plugins at the disk
922 $plugins = get_plugin_list($type);
923 $ondisk = array();
924 foreach ($plugins as $pluginname => $pluginrootdir) {
00ef3c3e
DM
925 $ondisk[$pluginname] = plugininfo_default_factory::make($type, $typerootdir,
926 $pluginname, $pluginrootdir, $typeclass);
b9934a17
DM
927 }
928 return $ondisk;
929 }
930
931 /**
b6ad8594 932 * Sets {@link $displayname} property to a localized name of the plugin
b9934a17 933 */
b8343e68 934 public function init_display_name() {
828788f0
TH
935 if (!get_string_manager()->string_exists('pluginname', $this->component)) {
936 $this->displayname = '[pluginname,' . $this->component . ']';
b9934a17 937 } else {
828788f0
TH
938 $this->displayname = get_string('pluginname', $this->component);
939 }
940 }
941
942 /**
943 * Magic method getter, redirects to read only values.
b6ad8594 944 *
828788f0
TH
945 * @param string $name
946 * @return mixed
947 */
948 public function __get($name) {
949 switch ($name) {
950 case 'component': return $this->type . '_' . $this->name;
951
952 default:
953 debugging('Invalid plugin property accessed! '.$name);
954 return null;
b9934a17
DM
955 }
956 }
957
958 /**
b6ad8594
DM
959 * Return the full path name of a file within the plugin.
960 *
961 * No check is made to see if the file exists.
962 *
963 * @param string $relativepath e.g. 'version.php'.
964 * @return string e.g. $CFG->dirroot . '/mod/quiz/version.php'.
b9934a17 965 */
473289a0 966 public function full_path($relativepath) {
b9934a17 967 if (empty($this->rootdir)) {
473289a0 968 return '';
b9934a17 969 }
473289a0
TH
970 return $this->rootdir . '/' . $relativepath;
971 }
b9934a17 972
473289a0
TH
973 /**
974 * Load the data from version.php.
b6ad8594
DM
975 *
976 * @return stdClass the object called $plugin defined in version.php
473289a0
TH
977 */
978 protected function load_version_php() {
979 $versionfile = $this->full_path('version.php');
b9934a17 980
473289a0 981 $plugin = new stdClass();
b9934a17
DM
982 if (is_readable($versionfile)) {
983 include($versionfile);
b9934a17 984 }
473289a0 985 return $plugin;
b9934a17
DM
986 }
987
988 /**
b6ad8594
DM
989 * Sets {@link $versiondisk} property to a numerical value representing the
990 * version of the plugin's source code.
991 *
992 * If the value is null after calling this method, either the plugin
993 * does not use versioning (typically does not have any database
994 * data) or is missing from disk.
b9934a17 995 */
473289a0
TH
996 public function load_disk_version() {
997 $plugin = $this->load_version_php();
998 if (isset($plugin->version)) {
999 $this->versiondisk = $plugin->version;
b9934a17
DM
1000 }
1001 }
1002
1003 /**
b6ad8594
DM
1004 * Sets {@link $versionrequires} property to a numerical value representing
1005 * the version of Moodle core that this plugin requires.
b9934a17 1006 */
b8343e68 1007 public function load_required_main_version() {
473289a0
TH
1008 $plugin = $this->load_version_php();
1009 if (isset($plugin->requires)) {
1010 $this->versionrequires = $plugin->requires;
b9934a17 1011 }
473289a0 1012 }
b9934a17 1013
0242bdc7 1014 /**
777781d1 1015 * Initialise {@link $dependencies} to the list of other plugins (in any)
0242bdc7
TH
1016 * that this one requires to be installed.
1017 */
1018 protected function load_other_required_plugins() {
1019 $plugin = $this->load_version_php();
777781d1
TH
1020 if (!empty($plugin->dependencies)) {
1021 $this->dependencies = $plugin->dependencies;
0242bdc7 1022 } else {
777781d1 1023 $this->dependencies = array(); // By default, no dependencies.
0242bdc7
TH
1024 }
1025 }
1026
1027 /**
b6ad8594
DM
1028 * Get the list of other plugins that this plugin requires to be installed.
1029 *
1030 * @return array with keys the frankenstyle plugin name, and values either
1031 * a version string (like '2011101700') or the constant ANY_VERSION.
0242bdc7
TH
1032 */
1033 public function get_other_required_plugins() {
777781d1 1034 if (is_null($this->dependencies)) {
0242bdc7
TH
1035 $this->load_other_required_plugins();
1036 }
777781d1 1037 return $this->dependencies;
0242bdc7
TH
1038 }
1039
473289a0 1040 /**
b6ad8594
DM
1041 * Sets {@link $versiondb} property to a numerical value representing the
1042 * currently installed version of the plugin.
1043 *
1044 * If the value is null after calling this method, either the plugin
1045 * does not use versioning (typically does not have any database
1046 * data) or has not been installed yet.
473289a0
TH
1047 */
1048 public function load_db_version() {
828788f0 1049 if ($ver = self::get_version_from_config_plugins($this->component)) {
473289a0 1050 $this->versiondb = $ver;
b9934a17
DM
1051 }
1052 }
1053
1054 /**
b6ad8594
DM
1055 * Sets {@link $source} property to one of plugin_manager::PLUGIN_SOURCE_xxx
1056 * constants.
1057 *
1058 * If the property's value is null after calling this method, then
1059 * the type of the plugin has not been recognized and you should throw
1060 * an exception.
b9934a17 1061 */
b8343e68 1062 public function init_is_standard() {
b9934a17
DM
1063
1064 $standard = plugin_manager::standard_plugins_list($this->type);
1065
1066 if ($standard !== false) {
1067 $standard = array_flip($standard);
1068 if (isset($standard[$this->name])) {
1069 $this->source = plugin_manager::PLUGIN_SOURCE_STANDARD;
ec8935f5
PS
1070 } else if (!is_null($this->versiondb) and is_null($this->versiondisk)
1071 and plugin_manager::is_deleted_standard_plugin($this->type, $this->name)) {
1072 $this->source = plugin_manager::PLUGIN_SOURCE_STANDARD; // to be deleted
b9934a17
DM
1073 } else {
1074 $this->source = plugin_manager::PLUGIN_SOURCE_EXTENSION;
1075 }
1076 }
1077 }
1078
1079 /**
b6ad8594
DM
1080 * Returns true if the plugin is shipped with the official distribution
1081 * of the current Moodle version, false otherwise.
1082 *
1083 * @return bool
b9934a17
DM
1084 */
1085 public function is_standard() {
1086 return $this->source === plugin_manager::PLUGIN_SOURCE_STANDARD;
1087 }
1088
1089 /**
b6ad8594
DM
1090 * Returns the status of the plugin
1091 *
1092 * @return string one of plugin_manager::PLUGIN_STATUS_xxx constants
b9934a17
DM
1093 */
1094 public function get_status() {
1095
1096 if (is_null($this->versiondb) and is_null($this->versiondisk)) {
1097 return plugin_manager::PLUGIN_STATUS_NODB;
1098
1099 } else if (is_null($this->versiondb) and !is_null($this->versiondisk)) {
1100 return plugin_manager::PLUGIN_STATUS_NEW;
1101
1102 } else if (!is_null($this->versiondb) and is_null($this->versiondisk)) {
ec8935f5
PS
1103 if (plugin_manager::is_deleted_standard_plugin($this->type, $this->name)) {
1104 return plugin_manager::PLUGIN_STATUS_DELETE;
1105 } else {
1106 return plugin_manager::PLUGIN_STATUS_MISSING;
1107 }
b9934a17
DM
1108
1109 } else if ((string)$this->versiondb === (string)$this->versiondisk) {
1110 return plugin_manager::PLUGIN_STATUS_UPTODATE;
1111
1112 } else if ($this->versiondb < $this->versiondisk) {
1113 return plugin_manager::PLUGIN_STATUS_UPGRADE;
1114
1115 } else if ($this->versiondb > $this->versiondisk) {
1116 return plugin_manager::PLUGIN_STATUS_DOWNGRADE;
1117
1118 } else {
1119 // $version = pi(); and similar funny jokes - hopefully Donald E. Knuth will never contribute to Moodle ;-)
1120 throw new coding_exception('Unable to determine plugin state, check the plugin versions');
1121 }
1122 }
1123
1124 /**
b6ad8594
DM
1125 * Returns the information about plugin availability
1126 *
1127 * True means that the plugin is enabled. False means that the plugin is
1128 * disabled. Null means that the information is not available, or the
1129 * plugin does not support configurable availability or the availability
1130 * can not be changed.
1131 *
1132 * @return null|bool
b9934a17
DM
1133 */
1134 public function is_enabled() {
1135 return null;
1136 }
1137
dd119e21
DM
1138 /**
1139 * Populates the property {@link $availableupdate} with the information provided by
1140 * available update checker
1141 *
1142 * @param available_update_checker $provider the class providing the available update info
1143 */
1144 public function check_available_update(available_update_checker $provider) {
1145 $this->availableupdate = $provider->get_update_info($this->component);
1146 }
1147
d26f3ddd
DM
1148 /**
1149 * If there is an update of this plugin available, returns the data about it.
1150 *
1151 * Returns object with various properties about the available update, if such
1152 * an update is available. Returns false if there is no update available for
1153 * this plugin. Returns null if the update availabitlity is unknown.
1154 *
1155 * @return stdClass|false|null
1156 */
1157 public function available_update() {
dd119e21
DM
1158
1159 if (empty($this->availableupdate)) {
1160 return null;
1161 }
1162
1163 if ($this->availableupdate->version > $this->versiondisk) {
1164 return $this->availableupdate;
1165 }
1166
1167 return false;
d26f3ddd
DM
1168 }
1169
b9934a17 1170 /**
b6ad8594
DM
1171 * Returns the URL of the plugin settings screen
1172 *
1173 * Null value means that the plugin either does not have the settings screen
1174 * or its location is not available via this library.
1175 *
1176 * @return null|moodle_url
b9934a17
DM
1177 */
1178 public function get_settings_url() {
1179 return null;
1180 }
1181
1182 /**
b6ad8594
DM
1183 * Returns the URL of the screen where this plugin can be uninstalled
1184 *
1185 * Visiting that URL must be safe, that is a manual confirmation is needed
1186 * for actual uninstallation of the plugin. Null value means that the
1187 * plugin either does not support uninstallation, or does not require any
1188 * database cleanup or the location of the screen is not available via this
1189 * library.
1190 *
1191 * @return null|moodle_url
b9934a17
DM
1192 */
1193 public function get_uninstall_url() {
1194 return null;
1195 }
1196
1197 /**
b6ad8594
DM
1198 * Returns relative directory of the plugin with heading '/'
1199 *
1200 * @return string
b9934a17
DM
1201 */
1202 public function get_dir() {
1203 global $CFG;
1204
1205 return substr($this->rootdir, strlen($CFG->dirroot));
1206 }
1207
1208 /**
1209 * Provides access to plugin versions from {config_plugins}
1210 *
1211 * @param string $plugin plugin name
1212 * @param double $disablecache optional, defaults to false
1213 * @return int|false the stored value or false if not found
1214 */
1215 protected function get_version_from_config_plugins($plugin, $disablecache=false) {
1216 global $DB;
1217 static $pluginversions = null;
1218
1219 if (is_null($pluginversions) or $disablecache) {
f433088d
PS
1220 try {
1221 $pluginversions = $DB->get_records_menu('config_plugins', array('name' => 'version'), 'plugin', 'plugin,value');
1222 } catch (dml_exception $e) {
1223 // before install
1224 $pluginversions = array();
1225 }
b9934a17
DM
1226 }
1227
1228 if (!array_key_exists($plugin, $pluginversions)) {
1229 return false;
1230 }
1231
1232 return $pluginversions[$plugin];
1233 }
1234}
1235
b6ad8594 1236
b9934a17
DM
1237/**
1238 * General class for all plugin types that do not have their own class
1239 */
b6ad8594 1240class plugininfo_general extends plugininfo_base {
b9934a17
DM
1241}
1242
b6ad8594 1243
b9934a17
DM
1244/**
1245 * Class for page side blocks
1246 */
b6ad8594 1247class plugininfo_block extends plugininfo_base {
b9934a17 1248
b9934a17
DM
1249 public static function get_plugins($type, $typerootdir, $typeclass) {
1250
1251 // get the information about blocks at the disk
1252 $blocks = parent::get_plugins($type, $typerootdir, $typeclass);
1253
1254 // add blocks missing from disk
1255 $blocksinfo = self::get_blocks_info();
1256 foreach ($blocksinfo as $blockname => $blockinfo) {
1257 if (isset($blocks[$blockname])) {
1258 continue;
1259 }
1260 $plugin = new $typeclass();
1261 $plugin->type = $type;
1262 $plugin->typerootdir = $typerootdir;
1263 $plugin->name = $blockname;
1264 $plugin->rootdir = null;
1265 $plugin->displayname = $blockname;
1266 $plugin->versiondb = $blockinfo->version;
b8343e68 1267 $plugin->init_is_standard();
b9934a17
DM
1268
1269 $blocks[$blockname] = $plugin;
1270 }
1271
1272 return $blocks;
1273 }
1274
b8343e68 1275 public function init_display_name() {
b9934a17
DM
1276
1277 if (get_string_manager()->string_exists('pluginname', 'block_' . $this->name)) {
1278 $this->displayname = get_string('pluginname', 'block_' . $this->name);
1279
1280 } else if (($block = block_instance($this->name)) !== false) {
1281 $this->displayname = $block->get_title();
1282
1283 } else {
b8343e68 1284 parent::init_display_name();
b9934a17
DM
1285 }
1286 }
1287
b8343e68 1288 public function load_db_version() {
b9934a17
DM
1289 global $DB;
1290
1291 $blocksinfo = self::get_blocks_info();
1292 if (isset($blocksinfo[$this->name]->version)) {
1293 $this->versiondb = $blocksinfo[$this->name]->version;
1294 }
1295 }
1296
b9934a17
DM
1297 public function is_enabled() {
1298
1299 $blocksinfo = self::get_blocks_info();
1300 if (isset($blocksinfo[$this->name]->visible)) {
1301 if ($blocksinfo[$this->name]->visible) {
1302 return true;
1303 } else {
1304 return false;
1305 }
1306 } else {
1307 return parent::is_enabled();
1308 }
1309 }
1310
b9934a17
DM
1311 public function get_settings_url() {
1312
1313 if (($block = block_instance($this->name)) === false) {
1314 return parent::get_settings_url();
1315
1316 } else if ($block->has_config()) {
6740c605 1317 if (file_exists($this->full_path('settings.php'))) {
b9934a17
DM
1318 return new moodle_url('/admin/settings.php', array('section' => 'blocksetting' . $this->name));
1319 } else {
1320 $blocksinfo = self::get_blocks_info();
1321 return new moodle_url('/admin/block.php', array('block' => $blocksinfo[$this->name]->id));
1322 }
1323
1324 } else {
1325 return parent::get_settings_url();
1326 }
1327 }
1328
b9934a17
DM
1329 public function get_uninstall_url() {
1330
1331 $blocksinfo = self::get_blocks_info();
1332 return new moodle_url('/admin/blocks.php', array('delete' => $blocksinfo[$this->name]->id, 'sesskey' => sesskey()));
1333 }
1334
1335 /**
1336 * Provides access to the records in {block} table
1337 *
1338 * @param bool $disablecache do not use internal static cache
1339 * @return array array of stdClasses
1340 */
1341 protected static function get_blocks_info($disablecache=false) {
1342 global $DB;
1343 static $blocksinfocache = null;
1344
1345 if (is_null($blocksinfocache) or $disablecache) {
f433088d
PS
1346 try {
1347 $blocksinfocache = $DB->get_records('block', null, 'name', 'name,id,version,visible');
1348 } catch (dml_exception $e) {
1349 // before install
1350 $blocksinfocache = array();
1351 }
b9934a17
DM
1352 }
1353
1354 return $blocksinfocache;
1355 }
1356}
1357
b6ad8594 1358
b9934a17
DM
1359/**
1360 * Class for text filters
1361 */
b6ad8594 1362class plugininfo_filter extends plugininfo_base {
b9934a17 1363
b9934a17 1364 public static function get_plugins($type, $typerootdir, $typeclass) {
7c9b837e 1365 global $CFG, $DB;
b9934a17
DM
1366
1367 $filters = array();
1368
1369 // get the list of filters from both /filter and /mod location
1370 $installed = filter_get_all_installed();
1371
1372 foreach ($installed as $filterlegacyname => $displayname) {
1373 $plugin = new $typeclass();
1374 $plugin->type = $type;
1375 $plugin->typerootdir = $typerootdir;
1376 $plugin->name = self::normalize_legacy_name($filterlegacyname);
1377 $plugin->rootdir = $CFG->dirroot . '/' . $filterlegacyname;
1378 $plugin->displayname = $displayname;
1379
b8343e68
TH
1380 $plugin->load_disk_version();
1381 $plugin->load_db_version();
1382 $plugin->load_required_main_version();
1383 $plugin->init_is_standard();
b9934a17
DM
1384
1385 $filters[$plugin->name] = $plugin;
1386 }
1387
b9934a17 1388 $globalstates = self::get_global_states();
7c9b837e
DM
1389
1390 if ($DB->get_manager()->table_exists('filter_active')) {
1391 // if we're upgrading from 1.9, the table does not exist yet
1392 // if it does, make sure that all installed filters are registered
1393 $needsreload = false;
1394 foreach (array_keys($installed) as $filterlegacyname) {
1395 if (!isset($globalstates[self::normalize_legacy_name($filterlegacyname)])) {
1396 filter_set_global_state($filterlegacyname, TEXTFILTER_DISABLED);
1397 $needsreload = true;
1398 }
1399 }
1400 if ($needsreload) {
1401 $globalstates = self::get_global_states(true);
b9934a17 1402 }
b9934a17
DM
1403 }
1404
1405 // make sure that all registered filters are installed, just in case
1406 foreach ($globalstates as $name => $info) {
1407 if (!isset($filters[$name])) {
1408 // oops, there is a record in filter_active but the filter is not installed
1409 $plugin = new $typeclass();
1410 $plugin->type = $type;
1411 $plugin->typerootdir = $typerootdir;
1412 $plugin->name = $name;
1413 $plugin->rootdir = $CFG->dirroot . '/' . $info->legacyname;
1414 $plugin->displayname = $info->legacyname;
1415
b8343e68 1416 $plugin->load_db_version();
b9934a17
DM
1417
1418 if (is_null($plugin->versiondb)) {
1419 // this is a hack to stimulate 'Missing from disk' error
1420 // because $plugin->versiondisk will be null !== false
1421 $plugin->versiondb = false;
1422 }
1423
1424 $filters[$plugin->name] = $plugin;
1425 }
1426 }
1427
1428 return $filters;
1429 }
1430
b8343e68 1431 public function init_display_name() {
b9934a17
DM
1432 // do nothing, the name is set in self::get_plugins()
1433 }
1434
1435 /**
b6ad8594 1436 * @see load_version_php()
b9934a17 1437 */
473289a0 1438 protected function load_version_php() {
b9934a17 1439 if (strpos($this->name, 'mod_') === 0) {
473289a0
TH
1440 // filters bundled with modules do not have a version.php and so
1441 // do not provide their own versioning information.
1442 return new stdClass();
b9934a17 1443 }
473289a0 1444 return parent::load_version_php();
b9934a17
DM
1445 }
1446
b9934a17
DM
1447 public function is_enabled() {
1448
1449 $globalstates = self::get_global_states();
1450
1451 foreach ($globalstates as $filterlegacyname => $info) {
1452 $name = self::normalize_legacy_name($filterlegacyname);
1453 if ($name === $this->name) {
1454 if ($info->active == TEXTFILTER_DISABLED) {
1455 return false;
1456 } else {
1457 // it may be 'On' or 'Off, but available'
1458 return null;
1459 }
1460 }
1461 }
1462
1463 return null;
1464 }
1465
b9934a17
DM
1466 public function get_settings_url() {
1467
1468 $globalstates = self::get_global_states();
1469 $legacyname = $globalstates[$this->name]->legacyname;
1470 if (filter_has_global_settings($legacyname)) {
1471 return new moodle_url('/admin/settings.php', array('section' => 'filtersetting' . str_replace('/', '', $legacyname)));
1472 } else {
1473 return null;
1474 }
1475 }
1476
b9934a17
DM
1477 public function get_uninstall_url() {
1478
1479 if (strpos($this->name, 'mod_') === 0) {
1480 return null;
1481 } else {
1482 $globalstates = self::get_global_states();
1483 $legacyname = $globalstates[$this->name]->legacyname;
1484 return new moodle_url('/admin/filters.php', array('sesskey' => sesskey(), 'filterpath' => $legacyname, 'action' => 'delete'));
1485 }
1486 }
1487
1488 /**
1489 * Convert legacy filter names like 'filter/foo' or 'mod/bar' into frankenstyle
1490 *
1491 * @param string $legacyfiltername legacy filter name
1492 * @return string frankenstyle-like name
1493 */
1494 protected static function normalize_legacy_name($legacyfiltername) {
1495
1496 $name = str_replace('/', '_', $legacyfiltername);
1497 if (strpos($name, 'filter_') === 0) {
1498 $name = substr($name, 7);
1499 if (empty($name)) {
1500 throw new coding_exception('Unable to determine filter name: ' . $legacyfiltername);
1501 }
1502 }
1503
1504 return $name;
1505 }
1506
1507 /**
1508 * Provides access to the results of {@link filter_get_global_states()}
1509 * but indexed by the normalized filter name
1510 *
1511 * The legacy filter name is available as ->legacyname property.
1512 *
1513 * @param bool $disablecache
1514 * @return array
1515 */
1516 protected static function get_global_states($disablecache=false) {
1517 global $DB;
1518 static $globalstatescache = null;
1519
1520 if ($disablecache or is_null($globalstatescache)) {
1521
1522 if (!$DB->get_manager()->table_exists('filter_active')) {
1523 // we're upgrading from 1.9 and the table used by {@link filter_get_global_states()}
1524 // does not exist yet
1525 $globalstatescache = array();
1526
1527 } else {
1528 foreach (filter_get_global_states() as $legacyname => $info) {
1529 $name = self::normalize_legacy_name($legacyname);
1530 $filterinfo = new stdClass();
1531 $filterinfo->legacyname = $legacyname;
1532 $filterinfo->active = $info->active;
1533 $filterinfo->sortorder = $info->sortorder;
1534 $globalstatescache[$name] = $filterinfo;
1535 }
1536 }
1537 }
1538
1539 return $globalstatescache;
1540 }
1541}
1542
b6ad8594 1543
b9934a17
DM
1544/**
1545 * Class for activity modules
1546 */
b6ad8594 1547class plugininfo_mod extends plugininfo_base {
b9934a17 1548
b9934a17
DM
1549 public static function get_plugins($type, $typerootdir, $typeclass) {
1550
1551 // get the information about plugins at the disk
1552 $modules = parent::get_plugins($type, $typerootdir, $typeclass);
1553
1554 // add modules missing from disk
1555 $modulesinfo = self::get_modules_info();
1556 foreach ($modulesinfo as $modulename => $moduleinfo) {
1557 if (isset($modules[$modulename])) {
1558 continue;
1559 }
1560 $plugin = new $typeclass();
1561 $plugin->type = $type;
1562 $plugin->typerootdir = $typerootdir;
1563 $plugin->name = $modulename;
1564 $plugin->rootdir = null;
1565 $plugin->displayname = $modulename;
1566 $plugin->versiondb = $moduleinfo->version;
b8343e68 1567 $plugin->init_is_standard();
b9934a17
DM
1568
1569 $modules[$modulename] = $plugin;
1570 }
1571
1572 return $modules;
1573 }
1574
b8343e68 1575 public function init_display_name() {
828788f0
TH
1576 if (get_string_manager()->string_exists('pluginname', $this->component)) {
1577 $this->displayname = get_string('pluginname', $this->component);
b9934a17 1578 } else {
828788f0 1579 $this->displayname = get_string('modulename', $this->component);
b9934a17
DM
1580 }
1581 }
1582
1583 /**
473289a0
TH
1584 * Load the data from version.php.
1585 * @return object the data object defined in version.php.
b9934a17 1586 */
473289a0
TH
1587 protected function load_version_php() {
1588 $versionfile = $this->full_path('version.php');
b9934a17 1589
473289a0 1590 $module = new stdClass();
b9934a17
DM
1591 if (is_readable($versionfile)) {
1592 include($versionfile);
b9934a17 1593 }
473289a0 1594 return $module;
b9934a17
DM
1595 }
1596
b8343e68 1597 public function load_db_version() {
b9934a17
DM
1598 global $DB;
1599
1600 $modulesinfo = self::get_modules_info();
1601 if (isset($modulesinfo[$this->name]->version)) {
1602 $this->versiondb = $modulesinfo[$this->name]->version;
1603 }
1604 }
1605
b9934a17
DM
1606 public function is_enabled() {
1607
1608 $modulesinfo = self::get_modules_info();
1609 if (isset($modulesinfo[$this->name]->visible)) {
1610 if ($modulesinfo[$this->name]->visible) {
1611 return true;
1612 } else {
1613 return false;
1614 }
1615 } else {
1616 return parent::is_enabled();
1617 }
1618 }
1619
b9934a17
DM
1620 public function get_settings_url() {
1621
6740c605 1622 if (file_exists($this->full_path('settings.php')) or file_exists($this->full_path('settingstree.php'))) {
b9934a17
DM
1623 return new moodle_url('/admin/settings.php', array('section' => 'modsetting' . $this->name));
1624 } else {
1625 return parent::get_settings_url();
1626 }
1627 }
1628
b9934a17
DM
1629 public function get_uninstall_url() {
1630
1631 if ($this->name !== 'forum') {
1632 return new moodle_url('/admin/modules.php', array('delete' => $this->name, 'sesskey' => sesskey()));
1633 } else {
1634 return null;
1635 }
1636 }
1637
1638 /**
1639 * Provides access to the records in {modules} table
1640 *
1641 * @param bool $disablecache do not use internal static cache
1642 * @return array array of stdClasses
1643 */
1644 protected static function get_modules_info($disablecache=false) {
1645 global $DB;
1646 static $modulesinfocache = null;
1647
1648 if (is_null($modulesinfocache) or $disablecache) {
f433088d
PS
1649 try {
1650 $modulesinfocache = $DB->get_records('modules', null, 'name', 'name,id,version,visible');
1651 } catch (dml_exception $e) {
1652 // before install
1653 $modulesinfocache = array();
1654 }
b9934a17
DM
1655 }
1656
1657 return $modulesinfocache;
1658 }
1659}
1660
0242bdc7
TH
1661
1662/**
1663 * Class for question behaviours.
1664 */
b6ad8594
DM
1665class plugininfo_qbehaviour extends plugininfo_base {
1666
828788f0
TH
1667 public function get_uninstall_url() {
1668 return new moodle_url('/admin/qbehaviours.php',
1669 array('delete' => $this->name, 'sesskey' => sesskey()));
1670 }
0242bdc7
TH
1671}
1672
1673
b9934a17
DM
1674/**
1675 * Class for question types
1676 */
b6ad8594
DM
1677class plugininfo_qtype extends plugininfo_base {
1678
828788f0
TH
1679 public function get_uninstall_url() {
1680 return new moodle_url('/admin/qtypes.php',
1681 array('delete' => $this->name, 'sesskey' => sesskey()));
1682 }
b9934a17
DM
1683}
1684
b9934a17
DM
1685
1686/**
1687 * Class for authentication plugins
1688 */
b6ad8594 1689class plugininfo_auth extends plugininfo_base {
b9934a17 1690
b9934a17
DM
1691 public function is_enabled() {
1692 global $CFG;
1693 /** @var null|array list of enabled authentication plugins */
1694 static $enabled = null;
1695
1696 if (in_array($this->name, array('nologin', 'manual'))) {
1697 // these two are always enabled and can't be disabled
1698 return null;
1699 }
1700
1701 if (is_null($enabled)) {
d5d181f5 1702 $enabled = array_flip(explode(',', $CFG->auth));
b9934a17
DM
1703 }
1704
1705 return isset($enabled[$this->name]);
1706 }
1707
b9934a17 1708 public function get_settings_url() {
6740c605 1709 if (file_exists($this->full_path('settings.php'))) {
b9934a17
DM
1710 return new moodle_url('/admin/settings.php', array('section' => 'authsetting' . $this->name));
1711 } else {
1712 return new moodle_url('/admin/auth_config.php', array('auth' => $this->name));
1713 }
1714 }
1715}
1716
b6ad8594 1717
b9934a17
DM
1718/**
1719 * Class for enrolment plugins
1720 */
b6ad8594 1721class plugininfo_enrol extends plugininfo_base {
b9934a17 1722
b9934a17
DM
1723 public function is_enabled() {
1724 global $CFG;
1725 /** @var null|array list of enabled enrolment plugins */
1726 static $enabled = null;
1727
b6ad8594
DM
1728 // We do not actually need whole enrolment classes here so we do not call
1729 // {@link enrol_get_plugins()}. Note that this may produce slightly different
1730 // results, for example if the enrolment plugin does not contain lib.php
1731 // but it is listed in $CFG->enrol_plugins_enabled
1732
b9934a17 1733 if (is_null($enabled)) {
d5d181f5 1734 $enabled = array_flip(explode(',', $CFG->enrol_plugins_enabled));
b9934a17
DM
1735 }
1736
1737 return isset($enabled[$this->name]);
1738 }
1739
b9934a17
DM
1740 public function get_settings_url() {
1741
6740c605 1742 if ($this->is_enabled() or file_exists($this->full_path('settings.php'))) {
b9934a17
DM
1743 return new moodle_url('/admin/settings.php', array('section' => 'enrolsettings' . $this->name));
1744 } else {
1745 return parent::get_settings_url();
1746 }
1747 }
1748
b9934a17
DM
1749 public function get_uninstall_url() {
1750 return new moodle_url('/admin/enrol.php', array('action' => 'uninstall', 'enrol' => $this->name, 'sesskey' => sesskey()));
1751 }
1752}
1753
b6ad8594 1754
b9934a17
DM
1755/**
1756 * Class for messaging processors
1757 */
b6ad8594 1758class plugininfo_message extends plugininfo_base {
b9934a17 1759
b9934a17
DM
1760 public function get_settings_url() {
1761
6740c605
TH
1762 if (file_exists($this->full_path('settings.php')) or file_exists($this->full_path('settingstree.php'))) {
1763 return new moodle_url('/admin/settings.php', array('section' => 'messagesetting' . $this->name));
1764 } else {
1765 return parent::get_settings_url();
b9934a17 1766 }
b9934a17
DM
1767 }
1768}
1769
b6ad8594 1770
b9934a17
DM
1771/**
1772 * Class for repositories
1773 */
b6ad8594 1774class plugininfo_repository extends plugininfo_base {
b9934a17 1775
b9934a17
DM
1776 public function is_enabled() {
1777
1778 $enabled = self::get_enabled_repositories();
1779
1780 return isset($enabled[$this->name]);
1781 }
1782
b9934a17
DM
1783 public function get_settings_url() {
1784
1785 if ($this->is_enabled()) {
1786 return new moodle_url('/admin/repository.php', array('sesskey' => sesskey(), 'action' => 'edit', 'repos' => $this->name));
1787 } else {
1788 return parent::get_settings_url();
1789 }
1790 }
1791
1792 /**
1793 * Provides access to the records in {repository} table
1794 *
1795 * @param bool $disablecache do not use internal static cache
1796 * @return array array of stdClasses
1797 */
1798 protected static function get_enabled_repositories($disablecache=false) {
1799 global $DB;
1800 static $repositories = null;
1801
1802 if (is_null($repositories) or $disablecache) {
1803 $repositories = $DB->get_records('repository', null, 'type', 'type,visible,sortorder');
1804 }
1805
1806 return $repositories;
1807 }
1808}
1809
b6ad8594 1810
b9934a17
DM
1811/**
1812 * Class for portfolios
1813 */
b6ad8594 1814class plugininfo_portfolio extends plugininfo_base {
b9934a17 1815
b9934a17
DM
1816 public function is_enabled() {
1817
1818 $enabled = self::get_enabled_portfolios();
1819
1820 return isset($enabled[$this->name]);
1821 }
1822
1823 /**
1824 * Provides access to the records in {portfolio_instance} table
1825 *
1826 * @param bool $disablecache do not use internal static cache
1827 * @return array array of stdClasses
1828 */
1829 protected static function get_enabled_portfolios($disablecache=false) {
1830 global $DB;
1831 static $portfolios = null;
1832
1833 if (is_null($portfolios) or $disablecache) {
1834 $portfolios = array();
1835 $instances = $DB->get_recordset('portfolio_instance', null, 'plugin');
1836 foreach ($instances as $instance) {
1837 if (isset($portfolios[$instance->plugin])) {
1838 if ($instance->visible) {
1839 $portfolios[$instance->plugin]->visible = $instance->visible;
1840 }
1841 } else {
1842 $portfolios[$instance->plugin] = $instance;
1843 }
1844 }
1845 }
1846
1847 return $portfolios;
1848 }
1849}
1850
b6ad8594 1851
b9934a17
DM
1852/**
1853 * Class for themes
1854 */
b6ad8594 1855class plugininfo_theme extends plugininfo_base {
b9934a17 1856
b9934a17
DM
1857 public function is_enabled() {
1858 global $CFG;
1859
1860 if ((!empty($CFG->theme) and $CFG->theme === $this->name) or
1861 (!empty($CFG->themelegacy) and $CFG->themelegacy === $this->name)) {
1862 return true;
1863 } else {
1864 return parent::is_enabled();
1865 }
1866 }
1867}
1868
b6ad8594 1869
b9934a17
DM
1870/**
1871 * Class representing an MNet service
1872 */
b6ad8594 1873class plugininfo_mnetservice extends plugininfo_base {
b9934a17 1874
b9934a17
DM
1875 public function is_enabled() {
1876 global $CFG;
1877
1878 if (empty($CFG->mnet_dispatcher_mode) || $CFG->mnet_dispatcher_mode !== 'strict') {
1879 return false;
1880 } else {
1881 return parent::is_enabled();
1882 }
1883 }
1884}
3cdfaeef 1885
b6ad8594 1886
3cdfaeef
PS
1887/**
1888 * Class for admin tool plugins
1889 */
b6ad8594 1890class plugininfo_tool extends plugininfo_base {
3cdfaeef
PS
1891
1892 public function get_uninstall_url() {
1893 return new moodle_url('/admin/tools.php', array('delete' => $this->name, 'sesskey' => sesskey()));
1894 }
1895}
4f6bba20 1896
b6ad8594 1897
4f6bba20
PS
1898/**
1899 * Class for admin tool plugins
1900 */
b6ad8594 1901class plugininfo_report extends plugininfo_base {
4f6bba20
PS
1902
1903 public function get_uninstall_url() {
1904 return new moodle_url('/admin/reports.php', array('delete' => $this->name, 'sesskey' => sesskey()));
1905 }
1906}