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