3 use Composer\Semver\Semver;
6 use Wikimedia\ScopedCallback;
59 'SkinLessImportPaths',
147 if ( self::$instance ===
null ) {
148 self::$instance =
new self();
171 $this->checkDev = $check;
172 $this->invalidateProcessCache();
183 $this->loadTestClassesAndNamespaces = $load;
193 if ( $mtime ===
false ) {
195 $mtime = @filemtime(
$path );
197 if ( $mtime ===
false ) {
198 $err = error_get_last();
199 throw new Exception(
"Unable to open file $path: {$err['message']}" );
203 $this->queued[
$path] = $mtime;
204 $this->invalidateProcessCache();
208 if ( !$this->cache ) {
219 return $cache->makeGlobalKey(
220 "registration-$component",
221 $this->getVaryHash(),
232 if ( $this->varyHash ===
null ) {
236 'registration' => self::CACHE_VERSION,
238 'abilities' => $this->getAbilities(),
239 'checkDev' => $this->checkDev,
240 'queue' => $this->queued,
242 $this->varyHash = md5( json_encode( $vary ) );
244 return $this->varyHash;
251 $this->varyHash =
null;
252 $this->lazyAttributes = [];
260 if ( !$this->queued ) {
264 if ( $this->finished ) {
266 "The following paths tried to load late: "
267 . implode(
', ', array_keys( $this->queued ) )
271 $cache = $this->getCache();
273 $key = $this->makeCacheKey(
$cache,
'main' );
274 $data =
$cache->get( $key );
276 $data = $this->readFromQueue( $this->queued );
277 $this->saveToCache(
$cache, $data );
279 $this->exportExtractedData( $data );
296 foreach ( self::LAZY_LOADED_ATTRIBUTES as $attrib ) {
297 if ( isset( $data[
'attributes'][$attrib] ) ) {
298 $lazy[$attrib] = $data[
'attributes'][$attrib];
299 unset( $data[
'attributes'][$attrib] );
302 $mainKey = $this->makeCacheKey(
$cache,
'main' );
303 $cache->set( $mainKey, $data, self::CACHE_EXPIRY );
304 foreach ( $lazy as $attrib => $value ) {
306 $this->makeCacheKey(
$cache,
'lazy-attrib', $attrib ),
320 return $this->queued;
329 $this->invalidateProcessCache();
338 $this->finished =
true;
347 'shell' => !Shell::isDisabled(),
365 PHP_MAJOR_VERSION .
'.' . PHP_MINOR_VERSION .
'.' . PHP_RELEASE_VERSION,
366 get_loaded_extensions(),
367 $this->getAbilities(),
384 $versionChecker = $this->buildVersionChecker();
385 $extDependencies = [];
388 $json = file_get_contents(
$path );
389 if ( $json ===
false ) {
390 throw new Exception(
"Unable to read $path, does it exist?" );
392 $info = json_decode( $json,
true );
393 if ( !is_array( $info ) ) {
394 throw new Exception(
"$path is not a valid JSON file." );
397 if ( !isset( $info[
'manifest_version'] ) ) {
399 "{$info['name']}'s extension.json or skin.json does not have manifest_version, " .
400 'this is deprecated since MediaWiki 1.29',
405 $info[
'manifest_version'] = 1;
407 $version = $info[
'manifest_version'];
408 if ( $version < self::OLDEST_MANIFEST_VERSION || $version > self::MANIFEST_VERSION ) {
409 throw new Exception(
"$path: unsupported manifest_version: {$version}" );
413 $requires = $processor->getRequirements( $info, $this->checkDev );
416 if ( is_array( $requires ) && $requires && isset( $info[
'name'] ) ) {
417 $extDependencies[$info[
'name']] = $requires;
421 $processor->extractInfo(
$path, $info, $version );
423 $data = $processor->getExtractedInfo( $this->loadTestClassesAndNamespaces );
424 $data[
'warnings'] = $warnings;
427 $incompatible = $versionChecker
428 ->setLoadedExtensionsAndSkins( $data[
'credits'] )
429 ->checkArray( $extDependencies );
431 if ( $incompatible ) {
439 foreach ( $info[
'globals'] as $key => $val ) {
442 if ( is_array( $val ) && isset( $val[self::MERGE_STRATEGY] ) ) {
443 $mergeStrategy = $val[self::MERGE_STRATEGY];
444 unset( $val[self::MERGE_STRATEGY] );
446 $mergeStrategy =
'array_merge';
449 if ( $mergeStrategy ===
'provide_default' ) {
450 if ( !array_key_exists( $key, $GLOBALS ) ) {
451 $GLOBALS[$key] = $val;
458 if ( !array_key_exists( $key, $GLOBALS ) || ( is_array( $GLOBALS[$key] ) && !$GLOBALS[$key] ) ) {
459 $GLOBALS[$key] = $val;
463 if ( !is_array( $GLOBALS[$key] ) || !is_array( $val ) ) {
468 switch ( $mergeStrategy ) {
469 case 'array_merge_recursive':
470 $GLOBALS[$key] = array_merge_recursive( $GLOBALS[$key], $val );
472 case 'array_replace_recursive':
473 $GLOBALS[$key] = array_replace_recursive( $val, $GLOBALS[$key] );
475 case 'array_plus_2d':
479 $GLOBALS[$key] += $val;
482 $GLOBALS[$key] = array_merge( $val, $GLOBALS[$key] );
485 throw new UnexpectedValueException(
"Unknown merge strategy '$mergeStrategy'" );
489 if ( isset( $info[
'autoloaderNS'] ) ) {
490 AutoLoader::registerNamespaces( $info[
'autoloaderNS'] );
493 if ( isset( $info[
'autoloaderClasses'] ) ) {
494 AutoLoader::registerClasses( $info[
'autoloaderClasses'] );
497 foreach ( $info[
'defines'] as $name => $val ) {
498 if ( !defined( $name ) ) {
499 define( $name, $val );
500 } elseif ( constant( $name ) !== $val ) {
501 throw new UnexpectedValueException(
502 "$name cannot be re-defined with $val it has already been set with " . constant( $name )
507 if ( isset( $info[
'autoloaderPaths'] ) ) {
508 AutoLoader::loadFiles( $info[
'autoloaderPaths'] );
511 $this->loaded += $info[
'credits'];
512 if ( $info[
'attributes'] ) {
513 if ( !$this->attributes ) {
514 $this->attributes = $info[
'attributes'];
516 $this->attributes = array_merge_recursive( $this->attributes, $info[
'attributes'] );
520 foreach ( $info[
'callbacks'] as $name => $cb ) {
521 if ( !is_callable( $cb ) ) {
522 if ( is_array( $cb ) ) {
523 $cb =
'[ ' . implode(
', ', $cb ) .
' ]';
525 throw new UnexpectedValueException(
"callback '$cb' is not callable" );
527 $cb( $info[
'credits'][$name] );
539 public function isLoaded( $name, $constraint =
'*' ) {
540 $isLoaded = isset( $this->loaded[$name] );
541 if ( $constraint ===
'*' || !$isLoaded ) {
545 if ( !isset( $this->loaded[$name][
'version'] ) ) {
546 $msg =
"{$name} does not expose its version, but an extension or a skin"
547 .
" requires: {$constraint}.";
548 throw new LogicException( $msg );
551 return Semver::satisfies( $this->loaded[$name][
'version'], $constraint );
559 if ( isset( $this->testAttributes[$name] ) ) {
560 return $this->testAttributes[$name];
563 if ( in_array( $name, self::LAZY_LOADED_ATTRIBUTES,
true ) ) {
564 return $this->getLazyLoadedAttribute( $name );
567 return $this->attributes[$name] ?? [];
577 if ( isset( $this->testAttributes[$name] ) ) {
578 return $this->testAttributes[$name];
580 if ( isset( $this->lazyAttributes[$name] ) ) {
581 return $this->lazyAttributes[$name];
585 $cache = $this->getCache();
586 $key = $this->makeCacheKey(
$cache,
'lazy-attrib', $name );
587 $data =
$cache->get( $key );
588 if ( $data !==
false ) {
589 $this->lazyAttributes[$name] = $data;
594 foreach ( $this->loaded as $info ) {
597 $paths[$info[
'path']] = 1;
600 $result = $this->readFromQueue( $paths );
601 $data = $result[
'attributes'][$name] ?? [];
602 $this->saveToCache(
$cache, $result );
603 $this->lazyAttributes[$name] = $data;
618 if ( !defined(
'MW_PHPUNIT_TEST' ) ) {
619 throw new RuntimeException( __METHOD__ .
' can only be used in tests' );
622 if ( isset( $this->testAttributes[$name] ) ) {
623 throw new Exception(
"The attribute '$name' has already been overridden" );
625 $this->testAttributes[$name] = $value;
626 return new ScopedCallback(
function () use ( $name ) {
627 unset( $this->testAttributes[$name] );
637 return $this->loaded;
649 foreach ( $files as &
$file ) {
650 $file =
"$dir/$file";
const MW_VERSION
The running version of MediaWiki.
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
wfArrayPlus2d(array $baseArray, array $newValues)
Merges two (possibly) 2 dimensional arrays into the target array ($baseArray).
if(!defined('MW_SETUP_CALLBACK'))
The persistent session ID (if any) loaded at startup.
Class representing a cache/ephemeral data store.
Copyright (C) 2018 Kunal Mehta legoktm@debian.org
Utility class for loading extension manifests and aggregating their contents.
The Registry loads JSON files, and uses a Processor to extract information from them.
setLoadTestClassesAndNamespaces( $load)
Controls if classes and namespaces defined under the keys TestAutoloadClasses and TestAutoloadNamespa...
static processAutoLoader( $dir, array $files)
Fully expand autoloader paths.
const LAZY_LOADED_ATTRIBUTES
Attributes that should be lazy-loaded.
isLoaded( $name, $constraint=' *')
Whether a thing has been loaded.
const MERGE_STRATEGY
Special key that defines the merge strategy.
getQueue()
Get the current load queue.
getLazyLoadedAttribute( $name)
Get an attribute value that isn't cached by reading each extension.json file again.
saveToCache(BagOStuff $cache, array $data)
Save data in the cache.
const MANIFEST_VERSION
Version of the highest supported manifest version Note: Update MANIFEST_VERSION_MW_VERSION when chang...
setAttributeForTest( $name, array $value)
Force override the value of an attribute during tests.
const OLDEST_MANIFEST_VERSION
Version of the oldest supported manifest version.
getAbilities()
Get the list of abilities and their values.
const CACHE_VERSION
Bump whenever the registration cache needs resetting.
array $testAttributes
Attributes for testing.
makeCacheKey(BagOStuff $cache, $component,... $extra)
clearQueue()
Clear the current load queue.
invalidateProcessCache()
Invalidate the cache of the vary hash and the lazy options.
array $lazyAttributes
Lazy-loaded attributes.
array[] $loaded
Array of loaded things, keyed by name, values are credits information.
setCache(BagOStuff $cache)
Set the cache to use for extension info.
static ExtensionRegistry $instance
const MANIFEST_VERSION_MW_VERSION
MediaWiki version constraint representing what the current highest MANIFEST_VERSION is supported in.
int[] $queued
List of paths that should be loaded.
const MEDIAWIKI_CORE
"requires" key that applies to MediaWiki core
bool $loadTestClassesAndNamespaces
Whether test classes and namespaces should be added to the auto loader.
readFromQueue(array $queue)
Process a queue of extensions and return their extracted data.
exportExtractedData(array $info)
bool $finished
Whether we are done loading things.
setCheckDevRequires( $check)
getAllThings()
Get credits information about all installed extensions and skins.
getVaryHash()
Get the cache varying hash.
finish()
After this is called, no more extensions can be loaded.
buildVersionChecker()
Queries information about the software environment and constructs an appropriate version checker.
array $attributes
Items in the JSON file that aren't being set as globals.
string null $varyHash
The hash of cache-varying options, lazy-initialised.
bool $checkDev
Whether to check dev-requires.
static makeLocalServerCache()
Create a new BagOStuff instance for local-server caching.
Provides functions to check a set of extensions with dependencies against a set of loaded extensions ...
$wgExtensionInfoMTime
Config variable stub for the ExtensionInfoMTime setting, for use by phpdoc and IDEs.
$wgDevelopmentWarnings
Config variable stub for the DevelopmentWarnings setting, for use by phpdoc and IDEs.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.