Merge branch 'MDL-32338' of git://github.com/nebgor/moodle
[moodle.git] / lib / pluginlib.php
1 <?php
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/>.
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  */
31 defined('MOODLE_INTERNAL') || die();
33 require_once($CFG->libdir.'/filelib.php');  // curl class needed here
35 /**
36  * Singleton class providing general plugins management functionality
37  */
38 class plugin_manager {
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';
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';
53     /** the standard plugin is about to be deleted */
54     const PLUGIN_STATUS_DELETE     = 'delete';
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';
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;
67     /**
68      * Direct initiation not allowed, use the factory method {@link self::instance()}
69      */
70     protected function __construct() {
71     }
73     /**
74      * Sorry, this is singleton
75      */
76     protected function __clone() {
77     }
79     /**
80      * Factory method for this class
81      *
82      * @return plugin_manager the singleton instance
83      */
84     public static function instance() {
85         if (is_null(self::$singletoninstance)) {
86             self::$singletoninstance = new self();
87         }
88         return self::$singletoninstance;
89     }
91     /**
92      * Returns a tree of known plugins and information about them
93      *
94      * @param bool $disablecache force reload, cache can be used otherwise
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
97      *      the values are the corresponding objects extending {@link plugininfo_base}
98      */
99     public function get_plugins($disablecache=false) {
100         global $CFG;
102         if ($disablecache or is_null($this->pluginsinfo)) {
103             $this->pluginsinfo = array();
104             $plugintypes = get_plugin_types();
105             $plugintypes = $this->reorder_plugin_types($plugintypes);
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                 }
110                 if (class_exists('plugininfo_' . $plugintype)) {
111                     $plugintypeclass = 'plugininfo_' . $plugintype;
112                 } else {
113                     $plugintypeclass = 'plugininfo_general';
114                 }
115                 if (!in_array('plugininfo_base', class_parents($plugintypeclass))) {
116                     throw new coding_exception('Class ' . $plugintypeclass . ' must extend plugininfo_base');
117                 }
118                 $plugins = call_user_func(array($plugintypeclass, 'get_plugins'), $plugintype, $plugintyperootdir, $plugintypeclass);
119                 $this->pluginsinfo[$plugintype] = $plugins;
120             }
122             if (empty($CFG->disableupdatenotifications) and !during_initial_install()) {
123                 // append the information about available updates provided by {@link available_update_checker()}
124                 $provider = available_update_checker::instance();
125                 foreach ($this->pluginsinfo as $plugintype => $plugins) {
126                     foreach ($plugins as $plugininfoholder) {
127                         $plugininfoholder->check_available_updates($provider);
128                     }
129                 }
130             }
131         }
133         return $this->pluginsinfo;
134     }
136     /**
137      * Returns list of plugins that define their subplugins and the information
138      * about them from the db/subplugins.php file.
139      *
140      * At the moment, only activity modules can define subplugins.
141      *
142      * @param bool $disablecache force reload, cache can be used otherwise
143      * @return array with keys like 'mod_quiz', and values the data from the
144      *      corresponding db/subplugins.php file.
145      */
146     public function get_subplugins($disablecache=false) {
148         if ($disablecache or is_null($this->subpluginsinfo)) {
149             $this->subpluginsinfo = array();
150             $mods = get_plugin_list('mod');
151             foreach ($mods as $mod => $moddir) {
152                 $modsubplugins = array();
153                 if (file_exists($moddir . '/db/subplugins.php')) {
154                     include($moddir . '/db/subplugins.php');
155                     foreach ($subplugins as $subplugintype => $subplugintyperootdir) {
156                         $subplugin = new stdClass();
157                         $subplugin->type = $subplugintype;
158                         $subplugin->typerootdir = $subplugintyperootdir;
159                         $modsubplugins[$subplugintype] = $subplugin;
160                     }
161                 $this->subpluginsinfo['mod_' . $mod] = $modsubplugins;
162                 }
163             }
164         }
166         return $this->subpluginsinfo;
167     }
169     /**
170      * Returns the name of the plugin that defines the given subplugin type
171      *
172      * If the given subplugin type is not actually a subplugin, returns false.
173      *
174      * @param string $subplugintype the name of subplugin type, eg. workshopform or quiz
175      * @return false|string the name of the parent plugin, eg. mod_workshop
176      */
177     public function get_parent_of_subplugin($subplugintype) {
179         $parent = false;
180         foreach ($this->get_subplugins() as $pluginname => $subplugintypes) {
181             if (isset($subplugintypes[$subplugintype])) {
182                 $parent = $pluginname;
183                 break;
184             }
185         }
187         return $parent;
188     }
190     /**
191      * Returns a localized name of a given plugin
192      *
193      * @param string $plugin name of the plugin, eg mod_workshop or auth_ldap
194      * @return string
195      */
196     public function plugin_name($plugin) {
197         list($type, $name) = normalize_component($plugin);
198         return $this->pluginsinfo[$type][$name]->displayname;
199     }
201     /**
202      * Returns a localized name of a plugin type in plural form
203      *
204      * Most plugin types define their names in core_plugin lang file. In case of subplugins,
205      * we try to ask the parent plugin for the name. In the worst case, we will return
206      * the value of the passed $type parameter.
207      *
208      * @param string $type the type of the plugin, e.g. mod or workshopform
209      * @return string
210      */
211     public function plugintype_name_plural($type) {
213         if (get_string_manager()->string_exists('type_' . $type . '_plural', 'core_plugin')) {
214             // for most plugin types, their names are defined in core_plugin lang file
215             return get_string('type_' . $type . '_plural', 'core_plugin');
217         } else if ($parent = $this->get_parent_of_subplugin($type)) {
218             // if this is a subplugin, try to ask the parent plugin for the name
219             if (get_string_manager()->string_exists('subplugintype_' . $type . '_plural', $parent)) {
220                 return $this->plugin_name($parent) . ' / ' . get_string('subplugintype_' . $type . '_plural', $parent);
221             } else {
222                 return $this->plugin_name($parent) . ' / ' . $type;
223             }
225         } else {
226             return $type;
227         }
228     }
230     /**
231      * @param string $component frankenstyle component name.
232      * @return plugininfo_base|null the corresponding plugin information.
233      */
234     public function get_plugin_info($component) {
235         list($type, $name) = normalize_component($component);
236         $plugins = $this->get_plugins();
237         if (isset($plugins[$type][$name])) {
238             return $plugins[$type][$name];
239         } else {
240             return null;
241         }
242     }
244     /**
245      * Get a list of any other plugins that require this one.
246      * @param string $component frankenstyle component name.
247      * @return array of frankensyle component names that require this one.
248      */
249     public function other_plugins_that_require($component) {
250         $others = array();
251         foreach ($this->get_plugins() as $type => $plugins) {
252             foreach ($plugins as $plugin) {
253                 $required = $plugin->get_other_required_plugins();
254                 if (isset($required[$component])) {
255                     $others[] = $plugin->component;
256                 }
257             }
258         }
259         return $others;
260     }
262     /**
263      * Check a dependencies list against the list of installed plugins.
264      * @param array $dependencies compenent name to required version or ANY_VERSION.
265      * @return bool true if all the dependencies are satisfied.
266      */
267     public function are_dependencies_satisfied($dependencies) {
268         foreach ($dependencies as $component => $requiredversion) {
269             $otherplugin = $this->get_plugin_info($component);
270             if (is_null($otherplugin)) {
271                 return false;
272             }
274             if ($requiredversion != ANY_VERSION and $otherplugin->versiondisk < $requiredversion) {
275                 return false;
276             }
277         }
279         return true;
280     }
282     /**
283      * Checks all dependencies for all installed plugins. Used by install and upgrade.
284      * @param int $moodleversion the version from version.php.
285      * @return bool true if all the dependencies are satisfied for all plugins.
286      */
287     public function all_plugins_ok($moodleversion) {
288         foreach ($this->get_plugins() as $type => $plugins) {
289             foreach ($plugins as $plugin) {
291                 if (!empty($plugin->versionrequires) && $plugin->versionrequires > $moodleversion) {
292                     return false;
293                 }
295                 if (!$this->are_dependencies_satisfied($plugin->get_other_required_plugins())) {
296                     return false;
297                 }
298             }
299         }
301         return true;
302     }
304     /**
305      * Checks if there are some plugins with a known available update
306      *
307      * @return bool true if there is at least one available update
308      */
309     public function some_plugins_updatable() {
310         foreach ($this->get_plugins() as $type => $plugins) {
311             foreach ($plugins as $plugin) {
312                 if ($plugin->available_updates()) {
313                     return true;
314                 }
315             }
316         }
318         return false;
319     }
321     /**
322      * Defines a list of all plugins that were originally shipped in the standard Moodle distribution,
323      * but are not anymore and are deleted during upgrades.
324      *
325      * The main purpose of this list is to hide missing plugins during upgrade.
326      *
327      * @param string $type plugin type
328      * @param string $name plugin name
329      * @return bool
330      */
331     public static function is_deleted_standard_plugin($type, $name) {
332         static $plugins = array(
333             // do not add 1.9-2.2 plugin removals here
334         );
336         if (!isset($plugins[$type])) {
337             return false;
338         }
339         return in_array($name, $plugins[$type]);
340     }
342     /**
343      * Defines a white list of all plugins shipped in the standard Moodle distribution
344      *
345      * @param string $type
346      * @return false|array array of standard plugins or false if the type is unknown
347      */
348     public static function standard_plugins_list($type) {
349         static $standard_plugins = array(
351             'assignment' => array(
352                 'offline', 'online', 'upload', 'uploadsingle'
353             ),
355             'assignsubmission' => array(
356                 'comments', 'file', 'onlinetext'
357             ),
359             'assignfeedback' => array(
360                 'comments', 'file'
361             ),
363             'auth' => array(
364                 'cas', 'db', 'email', 'fc', 'imap', 'ldap', 'manual', 'mnet',
365                 'nntp', 'nologin', 'none', 'pam', 'pop3', 'radius',
366                 'shibboleth', 'webservice'
367             ),
369             'block' => array(
370                 'activity_modules', 'admin_bookmarks', 'blog_menu',
371                 'blog_recent', 'blog_tags', 'calendar_month',
372                 'calendar_upcoming', 'comments', 'community',
373                 'completionstatus', 'course_list', 'course_overview',
374                 'course_summary', 'feedback', 'glossary_random', 'html',
375                 'login', 'mentees', 'messages', 'mnet_hosts', 'myprofile',
376                 'navigation', 'news_items', 'online_users', 'participants',
377                 'private_files', 'quiz_results', 'recent_activity',
378                 'rss_client', 'search_forums', 'section_links',
379                 'selfcompletion', 'settings', 'site_main_menu',
380                 'social_activities', 'tag_flickr', 'tag_youtube', 'tags'
381             ),
383             'booktool' => array(
384                 'exportimscp', 'importhtml', 'print'
385             ),
387             'coursereport' => array(
388                 //deprecated!
389             ),
391             'datafield' => array(
392                 'checkbox', 'date', 'file', 'latlong', 'menu', 'multimenu',
393                 'number', 'picture', 'radiobutton', 'text', 'textarea', 'url'
394             ),
396             'datapreset' => array(
397                 'imagegallery'
398             ),
400             'editor' => array(
401                 'textarea', 'tinymce'
402             ),
404             'enrol' => array(
405                 'authorize', 'category', 'cohort', 'database', 'flatfile',
406                 'guest', 'imsenterprise', 'ldap', 'manual', 'meta', 'mnet',
407                 'paypal', 'self'
408             ),
410             'filter' => array(
411                 'activitynames', 'algebra', 'censor', 'emailprotect',
412                 'emoticon', 'mediaplugin', 'multilang', 'tex', 'tidy',
413                 'urltolink', 'data', 'glossary'
414             ),
416             'format' => array(
417                 'scorm', 'social', 'topics', 'weeks'
418             ),
420             'gradeexport' => array(
421                 'ods', 'txt', 'xls', 'xml'
422             ),
424             'gradeimport' => array(
425                 'csv', 'xml'
426             ),
428             'gradereport' => array(
429                 'grader', 'outcomes', 'overview', 'user'
430             ),
432             'gradingform' => array(
433                 'rubric', 'guide'
434             ),
436             'local' => array(
437             ),
439             'message' => array(
440                 'email', 'jabber', 'popup'
441             ),
443             'mnetservice' => array(
444                 'enrol'
445             ),
447             'mod' => array(
448                 'assign', 'assignment', 'book', 'chat', 'choice', 'data', 'feedback', 'folder',
449                 'forum', 'glossary', 'imscp', 'label', 'lesson', 'lti', 'page',
450                 'quiz', 'resource', 'scorm', 'survey', 'url', 'wiki', 'workshop'
451             ),
453             'plagiarism' => array(
454             ),
456             'portfolio' => array(
457                 'boxnet', 'download', 'flickr', 'googledocs', 'mahara', 'picasa'
458             ),
460             'profilefield' => array(
461                 'checkbox', 'datetime', 'menu', 'text', 'textarea'
462             ),
464             'qbehaviour' => array(
465                 'adaptive', 'adaptivenopenalty', 'deferredcbm',
466                 'deferredfeedback', 'immediatecbm', 'immediatefeedback',
467                 'informationitem', 'interactive', 'interactivecountback',
468                 'manualgraded', 'missing'
469             ),
471             'qformat' => array(
472                 'aiken', 'blackboard', 'blackboard_six', 'examview', 'gift',
473                 'learnwise', 'missingword', 'multianswer', 'webct',
474                 'xhtml', 'xml'
475             ),
477             'qtype' => array(
478                 'calculated', 'calculatedmulti', 'calculatedsimple',
479                 'description', 'essay', 'match', 'missingtype', 'multianswer',
480                 'multichoice', 'numerical', 'random', 'randomsamatch',
481                 'shortanswer', 'truefalse'
482             ),
484             'quiz' => array(
485                 'grading', 'overview', 'responses', 'statistics'
486             ),
488             'quizaccess' => array(
489                 'delaybetweenattempts', 'ipaddress', 'numattempts', 'openclosedate',
490                 'password', 'safebrowser', 'securewindow', 'timelimit'
491             ),
493             'report' => array(
494                 'backups', 'completion', 'configlog', 'courseoverview',
495                 'log', 'loglive', 'outline', 'participation', 'progress', 'questioninstances', 'security', 'stats'
496             ),
498             'repository' => array(
499                 'alfresco', 'boxnet', 'coursefiles', 'dropbox', 'filesystem',
500                 'flickr', 'flickr_public', 'googledocs', 'local', 'merlot',
501                 'picasa', 'recent', 's3', 'upload', 'url', 'user', 'webdav',
502                 'wikimedia', 'youtube'
503             ),
505             'scormreport' => array(
506                 'basic',
507                 'interactions',
508                 'graphs'
509             ),
511             'theme' => array(
512                 'afterburner', 'anomaly', 'arialist', 'base', 'binarius',
513                 'boxxie', 'brick', 'canvas', 'formal_white', 'formfactor',
514                 'fusion', 'leatherbound', 'magazine', 'mymobile', 'nimble',
515                 'nonzero', 'overlay', 'serenity', 'sky_high', 'splash',
516                 'standard', 'standardold'
517             ),
519             'tool' => array(
520                 'assignmentupgrade', 'bloglevelupgrade', 'capability', 'customlang', 'dbtransfer', 'generator',
521                 'health', 'innodb', 'langimport', 'multilangupgrade', 'phpunit', 'profiling',
522                 'qeupgradehelper', 'replace', 'spamcleaner', 'timezoneimport', 'unittest',
523                 'uploaduser', 'unsuproles', 'xmldb'
524             ),
526             'webservice' => array(
527                 'amf', 'rest', 'soap', 'xmlrpc'
528             ),
530             'workshopallocation' => array(
531                 'manual', 'random', 'scheduled'
532             ),
534             'workshopeval' => array(
535                 'best'
536             ),
538             'workshopform' => array(
539                 'accumulative', 'comments', 'numerrors', 'rubric'
540             )
541         );
543         if (isset($standard_plugins[$type])) {
544             return $standard_plugins[$type];
545         } else {
546             return false;
547         }
548     }
550     /**
551      * Reorders plugin types into a sequence to be displayed
552      *
553      * For technical reasons, plugin types returned by {@link get_plugin_types()} are
554      * in a certain order that does not need to fit the expected order for the display.
555      * Particularly, activity modules should be displayed first as they represent the
556      * real heart of Moodle. They should be followed by other plugin types that are
557      * used to build the courses (as that is what one expects from LMS). After that,
558      * other supportive plugin types follow.
559      *
560      * @param array $types associative array
561      * @return array same array with altered order of items
562      */
563     protected function reorder_plugin_types(array $types) {
564         $fix = array(
565             'mod'        => $types['mod'],
566             'block'      => $types['block'],
567             'qtype'      => $types['qtype'],
568             'qbehaviour' => $types['qbehaviour'],
569             'qformat'    => $types['qformat'],
570             'filter'     => $types['filter'],
571             'enrol'      => $types['enrol'],
572         );
573         foreach ($types as $type => $path) {
574             if (!isset($fix[$type])) {
575                 $fix[$type] = $path;
576             }
577         }
578         return $fix;
579     }
583 /**
584  * General exception thrown by the {@link available_update_checker} class
585  */
586 class available_update_checker_exception extends moodle_exception {
588     /**
589      * @param string $errorcode exception description identifier
590      * @param mixed $debuginfo debugging data to display
591      */
592     public function __construct($errorcode, $debuginfo=null) {
593         parent::__construct($errorcode, 'core_plugin', '', null, print_r($debuginfo, true));
594     }
598 /**
599  * Singleton class that handles checking for available updates
600  */
601 class available_update_checker {
603     /** @var available_update_checker holds the singleton instance */
604     protected static $singletoninstance;
605     /** @var null|int the timestamp of when the most recent response was fetched */
606     protected $recentfetch = null;
607     /** @var null|array the recent response from the update notification provider */
608     protected $recentresponse = null;
609     /** @var null|string the numerical version of the local Moodle code */
610     protected $currentversion = null;
611     /** @var null|string the release info of the local Moodle code */
612     protected $currentrelease = null;
613     /** @var null|string branch of the local Moodle code */
614     protected $currentbranch = null;
615     /** @var array of (string)frankestyle => (string)version list of additional plugins deployed at this site */
616     protected $currentplugins = array();
618     /**
619      * Direct initiation not allowed, use the factory method {@link self::instance()}
620      */
621     protected function __construct() {
622     }
624     /**
625      * Sorry, this is singleton
626      */
627     protected function __clone() {
628     }
630     /**
631      * Factory method for this class
632      *
633      * @return available_update_checker the singleton instance
634      */
635     public static function instance() {
636         if (is_null(self::$singletoninstance)) {
637             self::$singletoninstance = new self();
638         }
639         return self::$singletoninstance;
640     }
642     /**
643      * Returns the timestamp of the last execution of {@link fetch()}
644      *
645      * @return int|null null if it has never been executed or we don't known
646      */
647     public function get_last_timefetched() {
649         $this->restore_response();
651         if (!empty($this->recentfetch)) {
652             return $this->recentfetch;
654         } else {
655             return null;
656         }
657     }
659     /**
660      * Fetches the available update status from the remote site
661      *
662      * @throws available_update_checker_exception
663      */
664     public function fetch() {
665         $response = $this->get_response();
666         $this->validate_response($response);
667         $this->store_response($response);
668     }
670     /**
671      * Returns the available update information for the given component
672      *
673      * This method returns null if the most recent response does not contain any information
674      * about it. The returned structure is an array of available updates for the given
675      * component. Each update info is an object with at least one property called
676      * 'version'. Other possible properties are 'release', 'maturity', 'url' and 'downloadurl'.
677      *
678      * For the 'core' component, the method returns real updates only (those with higher version).
679      * For all other components, the list of all known remote updates is returned and the caller
680      * (usually the {@link plugin_manager}) is supposed to make the actual comparison of versions.
681      *
682      * @param string $component frankenstyle
683      * @param array $options with supported keys 'minmaturity' and/or 'notifybuilds'
684      * @return null|array null or array of available_update_info objects
685      */
686     public function get_update_info($component, array $options = array()) {
688         if (!isset($options['minmaturity'])) {
689             $options['minmaturity'] = 0;
690         }
692         if (!isset($options['notifybuilds'])) {
693             $options['notifybuilds'] = false;
694         }
696         if ($component == 'core') {
697             $this->load_current_environment();
698         }
700         $this->restore_response();
702         if (empty($this->recentresponse['updates'][$component])) {
703             return null;
704         }
706         $updates = array();
707         foreach ($this->recentresponse['updates'][$component] as $info) {
708             $update = new available_update_info($component, $info);
709             if (isset($update->maturity) and ($update->maturity < $options['minmaturity'])) {
710                 continue;
711             }
712             if ($component == 'core') {
713                 if ($update->version <= $this->currentversion) {
714                     continue;
715                 }
716                 if (empty($options['notifybuilds']) and $this->is_same_release($update->release)) {
717                     continue;
718                 }
719             }
720             $updates[] = $update;
721         }
723         if (empty($updates)) {
724             return null;
725         }
727         return $updates;
728     }
730     /**
731      * The method being run via cron.php
732      */
733     public function cron() {
734         global $CFG;
736         if (!$this->cron_autocheck_enabled()) {
737             $this->cron_mtrace('Automatic check for available updates not enabled, skipping.');
738             return;
739         }
741         $now = $this->cron_current_timestamp();
743         if ($this->cron_has_fresh_fetch($now)) {
744             $this->cron_mtrace('Recently fetched info about available updates is still fresh enough, skipping.');
745             return;
746         }
748         if ($this->cron_has_outdated_fetch($now)) {
749             $this->cron_mtrace('Outdated or missing info about available updates, forced fetching ... ', '');
750             $this->cron_execute();
751             return;
752         }
754         $offset = $this->cron_execution_offset();
755         $start = mktime(1, 0, 0, date('n', $now), date('j', $now), date('Y', $now)); // 01:00 AM today local time
756         if ($now > $start + $offset) {
757             $this->cron_mtrace('Regular daily check for available updates ... ', '');
758             $this->cron_execute();
759             return;
760         }
761     }
763     /// end of public API //////////////////////////////////////////////////////
765     /**
766      * Makes cURL request to get data from the remote site
767      *
768      * @return string raw request result
769      * @throws available_update_checker_exception
770      */
771     protected function get_response() {
772         $curl = new curl(array('proxy' => true));
773         $response = $curl->post($this->prepare_request_url(), $this->prepare_request_params());
774         $curlinfo = $curl->get_info();
775         if ($curlinfo['http_code'] != 200) {
776             throw new available_update_checker_exception('err_response_http_code', $curlinfo['http_code']);
777         }
778         return $response;
779     }
781     /**
782      * Makes sure the response is valid, has correct API format etc.
783      *
784      * @param string $response raw response as returned by the {@link self::get_response()}
785      * @throws available_update_checker_exception
786      */
787     protected function validate_response($response) {
789         $response = $this->decode_response($response);
791         if (empty($response)) {
792             throw new available_update_checker_exception('err_response_empty');
793         }
795         if (empty($response['status']) or $response['status'] !== 'OK') {
796             throw new available_update_checker_exception('err_response_status', $response['status']);
797         }
799         if (empty($response['apiver']) or $response['apiver'] !== '1.0') {
800             throw new available_update_checker_exception('err_response_format_version', $response['apiver']);
801         }
803         if (empty($response['forbranch']) or $response['forbranch'] !== moodle_major_version(true)) {
804             throw new available_update_checker_exception('err_response_target_version', $response['target']);
805         }
806     }
808     /**
809      * Decodes the raw string response from the update notifications provider
810      *
811      * @param string $response as returned by {@link self::get_response()}
812      * @return array decoded response structure
813      */
814     protected function decode_response($response) {
815         return json_decode($response, true);
816     }
818     /**
819      * Stores the valid fetched response for later usage
820      *
821      * This implementation uses the config_plugins table as the permanent storage.
822      *
823      * @param string $response raw valid data returned by {@link self::get_response()}
824      */
825     protected function store_response($response) {
827         set_config('recentfetch', time(), 'core_plugin');
828         set_config('recentresponse', $response, 'core_plugin');
830         $this->restore_response(true);
831     }
833     /**
834      * Loads the most recent raw response record we have fetched
835      *
836      * This implementation uses the config_plugins table as the permanent storage.
837      *
838      * @param bool $forcereload reload even if it was already loaded
839      */
840     protected function restore_response($forcereload = false) {
842         if (!$forcereload and !is_null($this->recentresponse)) {
843             // we already have it, nothing to do
844             return;
845         }
847         $config = get_config('core_plugin');
849         if (!empty($config->recentresponse) and !empty($config->recentfetch)) {
850             try {
851                 $this->validate_response($config->recentresponse);
852                 $this->recentfetch = $config->recentfetch;
853                 $this->recentresponse = $this->decode_response($config->recentresponse);
854             } catch (available_update_checker_exception $e) {
855                 // do not set recentresponse if the validation fails
856             }
858         } else {
859             $this->recentresponse = array();
860         }
861     }
863     /**
864      * Compares two raw {@link $recentresponse} records and returns the list of changed updates
865      *
866      * This method is used to populate potential update info to be sent to site admins.
867      *
868      * @param array $old
869      * @param array $new
870      * @throws available_update_checker_exception
871      * @return array parts of $new['updates'] that have changed
872      */
873     protected function compare_responses(array $old, array $new) {
875         if (empty($new)) {
876             return array();
877         }
879         if (!array_key_exists('updates', $new)) {
880             throw new available_update_checker_exception('err_response_format');
881         }
883         if (empty($old)) {
884             return $new['updates'];
885         }
887         if (!array_key_exists('updates', $old)) {
888             throw new available_update_checker_exception('err_response_format');
889         }
891         $changes = array();
893         foreach ($new['updates'] as $newcomponent => $newcomponentupdates) {
894             if (empty($old['updates'][$newcomponent])) {
895                 $changes[$newcomponent] = $newcomponentupdates;
896                 continue;
897             }
898             foreach ($newcomponentupdates as $newcomponentupdate) {
899                 $inold = false;
900                 foreach ($old['updates'][$newcomponent] as $oldcomponentupdate) {
901                     if ($newcomponentupdate['version'] == $oldcomponentupdate['version']) {
902                         $inold = true;
903                     }
904                 }
905                 if (!$inold) {
906                     if (!isset($changes[$newcomponent])) {
907                         $changes[$newcomponent] = array();
908                     }
909                     $changes[$newcomponent][] = $newcomponentupdate;
910                 }
911             }
912         }
914         return $changes;
915     }
917     /**
918      * Returns the URL to send update requests to
919      *
920      * During the development or testing, you can set $CFG->alternativeupdateproviderurl
921      * to a custom URL that will be used. Otherwise the standard URL will be returned.
922      *
923      * @return string URL
924      */
925     protected function prepare_request_url() {
926         global $CFG;
928         if (!empty($CFG->alternativeupdateproviderurl)) {
929             return $CFG->alternativeupdateproviderurl;
930         } else {
931             return 'http://download.moodle.org/api/1.0/updates.php';
932         }
933     }
935     /**
936      * Sets the properties currentversion, currentrelease, currentbranch and currentplugins
937      *
938      * @param bool $forcereload
939      */
940     protected function load_current_environment($forcereload=false) {
941         global $CFG;
943         if (!is_null($this->currentversion) and !$forcereload) {
944             // nothing to do
945             return;
946         }
948         require($CFG->dirroot.'/version.php');
949         $this->currentversion = $version;
950         $this->currentrelease = $release;
951         $this->currentbranch = moodle_major_version(true);
953         $pluginman = plugin_manager::instance();
954         foreach ($pluginman->get_plugins() as $type => $plugins) {
955             foreach ($plugins as $plugin) {
956                 if (!$plugin->is_standard()) {
957                     $this->currentplugins[$plugin->component] = $plugin->versiondisk;
958                 }
959             }
960         }
961     }
963     /**
964      * Returns the list of HTTP params to be sent to the updates provider URL
965      *
966      * @return array of (string)param => (string)value
967      */
968     protected function prepare_request_params() {
969         global $CFG;
971         $this->load_current_environment();
972         $this->restore_response();
974         $params = array();
975         $params['format'] = 'json';
977         if (isset($this->recentresponse['ticket'])) {
978             $params['ticket'] = $this->recentresponse['ticket'];
979         }
981         if (isset($this->currentversion)) {
982             $params['version'] = $this->currentversion;
983         } else {
984             throw new coding_exception('Main Moodle version must be already known here');
985         }
987         if (isset($this->currentbranch)) {
988             $params['branch'] = $this->currentbranch;
989         } else {
990             throw new coding_exception('Moodle release must be already known here');
991         }
993         $plugins = array();
994         foreach ($this->currentplugins as $plugin => $version) {
995             $plugins[] = $plugin.'@'.$version;
996         }
997         if (!empty($plugins)) {
998             $params['plugins'] = implode(',', $plugins);
999         }
1001         return $params;
1002     }
1004     /**
1005      * Returns the current timestamp
1006      *
1007      * @return int the timestamp
1008      */
1009     protected function cron_current_timestamp() {
1010         return time();
1011     }
1013     /**
1014      * Output cron debugging info
1015      *
1016      * @see mtrace()
1017      * @param string $msg output message
1018      * @param string $eol end of line
1019      */
1020     protected function cron_mtrace($msg, $eol = PHP_EOL) {
1021         mtrace($msg, $eol);
1022     }
1024     /**
1025      * Decide if the autocheck feature is disabled in the server setting
1026      *
1027      * @return bool true if autocheck enabled, false if disabled
1028      */
1029     protected function cron_autocheck_enabled() {
1030         global $CFG;
1032         if (empty($CFG->updateautocheck)) {
1033             return false;
1034         } else {
1035             return true;
1036         }
1037     }
1039     /**
1040      * Decide if the recently fetched data are still fresh enough
1041      *
1042      * @param int $now current timestamp
1043      * @return bool true if no need to re-fetch, false otherwise
1044      */
1045     protected function cron_has_fresh_fetch($now) {
1046         $recent = $this->get_last_timefetched();
1048         if (empty($recent)) {
1049             return false;
1050         }
1052         if ($now < $recent) {
1053             $this->cron_mtrace('The most recent fetch is reported to be in the future, this is weird!');
1054             return true;
1055         }
1057         if ($now - $recent > HOURSECS) {
1058             return false;
1059         }
1061         return true;
1062     }
1064     /**
1065      * Decide if the fetch is outadated or even missing
1066      *
1067      * @param int $now current timestamp
1068      * @return bool false if no need to re-fetch, true otherwise
1069      */
1070     protected function cron_has_outdated_fetch($now) {
1071         $recent = $this->get_last_timefetched();
1073         if (empty($recent)) {
1074             return true;
1075         }
1077         if ($now < $recent) {
1078             $this->cron_mtrace('The most recent fetch is reported to be in the future, this is weird!');
1079             return false;
1080         }
1082         if ($now - $recent > 48 * HOURSECS) {
1083             return true;
1084         }
1086         return false;
1087     }
1089     /**
1090      * Returns the cron execution offset for this site
1091      *
1092      * The main {@link self::cron()} is supposed to run every night in some random time
1093      * between 01:00 and 06:00 AM (local time). The exact moment is defined by so called
1094      * execution offset, that is the amount of time after 01:00 AM. The offset value is
1095      * initially generated randomly and then used consistently at the site. This way, the
1096      * regular checks against the download.moodle.org server are spread in time.
1097      *
1098      * @return int the offset number of seconds from range 1 sec to 5 hours
1099      */
1100     protected function cron_execution_offset() {
1101         global $CFG;
1103         if (empty($CFG->updatecronoffset)) {
1104             set_config('updatecronoffset', rand(1, 5 * HOURSECS));
1105         }
1107         return $CFG->updatecronoffset;
1108     }
1110     /**
1111      * Fetch available updates info and eventually send notification to site admins
1112      */
1113     protected function cron_execute() {
1115         try {
1116             $this->restore_response();
1117             $previous = $this->recentresponse;
1118             $this->fetch();
1119             $this->restore_response(true);
1120             $current = $this->recentresponse;
1121             $changes = $this->compare_responses($previous, $current);
1122             $notifications = $this->cron_notifications($changes);
1123             $this->cron_notify($notifications);
1124             $this->cron_mtrace('done');
1125         } catch (available_update_checker_exception $e) {
1126             $this->cron_mtrace('FAILED!');
1127         }
1128     }
1130     /**
1131      * Given the list of changes in available updates, pick those to send to site admins
1132      *
1133      * @param array $changes as returned by {@link self::compare_responses()}
1134      * @return array of available_update_info objects to send to site admins
1135      */
1136     protected function cron_notifications(array $changes) {
1137         global $CFG;
1139         $notifications = array();
1140         $pluginman = plugin_manager::instance();
1141         $plugins = $pluginman->get_plugins(true);
1143         foreach ($changes as $component => $componentchanges) {
1144             if (empty($componentchanges)) {
1145                 continue;
1146             }
1147             $componentupdates = $this->get_update_info($component,
1148                 array('minmaturity' => $CFG->updateminmaturity, 'notifybuilds' => $CFG->updatenotifybuilds));
1149             if (empty($componentupdates)) {
1150                 continue;
1151             }
1152             // notify only about those $componentchanges that are present in $componentupdates
1153             // to respect the preferences
1154             foreach ($componentchanges as $componentchange) {
1155                 foreach ($componentupdates as $componentupdate) {
1156                     if ($componentupdate->version == $componentchange['version']) {
1157                         if ($component == 'core') {
1158                             // in case of 'core' this is enough, we already know that the
1159                             // $componentupdate is a real update with higher version
1160                             $notifications[] = $componentupdate;
1161                         } else {
1162                             // use the plugin_manager to check if the reported $componentchange
1163                             // is a real update with higher version. such a real update must be
1164                             // present in the 'availableupdates' property of one of the component's
1165                             // available_update_info object
1166                             list($plugintype, $pluginname) = normalize_component($component);
1167                             if (!empty($plugins[$plugintype][$pluginname]->availableupdates)) {
1168                                 foreach ($plugins[$plugintype][$pluginname]->availableupdates as $availableupdate) {
1169                                     if ($availableupdate->version == $componentchange['version']) {
1170                                         $notifications[] = $componentupdate;
1171                                     }
1172                                 }
1173                             }
1174                         }
1175                     }
1176                 }
1177             }
1178         }
1180         return $notifications;
1181     }
1183     /**
1184      * Sends the given notifications to site admins via messaging API
1185      *
1186      * @param array $notifications array of available_update_info objects to send
1187      */
1188     protected function cron_notify(array $notifications) {
1189         global $CFG;
1191         if (empty($notifications)) {
1192             return;
1193         }
1195         $admins = get_admins();
1197         if (empty($admins)) {
1198             return;
1199         }
1201         $this->cron_mtrace('sending notifications ... ', '');
1203         $text = get_string('updatenotifications', 'core_admin') . PHP_EOL;
1204         $html = html_writer::tag('h1', get_string('updatenotifications', 'core_admin')) . PHP_EOL;
1206         $coreupdates = array();
1207         $pluginupdates = array();
1209         foreach ($notifications as $notification) {
1210             if ($notification->component == 'core') {
1211                 $coreupdates[] = $notification;
1212             } else {
1213                 $pluginupdates[] = $notification;
1214             }
1215         }
1217         if (!empty($coreupdates)) {
1218             $text .= PHP_EOL . get_string('updateavailable', 'core_admin') . PHP_EOL;
1219             $html .= html_writer::tag('h2', get_string('updateavailable', 'core_admin')) . PHP_EOL;
1220             $html .= html_writer::start_tag('ul') . PHP_EOL;
1221             foreach ($coreupdates as $coreupdate) {
1222                 $html .= html_writer::start_tag('li');
1223                 if (isset($coreupdate->release)) {
1224                     $text .= get_string('updateavailable_release', 'core_admin', $coreupdate->release);
1225                     $html .= html_writer::tag('strong', get_string('updateavailable_release', 'core_admin', $coreupdate->release));
1226                 }
1227                 if (isset($coreupdate->version)) {
1228                     $text .= ' '.get_string('updateavailable_version', 'core_admin', $coreupdate->version);
1229                     $html .= ' '.get_string('updateavailable_version', 'core_admin', $coreupdate->version);
1230                 }
1231                 if (isset($coreupdate->maturity)) {
1232                     $text .= ' ('.get_string('maturity'.$coreupdate->maturity, 'core_admin').')';
1233                     $html .= ' ('.get_string('maturity'.$coreupdate->maturity, 'core_admin').')';
1234                 }
1235                 $text .= PHP_EOL;
1236                 $html .= html_writer::end_tag('li') . PHP_EOL;
1237             }
1238             $text .= PHP_EOL;
1239             $html .= html_writer::end_tag('ul') . PHP_EOL;
1241             $a = array('url' => $CFG->wwwroot.'/'.$CFG->admin.'/index.php');
1242             $text .= get_string('updateavailabledetailslink', 'core_admin', $a) . PHP_EOL;
1243             $a = array('url' => html_writer::link($CFG->wwwroot.'/'.$CFG->admin.'/index.php', $CFG->wwwroot.'/'.$CFG->admin.'/index.php'));
1244             $html .= html_writer::tag('p', get_string('updateavailabledetailslink', 'core_admin', $a)) . PHP_EOL;
1245         }
1247         if (!empty($pluginupdates)) {
1248             $text .= PHP_EOL . get_string('updateavailableforplugin', 'core_admin') . PHP_EOL;
1249             $html .= html_writer::tag('h2', get_string('updateavailableforplugin', 'core_admin')) . PHP_EOL;
1251             $html .= html_writer::start_tag('ul') . PHP_EOL;
1252             foreach ($pluginupdates as $pluginupdate) {
1253                 $html .= html_writer::start_tag('li');
1254                 $text .= get_string('pluginname', $pluginupdate->component);
1255                 $html .= html_writer::tag('strong', get_string('pluginname', $pluginupdate->component));
1257                 $text .= ' ('.$pluginupdate->component.')';
1258                 $html .= ' ('.$pluginupdate->component.')';
1260                 $text .= ' '.get_string('updateavailable', 'core_plugin', $pluginupdate->version);
1261                 $html .= ' '.get_string('updateavailable', 'core_plugin', $pluginupdate->version);
1263                 $text .= PHP_EOL;
1264                 $html .= html_writer::end_tag('li') . PHP_EOL;
1265             }
1266             $text .= PHP_EOL;
1267             $html .= html_writer::end_tag('ul') . PHP_EOL;
1269             $a = array('url' => $CFG->wwwroot.'/'.$CFG->admin.'/plugins.php');
1270             $text .= get_string('updateavailabledetailslink', 'core_admin', $a) . PHP_EOL;
1271             $a = array('url' => html_writer::link($CFG->wwwroot.'/'.$CFG->admin.'/plugins.php', $CFG->wwwroot.'/'.$CFG->admin.'/plugins.php'));
1272             $html .= html_writer::tag('p', get_string('updateavailabledetailslink', 'core_admin', $a)) . PHP_EOL;
1273         }
1275         $a = array('siteurl' => $CFG->wwwroot);
1276         $text .= get_string('updatenotificationfooter', 'core_admin', $a) . PHP_EOL;
1277         $a = array('siteurl' => html_writer::link($CFG->wwwroot, $CFG->wwwroot));
1278         $html .= html_writer::tag('footer', html_writer::tag('p', get_string('updatenotificationfooter', 'core_admin', $a),
1279             array('style' => 'font-size:smaller; color:#333;')));
1281         $mainadmin = reset($admins);
1283         foreach ($admins as $admin) {
1284             $message = new stdClass();
1285             $message->component         = 'moodle';
1286             $message->name              = 'availableupdate';
1287             $message->userfrom          = $mainadmin;
1288             $message->userto            = $admin;
1289             $message->subject           = get_string('updatenotifications', 'core_admin');
1290             $message->fullmessage       = $text;
1291             $message->fullmessageformat = FORMAT_PLAIN;
1292             $message->fullmessagehtml   = $html;
1293             $message->smallmessage      = get_string('updatenotifications', 'core_admin');
1294             $message->notification      = 1;
1295             message_send($message);
1296         }
1297     }
1299     /**
1300      * Compare two release labels and decide if they are the same
1301      *
1302      * @param string $remote release info of the available update
1303      * @param null|string $local release info of the local code, defaults to $release defined in version.php
1304      * @return boolean true if the releases declare the same minor+major version
1305      */
1306     protected function is_same_release($remote, $local=null) {
1308         if (is_null($local)) {
1309             $this->load_current_environment();
1310             $local = $this->currentrelease;
1311         }
1313         $pattern = '/^([0-9\.\+]+)([^(]*)/';
1315         preg_match($pattern, $remote, $remotematches);
1316         preg_match($pattern, $local, $localmatches);
1318         $remotematches[1] = str_replace('+', '', $remotematches[1]);
1319         $localmatches[1] = str_replace('+', '', $localmatches[1]);
1321         if ($remotematches[1] === $localmatches[1] and rtrim($remotematches[2]) === rtrim($localmatches[2])) {
1322             return true;
1323         } else {
1324             return false;
1325         }
1326     }
1330 /**
1331  * Defines the structure of objects returned by {@link available_update_checker::get_update_info()}
1332  */
1333 class available_update_info {
1335     /** @var string frankenstyle component name */
1336     public $component;
1337     /** @var int the available version of the component */
1338     public $version;
1339     /** @var string|null optional release name */
1340     public $release = null;
1341     /** @var int|null optional maturity info, eg {@link MATURITY_STABLE} */
1342     public $maturity = null;
1343     /** @var string|null optional URL of a page with more info about the update */
1344     public $url = null;
1345     /** @var string|null optional URL of a ZIP package that can be downloaded and installed */
1346     public $download = null;
1348     /**
1349      * Creates new instance of the class
1350      *
1351      * The $info array must provide at least the 'version' value and optionally all other
1352      * values to populate the object's properties.
1353      *
1354      * @param string $name the frankenstyle component name
1355      * @param array $info associative array with other properties
1356      */
1357     public function __construct($name, array $info) {
1358         $this->component = $name;
1359         foreach ($info as $k => $v) {
1360             if (property_exists('available_update_info', $k) and $k != 'component') {
1361                 $this->$k = $v;
1362             }
1363         }
1364     }
1368 /**
1369  * Factory class producing required subclasses of {@link plugininfo_base}
1370  */
1371 class plugininfo_default_factory {
1373     /**
1374      * Makes a new instance of the plugininfo class
1375      *
1376      * @param string $type the plugin type, eg. 'mod'
1377      * @param string $typerootdir full path to the location of all the plugins of this type
1378      * @param string $name the plugin name, eg. 'workshop'
1379      * @param string $namerootdir full path to the location of the plugin
1380      * @param string $typeclass the name of class that holds the info about the plugin
1381      * @return plugininfo_base the instance of $typeclass
1382      */
1383     public static function make($type, $typerootdir, $name, $namerootdir, $typeclass) {
1384         $plugin              = new $typeclass();
1385         $plugin->type        = $type;
1386         $plugin->typerootdir = $typerootdir;
1387         $plugin->name        = $name;
1388         $plugin->rootdir     = $namerootdir;
1390         $plugin->init_display_name();
1391         $plugin->load_disk_version();
1392         $plugin->load_db_version();
1393         $plugin->load_required_main_version();
1394         $plugin->init_is_standard();
1396         return $plugin;
1397     }
1401 /**
1402  * Base class providing access to the information about a plugin
1403  *
1404  * @property-read string component the component name, type_name
1405  */
1406 abstract class plugininfo_base {
1408     /** @var string the plugintype name, eg. mod, auth or workshopform */
1409     public $type;
1410     /** @var string full path to the location of all the plugins of this type */
1411     public $typerootdir;
1412     /** @var string the plugin name, eg. assignment, ldap */
1413     public $name;
1414     /** @var string the localized plugin name */
1415     public $displayname;
1416     /** @var string the plugin source, one of plugin_manager::PLUGIN_SOURCE_xxx constants */
1417     public $source;
1418     /** @var fullpath to the location of this plugin */
1419     public $rootdir;
1420     /** @var int|string the version of the plugin's source code */
1421     public $versiondisk;
1422     /** @var int|string the version of the installed plugin */
1423     public $versiondb;
1424     /** @var int|float|string required version of Moodle core  */
1425     public $versionrequires;
1426     /** @var array other plugins that this one depends on, lazy-loaded by {@link get_other_required_plugins()} */
1427     public $dependencies;
1428     /** @var int number of instances of the plugin - not supported yet */
1429     public $instances;
1430     /** @var int order of the plugin among other plugins of the same type - not supported yet */
1431     public $sortorder;
1432     /** @var array|null array of {@link available_update_info} for this plugin */
1433     public $availableupdates;
1435     /**
1436      * Gathers and returns the information about all plugins of the given type
1437      *
1438      * @param string $type the name of the plugintype, eg. mod, auth or workshopform
1439      * @param string $typerootdir full path to the location of the plugin dir
1440      * @param string $typeclass the name of the actually called class
1441      * @return array of plugintype classes, indexed by the plugin name
1442      */
1443     public static function get_plugins($type, $typerootdir, $typeclass) {
1445         // get the information about plugins at the disk
1446         $plugins = get_plugin_list($type);
1447         $ondisk = array();
1448         foreach ($plugins as $pluginname => $pluginrootdir) {
1449             $ondisk[$pluginname] = plugininfo_default_factory::make($type, $typerootdir,
1450                 $pluginname, $pluginrootdir, $typeclass);
1451         }
1452         return $ondisk;
1453     }
1455     /**
1456      * Sets {@link $displayname} property to a localized name of the plugin
1457      */
1458     public function init_display_name() {
1459         if (!get_string_manager()->string_exists('pluginname', $this->component)) {
1460             $this->displayname = '[pluginname,' . $this->component . ']';
1461         } else {
1462             $this->displayname = get_string('pluginname', $this->component);
1463         }
1464     }
1466     /**
1467      * Magic method getter, redirects to read only values.
1468      *
1469      * @param string $name
1470      * @return mixed
1471      */
1472     public function __get($name) {
1473         switch ($name) {
1474             case 'component': return $this->type . '_' . $this->name;
1476             default:
1477                 debugging('Invalid plugin property accessed! '.$name);
1478                 return null;
1479         }
1480     }
1482     /**
1483      * Return the full path name of a file within the plugin.
1484      *
1485      * No check is made to see if the file exists.
1486      *
1487      * @param string $relativepath e.g. 'version.php'.
1488      * @return string e.g. $CFG->dirroot . '/mod/quiz/version.php'.
1489      */
1490     public function full_path($relativepath) {
1491         if (empty($this->rootdir)) {
1492             return '';
1493         }
1494         return $this->rootdir . '/' . $relativepath;
1495     }
1497     /**
1498      * Load the data from version.php.
1499      *
1500      * @return stdClass the object called $plugin defined in version.php
1501      */
1502     protected function load_version_php() {
1503         $versionfile = $this->full_path('version.php');
1505         $plugin = new stdClass();
1506         if (is_readable($versionfile)) {
1507             include($versionfile);
1508         }
1509         return $plugin;
1510     }
1512     /**
1513      * Sets {@link $versiondisk} property to a numerical value representing the
1514      * version of the plugin's source code.
1515      *
1516      * If the value is null after calling this method, either the plugin
1517      * does not use versioning (typically does not have any database
1518      * data) or is missing from disk.
1519      */
1520     public function load_disk_version() {
1521         $plugin = $this->load_version_php();
1522         if (isset($plugin->version)) {
1523             $this->versiondisk = $plugin->version;
1524         }
1525     }
1527     /**
1528      * Sets {@link $versionrequires} property to a numerical value representing
1529      * the version of Moodle core that this plugin requires.
1530      */
1531     public function load_required_main_version() {
1532         $plugin = $this->load_version_php();
1533         if (isset($plugin->requires)) {
1534             $this->versionrequires = $plugin->requires;
1535         }
1536     }
1538     /**
1539      * Initialise {@link $dependencies} to the list of other plugins (in any)
1540      * that this one requires to be installed.
1541      */
1542     protected function load_other_required_plugins() {
1543         $plugin = $this->load_version_php();
1544         if (!empty($plugin->dependencies)) {
1545             $this->dependencies = $plugin->dependencies;
1546         } else {
1547             $this->dependencies = array(); // By default, no dependencies.
1548         }
1549     }
1551     /**
1552      * Get the list of other plugins that this plugin requires to be installed.
1553      *
1554      * @return array with keys the frankenstyle plugin name, and values either
1555      *      a version string (like '2011101700') or the constant ANY_VERSION.
1556      */
1557     public function get_other_required_plugins() {
1558         if (is_null($this->dependencies)) {
1559             $this->load_other_required_plugins();
1560         }
1561         return $this->dependencies;
1562     }
1564     /**
1565      * Sets {@link $versiondb} property to a numerical value representing the
1566      * currently installed version of the plugin.
1567      *
1568      * If the value is null after calling this method, either the plugin
1569      * does not use versioning (typically does not have any database
1570      * data) or has not been installed yet.
1571      */
1572     public function load_db_version() {
1573         if ($ver = self::get_version_from_config_plugins($this->component)) {
1574             $this->versiondb = $ver;
1575         }
1576     }
1578     /**
1579      * Sets {@link $source} property to one of plugin_manager::PLUGIN_SOURCE_xxx
1580      * constants.
1581      *
1582      * If the property's value is null after calling this method, then
1583      * the type of the plugin has not been recognized and you should throw
1584      * an exception.
1585      */
1586     public function init_is_standard() {
1588         $standard = plugin_manager::standard_plugins_list($this->type);
1590         if ($standard !== false) {
1591             $standard = array_flip($standard);
1592             if (isset($standard[$this->name])) {
1593                 $this->source = plugin_manager::PLUGIN_SOURCE_STANDARD;
1594             } else if (!is_null($this->versiondb) and is_null($this->versiondisk)
1595                     and plugin_manager::is_deleted_standard_plugin($this->type, $this->name)) {
1596                 $this->source = plugin_manager::PLUGIN_SOURCE_STANDARD; // to be deleted
1597             } else {
1598                 $this->source = plugin_manager::PLUGIN_SOURCE_EXTENSION;
1599             }
1600         }
1601     }
1603     /**
1604      * Returns true if the plugin is shipped with the official distribution
1605      * of the current Moodle version, false otherwise.
1606      *
1607      * @return bool
1608      */
1609     public function is_standard() {
1610         return $this->source === plugin_manager::PLUGIN_SOURCE_STANDARD;
1611     }
1613     /**
1614      * Returns the status of the plugin
1615      *
1616      * @return string one of plugin_manager::PLUGIN_STATUS_xxx constants
1617      */
1618     public function get_status() {
1620         if (is_null($this->versiondb) and is_null($this->versiondisk)) {
1621             return plugin_manager::PLUGIN_STATUS_NODB;
1623         } else if (is_null($this->versiondb) and !is_null($this->versiondisk)) {
1624             return plugin_manager::PLUGIN_STATUS_NEW;
1626         } else if (!is_null($this->versiondb) and is_null($this->versiondisk)) {
1627             if (plugin_manager::is_deleted_standard_plugin($this->type, $this->name)) {
1628                 return plugin_manager::PLUGIN_STATUS_DELETE;
1629             } else {
1630                 return plugin_manager::PLUGIN_STATUS_MISSING;
1631             }
1633         } else if ((string)$this->versiondb === (string)$this->versiondisk) {
1634             return plugin_manager::PLUGIN_STATUS_UPTODATE;
1636         } else if ($this->versiondb < $this->versiondisk) {
1637             return plugin_manager::PLUGIN_STATUS_UPGRADE;
1639         } else if ($this->versiondb > $this->versiondisk) {
1640             return plugin_manager::PLUGIN_STATUS_DOWNGRADE;
1642         } else {
1643             // $version = pi(); and similar funny jokes - hopefully Donald E. Knuth will never contribute to Moodle ;-)
1644             throw new coding_exception('Unable to determine plugin state, check the plugin versions');
1645         }
1646     }
1648     /**
1649      * Returns the information about plugin availability
1650      *
1651      * True means that the plugin is enabled. False means that the plugin is
1652      * disabled. Null means that the information is not available, or the
1653      * plugin does not support configurable availability or the availability
1654      * can not be changed.
1655      *
1656      * @return null|bool
1657      */
1658     public function is_enabled() {
1659         return null;
1660     }
1662     /**
1663      * Populates the property {@link $availableupdates} with the information provided by
1664      * available update checker
1665      *
1666      * @param available_update_checker $provider the class providing the available update info
1667      */
1668     public function check_available_updates(available_update_checker $provider) {
1669         global $CFG;
1671         if (isset($CFG->updateminmaturity)) {
1672             $minmaturity = $CFG->updateminmaturity;
1673         } else {
1674             // this can happen during the very first upgrade to 2.3
1675             $minmaturity = MATURITY_STABLE;
1676         }
1678         $this->availableupdates = $provider->get_update_info($this->component,
1679             array('minmaturity' => $minmaturity));
1680     }
1682     /**
1683      * If there are updates for this plugin available, returns them.
1684      *
1685      * Returns array of {@link available_update_info} objects, if some update
1686      * is available. Returns null if there is no update available or if the update
1687      * availability is unknown.
1688      *
1689      * @return array|null
1690      */
1691     public function available_updates() {
1693         if (empty($this->availableupdates) or !is_array($this->availableupdates)) {
1694             return null;
1695         }
1697         $updates = array();
1699         foreach ($this->availableupdates as $availableupdate) {
1700             if ($availableupdate->version > $this->versiondisk) {
1701                 $updates[] = $availableupdate;
1702             }
1703         }
1705         if (empty($updates)) {
1706             return null;
1707         }
1709         return $updates;
1710     }
1712     /**
1713      * Returns the URL of the plugin settings screen
1714      *
1715      * Null value means that the plugin either does not have the settings screen
1716      * or its location is not available via this library.
1717      *
1718      * @return null|moodle_url
1719      */
1720     public function get_settings_url() {
1721         return null;
1722     }
1724     /**
1725      * Returns the URL of the screen where this plugin can be uninstalled
1726      *
1727      * Visiting that URL must be safe, that is a manual confirmation is needed
1728      * for actual uninstallation of the plugin. Null value means that the
1729      * plugin either does not support uninstallation, or does not require any
1730      * database cleanup or the location of the screen is not available via this
1731      * library.
1732      *
1733      * @return null|moodle_url
1734      */
1735     public function get_uninstall_url() {
1736         return null;
1737     }
1739     /**
1740      * Returns relative directory of the plugin with heading '/'
1741      *
1742      * @return string
1743      */
1744     public function get_dir() {
1745         global $CFG;
1747         return substr($this->rootdir, strlen($CFG->dirroot));
1748     }
1750     /**
1751      * Provides access to plugin versions from {config_plugins}
1752      *
1753      * @param string $plugin plugin name
1754      * @param double $disablecache optional, defaults to false
1755      * @return int|false the stored value or false if not found
1756      */
1757     protected function get_version_from_config_plugins($plugin, $disablecache=false) {
1758         global $DB;
1759         static $pluginversions = null;
1761         if (is_null($pluginversions) or $disablecache) {
1762             try {
1763                 $pluginversions = $DB->get_records_menu('config_plugins', array('name' => 'version'), 'plugin', 'plugin,value');
1764             } catch (dml_exception $e) {
1765                 // before install
1766                 $pluginversions = array();
1767             }
1768         }
1770         if (!array_key_exists($plugin, $pluginversions)) {
1771             return false;
1772         }
1774         return $pluginversions[$plugin];
1775     }
1779 /**
1780  * General class for all plugin types that do not have their own class
1781  */
1782 class plugininfo_general extends plugininfo_base {
1786 /**
1787  * Class for page side blocks
1788  */
1789 class plugininfo_block extends plugininfo_base {
1791     public static function get_plugins($type, $typerootdir, $typeclass) {
1793         // get the information about blocks at the disk
1794         $blocks = parent::get_plugins($type, $typerootdir, $typeclass);
1796         // add blocks missing from disk
1797         $blocksinfo = self::get_blocks_info();
1798         foreach ($blocksinfo as $blockname => $blockinfo) {
1799             if (isset($blocks[$blockname])) {
1800                 continue;
1801             }
1802             $plugin                 = new $typeclass();
1803             $plugin->type           = $type;
1804             $plugin->typerootdir    = $typerootdir;
1805             $plugin->name           = $blockname;
1806             $plugin->rootdir        = null;
1807             $plugin->displayname    = $blockname;
1808             $plugin->versiondb      = $blockinfo->version;
1809             $plugin->init_is_standard();
1811             $blocks[$blockname]   = $plugin;
1812         }
1814         return $blocks;
1815     }
1817     public function init_display_name() {
1819         if (get_string_manager()->string_exists('pluginname', 'block_' . $this->name)) {
1820             $this->displayname = get_string('pluginname', 'block_' . $this->name);
1822         } else if (($block = block_instance($this->name)) !== false) {
1823             $this->displayname = $block->get_title();
1825         } else {
1826             parent::init_display_name();
1827         }
1828     }
1830     public function load_db_version() {
1831         global $DB;
1833         $blocksinfo = self::get_blocks_info();
1834         if (isset($blocksinfo[$this->name]->version)) {
1835             $this->versiondb = $blocksinfo[$this->name]->version;
1836         }
1837     }
1839     public function is_enabled() {
1841         $blocksinfo = self::get_blocks_info();
1842         if (isset($blocksinfo[$this->name]->visible)) {
1843             if ($blocksinfo[$this->name]->visible) {
1844                 return true;
1845             } else {
1846                 return false;
1847             }
1848         } else {
1849             return parent::is_enabled();
1850         }
1851     }
1853     public function get_settings_url() {
1855         if (($block = block_instance($this->name)) === false) {
1856             return parent::get_settings_url();
1858         } else if ($block->has_config()) {
1859             if (file_exists($this->full_path('settings.php'))) {
1860                 return new moodle_url('/admin/settings.php', array('section' => 'blocksetting' . $this->name));
1861             } else {
1862                 $blocksinfo = self::get_blocks_info();
1863                 return new moodle_url('/admin/block.php', array('block' => $blocksinfo[$this->name]->id));
1864             }
1866         } else {
1867             return parent::get_settings_url();
1868         }
1869     }
1871     public function get_uninstall_url() {
1873         $blocksinfo = self::get_blocks_info();
1874         return new moodle_url('/admin/blocks.php', array('delete' => $blocksinfo[$this->name]->id, 'sesskey' => sesskey()));
1875     }
1877     /**
1878      * Provides access to the records in {block} table
1879      *
1880      * @param bool $disablecache do not use internal static cache
1881      * @return array array of stdClasses
1882      */
1883     protected static function get_blocks_info($disablecache=false) {
1884         global $DB;
1885         static $blocksinfocache = null;
1887         if (is_null($blocksinfocache) or $disablecache) {
1888             try {
1889                 $blocksinfocache = $DB->get_records('block', null, 'name', 'name,id,version,visible');
1890             } catch (dml_exception $e) {
1891                 // before install
1892                 $blocksinfocache = array();
1893             }
1894         }
1896         return $blocksinfocache;
1897     }
1901 /**
1902  * Class for text filters
1903  */
1904 class plugininfo_filter extends plugininfo_base {
1906     public static function get_plugins($type, $typerootdir, $typeclass) {
1907         global $CFG, $DB;
1909         $filters = array();
1911         // get the list of filters from both /filter and /mod location
1912         $installed = filter_get_all_installed();
1914         foreach ($installed as $filterlegacyname => $displayname) {
1915             $plugin                 = new $typeclass();
1916             $plugin->type           = $type;
1917             $plugin->typerootdir    = $typerootdir;
1918             $plugin->name           = self::normalize_legacy_name($filterlegacyname);
1919             $plugin->rootdir        = $CFG->dirroot . '/' . $filterlegacyname;
1920             $plugin->displayname    = $displayname;
1922             $plugin->load_disk_version();
1923             $plugin->load_db_version();
1924             $plugin->load_required_main_version();
1925             $plugin->init_is_standard();
1927             $filters[$plugin->name] = $plugin;
1928         }
1930         $globalstates = self::get_global_states();
1932         if ($DB->get_manager()->table_exists('filter_active')) {
1933             // if we're upgrading from 1.9, the table does not exist yet
1934             // if it does, make sure that all installed filters are registered
1935             $needsreload  = false;
1936             foreach (array_keys($installed) as $filterlegacyname) {
1937                 if (!isset($globalstates[self::normalize_legacy_name($filterlegacyname)])) {
1938                     filter_set_global_state($filterlegacyname, TEXTFILTER_DISABLED);
1939                     $needsreload = true;
1940                 }
1941             }
1942             if ($needsreload) {
1943                 $globalstates = self::get_global_states(true);
1944             }
1945         }
1947         // make sure that all registered filters are installed, just in case
1948         foreach ($globalstates as $name => $info) {
1949             if (!isset($filters[$name])) {
1950                 // oops, there is a record in filter_active but the filter is not installed
1951                 $plugin                 = new $typeclass();
1952                 $plugin->type           = $type;
1953                 $plugin->typerootdir    = $typerootdir;
1954                 $plugin->name           = $name;
1955                 $plugin->rootdir        = $CFG->dirroot . '/' . $info->legacyname;
1956                 $plugin->displayname    = $info->legacyname;
1958                 $plugin->load_db_version();
1960                 if (is_null($plugin->versiondb)) {
1961                     // this is a hack to stimulate 'Missing from disk' error
1962                     // because $plugin->versiondisk will be null !== false
1963                     $plugin->versiondb = false;
1964                 }
1966                 $filters[$plugin->name] = $plugin;
1967             }
1968         }
1970         return $filters;
1971     }
1973     public function init_display_name() {
1974         // do nothing, the name is set in self::get_plugins()
1975     }
1977     /**
1978      * @see load_version_php()
1979      */
1980     protected function load_version_php() {
1981         if (strpos($this->name, 'mod_') === 0) {
1982             // filters bundled with modules do not have a version.php and so
1983             // do not provide their own versioning information.
1984             return new stdClass();
1985         }
1986         return parent::load_version_php();
1987     }
1989     public function is_enabled() {
1991         $globalstates = self::get_global_states();
1993         foreach ($globalstates as $filterlegacyname => $info) {
1994             $name = self::normalize_legacy_name($filterlegacyname);
1995             if ($name === $this->name) {
1996                 if ($info->active == TEXTFILTER_DISABLED) {
1997                     return false;
1998                 } else {
1999                     // it may be 'On' or 'Off, but available'
2000                     return null;
2001                 }
2002             }
2003         }
2005         return null;
2006     }
2008     public function get_settings_url() {
2010         $globalstates = self::get_global_states();
2011         $legacyname = $globalstates[$this->name]->legacyname;
2012         if (filter_has_global_settings($legacyname)) {
2013             return new moodle_url('/admin/settings.php', array('section' => 'filtersetting' . str_replace('/', '', $legacyname)));
2014         } else {
2015             return null;
2016         }
2017     }
2019     public function get_uninstall_url() {
2021         if (strpos($this->name, 'mod_') === 0) {
2022             return null;
2023         } else {
2024             $globalstates = self::get_global_states();
2025             $legacyname = $globalstates[$this->name]->legacyname;
2026             return new moodle_url('/admin/filters.php', array('sesskey' => sesskey(), 'filterpath' => $legacyname, 'action' => 'delete'));
2027         }
2028     }
2030     /**
2031      * Convert legacy filter names like 'filter/foo' or 'mod/bar' into frankenstyle
2032      *
2033      * @param string $legacyfiltername legacy filter name
2034      * @return string frankenstyle-like name
2035      */
2036     protected static function normalize_legacy_name($legacyfiltername) {
2038         $name = str_replace('/', '_', $legacyfiltername);
2039         if (strpos($name, 'filter_') === 0) {
2040             $name = substr($name, 7);
2041             if (empty($name)) {
2042                 throw new coding_exception('Unable to determine filter name: ' . $legacyfiltername);
2043             }
2044         }
2046         return $name;
2047     }
2049     /**
2050      * Provides access to the results of {@link filter_get_global_states()}
2051      * but indexed by the normalized filter name
2052      *
2053      * The legacy filter name is available as ->legacyname property.
2054      *
2055      * @param bool $disablecache
2056      * @return array
2057      */
2058     protected static function get_global_states($disablecache=false) {
2059         global $DB;
2060         static $globalstatescache = null;
2062         if ($disablecache or is_null($globalstatescache)) {
2064             if (!$DB->get_manager()->table_exists('filter_active')) {
2065                 // we're upgrading from 1.9 and the table used by {@link filter_get_global_states()}
2066                 // does not exist yet
2067                 $globalstatescache = array();
2069             } else {
2070                 foreach (filter_get_global_states() as $legacyname => $info) {
2071                     $name                       = self::normalize_legacy_name($legacyname);
2072                     $filterinfo                 = new stdClass();
2073                     $filterinfo->legacyname     = $legacyname;
2074                     $filterinfo->active         = $info->active;
2075                     $filterinfo->sortorder      = $info->sortorder;
2076                     $globalstatescache[$name]   = $filterinfo;
2077                 }
2078             }
2079         }
2081         return $globalstatescache;
2082     }
2086 /**
2087  * Class for activity modules
2088  */
2089 class plugininfo_mod extends plugininfo_base {
2091     public static function get_plugins($type, $typerootdir, $typeclass) {
2093         // get the information about plugins at the disk
2094         $modules = parent::get_plugins($type, $typerootdir, $typeclass);
2096         // add modules missing from disk
2097         $modulesinfo = self::get_modules_info();
2098         foreach ($modulesinfo as $modulename => $moduleinfo) {
2099             if (isset($modules[$modulename])) {
2100                 continue;
2101             }
2102             $plugin                 = new $typeclass();
2103             $plugin->type           = $type;
2104             $plugin->typerootdir    = $typerootdir;
2105             $plugin->name           = $modulename;
2106             $plugin->rootdir        = null;
2107             $plugin->displayname    = $modulename;
2108             $plugin->versiondb      = $moduleinfo->version;
2109             $plugin->init_is_standard();
2111             $modules[$modulename]   = $plugin;
2112         }
2114         return $modules;
2115     }
2117     public function init_display_name() {
2118         if (get_string_manager()->string_exists('pluginname', $this->component)) {
2119             $this->displayname = get_string('pluginname', $this->component);
2120         } else {
2121             $this->displayname = get_string('modulename', $this->component);
2122         }
2123     }
2125     /**
2126      * Load the data from version.php.
2127      * @return object the data object defined in version.php.
2128      */
2129     protected function load_version_php() {
2130         $versionfile = $this->full_path('version.php');
2132         $module = new stdClass();
2133         if (is_readable($versionfile)) {
2134             include($versionfile);
2135         }
2136         return $module;
2137     }
2139     public function load_db_version() {
2140         global $DB;
2142         $modulesinfo = self::get_modules_info();
2143         if (isset($modulesinfo[$this->name]->version)) {
2144             $this->versiondb = $modulesinfo[$this->name]->version;
2145         }
2146     }
2148     public function is_enabled() {
2150         $modulesinfo = self::get_modules_info();
2151         if (isset($modulesinfo[$this->name]->visible)) {
2152             if ($modulesinfo[$this->name]->visible) {
2153                 return true;
2154             } else {
2155                 return false;
2156             }
2157         } else {
2158             return parent::is_enabled();
2159         }
2160     }
2162     public function get_settings_url() {
2164         if (file_exists($this->full_path('settings.php')) or file_exists($this->full_path('settingstree.php'))) {
2165             return new moodle_url('/admin/settings.php', array('section' => 'modsetting' . $this->name));
2166         } else {
2167             return parent::get_settings_url();
2168         }
2169     }
2171     public function get_uninstall_url() {
2173         if ($this->name !== 'forum') {
2174             return new moodle_url('/admin/modules.php', array('delete' => $this->name, 'sesskey' => sesskey()));
2175         } else {
2176             return null;
2177         }
2178     }
2180     /**
2181      * Provides access to the records in {modules} table
2182      *
2183      * @param bool $disablecache do not use internal static cache
2184      * @return array array of stdClasses
2185      */
2186     protected static function get_modules_info($disablecache=false) {
2187         global $DB;
2188         static $modulesinfocache = null;
2190         if (is_null($modulesinfocache) or $disablecache) {
2191             try {
2192                 $modulesinfocache = $DB->get_records('modules', null, 'name', 'name,id,version,visible');
2193             } catch (dml_exception $e) {
2194                 // before install
2195                 $modulesinfocache = array();
2196             }
2197         }
2199         return $modulesinfocache;
2200     }
2204 /**
2205  * Class for question behaviours.
2206  */
2207 class plugininfo_qbehaviour extends plugininfo_base {
2209     public function get_uninstall_url() {
2210         return new moodle_url('/admin/qbehaviours.php',
2211                 array('delete' => $this->name, 'sesskey' => sesskey()));
2212     }
2216 /**
2217  * Class for question types
2218  */
2219 class plugininfo_qtype extends plugininfo_base {
2221     public function get_uninstall_url() {
2222         return new moodle_url('/admin/qtypes.php',
2223                 array('delete' => $this->name, 'sesskey' => sesskey()));
2224     }
2228 /**
2229  * Class for authentication plugins
2230  */
2231 class plugininfo_auth extends plugininfo_base {
2233     public function is_enabled() {
2234         global $CFG;
2235         /** @var null|array list of enabled authentication plugins */
2236         static $enabled = null;
2238         if (in_array($this->name, array('nologin', 'manual'))) {
2239             // these two are always enabled and can't be disabled
2240             return null;
2241         }
2243         if (is_null($enabled)) {
2244             $enabled = array_flip(explode(',', $CFG->auth));
2245         }
2247         return isset($enabled[$this->name]);
2248     }
2250     public function get_settings_url() {
2251         if (file_exists($this->full_path('settings.php'))) {
2252             return new moodle_url('/admin/settings.php', array('section' => 'authsetting' . $this->name));
2253         } else {
2254             return new moodle_url('/admin/auth_config.php', array('auth' => $this->name));
2255         }
2256     }
2260 /**
2261  * Class for enrolment plugins
2262  */
2263 class plugininfo_enrol extends plugininfo_base {
2265     public function is_enabled() {
2266         global $CFG;
2267         /** @var null|array list of enabled enrolment plugins */
2268         static $enabled = null;
2270         // We do not actually need whole enrolment classes here so we do not call
2271         // {@link enrol_get_plugins()}. Note that this may produce slightly different
2272         // results, for example if the enrolment plugin does not contain lib.php
2273         // but it is listed in $CFG->enrol_plugins_enabled
2275         if (is_null($enabled)) {
2276             $enabled = array_flip(explode(',', $CFG->enrol_plugins_enabled));
2277         }
2279         return isset($enabled[$this->name]);
2280     }
2282     public function get_settings_url() {
2284         if ($this->is_enabled() or file_exists($this->full_path('settings.php'))) {
2285             return new moodle_url('/admin/settings.php', array('section' => 'enrolsettings' . $this->name));
2286         } else {
2287             return parent::get_settings_url();
2288         }
2289     }
2291     public function get_uninstall_url() {
2292         return new moodle_url('/admin/enrol.php', array('action' => 'uninstall', 'enrol' => $this->name, 'sesskey' => sesskey()));
2293     }
2297 /**
2298  * Class for messaging processors
2299  */
2300 class plugininfo_message extends plugininfo_base {
2302     public function get_settings_url() {
2303         $processors = get_message_processors();
2304         if (isset($processors[$this->name])) {
2305             $processor = $processors[$this->name];
2306             if ($processor->available && $processor->hassettings) {
2307                 return new moodle_url('settings.php', array('section' => 'messagesetting'.$processor->name));
2308             }
2309         }
2310         return parent::get_settings_url();
2311     }
2313     /**
2314      * @see plugintype_interface::is_enabled()
2315      */
2316     public function is_enabled() {
2317         $processors = get_message_processors();
2318         if (isset($processors[$this->name])) {
2319             return $processors[$this->name]->configured && $processors[$this->name]->enabled;
2320         } else {
2321             return parent::is_enabled();
2322         }
2323     }
2325     /**
2326      * @see plugintype_interface::get_uninstall_url()
2327      */
2328     public function get_uninstall_url() {
2329         $processors = get_message_processors();
2330         if (isset($processors[$this->name])) {
2331             return new moodle_url('message.php', array('uninstall' => $processors[$this->name]->id, 'sesskey' => sesskey()));
2332         } else {
2333             return parent::get_uninstall_url();
2334         }
2335     }
2339 /**
2340  * Class for repositories
2341  */
2342 class plugininfo_repository extends plugininfo_base {
2344     public function is_enabled() {
2346         $enabled = self::get_enabled_repositories();
2348         return isset($enabled[$this->name]);
2349     }
2351     public function get_settings_url() {
2353         if ($this->is_enabled()) {
2354             return new moodle_url('/admin/repository.php', array('sesskey' => sesskey(), 'action' => 'edit', 'repos' => $this->name));
2355         } else {
2356             return parent::get_settings_url();
2357         }
2358     }
2360     /**
2361      * Provides access to the records in {repository} table
2362      *
2363      * @param bool $disablecache do not use internal static cache
2364      * @return array array of stdClasses
2365      */
2366     protected static function get_enabled_repositories($disablecache=false) {
2367         global $DB;
2368         static $repositories = null;
2370         if (is_null($repositories) or $disablecache) {
2371             $repositories = $DB->get_records('repository', null, 'type', 'type,visible,sortorder');
2372         }
2374         return $repositories;
2375     }
2379 /**
2380  * Class for portfolios
2381  */
2382 class plugininfo_portfolio extends plugininfo_base {
2384     public function is_enabled() {
2386         $enabled = self::get_enabled_portfolios();
2388         return isset($enabled[$this->name]);
2389     }
2391     /**
2392      * Provides access to the records in {portfolio_instance} table
2393      *
2394      * @param bool $disablecache do not use internal static cache
2395      * @return array array of stdClasses
2396      */
2397     protected static function get_enabled_portfolios($disablecache=false) {
2398         global $DB;
2399         static $portfolios = null;
2401         if (is_null($portfolios) or $disablecache) {
2402             $portfolios = array();
2403             $instances  = $DB->get_recordset('portfolio_instance', null, 'plugin');
2404             foreach ($instances as $instance) {
2405                 if (isset($portfolios[$instance->plugin])) {
2406                     if ($instance->visible) {
2407                         $portfolios[$instance->plugin]->visible = $instance->visible;
2408                     }
2409                 } else {
2410                     $portfolios[$instance->plugin] = $instance;
2411                 }
2412             }
2413         }
2415         return $portfolios;
2416     }
2420 /**
2421  * Class for themes
2422  */
2423 class plugininfo_theme extends plugininfo_base {
2425     public function is_enabled() {
2426         global $CFG;
2428         if ((!empty($CFG->theme) and $CFG->theme === $this->name) or
2429             (!empty($CFG->themelegacy) and $CFG->themelegacy === $this->name)) {
2430             return true;
2431         } else {
2432             return parent::is_enabled();
2433         }
2434     }
2438 /**
2439  * Class representing an MNet service
2440  */
2441 class plugininfo_mnetservice extends plugininfo_base {
2443     public function is_enabled() {
2444         global $CFG;
2446         if (empty($CFG->mnet_dispatcher_mode) || $CFG->mnet_dispatcher_mode !== 'strict') {
2447             return false;
2448         } else {
2449             return parent::is_enabled();
2450         }
2451     }
2455 /**
2456  * Class for admin tool plugins
2457  */
2458 class plugininfo_tool extends plugininfo_base {
2460     public function get_uninstall_url() {
2461         return new moodle_url('/admin/tools.php', array('delete' => $this->name, 'sesskey' => sesskey()));
2462     }
2466 /**
2467  * Class for admin tool plugins
2468  */
2469 class plugininfo_report extends plugininfo_base {
2471     public function get_uninstall_url() {
2472         return new moodle_url('/admin/reports.php', array('delete' => $this->name, 'sesskey' => sesskey()));
2473     }