MDL-54592 cachestore_mongodb: MongoDB cache store use new driver.
[moodle.git] / lib / classes / component.php
CommitLineData
9e19a0f0
PS
1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * Components (core subsystems + plugins) related code.
19 *
20 * @package core
21 * @copyright 2013 Petr Skoda {@link http://skodak.org}
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
abb043c3
PS
25defined('MOODLE_INTERNAL') || die();
26
81881cb9
PS
27// Constants used in version.php files, these must exist when core_component executes.
28
29/** Software maturity level - internals can be tested using white box techniques. */
30define('MATURITY_ALPHA', 50);
31/** Software maturity level - feature complete, ready for preview and testing. */
32define('MATURITY_BETA', 100);
33/** Software maturity level - tested, will be released unless there are fatal bugs. */
34define('MATURITY_RC', 150);
35/** Software maturity level - ready for production deployment. */
36define('MATURITY_STABLE', 200);
37/** Any version - special value that can be used in $plugin->dependencies in version.php files. */
38define('ANY_VERSION', 'any');
39
40
9e19a0f0
PS
41/**
42 * Collection of components related methods.
43 */
44class core_component {
45 /** @var array list of ignored directories - watch out for auth/db exception */
9ba6076c 46 protected static $ignoreddirs = array('CVS'=>true, '_vti_cnf'=>true, 'simpletest'=>true, 'db'=>true, 'yui'=>true, 'tests'=>true, 'classes'=>true, 'fonts'=>true);
9e19a0f0 47 /** @var array list plugin types that support subplugins, do not add more here unless absolutely necessary */
ac2b2713 48 protected static $supportsubplugins = array('mod', 'editor', 'tool', 'local');
9e19a0f0 49
37e60007 50 /** @var array cache of plugin types */
9e19a0f0 51 protected static $plugintypes = null;
37e60007 52 /** @var array cache of plugin locations */
9e19a0f0 53 protected static $plugins = null;
37e60007 54 /** @var array cache of core subsystems */
9e19a0f0 55 protected static $subsystems = null;
37e60007 56 /** @var array subplugin type parents */
e87214bd 57 protected static $parents = null;
37e60007 58 /** @var array subplugins */
e87214bd 59 protected static $subplugins = null;
37e60007 60 /** @var array list of all known classes that can be autoloaded */
9e19a0f0 61 protected static $classmap = null;
37e60007
SH
62 /** @var array list of all classes that have been renamed to be autoloaded */
63 protected static $classmaprenames = null;
64 /** @var array list of some known files that can be included. */
d26ec8a5 65 protected static $filemap = null;
3274c5db
FM
66 /** @var int|float core version. */
67 protected static $version = null;
d26ec8a5
FM
68 /** @var array list of the files to map. */
69 protected static $filestomap = array('lib.php', 'settings.php');
64f631c1
JO
70 /** @var array associative array of PSR-0 namespaces and corresponding paths. */
71 protected static $psr0namespaces = array(
a57e3d3b
DP
72 'Horde' => 'lib/horde/framework/Horde',
73 'Mustache' => 'lib/mustache/src/Mustache',
64f631c1
JO
74 );
75 /** @var array associative array of PRS-4 namespaces and corresponding paths. */
76 protected static $psr4namespaces = array(
eacc36a2 77 'MaxMind' => 'lib/maxmind/MaxMind',
948e6e3f 78 'GeoIp2' => 'lib/maxmind/GeoIp2',
5d9f0bb3
DW
79 'Sabberworm\\CSS' => 'lib/php-css-parser',
80 'MoodleHQ\\RTLCSS' => 'lib/rtlcss',
dd9ee3d8 81 'Leafo\\ScssPhp' => 'lib/scssphp',
4cb24aa9
DP
82 'Box\\Spout' => 'lib/spout/src/Spout',
83 'MatthiasMullie\\Minify' => 'lib/minify/matthiasmullie-minify/src/',
84 'MatthiasMullie\\PathConverter' => 'lib/minify/matthiasmullie-pathconverter/src/',
a1f3821e 85 'IMSGlobal\LTI' => 'lib/ltiprovider/src',
413f19bc 86 'Phpml' => 'lib/mlbackend/php/phpml/src/Phpml',
56e62bbb 87 'PHPMailer\\PHPMailer' => 'lib/phpmailer/src',
c8cc25ca 88 'RedeyeVentures\\GeoPattern' => 'lib/geopattern-php/GeoPattern',
2a944b5b 89 'MongoDB' => 'cache/stores/mongodb/MongoDB',
91c07d06 90 );
9e19a0f0
PS
91
92 /**
93 * Class loader for Frankenstyle named classes in standard locations.
94 * Frankenstyle namespaces are supported.
95 *
96 * The expected location for core classes is:
97 * 1/ core_xx_yy_zz ---> lib/classes/xx_yy_zz.php
98 * 2/ \core\xx_yy_zz ---> lib/classes/xx_yy_zz.php
99 * 3/ \core\xx\yy_zz ---> lib/classes/xx/yy_zz.php
100 *
101 * The expected location for plugin classes is:
102 * 1/ mod_name_xx_yy_zz ---> mod/name/classes/xx_yy_zz.php
103 * 2/ \mod_name\xx_yy_zz ---> mod/name/classes/xx_yy_zz.php
104 * 3/ \mod_name\xx\yy_zz ---> mod/name/classes/xx/yy_zz.php
105 *
106 * @param string $classname
107 */
108 public static function classloader($classname) {
109 self::init();
110
111 if (isset(self::$classmap[$classname])) {
112 // Global $CFG is expected in included scripts.
113 global $CFG;
114 // Function include would be faster, but for BC it is better to include only once.
115 include_once(self::$classmap[$classname]);
116 return;
117 }
37e60007
SH
118 if (isset(self::$classmaprenames[$classname]) && isset(self::$classmap[self::$classmaprenames[$classname]])) {
119 $newclassname = self::$classmaprenames[$classname];
120 $debugging = "Class '%s' has been renamed for the autoloader and is now deprecated. Please use '%s' instead.";
121 debugging(sprintf($debugging, $classname, $newclassname), DEBUG_DEVELOPER);
303936aa
TL
122 if (PHP_VERSION_ID >= 70000 && preg_match('#\\\null(\\\|$)#', $classname)) {
123 throw new \coding_exception("Cannot alias $classname to $newclassname");
124 }
37e60007
SH
125 class_alias($newclassname, $classname);
126 return;
127 }
4ba38f2a 128
64f631c1
JO
129 $file = self::psr_classloader($classname);
130 // If the file is found, require it.
131 if (!empty($file)) {
132 require($file);
4ba38f2a
AN
133 return;
134 }
9e19a0f0
PS
135 }
136
91c07d06 137 /**
64f631c1
JO
138 * Return the path to a class from our defined PSR-0 or PSR-4 standard namespaces on
139 * demand. Only returns paths to files that exist.
91c07d06
DP
140 *
141 * Adapated from http://www.php-fig.org/psr/psr-4/examples/ and made PSR-0
142 * compatible.
143 *
64f631c1
JO
144 * @param string $class the name of the class.
145 * @return string|bool The full path to the file defining the class. Or false if it could not be resolved or does not exist.
91c07d06
DP
146 */
147 protected static function psr_classloader($class) {
64f631c1
JO
148 // Iterate through each PSR-4 namespace prefix.
149 foreach (self::$psr4namespaces as $prefix => $path) {
150 $file = self::get_class_file($class, $prefix, $path, array('\\'));
151 if (!empty($file) && file_exists($file)) {
152 return $file;
91c07d06 153 }
64f631c1 154 }
91c07d06 155
64f631c1
JO
156 // Iterate through each PSR-0 namespace prefix.
157 foreach (self::$psr0namespaces as $prefix => $path) {
158 $file = self::get_class_file($class, $prefix, $path, array('\\', '_'));
159 if (!empty($file) && file_exists($file)) {
160 return $file;
161 }
162 }
91c07d06 163
64f631c1
JO
164 return false;
165 }
91c07d06 166
64f631c1
JO
167 /**
168 * Return the path to the class based on the given namespace prefix and path it corresponds to.
169 *
170 * Will return the path even if the file does not exist. Check the file esists before requiring.
171 *
172 * @param string $class the name of the class.
173 * @param string $prefix The namespace prefix used to identify the base directory of the source files.
174 * @param string $path The relative path to the base directory of the source files.
175 * @param string[] $separators The characters that should be used for separating.
176 * @return string|bool The full path to the file defining the class. Or false if it could not be resolved.
177 */
178 protected static function get_class_file($class, $prefix, $path, $separators) {
179 global $CFG;
91c07d06 180
64f631c1
JO
181 // Does the class use the namespace prefix?
182 $len = strlen($prefix);
183 if (strncmp($prefix, $class, $len) !== 0) {
184 // No, move to the next prefix.
185 return false;
91c07d06 186 }
64f631c1 187 $path = $CFG->dirroot . '/' . $path;
91c07d06 188
64f631c1
JO
189 // Get the relative class name.
190 $relativeclass = substr($class, $len);
191
192 // Replace the namespace prefix with the base directory, replace namespace
193 // separators with directory separators in the relative class name, append
194 // with .php.
195 $file = $path . str_replace($separators, '/', $relativeclass) . '.php';
196
197 return $file;
91c07d06
DP
198 }
199
200
9e19a0f0
PS
201 /**
202 * Initialise caches, always call before accessing self:: caches.
203 */
204 protected static function init() {
205 global $CFG;
206
207 // Init only once per request/CLI execution, we ignore changes done afterwards.
208 if (isset(self::$plugintypes)) {
209 return;
210 }
211
d7245e34 212 if (defined('IGNORE_COMPONENT_CACHE') and IGNORE_COMPONENT_CACHE) {
9e19a0f0
PS
213 self::fill_all_caches();
214 return;
215 }
216
d7245e34
PS
217 if (!empty($CFG->alternative_component_cache)) {
218 // Hack for heavily clustered sites that want to manage component cache invalidation manually.
219 $cachefile = $CFG->alternative_component_cache;
220
221 if (file_exists($cachefile)) {
222 if (CACHE_DISABLE_ALL) {
223 // Verify the cache state only on upgrade pages.
224 $content = self::get_cache_content();
225 if (sha1_file($cachefile) !== sha1($content)) {
226 die('Outdated component cache file defined in $CFG->alternative_component_cache, can not continue');
227 }
228 return;
229 }
230 $cache = array();
231 include($cachefile);
37e60007
SH
232 self::$plugintypes = $cache['plugintypes'];
233 self::$plugins = $cache['plugins'];
234 self::$subsystems = $cache['subsystems'];
235 self::$parents = $cache['parents'];
236 self::$subplugins = $cache['subplugins'];
237 self::$classmap = $cache['classmap'];
238 self::$classmaprenames = $cache['classmaprenames'];
239 self::$filemap = $cache['filemap'];
d7245e34
PS
240 return;
241 }
242
243 if (!is_writable(dirname($cachefile))) {
244 die('Can not create alternative component cache file defined in $CFG->alternative_component_cache, can not continue');
245 }
246
247 // Lets try to create the file, it might be in some writable directory or a local cache dir.
248
249 } else {
250 // Note: $CFG->cachedir MUST be shared by all servers in a cluster,
251 // use $CFG->alternative_component_cache if you do not like it.
252 $cachefile = "$CFG->cachedir/core_component.php";
253 }
9e19a0f0
PS
254
255 if (!CACHE_DISABLE_ALL and !self::is_developer()) {
256 // 1/ Use the cache only outside of install and upgrade.
257 // 2/ Let developers add/remove classes in developer mode.
258 if (is_readable($cachefile)) {
259 $cache = false;
260 include($cachefile);
261 if (!is_array($cache)) {
262 // Something is very wrong.
3274c5db 263 } else if (!isset($cache['version'])) {
9e19a0f0 264 // Something is very wrong.
3274c5db
FM
265 } else if ((float) $cache['version'] !== (float) self::fetch_core_version()) {
266 // Outdated cache. We trigger an error log to track an eventual repetitive failure of float comparison.
267 error_log('Resetting core_component cache after core upgrade to version ' . self::fetch_core_version());
9e19a0f0 268 } else if ($cache['plugintypes']['mod'] !== "$CFG->dirroot/mod") {
d7245e34 269 // $CFG->dirroot was changed.
9e19a0f0
PS
270 } else {
271 // The cache looks ok, let's use it.
37e60007
SH
272 self::$plugintypes = $cache['plugintypes'];
273 self::$plugins = $cache['plugins'];
274 self::$subsystems = $cache['subsystems'];
275 self::$parents = $cache['parents'];
276 self::$subplugins = $cache['subplugins'];
277 self::$classmap = $cache['classmap'];
278 self::$classmaprenames = $cache['classmaprenames'];
279 self::$filemap = $cache['filemap'];
9e19a0f0
PS
280 return;
281 }
d7245e34
PS
282 // Note: we do not verify $CFG->admin here intentionally,
283 // they must visit admin/index.php after any change.
9e19a0f0
PS
284 }
285 }
286
9e19a0f0 287 if (!isset(self::$plugintypes)) {
9e19a0f0
PS
288 // This needs to be atomic and self-fixing as much as possible.
289
290 $content = self::get_cache_content();
291 if (file_exists($cachefile)) {
292 if (sha1_file($cachefile) === sha1($content)) {
293 return;
294 }
d7245e34 295 // Stale cache detected!
9e19a0f0
PS
296 unlink($cachefile);
297 }
298
766e04f3
PS
299 // Permissions might not be setup properly in installers.
300 $dirpermissions = !isset($CFG->directorypermissions) ? 02777 : $CFG->directorypermissions;
301 $filepermissions = !isset($CFG->filepermissions) ? ($dirpermissions & 0666) : $CFG->filepermissions;
302
303 clearstatcache();
d7245e34
PS
304 $cachedir = dirname($cachefile);
305 if (!is_dir($cachedir)) {
766e04f3 306 mkdir($cachedir, $dirpermissions, true);
d7245e34
PS
307 }
308
9e19a0f0
PS
309 if ($fp = @fopen($cachefile.'.tmp', 'xb')) {
310 fwrite($fp, $content);
311 fclose($fp);
312 @rename($cachefile.'.tmp', $cachefile);
766e04f3 313 @chmod($cachefile, $filepermissions);
9e19a0f0
PS
314 }
315 @unlink($cachefile.'.tmp'); // Just in case anything fails (race condition).
c05a5099 316 self::invalidate_opcode_php_cache($cachefile);
9e19a0f0
PS
317 }
318 }
319
320 /**
321 * Are we in developer debug mode?
322 *
323 * Note: You need to set "$CFG->debug = (E_ALL | E_STRICT);" in config.php,
324 * the reason is we need to use this before we setup DB connection or caches for CFG.
325 *
326 * @return bool
327 */
328 protected static function is_developer() {
329 global $CFG;
330
96f81ea3 331 // Note we can not rely on $CFG->debug here because DB is not initialised yet.
d7245e34 332 if (isset($CFG->config_php_settings['debug'])) {
d7245e34 333 $debug = (int)$CFG->config_php_settings['debug'];
d7245e34 334 } else {
9e19a0f0
PS
335 return false;
336 }
337
9e19a0f0
PS
338 if ($debug & E_ALL and $debug & E_STRICT) {
339 return true;
340 }
341
342 return false;
343 }
344
345 /**
346 * Create cache file content.
347 *
d7245e34
PS
348 * @private this is intended for $CFG->alternative_component_cache only.
349 *
9e19a0f0
PS
350 * @return string
351 */
d7245e34
PS
352 public static function get_cache_content() {
353 if (!isset(self::$plugintypes)) {
354 self::fill_all_caches();
355 }
356
9e19a0f0 357 $cache = array(
37e60007
SH
358 'subsystems' => self::$subsystems,
359 'plugintypes' => self::$plugintypes,
360 'plugins' => self::$plugins,
361 'parents' => self::$parents,
362 'subplugins' => self::$subplugins,
363 'classmap' => self::$classmap,
364 'classmaprenames' => self::$classmaprenames,
365 'filemap' => self::$filemap,
366 'version' => self::$version,
9e19a0f0
PS
367 );
368
369 return '<?php
370$cache = '.var_export($cache, true).';
371';
372 }
373
374 /**
375 * Fill all caches.
376 */
377 protected static function fill_all_caches() {
378 self::$subsystems = self::fetch_subsystems();
379
e87214bd 380 list(self::$plugintypes, self::$parents, self::$subplugins) = self::fetch_plugintypes();
9e19a0f0
PS
381
382 self::$plugins = array();
383 foreach (self::$plugintypes as $type => $fulldir) {
384 self::$plugins[$type] = self::fetch_plugins($type, $fulldir);
385 }
386
387 self::fill_classmap_cache();
37e60007 388 self::fill_classmap_renames_cache();
d26ec8a5 389 self::fill_filemap_cache();
3274c5db
FM
390 self::fetch_core_version();
391 }
392
393 /**
394 * Get the core version.
395 *
396 * In order for this to work properly, opcache should be reset beforehand.
397 *
398 * @return float core version.
399 */
400 protected static function fetch_core_version() {
401 global $CFG;
402 if (self::$version === null) {
81881cb9 403 $version = null; // Prevent IDE complaints.
3274c5db
FM
404 require($CFG->dirroot . '/version.php');
405 self::$version = $version;
406 }
407 return self::$version;
9e19a0f0
PS
408 }
409
410 /**
411 * Returns list of core subsystems.
412 * @return array
413 */
414 protected static function fetch_subsystems() {
415 global $CFG;
416
417 // NOTE: Any additions here must be verified to not collide with existing add-on modules and subplugins!!!
418
419 $info = array(
420 'access' => null,
421 'admin' => $CFG->dirroot.'/'.$CFG->admin,
40fcb365 422 'analytics' => $CFG->dirroot . '/analytics',
146eeb76 423 'antivirus' => $CFG->dirroot . '/lib/antivirus',
9e19a0f0 424 'auth' => $CFG->dirroot.'/auth',
d3db4b03 425 'availability' => $CFG->dirroot . '/availability',
9e19a0f0
PS
426 'backup' => $CFG->dirroot.'/backup/util/ui',
427 'badges' => $CFG->dirroot.'/badges',
428 'block' => $CFG->dirroot.'/blocks',
429 'blog' => $CFG->dirroot.'/blog',
430 'bulkusers' => null,
431 'cache' => $CFG->dirroot.'/cache',
432 'calendar' => $CFG->dirroot.'/calendar',
433 'cohort' => $CFG->dirroot.'/cohort',
be985416 434 'comment' => $CFG->dirroot.'/comment',
67bc0eaf 435 'competency' => $CFG->dirroot.'/competency',
9802bd61 436 'completion' => $CFG->dirroot.'/completion',
9e19a0f0
PS
437 'countries' => null,
438 'course' => $CFG->dirroot.'/course',
439 'currencies' => null,
01c10959 440 'customfield' => $CFG->dirroot.'/customfield',
9e19a0f0
PS
441 'dbtransfer' => null,
442 'debug' => null,
9e19a0f0
PS
443 'editor' => $CFG->dirroot.'/lib/editor',
444 'edufields' => null,
445 'enrol' => $CFG->dirroot.'/enrol',
446 'error' => null,
4fca5dab 447 'favourites' => $CFG->dirroot . '/favourites',
9e19a0f0 448 'filepicker' => null,
34df779a 449 'fileconverter' => $CFG->dirroot.'/files/converter',
9e19a0f0 450 'files' => $CFG->dirroot.'/files',
dc78f150 451 'filters' => $CFG->dirroot.'/filter',
9e19a0f0
PS
452 //'fonts' => null, // Bogus.
453 'form' => $CFG->dirroot.'/lib/form',
454 'grades' => $CFG->dirroot.'/grade',
455 'grading' => $CFG->dirroot.'/grade/grading',
456 'group' => $CFG->dirroot.'/group',
457 'help' => null,
458 'hub' => null,
459 'imscc' => null,
460 'install' => null,
461 'iso6392' => null,
462 'langconfig' => null,
463 'license' => null,
464 'mathslib' => null,
fab11235 465 'media' => $CFG->dirroot.'/media',
9e19a0f0
PS
466 'message' => $CFG->dirroot.'/message',
467 'mimetypes' => null,
468 'mnet' => $CFG->dirroot.'/mnet',
469 //'moodle.org' => null, // Not used any more.
470 'my' => $CFG->dirroot.'/my',
471 'notes' => $CFG->dirroot.'/notes',
472 'pagetype' => null,
473 'pix' => null,
474 'plagiarism' => $CFG->dirroot.'/plagiarism',
475 'plugin' => null,
476 'portfolio' => $CFG->dirroot.'/portfolio',
dcc16e15 477 'privacy' => $CFG->dirroot . '/privacy',
9e19a0f0
PS
478 'question' => $CFG->dirroot.'/question',
479 'rating' => $CFG->dirroot.'/rating',
9e19a0f0
PS
480 'repository' => $CFG->dirroot.'/repository',
481 'rss' => $CFG->dirroot.'/rss',
482 'role' => $CFG->dirroot.'/'.$CFG->admin.'/roles',
f900b2b6 483 'search' => $CFG->dirroot.'/search',
9e19a0f0
PS
484 'table' => null,
485 'tag' => $CFG->dirroot.'/tag',
486 'timezones' => null,
487 'user' => $CFG->dirroot.'/user',
81626948 488 'userkey' => $CFG->dirroot.'/lib/userkey',
9e19a0f0
PS
489 'webservice' => $CFG->dirroot.'/webservice',
490 );
491
492 return $info;
493 }
494
495 /**
496 * Returns list of known plugin types.
497 * @return array
498 */
499 protected static function fetch_plugintypes() {
500 global $CFG;
501
502 $types = array(
146eeb76 503 'antivirus' => $CFG->dirroot . '/lib/antivirus',
d3db4b03 504 'availability' => $CFG->dirroot . '/availability/condition',
9e19a0f0
PS
505 'qtype' => $CFG->dirroot.'/question/type',
506 'mod' => $CFG->dirroot.'/mod',
507 'auth' => $CFG->dirroot.'/auth',
2f00e1b2 508 'calendartype' => $CFG->dirroot.'/calendar/type',
01c10959 509 'customfield' => $CFG->dirroot.'/customfield/field',
9e19a0f0
PS
510 'enrol' => $CFG->dirroot.'/enrol',
511 'message' => $CFG->dirroot.'/message/output',
512 'block' => $CFG->dirroot.'/blocks',
fab11235 513 'media' => $CFG->dirroot.'/media/player',
9e19a0f0
PS
514 'filter' => $CFG->dirroot.'/filter',
515 'editor' => $CFG->dirroot.'/lib/editor',
516 'format' => $CFG->dirroot.'/course/format',
bff1edbe 517 'dataformat' => $CFG->dirroot.'/dataformat',
9e19a0f0
PS
518 'profilefield' => $CFG->dirroot.'/user/profile/field',
519 'report' => $CFG->dirroot.'/report',
520 'coursereport' => $CFG->dirroot.'/course/report', // Must be after system reports.
521 'gradeexport' => $CFG->dirroot.'/grade/export',
522 'gradeimport' => $CFG->dirroot.'/grade/import',
523 'gradereport' => $CFG->dirroot.'/grade/report',
524 'gradingform' => $CFG->dirroot.'/grade/grading/form',
40fcb365 525 'mlbackend' => $CFG->dirroot.'/lib/mlbackend',
9e19a0f0
PS
526 'mnetservice' => $CFG->dirroot.'/mnet/service',
527 'webservice' => $CFG->dirroot.'/webservice',
528 'repository' => $CFG->dirroot.'/repository',
529 'portfolio' => $CFG->dirroot.'/portfolio',
f900b2b6 530 'search' => $CFG->dirroot.'/search/engine',
9e19a0f0
PS
531 'qbehaviour' => $CFG->dirroot.'/question/behaviour',
532 'qformat' => $CFG->dirroot.'/question/format',
533 'plagiarism' => $CFG->dirroot.'/plagiarism',
534 'tool' => $CFG->dirroot.'/'.$CFG->admin.'/tool',
535 'cachestore' => $CFG->dirroot.'/cache/stores',
536 'cachelock' => $CFG->dirroot.'/cache/locks',
34df779a 537 'fileconverter' => $CFG->dirroot.'/files/converter',
9e19a0f0 538 );
e87214bd
PS
539 $parents = array();
540 $subplugins = array();
9e19a0f0
PS
541
542 if (!empty($CFG->themedir) and is_dir($CFG->themedir) ) {
543 $types['theme'] = $CFG->themedir;
544 } else {
545 $types['theme'] = $CFG->dirroot.'/theme';
546 }
547
548 foreach (self::$supportsubplugins as $type) {
3601c5f0
PS
549 if ($type === 'local') {
550 // Local subplugins must be after local plugins.
551 continue;
552 }
e87214bd
PS
553 $plugins = self::fetch_plugins($type, $types[$type]);
554 foreach ($plugins as $plugin => $fulldir) {
555 $subtypes = self::fetch_subtypes($fulldir);
556 if (!$subtypes) {
3601c5f0 557 continue;
9e19a0f0 558 }
e87214bd
PS
559 $subplugins[$type.'_'.$plugin] = array();
560 foreach($subtypes as $subtype => $subdir) {
561 if (isset($types[$subtype])) {
562 error_log("Invalid subtype '$subtype', duplicate detected.");
563 continue;
564 }
565 $types[$subtype] = $subdir;
566 $parents[$subtype] = $type.'_'.$plugin;
567 $subplugins[$type.'_'.$plugin][$subtype] = array_keys(self::fetch_plugins($subtype, $subdir));
568 }
9e19a0f0
PS
569 }
570 }
9e19a0f0
PS
571 // Local is always last!
572 $types['local'] = $CFG->dirroot.'/local';
573
3601c5f0 574 if (in_array('local', self::$supportsubplugins)) {
e87214bd
PS
575 $type = 'local';
576 $plugins = self::fetch_plugins($type, $types[$type]);
577 foreach ($plugins as $plugin => $fulldir) {
578 $subtypes = self::fetch_subtypes($fulldir);
579 if (!$subtypes) {
3601c5f0
PS
580 continue;
581 }
e87214bd
PS
582 $subplugins[$type.'_'.$plugin] = array();
583 foreach($subtypes as $subtype => $subdir) {
584 if (isset($types[$subtype])) {
585 error_log("Invalid subtype '$subtype', duplicate detected.");
586 continue;
587 }
588 $types[$subtype] = $subdir;
589 $parents[$subtype] = $type.'_'.$plugin;
590 $subplugins[$type.'_'.$plugin][$subtype] = array_keys(self::fetch_plugins($subtype, $subdir));
591 }
3601c5f0
PS
592 }
593 }
594
e87214bd 595 return array($types, $parents, $subplugins);
3601c5f0
PS
596 }
597
598 /**
e87214bd
PS
599 * Returns list of subtypes.
600 * @param string $ownerdir
3601c5f0
PS
601 * @return array
602 */
e87214bd 603 protected static function fetch_subtypes($ownerdir) {
3601c5f0
PS
604 global $CFG;
605
606 $types = array();
e87214bd
PS
607 if (file_exists("$ownerdir/db/subplugins.php")) {
608 $subplugins = array();
609 include("$ownerdir/db/subplugins.php");
610 foreach ($subplugins as $subtype => $dir) {
611 if (!preg_match('/^[a-z][a-z0-9]*$/', $subtype)) {
612 error_log("Invalid subtype '$subtype'' detected in '$ownerdir', invalid characters present.");
613 continue;
614 }
615 if (isset(self::$subsystems[$subtype])) {
616 error_log("Invalid subtype '$subtype'' detected in '$ownerdir', duplicates core subsystem.");
617 continue;
618 }
619 if ($CFG->admin !== 'admin' and strpos($dir, 'admin/') === 0) {
620 $dir = preg_replace('|^admin/|', "$CFG->admin/", $dir);
621 }
622 if (!is_dir("$CFG->dirroot/$dir")) {
623 error_log("Invalid subtype directory '$dir' detected in '$ownerdir'.");
624 continue;
3601c5f0 625 }
e87214bd 626 $types[$subtype] = "$CFG->dirroot/$dir";
3601c5f0
PS
627 }
628 }
9e19a0f0
PS
629 return $types;
630 }
631
632 /**
633 * Returns list of plugins of given type in given directory.
634 * @param string $plugintype
635 * @param string $fulldir
636 * @return array
637 */
638 protected static function fetch_plugins($plugintype, $fulldir) {
639 global $CFG;
640
641 $fulldirs = (array)$fulldir;
642 if ($plugintype === 'theme') {
643 if (realpath($fulldir) !== realpath($CFG->dirroot.'/theme')) {
644 // Include themes in standard location too.
645 array_unshift($fulldirs, $CFG->dirroot.'/theme');
646 }
647 }
648
649 $result = array();
650
651 foreach ($fulldirs as $fulldir) {
652 if (!is_dir($fulldir)) {
653 continue;
654 }
655 $items = new \DirectoryIterator($fulldir);
656 foreach ($items as $item) {
657 if ($item->isDot() or !$item->isDir()) {
658 continue;
659 }
660 $pluginname = $item->getFilename();
661 if ($plugintype === 'auth' and $pluginname === 'db') {
662 // Special exception for this wrong plugin name.
663 } else if (isset(self::$ignoreddirs[$pluginname])) {
664 continue;
665 }
666 if (!self::is_valid_plugin_name($plugintype, $pluginname)) {
667 // Always ignore plugins with problematic names here.
668 continue;
669 }
670 $result[$pluginname] = $fulldir.'/'.$pluginname;
671 unset($item);
672 }
673 unset($items);
674 }
675
676 ksort($result);
677 return $result;
678 }
679
680 /**
681 * Find all classes that can be autoloaded including frankenstyle namespaces.
682 */
683 protected static function fill_classmap_cache() {
684 global $CFG;
685
686 self::$classmap = array();
687
688 self::load_classes('core', "$CFG->dirroot/lib/classes");
689
690 foreach (self::$subsystems as $subsystem => $fulldir) {
6ef8d163
PS
691 if (!$fulldir) {
692 continue;
693 }
9e19a0f0
PS
694 self::load_classes('core_'.$subsystem, "$fulldir/classes");
695 }
696
697 foreach (self::$plugins as $plugintype => $plugins) {
698 foreach ($plugins as $pluginname => $fulldir) {
699 self::load_classes($plugintype.'_'.$pluginname, "$fulldir/classes");
700 }
701 }
be6f3c6e 702 ksort(self::$classmap);
9e19a0f0
PS
703 }
704
d26ec8a5
FM
705 /**
706 * Fills up the cache defining what plugins have certain files.
707 *
708 * @see self::get_plugin_list_with_file
709 * @return void
710 */
711 protected static function fill_filemap_cache() {
712 global $CFG;
713
714 self::$filemap = array();
715
716 foreach (self::$filestomap as $file) {
717 if (!isset(self::$filemap[$file])) {
718 self::$filemap[$file] = array();
719 }
720 foreach (self::$plugins as $plugintype => $plugins) {
721 if (!isset(self::$filemap[$file][$plugintype])) {
722 self::$filemap[$file][$plugintype] = array();
723 }
724 foreach ($plugins as $pluginname => $fulldir) {
725 if (file_exists("$fulldir/$file")) {
726 self::$filemap[$file][$plugintype][$pluginname] = "$fulldir/$file";
727 }
728 }
729 }
730 }
731 }
732
9e19a0f0
PS
733 /**
734 * Find classes in directory and recurse to subdirs.
735 * @param string $component
736 * @param string $fulldir
737 * @param string $namespace
738 */
739 protected static function load_classes($component, $fulldir, $namespace = '') {
740 if (!is_dir($fulldir)) {
741 return;
742 }
743
0496ac63
DP
744 if (!is_readable($fulldir)) {
745 // TODO: MDL-51711 We should generate some diagnostic debugging information in this case
746 // because its pretty likely to lead to a missing class error further down the line.
747 // But our early setup code can't handle errors this early at the moment.
748 return;
749 }
750
9e19a0f0
PS
751 $items = new \DirectoryIterator($fulldir);
752 foreach ($items as $item) {
753 if ($item->isDot()) {
754 continue;
755 }
756 if ($item->isDir()) {
757 $dirname = $item->getFilename();
758 self::load_classes($component, "$fulldir/$dirname", $namespace.'\\'.$dirname);
759 continue;
760 }
761
762 $filename = $item->getFilename();
763 $classname = preg_replace('/\.php$/', '', $filename);
764
765 if ($filename === $classname) {
766 // Not a php file.
767 continue;
768 }
769 if ($namespace === '') {
770 // Legacy long frankenstyle class name.
771 self::$classmap[$component.'_'.$classname] = "$fulldir/$filename";
772 }
773 // New namespaced classes.
774 self::$classmap[$component.$namespace.'\\'.$classname] = "$fulldir/$filename";
775 }
776 unset($item);
777 unset($items);
778 }
779
4ba38f2a 780
9e19a0f0
PS
781 /**
782 * List all core subsystems and their location
783 *
784 * This is a whitelist of components that are part of the core and their
785 * language strings are defined in /lang/en/<<subsystem>>.php. If a given
786 * plugin is not listed here and it does not have proper plugintype prefix,
787 * then it is considered as course activity module.
788 *
789 * The location is absolute file path to dir. NULL means there is no special
790 * directory for this subsystem. If the location is set, the subsystem's
791 * renderer.php is expected to be there.
792 *
793 * @return array of (string)name => (string|null)full dir location
794 */
795 public static function get_core_subsystems() {
796 self::init();
797 return self::$subsystems;
798 }
799
800 /**
801 * Get list of available plugin types together with their location.
802 *
803 * @return array as (string)plugintype => (string)fulldir
804 */
805 public static function get_plugin_types() {
806 self::init();
807 return self::$plugintypes;
808 }
809
810 /**
811 * Get list of plugins of given type.
812 *
813 * @param string $plugintype
814 * @return array as (string)pluginname => (string)fulldir
815 */
816 public static function get_plugin_list($plugintype) {
817 self::init();
818
819 if (!isset(self::$plugins[$plugintype])) {
820 return array();
821 }
822 return self::$plugins[$plugintype];
823 }
824
825 /**
826 * Get a list of all the plugins of a given type that define a certain class
827 * in a certain file. The plugin component names and class names are returned.
828 *
829 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
830 * @param string $class the part of the name of the class after the
831 * frankenstyle prefix. e.g 'thing' if you are looking for classes with
832 * names like report_courselist_thing. If you are looking for classes with
833 * the same name as the plugin name (e.g. qtype_multichoice) then pass ''.
834 * Frankenstyle namespaces are also supported.
835 * @param string $file the name of file within the plugin that defines the class.
836 * @return array with frankenstyle plugin names as keys (e.g. 'report_courselist', 'mod_forum')
837 * and the class names as values (e.g. 'report_courselist_thing', 'qtype_multichoice').
838 */
839 public static function get_plugin_list_with_class($plugintype, $class, $file = null) {
840 global $CFG; // Necessary in case it is referenced by included PHP scripts.
841
842 if ($class) {
843 $suffix = '_' . $class;
844 } else {
845 $suffix = '';
846 }
847
848 $pluginclasses = array();
849 $plugins = self::get_plugin_list($plugintype);
850 foreach ($plugins as $plugin => $fulldir) {
851 // Try class in frankenstyle namespace.
852 if ($class) {
853 $classname = '\\' . $plugintype . '_' . $plugin . '\\' . $class;
854 if (class_exists($classname, true)) {
855 $pluginclasses[$plugintype . '_' . $plugin] = $classname;
856 continue;
857 }
858 }
859
860 // Try autoloading of class with frankenstyle prefix.
861 $classname = $plugintype . '_' . $plugin . $suffix;
862 if (class_exists($classname, true)) {
863 $pluginclasses[$plugintype . '_' . $plugin] = $classname;
864 continue;
865 }
866
867 // Fall back to old file location and class name.
868 if ($file and file_exists("$fulldir/$file")) {
869 include_once("$fulldir/$file");
870 if (class_exists($classname, false)) {
871 $pluginclasses[$plugintype . '_' . $plugin] = $classname;
872 continue;
873 }
874 }
875 }
876
877 return $pluginclasses;
878 }
879
d26ec8a5
FM
880 /**
881 * Get a list of all the plugins of a given type that contain a particular file.
882 *
883 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
884 * @param string $file the name of file that must be present in the plugin.
885 * (e.g. 'view.php', 'db/install.xml').
886 * @param bool $include if true (default false), the file will be include_once-ed if found.
887 * @return array with plugin name as keys (e.g. 'forum', 'courselist') and the path
888 * to the file relative to dirroot as value (e.g. "$CFG->dirroot/mod/forum/view.php").
889 */
890 public static function get_plugin_list_with_file($plugintype, $file, $include = false) {
891 global $CFG; // Necessary in case it is referenced by included PHP scripts.
892 $pluginfiles = array();
893
894 if (isset(self::$filemap[$file])) {
895 // If the file was supposed to be mapped, then it should have been set in the array.
896 if (isset(self::$filemap[$file][$plugintype])) {
897 $pluginfiles = self::$filemap[$file][$plugintype];
898 }
899 } else {
900 // Old-style search for non-cached files.
901 $plugins = self::get_plugin_list($plugintype);
902 foreach ($plugins as $plugin => $fulldir) {
903 $path = $fulldir . '/' . $file;
904 if (file_exists($path)) {
905 $pluginfiles[$plugin] = $path;
906 }
907 }
908 }
909
910 if ($include) {
911 foreach ($pluginfiles as $path) {
912 include_once($path);
913 }
914 }
915
916 return $pluginfiles;
917 }
918
f900b2b6
DM
919 /**
920 * Returns all classes in a component matching the provided namespace.
921 *
922 * It checks that the class exists.
923 *
924 * e.g. get_component_classes_in_namespace('mod_forum', 'event')
925 *
926 * @param string $component A valid moodle component (frankenstyle)
8bee2d8d 927 * @param string $namespace Namespace from the component name or empty if all $component namespace classes.
f900b2b6
DM
928 * @return array The full class name as key and the class path as value.
929 */
930 public static function get_component_classes_in_namespace($component, $namespace = '') {
931
8bee2d8d 932 $component = self::normalize_componentname($component);
f900b2b6 933
8bee2d8d 934 if ($namespace) {
f900b2b6 935
8bee2d8d
DM
936 // We will add them later.
937 $namespace = trim($namespace, '\\');
938
939 // We need add double backslashes as it is how classes are stored into self::$classmap.
940 $namespace = implode('\\\\', explode('\\', $namespace));
941 $namespace = $namespace . '\\\\';
942 }
943
944 $regex = '|^' . $component . '\\\\' . $namespace . '|';
f900b2b6
DM
945 $it = new RegexIterator(new ArrayIterator(self::$classmap), $regex, RegexIterator::GET_MATCH, RegexIterator::USE_KEY);
946
947 // We want to be sure that they exist.
948 $classes = array();
949 foreach ($it as $classname => $classpath) {
950 if (class_exists($classname)) {
951 $classes[$classname] = $classpath;
952 }
953 }
954
955 return $classes;
956 }
957
9e19a0f0
PS
958 /**
959 * Returns the exact absolute path to plugin directory.
960 *
961 * @param string $plugintype type of plugin
962 * @param string $pluginname name of the plugin
963 * @return string full path to plugin directory; null if not found
964 */
965 public static function get_plugin_directory($plugintype, $pluginname) {
966 if (empty($pluginname)) {
967 // Invalid plugin name, sorry.
968 return null;
969 }
970
971 self::init();
972
973 if (!isset(self::$plugins[$plugintype][$pluginname])) {
974 return null;
975 }
976 return self::$plugins[$plugintype][$pluginname];
977 }
978
979 /**
980 * Returns the exact absolute path to plugin directory.
981 *
982 * @param string $subsystem type of core subsystem
983 * @return string full path to subsystem directory; null if not found
984 */
985 public static function get_subsystem_directory($subsystem) {
986 self::init();
987
988 if (!isset(self::$subsystems[$subsystem])) {
989 return null;
990 }
991 return self::$subsystems[$subsystem];
992 }
993
994 /**
995 * This method validates a plug name. It is much faster than calling clean_param.
996 *
997 * @param string $plugintype type of plugin
998 * @param string $pluginname a string that might be a plugin name.
999 * @return bool if this string is a valid plugin name.
1000 */
1001 public static function is_valid_plugin_name($plugintype, $pluginname) {
1002 if ($plugintype === 'mod') {
1003 // Modules must not have the same name as core subsystems.
1004 if (!isset(self::$subsystems)) {
1005 // Watch out, this is called from init!
1006 self::init();
1007 }
1008 if (isset(self::$subsystems[$pluginname])) {
1009 return false;
1010 }
1011 // Modules MUST NOT have any underscores,
1012 // component normalisation would break very badly otherwise!
1013 return (bool)preg_match('/^[a-z][a-z0-9]*$/', $pluginname);
1014
1015 } else {
a41d1ca0 1016 return (bool)preg_match('/^[a-z](?:[a-z0-9_](?!__))*[a-z0-9]+$/', $pluginname);
9e19a0f0
PS
1017 }
1018 }
1019
7ace3287
AN
1020 /**
1021 * Normalize the component name.
1022 *
1023 * Note: this does not verify the validity of the plugin or component.
1024 *
1025 * @param string $component
1026 * @return string
1027 */
1028 public static function normalize_componentname($componentname) {
1029 list($plugintype, $pluginname) = self::normalize_component($componentname);
1030 if ($plugintype === 'core' && is_null($pluginname)) {
1031 return $plugintype;
1032 }
1033 return $plugintype . '_' . $pluginname;
1034 }
1035
9e19a0f0
PS
1036 /**
1037 * Normalize the component name using the "frankenstyle" rules.
1038 *
1039 * Note: this does not verify the validity of plugin or type names.
1040 *
1041 * @param string $component
6b6a2ec4 1042 * @return array two-items list of [(string)type, (string|null)name]
9e19a0f0
PS
1043 */
1044 public static function normalize_component($component) {
1045 if ($component === 'moodle' or $component === 'core' or $component === '') {
1046 return array('core', null);
1047 }
1048
1049 if (strpos($component, '_') === false) {
1050 self::init();
1051 if (array_key_exists($component, self::$subsystems)) {
1052 $type = 'core';
1053 $plugin = $component;
1054 } else {
1055 // Everything else without underscore is a module.
1056 $type = 'mod';
1057 $plugin = $component;
1058 }
1059
1060 } else {
1061 list($type, $plugin) = explode('_', $component, 2);
1062 if ($type === 'moodle') {
1063 $type = 'core';
1064 }
1065 // Any unknown type must be a subplugin.
1066 }
1067
1068 return array($type, $plugin);
1069 }
1070
1071 /**
1072 * Return exact absolute path to a plugin directory.
1073 *
1074 * @param string $component name such as 'moodle', 'mod_forum'
1075 * @return string full path to component directory; NULL if not found
1076 */
1077 public static function get_component_directory($component) {
1078 global $CFG;
1079
1080 list($type, $plugin) = self::normalize_component($component);
1081
1082 if ($type === 'core') {
1083 if ($plugin === null) {
1084 return $path = $CFG->libdir;
1085 }
1086 return self::get_subsystem_directory($plugin);
1087 }
1088
1089 return self::get_plugin_directory($type, $plugin);
1090 }
3601c5f0
PS
1091
1092 /**
1093 * Returns list of plugin types that allow subplugins.
1094 * @return array as (string)plugintype => (string)fulldir
1095 */
1096 public static function get_plugin_types_with_subplugins() {
1097 self::init();
1098
1099 $return = array();
1100 foreach (self::$supportsubplugins as $type) {
1101 $return[$type] = self::$plugintypes[$type];
1102 }
1103 return $return;
1104 }
c05a5099 1105
e87214bd
PS
1106 /**
1107 * Returns parent of this subplugin type.
1108 *
1109 * @param string $type
1110 * @return string parent component or null
1111 */
1112 public static function get_subtype_parent($type) {
1113 self::init();
1114
1115 if (isset(self::$parents[$type])) {
1116 return self::$parents[$type];
1117 }
1118
1119 return null;
1120 }
1121
1122 /**
1123 * Return all subplugins of this component.
1124 * @param string $component.
1125 * @return array $subtype=>array($component, ..), null if no subtypes defined
1126 */
1127 public static function get_subplugins($component) {
1128 self::init();
1129
1130 if (isset(self::$subplugins[$component])) {
1131 return self::$subplugins[$component];
1132 }
1133
1134 return null;
1135 }
1136
c5701ce7
PS
1137 /**
1138 * Returns hash of all versions including core and all plugins.
1139 *
1140 * This is relatively slow and not fully cached, use with care!
1141 *
1142 * @return string sha1 hash
1143 */
1144 public static function get_all_versions_hash() {
e4453adc
DM
1145 return sha1(serialize(self::get_all_versions()));
1146 }
1147
1148 /**
1149 * Returns hash of all versions including core and all plugins.
1150 *
1151 * This is relatively slow and not fully cached, use with care!
1152 *
1153 * @return array as (string)plugintype_pluginname => (int)version
1154 */
1155 public static function get_all_versions() : array {
c5701ce7
PS
1156 global $CFG;
1157
1158 self::init();
1159
1160 $versions = array();
1161
1162 // Main version first.
3274c5db 1163 $versions['core'] = self::fetch_core_version();
c5701ce7
PS
1164
1165 // The problem here is tha the component cache might be stable,
1166 // we want this to work also on frontpage without resetting the component cache.
1167 $usecache = false;
1168 if (CACHE_DISABLE_ALL or (defined('IGNORE_COMPONENT_CACHE') and IGNORE_COMPONENT_CACHE)) {
1169 $usecache = true;
1170 }
1171
1172 // Now all plugins.
1173 $plugintypes = core_component::get_plugin_types();
1174 foreach ($plugintypes as $type => $typedir) {
1175 if ($usecache) {
1176 $plugs = core_component::get_plugin_list($type);
1177 } else {
1178 $plugs = self::fetch_plugins($type, $typedir);
1179 }
1180 foreach ($plugs as $plug => $fullplug) {
bde002b8
PS
1181 $plugin = new stdClass();
1182 $plugin->version = null;
1183 $module = $plugin;
0b468c59 1184 include($fullplug.'/version.php');
bde002b8 1185 $versions[$type.'_'.$plug] = $plugin->version;
c5701ce7
PS
1186 }
1187 }
1188
e4453adc 1189 return $versions;
c5701ce7
PS
1190 }
1191
c05a5099
PS
1192 /**
1193 * Invalidate opcode cache for given file, this is intended for
1194 * php files that are stored in dataroot.
1195 *
1196 * Note: we need it here because this class must be self-contained.
1197 *
1198 * @param string $file
1199 */
1200 public static function invalidate_opcode_php_cache($file) {
1201 if (function_exists('opcache_invalidate')) {
1202 if (!file_exists($file)) {
1203 return;
1204 }
1205 opcache_invalidate($file, true);
1206 }
1207 }
a55eaf03
RT
1208
1209 /**
1210 * Return true if subsystemname is core subsystem.
1211 *
1212 * @param string $subsystemname name of the subsystem.
1213 * @return bool true if core subsystem.
1214 */
1215 public static function is_core_subsystem($subsystemname) {
1216 return isset(self::$subsystems[$subsystemname]);
1217 }
37e60007
SH
1218
1219 /**
1220 * Records all class renames that have been made to facilitate autoloading.
1221 */
1222 protected static function fill_classmap_renames_cache() {
1223 global $CFG;
1224
1225 self::$classmaprenames = array();
1226
1227 self::load_renamed_classes("$CFG->dirroot/lib/");
1228
1229 foreach (self::$subsystems as $subsystem => $fulldir) {
1230 self::load_renamed_classes($fulldir);
1231 }
1232
1233 foreach (self::$plugins as $plugintype => $plugins) {
1234 foreach ($plugins as $pluginname => $fulldir) {
1235 self::load_renamed_classes($fulldir);
1236 }
1237 }
1238 }
1239
1240 /**
1241 * Loads the db/renamedclasses.php file from the given directory.
1242 *
1243 * The renamedclasses.php should contain a key => value array ($renamedclasses) where the key is old class name,
1244 * and the value is the new class name.
1245 * It is only included when we are populating the component cache. After that is not needed.
1246 *
1247 * @param string $fulldir
1248 */
1249 protected static function load_renamed_classes($fulldir) {
1250 $file = $fulldir . '/db/renamedclasses.php';
1251 if (is_readable($file)) {
1252 $renamedclasses = null;
1253 require($file);
1254 if (is_array($renamedclasses)) {
1255 foreach ($renamedclasses as $oldclass => $newclass) {
1256 self::$classmaprenames[(string)$oldclass] = (string)$newclass;
1257 }
1258 }
1259 }
1260 }
5749f8a4
JD
1261
1262 /**
1263 * Returns a list of frankenstyle component names and their paths, for all components (plugins and subsystems).
1264 *
1265 * E.g.
1266 * [
1267 * 'mod' => [
1268 * 'mod_forum' => FORUM_PLUGIN_PATH,
1269 * ...
1270 * ],
1271 * ...
1272 * 'core' => [
1273 * 'core_comment' => COMMENT_SUBSYSTEM_PATH,
1274 * ...
1275 * ]
1276 * ]
1277 *
1278 * @return array an associative array of components and their corresponding paths.
1279 */
1280 public static function get_component_list() : array {
1281 $components = [];
1282 // Get all plugins.
1283 foreach (self::get_plugin_types() as $plugintype => $typedir) {
1284 $components[$plugintype] = [];
1285 foreach (self::get_plugin_list($plugintype) as $pluginname => $plugindir) {
1286 $components[$plugintype][$plugintype . '_' . $pluginname] = $plugindir;
1287 }
1288 }
1289 // Get all subsystems.
1290 foreach (self::get_core_subsystems() as $subsystemname => $subsystempath) {
1291 $components['core']['core_' . $subsystemname] = $subsystempath;
1292 }
1293 return $components;
1294 }
3a1ece14
JD
1295
1296 /**
1297 * Returns a list of frankenstyle component names.
1298 *
1299 * E.g.
1300 * [
1301 * 'core_course',
1302 * 'core_message',
1303 * 'mod_assign',
1304 * ...
1305 * ]
1306 * @return array the list of frankenstyle component names.
1307 */
1308 public static function get_component_names() : array {
1309 $componentnames = [];
1310 // Get all plugins.
1311 foreach (self::get_plugin_types() as $plugintype => $typedir) {
1312 foreach (self::get_plugin_list($plugintype) as $pluginname => $plugindir) {
1313 $componentnames[] = $plugintype . '_' . $pluginname;
1314 }
1315 }
1316 // Get all subsystems.
1317 foreach (self::get_core_subsystems() as $subsystemname => $subsystempath) {
1318 $componentnames[] = 'core_' . $subsystemname;
1319 }
1320 return $componentnames;
1321 }
9e19a0f0 1322}