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