| 1 | efrain | 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 |   | 
        
           | 1441 | ariadna | 25 | namespace core;
 | 
        
           |  |  | 26 |   | 
        
           |  |  | 27 | use core\exception\coding_exception;
 | 
        
           |  |  | 28 | use core\output\theme_config;
 | 
        
           |  |  | 29 | use stdClass;
 | 
        
           |  |  | 30 | use ArrayIterator;
 | 
        
           |  |  | 31 | use DirectoryIterator;
 | 
        
           |  |  | 32 | use Exception;
 | 
        
           |  |  | 33 | use RegexIterator;
 | 
        
           |  |  | 34 |   | 
        
           | 1 | efrain | 35 | // Constants used in version.php files, these must exist when core_component executes.
 | 
        
           |  |  | 36 |   | 
        
           |  |  | 37 | // We make use of error_log as debugging is not always available.
 | 
        
           |  |  | 38 | // phpcs:disable moodle.PHP.ForbiddenFunctions.FoundWithAlternative
 | 
        
           |  |  | 39 | // We make use of empty if statements to make complex decisions clearer.
 | 
        
           |  |  | 40 | // phpcs:disable Generic.CodeAnalysis.EmptyStatement.DetectedIf
 | 
        
           |  |  | 41 |   | 
        
           |  |  | 42 | /** Software maturity level - internals can be tested using white box techniques. */
 | 
        
           |  |  | 43 | define('MATURITY_ALPHA', 50);
 | 
        
           |  |  | 44 | /** Software maturity level - feature complete, ready for preview and testing. */
 | 
        
           |  |  | 45 | define('MATURITY_BETA', 100);
 | 
        
           |  |  | 46 | /** Software maturity level - tested, will be released unless there are fatal bugs. */
 | 
        
           |  |  | 47 | define('MATURITY_RC', 150);
 | 
        
           |  |  | 48 | /** Software maturity level - ready for production deployment. */
 | 
        
           |  |  | 49 | define('MATURITY_STABLE', 200);
 | 
        
           |  |  | 50 | /** Any version - special value that can be used in $plugin->dependencies in version.php files. */
 | 
        
           |  |  | 51 | define('ANY_VERSION', 'any');
 | 
        
           |  |  | 52 |   | 
        
           |  |  | 53 | /**
 | 
        
           |  |  | 54 |  * Collection of components related methods.
 | 
        
           |  |  | 55 |  */
 | 
        
           | 1441 | ariadna | 56 | class component {
 | 
        
           | 1 | efrain | 57 |     /** @var array list of ignored directories in plugin type roots - watch out for auth/db exception */
 | 
        
           |  |  | 58 |     protected static $ignoreddirs = [
 | 
        
           |  |  | 59 |         'CVS' => true,
 | 
        
           |  |  | 60 |         '_vti_cnf' => true,
 | 
        
           |  |  | 61 |         'amd' => true,
 | 
        
           |  |  | 62 |         'classes' => true,
 | 
        
           |  |  | 63 |         'db' => true,
 | 
        
           |  |  | 64 |         'fonts' => true,
 | 
        
           |  |  | 65 |         'lang' => true,
 | 
        
           |  |  | 66 |         'pix' => true,
 | 
        
           |  |  | 67 |         'simpletest' => true,
 | 
        
           |  |  | 68 |         'templates' => true,
 | 
        
           |  |  | 69 |         'tests' => true,
 | 
        
           |  |  | 70 |         'yui' => true,
 | 
        
           |  |  | 71 |     ];
 | 
        
           |  |  | 72 |     /** @var array list plugin types that support subplugins, do not add more here unless absolutely necessary */
 | 
        
           |  |  | 73 |     protected static $supportsubplugins = ['mod', 'editor', 'tool', 'local'];
 | 
        
           |  |  | 74 |   | 
        
           |  |  | 75 |     /** @var object JSON source of the component data */
 | 
        
           |  |  | 76 |     protected static $componentsource = null;
 | 
        
           |  |  | 77 |     /** @var array cache of plugin types */
 | 
        
           |  |  | 78 |     protected static $plugintypes = null;
 | 
        
           |  |  | 79 |     /** @var array cache of plugin locations */
 | 
        
           |  |  | 80 |     protected static $plugins = null;
 | 
        
           |  |  | 81 |     /** @var array cache of core subsystems */
 | 
        
           |  |  | 82 |     protected static $subsystems = null;
 | 
        
           |  |  | 83 |     /** @var array subplugin type parents */
 | 
        
           |  |  | 84 |     protected static $parents = null;
 | 
        
           |  |  | 85 |     /** @var array subplugins */
 | 
        
           |  |  | 86 |     protected static $subplugins = null;
 | 
        
           | 1441 | ariadna | 87 |     /** @var array deprecated plugins  */
 | 
        
           |  |  | 88 |     protected static $deprecatedplugins = null;
 | 
        
           |  |  | 89 |     /** @var array deleted plugins */
 | 
        
           |  |  | 90 |     protected static $deletedplugins = null;
 | 
        
           |  |  | 91 |     /** @var array deprecated plugin types */
 | 
        
           |  |  | 92 |     protected static $deprecatedplugintypes = null;
 | 
        
           |  |  | 93 |     /** @var array deleted plugin types */
 | 
        
           |  |  | 94 |     protected static $deletedplugintypes = null;
 | 
        
           |  |  | 95 |     /** @var array deprecated sub plugins */
 | 
        
           |  |  | 96 |     protected static $deprecatedsubplugins = null;
 | 
        
           |  |  | 97 |     /** @var array deleted sub plugins */
 | 
        
           |  |  | 98 |     protected static $deletedsubplugins = null;
 | 
        
           | 1 | efrain | 99 |     /** @var array cache of core APIs */
 | 
        
           |  |  | 100 |     protected static $apis = null;
 | 
        
           |  |  | 101 |     /** @var array list of all known classes that can be autoloaded */
 | 
        
           |  |  | 102 |     protected static $classmap = null;
 | 
        
           |  |  | 103 |     /** @var array list of all classes that have been renamed to be autoloaded */
 | 
        
           |  |  | 104 |     protected static $classmaprenames = null;
 | 
        
           |  |  | 105 |     /** @var array list of some known files that can be included. */
 | 
        
           |  |  | 106 |     protected static $filemap = null;
 | 
        
           |  |  | 107 |     /** @var int|float core version. */
 | 
        
           |  |  | 108 |     protected static $version = null;
 | 
        
           |  |  | 109 |     /** @var array list of the files to map. */
 | 
        
           |  |  | 110 |     protected static $filestomap = ['lib.php', 'settings.php'];
 | 
        
           |  |  | 111 |     /** @var array associative array of PSR-0 namespaces and corresponding paths. */
 | 
        
           |  |  | 112 |     protected static $psr0namespaces = [
 | 
        
           |  |  | 113 |         'Mustache' => 'lib/mustache/src/Mustache',
 | 
        
           |  |  | 114 |     ];
 | 
        
           |  |  | 115 |     /** @var array<string|array<string>> associative array of PRS-4 namespaces and corresponding paths. */
 | 
        
           |  |  | 116 |     protected static $psr4namespaces = [
 | 
        
           | 1441 | ariadna | 117 |         \Aws::class => 'lib/aws-sdk/src',
 | 
        
           |  |  | 118 |         \CFPropertyList::class => 'lib/plist/src/CFPropertyList',
 | 
        
           |  |  | 119 |         \Complex::class => 'lib/phpspreadsheet/markbaker/complex/classes/src',
 | 
        
           |  |  | 120 |         \Composer\Pcre::class => 'lib/composer/pcre/src',
 | 
        
           |  |  | 121 |         \DI::class => 'lib/php-di/php-di/src',
 | 
        
           |  |  | 122 |         \GeoIp2::class => 'lib/maxmind/GeoIp2/src',
 | 
        
           |  |  | 123 |         \FastRoute::class => 'lib/nikic/fast-route/src',
 | 
        
           |  |  | 124 |         \Firebase\JWT::class => 'lib/php-jwt/src',
 | 
        
           |  |  | 125 |         \GuzzleHttp::class => 'lib/guzzlehttp/guzzle/src',
 | 
        
           |  |  | 126 |         \GuzzleHttp\Promise::class => 'lib/guzzlehttp/promises/src',
 | 
        
           |  |  | 127 |         \GuzzleHttp\Psr7::class => 'lib/guzzlehttp/psr7/src',
 | 
        
           |  |  | 128 |         \Html2Text::class => 'lib/html2text/src',
 | 
        
           |  |  | 129 |         \IMSGlobal\LTI::class => 'lib/ltiprovider/src',
 | 
        
           |  |  | 130 |         \Invoker::class => 'lib/php-di/invoker/src',
 | 
        
           |  |  | 131 |         \JmesPath::class => 'lib/jmespath/src',
 | 
        
           |  |  | 132 |         \Kevinrob\GuzzleCache::class => 'lib/guzzlehttp/kevinrob/guzzlecache/src',
 | 
        
           |  |  | 133 |         \Laravel\SerializableClosure::class => 'lib/laravel/serializable-closure/src',
 | 
        
           |  |  | 134 |         \lbuchs\WebAuthn::class => 'lib/webauthn/src',
 | 
        
           |  |  | 135 |         \libphonenumber::class => 'lib/giggsey/libphonenumber-for-php-lite/src',
 | 
        
           |  |  | 136 |         \Matrix::class => 'lib/phpspreadsheet/markbaker/matrix/classes/src',
 | 
        
           |  |  | 137 |         \MatthiasMullie\Minify::class => 'lib/minify/matthiasmullie-minify/src',
 | 
        
           |  |  | 138 |         \MatthiasMullie\PathConverter::class => 'lib/minify/matthiasmullie-pathconverter/src',
 | 
        
           |  |  | 139 |         \MaxMind\Db::class => 'lib/maxmind/MaxMind/src/MaxMind/Db',
 | 
        
           |  |  | 140 |         \Michelf::class => 'lib/markdown/Michelf',
 | 
        
           |  |  | 141 |         \MoodleHQ::class => [
 | 
        
           |  |  | 142 |             'lib/rtlcss/src/MoodleHQ',
 | 
        
           |  |  | 143 |         ],
 | 
        
           |  |  | 144 |         \OpenSpout::class => 'lib/openspout/src',
 | 
        
           |  |  | 145 |         \Packback\Lti1p3::class => 'lib/lti1p3/src',
 | 
        
           |  |  | 146 |         \PHPMailer\PHPMailer::class => 'lib/phpmailer/src',
 | 
        
           |  |  | 147 |         \PhpOffice\PhpSpreadsheet::class => 'lib/phpspreadsheet/phpspreadsheet/src/PhpSpreadsheet',
 | 
        
           |  |  | 148 |         \PhpXmlRpc::class => 'lib/phpxmlrpc/src',
 | 
        
           |  |  | 149 |         \Phpml::class => 'lib/mlbackend/php/phpml/src/Phpml',
 | 
        
           |  |  | 150 |         \Psr\Clock::class => 'lib/psr/clock/src',
 | 
        
           |  |  | 151 |         \Psr\Container::class => 'lib/psr/container/src',
 | 
        
           |  |  | 152 |         \Psr\EventDispatcher::class => 'lib/psr/event-dispatcher/src',
 | 
        
           |  |  | 153 |         \Psr\Http\Client::class => 'lib/psr/http-client/src',
 | 
        
           |  |  | 154 |         \Psr\Http\Message::class => [
 | 
        
           |  |  | 155 |             'lib/psr/http-factory/src',
 | 
        
           | 1 | efrain | 156 |             'lib/psr/http-message/src',
 | 
        
           |  |  | 157 |         ],
 | 
        
           | 1441 | ariadna | 158 |         \Psr\Http\Server::class => [
 | 
        
           |  |  | 159 |             "lib/psr/http-server-handler/src",
 | 
        
           |  |  | 160 |             "lib/psr/http-server-middleware/src",
 | 
        
           |  |  | 161 |         ],
 | 
        
           |  |  | 162 |         \Psr\Log::class => "lib/psr/log/src",
 | 
        
           |  |  | 163 |         \Psr\SimpleCache::class => 'lib/psr/simple-cache/src',
 | 
        
           |  |  | 164 |         \RedeyeVentures::class => 'lib/geopattern-php/src',
 | 
        
           |  |  | 165 |         \Sabberworm\CSS::class => 'lib/php-css-parser/src',
 | 
        
           |  |  | 166 |         \ScssPhp\ScssPhp::class => 'lib/scssphp/src',
 | 
        
           |  |  | 167 |         \SimplePie::class => 'lib/simplepie/src',
 | 
        
           |  |  | 168 |         \Slim::class => 'lib/slim/slim/Slim',
 | 
        
           |  |  | 169 |         \Spatie\Cloneable::class => 'lib/spatie/php-cloneable/src',
 | 
        
           |  |  | 170 |         \ZipStream::class => 'lib/zipstream/src',
 | 
        
           | 1 | efrain | 171 |     ];
 | 
        
           |  |  | 172 |   | 
        
           |  |  | 173 |     /**
 | 
        
           | 1441 | ariadna | 174 |      *  An array containing files which are normally in a package's composer/autoload.files section.
 | 
        
           |  |  | 175 |      *
 | 
        
           |  |  | 176 |      * PHP does not provide a mechanism for automatically including the files that methods are in.
 | 
        
           |  |  | 177 |      *
 | 
        
           |  |  | 178 |      * The Composer autoloader includes all files in this section of the composer.json file during the instantiation of the loader.
 | 
        
           |  |  | 179 |      *
 | 
        
           |  |  | 180 |      * @var array<string>
 | 
        
           |  |  | 181 |      */
 | 
        
           |  |  | 182 |     protected static $composerautoloadfiles = [
 | 
        
           |  |  | 183 |         'lib/aws-sdk/src/functions.php',
 | 
        
           |  |  | 184 |         'lib/guzzlehttp/guzzle/src/functions_include.php',
 | 
        
           |  |  | 185 |         'lib/jmespath/src/JmesPath.php',
 | 
        
           |  |  | 186 |         'lib/nikic/fast-route/src/functions.php',
 | 
        
           |  |  | 187 |         'lib/php-di/php-di/src/functions.php',
 | 
        
           |  |  | 188 |         'lib/ralouphie/getallheaders/src/getallheaders.php',
 | 
        
           |  |  | 189 |         'lib/symfony/deprecation-contracts/function.php',
 | 
        
           |  |  | 190 |     ];
 | 
        
           |  |  | 191 |   | 
        
           |  |  | 192 |     /**
 | 
        
           |  |  | 193 |      * Register the Moodle class autoloader.
 | 
        
           |  |  | 194 |      */
 | 
        
           |  |  | 195 |     public static function register_autoloader(): void {
 | 
        
           |  |  | 196 |         if (defined('COMPONENT_CLASSLOADER')) {
 | 
        
           |  |  | 197 |             spl_autoload_register(COMPONENT_CLASSLOADER);
 | 
        
           |  |  | 198 |         } else {
 | 
        
           |  |  | 199 |             spl_autoload_register([self::class, 'classloader']);
 | 
        
           |  |  | 200 |         }
 | 
        
           |  |  | 201 |   | 
        
           |  |  | 202 |         // Load any composer-driven autoload files.
 | 
        
           |  |  | 203 |         // This is intended to mimic the behaviour of the standard Composer Autoloader.
 | 
        
           |  |  | 204 |         foreach (static::$composerautoloadfiles as $file) {
 | 
        
           |  |  | 205 |             $path = dirname(__DIR__, 2) . '/' . $file;
 | 
        
           |  |  | 206 |             if (file_exists($path)) {
 | 
        
           |  |  | 207 |                 require_once($path);
 | 
        
           |  |  | 208 |             }
 | 
        
           |  |  | 209 |         }
 | 
        
           |  |  | 210 |     }
 | 
        
           |  |  | 211 |   | 
        
           |  |  | 212 |     /**
 | 
        
           | 1 | efrain | 213 |      * Class loader for Frankenstyle named classes in standard locations.
 | 
        
           |  |  | 214 |      * Frankenstyle namespaces are supported.
 | 
        
           |  |  | 215 |      *
 | 
        
           |  |  | 216 |      * The expected location for core classes is:
 | 
        
           |  |  | 217 |      *    1/ core_xx_yy_zz ---> lib/classes/xx_yy_zz.php
 | 
        
           |  |  | 218 |      *    2/ \core\xx_yy_zz ---> lib/classes/xx_yy_zz.php
 | 
        
           |  |  | 219 |      *    3/ \core\xx\yy_zz ---> lib/classes/xx/yy_zz.php
 | 
        
           |  |  | 220 |      *
 | 
        
           |  |  | 221 |      * The expected location for plugin classes is:
 | 
        
           |  |  | 222 |      *    1/ mod_name_xx_yy_zz ---> mod/name/classes/xx_yy_zz.php
 | 
        
           |  |  | 223 |      *    2/ \mod_name\xx_yy_zz ---> mod/name/classes/xx_yy_zz.php
 | 
        
           |  |  | 224 |      *    3/ \mod_name\xx\yy_zz ---> mod/name/classes/xx/yy_zz.php
 | 
        
           |  |  | 225 |      *
 | 
        
           |  |  | 226 |      * @param string $classname
 | 
        
           |  |  | 227 |      */
 | 
        
           |  |  | 228 |     public static function classloader($classname) {
 | 
        
           |  |  | 229 |         self::init();
 | 
        
           |  |  | 230 |   | 
        
           |  |  | 231 |         if (isset(self::$classmap[$classname])) {
 | 
        
           |  |  | 232 |             // Global $CFG is expected in included scripts.
 | 
        
           |  |  | 233 |             global $CFG;
 | 
        
           |  |  | 234 |             // Function include would be faster, but for BC it is better to include only once.
 | 
        
           |  |  | 235 |             include_once(self::$classmap[$classname]);
 | 
        
           |  |  | 236 |             return;
 | 
        
           |  |  | 237 |         }
 | 
        
           |  |  | 238 |         if (isset(self::$classmaprenames[$classname]) && isset(self::$classmap[self::$classmaprenames[$classname]])) {
 | 
        
           |  |  | 239 |             $newclassname = self::$classmaprenames[$classname];
 | 
        
           |  |  | 240 |             $debugging = "Class '%s' has been renamed for the autoloader and is now deprecated. Please use '%s' instead.";
 | 
        
           |  |  | 241 |             debugging(sprintf($debugging, $classname, $newclassname), DEBUG_DEVELOPER);
 | 
        
           | 1441 | ariadna | 242 |             if (preg_match('#\\\null(\\\|$)#', $classname)) {
 | 
        
           |  |  | 243 |                 throw new coding_exception("Cannot alias $classname to $newclassname");
 | 
        
           | 1 | efrain | 244 |             }
 | 
        
           |  |  | 245 |             class_alias($newclassname, $classname);
 | 
        
           |  |  | 246 |             return;
 | 
        
           |  |  | 247 |         }
 | 
        
           |  |  | 248 |   | 
        
           |  |  | 249 |         $file = self::psr_classloader($classname);
 | 
        
           |  |  | 250 |         // If the file is found, require it.
 | 
        
           |  |  | 251 |         if (!empty($file)) {
 | 
        
           |  |  | 252 |             require($file);
 | 
        
           |  |  | 253 |             return;
 | 
        
           |  |  | 254 |         }
 | 
        
           | 11 | efrain | 255 |   | 
        
           |  |  | 256 |         if (defined('PHPUNIT_TEST') && PHPUNIT_TEST) {
 | 
        
           |  |  | 257 |             // For unit tests we support classes in `\frankenstyle_component\tests\` to be loaded from
 | 
        
           |  |  | 258 |             // `path/to/frankenstyle/component/tests/classes` directory.
 | 
        
           |  |  | 259 |             // Note: We do *not* support the legacy `\frankenstyle_component_tests_style_classnames`.
 | 
        
           |  |  | 260 |             if ($component = self::get_component_from_classname($classname)) {
 | 
        
           |  |  | 261 |                 $pathoptions = [
 | 
        
           |  |  | 262 |                     '/tests/classes' => "{$component}\\tests\\",
 | 
        
           |  |  | 263 |                     '/tests/behat' => "{$component}\\behat\\",
 | 
        
           |  |  | 264 |                 ];
 | 
        
           |  |  | 265 |                 foreach ($pathoptions as $path => $testnamespace) {
 | 
        
           |  |  | 266 |                     if (preg_match("#^" . preg_quote($testnamespace) . "#", $classname)) {
 | 
        
           |  |  | 267 |                         $path = self::get_component_directory($component) . $path;
 | 
        
           |  |  | 268 |                         $relativeclassname = str_replace(
 | 
        
           |  |  | 269 |                             $testnamespace,
 | 
        
           |  |  | 270 |                             '',
 | 
        
           |  |  | 271 |                             $classname,
 | 
        
           |  |  | 272 |                         );
 | 
        
           |  |  | 273 |                         $file = sprintf(
 | 
        
           |  |  | 274 |                             "%s/%s.php",
 | 
        
           |  |  | 275 |                             $path,
 | 
        
           |  |  | 276 |                             str_replace('\\', '/', $relativeclassname),
 | 
        
           |  |  | 277 |                         );
 | 
        
           |  |  | 278 |                         if (!empty($file) && file_exists($file)) {
 | 
        
           |  |  | 279 |                             require($file);
 | 
        
           |  |  | 280 |                             return;
 | 
        
           |  |  | 281 |                         }
 | 
        
           |  |  | 282 |                         break;
 | 
        
           |  |  | 283 |                     }
 | 
        
           |  |  | 284 |                 }
 | 
        
           |  |  | 285 |             }
 | 
        
           |  |  | 286 |         }
 | 
        
           | 1 | efrain | 287 |     }
 | 
        
           |  |  | 288 |   | 
        
           |  |  | 289 |     /**
 | 
        
           |  |  | 290 |      * Return the path to a class from our defined PSR-0 or PSR-4 standard namespaces on
 | 
        
           |  |  | 291 |      * demand. Only returns paths to files that exist.
 | 
        
           |  |  | 292 |      *
 | 
        
           |  |  | 293 |      * Adapated from http://www.php-fig.org/psr/psr-4/examples/ and made PSR-0
 | 
        
           |  |  | 294 |      * compatible.
 | 
        
           |  |  | 295 |      *
 | 
        
           |  |  | 296 |      * @param string $class the name of the class.
 | 
        
           |  |  | 297 |      * @return string|bool The full path to the file defining the class. Or false if it could not be resolved or does not exist.
 | 
        
           |  |  | 298 |      */
 | 
        
           |  |  | 299 |     protected static function psr_classloader($class) {
 | 
        
           |  |  | 300 |         // Iterate through each PSR-4 namespace prefix.
 | 
        
           |  |  | 301 |         foreach (self::$psr4namespaces as $prefix => $paths) {
 | 
        
           |  |  | 302 |             if (!is_array($paths)) {
 | 
        
           |  |  | 303 |                 $paths = [$paths];
 | 
        
           |  |  | 304 |             }
 | 
        
           |  |  | 305 |             foreach ($paths as $path) {
 | 
        
           |  |  | 306 |                 $file = self::get_class_file($class, $prefix, $path, ['\\']);
 | 
        
           |  |  | 307 |                 if (!empty($file) && file_exists($file)) {
 | 
        
           |  |  | 308 |                     return $file;
 | 
        
           |  |  | 309 |                 }
 | 
        
           |  |  | 310 |             }
 | 
        
           |  |  | 311 |         }
 | 
        
           |  |  | 312 |   | 
        
           |  |  | 313 |         // Iterate through each PSR-0 namespace prefix.
 | 
        
           |  |  | 314 |         foreach (self::$psr0namespaces as $prefix => $path) {
 | 
        
           |  |  | 315 |             $file = self::get_class_file($class, $prefix, $path, ['\\', '_']);
 | 
        
           |  |  | 316 |             if (!empty($file) && file_exists($file)) {
 | 
        
           |  |  | 317 |                 return $file;
 | 
        
           |  |  | 318 |             }
 | 
        
           |  |  | 319 |         }
 | 
        
           |  |  | 320 |   | 
        
           |  |  | 321 |         return false;
 | 
        
           |  |  | 322 |     }
 | 
        
           |  |  | 323 |   | 
        
           |  |  | 324 |     /**
 | 
        
           |  |  | 325 |      * Return the path to the class based on the given namespace prefix and path it corresponds to.
 | 
        
           |  |  | 326 |      *
 | 
        
           |  |  | 327 |      * Will return the path even if the file does not exist. Check the file esists before requiring.
 | 
        
           |  |  | 328 |      *
 | 
        
           |  |  | 329 |      * @param string $class the name of the class.
 | 
        
           |  |  | 330 |      * @param string $prefix The namespace prefix used to identify the base directory of the source files.
 | 
        
           |  |  | 331 |      * @param string $path The relative path to the base directory of the source files.
 | 
        
           |  |  | 332 |      * @param string[] $separators The characters that should be used for separating.
 | 
        
           |  |  | 333 |      * @return string|bool The full path to the file defining the class. Or false if it could not be resolved.
 | 
        
           |  |  | 334 |      */
 | 
        
           |  |  | 335 |     protected static function get_class_file($class, $prefix, $path, $separators) {
 | 
        
           |  |  | 336 |         global $CFG;
 | 
        
           |  |  | 337 |   | 
        
           |  |  | 338 |         // Does the class use the namespace prefix?
 | 
        
           |  |  | 339 |         $len = strlen($prefix);
 | 
        
           |  |  | 340 |         if (strncmp($prefix, $class, $len) !== 0) {
 | 
        
           |  |  | 341 |             // No, move to the next prefix.
 | 
        
           |  |  | 342 |             return false;
 | 
        
           |  |  | 343 |         }
 | 
        
           |  |  | 344 |         $path = $CFG->dirroot . '/' . $path;
 | 
        
           |  |  | 345 |   | 
        
           |  |  | 346 |         // Get the relative class name.
 | 
        
           |  |  | 347 |         $relativeclass = substr($class, $len);
 | 
        
           |  |  | 348 |   | 
        
           |  |  | 349 |         // Replace the namespace prefix with the base directory, replace namespace
 | 
        
           |  |  | 350 |         // separators with directory separators in the relative class name, append
 | 
        
           |  |  | 351 |         // with .php.
 | 
        
           |  |  | 352 |         $file = $path . str_replace($separators, '/', $relativeclass) . '.php';
 | 
        
           |  |  | 353 |   | 
        
           |  |  | 354 |         return $file;
 | 
        
           |  |  | 355 |     }
 | 
        
           |  |  | 356 |   | 
        
           |  |  | 357 |     /**
 | 
        
           |  |  | 358 |      * Initialise caches, always call before accessing self:: caches.
 | 
        
           |  |  | 359 |      */
 | 
        
           |  |  | 360 |     protected static function init() {
 | 
        
           |  |  | 361 |         global $CFG;
 | 
        
           |  |  | 362 |   | 
        
           |  |  | 363 |         // Init only once per request/CLI execution, we ignore changes done afterwards.
 | 
        
           |  |  | 364 |         if (isset(self::$plugintypes)) {
 | 
        
           |  |  | 365 |             return;
 | 
        
           |  |  | 366 |         }
 | 
        
           |  |  | 367 |   | 
        
           |  |  | 368 |         if (defined('IGNORE_COMPONENT_CACHE') && IGNORE_COMPONENT_CACHE) {
 | 
        
           |  |  | 369 |             self::fill_all_caches();
 | 
        
           |  |  | 370 |             return;
 | 
        
           |  |  | 371 |         }
 | 
        
           |  |  | 372 |   | 
        
           |  |  | 373 |         if (!empty($CFG->alternative_component_cache)) {
 | 
        
           |  |  | 374 |             // Hack for heavily clustered sites that want to manage component cache invalidation manually.
 | 
        
           |  |  | 375 |             $cachefile = $CFG->alternative_component_cache;
 | 
        
           |  |  | 376 |   | 
        
           |  |  | 377 |             if (file_exists($cachefile)) {
 | 
        
           |  |  | 378 |                 if (CACHE_DISABLE_ALL) {
 | 
        
           |  |  | 379 |                     // Verify the cache state only on upgrade pages.
 | 
        
           |  |  | 380 |                     $content = self::get_cache_content();
 | 
        
           |  |  | 381 |                     if (sha1_file($cachefile) !== sha1($content)) {
 | 
        
           |  |  | 382 |                         die('Outdated component cache file defined in $CFG->alternative_component_cache, can not continue');
 | 
        
           |  |  | 383 |                     }
 | 
        
           |  |  | 384 |                     return;
 | 
        
           |  |  | 385 |                 }
 | 
        
           |  |  | 386 |                 $cache = [];
 | 
        
           |  |  | 387 |                 include($cachefile);
 | 
        
           | 1441 | ariadna | 388 |                 self::$plugintypes              = $cache['plugintypes'];
 | 
        
           |  |  | 389 |                 self::$deprecatedplugintypes    = $cache['deprecatedplugintypes'];
 | 
        
           |  |  | 390 |                 self::$deletedplugintypes       = $cache['deletedplugintypes'];
 | 
        
           |  |  | 391 |                 self::$plugins                  = $cache['plugins'];
 | 
        
           |  |  | 392 |                 self::$deprecatedplugins        = $cache['deprecatedplugins'];
 | 
        
           |  |  | 393 |                 self::$deletedplugins           = $cache['deletedplugins'];
 | 
        
           |  |  | 394 |                 self::$subsystems               = $cache['subsystems'];
 | 
        
           |  |  | 395 |                 self::$parents                  = $cache['parents'];
 | 
        
           |  |  | 396 |                 self::$subplugins               = $cache['subplugins'];
 | 
        
           |  |  | 397 |                 self::$deprecatedsubplugins     = $cache['deprecatedsubplugins'];
 | 
        
           |  |  | 398 |                 self::$deletedsubplugins        = $cache['deletedsubplugins'];
 | 
        
           |  |  | 399 |                 self::$apis                     = $cache['apis'];
 | 
        
           |  |  | 400 |                 self::$classmap                 = $cache['classmap'];
 | 
        
           |  |  | 401 |                 self::$classmaprenames          = $cache['classmaprenames'];
 | 
        
           |  |  | 402 |                 self::$filemap                  = $cache['filemap'];
 | 
        
           | 1 | efrain | 403 |                 return;
 | 
        
           |  |  | 404 |             }
 | 
        
           |  |  | 405 |   | 
        
           |  |  | 406 |             if (!is_writable(dirname($cachefile))) {
 | 
        
           |  |  | 407 |                 die(
 | 
        
           |  |  | 408 |                     'Can not create alternative component cache file defined in ' .
 | 
        
           |  |  | 409 |                     '$CFG->alternative_component_cache, can not continue'
 | 
        
           |  |  | 410 |                 );
 | 
        
           |  |  | 411 |             }
 | 
        
           |  |  | 412 |   | 
        
           |  |  | 413 |             // Lets try to create the file, it might be in some writable directory or a local cache dir.
 | 
        
           |  |  | 414 |         } else {
 | 
        
           |  |  | 415 |             // Note: $CFG->cachedir MUST be shared by all servers in a cluster,
 | 
        
           |  |  | 416 |             // use $CFG->alternative_component_cache if you do not like it.
 | 
        
           |  |  | 417 |             $cachefile = "$CFG->cachedir/core_component.php";
 | 
        
           |  |  | 418 |         }
 | 
        
           |  |  | 419 |   | 
        
           |  |  | 420 |         if (!CACHE_DISABLE_ALL && !self::is_developer()) {
 | 
        
           |  |  | 421 |             // 1/ Use the cache only outside of install and upgrade.
 | 
        
           |  |  | 422 |             // 2/ Let developers add/remove classes in developer mode.
 | 
        
           |  |  | 423 |             if (is_readable($cachefile)) {
 | 
        
           |  |  | 424 |                 $cache = false;
 | 
        
           |  |  | 425 |                 include($cachefile);
 | 
        
           | 1441 | ariadna | 426 |                 if (is_array($cache) && self::is_cache_valid($cache)) {
 | 
        
           | 1 | efrain | 427 |                     // The cache looks ok, let's use it.
 | 
        
           | 1441 | ariadna | 428 |                     self::$plugintypes              = $cache['plugintypes'];
 | 
        
           |  |  | 429 |                     self::$deprecatedplugintypes    = $cache['deprecatedplugintypes'];
 | 
        
           |  |  | 430 |                     self::$deletedplugintypes       = $cache['deletedplugintypes'];
 | 
        
           |  |  | 431 |                     self::$plugins                  = $cache['plugins'];
 | 
        
           |  |  | 432 |                     self::$deprecatedplugins        = $cache['deprecatedplugins'];
 | 
        
           |  |  | 433 |                     self::$deletedplugins           = $cache['deletedplugins'];
 | 
        
           |  |  | 434 |                     self::$subsystems               = $cache['subsystems'];
 | 
        
           |  |  | 435 |                     self::$parents                  = $cache['parents'];
 | 
        
           |  |  | 436 |                     self::$subplugins               = $cache['subplugins'];
 | 
        
           |  |  | 437 |                     self::$deprecatedsubplugins     = $cache['deprecatedsubplugins'];
 | 
        
           |  |  | 438 |                     self::$deletedsubplugins        = $cache['deletedsubplugins'];
 | 
        
           |  |  | 439 |                     self::$apis                     = $cache['apis'];
 | 
        
           |  |  | 440 |                     self::$classmap                 = $cache['classmap'];
 | 
        
           |  |  | 441 |                     self::$classmaprenames          = $cache['classmaprenames'];
 | 
        
           |  |  | 442 |                     self::$filemap                  = $cache['filemap'];
 | 
        
           | 1 | efrain | 443 |                     return;
 | 
        
           |  |  | 444 |                 }
 | 
        
           |  |  | 445 |                 // Note: we do not verify $CFG->admin here intentionally,
 | 
        
           |  |  | 446 |                 // they must visit admin/index.php after any change.
 | 
        
           |  |  | 447 |             }
 | 
        
           |  |  | 448 |         }
 | 
        
           |  |  | 449 |   | 
        
           |  |  | 450 |         if (!isset(self::$plugintypes)) {
 | 
        
           |  |  | 451 |             // This needs to be atomic and self-fixing as much as possible.
 | 
        
           |  |  | 452 |   | 
        
           |  |  | 453 |             $content = self::get_cache_content();
 | 
        
           |  |  | 454 |             if (file_exists($cachefile)) {
 | 
        
           |  |  | 455 |                 if (sha1_file($cachefile) === sha1($content)) {
 | 
        
           |  |  | 456 |                     return;
 | 
        
           |  |  | 457 |                 }
 | 
        
           |  |  | 458 |                 // Stale cache detected!
 | 
        
           |  |  | 459 |                 unlink($cachefile);
 | 
        
           |  |  | 460 |             }
 | 
        
           |  |  | 461 |   | 
        
           |  |  | 462 |             // Permissions might not be setup properly in installers.
 | 
        
           |  |  | 463 |             $dirpermissions = !isset($CFG->directorypermissions) ? 02777 : $CFG->directorypermissions;
 | 
        
           |  |  | 464 |             $filepermissions = !isset($CFG->filepermissions) ? ($dirpermissions & 0666) : $CFG->filepermissions;
 | 
        
           |  |  | 465 |   | 
        
           |  |  | 466 |             clearstatcache();
 | 
        
           |  |  | 467 |             $cachedir = dirname($cachefile);
 | 
        
           |  |  | 468 |             if (!is_dir($cachedir)) {
 | 
        
           |  |  | 469 |                 mkdir($cachedir, $dirpermissions, true);
 | 
        
           |  |  | 470 |             }
 | 
        
           |  |  | 471 |   | 
        
           |  |  | 472 |             if ($fp = @fopen($cachefile . '.tmp', 'xb')) {
 | 
        
           |  |  | 473 |                 fwrite($fp, $content);
 | 
        
           |  |  | 474 |                 fclose($fp);
 | 
        
           |  |  | 475 |                 @rename($cachefile . '.tmp', $cachefile);
 | 
        
           |  |  | 476 |                 @chmod($cachefile, $filepermissions);
 | 
        
           |  |  | 477 |             }
 | 
        
           |  |  | 478 |             @unlink($cachefile . '.tmp'); // Just in case anything fails (race condition).
 | 
        
           |  |  | 479 |             self::invalidate_opcode_php_cache($cachefile);
 | 
        
           |  |  | 480 |         }
 | 
        
           |  |  | 481 |     }
 | 
        
           |  |  | 482 |   | 
        
           |  |  | 483 |     /**
 | 
        
           | 11 | efrain | 484 |      * Reset the initialisation of the component utility.
 | 
        
           |  |  | 485 |      *
 | 
        
           |  |  | 486 |      * Note: It should not be necessary to call this in regular code.
 | 
        
           |  |  | 487 |      * Please only use it where strictly required.
 | 
        
           |  |  | 488 |      */
 | 
        
           | 1441 | ariadna | 489 |     public static function reset(
 | 
        
           |  |  | 490 |         bool $fullreset = false,
 | 
        
           |  |  | 491 |     ): void {
 | 
        
           | 11 | efrain | 492 |         // The autoloader will re-initialise if plugintypes is null.
 | 
        
           |  |  | 493 |         self::$plugintypes = null;
 | 
        
           | 1441 | ariadna | 494 |   | 
        
           |  |  | 495 |         // Reset all caches to ensure they are reloaded.
 | 
        
           |  |  | 496 |         self::$plugins = null;
 | 
        
           |  |  | 497 |         self::$subsystems = null;
 | 
        
           |  |  | 498 |         self::$parents = null;
 | 
        
           |  |  | 499 |         self::$subplugins = null;
 | 
        
           |  |  | 500 |         self::$deprecatedplugins = null;
 | 
        
           |  |  | 501 |         self::$deletedplugins = null;
 | 
        
           |  |  | 502 |         self::$deprecatedplugintypes = null;
 | 
        
           |  |  | 503 |         self::$deletedplugintypes = null;
 | 
        
           |  |  | 504 |         self::$deprecatedsubplugins = null;
 | 
        
           |  |  | 505 |         self::$deletedsubplugins = null;
 | 
        
           |  |  | 506 |         self::$apis = null;
 | 
        
           |  |  | 507 |         self::$classmap = null;
 | 
        
           |  |  | 508 |         self::$classmaprenames = null;
 | 
        
           |  |  | 509 |         self::$filemap = null;
 | 
        
           |  |  | 510 |   | 
        
           |  |  | 511 |         if ($fullreset) {
 | 
        
           |  |  | 512 |             self::$componentsource = null;
 | 
        
           |  |  | 513 |             self::$version = null;
 | 
        
           |  |  | 514 |             self::$supportsubplugins = ['mod', 'editor', 'tool', 'local'];
 | 
        
           |  |  | 515 |         }
 | 
        
           | 11 | efrain | 516 |     }
 | 
        
           |  |  | 517 |   | 
        
           |  |  | 518 |     /**
 | 
        
           | 1441 | ariadna | 519 |      * Check whether the cache content in the supplied cache is valid.
 | 
        
           |  |  | 520 |      *
 | 
        
           |  |  | 521 |      * @param array $cache The content being loaded
 | 
        
           |  |  | 522 |      * @return bool Whether it is valid
 | 
        
           |  |  | 523 |      */
 | 
        
           |  |  | 524 |     protected static function is_cache_valid(array $cache): bool {
 | 
        
           |  |  | 525 |         global $CFG;
 | 
        
           |  |  | 526 |   | 
        
           |  |  | 527 |         if (!isset($cache['version'])) {
 | 
        
           |  |  | 528 |             // Something is very wrong.
 | 
        
           |  |  | 529 |             return false;
 | 
        
           |  |  | 530 |         }
 | 
        
           |  |  | 531 |   | 
        
           |  |  | 532 |         if ((float) $cache['version'] !== (float) self::fetch_core_version()) {
 | 
        
           |  |  | 533 |             // Outdated cache. We trigger an error log to track an eventual repetitive failure of float comparison.
 | 
        
           |  |  | 534 |             error_log('Resetting core_component cache after core upgrade to version ' . self::fetch_core_version());
 | 
        
           |  |  | 535 |             return false;
 | 
        
           |  |  | 536 |         }
 | 
        
           |  |  | 537 |   | 
        
           |  |  | 538 |         if ($cache['plugintypes']['mod'] !== "$CFG->dirroot/mod") {
 | 
        
           |  |  | 539 |             // phpcs:ignore moodle.Commenting.InlineComment.NotCapital
 | 
        
           |  |  | 540 |             // $CFG->dirroot was changed.
 | 
        
           |  |  | 541 |             return false;
 | 
        
           |  |  | 542 |         }
 | 
        
           |  |  | 543 |   | 
        
           |  |  | 544 |         // Check for key classes which block access to the upgrade in some way.
 | 
        
           |  |  | 545 |         // Note: This list should be kept _extremely_ minimal and generally
 | 
        
           |  |  | 546 |         // when adding a newly discovered classes older ones should be removed.
 | 
        
           |  |  | 547 |         // Always keep moodle_exception in place.
 | 
        
           |  |  | 548 |         $keyclasses = [
 | 
        
           |  |  | 549 |             \core\exception\moodle_exception::class,
 | 
        
           |  |  | 550 |             \core\output\bootstrap_renderer::class,
 | 
        
           |  |  | 551 |             \core_cache\cache::class,
 | 
        
           |  |  | 552 |         ];
 | 
        
           |  |  | 553 |         foreach ($keyclasses as $classname) {
 | 
        
           |  |  | 554 |             if (!array_key_exists($classname, $cache['classmap'])) {
 | 
        
           |  |  | 555 |                 // The cache is missing some key classes. This is likely before the upgrade has run.
 | 
        
           |  |  | 556 |                 error_log(
 | 
        
           |  |  | 557 |                     "The '{$classname}' class was not found in the component class cache. Resetting the classmap.",
 | 
        
           |  |  | 558 |                 );
 | 
        
           |  |  | 559 |                 return false;
 | 
        
           |  |  | 560 |             }
 | 
        
           |  |  | 561 |         }
 | 
        
           |  |  | 562 |   | 
        
           |  |  | 563 |         return true;
 | 
        
           |  |  | 564 |     }
 | 
        
           |  |  | 565 |   | 
        
           |  |  | 566 |     /**
 | 
        
           | 1 | efrain | 567 |      * Are we in developer debug mode?
 | 
        
           |  |  | 568 |      *
 | 
        
           | 1441 | ariadna | 569 |      * Note: You need to set "$CFG->debug = (E_ALL);" in config.php,
 | 
        
           | 1 | efrain | 570 |      *       the reason is we need to use this before we setup DB connection or caches for CFG.
 | 
        
           |  |  | 571 |      *
 | 
        
           |  |  | 572 |      * @return bool
 | 
        
           |  |  | 573 |      */
 | 
        
           |  |  | 574 |     protected static function is_developer() {
 | 
        
           |  |  | 575 |         global $CFG;
 | 
        
           |  |  | 576 |   | 
        
           |  |  | 577 |         // Note we can not rely on $CFG->debug here because DB is not initialised yet.
 | 
        
           |  |  | 578 |         if (isset($CFG->config_php_settings['debug'])) {
 | 
        
           |  |  | 579 |             $debug = (int)$CFG->config_php_settings['debug'];
 | 
        
           |  |  | 580 |         } else {
 | 
        
           |  |  | 581 |             return false;
 | 
        
           |  |  | 582 |         }
 | 
        
           |  |  | 583 |   | 
        
           | 1441 | ariadna | 584 |         if ($debug & E_ALL) {
 | 
        
           | 1 | efrain | 585 |             return true;
 | 
        
           |  |  | 586 |         }
 | 
        
           |  |  | 587 |   | 
        
           |  |  | 588 |         return false;
 | 
        
           |  |  | 589 |     }
 | 
        
           |  |  | 590 |   | 
        
           |  |  | 591 |     /**
 | 
        
           |  |  | 592 |      * Create cache file content.
 | 
        
           |  |  | 593 |      *
 | 
        
           |  |  | 594 |      * @private this is intended for $CFG->alternative_component_cache only.
 | 
        
           |  |  | 595 |      *
 | 
        
           |  |  | 596 |      * @return string
 | 
        
           |  |  | 597 |      */
 | 
        
           |  |  | 598 |     public static function get_cache_content() {
 | 
        
           |  |  | 599 |         if (!isset(self::$plugintypes)) {
 | 
        
           |  |  | 600 |             self::fill_all_caches();
 | 
        
           |  |  | 601 |         }
 | 
        
           |  |  | 602 |   | 
        
           |  |  | 603 |         $cache = [
 | 
        
           | 1441 | ariadna | 604 |             'subsystems'            => self::$subsystems,
 | 
        
           |  |  | 605 |             'plugintypes'           => self::$plugintypes,
 | 
        
           |  |  | 606 |             'deprecatedplugintypes' => self::$deprecatedplugintypes,
 | 
        
           |  |  | 607 |             'deletedplugintypes'    => self::$deletedplugintypes,
 | 
        
           |  |  | 608 |             'plugins'               => self::$plugins,
 | 
        
           |  |  | 609 |             'deprecatedplugins'     => self::$deprecatedplugins,
 | 
        
           |  |  | 610 |             'deletedplugins'        => self::$deletedplugins,
 | 
        
           |  |  | 611 |             'subplugins'            => self::$subplugins,
 | 
        
           |  |  | 612 |             'deprecatedsubplugins'  => self::$deprecatedsubplugins,
 | 
        
           |  |  | 613 |             'deletedsubplugins'     => self::$deletedsubplugins,
 | 
        
           |  |  | 614 |             'parents'               => self::$parents,
 | 
        
           |  |  | 615 |             'apis'                  => self::$apis,
 | 
        
           |  |  | 616 |             'classmap'              => self::$classmap,
 | 
        
           |  |  | 617 |             'classmaprenames'       => self::$classmaprenames,
 | 
        
           |  |  | 618 |             'filemap'               => self::$filemap,
 | 
        
           |  |  | 619 |             'version'               => self::$version,
 | 
        
           | 1 | efrain | 620 |         ];
 | 
        
           |  |  | 621 |   | 
        
           |  |  | 622 |         return '<?php
 | 
        
           |  |  | 623 | $cache = ' . var_export($cache, true) . ';
 | 
        
           |  |  | 624 | ';
 | 
        
           |  |  | 625 |     }
 | 
        
           |  |  | 626 |   | 
        
           |  |  | 627 |     /**
 | 
        
           |  |  | 628 |      * Fill all caches.
 | 
        
           |  |  | 629 |      */
 | 
        
           |  |  | 630 |     protected static function fill_all_caches() {
 | 
        
           |  |  | 631 |         self::$subsystems = self::fetch_subsystems();
 | 
        
           |  |  | 632 |   | 
        
           | 1441 | ariadna | 633 |         [
 | 
        
           |  |  | 634 |             'plugintypes' => self::$plugintypes,
 | 
        
           |  |  | 635 |             'parents' => self::$parents,
 | 
        
           |  |  | 636 |             'subplugins' => self::$subplugins,
 | 
        
           |  |  | 637 |             'deprecatedplugintypes' => self::$deprecatedplugintypes,
 | 
        
           |  |  | 638 |             'deletedplugintypes' => self::$deletedplugintypes,
 | 
        
           |  |  | 639 |             'deprecatedsubplugins' => self::$deprecatedsubplugins,
 | 
        
           |  |  | 640 |             'deletedsubplugin' => self::$deletedsubplugins
 | 
        
           |  |  | 641 |         ] = self::fetch_plugintypes();
 | 
        
           | 1 | efrain | 642 |   | 
        
           |  |  | 643 |         self::$plugins = [];
 | 
        
           |  |  | 644 |         foreach (self::$plugintypes as $type => $fulldir) {
 | 
        
           |  |  | 645 |             self::$plugins[$type] = self::fetch_plugins($type, $fulldir);
 | 
        
           |  |  | 646 |         }
 | 
        
           |  |  | 647 |   | 
        
           | 1441 | ariadna | 648 |         self::$deprecatedplugins = [];
 | 
        
           |  |  | 649 |         foreach (self::$deprecatedplugintypes as $type => $fulldir) {
 | 
        
           |  |  | 650 |             self::$deprecatedplugins[$type] = self::fetch_plugins($type, $fulldir);
 | 
        
           |  |  | 651 |         }
 | 
        
           |  |  | 652 |   | 
        
           |  |  | 653 |         self::$deletedplugins = [];
 | 
        
           |  |  | 654 |         foreach (self::$deletedplugintypes as $type => $fulldir) {
 | 
        
           |  |  | 655 |             self::$deletedplugins[$type] = self::fetch_plugins($type, $fulldir);
 | 
        
           |  |  | 656 |         }
 | 
        
           |  |  | 657 |   | 
        
           | 1 | efrain | 658 |         self::$apis = self::fetch_apis();
 | 
        
           |  |  | 659 |   | 
        
           |  |  | 660 |         self::fill_classmap_cache();
 | 
        
           |  |  | 661 |         self::fill_classmap_renames_cache();
 | 
        
           |  |  | 662 |         self::fill_filemap_cache();
 | 
        
           |  |  | 663 |         self::fetch_core_version();
 | 
        
           |  |  | 664 |     }
 | 
        
           |  |  | 665 |   | 
        
           |  |  | 666 |     /**
 | 
        
           |  |  | 667 |      * Get the core version.
 | 
        
           |  |  | 668 |      *
 | 
        
           |  |  | 669 |      * In order for this to work properly, opcache should be reset beforehand.
 | 
        
           |  |  | 670 |      *
 | 
        
           |  |  | 671 |      * @return float core version.
 | 
        
           |  |  | 672 |      */
 | 
        
           |  |  | 673 |     protected static function fetch_core_version() {
 | 
        
           |  |  | 674 |         global $CFG;
 | 
        
           |  |  | 675 |         if (self::$version === null) {
 | 
        
           |  |  | 676 |             $version = null; // Prevent IDE complaints.
 | 
        
           |  |  | 677 |             require($CFG->dirroot . '/version.php');
 | 
        
           |  |  | 678 |             self::$version = $version;
 | 
        
           |  |  | 679 |         }
 | 
        
           |  |  | 680 |         return self::$version;
 | 
        
           |  |  | 681 |     }
 | 
        
           |  |  | 682 |   | 
        
           |  |  | 683 |     /**
 | 
        
           |  |  | 684 |      * Returns list of core subsystems.
 | 
        
           |  |  | 685 |      * @return array
 | 
        
           |  |  | 686 |      */
 | 
        
           |  |  | 687 |     protected static function fetch_subsystems() {
 | 
        
           |  |  | 688 |         global $CFG;
 | 
        
           |  |  | 689 |   | 
        
           |  |  | 690 |         // NOTE: Any additions here must be verified to not collide with existing add-on modules and subplugins!!!
 | 
        
           |  |  | 691 |         $info = [];
 | 
        
           |  |  | 692 |         foreach (self::fetch_component_source('subsystems') as $subsystem => $path) {
 | 
        
           |  |  | 693 |             // Replace admin/ directory with the config setting.
 | 
        
           |  |  | 694 |             if ($CFG->admin !== 'admin') {
 | 
        
           |  |  | 695 |                 if ($path === 'admin') {
 | 
        
           |  |  | 696 |                     $path = $CFG->admin;
 | 
        
           |  |  | 697 |                 }
 | 
        
           |  |  | 698 |                 if (strpos($path, 'admin/') === 0) {
 | 
        
           |  |  | 699 |                     $path = $CFG->admin . substr($path, 5);
 | 
        
           |  |  | 700 |                 }
 | 
        
           |  |  | 701 |             }
 | 
        
           |  |  | 702 |   | 
        
           |  |  | 703 |             $info[$subsystem] = empty($path) ? null : "{$CFG->dirroot}/{$path}";
 | 
        
           |  |  | 704 |         }
 | 
        
           |  |  | 705 |   | 
        
           |  |  | 706 |         return $info;
 | 
        
           |  |  | 707 |     }
 | 
        
           |  |  | 708 |   | 
        
           |  |  | 709 |     /**
 | 
        
           |  |  | 710 |      * Returns list of core APIs.
 | 
        
           |  |  | 711 |      * @return stdClass[]
 | 
        
           |  |  | 712 |      */
 | 
        
           |  |  | 713 |     protected static function fetch_apis() {
 | 
        
           |  |  | 714 |         return (array) json_decode(file_get_contents(__DIR__ . '/../apis.json'));
 | 
        
           |  |  | 715 |     }
 | 
        
           |  |  | 716 |   | 
        
           |  |  | 717 |     /**
 | 
        
           |  |  | 718 |      * Returns list of known plugin types.
 | 
        
           |  |  | 719 |      * @return array
 | 
        
           |  |  | 720 |      */
 | 
        
           |  |  | 721 |     protected static function fetch_plugintypes() {
 | 
        
           |  |  | 722 |         global $CFG;
 | 
        
           |  |  | 723 |   | 
        
           | 1441 | ariadna | 724 |         // Top level plugin types.
 | 
        
           |  |  | 725 |         $plugintypesmap = [
 | 
        
           |  |  | 726 |             'plugintypes' => [],
 | 
        
           |  |  | 727 |             'deprecatedplugintypes' => [],
 | 
        
           |  |  | 728 |             'deletedplugintypes' => [],
 | 
        
           |  |  | 729 |         ];
 | 
        
           |  |  | 730 |   | 
        
           |  |  | 731 |         $subplugintypesmap = [
 | 
        
           |  |  | 732 |             'plugintypes' => [],
 | 
        
           |  |  | 733 |             'deprecatedplugintypes' => [],
 | 
        
           |  |  | 734 |             'deletedplugintypes' => [],
 | 
        
           |  |  | 735 |         ];
 | 
        
           |  |  | 736 |   | 
        
           |  |  | 737 |         $parents = [];
 | 
        
           |  |  | 738 |   | 
        
           |  |  | 739 |         foreach ($plugintypesmap as $sourcekey => $typesarr) {
 | 
        
           |  |  | 740 |             /** @var string $plugintype */
 | 
        
           |  |  | 741 |             foreach (self::fetch_component_source($sourcekey) as $plugintype => $path) {
 | 
        
           |  |  | 742 |                 // Replace admin/ with the config setting.
 | 
        
           |  |  | 743 |                 if ($CFG->admin !== 'admin' && strpos($path, 'admin/') === 0) {
 | 
        
           |  |  | 744 |                     $path = $CFG->admin . substr($path, 5);
 | 
        
           |  |  | 745 |                 }
 | 
        
           |  |  | 746 |                 $plugintypesmap[$sourcekey][$plugintype] = "{$CFG->dirroot}/{$path}";
 | 
        
           | 1 | efrain | 747 |             }
 | 
        
           |  |  | 748 |         }
 | 
        
           |  |  | 749 |   | 
        
           | 1441 | ariadna | 750 |         // Prevent deprecation of plugin types supporting subplugins (those in self::$supportsubplugins).
 | 
        
           |  |  | 751 |         foreach (['deprecatedplugintypes', 'deletedplugintypes'] as $key) {
 | 
        
           |  |  | 752 |             $illegaltypes = array_intersect(self::$supportsubplugins, array_keys($plugintypesmap[$key]));
 | 
        
           |  |  | 753 |             if (!empty($illegaltypes)) {
 | 
        
           |  |  | 754 |                 debugging("Deprecation of a plugin type which supports subplugins is not supported. These plugin types will ".
 | 
        
           |  |  | 755 |                     "continue to be treated as active.", DEBUG_DEVELOPER);
 | 
        
           |  |  | 756 |                 foreach ($illegaltypes as $plugintype) {
 | 
        
           |  |  | 757 |                     $plugintypesmap['plugintypes'][$plugintype] = $plugintypesmap[$key][$plugintype];
 | 
        
           |  |  | 758 |                     unset($plugintypesmap[$key][$plugintype]);
 | 
        
           |  |  | 759 |                 }
 | 
        
           |  |  | 760 |             }
 | 
        
           |  |  | 761 |         }
 | 
        
           | 1 | efrain | 762 |   | 
        
           |  |  | 763 |         if (!empty($CFG->themedir) && is_dir($CFG->themedir)) {
 | 
        
           | 1441 | ariadna | 764 |             $plugintypesmap['plugintypes']['theme'] = $CFG->themedir;
 | 
        
           | 1 | efrain | 765 |         } else {
 | 
        
           | 1441 | ariadna | 766 |             $plugintypesmap['plugintypes']['theme'] = $CFG->dirroot . '/theme';
 | 
        
           | 1 | efrain | 767 |         }
 | 
        
           |  |  | 768 |   | 
        
           |  |  | 769 |         foreach (self::$supportsubplugins as $type) {
 | 
        
           |  |  | 770 |             if ($type === 'local') {
 | 
        
           |  |  | 771 |                 // Local subplugins must be after local plugins.
 | 
        
           |  |  | 772 |                 continue;
 | 
        
           |  |  | 773 |             }
 | 
        
           | 1441 | ariadna | 774 |   | 
        
           |  |  | 775 |             $plugins = self::fetch_plugins($type, $plugintypesmap['plugintypes'][$type]);
 | 
        
           | 1 | efrain | 776 |             foreach ($plugins as $plugin => $fulldir) {
 | 
        
           | 1441 | ariadna | 777 |                 $allsubtypes = self::fetch_subtypes($fulldir);
 | 
        
           |  |  | 778 |                 $subplugintypesdata = [
 | 
        
           |  |  | 779 |                     'plugintypes' => $allsubtypes['plugintypes'] ?? [],
 | 
        
           |  |  | 780 |                     'deprecatedplugintypes' => $allsubtypes['deprecatedplugintypes'] ?? [],
 | 
        
           |  |  | 781 |                     'deletedplugintypes' => $allsubtypes['deletedplugintypes'] ?? [],
 | 
        
           |  |  | 782 |                 ];
 | 
        
           |  |  | 783 |   | 
        
           |  |  | 784 |                 if (!$subplugintypesdata['plugintypes'] && !$subplugintypesdata['deprecatedplugintypes']
 | 
        
           |  |  | 785 |                         && !$subplugintypesdata['deletedplugintypes']) {
 | 
        
           | 1 | efrain | 786 |                     continue;
 | 
        
           |  |  | 787 |                 }
 | 
        
           | 1441 | ariadna | 788 |                 $subplugintypesmap['plugintypes'][$type . '_' . $plugin] = [];
 | 
        
           |  |  | 789 |                 $subplugintypesmap['deprecatedplugintypes'][$type . '_' . $plugin] = [];
 | 
        
           |  |  | 790 |                 $subplugintypesmap['deletedplugintypes'][$type . '_' . $plugin] = [];
 | 
        
           |  |  | 791 |   | 
        
           |  |  | 792 |                 foreach ($subplugintypesdata as $key => $subplugintypes) {
 | 
        
           |  |  | 793 |                     foreach ($subplugintypes as $subtype => $subdir) {
 | 
        
           |  |  | 794 |                         if (isset($plugintypesmap['plugintypes'][$subtype])
 | 
        
           |  |  | 795 |                                 || isset($plugintypesmap['deprecatedplugintypes'][$subtype])
 | 
        
           |  |  | 796 |                                 || isset($plugintypesmap['deletedplugintypes'][$subtype])) {
 | 
        
           |  |  | 797 |                             error_log("Invalid subtype '$subtype', duplicate detected.");
 | 
        
           |  |  | 798 |                             continue;
 | 
        
           |  |  | 799 |                         }
 | 
        
           |  |  | 800 |                         $plugintypesmap[$key][$subtype] = $subdir;
 | 
        
           |  |  | 801 |                         $parents[$subtype] = $type . '_' . $plugin;
 | 
        
           |  |  | 802 |                         $subplugintypesmap[$key][$type . '_' . $plugin][$subtype] = array_keys(
 | 
        
           |  |  | 803 |                             self::fetch_plugins($subtype, $subdir)
 | 
        
           |  |  | 804 |                         );
 | 
        
           | 1 | efrain | 805 |                     }
 | 
        
           |  |  | 806 |                 }
 | 
        
           |  |  | 807 |             }
 | 
        
           |  |  | 808 |         }
 | 
        
           |  |  | 809 |         // Local is always last!
 | 
        
           | 1441 | ariadna | 810 |         $plugintypesmap['plugintypes']['local'] = $CFG->dirroot . '/local';
 | 
        
           | 1 | efrain | 811 |   | 
        
           |  |  | 812 |         if (in_array('local', self::$supportsubplugins)) {
 | 
        
           |  |  | 813 |             $type = 'local';
 | 
        
           | 1441 | ariadna | 814 |             $plugins = self::fetch_plugins($type, $plugintypesmap['plugintypes'][$type]);
 | 
        
           | 1 | efrain | 815 |             foreach ($plugins as $plugin => $fulldir) {
 | 
        
           | 1441 | ariadna | 816 |                 $allsubtypes = self::fetch_subtypes($fulldir);
 | 
        
           |  |  | 817 |                 $subplugintypesdata = [
 | 
        
           |  |  | 818 |                     'plugintypes' => $allsubtypes['plugintypes'] ?? [],
 | 
        
           |  |  | 819 |                     'deprecatedplugintypes' => $allsubtypes['deprecatedplugintypes'] ?? [],
 | 
        
           |  |  | 820 |                     'deletedplugintypes' => $allsubtypes['deletedplugintypes'] ?? [],
 | 
        
           |  |  | 821 |                 ];
 | 
        
           |  |  | 822 |                 if (!$subplugintypesdata['plugintypes'] && !$subplugintypesdata['deprecatedplugintypes']
 | 
        
           |  |  | 823 |                         && !$subplugintypesdata['deletedplugintypes']) {
 | 
        
           | 1 | efrain | 824 |                     continue;
 | 
        
           |  |  | 825 |                 }
 | 
        
           | 1441 | ariadna | 826 |                 $subplugintypesmap['plugintypes'][$type . '_' . $plugin] = [];
 | 
        
           |  |  | 827 |                 $subplugintypesmap['deprecatedplugintypes'][$type . '_' . $plugin] = [];
 | 
        
           |  |  | 828 |                 $subplugintypesmap['deletedplugintypes'][$type . '_' . $plugin] = [];
 | 
        
           |  |  | 829 |   | 
        
           |  |  | 830 |                 foreach ($subplugintypesdata as $key => $subplugintypes) {
 | 
        
           |  |  | 831 |                     foreach ($subplugintypes as $subtype => $subdir) {
 | 
        
           |  |  | 832 |                         if (isset($plugintypesmap['plugintypes'][$subtype])
 | 
        
           |  |  | 833 |                                 || isset($plugintypesmap['deprecatedplugintypes'][$subtype])
 | 
        
           |  |  | 834 |                                 || isset($plugintypesmap['deletedplugintypes'][$subtype])) {
 | 
        
           |  |  | 835 |                             error_log("Invalid subtype '$subtype', duplicate detected.");
 | 
        
           |  |  | 836 |                             continue;
 | 
        
           |  |  | 837 |                         }
 | 
        
           |  |  | 838 |                         $plugintypesmap[$key][$subtype] = $subdir;
 | 
        
           |  |  | 839 |                         $parents[$subtype] = $type . '_' . $plugin;
 | 
        
           |  |  | 840 |                         $subplugintypesmap[$key][$type . '_' . $plugin][$subtype] = array_keys(
 | 
        
           |  |  | 841 |                             self::fetch_plugins($subtype, $subdir)
 | 
        
           |  |  | 842 |                         );
 | 
        
           | 1 | efrain | 843 |                     }
 | 
        
           |  |  | 844 |                 }
 | 
        
           |  |  | 845 |             }
 | 
        
           |  |  | 846 |         }
 | 
        
           |  |  | 847 |   | 
        
           | 1441 | ariadna | 848 |         return [
 | 
        
           |  |  | 849 |             'plugintypes' => $plugintypesmap['plugintypes'],
 | 
        
           |  |  | 850 |             'parents' => $parents,
 | 
        
           |  |  | 851 |             'subplugins' => $subplugintypesmap['plugintypes'],
 | 
        
           |  |  | 852 |             'deprecatedplugintypes' => $plugintypesmap['deprecatedplugintypes'],
 | 
        
           |  |  | 853 |             'deletedplugintypes' => $plugintypesmap['deletedplugintypes'],
 | 
        
           |  |  | 854 |             'deprecatedsubplugins' => $subplugintypesmap['deprecatedplugintypes'],
 | 
        
           |  |  | 855 |             'deletedsubplugin' => $subplugintypesmap['deletedplugintypes'],
 | 
        
           |  |  | 856 |         ];
 | 
        
           | 1 | efrain | 857 |     }
 | 
        
           |  |  | 858 |   | 
        
           |  |  | 859 |     /**
 | 
        
           |  |  | 860 |      * Returns the component source content as loaded from /lib/components.json.
 | 
        
           |  |  | 861 |      *
 | 
        
           |  |  | 862 |      * @return array
 | 
        
           |  |  | 863 |      */
 | 
        
           |  |  | 864 |     protected static function fetch_component_source(string $key) {
 | 
        
           |  |  | 865 |         if (null === self::$componentsource) {
 | 
        
           |  |  | 866 |             self::$componentsource = (array) json_decode(file_get_contents(__DIR__ . '/../components.json'));
 | 
        
           |  |  | 867 |         }
 | 
        
           |  |  | 868 |   | 
        
           | 1441 | ariadna | 869 |         return !empty(self::$componentsource[$key]) ? (array) self::$componentsource[$key] : [];
 | 
        
           | 1 | efrain | 870 |     }
 | 
        
           |  |  | 871 |   | 
        
           |  |  | 872 |     /**
 | 
        
           |  |  | 873 |      * Returns list of subtypes.
 | 
        
           |  |  | 874 |      * @param string $ownerdir
 | 
        
           |  |  | 875 |      * @return array
 | 
        
           |  |  | 876 |      */
 | 
        
           |  |  | 877 |     protected static function fetch_subtypes($ownerdir) {
 | 
        
           |  |  | 878 |         global $CFG;
 | 
        
           |  |  | 879 |   | 
        
           |  |  | 880 |         $types = [];
 | 
        
           |  |  | 881 |         $subplugins = [];
 | 
        
           | 1441 | ariadna | 882 |         if (str_contains($ownerdir, $CFG->dirroot)) {
 | 
        
           |  |  | 883 |             $plugindir = substr($ownerdir, strlen($CFG->dirroot) + 1);
 | 
        
           |  |  | 884 |         } else {
 | 
        
           |  |  | 885 |             $realownerdir = realpath($ownerdir);
 | 
        
           |  |  | 886 |             $realroot = realpath(dirname(__DIR__, 2));
 | 
        
           |  |  | 887 |             $plugindir = substr($realownerdir, strlen($realroot) + 1);
 | 
        
           |  |  | 888 |         }
 | 
        
           |  |  | 889 |   | 
        
           |  |  | 890 |         $subtypesregister = [
 | 
        
           |  |  | 891 |             'plugintypes' => [],
 | 
        
           |  |  | 892 |             'deprecatedplugintypes' => [],
 | 
        
           |  |  | 893 |             'deletedplugintypes' => [],
 | 
        
           |  |  | 894 |         ];
 | 
        
           | 1 | efrain | 895 |         if (file_exists("$ownerdir/db/subplugins.json")) {
 | 
        
           | 1441 | ariadna | 896 |             $subpluginpathformatter = fn (string $value): string => "{$plugindir}/{$value}";
 | 
        
           | 1 | efrain | 897 |             $subpluginsjson = json_decode(file_get_contents("$ownerdir/db/subplugins.json"));
 | 
        
           |  |  | 898 |             if (json_last_error() === JSON_ERROR_NONE) {
 | 
        
           | 1441 | ariadna | 899 |                 $subplugins = [];
 | 
        
           |  |  | 900 |                 if (!empty($subpluginsjson->subplugintypes)) {
 | 
        
           |  |  | 901 |                     // If the newer subplugintypes is defined, use it.
 | 
        
           |  |  | 902 |                     // The value here is relative to the plugin's owner directory.
 | 
        
           |  |  | 903 |                     $subplugins = array_map($subpluginpathformatter, (array) $subpluginsjson->subplugintypes);
 | 
        
           |  |  | 904 |                 } else if (!empty($subpluginsjson->plugintypes)) {
 | 
        
           |  |  | 905 |                     error_log(
 | 
        
           |  |  | 906 |                         "No subplugintypes defined in $ownerdir/db/subplugins.json. " .
 | 
        
           |  |  | 907 |                         "Falling back to deprecated plugintypes value. " .
 | 
        
           |  |  | 908 |                         "See MDL-83705 for further information.",
 | 
        
           |  |  | 909 |                     );
 | 
        
           | 1 | efrain | 910 |                     $subplugins = (array) $subpluginsjson->plugintypes;
 | 
        
           | 1441 | ariadna | 911 |                 } else if (empty($subpluginjson->deprecatedplugintypes) && empty($subpluginsjson->deletedplugintypes)) {
 | 
        
           | 1 | efrain | 912 |                     error_log("No plugintypes defined in $ownerdir/db/subplugins.json");
 | 
        
           |  |  | 913 |                 }
 | 
        
           | 1441 | ariadna | 914 |   | 
        
           |  |  | 915 |                 $subtypesregister['plugintypes'] = $subplugins;
 | 
        
           |  |  | 916 |   | 
        
           |  |  | 917 |                 // The deprecated and deleted subplugintypes are optional and are always relative to the plugin's root directory.
 | 
        
           |  |  | 918 |                 $subtypesregister['deprecatedplugintypes'] = array_map(
 | 
        
           |  |  | 919 |                     $subpluginpathformatter,
 | 
        
           |  |  | 920 |                     (array) ($subpluginsjson->deprecatedsubplugintypes ?? []),
 | 
        
           |  |  | 921 |                 );
 | 
        
           |  |  | 922 |                 $subtypesregister['deletedplugintypes'] = array_map(
 | 
        
           |  |  | 923 |                     $subpluginpathformatter,
 | 
        
           |  |  | 924 |                     (array) ($subpluginsjson->deletedsubplugintypes ?? []),
 | 
        
           |  |  | 925 |                 );
 | 
        
           | 1 | efrain | 926 |             } else {
 | 
        
           |  |  | 927 |                 $jsonerror = json_last_error_msg();
 | 
        
           |  |  | 928 |                 error_log("$ownerdir/db/subplugins.json is invalid ($jsonerror)");
 | 
        
           |  |  | 929 |             }
 | 
        
           | 1441 | ariadna | 930 |   | 
        
           |  |  | 931 |             if (function_exists('debugging') && debugging()) {
 | 
        
           |  |  | 932 |                 if (property_exists($subpluginsjson, 'subplugintypes') && property_exists($subpluginsjson, 'plugintypes')) {
 | 
        
           |  |  | 933 |                     $subplugintypes = (array) $subpluginsjson->subplugintypes;
 | 
        
           |  |  | 934 |                     $plugintypes = (array) $subpluginsjson->plugintypes;
 | 
        
           |  |  | 935 |                     if (count($subplugintypes) !== count(($plugintypes))) {
 | 
        
           |  |  | 936 |                         error_log("Subplugintypes and plugintypes are not in sync in $ownerdir/db/subplugins.json");
 | 
        
           |  |  | 937 |                     }
 | 
        
           |  |  | 938 |                     foreach ($subplugintypes as $type => $path) {
 | 
        
           |  |  | 939 |                         if (!isset($plugintypes[$type])) {
 | 
        
           |  |  | 940 |                             error_log("Subplugintypes and plugintypes are not in sync for '$type' in $ownerdir/db/subplugins.json");
 | 
        
           |  |  | 941 |   | 
        
           |  |  | 942 |                             continue;
 | 
        
           |  |  | 943 |                         }
 | 
        
           |  |  | 944 |   | 
        
           |  |  | 945 |                         if ($plugintypes[$type] !== $subplugins[$type]) {
 | 
        
           |  |  | 946 |                             error_log("Subplugintypes and plugintypes are not in sync for '$type' in $ownerdir/db/subplugins.json");
 | 
        
           |  |  | 947 |                         }
 | 
        
           |  |  | 948 |                     }
 | 
        
           |  |  | 949 |                 }
 | 
        
           |  |  | 950 |             }
 | 
        
           | 1 | efrain | 951 |         } else if (file_exists("$ownerdir/db/subplugins.php")) {
 | 
        
           | 1441 | ariadna | 952 |             throw new coding_exception(
 | 
        
           |  |  | 953 |                 'Use of subplugins.php has been deprecated and is no longer supported. ' .
 | 
        
           |  |  | 954 |                 "Please update your '$ownerdir' plugin to provide a subplugins.json file instead.",
 | 
        
           |  |  | 955 |             );
 | 
        
           | 1 | efrain | 956 |         }
 | 
        
           |  |  | 957 |   | 
        
           | 1441 | ariadna | 958 |         foreach ($subtypesregister as $key => $subtypes) {
 | 
        
           |  |  | 959 |             foreach ($subtypes as $subtype => $dir) {
 | 
        
           |  |  | 960 |                 if (!preg_match('/^[a-z][a-z0-9]*$/', $subtype)) {
 | 
        
           |  |  | 961 |                     error_log("Invalid subtype '$subtype'' detected in '$ownerdir', invalid characters present.");
 | 
        
           |  |  | 962 |                     continue;
 | 
        
           |  |  | 963 |                 }
 | 
        
           |  |  | 964 |                 if (isset(self::$subsystems[$subtype])) {
 | 
        
           |  |  | 965 |                     error_log("Invalid subtype '$subtype'' detected in '$ownerdir', duplicates core subsystem.");
 | 
        
           |  |  | 966 |                     continue;
 | 
        
           |  |  | 967 |                 }
 | 
        
           |  |  | 968 |                 if ($CFG->admin !== 'admin' && strpos($dir, 'admin/') === 0) {
 | 
        
           |  |  | 969 |                     $dir = preg_replace('|^admin/|', "$CFG->admin/", $dir);
 | 
        
           |  |  | 970 |                 }
 | 
        
           |  |  | 971 |                 if (!is_dir("$CFG->dirroot/$dir")) {
 | 
        
           |  |  | 972 |                     error_log("Invalid subtype directory '$dir' detected in '$ownerdir'.");
 | 
        
           |  |  | 973 |                     continue;
 | 
        
           |  |  | 974 |                 }
 | 
        
           |  |  | 975 |                 $types[$key][$subtype] = "$CFG->dirroot/$dir";
 | 
        
           | 1 | efrain | 976 |             }
 | 
        
           |  |  | 977 |         }
 | 
        
           |  |  | 978 |   | 
        
           |  |  | 979 |         return $types;
 | 
        
           |  |  | 980 |     }
 | 
        
           |  |  | 981 |   | 
        
           |  |  | 982 |     /**
 | 
        
           |  |  | 983 |      * Returns list of plugins of given type in given directory.
 | 
        
           |  |  | 984 |      * @param string $plugintype
 | 
        
           |  |  | 985 |      * @param string $fulldir
 | 
        
           |  |  | 986 |      * @return array
 | 
        
           |  |  | 987 |      */
 | 
        
           |  |  | 988 |     protected static function fetch_plugins($plugintype, $fulldir) {
 | 
        
           |  |  | 989 |         global $CFG;
 | 
        
           |  |  | 990 |   | 
        
           |  |  | 991 |         $fulldirs = (array)$fulldir;
 | 
        
           |  |  | 992 |         if ($plugintype === 'theme') {
 | 
        
           |  |  | 993 |             if (realpath($fulldir) !== realpath($CFG->dirroot . '/theme')) {
 | 
        
           |  |  | 994 |                 // Include themes in standard location too.
 | 
        
           |  |  | 995 |                 array_unshift($fulldirs, $CFG->dirroot . '/theme');
 | 
        
           |  |  | 996 |             }
 | 
        
           |  |  | 997 |         }
 | 
        
           |  |  | 998 |   | 
        
           |  |  | 999 |         $result = [];
 | 
        
           |  |  | 1000 |   | 
        
           |  |  | 1001 |         foreach ($fulldirs as $fulldir) {
 | 
        
           |  |  | 1002 |             if (!is_dir($fulldir)) {
 | 
        
           |  |  | 1003 |                 continue;
 | 
        
           |  |  | 1004 |             }
 | 
        
           | 1441 | ariadna | 1005 |             $items = new DirectoryIterator($fulldir);
 | 
        
           | 1 | efrain | 1006 |             foreach ($items as $item) {
 | 
        
           |  |  | 1007 |                 if ($item->isDot() || !$item->isDir()) {
 | 
        
           |  |  | 1008 |                     continue;
 | 
        
           |  |  | 1009 |                 }
 | 
        
           |  |  | 1010 |                 $pluginname = $item->getFilename();
 | 
        
           |  |  | 1011 |                 if ($plugintype === 'auth' && $pluginname === 'db') {
 | 
        
           |  |  | 1012 |                     // Special exception for this wrong plugin name.
 | 
        
           |  |  | 1013 |                 } else if (isset(self::$ignoreddirs[$pluginname])) {
 | 
        
           |  |  | 1014 |                     continue;
 | 
        
           |  |  | 1015 |                 }
 | 
        
           |  |  | 1016 |                 if (!self::is_valid_plugin_name($plugintype, $pluginname)) {
 | 
        
           |  |  | 1017 |                     // Always ignore plugins with problematic names here.
 | 
        
           |  |  | 1018 |                     continue;
 | 
        
           |  |  | 1019 |                 }
 | 
        
           |  |  | 1020 |                 $result[$pluginname] = $fulldir . '/' . $pluginname;
 | 
        
           |  |  | 1021 |                 unset($item);
 | 
        
           |  |  | 1022 |             }
 | 
        
           |  |  | 1023 |             unset($items);
 | 
        
           |  |  | 1024 |         }
 | 
        
           |  |  | 1025 |   | 
        
           |  |  | 1026 |         ksort($result);
 | 
        
           |  |  | 1027 |         return $result;
 | 
        
           |  |  | 1028 |     }
 | 
        
           |  |  | 1029 |   | 
        
           |  |  | 1030 |     /**
 | 
        
           |  |  | 1031 |      * Find all classes that can be autoloaded including frankenstyle namespaces.
 | 
        
           |  |  | 1032 |      */
 | 
        
           |  |  | 1033 |     protected static function fill_classmap_cache() {
 | 
        
           |  |  | 1034 |         global $CFG;
 | 
        
           |  |  | 1035 |   | 
        
           |  |  | 1036 |         self::$classmap = [];
 | 
        
           |  |  | 1037 |   | 
        
           |  |  | 1038 |         self::load_classes('core', "$CFG->dirroot/lib/classes");
 | 
        
           | 1441 | ariadna | 1039 |         self::load_legacy_classes($CFG->libdir, true);
 | 
        
           | 1 | efrain | 1040 |   | 
        
           |  |  | 1041 |         foreach (self::$subsystems as $subsystem => $fulldir) {
 | 
        
           |  |  | 1042 |             if (!$fulldir) {
 | 
        
           |  |  | 1043 |                 continue;
 | 
        
           |  |  | 1044 |             }
 | 
        
           |  |  | 1045 |             self::load_classes('core_' . $subsystem, "$fulldir/classes");
 | 
        
           |  |  | 1046 |         }
 | 
        
           |  |  | 1047 |   | 
        
           |  |  | 1048 |         foreach (self::$plugins as $plugintype => $plugins) {
 | 
        
           |  |  | 1049 |             foreach ($plugins as $pluginname => $fulldir) {
 | 
        
           |  |  | 1050 |                 self::load_classes($plugintype . '_' . $pluginname, "$fulldir/classes");
 | 
        
           | 1441 | ariadna | 1051 |                 self::load_legacy_classes($fulldir);
 | 
        
           | 1 | efrain | 1052 |             }
 | 
        
           |  |  | 1053 |         }
 | 
        
           | 1441 | ariadna | 1054 |   | 
        
           |  |  | 1055 |         // Include deprecated plugins in the classmap, to facilitate migration code which uses existing plugin classes.
 | 
        
           |  |  | 1056 |         foreach (self::$deprecatedplugins as $plugintype => $plugins) {
 | 
        
           |  |  | 1057 |             foreach ($plugins as $pluginname => $fulldir) {
 | 
        
           |  |  | 1058 |                 self::load_classes($plugintype . '_' . $pluginname, "$fulldir/classes");
 | 
        
           |  |  | 1059 |             }
 | 
        
           |  |  | 1060 |         }
 | 
        
           |  |  | 1061 |   | 
        
           | 1 | efrain | 1062 |         ksort(self::$classmap);
 | 
        
           |  |  | 1063 |     }
 | 
        
           |  |  | 1064 |   | 
        
           |  |  | 1065 |     /**
 | 
        
           |  |  | 1066 |      * Fills up the cache defining what plugins have certain files.
 | 
        
           |  |  | 1067 |      *
 | 
        
           |  |  | 1068 |      * @see self::get_plugin_list_with_file
 | 
        
           |  |  | 1069 |      * @return void
 | 
        
           |  |  | 1070 |      */
 | 
        
           |  |  | 1071 |     protected static function fill_filemap_cache() {
 | 
        
           |  |  | 1072 |         global $CFG;
 | 
        
           |  |  | 1073 |   | 
        
           |  |  | 1074 |         self::$filemap = [];
 | 
        
           |  |  | 1075 |   | 
        
           |  |  | 1076 |         foreach (self::$filestomap as $file) {
 | 
        
           |  |  | 1077 |             if (!isset(self::$filemap[$file])) {
 | 
        
           |  |  | 1078 |                 self::$filemap[$file] = [];
 | 
        
           |  |  | 1079 |             }
 | 
        
           |  |  | 1080 |             foreach (self::$plugins as $plugintype => $plugins) {
 | 
        
           |  |  | 1081 |                 if (!isset(self::$filemap[$file][$plugintype])) {
 | 
        
           |  |  | 1082 |                     self::$filemap[$file][$plugintype] = [];
 | 
        
           |  |  | 1083 |                 }
 | 
        
           |  |  | 1084 |                 foreach ($plugins as $pluginname => $fulldir) {
 | 
        
           |  |  | 1085 |                     if (file_exists("$fulldir/$file")) {
 | 
        
           |  |  | 1086 |                         self::$filemap[$file][$plugintype][$pluginname] = "$fulldir/$file";
 | 
        
           |  |  | 1087 |                     }
 | 
        
           |  |  | 1088 |                 }
 | 
        
           |  |  | 1089 |             }
 | 
        
           |  |  | 1090 |         }
 | 
        
           |  |  | 1091 |     }
 | 
        
           |  |  | 1092 |   | 
        
           |  |  | 1093 |     /**
 | 
        
           |  |  | 1094 |      * Find classes in directory and recurse to subdirs.
 | 
        
           |  |  | 1095 |      * @param string $component
 | 
        
           |  |  | 1096 |      * @param string $fulldir
 | 
        
           |  |  | 1097 |      * @param string $namespace
 | 
        
           |  |  | 1098 |      */
 | 
        
           |  |  | 1099 |     protected static function load_classes($component, $fulldir, $namespace = '') {
 | 
        
           |  |  | 1100 |         if (!is_dir($fulldir)) {
 | 
        
           |  |  | 1101 |             return;
 | 
        
           |  |  | 1102 |         }
 | 
        
           |  |  | 1103 |   | 
        
           |  |  | 1104 |         if (!is_readable($fulldir)) {
 | 
        
           |  |  | 1105 |             // TODO: MDL-51711 We should generate some diagnostic debugging information in this case
 | 
        
           |  |  | 1106 |             // because its pretty likely to lead to a missing class error further down the line.
 | 
        
           |  |  | 1107 |             // But our early setup code can't handle errors this early at the moment.
 | 
        
           |  |  | 1108 |             return;
 | 
        
           |  |  | 1109 |         }
 | 
        
           |  |  | 1110 |   | 
        
           | 1441 | ariadna | 1111 |         $items = new DirectoryIterator($fulldir);
 | 
        
           | 1 | efrain | 1112 |         foreach ($items as $item) {
 | 
        
           |  |  | 1113 |             if ($item->isDot()) {
 | 
        
           |  |  | 1114 |                 continue;
 | 
        
           |  |  | 1115 |             }
 | 
        
           |  |  | 1116 |             if ($item->isDir()) {
 | 
        
           |  |  | 1117 |                 $dirname = $item->getFilename();
 | 
        
           |  |  | 1118 |                 self::load_classes($component, "$fulldir/$dirname", $namespace . '\\' . $dirname);
 | 
        
           |  |  | 1119 |                 continue;
 | 
        
           |  |  | 1120 |             }
 | 
        
           |  |  | 1121 |   | 
        
           |  |  | 1122 |             $filename = $item->getFilename();
 | 
        
           |  |  | 1123 |             $classname = preg_replace('/\.php$/', '', $filename);
 | 
        
           |  |  | 1124 |   | 
        
           |  |  | 1125 |             if ($filename === $classname) {
 | 
        
           |  |  | 1126 |                 // Not a php file.
 | 
        
           |  |  | 1127 |                 continue;
 | 
        
           |  |  | 1128 |             }
 | 
        
           |  |  | 1129 |             if ($namespace === '') {
 | 
        
           |  |  | 1130 |                 // Legacy long frankenstyle class name.
 | 
        
           |  |  | 1131 |                 self::$classmap[$component . '_' . $classname] = "$fulldir/$filename";
 | 
        
           |  |  | 1132 |             }
 | 
        
           |  |  | 1133 |             // New namespaced classes.
 | 
        
           |  |  | 1134 |             self::$classmap[$component . $namespace . '\\' . $classname] = "$fulldir/$filename";
 | 
        
           |  |  | 1135 |         }
 | 
        
           |  |  | 1136 |         unset($item);
 | 
        
           |  |  | 1137 |         unset($items);
 | 
        
           |  |  | 1138 |     }
 | 
        
           |  |  | 1139 |   | 
        
           |  |  | 1140 |   | 
        
           |  |  | 1141 |     /**
 | 
        
           |  |  | 1142 |      * List all core subsystems and their location
 | 
        
           |  |  | 1143 |      *
 | 
        
           |  |  | 1144 |      * This is a list of components that are part of the core and their
 | 
        
           |  |  | 1145 |      * language strings are defined in /lang/en/<<subsystem>>.php. If a given
 | 
        
           |  |  | 1146 |      * plugin is not listed here and it does not have proper plugintype prefix,
 | 
        
           |  |  | 1147 |      * then it is considered as course activity module.
 | 
        
           |  |  | 1148 |      *
 | 
        
           |  |  | 1149 |      * The location is absolute file path to dir. NULL means there is no special
 | 
        
           |  |  | 1150 |      * directory for this subsystem. If the location is set, the subsystem's
 | 
        
           |  |  | 1151 |      * renderer.php is expected to be there.
 | 
        
           |  |  | 1152 |      *
 | 
        
           |  |  | 1153 |      * @return array of (string)name => (string|null)full dir location
 | 
        
           |  |  | 1154 |      */
 | 
        
           |  |  | 1155 |     public static function get_core_subsystems() {
 | 
        
           |  |  | 1156 |         self::init();
 | 
        
           |  |  | 1157 |         return self::$subsystems;
 | 
        
           |  |  | 1158 |     }
 | 
        
           |  |  | 1159 |   | 
        
           |  |  | 1160 |     /**
 | 
        
           |  |  | 1161 |      * List all core APIs and their attributes.
 | 
        
           |  |  | 1162 |      *
 | 
        
           |  |  | 1163 |      * This is a list of all the existing / allowed APIs in moodle, each one with the
 | 
        
           |  |  | 1164 |      * following attributes:
 | 
        
           |  |  | 1165 |      *   - component: the component, usually a subsystem or core, the API belongs to.
 | 
        
           |  |  | 1166 |      *   - allowedlevel2: if the API is allowed as level2 namespace or no.
 | 
        
           |  |  | 1167 |      *   - allowedspread: if the API can spread out from its component or no.
 | 
        
           |  |  | 1168 |      *
 | 
        
           |  |  | 1169 |      * @return stdClass[] array of APIs (as keys) with their attributes as object instances.
 | 
        
           |  |  | 1170 |      */
 | 
        
           |  |  | 1171 |     public static function get_core_apis() {
 | 
        
           |  |  | 1172 |         self::init();
 | 
        
           |  |  | 1173 |         return self::$apis;
 | 
        
           |  |  | 1174 |     }
 | 
        
           |  |  | 1175 |   | 
        
           |  |  | 1176 |     /**
 | 
        
           |  |  | 1177 |      * Get list of available plugin types together with their location.
 | 
        
           |  |  | 1178 |      *
 | 
        
           |  |  | 1179 |      * @return array as (string)plugintype => (string)fulldir
 | 
        
           |  |  | 1180 |      */
 | 
        
           |  |  | 1181 |     public static function get_plugin_types() {
 | 
        
           |  |  | 1182 |         self::init();
 | 
        
           |  |  | 1183 |         return self::$plugintypes;
 | 
        
           |  |  | 1184 |     }
 | 
        
           |  |  | 1185 |   | 
        
           |  |  | 1186 |     /**
 | 
        
           | 1441 | ariadna | 1187 |      * Get a list of deprecated plugin types and their locations.
 | 
        
           |  |  | 1188 |      *
 | 
        
           |  |  | 1189 |      * @return array as (string)plugintype => (string)fulldir
 | 
        
           |  |  | 1190 |      */
 | 
        
           |  |  | 1191 |     public static function get_deprecated_plugin_types(): array {
 | 
        
           |  |  | 1192 |         self::init();
 | 
        
           |  |  | 1193 |         return self::$deprecatedplugintypes;
 | 
        
           |  |  | 1194 |     }
 | 
        
           |  |  | 1195 |   | 
        
           |  |  | 1196 |     /**
 | 
        
           |  |  | 1197 |      * Get a list of all deleted plugin types and their locations.
 | 
        
           |  |  | 1198 |      *
 | 
        
           |  |  | 1199 |      * @return array as (string)plugintype => (string)fulldir
 | 
        
           |  |  | 1200 |      */
 | 
        
           |  |  | 1201 |     public static function get_deleted_plugin_types(): array {
 | 
        
           |  |  | 1202 |         self::init();
 | 
        
           |  |  | 1203 |         return self::$deletedplugintypes;
 | 
        
           |  |  | 1204 |     }
 | 
        
           |  |  | 1205 |   | 
        
           |  |  | 1206 |     /**
 | 
        
           |  |  | 1207 |      * Gets list of all plugin types, comprising available plugin types as well as any plugin types currently in deprecation.
 | 
        
           |  |  | 1208 |      *
 | 
        
           |  |  | 1209 |      * @return array as (string)plugintype => (string)fulldir
 | 
        
           |  |  | 1210 |      */
 | 
        
           |  |  | 1211 |     public static function get_all_plugin_types(): array {
 | 
        
           |  |  | 1212 |         self::init();
 | 
        
           |  |  | 1213 |         return array_merge(self::$plugintypes, self::$deprecatedplugintypes, self::$deletedplugintypes);
 | 
        
           |  |  | 1214 |     }
 | 
        
           |  |  | 1215 |   | 
        
           |  |  | 1216 |     /**
 | 
        
           |  |  | 1217 |      * Is the plugintype deprecated.
 | 
        
           |  |  | 1218 |      *
 | 
        
           |  |  | 1219 |      * @param string $plugintype
 | 
        
           |  |  | 1220 |      * @return bool true if deprecated, false otherwise.
 | 
        
           |  |  | 1221 |      */
 | 
        
           |  |  | 1222 |     public static function is_deprecated_plugin_type(string $plugintype): bool {
 | 
        
           |  |  | 1223 |         self::init();
 | 
        
           |  |  | 1224 |         return array_key_exists($plugintype, self::$deprecatedplugintypes);
 | 
        
           |  |  | 1225 |     }
 | 
        
           |  |  | 1226 |   | 
        
           |  |  | 1227 |     /**
 | 
        
           |  |  | 1228 |      * Is the plugintype deleted.
 | 
        
           |  |  | 1229 |      *
 | 
        
           |  |  | 1230 |      * @param string $plugintype
 | 
        
           |  |  | 1231 |      * @return bool true if deleted, false otherwise.
 | 
        
           |  |  | 1232 |      */
 | 
        
           |  |  | 1233 |     public static function is_deleted_plugin_type(string $plugintype): bool {
 | 
        
           |  |  | 1234 |         self::init();
 | 
        
           |  |  | 1235 |         return array_key_exists($plugintype, self::$deletedplugintypes);
 | 
        
           |  |  | 1236 |     }
 | 
        
           |  |  | 1237 |   | 
        
           |  |  | 1238 |     /**
 | 
        
           |  |  | 1239 |      * Is the plugintype in deprecation.
 | 
        
           |  |  | 1240 |      *
 | 
        
           |  |  | 1241 |      * @param string $plugintype
 | 
        
           |  |  | 1242 |      * @return bool true if in either phase 1 (deprecated) or phase 2 (deleted) or deprecation.
 | 
        
           |  |  | 1243 |      */
 | 
        
           |  |  | 1244 |     public static function is_plugintype_in_deprecation(string $plugintype): bool {
 | 
        
           |  |  | 1245 |         self::init();
 | 
        
           |  |  | 1246 |         return array_key_exists($plugintype, array_merge(self::$deprecatedplugintypes, self::$deletedplugintypes));
 | 
        
           |  |  | 1247 |     }
 | 
        
           |  |  | 1248 |   | 
        
           |  |  | 1249 |     /**
 | 
        
           | 1 | efrain | 1250 |      * Get list of plugins of given type.
 | 
        
           |  |  | 1251 |      *
 | 
        
           |  |  | 1252 |      * @param string $plugintype
 | 
        
           |  |  | 1253 |      * @return array as (string)pluginname => (string)fulldir
 | 
        
           |  |  | 1254 |      */
 | 
        
           |  |  | 1255 |     public static function get_plugin_list($plugintype) {
 | 
        
           |  |  | 1256 |         self::init();
 | 
        
           |  |  | 1257 |   | 
        
           |  |  | 1258 |         if (!isset(self::$plugins[$plugintype])) {
 | 
        
           |  |  | 1259 |             return [];
 | 
        
           |  |  | 1260 |         }
 | 
        
           |  |  | 1261 |         return self::$plugins[$plugintype];
 | 
        
           |  |  | 1262 |     }
 | 
        
           |  |  | 1263 |   | 
        
           |  |  | 1264 |     /**
 | 
        
           | 1441 | ariadna | 1265 |      * Get list of deprecated plugins of a given type.
 | 
        
           |  |  | 1266 |      *
 | 
        
           |  |  | 1267 |      * @param string $plugintype
 | 
        
           |  |  | 1268 |      * @return array as (string)pluginname => (string)fulldir
 | 
        
           |  |  | 1269 |      */
 | 
        
           |  |  | 1270 |     public static function get_deprecated_plugin_list($plugintype): array {
 | 
        
           |  |  | 1271 |         self::init();
 | 
        
           |  |  | 1272 |         return self::$deprecatedplugins[$plugintype] ?? [];
 | 
        
           |  |  | 1273 |     }
 | 
        
           |  |  | 1274 |   | 
        
           |  |  | 1275 |     /**
 | 
        
           |  |  | 1276 |      * Get list of deleted plugins of a given type.
 | 
        
           |  |  | 1277 |      *
 | 
        
           |  |  | 1278 |      * @param string $plugintype
 | 
        
           |  |  | 1279 |      * @return array as (string)pluginname => (string)fulldir
 | 
        
           |  |  | 1280 |      */
 | 
        
           |  |  | 1281 |     public static function get_deleted_plugin_list($plugintype): array {
 | 
        
           |  |  | 1282 |         self::init();
 | 
        
           |  |  | 1283 |         return self::$deletedplugins[$plugintype] ?? [];
 | 
        
           |  |  | 1284 |     }
 | 
        
           |  |  | 1285 |   | 
        
           |  |  | 1286 |     /**
 | 
        
           |  |  | 1287 |      * Get list of all plugins of a given type, comprising all available plugins as well as any plugins in deprecation.
 | 
        
           |  |  | 1288 |      *
 | 
        
           |  |  | 1289 |      * @param string $plugintype
 | 
        
           |  |  | 1290 |      * @return array as (string)pluginname => (string)fulldir
 | 
        
           |  |  | 1291 |      */
 | 
        
           |  |  | 1292 |     public static function get_all_plugins_list(string $plugintype): array {
 | 
        
           |  |  | 1293 |         self::init();
 | 
        
           |  |  | 1294 |         return array_merge(
 | 
        
           |  |  | 1295 |             self::$plugins[$plugintype] ?? [],
 | 
        
           |  |  | 1296 |             self::$deprecatedplugins[$plugintype] ?? [],
 | 
        
           |  |  | 1297 |             self::$deletedplugins[$plugintype] ?? []
 | 
        
           |  |  | 1298 |         );
 | 
        
           |  |  | 1299 |     }
 | 
        
           |  |  | 1300 |   | 
        
           |  |  | 1301 |     /**
 | 
        
           | 1 | efrain | 1302 |      * Get a list of all the plugins of a given type that define a certain class
 | 
        
           |  |  | 1303 |      * in a certain file. The plugin component names and class names are returned.
 | 
        
           |  |  | 1304 |      *
 | 
        
           |  |  | 1305 |      * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
 | 
        
           |  |  | 1306 |      * @param string $class the part of the name of the class after the
 | 
        
           |  |  | 1307 |      *      frankenstyle prefix. e.g 'thing' if you are looking for classes with
 | 
        
           |  |  | 1308 |      *      names like report_courselist_thing. If you are looking for classes with
 | 
        
           |  |  | 1309 |      *      the same name as the plugin name (e.g. qtype_multichoice) then pass ''.
 | 
        
           |  |  | 1310 |      *      Frankenstyle namespaces are also supported.
 | 
        
           |  |  | 1311 |      * @param string $file the name of file within the plugin that defines the class.
 | 
        
           |  |  | 1312 |      * @return array with frankenstyle plugin names as keys (e.g. 'report_courselist', 'mod_forum')
 | 
        
           |  |  | 1313 |      *      and the class names as values (e.g. 'report_courselist_thing', 'qtype_multichoice').
 | 
        
           |  |  | 1314 |      */
 | 
        
           |  |  | 1315 |     public static function get_plugin_list_with_class($plugintype, $class, $file = null) {
 | 
        
           |  |  | 1316 |         global $CFG; // Necessary in case it is referenced by included PHP scripts.
 | 
        
           |  |  | 1317 |   | 
        
           |  |  | 1318 |         if ($class) {
 | 
        
           |  |  | 1319 |             $suffix = '_' . $class;
 | 
        
           |  |  | 1320 |         } else {
 | 
        
           |  |  | 1321 |             $suffix = '';
 | 
        
           |  |  | 1322 |         }
 | 
        
           |  |  | 1323 |   | 
        
           |  |  | 1324 |         $pluginclasses = [];
 | 
        
           |  |  | 1325 |         $plugins = self::get_plugin_list($plugintype);
 | 
        
           |  |  | 1326 |         foreach ($plugins as $plugin => $fulldir) {
 | 
        
           |  |  | 1327 |             // Try class in frankenstyle namespace.
 | 
        
           |  |  | 1328 |             if ($class) {
 | 
        
           |  |  | 1329 |                 $classname = '\\' . $plugintype . '_' . $plugin . '\\' . $class;
 | 
        
           |  |  | 1330 |                 if (class_exists($classname, true)) {
 | 
        
           |  |  | 1331 |                     $pluginclasses[$plugintype . '_' . $plugin] = $classname;
 | 
        
           |  |  | 1332 |                     continue;
 | 
        
           |  |  | 1333 |                 }
 | 
        
           |  |  | 1334 |             }
 | 
        
           |  |  | 1335 |   | 
        
           |  |  | 1336 |             // Try autoloading of class with frankenstyle prefix.
 | 
        
           |  |  | 1337 |             $classname = $plugintype . '_' . $plugin . $suffix;
 | 
        
           |  |  | 1338 |             if (class_exists($classname, true)) {
 | 
        
           |  |  | 1339 |                 $pluginclasses[$plugintype . '_' . $plugin] = $classname;
 | 
        
           |  |  | 1340 |                 continue;
 | 
        
           |  |  | 1341 |             }
 | 
        
           |  |  | 1342 |   | 
        
           |  |  | 1343 |             // Fall back to old file location and class name.
 | 
        
           |  |  | 1344 |             if ($file && file_exists("$fulldir/$file")) {
 | 
        
           |  |  | 1345 |                 include_once("$fulldir/$file");
 | 
        
           |  |  | 1346 |                 if (class_exists($classname, false)) {
 | 
        
           |  |  | 1347 |                     $pluginclasses[$plugintype . '_' . $plugin] = $classname;
 | 
        
           |  |  | 1348 |                     continue;
 | 
        
           |  |  | 1349 |                 }
 | 
        
           |  |  | 1350 |             }
 | 
        
           |  |  | 1351 |         }
 | 
        
           |  |  | 1352 |   | 
        
           |  |  | 1353 |         return $pluginclasses;
 | 
        
           |  |  | 1354 |     }
 | 
        
           |  |  | 1355 |   | 
        
           |  |  | 1356 |     /**
 | 
        
           |  |  | 1357 |      * Get a list of all the plugins of a given type that contain a particular file.
 | 
        
           |  |  | 1358 |      *
 | 
        
           |  |  | 1359 |      * @param string $plugintype the type of plugin, e.g. 'mod' or 'report'.
 | 
        
           |  |  | 1360 |      * @param string $file the name of file that must be present in the plugin.
 | 
        
           |  |  | 1361 |      *                     (e.g. 'view.php', 'db/install.xml').
 | 
        
           |  |  | 1362 |      * @param bool $include if true (default false), the file will be include_once-ed if found.
 | 
        
           |  |  | 1363 |      * @return array with plugin name as keys (e.g. 'forum', 'courselist') and the path
 | 
        
           |  |  | 1364 |      *               to the file relative to dirroot as value (e.g. "$CFG->dirroot/mod/forum/view.php").
 | 
        
           |  |  | 1365 |      */
 | 
        
           |  |  | 1366 |     public static function get_plugin_list_with_file($plugintype, $file, $include = false) {
 | 
        
           |  |  | 1367 |         global $CFG; // Necessary in case it is referenced by included PHP scripts.
 | 
        
           |  |  | 1368 |         $pluginfiles = [];
 | 
        
           |  |  | 1369 |   | 
        
           |  |  | 1370 |         if (isset(self::$filemap[$file])) {
 | 
        
           |  |  | 1371 |             // If the file was supposed to be mapped, then it should have been set in the array.
 | 
        
           |  |  | 1372 |             if (isset(self::$filemap[$file][$plugintype])) {
 | 
        
           |  |  | 1373 |                 $pluginfiles = self::$filemap[$file][$plugintype];
 | 
        
           |  |  | 1374 |             }
 | 
        
           |  |  | 1375 |         } else {
 | 
        
           |  |  | 1376 |             // Old-style search for non-cached files.
 | 
        
           |  |  | 1377 |             $plugins = self::get_plugin_list($plugintype);
 | 
        
           |  |  | 1378 |             foreach ($plugins as $plugin => $fulldir) {
 | 
        
           |  |  | 1379 |                 $path = $fulldir . '/' . $file;
 | 
        
           |  |  | 1380 |                 if (file_exists($path)) {
 | 
        
           |  |  | 1381 |                     $pluginfiles[$plugin] = $path;
 | 
        
           |  |  | 1382 |                 }
 | 
        
           |  |  | 1383 |             }
 | 
        
           |  |  | 1384 |         }
 | 
        
           |  |  | 1385 |   | 
        
           |  |  | 1386 |         if ($include) {
 | 
        
           |  |  | 1387 |             foreach ($pluginfiles as $path) {
 | 
        
           |  |  | 1388 |                 include_once($path);
 | 
        
           |  |  | 1389 |             }
 | 
        
           |  |  | 1390 |         }
 | 
        
           |  |  | 1391 |   | 
        
           |  |  | 1392 |         return $pluginfiles;
 | 
        
           |  |  | 1393 |     }
 | 
        
           |  |  | 1394 |   | 
        
           |  |  | 1395 |     /**
 | 
        
           |  |  | 1396 |      * Returns all classes in a component matching the provided namespace.
 | 
        
           |  |  | 1397 |      *
 | 
        
           |  |  | 1398 |      * It checks that the class exists.
 | 
        
           |  |  | 1399 |      *
 | 
        
           |  |  | 1400 |      * e.g. get_component_classes_in_namespace('mod_forum', 'event')
 | 
        
           |  |  | 1401 |      *
 | 
        
           |  |  | 1402 |      * @param string|null $component A valid moodle component (frankenstyle) or null if searching all components
 | 
        
           |  |  | 1403 |      * @param string $namespace Namespace from the component name or empty string if all $component classes.
 | 
        
           |  |  | 1404 |      * @return array The full class name as key and the class path as value, empty array if $component is `null`
 | 
        
           |  |  | 1405 |      * and $namespace is empty.
 | 
        
           |  |  | 1406 |      */
 | 
        
           |  |  | 1407 |     public static function get_component_classes_in_namespace($component = null, $namespace = '') {
 | 
        
           |  |  | 1408 |   | 
        
           |  |  | 1409 |         $classes = [];
 | 
        
           |  |  | 1410 |   | 
        
           |  |  | 1411 |         // Only look for components if a component name is set or a namespace is set.
 | 
        
           |  |  | 1412 |         if (isset($component) || !empty($namespace)) {
 | 
        
           |  |  | 1413 |             // If a component parameter value is set we only want to look in that component.
 | 
        
           |  |  | 1414 |             // Otherwise we want to check all components.
 | 
        
           |  |  | 1415 |             $component = (isset($component)) ? self::normalize_componentname($component) : '\w+';
 | 
        
           |  |  | 1416 |             if ($namespace) {
 | 
        
           |  |  | 1417 |                 // We will add them later.
 | 
        
           |  |  | 1418 |                 $namespace = trim($namespace, '\\');
 | 
        
           |  |  | 1419 |   | 
        
           |  |  | 1420 |                 // We need add double backslashes as it is how classes are stored into self::$classmap.
 | 
        
           |  |  | 1421 |                 $namespace = implode('\\\\', explode('\\', $namespace));
 | 
        
           |  |  | 1422 |                 $namespace = $namespace . '\\\\';
 | 
        
           |  |  | 1423 |             }
 | 
        
           |  |  | 1424 |             $regex = '|^' . $component . '\\\\' . $namespace . '|';
 | 
        
           |  |  | 1425 |             $it = new RegexIterator(new ArrayIterator(self::$classmap), $regex, RegexIterator::GET_MATCH, RegexIterator::USE_KEY);
 | 
        
           |  |  | 1426 |   | 
        
           |  |  | 1427 |             // We want to be sure that they exist.
 | 
        
           |  |  | 1428 |             foreach ($it as $classname => $classpath) {
 | 
        
           |  |  | 1429 |                 if (class_exists($classname)) {
 | 
        
           |  |  | 1430 |                     $classes[$classname] = $classpath;
 | 
        
           |  |  | 1431 |                 }
 | 
        
           |  |  | 1432 |             }
 | 
        
           |  |  | 1433 |         }
 | 
        
           |  |  | 1434 |   | 
        
           |  |  | 1435 |         return $classes;
 | 
        
           |  |  | 1436 |     }
 | 
        
           |  |  | 1437 |   | 
        
           |  |  | 1438 |     /**
 | 
        
           |  |  | 1439 |      * Returns the exact absolute path to plugin directory.
 | 
        
           |  |  | 1440 |      *
 | 
        
           |  |  | 1441 |      * @param string $plugintype type of plugin
 | 
        
           |  |  | 1442 |      * @param string $pluginname name of the plugin
 | 
        
           |  |  | 1443 |      * @return string full path to plugin directory; null if not found
 | 
        
           |  |  | 1444 |      */
 | 
        
           |  |  | 1445 |     public static function get_plugin_directory($plugintype, $pluginname) {
 | 
        
           |  |  | 1446 |         if (empty($pluginname)) {
 | 
        
           |  |  | 1447 |             // Invalid plugin name, sorry.
 | 
        
           |  |  | 1448 |             return null;
 | 
        
           |  |  | 1449 |         }
 | 
        
           |  |  | 1450 |   | 
        
           |  |  | 1451 |         self::init();
 | 
        
           |  |  | 1452 |   | 
        
           | 1441 | ariadna | 1453 |         if (!isset(self::$plugins[$plugintype][$pluginname]) && !isset(self::$deprecatedplugins[$plugintype][$pluginname])) {
 | 
        
           | 1 | efrain | 1454 |             return null;
 | 
        
           |  |  | 1455 |         }
 | 
        
           | 1441 | ariadna | 1456 |         return self::$plugins[$plugintype][$pluginname] ?? self::$deprecatedplugins[$plugintype][$pluginname];
 | 
        
           | 1 | efrain | 1457 |     }
 | 
        
           |  |  | 1458 |   | 
        
           |  |  | 1459 |     /**
 | 
        
           |  |  | 1460 |      * Returns the exact absolute path to plugin directory.
 | 
        
           |  |  | 1461 |      *
 | 
        
           |  |  | 1462 |      * @param string $subsystem type of core subsystem
 | 
        
           |  |  | 1463 |      * @return string full path to subsystem directory; null if not found
 | 
        
           |  |  | 1464 |      */
 | 
        
           |  |  | 1465 |     public static function get_subsystem_directory($subsystem) {
 | 
        
           |  |  | 1466 |         self::init();
 | 
        
           |  |  | 1467 |   | 
        
           |  |  | 1468 |         if (!isset(self::$subsystems[$subsystem])) {
 | 
        
           |  |  | 1469 |             return null;
 | 
        
           |  |  | 1470 |         }
 | 
        
           |  |  | 1471 |         return self::$subsystems[$subsystem];
 | 
        
           |  |  | 1472 |     }
 | 
        
           |  |  | 1473 |   | 
        
           |  |  | 1474 |     /**
 | 
        
           |  |  | 1475 |      * This method validates a plug name. It is much faster than calling clean_param.
 | 
        
           |  |  | 1476 |      *
 | 
        
           |  |  | 1477 |      * @param string $plugintype type of plugin
 | 
        
           |  |  | 1478 |      * @param string $pluginname a string that might be a plugin name.
 | 
        
           |  |  | 1479 |      * @return bool if this string is a valid plugin name.
 | 
        
           |  |  | 1480 |      */
 | 
        
           |  |  | 1481 |     public static function is_valid_plugin_name($plugintype, $pluginname) {
 | 
        
           |  |  | 1482 |         if ($plugintype === 'mod') {
 | 
        
           |  |  | 1483 |             // Modules must not have the same name as core subsystems.
 | 
        
           |  |  | 1484 |             if (!isset(self::$subsystems)) {
 | 
        
           |  |  | 1485 |                 // Watch out, this is called from init!
 | 
        
           |  |  | 1486 |                 self::init();
 | 
        
           |  |  | 1487 |             }
 | 
        
           |  |  | 1488 |             if (isset(self::$subsystems[$pluginname])) {
 | 
        
           |  |  | 1489 |                 return false;
 | 
        
           |  |  | 1490 |             }
 | 
        
           |  |  | 1491 |             // Modules MUST NOT have any underscores,
 | 
        
           |  |  | 1492 |             // component normalisation would break very badly otherwise!
 | 
        
           |  |  | 1493 |             return !is_null($pluginname) && (bool) preg_match('/^[a-z][a-z0-9]*$/', $pluginname);
 | 
        
           |  |  | 1494 |         } else {
 | 
        
           |  |  | 1495 |             return !is_null($pluginname) && (bool) preg_match('/^[a-z](?:[a-z0-9_](?!__))*[a-z0-9]+$/', $pluginname);
 | 
        
           |  |  | 1496 |         }
 | 
        
           |  |  | 1497 |     }
 | 
        
           |  |  | 1498 |   | 
        
           |  |  | 1499 |     /**
 | 
        
           |  |  | 1500 |      * Normalize the component name.
 | 
        
           |  |  | 1501 |      *
 | 
        
           |  |  | 1502 |      * Note: this does not verify the validity of the plugin or component.
 | 
        
           |  |  | 1503 |      *
 | 
        
           |  |  | 1504 |      * @param string $component
 | 
        
           |  |  | 1505 |      * @return string
 | 
        
           |  |  | 1506 |      */
 | 
        
           |  |  | 1507 |     public static function normalize_componentname($componentname) {
 | 
        
           |  |  | 1508 |         [$plugintype, $pluginname] = self::normalize_component($componentname);
 | 
        
           |  |  | 1509 |         if ($plugintype === 'core' && is_null($pluginname)) {
 | 
        
           |  |  | 1510 |             return $plugintype;
 | 
        
           |  |  | 1511 |         }
 | 
        
           |  |  | 1512 |         return $plugintype . '_' . $pluginname;
 | 
        
           |  |  | 1513 |     }
 | 
        
           |  |  | 1514 |   | 
        
           |  |  | 1515 |     /**
 | 
        
           |  |  | 1516 |      * Normalize the component name using the "frankenstyle" rules.
 | 
        
           |  |  | 1517 |      *
 | 
        
           |  |  | 1518 |      * Note: this does not verify the validity of plugin or type names.
 | 
        
           |  |  | 1519 |      *
 | 
        
           |  |  | 1520 |      * @param string $component
 | 
        
           |  |  | 1521 |      * @return array two-items list of [(string)type, (string|null)name]
 | 
        
           |  |  | 1522 |      */
 | 
        
           |  |  | 1523 |     public static function normalize_component($component) {
 | 
        
           |  |  | 1524 |         if ($component === 'moodle' || $component === 'core' || $component === '') {
 | 
        
           |  |  | 1525 |             return ['core', null];
 | 
        
           |  |  | 1526 |         }
 | 
        
           |  |  | 1527 |   | 
        
           |  |  | 1528 |         if (strpos($component, '_') === false) {
 | 
        
           |  |  | 1529 |             self::init();
 | 
        
           |  |  | 1530 |             if (array_key_exists($component, self::$subsystems)) {
 | 
        
           |  |  | 1531 |                 $type   = 'core';
 | 
        
           |  |  | 1532 |                 $plugin = $component;
 | 
        
           |  |  | 1533 |             } else {
 | 
        
           |  |  | 1534 |                 // Everything else without underscore is a module.
 | 
        
           |  |  | 1535 |                 $type   = 'mod';
 | 
        
           |  |  | 1536 |                 $plugin = $component;
 | 
        
           |  |  | 1537 |             }
 | 
        
           |  |  | 1538 |         } else {
 | 
        
           |  |  | 1539 |             [$type, $plugin] = explode('_', $component, 2);
 | 
        
           |  |  | 1540 |             if ($type === 'moodle') {
 | 
        
           |  |  | 1541 |                 $type = 'core';
 | 
        
           |  |  | 1542 |             }
 | 
        
           |  |  | 1543 |             // Any unknown type must be a subplugin.
 | 
        
           |  |  | 1544 |         }
 | 
        
           |  |  | 1545 |   | 
        
           |  |  | 1546 |         return [$type, $plugin];
 | 
        
           |  |  | 1547 |     }
 | 
        
           |  |  | 1548 |   | 
        
           |  |  | 1549 |     /**
 | 
        
           |  |  | 1550 |      * Fetch the component name from a Moodle PSR-like namespace.
 | 
        
           |  |  | 1551 |      *
 | 
        
           |  |  | 1552 |      * Note: Classnames in the flat underscore_class_name_format are not supported.
 | 
        
           |  |  | 1553 |      *
 | 
        
           |  |  | 1554 |      * @param string $classname
 | 
        
           |  |  | 1555 |      * @return null|string The component name, or null if a matching component was not found
 | 
        
           |  |  | 1556 |      */
 | 
        
           |  |  | 1557 |     public static function get_component_from_classname(string $classname): ?string {
 | 
        
           | 1441 | ariadna | 1558 |         $components = static::get_component_names(true, true);
 | 
        
           | 1 | efrain | 1559 |   | 
        
           |  |  | 1560 |         $classname = ltrim($classname, '\\');
 | 
        
           |  |  | 1561 |   | 
        
           |  |  | 1562 |         // Prefer PSR-4 classnames.
 | 
        
           |  |  | 1563 |         $parts = explode('\\', $classname);
 | 
        
           |  |  | 1564 |         if ($parts) {
 | 
        
           |  |  | 1565 |             $component = array_shift($parts);
 | 
        
           |  |  | 1566 |             if (array_search($component, $components) !== false) {
 | 
        
           |  |  | 1567 |                 return $component;
 | 
        
           |  |  | 1568 |             }
 | 
        
           |  |  | 1569 |         }
 | 
        
           |  |  | 1570 |   | 
        
           |  |  | 1571 |         // Note: Frankenstyle classnames are not supported as they lead to false positives, for example:
 | 
        
           |  |  | 1572 |         // \core_typo\example => \core instead of \core_typo because it does not exist
 | 
        
           |  |  | 1573 |         // Please *do not* add support for Frankenstyle classnames. They will break other things.
 | 
        
           |  |  | 1574 |   | 
        
           |  |  | 1575 |         return null;
 | 
        
           |  |  | 1576 |     }
 | 
        
           |  |  | 1577 |   | 
        
           |  |  | 1578 |     /**
 | 
        
           |  |  | 1579 |      * Return exact absolute path to a plugin directory.
 | 
        
           |  |  | 1580 |      *
 | 
        
           |  |  | 1581 |      * @param string $component name such as 'moodle', 'mod_forum'
 | 
        
           |  |  | 1582 |      * @return string full path to component directory; NULL if not found
 | 
        
           |  |  | 1583 |      */
 | 
        
           |  |  | 1584 |     public static function get_component_directory($component) {
 | 
        
           |  |  | 1585 |         global $CFG;
 | 
        
           |  |  | 1586 |   | 
        
           |  |  | 1587 |         [$type, $plugin] = self::normalize_component($component);
 | 
        
           |  |  | 1588 |   | 
        
           |  |  | 1589 |         if ($type === 'core') {
 | 
        
           |  |  | 1590 |             if ($plugin === null) {
 | 
        
           |  |  | 1591 |                 return $path = $CFG->libdir;
 | 
        
           |  |  | 1592 |             }
 | 
        
           |  |  | 1593 |             return self::get_subsystem_directory($plugin);
 | 
        
           |  |  | 1594 |         }
 | 
        
           |  |  | 1595 |   | 
        
           |  |  | 1596 |         return self::get_plugin_directory($type, $plugin);
 | 
        
           |  |  | 1597 |     }
 | 
        
           |  |  | 1598 |   | 
        
           |  |  | 1599 |     /**
 | 
        
           |  |  | 1600 |      * Returns list of plugin types that allow subplugins.
 | 
        
           |  |  | 1601 |      * @return array as (string)plugintype => (string)fulldir
 | 
        
           |  |  | 1602 |      */
 | 
        
           |  |  | 1603 |     public static function get_plugin_types_with_subplugins() {
 | 
        
           |  |  | 1604 |         self::init();
 | 
        
           |  |  | 1605 |   | 
        
           |  |  | 1606 |         $return = [];
 | 
        
           |  |  | 1607 |         foreach (self::$supportsubplugins as $type) {
 | 
        
           |  |  | 1608 |             $return[$type] = self::$plugintypes[$type];
 | 
        
           |  |  | 1609 |         }
 | 
        
           |  |  | 1610 |         return $return;
 | 
        
           |  |  | 1611 |     }
 | 
        
           |  |  | 1612 |   | 
        
           |  |  | 1613 |     /**
 | 
        
           |  |  | 1614 |      * Returns parent of this subplugin type.
 | 
        
           |  |  | 1615 |      *
 | 
        
           | 1441 | ariadna | 1616 |      * No filtering is done on deprecated/deleted subtypes. Calling code should check this if needed.
 | 
        
           |  |  | 1617 |      *
 | 
        
           | 1 | efrain | 1618 |      * @param string $type
 | 
        
           |  |  | 1619 |      * @return string parent component or null
 | 
        
           |  |  | 1620 |      */
 | 
        
           |  |  | 1621 |     public static function get_subtype_parent($type) {
 | 
        
           |  |  | 1622 |         self::init();
 | 
        
           |  |  | 1623 |   | 
        
           |  |  | 1624 |         if (isset(self::$parents[$type])) {
 | 
        
           |  |  | 1625 |             return self::$parents[$type];
 | 
        
           |  |  | 1626 |         }
 | 
        
           |  |  | 1627 |   | 
        
           |  |  | 1628 |         return null;
 | 
        
           |  |  | 1629 |     }
 | 
        
           |  |  | 1630 |   | 
        
           |  |  | 1631 |     /**
 | 
        
           | 1441 | ariadna | 1632 |      * Return all available subplugins of this component.
 | 
        
           | 1 | efrain | 1633 |      * @param string $component.
 | 
        
           |  |  | 1634 |      * @return array $subtype=>array($component, ..), null if no subtypes defined
 | 
        
           |  |  | 1635 |      */
 | 
        
           |  |  | 1636 |     public static function get_subplugins($component) {
 | 
        
           |  |  | 1637 |         self::init();
 | 
        
           |  |  | 1638 |   | 
        
           |  |  | 1639 |         if (isset(self::$subplugins[$component])) {
 | 
        
           |  |  | 1640 |             return self::$subplugins[$component];
 | 
        
           |  |  | 1641 |         }
 | 
        
           |  |  | 1642 |   | 
        
           |  |  | 1643 |         return null;
 | 
        
           |  |  | 1644 |     }
 | 
        
           |  |  | 1645 |   | 
        
           |  |  | 1646 |     /**
 | 
        
           | 1441 | ariadna | 1647 |      * Return all subplugins for the component, comprising all available subplugins plus any in deprecation.
 | 
        
           |  |  | 1648 |      *
 | 
        
           |  |  | 1649 |      * @param string $component
 | 
        
           |  |  | 1650 |      * @return array|null $subtype=>array($component, ..), null if no subtypes defined
 | 
        
           |  |  | 1651 |      */
 | 
        
           |  |  | 1652 |     public static function get_all_subplugins($component): ?array {
 | 
        
           |  |  | 1653 |         self::init();
 | 
        
           |  |  | 1654 |         $subplugins = array_merge(
 | 
        
           |  |  | 1655 |             self::$subplugins[$component] ?? [],
 | 
        
           |  |  | 1656 |             self::$deprecatedsubplugins[$component] ?? [],
 | 
        
           |  |  | 1657 |             self::$deletedsubplugins[$component] ?? [],
 | 
        
           |  |  | 1658 |         );
 | 
        
           |  |  | 1659 |         return $subplugins ?: null;
 | 
        
           |  |  | 1660 |     }
 | 
        
           |  |  | 1661 |   | 
        
           |  |  | 1662 |     /**
 | 
        
           | 1 | efrain | 1663 |      * Returns hash of all versions including core and all plugins.
 | 
        
           |  |  | 1664 |      *
 | 
        
           |  |  | 1665 |      * This is relatively slow and not fully cached, use with care!
 | 
        
           |  |  | 1666 |      *
 | 
        
           |  |  | 1667 |      * @return string sha1 hash
 | 
        
           |  |  | 1668 |      */
 | 
        
           |  |  | 1669 |     public static function get_all_versions_hash() {
 | 
        
           |  |  | 1670 |         return sha1(serialize(self::get_all_versions()));
 | 
        
           |  |  | 1671 |     }
 | 
        
           |  |  | 1672 |   | 
        
           |  |  | 1673 |     /**
 | 
        
           |  |  | 1674 |      * Returns hash of all versions including core and all plugins.
 | 
        
           |  |  | 1675 |      *
 | 
        
           |  |  | 1676 |      * This is relatively slow and not fully cached, use with care!
 | 
        
           |  |  | 1677 |      *
 | 
        
           |  |  | 1678 |      * @return array as (string)plugintype_pluginname => (int)version
 | 
        
           |  |  | 1679 |      */
 | 
        
           |  |  | 1680 |     public static function get_all_versions(): array {
 | 
        
           |  |  | 1681 |         global $CFG;
 | 
        
           |  |  | 1682 |   | 
        
           |  |  | 1683 |         self::init();
 | 
        
           |  |  | 1684 |   | 
        
           |  |  | 1685 |         $versions = [];
 | 
        
           |  |  | 1686 |   | 
        
           |  |  | 1687 |         // Main version first.
 | 
        
           |  |  | 1688 |         $versions['core'] = self::fetch_core_version();
 | 
        
           |  |  | 1689 |   | 
        
           |  |  | 1690 |         // The problem here is tha the component cache might be stable,
 | 
        
           |  |  | 1691 |         // we want this to work also on frontpage without resetting the component cache.
 | 
        
           |  |  | 1692 |         $usecache = false;
 | 
        
           |  |  | 1693 |         if (CACHE_DISABLE_ALL || (defined('IGNORE_COMPONENT_CACHE') && IGNORE_COMPONENT_CACHE)) {
 | 
        
           |  |  | 1694 |             $usecache = true;
 | 
        
           |  |  | 1695 |         }
 | 
        
           |  |  | 1696 |   | 
        
           |  |  | 1697 |         // Now all plugins.
 | 
        
           |  |  | 1698 |         $plugintypes = self::get_plugin_types();
 | 
        
           |  |  | 1699 |         foreach ($plugintypes as $type => $typedir) {
 | 
        
           |  |  | 1700 |             if ($usecache) {
 | 
        
           |  |  | 1701 |                 $plugs = self::get_plugin_list($type);
 | 
        
           |  |  | 1702 |             } else {
 | 
        
           |  |  | 1703 |                 $plugs = self::fetch_plugins($type, $typedir);
 | 
        
           |  |  | 1704 |             }
 | 
        
           |  |  | 1705 |             foreach ($plugs as $plug => $fullplug) {
 | 
        
           |  |  | 1706 |                 $plugin = new stdClass();
 | 
        
           |  |  | 1707 |                 $plugin->version = null;
 | 
        
           |  |  | 1708 |                 $module = $plugin;
 | 
        
           |  |  | 1709 |                 include($fullplug . '/version.php');
 | 
        
           |  |  | 1710 |                 $versions[$type . '_' . $plug] = $plugin->version;
 | 
        
           |  |  | 1711 |             }
 | 
        
           |  |  | 1712 |         }
 | 
        
           |  |  | 1713 |   | 
        
           |  |  | 1714 |         return $versions;
 | 
        
           |  |  | 1715 |     }
 | 
        
           |  |  | 1716 |   | 
        
           |  |  | 1717 |     /**
 | 
        
           |  |  | 1718 |      * Returns hash of all core + plugin /db/ directories.
 | 
        
           |  |  | 1719 |      *
 | 
        
           |  |  | 1720 |      * This is relatively slow and not fully cached, use with care!
 | 
        
           |  |  | 1721 |      *
 | 
        
           |  |  | 1722 |      * @param array|null $components optional component directory => hash array to use. Only used in PHPUnit.
 | 
        
           |  |  | 1723 |      * @return string sha1 hash.
 | 
        
           |  |  | 1724 |      */
 | 
        
           |  |  | 1725 |     public static function get_all_component_hash(?array $components = null): string {
 | 
        
           |  |  | 1726 |         $tohash = $components ?? self::get_all_directory_hashes();
 | 
        
           |  |  | 1727 |         return sha1(serialize($tohash));
 | 
        
           |  |  | 1728 |     }
 | 
        
           |  |  | 1729 |   | 
        
           |  |  | 1730 |     /**
 | 
        
           |  |  | 1731 |      * Get the hashes of all core + plugin /db/ directories.
 | 
        
           |  |  | 1732 |      *
 | 
        
           |  |  | 1733 |      * @param array|null $directories optional component directory array to hash. Only used in PHPUnit.
 | 
        
           |  |  | 1734 |      * @return array of directory => hash.
 | 
        
           |  |  | 1735 |      */
 | 
        
           |  |  | 1736 |     public static function get_all_directory_hashes(?array $directories = null): array {
 | 
        
           |  |  | 1737 |         global $CFG;
 | 
        
           |  |  | 1738 |   | 
        
           |  |  | 1739 |         self::init();
 | 
        
           |  |  | 1740 |   | 
        
           |  |  | 1741 |         // The problem here is that the component cache might be stale,
 | 
        
           |  |  | 1742 |         // we want this to work also on frontpage without resetting the component cache.
 | 
        
           |  |  | 1743 |         $usecache = false;
 | 
        
           |  |  | 1744 |         if (CACHE_DISABLE_ALL || (defined('IGNORE_COMPONENT_CACHE') && IGNORE_COMPONENT_CACHE)) {
 | 
        
           |  |  | 1745 |             $usecache = true;
 | 
        
           |  |  | 1746 |         }
 | 
        
           |  |  | 1747 |   | 
        
           |  |  | 1748 |         if (empty($directories)) {
 | 
        
           |  |  | 1749 |             $directories = [
 | 
        
           |  |  | 1750 |                 $CFG->libdir . '/db',
 | 
        
           |  |  | 1751 |             ];
 | 
        
           |  |  | 1752 |             // For all components, get the directory of the /db directory.
 | 
        
           |  |  | 1753 |             $plugintypes = self::get_plugin_types();
 | 
        
           |  |  | 1754 |             foreach ($plugintypes as $type => $typedir) {
 | 
        
           |  |  | 1755 |                 if ($usecache) {
 | 
        
           |  |  | 1756 |                     $plugs = self::get_plugin_list($type);
 | 
        
           |  |  | 1757 |                 } else {
 | 
        
           |  |  | 1758 |                     $plugs = self::fetch_plugins($type, $typedir);
 | 
        
           |  |  | 1759 |                 }
 | 
        
           |  |  | 1760 |                 foreach ($plugs as $plug) {
 | 
        
           |  |  | 1761 |                     $directories[] = $plug . '/db';
 | 
        
           |  |  | 1762 |                 }
 | 
        
           |  |  | 1763 |             }
 | 
        
           |  |  | 1764 |         }
 | 
        
           |  |  | 1765 |   | 
        
           |  |  | 1766 |         // Create a mapping of directories to their hash.
 | 
        
           |  |  | 1767 |         $hashes = [];
 | 
        
           |  |  | 1768 |         foreach ($directories as $directory) {
 | 
        
           |  |  | 1769 |             if (!is_dir($directory)) {
 | 
        
           |  |  | 1770 |                 // Just hash an empty string as the non-existing representation.
 | 
        
           |  |  | 1771 |                 $hashes[$directory] = sha1('');
 | 
        
           |  |  | 1772 |                 continue;
 | 
        
           |  |  | 1773 |             }
 | 
        
           |  |  | 1774 |   | 
        
           |  |  | 1775 |             $scan = scandir($directory);
 | 
        
           |  |  | 1776 |             if ($scan) {
 | 
        
           |  |  | 1777 |                 sort($scan);
 | 
        
           |  |  | 1778 |             }
 | 
        
           |  |  | 1779 |             $scanhashes = [];
 | 
        
           |  |  | 1780 |             foreach ($scan as $file) {
 | 
        
           |  |  | 1781 |                 $file = $directory . '/' . $file;
 | 
        
           |  |  | 1782 |                 // Moodle ignores directories.
 | 
        
           |  |  | 1783 |                 if (!is_dir($file)) {
 | 
        
           |  |  | 1784 |                     $scanhashes[] = hash_file('sha1', $file);
 | 
        
           |  |  | 1785 |                 }
 | 
        
           |  |  | 1786 |             }
 | 
        
           |  |  | 1787 |             // Finally we can serialize and hash the whole dir.
 | 
        
           |  |  | 1788 |             $hashes[$directory] = sha1(serialize($scanhashes));
 | 
        
           |  |  | 1789 |         }
 | 
        
           |  |  | 1790 |   | 
        
           |  |  | 1791 |         return $hashes;
 | 
        
           |  |  | 1792 |     }
 | 
        
           |  |  | 1793 |   | 
        
           |  |  | 1794 |     /**
 | 
        
           |  |  | 1795 |      * Invalidate opcode cache for given file, this is intended for
 | 
        
           |  |  | 1796 |      * php files that are stored in dataroot.
 | 
        
           |  |  | 1797 |      *
 | 
        
           |  |  | 1798 |      * Note: we need it here because this class must be self-contained.
 | 
        
           |  |  | 1799 |      *
 | 
        
           |  |  | 1800 |      * @param string $file
 | 
        
           |  |  | 1801 |      */
 | 
        
           |  |  | 1802 |     public static function invalidate_opcode_php_cache($file) {
 | 
        
           |  |  | 1803 |         if (function_exists('opcache_invalidate')) {
 | 
        
           |  |  | 1804 |             if (!file_exists($file)) {
 | 
        
           |  |  | 1805 |                 return;
 | 
        
           |  |  | 1806 |             }
 | 
        
           |  |  | 1807 |             opcache_invalidate($file, true);
 | 
        
           |  |  | 1808 |         }
 | 
        
           |  |  | 1809 |     }
 | 
        
           |  |  | 1810 |   | 
        
           |  |  | 1811 |     /**
 | 
        
           |  |  | 1812 |      * Return true if subsystemname is core subsystem.
 | 
        
           |  |  | 1813 |      *
 | 
        
           |  |  | 1814 |      * @param string $subsystemname name of the subsystem.
 | 
        
           |  |  | 1815 |      * @return bool true if core subsystem.
 | 
        
           |  |  | 1816 |      */
 | 
        
           |  |  | 1817 |     public static function is_core_subsystem($subsystemname) {
 | 
        
           |  |  | 1818 |         return isset(self::$subsystems[$subsystemname]);
 | 
        
           |  |  | 1819 |     }
 | 
        
           |  |  | 1820 |   | 
        
           |  |  | 1821 |     /**
 | 
        
           |  |  | 1822 |      * Return true if apiname is a core API.
 | 
        
           |  |  | 1823 |      *
 | 
        
           |  |  | 1824 |      * @param string $apiname name of the API.
 | 
        
           |  |  | 1825 |      * @return bool true if core API.
 | 
        
           |  |  | 1826 |      */
 | 
        
           |  |  | 1827 |     public static function is_core_api($apiname) {
 | 
        
           |  |  | 1828 |         return isset(self::$apis[$apiname]);
 | 
        
           |  |  | 1829 |     }
 | 
        
           |  |  | 1830 |   | 
        
           |  |  | 1831 |     /**
 | 
        
           |  |  | 1832 |      * Records all class renames that have been made to facilitate autoloading.
 | 
        
           |  |  | 1833 |      */
 | 
        
           |  |  | 1834 |     protected static function fill_classmap_renames_cache() {
 | 
        
           |  |  | 1835 |         global $CFG;
 | 
        
           |  |  | 1836 |   | 
        
           |  |  | 1837 |         self::$classmaprenames = [];
 | 
        
           |  |  | 1838 |   | 
        
           |  |  | 1839 |         self::load_renamed_classes("$CFG->dirroot/lib/");
 | 
        
           |  |  | 1840 |   | 
        
           |  |  | 1841 |         foreach (self::$subsystems as $subsystem => $fulldir) {
 | 
        
           |  |  | 1842 |             self::load_renamed_classes($fulldir);
 | 
        
           |  |  | 1843 |         }
 | 
        
           |  |  | 1844 |   | 
        
           |  |  | 1845 |         foreach (self::$plugins as $plugintype => $plugins) {
 | 
        
           |  |  | 1846 |             foreach ($plugins as $pluginname => $fulldir) {
 | 
        
           |  |  | 1847 |                 self::load_renamed_classes($fulldir);
 | 
        
           |  |  | 1848 |             }
 | 
        
           |  |  | 1849 |         }
 | 
        
           |  |  | 1850 |     }
 | 
        
           |  |  | 1851 |   | 
        
           |  |  | 1852 |     /**
 | 
        
           |  |  | 1853 |      * Loads the db/renamedclasses.php file from the given directory.
 | 
        
           |  |  | 1854 |      *
 | 
        
           |  |  | 1855 |      * The renamedclasses.php should contain a key => value array ($renamedclasses) where the key is old class name,
 | 
        
           |  |  | 1856 |      * and the value is the new class name.
 | 
        
           |  |  | 1857 |      * It is only included when we are populating the component cache. After that is not needed.
 | 
        
           |  |  | 1858 |      *
 | 
        
           |  |  | 1859 |      * @param string|null $fulldir The directory to the renamed classes.
 | 
        
           |  |  | 1860 |      */
 | 
        
           |  |  | 1861 |     protected static function load_renamed_classes(?string $fulldir) {
 | 
        
           |  |  | 1862 |         if (is_null($fulldir)) {
 | 
        
           |  |  | 1863 |             return;
 | 
        
           |  |  | 1864 |         }
 | 
        
           |  |  | 1865 |   | 
        
           |  |  | 1866 |         $file = $fulldir . '/db/renamedclasses.php';
 | 
        
           |  |  | 1867 |         if (is_readable($file)) {
 | 
        
           |  |  | 1868 |             $renamedclasses = null;
 | 
        
           |  |  | 1869 |             require($file);
 | 
        
           |  |  | 1870 |             if (is_array($renamedclasses)) {
 | 
        
           |  |  | 1871 |                 foreach ($renamedclasses as $oldclass => $newclass) {
 | 
        
           |  |  | 1872 |                     self::$classmaprenames[(string)$oldclass] = (string)$newclass;
 | 
        
           |  |  | 1873 |                 }
 | 
        
           |  |  | 1874 |             }
 | 
        
           |  |  | 1875 |         }
 | 
        
           |  |  | 1876 |     }
 | 
        
           |  |  | 1877 |   | 
        
           |  |  | 1878 |     /**
 | 
        
           | 1441 | ariadna | 1879 |      * Load legacy classes based upon the db/legacyclasses.php file.
 | 
        
           |  |  | 1880 |      *
 | 
        
           |  |  | 1881 |      * The legacyclasses.php should contain a key => value array ($legacyclasses) where the key is the class name,
 | 
        
           |  |  | 1882 |      * and the value is the path to the class file within the relative ../classes/ directory.
 | 
        
           |  |  | 1883 |      *
 | 
        
           |  |  | 1884 |      * @param string|null $fulldir The directory to the legacy classes.
 | 
        
           |  |  | 1885 |      * @param bool $allowsubsystems Whether to allow the specification of alternative subsystems for this path.
 | 
        
           |  |  | 1886 |      */
 | 
        
           |  |  | 1887 |     protected static function load_legacy_classes(
 | 
        
           |  |  | 1888 |         ?string $fulldir,
 | 
        
           |  |  | 1889 |         bool $allowsubsystems = false,
 | 
        
           |  |  | 1890 |     ): void {
 | 
        
           |  |  | 1891 |         if (is_null($fulldir)) {
 | 
        
           |  |  | 1892 |             return;
 | 
        
           |  |  | 1893 |         }
 | 
        
           |  |  | 1894 |   | 
        
           |  |  | 1895 |         $file = $fulldir . '/db/legacyclasses.php';
 | 
        
           |  |  | 1896 |         if (is_readable($file)) {
 | 
        
           |  |  | 1897 |             $legacyclasses = null;
 | 
        
           |  |  | 1898 |             require($file);
 | 
        
           |  |  | 1899 |             if (is_array($legacyclasses)) {
 | 
        
           |  |  | 1900 |                 foreach ($legacyclasses as $classname => $path) {
 | 
        
           |  |  | 1901 |                     if (is_array($path)) {
 | 
        
           |  |  | 1902 |                         if (!$allowsubsystems) {
 | 
        
           |  |  | 1903 |                             throw new Exception(
 | 
        
           |  |  | 1904 |                                 "Invalid legacy classes path entry for {$classname}. " .
 | 
        
           |  |  | 1905 |                                     "Only files within the component can be specified.",
 | 
        
           |  |  | 1906 |                             );
 | 
        
           |  |  | 1907 |                         }
 | 
        
           |  |  | 1908 |                         if (count($path) !== 2) {
 | 
        
           |  |  | 1909 |                             throw new Exception(
 | 
        
           |  |  | 1910 |                                 "Invalid legacy classes path entry for {$classname}. " .
 | 
        
           |  |  | 1911 |                                     "Entries must be in the format [subsystem, path].",
 | 
        
           |  |  | 1912 |                             );
 | 
        
           |  |  | 1913 |                         }
 | 
        
           |  |  | 1914 |                         [$subsystem, $path] = $path;
 | 
        
           |  |  | 1915 |                         $subsystem = substr($subsystem, 5);
 | 
        
           |  |  | 1916 |                         if (!array_key_exists($subsystem, self::$subsystems)) {
 | 
        
           |  |  | 1917 |                             throw new Exception(
 | 
        
           |  |  | 1918 |                                 "Unknown subsystem '{$subsystem}' for legacy classes entry of '{$classname}'",
 | 
        
           |  |  | 1919 |                             );
 | 
        
           |  |  | 1920 |                         }
 | 
        
           |  |  | 1921 |   | 
        
           |  |  | 1922 |                         $subsystemfulldir = self::$subsystems[$subsystem];
 | 
        
           |  |  | 1923 |                         self::$classmap[$classname] = "{$subsystemfulldir}/classes/{$path}";
 | 
        
           |  |  | 1924 |                     } else {
 | 
        
           |  |  | 1925 |                         self::$classmap[$classname] = "{$fulldir}/classes/{$path}";
 | 
        
           |  |  | 1926 |                     }
 | 
        
           |  |  | 1927 |                 }
 | 
        
           |  |  | 1928 |             }
 | 
        
           |  |  | 1929 |         }
 | 
        
           |  |  | 1930 |     }
 | 
        
           |  |  | 1931 |   | 
        
           |  |  | 1932 |     /**
 | 
        
           | 1 | efrain | 1933 |      * Returns a list of frankenstyle component names and their paths, for all components (plugins and subsystems).
 | 
        
           |  |  | 1934 |      *
 | 
        
           |  |  | 1935 |      * E.g.
 | 
        
           |  |  | 1936 |      *  [
 | 
        
           |  |  | 1937 |      *      'mod' => [
 | 
        
           |  |  | 1938 |      *          'mod_forum' => FORUM_PLUGIN_PATH,
 | 
        
           |  |  | 1939 |      *          ...
 | 
        
           |  |  | 1940 |      *      ],
 | 
        
           |  |  | 1941 |      *      ...
 | 
        
           |  |  | 1942 |      *      'core' => [
 | 
        
           |  |  | 1943 |      *          'core_comment' => COMMENT_SUBSYSTEM_PATH,
 | 
        
           |  |  | 1944 |      *          ...
 | 
        
           |  |  | 1945 |      *      ]
 | 
        
           |  |  | 1946 |      * ]
 | 
        
           |  |  | 1947 |      *
 | 
        
           |  |  | 1948 |      * @return array an associative array of components and their corresponding paths.
 | 
        
           |  |  | 1949 |      */
 | 
        
           |  |  | 1950 |     public static function get_component_list(): array {
 | 
        
           |  |  | 1951 |         $components = [];
 | 
        
           |  |  | 1952 |         // Get all plugins.
 | 
        
           |  |  | 1953 |         foreach (self::get_plugin_types() as $plugintype => $typedir) {
 | 
        
           |  |  | 1954 |             $components[$plugintype] = [];
 | 
        
           |  |  | 1955 |             foreach (self::get_plugin_list($plugintype) as $pluginname => $plugindir) {
 | 
        
           |  |  | 1956 |                 $components[$plugintype][$plugintype . '_' . $pluginname] = $plugindir;
 | 
        
           |  |  | 1957 |             }
 | 
        
           |  |  | 1958 |         }
 | 
        
           |  |  | 1959 |         // Get all subsystems.
 | 
        
           |  |  | 1960 |         foreach (self::get_core_subsystems() as $subsystemname => $subsystempath) {
 | 
        
           |  |  | 1961 |             $components['core']['core_' . $subsystemname] = $subsystempath;
 | 
        
           |  |  | 1962 |         }
 | 
        
           |  |  | 1963 |         return $components;
 | 
        
           |  |  | 1964 |     }
 | 
        
           |  |  | 1965 |   | 
        
           |  |  | 1966 |     /**
 | 
        
           |  |  | 1967 |      * Returns a list of frankenstyle component names, including all plugins, subplugins, and subsystems.
 | 
        
           |  |  | 1968 |      *
 | 
        
           |  |  | 1969 |      * Note: By default the 'core' subsystem is not included.
 | 
        
           |  |  | 1970 |      *
 | 
        
           |  |  | 1971 |      * @param bool $includecore Whether to include the 'core' subsystem
 | 
        
           | 1441 | ariadna | 1972 |      * @param bool $includedeprecated Whether to include deprecated components
 | 
        
           | 1 | efrain | 1973 |      * @return string[] the list of frankenstyle component names.
 | 
        
           |  |  | 1974 |      */
 | 
        
           |  |  | 1975 |     public static function get_component_names(
 | 
        
           |  |  | 1976 |         bool $includecore = false,
 | 
        
           | 1441 | ariadna | 1977 |         bool $includedeprecated = false
 | 
        
           | 1 | efrain | 1978 |     ): array {
 | 
        
           |  |  | 1979 |         $componentnames = [];
 | 
        
           |  |  | 1980 |         // Get all plugins.
 | 
        
           |  |  | 1981 |         foreach (self::get_plugin_types() as $plugintype => $typedir) {
 | 
        
           |  |  | 1982 |             foreach (self::get_plugin_list($plugintype) as $pluginname => $plugindir) {
 | 
        
           |  |  | 1983 |                 $componentnames[] = $plugintype . '_' . $pluginname;
 | 
        
           |  |  | 1984 |             }
 | 
        
           |  |  | 1985 |         }
 | 
        
           |  |  | 1986 |         // Get all subsystems.
 | 
        
           |  |  | 1987 |         foreach (self::get_core_subsystems() as $subsystemname => $subsystempath) {
 | 
        
           |  |  | 1988 |             $componentnames[] = 'core_' . $subsystemname;
 | 
        
           |  |  | 1989 |         }
 | 
        
           |  |  | 1990 |   | 
        
           |  |  | 1991 |         if ($includecore) {
 | 
        
           |  |  | 1992 |             $componentnames[] = 'core';
 | 
        
           |  |  | 1993 |         }
 | 
        
           |  |  | 1994 |   | 
        
           | 1441 | ariadna | 1995 |         if ($includedeprecated) {
 | 
        
           |  |  | 1996 |             foreach (self::get_deprecated_plugin_types() as $plugintype => $typedir) {
 | 
        
           |  |  | 1997 |                 foreach (self::get_deprecated_plugin_list($plugintype) as $pluginname => $plugindir) {
 | 
        
           |  |  | 1998 |                     $componentnames[] = $plugintype . '_' . $pluginname;
 | 
        
           |  |  | 1999 |                 }
 | 
        
           |  |  | 2000 |             }
 | 
        
           |  |  | 2001 |         }
 | 
        
           |  |  | 2002 |   | 
        
           | 1 | efrain | 2003 |         return $componentnames;
 | 
        
           |  |  | 2004 |     }
 | 
        
           |  |  | 2005 |   | 
        
           |  |  | 2006 |     /**
 | 
        
           |  |  | 2007 |      * Returns the list of available API names.
 | 
        
           |  |  | 2008 |      *
 | 
        
           |  |  | 2009 |      * @return string[] the list of available API names.
 | 
        
           |  |  | 2010 |      */
 | 
        
           |  |  | 2011 |     public static function get_core_api_names(): array {
 | 
        
           |  |  | 2012 |         return array_keys(self::get_core_apis());
 | 
        
           |  |  | 2013 |     }
 | 
        
           |  |  | 2014 |   | 
        
           |  |  | 2015 |     /**
 | 
        
           |  |  | 2016 |      * Checks for the presence of monologo icons within a plugin.
 | 
        
           |  |  | 2017 |      *
 | 
        
           |  |  | 2018 |      * Only checks monologo icons in PNG and SVG formats as they are
 | 
        
           |  |  | 2019 |      * formats that can have transparent background.
 | 
        
           |  |  | 2020 |      *
 | 
        
           |  |  | 2021 |      * @param string $plugintype The plugin type.
 | 
        
           |  |  | 2022 |      * @param string $pluginname The plugin name.
 | 
        
           |  |  | 2023 |      * @return bool True if the plugin has a monologo icon
 | 
        
           |  |  | 2024 |      */
 | 
        
           |  |  | 2025 |     public static function has_monologo_icon(string $plugintype, string $pluginname): bool {
 | 
        
           | 1441 | ariadna | 2026 |         global $PAGE;
 | 
        
           | 1 | efrain | 2027 |         $plugindir = self::get_plugin_directory($plugintype, $pluginname);
 | 
        
           |  |  | 2028 |         if ($plugindir === null) {
 | 
        
           |  |  | 2029 |             return false;
 | 
        
           |  |  | 2030 |         }
 | 
        
           | 1441 | ariadna | 2031 |         $theme = theme_config::load($PAGE->theme->name);
 | 
        
           |  |  | 2032 |         $component = self::normalize_componentname("{$plugintype}_{$pluginname}");
 | 
        
           |  |  | 2033 |         $hassvgmonologo = $theme->resolve_image_location('monologo', $component, true) !== null;
 | 
        
           |  |  | 2034 |         $haspngmonologo = $theme->resolve_image_location('monologo', $component) !== null;
 | 
        
           |  |  | 2035 |         return $haspngmonologo || $hassvgmonologo;
 | 
        
           | 1 | efrain | 2036 |     }
 | 
        
           |  |  | 2037 | }
 | 
        
           | 1441 | ariadna | 2038 |   | 
        
           |  |  | 2039 | // Alias this class to the old name.
 | 
        
           |  |  | 2040 | // This should be kept here because we use this class in external tooling.
 | 
        
           |  |  | 2041 | class_alias(component::class, \core_component::class);
 |