2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * Components (core subsystems + plugins) related code.
21 * @copyright 2013 Petr Skoda {@link http://skodak.org}
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 defined('MOODLE_INTERNAL') ||
die();
27 // Constants used in version.php files, these must exist when core_component executes.
29 /** Software maturity level - internals can be tested using white box techniques. */
30 define('MATURITY_ALPHA', 50);
31 /** Software maturity level - feature complete, ready for preview and testing. */
32 define('MATURITY_BETA', 100);
33 /** Software maturity level - tested, will be released unless there are fatal bugs. */
34 define('MATURITY_RC', 150);
35 /** Software maturity level - ready for production deployment. */
36 define('MATURITY_STABLE', 200);
37 /** Any version - special value that can be used in $plugin->dependencies in version.php files. */
38 define('ANY_VERSION', 'any');
42 * Collection of components related methods.
44 class core_component
{
45 /** @var array list of ignored directories - watch out for auth/db exception */
46 protected static $ignoreddirs = array('CVS'=>true, '_vti_cnf'=>true, 'simpletest'=>true, 'db'=>true, 'yui'=>true, 'tests'=>true, 'classes'=>true, 'fonts'=>true);
47 /** @var array list plugin types that support subplugins, do not add more here unless absolutely necessary */
48 protected static $supportsubplugins = array('mod', 'editor', 'tool', 'local');
50 /** @var null cache of plugin types */
51 protected static $plugintypes = null;
52 /** @var null cache of plugin locations */
53 protected static $plugins = null;
54 /** @var null cache of core subsystems */
55 protected static $subsystems = null;
56 /** @var null subplugin type parents */
57 protected static $parents = null;
58 /** @var null subplugins */
59 protected static $subplugins = null;
60 /** @var null list of all known classes that can be autoloaded */
61 protected static $classmap = null;
62 /** @var null list of some known files that can be included. */
63 protected static $filemap = null;
64 /** @var int|float core version. */
65 protected static $version = null;
66 /** @var array list of the files to map. */
67 protected static $filestomap = array('lib.php', 'settings.php');
70 * Class loader for Frankenstyle named classes in standard locations.
71 * Frankenstyle namespaces are supported.
73 * The expected location for core classes is:
74 * 1/ core_xx_yy_zz ---> lib/classes/xx_yy_zz.php
75 * 2/ \core\xx_yy_zz ---> lib/classes/xx_yy_zz.php
76 * 3/ \core\xx\yy_zz ---> lib/classes/xx/yy_zz.php
78 * The expected location for plugin classes is:
79 * 1/ mod_name_xx_yy_zz ---> mod/name/classes/xx_yy_zz.php
80 * 2/ \mod_name\xx_yy_zz ---> mod/name/classes/xx_yy_zz.php
81 * 3/ \mod_name\xx\yy_zz ---> mod/name/classes/xx/yy_zz.php
83 * @param string $classname
85 public static function classloader($classname) {
88 if (isset(self
::$classmap[$classname])) {
89 // Global $CFG is expected in included scripts.
91 // Function include would be faster, but for BC it is better to include only once.
92 include_once(self
::$classmap[$classname]);
98 * Initialise caches, always call before accessing self:: caches.
100 protected static function init() {
103 // Init only once per request/CLI execution, we ignore changes done afterwards.
104 if (isset(self
::$plugintypes)) {
108 if (defined('IGNORE_COMPONENT_CACHE') and IGNORE_COMPONENT_CACHE
) {
109 self
::fill_all_caches();
113 if (!empty($CFG->alternative_component_cache
)) {
114 // Hack for heavily clustered sites that want to manage component cache invalidation manually.
115 $cachefile = $CFG->alternative_component_cache
;
117 if (file_exists($cachefile)) {
118 if (CACHE_DISABLE_ALL
) {
119 // Verify the cache state only on upgrade pages.
120 $content = self
::get_cache_content();
121 if (sha1_file($cachefile) !== sha1($content)) {
122 die('Outdated component cache file defined in $CFG->alternative_component_cache, can not continue');
128 self
::$plugintypes = $cache['plugintypes'];
129 self
::$plugins = $cache['plugins'];
130 self
::$subsystems = $cache['subsystems'];
131 self
::$parents = $cache['parents'];
132 self
::$subplugins = $cache['subplugins'];
133 self
::$classmap = $cache['classmap'];
134 self
::$filemap = $cache['filemap'];
138 if (!is_writable(dirname($cachefile))) {
139 die('Can not create alternative component cache file defined in $CFG->alternative_component_cache, can not continue');
142 // Lets try to create the file, it might be in some writable directory or a local cache dir.
145 // Note: $CFG->cachedir MUST be shared by all servers in a cluster,
146 // use $CFG->alternative_component_cache if you do not like it.
147 $cachefile = "$CFG->cachedir/core_component.php";
150 if (!CACHE_DISABLE_ALL
and !self
::is_developer()) {
151 // 1/ Use the cache only outside of install and upgrade.
152 // 2/ Let developers add/remove classes in developer mode.
153 if (is_readable($cachefile)) {
156 if (!is_array($cache)) {
157 // Something is very wrong.
158 } else if (!isset($cache['version'])) {
159 // Something is very wrong.
160 } else if ((float) $cache['version'] !== (float) self
::fetch_core_version()) {
161 // Outdated cache. We trigger an error log to track an eventual repetitive failure of float comparison.
162 error_log('Resetting core_component cache after core upgrade to version ' . self
::fetch_core_version());
163 } else if ($cache['plugintypes']['mod'] !== "$CFG->dirroot/mod") {
164 // $CFG->dirroot was changed.
166 // The cache looks ok, let's use it.
167 self
::$plugintypes = $cache['plugintypes'];
168 self
::$plugins = $cache['plugins'];
169 self
::$subsystems = $cache['subsystems'];
170 self
::$parents = $cache['parents'];
171 self
::$subplugins = $cache['subplugins'];
172 self
::$classmap = $cache['classmap'];
173 self
::$filemap = $cache['filemap'];
176 // Note: we do not verify $CFG->admin here intentionally,
177 // they must visit admin/index.php after any change.
181 if (!isset(self
::$plugintypes)) {
182 // This needs to be atomic and self-fixing as much as possible.
184 $content = self
::get_cache_content();
185 if (file_exists($cachefile)) {
186 if (sha1_file($cachefile) === sha1($content)) {
189 // Stale cache detected!
193 // Permissions might not be setup properly in installers.
194 $dirpermissions = !isset($CFG->directorypermissions
) ?
02777 : $CFG->directorypermissions
;
195 $filepermissions = !isset($CFG->filepermissions
) ?
($dirpermissions & 0666) : $CFG->filepermissions
;
198 $cachedir = dirname($cachefile);
199 if (!is_dir($cachedir)) {
200 mkdir($cachedir, $dirpermissions, true);
203 if ($fp = @fopen
($cachefile.'.tmp', 'xb')) {
204 fwrite($fp, $content);
206 @rename
($cachefile.'.tmp', $cachefile);
207 @chmod
($cachefile, $filepermissions);
209 @unlink
($cachefile.'.tmp'); // Just in case anything fails (race condition).
210 self
::invalidate_opcode_php_cache($cachefile);
215 * Are we in developer debug mode?
217 * Note: You need to set "$CFG->debug = (E_ALL | E_STRICT);" in config.php,
218 * the reason is we need to use this before we setup DB connection or caches for CFG.
222 protected static function is_developer() {
225 // Note we can not rely on $CFG->debug here because DB is not initialised yet.
226 if (isset($CFG->config_php_settings
['debug'])) {
227 $debug = (int)$CFG->config_php_settings
['debug'];
232 if ($debug & E_ALL
and $debug & E_STRICT
) {
240 * Create cache file content.
242 * @private this is intended for $CFG->alternative_component_cache only.
246 public static function get_cache_content() {
247 if (!isset(self
::$plugintypes)) {
248 self
::fill_all_caches();
252 'subsystems' => self
::$subsystems,
253 'plugintypes' => self
::$plugintypes,
254 'plugins' => self
::$plugins,
255 'parents' => self
::$parents,
256 'subplugins' => self
::$subplugins,
257 'classmap' => self
::$classmap,
258 'filemap' => self
::$filemap,
259 'version' => self
::$version,
263 $cache = '.var_export($cache, true).';
270 protected static function fill_all_caches() {
271 self
::$subsystems = self
::fetch_subsystems();
273 list(self
::$plugintypes, self
::$parents, self
::$subplugins) = self
::fetch_plugintypes();
275 self
::$plugins = array();
276 foreach (self
::$plugintypes as $type => $fulldir) {
277 self
::$plugins[$type] = self
::fetch_plugins($type, $fulldir);
280 self
::fill_classmap_cache();
281 self
::fill_filemap_cache();
282 self
::fetch_core_version();
286 * Get the core version.
288 * In order for this to work properly, opcache should be reset beforehand.
290 * @return float core version.
292 protected static function fetch_core_version() {
294 if (self
::$version === null) {
295 $version = null; // Prevent IDE complaints.
296 require($CFG->dirroot
. '/version.php');
297 self
::$version = $version;
299 return self
::$version;
303 * Returns list of core subsystems.
306 protected static function fetch_subsystems() {
309 // NOTE: Any additions here must be verified to not collide with existing add-on modules and subplugins!!!
313 'admin' => $CFG->dirroot
.'/'.$CFG->admin
,
314 'auth' => $CFG->dirroot
.'/auth',
315 'backup' => $CFG->dirroot
.'/backup/util/ui',
316 'badges' => $CFG->dirroot
.'/badges',
317 'block' => $CFG->dirroot
.'/blocks',
318 'blog' => $CFG->dirroot
.'/blog',
320 'cache' => $CFG->dirroot
.'/cache',
321 'calendar' => $CFG->dirroot
.'/calendar',
322 'cohort' => $CFG->dirroot
.'/cohort',
324 'completion' => null,
326 'course' => $CFG->dirroot
.'/course',
327 'currencies' => null,
328 'dbtransfer' => null,
330 'editor' => $CFG->dirroot
.'/lib/editor',
332 'enrol' => $CFG->dirroot
.'/enrol',
334 'filepicker' => null,
335 'files' => $CFG->dirroot
.'/files',
337 //'fonts' => null, // Bogus.
338 'form' => $CFG->dirroot
.'/lib/form',
339 'grades' => $CFG->dirroot
.'/grade',
340 'grading' => $CFG->dirroot
.'/grade/grading',
341 'group' => $CFG->dirroot
.'/group',
347 'langconfig' => null,
351 'message' => $CFG->dirroot
.'/message',
353 'mnet' => $CFG->dirroot
.'/mnet',
354 //'moodle.org' => null, // Not used any more.
355 'my' => $CFG->dirroot
.'/my',
356 'notes' => $CFG->dirroot
.'/notes',
359 'plagiarism' => $CFG->dirroot
.'/plagiarism',
361 'portfolio' => $CFG->dirroot
.'/portfolio',
362 'publish' => $CFG->dirroot
.'/course/publish',
363 'question' => $CFG->dirroot
.'/question',
364 'rating' => $CFG->dirroot
.'/rating',
365 'register' => $CFG->dirroot
.'/'.$CFG->admin
.'/registration', // Broken badly if $CFG->admin changed.
366 'repository' => $CFG->dirroot
.'/repository',
367 'rss' => $CFG->dirroot
.'/rss',
368 'role' => $CFG->dirroot
.'/'.$CFG->admin
.'/roles',
371 'tag' => $CFG->dirroot
.'/tag',
373 'user' => $CFG->dirroot
.'/user',
375 'webservice' => $CFG->dirroot
.'/webservice',
382 * Returns list of known plugin types.
385 protected static function fetch_plugintypes() {
389 'qtype' => $CFG->dirroot
.'/question/type',
390 'mod' => $CFG->dirroot
.'/mod',
391 'auth' => $CFG->dirroot
.'/auth',
392 'calendartype' => $CFG->dirroot
.'/calendar/type',
393 'enrol' => $CFG->dirroot
.'/enrol',
394 'message' => $CFG->dirroot
.'/message/output',
395 'block' => $CFG->dirroot
.'/blocks',
396 'filter' => $CFG->dirroot
.'/filter',
397 'editor' => $CFG->dirroot
.'/lib/editor',
398 'format' => $CFG->dirroot
.'/course/format',
399 'profilefield' => $CFG->dirroot
.'/user/profile/field',
400 'report' => $CFG->dirroot
.'/report',
401 'coursereport' => $CFG->dirroot
.'/course/report', // Must be after system reports.
402 'gradeexport' => $CFG->dirroot
.'/grade/export',
403 'gradeimport' => $CFG->dirroot
.'/grade/import',
404 'gradereport' => $CFG->dirroot
.'/grade/report',
405 'gradingform' => $CFG->dirroot
.'/grade/grading/form',
406 'mnetservice' => $CFG->dirroot
.'/mnet/service',
407 'webservice' => $CFG->dirroot
.'/webservice',
408 'repository' => $CFG->dirroot
.'/repository',
409 'portfolio' => $CFG->dirroot
.'/portfolio',
410 'qbehaviour' => $CFG->dirroot
.'/question/behaviour',
411 'qformat' => $CFG->dirroot
.'/question/format',
412 'plagiarism' => $CFG->dirroot
.'/plagiarism',
413 'tool' => $CFG->dirroot
.'/'.$CFG->admin
.'/tool',
414 'cachestore' => $CFG->dirroot
.'/cache/stores',
415 'cachelock' => $CFG->dirroot
.'/cache/locks',
418 $subplugins = array();
420 if (!empty($CFG->themedir
) and is_dir($CFG->themedir
) ) {
421 $types['theme'] = $CFG->themedir
;
423 $types['theme'] = $CFG->dirroot
.'/theme';
426 foreach (self
::$supportsubplugins as $type) {
427 if ($type === 'local') {
428 // Local subplugins must be after local plugins.
431 $plugins = self
::fetch_plugins($type, $types[$type]);
432 foreach ($plugins as $plugin => $fulldir) {
433 $subtypes = self
::fetch_subtypes($fulldir);
437 $subplugins[$type.'_'.$plugin] = array();
438 foreach($subtypes as $subtype => $subdir) {
439 if (isset($types[$subtype])) {
440 error_log("Invalid subtype '$subtype', duplicate detected.");
443 $types[$subtype] = $subdir;
444 $parents[$subtype] = $type.'_'.$plugin;
445 $subplugins[$type.'_'.$plugin][$subtype] = array_keys(self
::fetch_plugins($subtype, $subdir));
449 // Local is always last!
450 $types['local'] = $CFG->dirroot
.'/local';
452 if (in_array('local', self
::$supportsubplugins)) {
454 $plugins = self
::fetch_plugins($type, $types[$type]);
455 foreach ($plugins as $plugin => $fulldir) {
456 $subtypes = self
::fetch_subtypes($fulldir);
460 $subplugins[$type.'_'.$plugin] = array();
461 foreach($subtypes as $subtype => $subdir) {
462 if (isset($types[$subtype])) {
463 error_log("Invalid subtype '$subtype', duplicate detected.");
466 $types[$subtype] = $subdir;
467 $parents[$subtype] = $type.'_'.$plugin;
468 $subplugins[$type.'_'.$plugin][$subtype] = array_keys(self
::fetch_plugins($subtype, $subdir));
473 return array($types, $parents, $subplugins);
477 * Returns list of subtypes.
478 * @param string $ownerdir
481 protected static function fetch_subtypes($ownerdir) {
485 if (file_exists("$ownerdir/db/subplugins.php")) {
486 $subplugins = array();
487 include("$ownerdir/db/subplugins.php");
488 foreach ($subplugins as $subtype => $dir) {
489 if (!preg_match('/^[a-z][a-z0-9]*$/', $subtype)) {
490 error_log("Invalid subtype '$subtype'' detected in '$ownerdir', invalid characters present.");
493 if (isset(self
::$subsystems[$subtype])) {
494 error_log("Invalid subtype '$subtype'' detected in '$ownerdir', duplicates core subsystem.");
497 if ($CFG->admin
!== 'admin' and strpos($dir, 'admin/') === 0) {
498 $dir = preg_replace('|^admin/|', "$CFG->admin/", $dir);
500 if (!is_dir("$CFG->dirroot/$dir")) {
501 error_log("Invalid subtype directory '$dir' detected in '$ownerdir'.");
504 $types[$subtype] = "$CFG->dirroot/$dir";
511 * Returns list of plugins of given type in given directory.
512 * @param string $plugintype
513 * @param string $fulldir
516 protected static function fetch_plugins($plugintype, $fulldir) {
519 $fulldirs = (array)$fulldir;
520 if ($plugintype === 'theme') {
521 if (realpath($fulldir) !== realpath($CFG->dirroot
.'/theme')) {
522 // Include themes in standard location too.
523 array_unshift($fulldirs, $CFG->dirroot
.'/theme');
529 foreach ($fulldirs as $fulldir) {
530 if (!is_dir($fulldir)) {
533 $items = new \
DirectoryIterator($fulldir);
534 foreach ($items as $item) {
535 if ($item->isDot() or !$item->isDir()) {
538 $pluginname = $item->getFilename();
539 if ($plugintype === 'auth' and $pluginname === 'db') {
540 // Special exception for this wrong plugin name.
541 } else if (isset(self
::$ignoreddirs[$pluginname])) {
544 if (!self
::is_valid_plugin_name($plugintype, $pluginname)) {
545 // Always ignore plugins with problematic names here.
548 $result[$pluginname] = $fulldir.'/'.$pluginname;
559 * Find all classes that can be autoloaded including frankenstyle namespaces.
561 protected static function fill_classmap_cache() {
564 self
::$classmap = array();
566 self
::load_classes('core', "$CFG->dirroot/lib/classes");
568 foreach (self
::$subsystems as $subsystem => $fulldir) {
569 self
::load_classes('core_'.$subsystem, "$fulldir/classes");
572 foreach (self
::$plugins as $plugintype => $plugins) {
573 foreach ($plugins as $pluginname => $fulldir) {
574 self
::load_classes($plugintype.'_'.$pluginname, "$fulldir/classes");
578 // Note: Add extra deprecated legacy classes here as necessary.
579 self
::$classmap['textlib'] = "$CFG->dirroot/lib/classes/text.php";
580 self
::$classmap['collatorlib'] = "$CFG->dirroot/lib/classes/collator.php";
585 * Fills up the cache defining what plugins have certain files.
587 * @see self::get_plugin_list_with_file
590 protected static function fill_filemap_cache() {
593 self
::$filemap = array();
595 foreach (self
::$filestomap as $file) {
596 if (!isset(self
::$filemap[$file])) {
597 self
::$filemap[$file] = array();
599 foreach (self
::$plugins as $plugintype => $plugins) {
600 if (!isset(self
::$filemap[$file][$plugintype])) {
601 self
::$filemap[$file][$plugintype] = array();
603 foreach ($plugins as $pluginname => $fulldir) {
604 if (file_exists("$fulldir/$file")) {
605 self
::$filemap[$file][$plugintype][$pluginname] = "$fulldir/$file";
613 * Find classes in directory and recurse to subdirs.
614 * @param string $component
615 * @param string $fulldir
616 * @param string $namespace
618 protected static function load_classes($component, $fulldir, $namespace = '') {
619 if (!is_dir($fulldir)) {
623 $items = new \
DirectoryIterator($fulldir);
624 foreach ($items as $item) {
625 if ($item->isDot()) {
628 if ($item->isDir()) {
629 $dirname = $item->getFilename();
630 self
::load_classes($component, "$fulldir/$dirname", $namespace.'\\'.$dirname);
634 $filename = $item->getFilename();
635 $classname = preg_replace('/\.php$/', '', $filename);
637 if ($filename === $classname) {
641 if ($namespace === '') {
642 // Legacy long frankenstyle class name.
643 self
::$classmap[$component.'_'.$classname] = "$fulldir/$filename";
645 // New namespaced classes.
646 self
::$classmap[$component.$namespace.'\\'.$classname] = "$fulldir/$filename";
653 * List all core subsystems and their location
655 * This is a whitelist of components that are part of the core and their
656 * language strings are defined in /lang/en/<<subsystem>>.php. If a given
657 * plugin is not listed here and it does not have proper plugintype prefix,
658 * then it is considered as course activity module.
660 * The location is absolute file path to dir. NULL means there is no special
661 * directory for this subsystem. If the location is set, the subsystem's
662 * renderer.php is expected to be there.
664 * @return array of (string)name => (string|null)full dir location
666 public static function get_core_subsystems() {
668 return self
::$subsystems;
672 * Get list of available plugin types together with their location.
674 * @return array as (string)plugintype => (string)fulldir
676 public static function get_plugin_types() {
678 return self
::$plugintypes;
682 * Get list of plugins of given type.
684 * @param string $plugintype
685 * @return array as (string)pluginname => (string)fulldir
687 public static function get_plugin_list($plugintype) {
690 if (!isset(self
::$plugins[$plugintype])) {
693 return self
::$plugins[$plugintype];
697 * Get a list of all the plugins of a given type that define a certain class
698 * in a certain file. The plugin component names and class names are returned.
700 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
701 * @param string $class the part of the name of the class after the
702 * frankenstyle prefix. e.g 'thing' if you are looking for classes with
703 * names like report_courselist_thing. If you are looking for classes with
704 * the same name as the plugin name (e.g. qtype_multichoice) then pass ''.
705 * Frankenstyle namespaces are also supported.
706 * @param string $file the name of file within the plugin that defines the class.
707 * @return array with frankenstyle plugin names as keys (e.g. 'report_courselist', 'mod_forum')
708 * and the class names as values (e.g. 'report_courselist_thing', 'qtype_multichoice').
710 public static function get_plugin_list_with_class($plugintype, $class, $file = null) {
711 global $CFG; // Necessary in case it is referenced by included PHP scripts.
714 $suffix = '_' . $class;
719 $pluginclasses = array();
720 $plugins = self
::get_plugin_list($plugintype);
721 foreach ($plugins as $plugin => $fulldir) {
722 // Try class in frankenstyle namespace.
724 $classname = '\\' . $plugintype . '_' . $plugin . '\\' . $class;
725 if (class_exists($classname, true)) {
726 $pluginclasses[$plugintype . '_' . $plugin] = $classname;
731 // Try autoloading of class with frankenstyle prefix.
732 $classname = $plugintype . '_' . $plugin . $suffix;
733 if (class_exists($classname, true)) {
734 $pluginclasses[$plugintype . '_' . $plugin] = $classname;
738 // Fall back to old file location and class name.
739 if ($file and file_exists("$fulldir/$file")) {
740 include_once("$fulldir/$file");
741 if (class_exists($classname, false)) {
742 $pluginclasses[$plugintype . '_' . $plugin] = $classname;
748 return $pluginclasses;
752 * Get a list of all the plugins of a given type that contain a particular file.
754 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
755 * @param string $file the name of file that must be present in the plugin.
756 * (e.g. 'view.php', 'db/install.xml').
757 * @param bool $include if true (default false), the file will be include_once-ed if found.
758 * @return array with plugin name as keys (e.g. 'forum', 'courselist') and the path
759 * to the file relative to dirroot as value (e.g. "$CFG->dirroot/mod/forum/view.php").
761 public static function get_plugin_list_with_file($plugintype, $file, $include = false) {
762 global $CFG; // Necessary in case it is referenced by included PHP scripts.
763 $pluginfiles = array();
765 if (isset(self
::$filemap[$file])) {
766 // If the file was supposed to be mapped, then it should have been set in the array.
767 if (isset(self
::$filemap[$file][$plugintype])) {
768 $pluginfiles = self
::$filemap[$file][$plugintype];
771 // Old-style search for non-cached files.
772 $plugins = self
::get_plugin_list($plugintype);
773 foreach ($plugins as $plugin => $fulldir) {
774 $path = $fulldir . '/' . $file;
775 if (file_exists($path)) {
776 $pluginfiles[$plugin] = $path;
782 foreach ($pluginfiles as $path) {
791 * Returns the exact absolute path to plugin directory.
793 * @param string $plugintype type of plugin
794 * @param string $pluginname name of the plugin
795 * @return string full path to plugin directory; null if not found
797 public static function get_plugin_directory($plugintype, $pluginname) {
798 if (empty($pluginname)) {
799 // Invalid plugin name, sorry.
805 if (!isset(self
::$plugins[$plugintype][$pluginname])) {
808 return self
::$plugins[$plugintype][$pluginname];
812 * Returns the exact absolute path to plugin directory.
814 * @param string $subsystem type of core subsystem
815 * @return string full path to subsystem directory; null if not found
817 public static function get_subsystem_directory($subsystem) {
820 if (!isset(self
::$subsystems[$subsystem])) {
823 return self
::$subsystems[$subsystem];
827 * This method validates a plug name. It is much faster than calling clean_param.
829 * @param string $plugintype type of plugin
830 * @param string $pluginname a string that might be a plugin name.
831 * @return bool if this string is a valid plugin name.
833 public static function is_valid_plugin_name($plugintype, $pluginname) {
834 if ($plugintype === 'mod') {
835 // Modules must not have the same name as core subsystems.
836 if (!isset(self
::$subsystems)) {
837 // Watch out, this is called from init!
840 if (isset(self
::$subsystems[$pluginname])) {
843 // Modules MUST NOT have any underscores,
844 // component normalisation would break very badly otherwise!
845 return (bool)preg_match('/^[a-z][a-z0-9]*$/', $pluginname);
848 return (bool)preg_match('/^[a-z](?:[a-z0-9_](?!__))*[a-z0-9]+$/', $pluginname);
853 * Normalize the component name using the "frankenstyle" rules.
855 * Note: this does not verify the validity of plugin or type names.
857 * @param string $component
858 * @return array as (string)$type => (string)$plugin
860 public static function normalize_component($component) {
861 if ($component === 'moodle' or $component === 'core' or $component === '') {
862 return array('core', null);
865 if (strpos($component, '_') === false) {
867 if (array_key_exists($component, self
::$subsystems)) {
869 $plugin = $component;
871 // Everything else without underscore is a module.
873 $plugin = $component;
877 list($type, $plugin) = explode('_', $component, 2);
878 if ($type === 'moodle') {
881 // Any unknown type must be a subplugin.
884 return array($type, $plugin);
888 * Return exact absolute path to a plugin directory.
890 * @param string $component name such as 'moodle', 'mod_forum'
891 * @return string full path to component directory; NULL if not found
893 public static function get_component_directory($component) {
896 list($type, $plugin) = self
::normalize_component($component);
898 if ($type === 'core') {
899 if ($plugin === null) {
900 return $path = $CFG->libdir
;
902 return self
::get_subsystem_directory($plugin);
905 return self
::get_plugin_directory($type, $plugin);
909 * Returns list of plugin types that allow subplugins.
910 * @return array as (string)plugintype => (string)fulldir
912 public static function get_plugin_types_with_subplugins() {
916 foreach (self
::$supportsubplugins as $type) {
917 $return[$type] = self
::$plugintypes[$type];
923 * Returns parent of this subplugin type.
925 * @param string $type
926 * @return string parent component or null
928 public static function get_subtype_parent($type) {
931 if (isset(self
::$parents[$type])) {
932 return self
::$parents[$type];
939 * Return all subplugins of this component.
940 * @param string $component.
941 * @return array $subtype=>array($component, ..), null if no subtypes defined
943 public static function get_subplugins($component) {
946 if (isset(self
::$subplugins[$component])) {
947 return self
::$subplugins[$component];
954 * Returns hash of all versions including core and all plugins.
956 * This is relatively slow and not fully cached, use with care!
958 * @return string sha1 hash
960 public static function get_all_versions_hash() {
967 // Main version first.
968 $versions['core'] = self
::fetch_core_version();
970 // The problem here is tha the component cache might be stable,
971 // we want this to work also on frontpage without resetting the component cache.
973 if (CACHE_DISABLE_ALL
or (defined('IGNORE_COMPONENT_CACHE') and IGNORE_COMPONENT_CACHE
)) {
978 $plugintypes = core_component
::get_plugin_types();
979 foreach ($plugintypes as $type => $typedir) {
981 $plugs = core_component
::get_plugin_list($type);
983 $plugs = self
::fetch_plugins($type, $typedir);
985 foreach ($plugs as $plug => $fullplug) {
986 $plugin = new stdClass();
987 $plugin->version
= null;
989 @include
($fullplug.'/version.php');
990 $versions[$type.'_'.$plug] = $plugin->version
;
994 return sha1(serialize($versions));
998 * Invalidate opcode cache for given file, this is intended for
999 * php files that are stored in dataroot.
1001 * Note: we need it here because this class must be self-contained.
1003 * @param string $file
1005 public static function invalidate_opcode_php_cache($file) {
1006 if (function_exists('opcache_invalidate')) {
1007 if (!file_exists($file)) {
1010 opcache_invalidate($file, true);