[ Index ]

PHP Cross Reference of Unnamed Project

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              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  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1