Search moodle.org's
Developer Documentation


  • Bug fixes for general core bugs in 2.8.x ended 9 November 2015 (12 months).
  • Bug fixes for security issues in 2.8.x ended 9 May 2016 (18 months).
  • minimum PHP 5.4.4 (always use latest PHP 5.4.x or 5.5.x on Windows - http://windows.php.net/download/), PHP 7 is NOT supported
  • Differences Between: [Versions 28 and 29] [Versions 28 and 30] [Versions 28 and 31] [Versions 28 and 32] [Versions 28 and 33] [Versions 28 and 34] [Versions 28 and 35] [Versions 28 and 36] [Versions 28 and 37]

       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'  => $CFG->dirroot.'/completion',
     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          if (!is_readable($fulldir)) {
     651              // TODO: MDL-51711 We should generate some diagnostic debugging information in this case
     652              // because its pretty likely to lead to a missing class error further down the line.
     653              // But our early setup code can't handle errors this early at the moment.
     654              return;
     655          }
     656  
     657          $items = new \DirectoryIterator($fulldir);
     658          foreach ($items as $item) {
     659              if ($item->isDot()) {
     660                  continue;
     661              }
     662              if ($item->isDir()) {
     663                  $dirname = $item->getFilename();
     664                  self::load_classes($component, "$fulldir/$dirname", $namespace.'\\'.$dirname);
     665                  continue;
     666              }
     667  
     668              $filename = $item->getFilename();
     669              $classname = preg_replace('/\.php$/', '', $filename);
     670  
     671              if ($filename === $classname) {
     672                  // Not a php file.
     673                  continue;
     674              }
     675              if ($namespace === '') {
     676                  // Legacy long frankenstyle class name.
     677                  self::$classmap[$component.'_'.$classname] = "$fulldir/$filename";
     678              }
     679              // New namespaced classes.
     680              self::$classmap[$component.$namespace.'\\'.$classname] = "$fulldir/$filename";
     681          }
     682          unset($item);
     683          unset($items);
     684      }
     685  
     686      /**
     687       * Fill caches for classes following the PSR-0 standard for the
     688       * specified Vendors.
     689       *
     690       * PSR Autoloading is detailed at http://www.php-fig.org/psr/psr-0/.
     691       */
     692      protected static function fill_psr_cache() {
     693          global $CFG;
     694  
     695          $psrsystems = array(
     696              'Horde' => 'horde/framework',
     697          );
     698          self::$psrclassmap = array();
     699  
     700          foreach ($psrsystems as $system => $fulldir) {
     701              if (!$fulldir) {
     702                  continue;
     703              }
     704              self::load_psr_classes($CFG->libdir . DIRECTORY_SEPARATOR . $fulldir);
     705          }
     706      }
     707  
     708      /**
     709       * Find all PSR-0 style classes in within the base directory.
     710       *
     711       * @param string $basedir The base directory that the PSR-type library can be found in.
     712       * @param string $subdir The directory within the basedir to search for classes within.
     713       */
     714      protected static function load_psr_classes($basedir, $subdir = null) {
     715          if ($subdir) {
     716              $fulldir = realpath($basedir . DIRECTORY_SEPARATOR . $subdir);
     717              $classnameprefix = preg_replace('#' . preg_quote(DIRECTORY_SEPARATOR) . '#', '_', $subdir);
     718          } else {
     719              $fulldir = $basedir;
     720          }
     721          if (!$fulldir || !is_dir($fulldir)) {
     722              return;
     723          }
     724  
     725          $items = new \DirectoryIterator($fulldir);
     726          foreach ($items as $item) {
     727              if ($item->isDot()) {
     728                  continue;
     729              }
     730              if ($item->isDir()) {
     731                  $dirname = $item->getFilename();
     732                  $newsubdir = $dirname;
     733                  if ($subdir) {
     734                      $newsubdir = implode(DIRECTORY_SEPARATOR, array($subdir, $dirname));
     735                  }
     736                  self::load_psr_classes($basedir, $newsubdir);
     737                  continue;
     738              }
     739  
     740              $filename = $item->getFilename();
     741              $classname = preg_replace('/\.php$/', '', $filename);
     742  
     743              if ($filename === $classname) {
     744                  // Not a php file.
     745                  continue;
     746              }
     747  
     748              if ($classnameprefix) {
     749                  $classname = $classnameprefix . '_' . $classname;
     750              }
     751  
     752              self::$psrclassmap[$classname] = $fulldir . DIRECTORY_SEPARATOR . $filename;
     753          }
     754          unset($item);
     755          unset($items);
     756      }
     757  
     758      /**
     759       * List all core subsystems and their location
     760       *
     761       * This is a whitelist of components that are part of the core and their
     762       * language strings are defined in /lang/en/<<subsystem>>.php. If a given
     763       * plugin is not listed here and it does not have proper plugintype prefix,
     764       * then it is considered as course activity module.
     765       *
     766       * The location is absolute file path to dir. NULL means there is no special
     767       * directory for this subsystem. If the location is set, the subsystem's
     768       * renderer.php is expected to be there.
     769       *
     770       * @return array of (string)name => (string|null)full dir location
     771       */
     772      public static function get_core_subsystems() {
     773          self::init();
     774          return self::$subsystems;
     775      }
     776  
     777      /**
     778       * Get list of available plugin types together with their location.
     779       *
     780       * @return array as (string)plugintype => (string)fulldir
     781       */
     782      public static function get_plugin_types() {
     783          self::init();
     784          return self::$plugintypes;
     785      }
     786  
     787      /**
     788       * Get list of plugins of given type.
     789       *
     790       * @param string $plugintype
     791       * @return array as (string)pluginname => (string)fulldir
     792       */
     793      public static function get_plugin_list($plugintype) {
     794          self::init();
     795  
     796          if (!isset(self::$plugins[$plugintype])) {
     797              return array();
     798          }
     799          return self::$plugins[$plugintype];
     800      }
     801  
     802      /**
     803       * Get a list of all the plugins of a given type that define a certain class
     804       * in a certain file. The plugin component names and class names are returned.
     805       *
     806       * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
     807       * @param string $class the part of the name of the class after the
     808       *      frankenstyle prefix. e.g 'thing' if you are looking for classes with
     809       *      names like report_courselist_thing. If you are looking for classes with
     810       *      the same name as the plugin name (e.g. qtype_multichoice) then pass ''.
     811       *      Frankenstyle namespaces are also supported.
     812       * @param string $file the name of file within the plugin that defines the class.
     813       * @return array with frankenstyle plugin names as keys (e.g. 'report_courselist', 'mod_forum')
     814       *      and the class names as values (e.g. 'report_courselist_thing', 'qtype_multichoice').
     815       */
     816      public static function get_plugin_list_with_class($plugintype, $class, $file = null) {
     817          global $CFG; // Necessary in case it is referenced by included PHP scripts.
     818  
     819          if ($class) {
     820              $suffix = '_' . $class;
     821          } else {
     822              $suffix = '';
     823          }
     824  
     825          $pluginclasses = array();
     826          $plugins = self::get_plugin_list($plugintype);
     827          foreach ($plugins as $plugin => $fulldir) {
     828              // Try class in frankenstyle namespace.
     829              if ($class) {
     830                  $classname = '\\' . $plugintype . '_' . $plugin . '\\' . $class;
     831                  if (class_exists($classname, true)) {
     832                      $pluginclasses[$plugintype . '_' . $plugin] = $classname;
     833                      continue;
     834                  }
     835              }
     836  
     837              // Try autoloading of class with frankenstyle prefix.
     838              $classname = $plugintype . '_' . $plugin . $suffix;
     839              if (class_exists($classname, true)) {
     840                  $pluginclasses[$plugintype . '_' . $plugin] = $classname;
     841                  continue;
     842              }
     843  
     844              // Fall back to old file location and class name.
     845              if ($file and file_exists("$fulldir/$file")) {
     846                  include_once("$fulldir/$file");
     847                  if (class_exists($classname, false)) {
     848                      $pluginclasses[$plugintype . '_' . $plugin] = $classname;
     849                      continue;
     850                  }
     851              }
     852          }
     853  
     854          return $pluginclasses;
     855      }
     856  
     857      /**
     858       * Get a list of all the plugins of a given type that contain a particular file.
     859       *
     860       * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
     861       * @param string $file the name of file that must be present in the plugin.
     862       *                     (e.g. 'view.php', 'db/install.xml').
     863       * @param bool $include if true (default false), the file will be include_once-ed if found.
     864       * @return array with plugin name as keys (e.g. 'forum', 'courselist') and the path
     865       *               to the file relative to dirroot as value (e.g. "$CFG->dirroot/mod/forum/view.php").
     866       */
     867      public static function get_plugin_list_with_file($plugintype, $file, $include = false) {
     868          global $CFG; // Necessary in case it is referenced by included PHP scripts.
     869          $pluginfiles = array();
     870  
     871          if (isset(self::$filemap[$file])) {
     872              // If the file was supposed to be mapped, then it should have been set in the array.
     873              if (isset(self::$filemap[$file][$plugintype])) {
     874                  $pluginfiles = self::$filemap[$file][$plugintype];
     875              }
     876          } else {
     877              // Old-style search for non-cached files.
     878              $plugins = self::get_plugin_list($plugintype);
     879              foreach ($plugins as $plugin => $fulldir) {
     880                  $path = $fulldir . '/' . $file;
     881                  if (file_exists($path)) {
     882                      $pluginfiles[$plugin] = $path;
     883                  }
     884              }
     885          }
     886  
     887          if ($include) {
     888              foreach ($pluginfiles as $path) {
     889                  include_once($path);
     890              }
     891          }
     892  
     893          return $pluginfiles;
     894      }
     895  
     896      /**
     897       * Returns the exact absolute path to plugin directory.
     898       *
     899       * @param string $plugintype type of plugin
     900       * @param string $pluginname name of the plugin
     901       * @return string full path to plugin directory; null if not found
     902       */
     903      public static function get_plugin_directory($plugintype, $pluginname) {
     904          if (empty($pluginname)) {
     905              // Invalid plugin name, sorry.
     906              return null;
     907          }
     908  
     909          self::init();
     910  
     911          if (!isset(self::$plugins[$plugintype][$pluginname])) {
     912              return null;
     913          }
     914          return self::$plugins[$plugintype][$pluginname];
     915      }
     916  
     917      /**
     918       * Returns the exact absolute path to plugin directory.
     919       *
     920       * @param string $subsystem type of core subsystem
     921       * @return string full path to subsystem directory; null if not found
     922       */
     923      public static function get_subsystem_directory($subsystem) {
     924          self::init();
     925  
     926          if (!isset(self::$subsystems[$subsystem])) {
     927              return null;
     928          }
     929          return self::$subsystems[$subsystem];
     930      }
     931  
     932      /**
     933       * This method validates a plug name. It is much faster than calling clean_param.
     934       *
     935       * @param string $plugintype type of plugin
     936       * @param string $pluginname a string that might be a plugin name.
     937       * @return bool if this string is a valid plugin name.
     938       */
     939      public static function is_valid_plugin_name($plugintype, $pluginname) {
     940          if ($plugintype === 'mod') {
     941              // Modules must not have the same name as core subsystems.
     942              if (!isset(self::$subsystems)) {
     943                  // Watch out, this is called from init!
     944                  self::init();
     945              }
     946              if (isset(self::$subsystems[$pluginname])) {
     947                  return false;
     948              }
     949              // Modules MUST NOT have any underscores,
     950              // component normalisation would break very badly otherwise!
     951              return (bool)preg_match('/^[a-z][a-z0-9]*$/', $pluginname);
     952  
     953          } else {
     954              return (bool)preg_match('/^[a-z](?:[a-z0-9_](?!__))*[a-z0-9]+$/', $pluginname);
     955          }
     956      }
     957  
     958      /**
     959       * Normalize the component name.
     960       *
     961       * Note: this does not verify the validity of the plugin or component.
     962       *
     963       * @param string $component
     964       * @return string
     965       */
     966      public static function normalize_componentname($componentname) {
     967          list($plugintype, $pluginname) = self::normalize_component($componentname);
     968          if ($plugintype === 'core' && is_null($pluginname)) {
     969              return $plugintype;
     970          }
     971          return $plugintype . '_' . $pluginname;
     972      }
     973  
     974      /**
     975       * Normalize the component name using the "frankenstyle" rules.
     976       *
     977       * Note: this does not verify the validity of plugin or type names.
     978       *
     979       * @param string $component
     980       * @return array as (string)$type => (string)$plugin
     981       */
     982      public static function normalize_component($component) {
     983          if ($component === 'moodle' or $component === 'core' or $component === '') {
     984              return array('core', null);
     985          }
     986  
     987          if (strpos($component, '_') === false) {
     988              self::init();
     989              if (array_key_exists($component, self::$subsystems)) {
     990                  $type   = 'core';
     991                  $plugin = $component;
     992              } else {
     993                  // Everything else without underscore is a module.
     994                  $type   = 'mod';
     995                  $plugin = $component;
     996              }
     997  
     998          } else {
     999              list($type, $plugin) = explode('_', $component, 2);
    1000              if ($type === 'moodle') {
    1001                  $type = 'core';
    1002              }
    1003              // Any unknown type must be a subplugin.
    1004          }
    1005  
    1006          return array($type, $plugin);
    1007      }
    1008  
    1009      /**
    1010       * Return exact absolute path to a plugin directory.
    1011       *
    1012       * @param string $component name such as 'moodle', 'mod_forum'
    1013       * @return string full path to component directory; NULL if not found
    1014       */
    1015      public static function get_component_directory($component) {
    1016          global $CFG;
    1017  
    1018          list($type, $plugin) = self::normalize_component($component);
    1019  
    1020          if ($type === 'core') {
    1021              if ($plugin === null) {
    1022                  return $path = $CFG->libdir;
    1023              }
    1024              return self::get_subsystem_directory($plugin);
    1025          }
    1026  
    1027          return self::get_plugin_directory($type, $plugin);
    1028      }
    1029  
    1030      /**
    1031       * Returns list of plugin types that allow subplugins.
    1032       * @return array as (string)plugintype => (string)fulldir
    1033       */
    1034      public static function get_plugin_types_with_subplugins() {
    1035          self::init();
    1036  
    1037          $return = array();
    1038          foreach (self::$supportsubplugins as $type) {
    1039              $return[$type] = self::$plugintypes[$type];
    1040          }
    1041          return $return;
    1042      }
    1043  
    1044      /**
    1045       * Returns parent of this subplugin type.
    1046       *
    1047       * @param string $type
    1048       * @return string parent component or null
    1049       */
    1050      public static function get_subtype_parent($type) {
    1051          self::init();
    1052  
    1053          if (isset(self::$parents[$type])) {
    1054              return self::$parents[$type];
    1055          }
    1056  
    1057          return null;
    1058      }
    1059  
    1060      /**
    1061       * Return all subplugins of this component.
    1062       * @param string $component.
    1063       * @return array $subtype=>array($component, ..), null if no subtypes defined
    1064       */
    1065      public static function get_subplugins($component) {
    1066          self::init();
    1067  
    1068          if (isset(self::$subplugins[$component])) {
    1069              return self::$subplugins[$component];
    1070          }
    1071  
    1072          return null;
    1073      }
    1074  
    1075      /**
    1076       * Returns hash of all versions including core and all plugins.
    1077       *
    1078       * This is relatively slow and not fully cached, use with care!
    1079       *
    1080       * @return string sha1 hash
    1081       */
    1082      public static function get_all_versions_hash() {
    1083          global $CFG;
    1084  
    1085          self::init();
    1086  
    1087          $versions = array();
    1088  
    1089          // Main version first.
    1090          $versions['core'] = self::fetch_core_version();
    1091  
    1092          // The problem here is tha the component cache might be stable,
    1093          // we want this to work also on frontpage without resetting the component cache.
    1094          $usecache = false;
    1095          if (CACHE_DISABLE_ALL or (defined('IGNORE_COMPONENT_CACHE') and IGNORE_COMPONENT_CACHE)) {
    1096              $usecache = true;
    1097          }
    1098  
    1099          // Now all plugins.
    1100          $plugintypes = core_component::get_plugin_types();
    1101          foreach ($plugintypes as $type => $typedir) {
    1102              if ($usecache) {
    1103                  $plugs = core_component::get_plugin_list($type);
    1104              } else {
    1105                  $plugs = self::fetch_plugins($type, $typedir);
    1106              }
    1107              foreach ($plugs as $plug => $fullplug) {
    1108                  $plugin = new stdClass();
    1109                  $plugin->version = null;
    1110                  $module = $plugin;
    1111                  @include ($fullplug.'/version.php');
    1112                  $versions[$type.'_'.$plug] = $plugin->version;
    1113              }
    1114          }
    1115  
    1116          return sha1(serialize($versions));
    1117      }
    1118  
    1119      /**
    1120       * Invalidate opcode cache for given file, this is intended for
    1121       * php files that are stored in dataroot.
    1122       *
    1123       * Note: we need it here because this class must be self-contained.
    1124       *
    1125       * @param string $file
    1126       */
    1127      public static function invalidate_opcode_php_cache($file) {
    1128          if (function_exists('opcache_invalidate')) {
    1129              if (!file_exists($file)) {
    1130                  return;
    1131              }
    1132              opcache_invalidate($file, true);
    1133          }
    1134      }
    1135  
    1136      /**
    1137       * Return true if subsystemname is core subsystem.
    1138       *
    1139       * @param string $subsystemname name of the subsystem.
    1140       * @return bool true if core subsystem.
    1141       */
    1142      public static function is_core_subsystem($subsystemname) {
    1143          return isset(self::$subsystems[$subsystemname]);
    1144      }
    1145  
    1146      /**
    1147       * Records all class renames that have been made to facilitate autoloading.
    1148       */
    1149      protected static function fill_classmap_renames_cache() {
    1150          global $CFG;
    1151  
    1152          self::$classmaprenames = array();
    1153  
    1154          self::load_renamed_classes("$CFG->dirroot/lib/");
    1155  
    1156          foreach (self::$subsystems as $subsystem => $fulldir) {
    1157              self::load_renamed_classes($fulldir);
    1158          }
    1159  
    1160          foreach (self::$plugins as $plugintype => $plugins) {
    1161              foreach ($plugins as $pluginname => $fulldir) {
    1162                  self::load_renamed_classes($fulldir);
    1163              }
    1164          }
    1165      }
    1166  
    1167      /**
    1168       * Loads the db/renamedclasses.php file from the given directory.
    1169       *
    1170       * The renamedclasses.php should contain a key => value array ($renamedclasses) where the key is old class name,
    1171       * and the value is the new class name.
    1172       * It is only included when we are populating the component cache. After that is not needed.
    1173       *
    1174       * @param string $fulldir
    1175       */
    1176      protected static function load_renamed_classes($fulldir) {
    1177          $file = $fulldir . '/db/renamedclasses.php';
    1178          if (is_readable($file)) {
    1179              $renamedclasses = null;
    1180              require($file);
    1181              if (is_array($renamedclasses)) {
    1182                  foreach ($renamedclasses as $oldclass => $newclass) {
    1183                      self::$classmaprenames[(string)$oldclass] = (string)$newclass;
    1184                  }
    1185              }
    1186          }
    1187      }
    1188  }
    

    Search This Site: