weekly release 2.6dev
[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
9e19a0f0
PS
27/**
28 * Collection of components related methods.
29 */
30class core_component {
31 /** @var array list of ignored directories - watch out for auth/db exception */
9ba6076c 32 protected static $ignoreddirs = array('CVS'=>true, '_vti_cnf'=>true, 'simpletest'=>true, 'db'=>true, 'yui'=>true, 'tests'=>true, 'classes'=>true, 'fonts'=>true);
9e19a0f0 33 /** @var array list plugin types that support subplugins, do not add more here unless absolutely necessary */
3601c5f0 34 protected static $supportsubplugins = array('mod', 'editor', 'local');
9e19a0f0
PS
35
36 /** @var null cache of plugin types */
37 protected static $plugintypes = null;
38 /** @var null cache of plugin locations */
39 protected static $plugins = null;
40 /** @var null cache of core subsystems */
41 protected static $subsystems = null;
42 /** @var null list of all known classes that can be autoloaded */
43 protected static $classmap = null;
44
45 /**
46 * Class loader for Frankenstyle named classes in standard locations.
47 * Frankenstyle namespaces are supported.
48 *
49 * The expected location for core classes is:
50 * 1/ core_xx_yy_zz ---> lib/classes/xx_yy_zz.php
51 * 2/ \core\xx_yy_zz ---> lib/classes/xx_yy_zz.php
52 * 3/ \core\xx\yy_zz ---> lib/classes/xx/yy_zz.php
53 *
54 * The expected location for plugin classes is:
55 * 1/ mod_name_xx_yy_zz ---> mod/name/classes/xx_yy_zz.php
56 * 2/ \mod_name\xx_yy_zz ---> mod/name/classes/xx_yy_zz.php
57 * 3/ \mod_name\xx\yy_zz ---> mod/name/classes/xx/yy_zz.php
58 *
59 * @param string $classname
60 */
61 public static function classloader($classname) {
62 self::init();
63
64 if (isset(self::$classmap[$classname])) {
65 // Global $CFG is expected in included scripts.
66 global $CFG;
67 // Function include would be faster, but for BC it is better to include only once.
68 include_once(self::$classmap[$classname]);
69 return;
70 }
71 }
72
73 /**
74 * Initialise caches, always call before accessing self:: caches.
75 */
76 protected static function init() {
77 global $CFG;
78
79 // Init only once per request/CLI execution, we ignore changes done afterwards.
80 if (isset(self::$plugintypes)) {
81 return;
82 }
83
d7245e34 84 if (defined('IGNORE_COMPONENT_CACHE') and IGNORE_COMPONENT_CACHE) {
9e19a0f0
PS
85 self::fill_all_caches();
86 return;
87 }
88
d7245e34
PS
89 if (!empty($CFG->alternative_component_cache)) {
90 // Hack for heavily clustered sites that want to manage component cache invalidation manually.
91 $cachefile = $CFG->alternative_component_cache;
92
93 if (file_exists($cachefile)) {
94 if (CACHE_DISABLE_ALL) {
95 // Verify the cache state only on upgrade pages.
96 $content = self::get_cache_content();
97 if (sha1_file($cachefile) !== sha1($content)) {
98 die('Outdated component cache file defined in $CFG->alternative_component_cache, can not continue');
99 }
100 return;
101 }
102 $cache = array();
103 include($cachefile);
104 self::$plugintypes = $cache['plugintypes'];
105 self::$plugins = $cache['plugins'];
106 self::$subsystems = $cache['subsystems'];
107 self::$classmap = $cache['classmap'];
108 return;
109 }
110
111 if (!is_writable(dirname($cachefile))) {
112 die('Can not create alternative component cache file defined in $CFG->alternative_component_cache, can not continue');
113 }
114
115 // Lets try to create the file, it might be in some writable directory or a local cache dir.
116
117 } else {
118 // Note: $CFG->cachedir MUST be shared by all servers in a cluster,
119 // use $CFG->alternative_component_cache if you do not like it.
120 $cachefile = "$CFG->cachedir/core_component.php";
121 }
9e19a0f0
PS
122
123 if (!CACHE_DISABLE_ALL and !self::is_developer()) {
124 // 1/ Use the cache only outside of install and upgrade.
125 // 2/ Let developers add/remove classes in developer mode.
126 if (is_readable($cachefile)) {
127 $cache = false;
128 include($cachefile);
129 if (!is_array($cache)) {
130 // Something is very wrong.
131 } else if (!isset($cache['plugintypes']) or !isset($cache['plugins']) or !isset($cache['subsystems']) or !isset($cache['classmap'])) {
132 // Something is very wrong.
133 } else if ($cache['plugintypes']['mod'] !== "$CFG->dirroot/mod") {
d7245e34 134 // $CFG->dirroot was changed.
9e19a0f0
PS
135 } else {
136 // The cache looks ok, let's use it.
137 self::$plugintypes = $cache['plugintypes'];
138 self::$plugins = $cache['plugins'];
139 self::$subsystems = $cache['subsystems'];
140 self::$classmap = $cache['classmap'];
141 return;
142 }
d7245e34
PS
143 // Note: we do not verify $CFG->admin here intentionally,
144 // they must visit admin/index.php after any change.
9e19a0f0
PS
145 }
146 }
147
9e19a0f0 148 if (!isset(self::$plugintypes)) {
9e19a0f0
PS
149 // This needs to be atomic and self-fixing as much as possible.
150
151 $content = self::get_cache_content();
152 if (file_exists($cachefile)) {
153 if (sha1_file($cachefile) === sha1($content)) {
154 return;
155 }
d7245e34 156 // Stale cache detected!
9e19a0f0
PS
157 unlink($cachefile);
158 }
159
766e04f3
PS
160 // Permissions might not be setup properly in installers.
161 $dirpermissions = !isset($CFG->directorypermissions) ? 02777 : $CFG->directorypermissions;
162 $filepermissions = !isset($CFG->filepermissions) ? ($dirpermissions & 0666) : $CFG->filepermissions;
163
164 clearstatcache();
d7245e34
PS
165 $cachedir = dirname($cachefile);
166 if (!is_dir($cachedir)) {
766e04f3 167 mkdir($cachedir, $dirpermissions, true);
d7245e34
PS
168 }
169
9e19a0f0
PS
170 if ($fp = @fopen($cachefile.'.tmp', 'xb')) {
171 fwrite($fp, $content);
172 fclose($fp);
173 @rename($cachefile.'.tmp', $cachefile);
766e04f3 174 @chmod($cachefile, $filepermissions);
9e19a0f0
PS
175 }
176 @unlink($cachefile.'.tmp'); // Just in case anything fails (race condition).
c05a5099 177 self::invalidate_opcode_php_cache($cachefile);
9e19a0f0
PS
178 }
179 }
180
181 /**
182 * Are we in developer debug mode?
183 *
184 * Note: You need to set "$CFG->debug = (E_ALL | E_STRICT);" in config.php,
185 * the reason is we need to use this before we setup DB connection or caches for CFG.
186 *
187 * @return bool
188 */
189 protected static function is_developer() {
190 global $CFG;
191
d7245e34
PS
192 if (isset($CFG->config_php_settings['debug'])) {
193 // Standard moodle script.
194 $debug = (int)$CFG->config_php_settings['debug'];
195 } else if (isset($CFG->debug)) {
196 // Usually script with ABORT_AFTER_CONFIG.
197 $debug = (int)$CFG->debug;
198 } else {
9e19a0f0
PS
199 return false;
200 }
201
9e19a0f0
PS
202 if ($debug & E_ALL and $debug & E_STRICT) {
203 return true;
204 }
205
206 return false;
207 }
208
209 /**
210 * Create cache file content.
211 *
d7245e34
PS
212 * @private this is intended for $CFG->alternative_component_cache only.
213 *
9e19a0f0
PS
214 * @return string
215 */
d7245e34
PS
216 public static function get_cache_content() {
217 if (!isset(self::$plugintypes)) {
218 self::fill_all_caches();
219 }
220
9e19a0f0 221 $cache = array(
1652aa9c
PS
222 'subsystems' => self::$subsystems,
223 'plugintypes' => self::$plugintypes,
224 'plugins' => self::$plugins,
225 'classmap' => self::$classmap,
9e19a0f0
PS
226 );
227
228 return '<?php
229$cache = '.var_export($cache, true).';
230';
231 }
232
233 /**
234 * Fill all caches.
235 */
236 protected static function fill_all_caches() {
237 self::$subsystems = self::fetch_subsystems();
238
239 self::$plugintypes = self::fetch_plugintypes();
240
241 self::$plugins = array();
242 foreach (self::$plugintypes as $type => $fulldir) {
243 self::$plugins[$type] = self::fetch_plugins($type, $fulldir);
244 }
245
246 self::fill_classmap_cache();
247 }
248
249 /**
250 * Returns list of core subsystems.
251 * @return array
252 */
253 protected static function fetch_subsystems() {
254 global $CFG;
255
256 // NOTE: Any additions here must be verified to not collide with existing add-on modules and subplugins!!!
257
258 $info = array(
259 'access' => null,
260 'admin' => $CFG->dirroot.'/'.$CFG->admin,
261 'auth' => $CFG->dirroot.'/auth',
262 'backup' => $CFG->dirroot.'/backup/util/ui',
263 'badges' => $CFG->dirroot.'/badges',
264 'block' => $CFG->dirroot.'/blocks',
265 'blog' => $CFG->dirroot.'/blog',
266 'bulkusers' => null,
267 'cache' => $CFG->dirroot.'/cache',
268 'calendar' => $CFG->dirroot.'/calendar',
269 'cohort' => $CFG->dirroot.'/cohort',
270 'condition' => null,
271 'completion' => null,
272 'countries' => null,
273 'course' => $CFG->dirroot.'/course',
274 'currencies' => null,
275 'dbtransfer' => null,
276 'debug' => null,
9e19a0f0
PS
277 'editor' => $CFG->dirroot.'/lib/editor',
278 'edufields' => null,
279 'enrol' => $CFG->dirroot.'/enrol',
280 'error' => null,
281 'filepicker' => null,
282 'files' => $CFG->dirroot.'/files',
283 'filters' => null,
284 //'fonts' => null, // Bogus.
285 'form' => $CFG->dirroot.'/lib/form',
286 'grades' => $CFG->dirroot.'/grade',
287 'grading' => $CFG->dirroot.'/grade/grading',
288 'group' => $CFG->dirroot.'/group',
289 'help' => null,
290 'hub' => null,
291 'imscc' => null,
292 'install' => null,
293 'iso6392' => null,
294 'langconfig' => null,
295 'license' => null,
296 'mathslib' => null,
297 'media' => null,
298 'message' => $CFG->dirroot.'/message',
299 'mimetypes' => null,
300 'mnet' => $CFG->dirroot.'/mnet',
301 //'moodle.org' => null, // Not used any more.
302 'my' => $CFG->dirroot.'/my',
303 'notes' => $CFG->dirroot.'/notes',
304 'pagetype' => null,
305 'pix' => null,
306 'plagiarism' => $CFG->dirroot.'/plagiarism',
307 'plugin' => null,
308 'portfolio' => $CFG->dirroot.'/portfolio',
309 'publish' => $CFG->dirroot.'/course/publish',
310 'question' => $CFG->dirroot.'/question',
311 'rating' => $CFG->dirroot.'/rating',
312 'register' => $CFG->dirroot.'/'.$CFG->admin.'/registration', // Broken badly if $CFG->admin changed.
313 'repository' => $CFG->dirroot.'/repository',
314 'rss' => $CFG->dirroot.'/rss',
315 'role' => $CFG->dirroot.'/'.$CFG->admin.'/roles',
316 'search' => null,
317 'table' => null,
318 'tag' => $CFG->dirroot.'/tag',
319 'timezones' => null,
320 'user' => $CFG->dirroot.'/user',
321 'userkey' => null,
322 'webservice' => $CFG->dirroot.'/webservice',
323 );
324
325 return $info;
326 }
327
328 /**
329 * Returns list of known plugin types.
330 * @return array
331 */
332 protected static function fetch_plugintypes() {
333 global $CFG;
334
335 $types = array(
336 'qtype' => $CFG->dirroot.'/question/type',
337 'mod' => $CFG->dirroot.'/mod',
338 'auth' => $CFG->dirroot.'/auth',
339 'enrol' => $CFG->dirroot.'/enrol',
340 'message' => $CFG->dirroot.'/message/output',
341 'block' => $CFG->dirroot.'/blocks',
342 'filter' => $CFG->dirroot.'/filter',
343 'editor' => $CFG->dirroot.'/lib/editor',
344 'format' => $CFG->dirroot.'/course/format',
345 'profilefield' => $CFG->dirroot.'/user/profile/field',
346 'report' => $CFG->dirroot.'/report',
347 'coursereport' => $CFG->dirroot.'/course/report', // Must be after system reports.
348 'gradeexport' => $CFG->dirroot.'/grade/export',
349 'gradeimport' => $CFG->dirroot.'/grade/import',
350 'gradereport' => $CFG->dirroot.'/grade/report',
351 'gradingform' => $CFG->dirroot.'/grade/grading/form',
352 'mnetservice' => $CFG->dirroot.'/mnet/service',
353 'webservice' => $CFG->dirroot.'/webservice',
354 'repository' => $CFG->dirroot.'/repository',
355 'portfolio' => $CFG->dirroot.'/portfolio',
356 'qbehaviour' => $CFG->dirroot.'/question/behaviour',
357 'qformat' => $CFG->dirroot.'/question/format',
358 'plagiarism' => $CFG->dirroot.'/plagiarism',
359 'tool' => $CFG->dirroot.'/'.$CFG->admin.'/tool',
360 'cachestore' => $CFG->dirroot.'/cache/stores',
361 'cachelock' => $CFG->dirroot.'/cache/locks',
362
363 );
364
365 if (!empty($CFG->themedir) and is_dir($CFG->themedir) ) {
366 $types['theme'] = $CFG->themedir;
367 } else {
368 $types['theme'] = $CFG->dirroot.'/theme';
369 }
370
371 foreach (self::$supportsubplugins as $type) {
3601c5f0
PS
372 if ($type === 'local') {
373 // Local subplugins must be after local plugins.
374 continue;
375 }
376 $subplugins = self::fetch_subplugins($type, $types[$type]);
377 foreach($subplugins as $subtype => $subplugin) {
378 if (isset($types[$subtype])) {
379 error_log("Invalid subtype '$subtype', duplicate detected.");
380 continue;
9e19a0f0 381 }
3601c5f0 382 $types[$subtype] = $subplugin;
9e19a0f0
PS
383 }
384 }
385
386 // Local is always last!
387 $types['local'] = $CFG->dirroot.'/local';
388
3601c5f0
PS
389 if (in_array('local', self::$supportsubplugins)) {
390 $subplugins = self::fetch_subplugins('local', $types['local']);
391 foreach($subplugins as $subtype => $subplugin) {
392 if (isset($types[$subtype])) {
393 error_log("Invalid subtype '$subtype', duplicate detected.");
394 continue;
395 }
396 $types[$subtype] = $subplugin;
397 }
398 }
399
400 return $types;
401 }
402
403 /**
404 * Returns list of subtypes defined in given plugin type.
405 * @param string $type
406 * @param string $fulldir
407 * @return array
408 */
409 protected static function fetch_subplugins($type, $fulldir) {
410 global $CFG;
411
412 $types = array();
413 $subpluginowners = self::fetch_plugins($type, $fulldir);
414 foreach ($subpluginowners as $ownerdir) {
415 if (file_exists("$ownerdir/db/subplugins.php")) {
416 $subplugins = array();
417 include("$ownerdir/db/subplugins.php");
418 foreach ($subplugins as $subtype => $dir) {
419 if (!preg_match('/^[a-z][a-z0-9]*$/', $subtype)) {
420 error_log("Invalid subtype '$subtype'' detected in '$ownerdir', invalid characters present.");
421 continue;
422 }
423 if (isset(self::$subsystems[$subtype])) {
424 error_log("Invalid subtype '$subtype'' detected in '$ownerdir', duplicates core subsystem.");
425 continue;
426 }
427 if (!is_dir("$CFG->dirroot/$dir")) {
428 error_log("Invalid subtype directory '$dir' detected in '$ownerdir'.");
429 continue;
430 }
431 $types[$subtype] = "$CFG->dirroot/$dir";
432 }
433 }
434 }
9e19a0f0
PS
435 return $types;
436 }
437
438 /**
439 * Returns list of plugins of given type in given directory.
440 * @param string $plugintype
441 * @param string $fulldir
442 * @return array
443 */
444 protected static function fetch_plugins($plugintype, $fulldir) {
445 global $CFG;
446
447 $fulldirs = (array)$fulldir;
448 if ($plugintype === 'theme') {
449 if (realpath($fulldir) !== realpath($CFG->dirroot.'/theme')) {
450 // Include themes in standard location too.
451 array_unshift($fulldirs, $CFG->dirroot.'/theme');
452 }
453 }
454
455 $result = array();
456
457 foreach ($fulldirs as $fulldir) {
458 if (!is_dir($fulldir)) {
459 continue;
460 }
461 $items = new \DirectoryIterator($fulldir);
462 foreach ($items as $item) {
463 if ($item->isDot() or !$item->isDir()) {
464 continue;
465 }
466 $pluginname = $item->getFilename();
467 if ($plugintype === 'auth' and $pluginname === 'db') {
468 // Special exception for this wrong plugin name.
469 } else if (isset(self::$ignoreddirs[$pluginname])) {
470 continue;
471 }
472 if (!self::is_valid_plugin_name($plugintype, $pluginname)) {
473 // Always ignore plugins with problematic names here.
474 continue;
475 }
476 $result[$pluginname] = $fulldir.'/'.$pluginname;
477 unset($item);
478 }
479 unset($items);
480 }
481
482 ksort($result);
483 return $result;
484 }
485
486 /**
487 * Find all classes that can be autoloaded including frankenstyle namespaces.
488 */
489 protected static function fill_classmap_cache() {
490 global $CFG;
491
492 self::$classmap = array();
493
494 self::load_classes('core', "$CFG->dirroot/lib/classes");
495
496 foreach (self::$subsystems as $subsystem => $fulldir) {
497 self::load_classes('core_'.$subsystem, "$fulldir/classes");
498 }
499
500 foreach (self::$plugins as $plugintype => $plugins) {
501 foreach ($plugins as $pluginname => $fulldir) {
502 self::load_classes($plugintype.'_'.$pluginname, "$fulldir/classes");
503 }
504 }
505
d534708f
PS
506 // Note: Add extra deprecated legacy classes here as necessary.
507 self::$classmap['textlib'] = "$CFG->dirroot/lib/classes/text.php";
508 self::$classmap['collatorlib'] = "$CFG->dirroot/lib/classes/collator.php";
9e19a0f0
PS
509 }
510
511 /**
512 * Find classes in directory and recurse to subdirs.
513 * @param string $component
514 * @param string $fulldir
515 * @param string $namespace
516 */
517 protected static function load_classes($component, $fulldir, $namespace = '') {
518 if (!is_dir($fulldir)) {
519 return;
520 }
521
522 $items = new \DirectoryIterator($fulldir);
523 foreach ($items as $item) {
524 if ($item->isDot()) {
525 continue;
526 }
527 if ($item->isDir()) {
528 $dirname = $item->getFilename();
529 self::load_classes($component, "$fulldir/$dirname", $namespace.'\\'.$dirname);
530 continue;
531 }
532
533 $filename = $item->getFilename();
534 $classname = preg_replace('/\.php$/', '', $filename);
535
536 if ($filename === $classname) {
537 // Not a php file.
538 continue;
539 }
540 if ($namespace === '') {
541 // Legacy long frankenstyle class name.
542 self::$classmap[$component.'_'.$classname] = "$fulldir/$filename";
543 }
544 // New namespaced classes.
545 self::$classmap[$component.$namespace.'\\'.$classname] = "$fulldir/$filename";
546 }
547 unset($item);
548 unset($items);
549 }
550
551 /**
552 * List all core subsystems and their location
553 *
554 * This is a whitelist of components that are part of the core and their
555 * language strings are defined in /lang/en/<<subsystem>>.php. If a given
556 * plugin is not listed here and it does not have proper plugintype prefix,
557 * then it is considered as course activity module.
558 *
559 * The location is absolute file path to dir. NULL means there is no special
560 * directory for this subsystem. If the location is set, the subsystem's
561 * renderer.php is expected to be there.
562 *
563 * @return array of (string)name => (string|null)full dir location
564 */
565 public static function get_core_subsystems() {
566 self::init();
567 return self::$subsystems;
568 }
569
570 /**
571 * Get list of available plugin types together with their location.
572 *
573 * @return array as (string)plugintype => (string)fulldir
574 */
575 public static function get_plugin_types() {
576 self::init();
577 return self::$plugintypes;
578 }
579
580 /**
581 * Get list of plugins of given type.
582 *
583 * @param string $plugintype
584 * @return array as (string)pluginname => (string)fulldir
585 */
586 public static function get_plugin_list($plugintype) {
587 self::init();
588
589 if (!isset(self::$plugins[$plugintype])) {
590 return array();
591 }
592 return self::$plugins[$plugintype];
593 }
594
595 /**
596 * Get a list of all the plugins of a given type that define a certain class
597 * in a certain file. The plugin component names and class names are returned.
598 *
599 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
600 * @param string $class the part of the name of the class after the
601 * frankenstyle prefix. e.g 'thing' if you are looking for classes with
602 * names like report_courselist_thing. If you are looking for classes with
603 * the same name as the plugin name (e.g. qtype_multichoice) then pass ''.
604 * Frankenstyle namespaces are also supported.
605 * @param string $file the name of file within the plugin that defines the class.
606 * @return array with frankenstyle plugin names as keys (e.g. 'report_courselist', 'mod_forum')
607 * and the class names as values (e.g. 'report_courselist_thing', 'qtype_multichoice').
608 */
609 public static function get_plugin_list_with_class($plugintype, $class, $file = null) {
610 global $CFG; // Necessary in case it is referenced by included PHP scripts.
611
612 if ($class) {
613 $suffix = '_' . $class;
614 } else {
615 $suffix = '';
616 }
617
618 $pluginclasses = array();
619 $plugins = self::get_plugin_list($plugintype);
620 foreach ($plugins as $plugin => $fulldir) {
621 // Try class in frankenstyle namespace.
622 if ($class) {
623 $classname = '\\' . $plugintype . '_' . $plugin . '\\' . $class;
624 if (class_exists($classname, true)) {
625 $pluginclasses[$plugintype . '_' . $plugin] = $classname;
626 continue;
627 }
628 }
629
630 // Try autoloading of class with frankenstyle prefix.
631 $classname = $plugintype . '_' . $plugin . $suffix;
632 if (class_exists($classname, true)) {
633 $pluginclasses[$plugintype . '_' . $plugin] = $classname;
634 continue;
635 }
636
637 // Fall back to old file location and class name.
638 if ($file and file_exists("$fulldir/$file")) {
639 include_once("$fulldir/$file");
640 if (class_exists($classname, false)) {
641 $pluginclasses[$plugintype . '_' . $plugin] = $classname;
642 continue;
643 }
644 }
645 }
646
647 return $pluginclasses;
648 }
649
650 /**
651 * Returns the exact absolute path to plugin directory.
652 *
653 * @param string $plugintype type of plugin
654 * @param string $pluginname name of the plugin
655 * @return string full path to plugin directory; null if not found
656 */
657 public static function get_plugin_directory($plugintype, $pluginname) {
658 if (empty($pluginname)) {
659 // Invalid plugin name, sorry.
660 return null;
661 }
662
663 self::init();
664
665 if (!isset(self::$plugins[$plugintype][$pluginname])) {
666 return null;
667 }
668 return self::$plugins[$plugintype][$pluginname];
669 }
670
671 /**
672 * Returns the exact absolute path to plugin directory.
673 *
674 * @param string $subsystem type of core subsystem
675 * @return string full path to subsystem directory; null if not found
676 */
677 public static function get_subsystem_directory($subsystem) {
678 self::init();
679
680 if (!isset(self::$subsystems[$subsystem])) {
681 return null;
682 }
683 return self::$subsystems[$subsystem];
684 }
685
686 /**
687 * This method validates a plug name. It is much faster than calling clean_param.
688 *
689 * @param string $plugintype type of plugin
690 * @param string $pluginname a string that might be a plugin name.
691 * @return bool if this string is a valid plugin name.
692 */
693 public static function is_valid_plugin_name($plugintype, $pluginname) {
694 if ($plugintype === 'mod') {
695 // Modules must not have the same name as core subsystems.
696 if (!isset(self::$subsystems)) {
697 // Watch out, this is called from init!
698 self::init();
699 }
700 if (isset(self::$subsystems[$pluginname])) {
701 return false;
702 }
703 // Modules MUST NOT have any underscores,
704 // component normalisation would break very badly otherwise!
705 return (bool)preg_match('/^[a-z][a-z0-9]*$/', $pluginname);
706
707 } else {
708 return (bool)preg_match('/^[a-z](?:[a-z0-9_](?!__))*[a-z0-9]$/', $pluginname);
709 }
710 }
711
712 /**
713 * Normalize the component name using the "frankenstyle" rules.
714 *
715 * Note: this does not verify the validity of plugin or type names.
716 *
717 * @param string $component
718 * @return array as (string)$type => (string)$plugin
719 */
720 public static function normalize_component($component) {
721 if ($component === 'moodle' or $component === 'core' or $component === '') {
722 return array('core', null);
723 }
724
725 if (strpos($component, '_') === false) {
726 self::init();
727 if (array_key_exists($component, self::$subsystems)) {
728 $type = 'core';
729 $plugin = $component;
730 } else {
731 // Everything else without underscore is a module.
732 $type = 'mod';
733 $plugin = $component;
734 }
735
736 } else {
737 list($type, $plugin) = explode('_', $component, 2);
738 if ($type === 'moodle') {
739 $type = 'core';
740 }
741 // Any unknown type must be a subplugin.
742 }
743
744 return array($type, $plugin);
745 }
746
747 /**
748 * Return exact absolute path to a plugin directory.
749 *
750 * @param string $component name such as 'moodle', 'mod_forum'
751 * @return string full path to component directory; NULL if not found
752 */
753 public static function get_component_directory($component) {
754 global $CFG;
755
756 list($type, $plugin) = self::normalize_component($component);
757
758 if ($type === 'core') {
759 if ($plugin === null) {
760 return $path = $CFG->libdir;
761 }
762 return self::get_subsystem_directory($plugin);
763 }
764
765 return self::get_plugin_directory($type, $plugin);
766 }
3601c5f0
PS
767
768 /**
769 * Returns list of plugin types that allow subplugins.
770 * @return array as (string)plugintype => (string)fulldir
771 */
772 public static function get_plugin_types_with_subplugins() {
773 self::init();
774
775 $return = array();
776 foreach (self::$supportsubplugins as $type) {
777 $return[$type] = self::$plugintypes[$type];
778 }
779 return $return;
780 }
c05a5099
PS
781
782 /**
783 * Invalidate opcode cache for given file, this is intended for
784 * php files that are stored in dataroot.
785 *
786 * Note: we need it here because this class must be self-contained.
787 *
788 * @param string $file
789 */
790 public static function invalidate_opcode_php_cache($file) {
791 if (function_exists('opcache_invalidate')) {
792 if (!file_exists($file)) {
793 return;
794 }
795 opcache_invalidate($file, true);
796 }
797 }
9e19a0f0 798}