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