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