[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/classes/ -> component.php (source)

   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  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1