[ Index ] |
PHP Cross Reference of moodle-2.8 |
[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 class_alias($newclassname, $classname); 104 return; 105 } 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 } 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 127 if (defined('IGNORE_COMPONENT_CACHE') and IGNORE_COMPONENT_CACHE) { 128 self::fill_all_caches(); 129 return; 130 } 131 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); 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']; 155 self::$psrclassmap = $cache['psrclassmap']; 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 } 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. 179 } else if (!isset($cache['version'])) { 180 // Something is very wrong. 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()); 184 } else if ($cache['plugintypes']['mod'] !== "$CFG->dirroot/mod") { 185 // $CFG->dirroot was changed. 186 } else { 187 // The cache looks ok, let's use it. 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']; 196 self::$psrclassmap = $cache['psrclassmap']; 197 return; 198 } 199 // Note: we do not verify $CFG->admin here intentionally, 200 // they must visit admin/index.php after any change. 201 } 202 } 203 204 if (!isset(self::$plugintypes)) { 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 } 212 // Stale cache detected! 213 unlink($cachefile); 214 } 215 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(); 221 $cachedir = dirname($cachefile); 222 if (!is_dir($cachedir)) { 223 mkdir($cachedir, $dirpermissions, true); 224 } 225 226 if ($fp = @fopen($cachefile.'.tmp', 'xb')) { 227 fwrite($fp, $content); 228 fclose($fp); 229 @rename($cachefile.'.tmp', $cachefile); 230 @chmod($cachefile, $filepermissions); 231 } 232 @unlink($cachefile.'.tmp'); // Just in case anything fails (race condition). 233 self::invalidate_opcode_php_cache($cachefile); 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 248 // Note we can not rely on $CFG->debug here because DB is not initialised yet. 249 if (isset($CFG->config_php_settings['debug'])) { 250 $debug = (int)$CFG->config_php_settings['debug']; 251 } else { 252 return false; 253 } 254 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 * 265 * @private this is intended for $CFG->alternative_component_cache only. 266 * 267 * @return string 268 */ 269 public static function get_cache_content() { 270 if (!isset(self::$plugintypes)) { 271 self::fill_all_caches(); 272 } 273 274 $cache = array( 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, 284 'psrclassmap' => self::$psrclassmap, 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 298 list(self::$plugintypes, self::$parents, self::$subplugins) = self::fetch_plugintypes(); 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(); 306 self::fill_classmap_renames_cache(); 307 self::fill_filemap_cache(); 308 self::fill_psr_cache(); 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) { 322 $version = null; // Prevent IDE complaints. 323 require($CFG->dirroot . '/version.php'); 324 self::$version = $version; 325 } 326 return self::$version; 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', 342 'availability' => $CFG->dirroot . '/availability', 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', 351 'completion' => null, 352 'countries' => null, 353 'course' => $CFG->dirroot.'/course', 354 'currencies' => null, 355 'dbtransfer' => null, 356 'debug' => null, 357 'editor' => $CFG->dirroot.'/lib/editor', 358 'edufields' => null, 359 'enrol' => $CFG->dirroot.'/enrol', 360 'error' => null, 361 'filepicker' => null, 362 'files' => $CFG->dirroot.'/files', 363 'filters' => null, 364 //'fonts' => null, // Bogus. 365 'form' => $CFG->dirroot.'/lib/form', 366 'grades' => $CFG->dirroot.'/grade', 367 'grading' => $CFG->dirroot.'/grade/grading', 368 'group' => $CFG->dirroot.'/group', 369 'help' => null, 370 'hub' => null, 371 'imscc' => null, 372 'install' => null, 373 'iso6392' => null, 374 'langconfig' => null, 375 'license' => null, 376 'mathslib' => null, 377 'media' => null, 378 'message' => $CFG->dirroot.'/message', 379 'mimetypes' => null, 380 'mnet' => $CFG->dirroot.'/mnet', 381 //'moodle.org' => null, // Not used any more. 382 'my' => $CFG->dirroot.'/my', 383 'notes' => $CFG->dirroot.'/notes', 384 'pagetype' => null, 385 'pix' => null, 386 'plagiarism' => $CFG->dirroot.'/plagiarism', 387 'plugin' => null, 388 'portfolio' => $CFG->dirroot.'/portfolio', 389 'publish' => $CFG->dirroot.'/course/publish', 390 'question' => $CFG->dirroot.'/question', 391 'rating' => $CFG->dirroot.'/rating', 392 'register' => $CFG->dirroot.'/'.$CFG->admin.'/registration', // Broken badly if $CFG->admin changed. 393 'repository' => $CFG->dirroot.'/repository', 394 'rss' => $CFG->dirroot.'/rss', 395 'role' => $CFG->dirroot.'/'.$CFG->admin.'/roles', 396 'search' => null, 397 'table' => null, 398 'tag' => $CFG->dirroot.'/tag', 399 'timezones' => null, 400 'user' => $CFG->dirroot.'/user', 401 'userkey' => null, 402 'webservice' => $CFG->dirroot.'/webservice', 403 ); 404 405 return $info; 406 } 407 408 /** 409 * Returns list of known plugin types. 410 * @return array 411 */ 412 protected static function fetch_plugintypes() { 413 global $CFG; 414 415 $types = array( 416 'availability' => $CFG->dirroot . '/availability/condition', 417 'qtype' => $CFG->dirroot.'/question/type', 418 'mod' => $CFG->dirroot.'/mod', 419 'auth' => $CFG->dirroot.'/auth', 420 'calendartype' => $CFG->dirroot.'/calendar/type', 421 'enrol' => $CFG->dirroot.'/enrol', 422 'message' => $CFG->dirroot.'/message/output', 423 'block' => $CFG->dirroot.'/blocks', 424 'filter' => $CFG->dirroot.'/filter', 425 'editor' => $CFG->dirroot.'/lib/editor', 426 'format' => $CFG->dirroot.'/course/format', 427 'profilefield' => $CFG->dirroot.'/user/profile/field', 428 'report' => $CFG->dirroot.'/report', 429 'coursereport' => $CFG->dirroot.'/course/report', // Must be after system reports. 430 'gradeexport' => $CFG->dirroot.'/grade/export', 431 'gradeimport' => $CFG->dirroot.'/grade/import', 432 'gradereport' => $CFG->dirroot.'/grade/report', 433 'gradingform' => $CFG->dirroot.'/grade/grading/form', 434 'mnetservice' => $CFG->dirroot.'/mnet/service', 435 'webservice' => $CFG->dirroot.'/webservice', 436 'repository' => $CFG->dirroot.'/repository', 437 'portfolio' => $CFG->dirroot.'/portfolio', 438 'qbehaviour' => $CFG->dirroot.'/question/behaviour', 439 'qformat' => $CFG->dirroot.'/question/format', 440 'plagiarism' => $CFG->dirroot.'/plagiarism', 441 'tool' => $CFG->dirroot.'/'.$CFG->admin.'/tool', 442 'cachestore' => $CFG->dirroot.'/cache/stores', 443 'cachelock' => $CFG->dirroot.'/cache/locks', 444 ); 445 $parents = array(); 446 $subplugins = array(); 447 448 if (!empty($CFG->themedir) and is_dir($CFG->themedir) ) { 449 $types['theme'] = $CFG->themedir; 450 } else { 451 $types['theme'] = $CFG->dirroot.'/theme'; 452 } 453 454 foreach (self::$supportsubplugins as $type) { 455 if ($type === 'local') { 456 // Local subplugins must be after local plugins. 457 continue; 458 } 459 $plugins = self::fetch_plugins($type, $types[$type]); 460 foreach ($plugins as $plugin => $fulldir) { 461 $subtypes = self::fetch_subtypes($fulldir); 462 if (!$subtypes) { 463 continue; 464 } 465 $subplugins[$type.'_'.$plugin] = array(); 466 foreach($subtypes as $subtype => $subdir) { 467 if (isset($types[$subtype])) { 468 error_log("Invalid subtype '$subtype', duplicate detected."); 469 continue; 470 } 471 $types[$subtype] = $subdir; 472 $parents[$subtype] = $type.'_'.$plugin; 473 $subplugins[$type.'_'.$plugin][$subtype] = array_keys(self::fetch_plugins($subtype, $subdir)); 474 } 475 } 476 } 477 // Local is always last! 478 $types['local'] = $CFG->dirroot.'/local'; 479 480 if (in_array('local', self::$supportsubplugins)) { 481 $type = 'local'; 482 $plugins = self::fetch_plugins($type, $types[$type]); 483 foreach ($plugins as $plugin => $fulldir) { 484 $subtypes = self::fetch_subtypes($fulldir); 485 if (!$subtypes) { 486 continue; 487 } 488 $subplugins[$type.'_'.$plugin] = array(); 489 foreach($subtypes as $subtype => $subdir) { 490 if (isset($types[$subtype])) { 491 error_log("Invalid subtype '$subtype', duplicate detected."); 492 continue; 493 } 494 $types[$subtype] = $subdir; 495 $parents[$subtype] = $type.'_'.$plugin; 496 $subplugins[$type.'_'.$plugin][$subtype] = array_keys(self::fetch_plugins($subtype, $subdir)); 497 } 498 } 499 } 500 501 return array($types, $parents, $subplugins); 502 } 503 504 /** 505 * Returns list of subtypes. 506 * @param string $ownerdir 507 * @return array 508 */ 509 protected static function fetch_subtypes($ownerdir) { 510 global $CFG; 511 512 $types = array(); 513 if (file_exists("$ownerdir/db/subplugins.php")) { 514 $subplugins = array(); 515 include("$ownerdir/db/subplugins.php"); 516 foreach ($subplugins as $subtype => $dir) { 517 if (!preg_match('/^[a-z][a-z0-9]*$/', $subtype)) { 518 error_log("Invalid subtype '$subtype'' detected in '$ownerdir', invalid characters present."); 519 continue; 520 } 521 if (isset(self::$subsystems[$subtype])) { 522 error_log("Invalid subtype '$subtype'' detected in '$ownerdir', duplicates core subsystem."); 523 continue; 524 } 525 if ($CFG->admin !== 'admin' and strpos($dir, 'admin/') === 0) { 526 $dir = preg_replace('|^admin/|', "$CFG->admin/", $dir); 527 } 528 if (!is_dir("$CFG->dirroot/$dir")) { 529 error_log("Invalid subtype directory '$dir' detected in '$ownerdir'."); 530 continue; 531 } 532 $types[$subtype] = "$CFG->dirroot/$dir"; 533 } 534 } 535 return $types; 536 } 537 538 /** 539 * Returns list of plugins of given type in given directory. 540 * @param string $plugintype 541 * @param string $fulldir 542 * @return array 543 */ 544 protected static function fetch_plugins($plugintype, $fulldir) { 545 global $CFG; 546 547 $fulldirs = (array)$fulldir; 548 if ($plugintype === 'theme') { 549 if (realpath($fulldir) !== realpath($CFG->dirroot.'/theme')) { 550 // Include themes in standard location too. 551 array_unshift($fulldirs, $CFG->dirroot.'/theme'); 552 } 553 } 554 555 $result = array(); 556 557 foreach ($fulldirs as $fulldir) { 558 if (!is_dir($fulldir)) { 559 continue; 560 } 561 $items = new \DirectoryIterator($fulldir); 562 foreach ($items as $item) { 563 if ($item->isDot() or !$item->isDir()) { 564 continue; 565 } 566 $pluginname = $item->getFilename(); 567 if ($plugintype === 'auth' and $pluginname === 'db') { 568 // Special exception for this wrong plugin name. 569 } else if (isset(self::$ignoreddirs[$pluginname])) { 570 continue; 571 } 572 if (!self::is_valid_plugin_name($plugintype, $pluginname)) { 573 // Always ignore plugins with problematic names here. 574 continue; 575 } 576 $result[$pluginname] = $fulldir.'/'.$pluginname; 577 unset($item); 578 } 579 unset($items); 580 } 581 582 ksort($result); 583 return $result; 584 } 585 586 /** 587 * Find all classes that can be autoloaded including frankenstyle namespaces. 588 */ 589 protected static function fill_classmap_cache() { 590 global $CFG; 591 592 self::$classmap = array(); 593 594 self::load_classes('core', "$CFG->dirroot/lib/classes"); 595 596 foreach (self::$subsystems as $subsystem => $fulldir) { 597 if (!$fulldir) { 598 continue; 599 } 600 self::load_classes('core_'.$subsystem, "$fulldir/classes"); 601 } 602 603 foreach (self::$plugins as $plugintype => $plugins) { 604 foreach ($plugins as $pluginname => $fulldir) { 605 self::load_classes($plugintype.'_'.$pluginname, "$fulldir/classes"); 606 } 607 } 608 ksort(self::$classmap); 609 } 610 611 /** 612 * Fills up the cache defining what plugins have certain files. 613 * 614 * @see self::get_plugin_list_with_file 615 * @return void 616 */ 617 protected static function fill_filemap_cache() { 618 global $CFG; 619 620 self::$filemap = array(); 621 622 foreach (self::$filestomap as $file) { 623 if (!isset(self::$filemap[$file])) { 624 self::$filemap[$file] = array(); 625 } 626 foreach (self::$plugins as $plugintype => $plugins) { 627 if (!isset(self::$filemap[$file][$plugintype])) { 628 self::$filemap[$file][$plugintype] = array(); 629 } 630 foreach ($plugins as $pluginname => $fulldir) { 631 if (file_exists("$fulldir/$file")) { 632 self::$filemap[$file][$plugintype][$pluginname] = "$fulldir/$file"; 633 } 634 } 635 } 636 } 637 } 638 639 /** 640 * Find classes in directory and recurse to subdirs. 641 * @param string $component 642 * @param string $fulldir 643 * @param string $namespace 644 */ 645 protected static function load_classes($component, $fulldir, $namespace = '') { 646 if (!is_dir($fulldir)) { 647 return; 648 } 649 650 $items = new \DirectoryIterator($fulldir); 651 foreach ($items as $item) { 652 if ($item->isDot()) { 653 continue; 654 } 655 if ($item->isDir()) { 656 $dirname = $item->getFilename(); 657 self::load_classes($component, "$fulldir/$dirname", $namespace.'\\'.$dirname); 658 continue; 659 } 660 661 $filename = $item->getFilename(); 662 $classname = preg_replace('/\.php$/', '', $filename); 663 664 if ($filename === $classname) { 665 // Not a php file. 666 continue; 667 } 668 if ($namespace === '') { 669 // Legacy long frankenstyle class name. 670 self::$classmap[$component.'_'.$classname] = "$fulldir/$filename"; 671 } 672 // New namespaced classes. 673 self::$classmap[$component.$namespace.'\\'.$classname] = "$fulldir/$filename"; 674 } 675 unset($item); 676 unset($items); 677 } 678 679 /** 680 * Fill caches for classes following the PSR-0 standard for the 681 * specified Vendors. 682 * 683 * PSR Autoloading is detailed at http://www.php-fig.org/psr/psr-0/. 684 */ 685 protected static function fill_psr_cache() { 686 global $CFG; 687 688 $psrsystems = array( 689 'Horde' => 'horde/framework', 690 ); 691 self::$psrclassmap = array(); 692 693 foreach ($psrsystems as $system => $fulldir) { 694 if (!$fulldir) { 695 continue; 696 } 697 self::load_psr_classes($CFG->libdir . DIRECTORY_SEPARATOR . $fulldir); 698 } 699 } 700 701 /** 702 * Find all PSR-0 style classes in within the base directory. 703 * 704 * @param string $basedir The base directory that the PSR-type library can be found in. 705 * @param string $subdir The directory within the basedir to search for classes within. 706 */ 707 protected static function load_psr_classes($basedir, $subdir = null) { 708 if ($subdir) { 709 $fulldir = implode(DIRECTORY_SEPARATOR, array($basedir, $subdir)); 710 $classnameprefix = preg_replace('/\//', '_', $subdir); 711 } else { 712 $fulldir = $basedir; 713 } 714 if (!is_dir($fulldir)) { 715 return; 716 } 717 718 $items = new \DirectoryIterator($fulldir); 719 foreach ($items as $item) { 720 if ($item->isDot()) { 721 continue; 722 } 723 if ($item->isDir()) { 724 $dirname = $item->getFilename(); 725 $newsubdir = $dirname; 726 if ($subdir) { 727 $newsubdir = implode(DIRECTORY_SEPARATOR, array($subdir, $dirname)); 728 } 729 self::load_psr_classes($basedir, $newsubdir); 730 continue; 731 } 732 733 $filename = $item->getFilename(); 734 $classname = preg_replace('/\.php$/', '', $filename); 735 736 if ($filename === $classname) { 737 // Not a php file. 738 continue; 739 } 740 741 if ($classnameprefix) { 742 $classname = $classnameprefix . '_' . $classname; 743 } 744 745 self::$psrclassmap[$classname] = $fulldir . DIRECTORY_SEPARATOR . $filename; 746 } 747 unset($item); 748 unset($items); 749 } 750 751 /** 752 * List all core subsystems and their location 753 * 754 * This is a whitelist of components that are part of the core and their 755 * language strings are defined in /lang/en/<<subsystem>>.php. If a given 756 * plugin is not listed here and it does not have proper plugintype prefix, 757 * then it is considered as course activity module. 758 * 759 * The location is absolute file path to dir. NULL means there is no special 760 * directory for this subsystem. If the location is set, the subsystem's 761 * renderer.php is expected to be there. 762 * 763 * @return array of (string)name => (string|null)full dir location 764 */ 765 public static function get_core_subsystems() { 766 self::init(); 767 return self::$subsystems; 768 } 769 770 /** 771 * Get list of available plugin types together with their location. 772 * 773 * @return array as (string)plugintype => (string)fulldir 774 */ 775 public static function get_plugin_types() { 776 self::init(); 777 return self::$plugintypes; 778 } 779 780 /** 781 * Get list of plugins of given type. 782 * 783 * @param string $plugintype 784 * @return array as (string)pluginname => (string)fulldir 785 */ 786 public static function get_plugin_list($plugintype) { 787 self::init(); 788 789 if (!isset(self::$plugins[$plugintype])) { 790 return array(); 791 } 792 return self::$plugins[$plugintype]; 793 } 794 795 /** 796 * Get a list of all the plugins of a given type that define a certain class 797 * in a certain file. The plugin component names and class names are returned. 798 * 799 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'. 800 * @param string $class the part of the name of the class after the 801 * frankenstyle prefix. e.g 'thing' if you are looking for classes with 802 * names like report_courselist_thing. If you are looking for classes with 803 * the same name as the plugin name (e.g. qtype_multichoice) then pass ''. 804 * Frankenstyle namespaces are also supported. 805 * @param string $file the name of file within the plugin that defines the class. 806 * @return array with frankenstyle plugin names as keys (e.g. 'report_courselist', 'mod_forum') 807 * and the class names as values (e.g. 'report_courselist_thing', 'qtype_multichoice'). 808 */ 809 public static function get_plugin_list_with_class($plugintype, $class, $file = null) { 810 global $CFG; // Necessary in case it is referenced by included PHP scripts. 811 812 if ($class) { 813 $suffix = '_' . $class; 814 } else { 815 $suffix = ''; 816 } 817 818 $pluginclasses = array(); 819 $plugins = self::get_plugin_list($plugintype); 820 foreach ($plugins as $plugin => $fulldir) { 821 // Try class in frankenstyle namespace. 822 if ($class) { 823 $classname = '\\' . $plugintype . '_' . $plugin . '\\' . $class; 824 if (class_exists($classname, true)) { 825 $pluginclasses[$plugintype . '_' . $plugin] = $classname; 826 continue; 827 } 828 } 829 830 // Try autoloading of class with frankenstyle prefix. 831 $classname = $plugintype . '_' . $plugin . $suffix; 832 if (class_exists($classname, true)) { 833 $pluginclasses[$plugintype . '_' . $plugin] = $classname; 834 continue; 835 } 836 837 // Fall back to old file location and class name. 838 if ($file and file_exists("$fulldir/$file")) { 839 include_once("$fulldir/$file"); 840 if (class_exists($classname, false)) { 841 $pluginclasses[$plugintype . '_' . $plugin] = $classname; 842 continue; 843 } 844 } 845 } 846 847 return $pluginclasses; 848 } 849 850 /** 851 * Get a list of all the plugins of a given type that contain a particular file. 852 * 853 * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'. 854 * @param string $file the name of file that must be present in the plugin. 855 * (e.g. 'view.php', 'db/install.xml'). 856 * @param bool $include if true (default false), the file will be include_once-ed if found. 857 * @return array with plugin name as keys (e.g. 'forum', 'courselist') and the path 858 * to the file relative to dirroot as value (e.g. "$CFG->dirroot/mod/forum/view.php"). 859 */ 860 public static function get_plugin_list_with_file($plugintype, $file, $include = false) { 861 global $CFG; // Necessary in case it is referenced by included PHP scripts. 862 $pluginfiles = array(); 863 864 if (isset(self::$filemap[$file])) { 865 // If the file was supposed to be mapped, then it should have been set in the array. 866 if (isset(self::$filemap[$file][$plugintype])) { 867 $pluginfiles = self::$filemap[$file][$plugintype]; 868 } 869 } else { 870 // Old-style search for non-cached files. 871 $plugins = self::get_plugin_list($plugintype); 872 foreach ($plugins as $plugin => $fulldir) { 873 $path = $fulldir . '/' . $file; 874 if (file_exists($path)) { 875 $pluginfiles[$plugin] = $path; 876 } 877 } 878 } 879 880 if ($include) { 881 foreach ($pluginfiles as $path) { 882 include_once($path); 883 } 884 } 885 886 return $pluginfiles; 887 } 888 889 /** 890 * Returns the exact absolute path to plugin directory. 891 * 892 * @param string $plugintype type of plugin 893 * @param string $pluginname name of the plugin 894 * @return string full path to plugin directory; null if not found 895 */ 896 public static function get_plugin_directory($plugintype, $pluginname) { 897 if (empty($pluginname)) { 898 // Invalid plugin name, sorry. 899 return null; 900 } 901 902 self::init(); 903 904 if (!isset(self::$plugins[$plugintype][$pluginname])) { 905 return null; 906 } 907 return self::$plugins[$plugintype][$pluginname]; 908 } 909 910 /** 911 * Returns the exact absolute path to plugin directory. 912 * 913 * @param string $subsystem type of core subsystem 914 * @return string full path to subsystem directory; null if not found 915 */ 916 public static function get_subsystem_directory($subsystem) { 917 self::init(); 918 919 if (!isset(self::$subsystems[$subsystem])) { 920 return null; 921 } 922 return self::$subsystems[$subsystem]; 923 } 924 925 /** 926 * This method validates a plug name. It is much faster than calling clean_param. 927 * 928 * @param string $plugintype type of plugin 929 * @param string $pluginname a string that might be a plugin name. 930 * @return bool if this string is a valid plugin name. 931 */ 932 public static function is_valid_plugin_name($plugintype, $pluginname) { 933 if ($plugintype === 'mod') { 934 // Modules must not have the same name as core subsystems. 935 if (!isset(self::$subsystems)) { 936 // Watch out, this is called from init! 937 self::init(); 938 } 939 if (isset(self::$subsystems[$pluginname])) { 940 return false; 941 } 942 // Modules MUST NOT have any underscores, 943 // component normalisation would break very badly otherwise! 944 return (bool)preg_match('/^[a-z][a-z0-9]*$/', $pluginname); 945 946 } else { 947 return (bool)preg_match('/^[a-z](?:[a-z0-9_](?!__))*[a-z0-9]+$/', $pluginname); 948 } 949 } 950 951 /** 952 * Normalize the component name. 953 * 954 * Note: this does not verify the validity of the plugin or component. 955 * 956 * @param string $component 957 * @return string 958 */ 959 public static function normalize_componentname($componentname) { 960 list($plugintype, $pluginname) = self::normalize_component($componentname); 961 if ($plugintype === 'core' && is_null($pluginname)) { 962 return $plugintype; 963 } 964 return $plugintype . '_' . $pluginname; 965 } 966 967 /** 968 * Normalize the component name using the "frankenstyle" rules. 969 * 970 * Note: this does not verify the validity of plugin or type names. 971 * 972 * @param string $component 973 * @return array as (string)$type => (string)$plugin 974 */ 975 public static function normalize_component($component) { 976 if ($component === 'moodle' or $component === 'core' or $component === '') { 977 return array('core', null); 978 } 979 980 if (strpos($component, '_') === false) { 981 self::init(); 982 if (array_key_exists($component, self::$subsystems)) { 983 $type = 'core'; 984 $plugin = $component; 985 } else { 986 // Everything else without underscore is a module. 987 $type = 'mod'; 988 $plugin = $component; 989 } 990 991 } else { 992 list($type, $plugin) = explode('_', $component, 2); 993 if ($type === 'moodle') { 994 $type = 'core'; 995 } 996 // Any unknown type must be a subplugin. 997 } 998 999 return array($type, $plugin); 1000 } 1001 1002 /** 1003 * Return exact absolute path to a plugin directory. 1004 * 1005 * @param string $component name such as 'moodle', 'mod_forum' 1006 * @return string full path to component directory; NULL if not found 1007 */ 1008 public static function get_component_directory($component) { 1009 global $CFG; 1010 1011 list($type, $plugin) = self::normalize_component($component); 1012 1013 if ($type === 'core') { 1014 if ($plugin === null) { 1015 return $path = $CFG->libdir; 1016 } 1017 return self::get_subsystem_directory($plugin); 1018 } 1019 1020 return self::get_plugin_directory($type, $plugin); 1021 } 1022 1023 /** 1024 * Returns list of plugin types that allow subplugins. 1025 * @return array as (string)plugintype => (string)fulldir 1026 */ 1027 public static function get_plugin_types_with_subplugins() { 1028 self::init(); 1029 1030 $return = array(); 1031 foreach (self::$supportsubplugins as $type) { 1032 $return[$type] = self::$plugintypes[$type]; 1033 } 1034 return $return; 1035 } 1036 1037 /** 1038 * Returns parent of this subplugin type. 1039 * 1040 * @param string $type 1041 * @return string parent component or null 1042 */ 1043 public static function get_subtype_parent($type) { 1044 self::init(); 1045 1046 if (isset(self::$parents[$type])) { 1047 return self::$parents[$type]; 1048 } 1049 1050 return null; 1051 } 1052 1053 /** 1054 * Return all subplugins of this component. 1055 * @param string $component. 1056 * @return array $subtype=>array($component, ..), null if no subtypes defined 1057 */ 1058 public static function get_subplugins($component) { 1059 self::init(); 1060 1061 if (isset(self::$subplugins[$component])) { 1062 return self::$subplugins[$component]; 1063 } 1064 1065 return null; 1066 } 1067 1068 /** 1069 * Returns hash of all versions including core and all plugins. 1070 * 1071 * This is relatively slow and not fully cached, use with care! 1072 * 1073 * @return string sha1 hash 1074 */ 1075 public static function get_all_versions_hash() { 1076 global $CFG; 1077 1078 self::init(); 1079 1080 $versions = array(); 1081 1082 // Main version first. 1083 $versions['core'] = self::fetch_core_version(); 1084 1085 // The problem here is tha the component cache might be stable, 1086 // we want this to work also on frontpage without resetting the component cache. 1087 $usecache = false; 1088 if (CACHE_DISABLE_ALL or (defined('IGNORE_COMPONENT_CACHE') and IGNORE_COMPONENT_CACHE)) { 1089 $usecache = true; 1090 } 1091 1092 // Now all plugins. 1093 $plugintypes = core_component::get_plugin_types(); 1094 foreach ($plugintypes as $type => $typedir) { 1095 if ($usecache) { 1096 $plugs = core_component::get_plugin_list($type); 1097 } else { 1098 $plugs = self::fetch_plugins($type, $typedir); 1099 } 1100 foreach ($plugs as $plug => $fullplug) { 1101 $plugin = new stdClass(); 1102 $plugin->version = null; 1103 $module = $plugin; 1104 @include ($fullplug.'/version.php'); 1105 $versions[$type.'_'.$plug] = $plugin->version; 1106 } 1107 } 1108 1109 return sha1(serialize($versions)); 1110 } 1111 1112 /** 1113 * Invalidate opcode cache for given file, this is intended for 1114 * php files that are stored in dataroot. 1115 * 1116 * Note: we need it here because this class must be self-contained. 1117 * 1118 * @param string $file 1119 */ 1120 public static function invalidate_opcode_php_cache($file) { 1121 if (function_exists('opcache_invalidate')) { 1122 if (!file_exists($file)) { 1123 return; 1124 } 1125 opcache_invalidate($file, true); 1126 } 1127 } 1128 1129 /** 1130 * Return true if subsystemname is core subsystem. 1131 * 1132 * @param string $subsystemname name of the subsystem. 1133 * @return bool true if core subsystem. 1134 */ 1135 public static function is_core_subsystem($subsystemname) { 1136 return isset(self::$subsystems[$subsystemname]); 1137 } 1138 1139 /** 1140 * Records all class renames that have been made to facilitate autoloading. 1141 */ 1142 protected static function fill_classmap_renames_cache() { 1143 global $CFG; 1144 1145 self::$classmaprenames = array(); 1146 1147 self::load_renamed_classes("$CFG->dirroot/lib/"); 1148 1149 foreach (self::$subsystems as $subsystem => $fulldir) { 1150 self::load_renamed_classes($fulldir); 1151 } 1152 1153 foreach (self::$plugins as $plugintype => $plugins) { 1154 foreach ($plugins as $pluginname => $fulldir) { 1155 self::load_renamed_classes($fulldir); 1156 } 1157 } 1158 } 1159 1160 /** 1161 * Loads the db/renamedclasses.php file from the given directory. 1162 * 1163 * The renamedclasses.php should contain a key => value array ($renamedclasses) where the key is old class name, 1164 * and the value is the new class name. 1165 * It is only included when we are populating the component cache. After that is not needed. 1166 * 1167 * @param string $fulldir 1168 */ 1169 protected static function load_renamed_classes($fulldir) { 1170 $file = $fulldir . '/db/renamedclasses.php'; 1171 if (is_readable($file)) { 1172 $renamedclasses = null; 1173 require($file); 1174 if (is_array($renamedclasses)) { 1175 foreach ($renamedclasses as $oldclass => $newclass) { 1176 self::$classmaprenames[(string)$oldclass] = (string)$newclass; 1177 } 1178 } 1179 } 1180 } 1181 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |