3use Composer\Semver\Semver;
4use Wikimedia\AtEase\AtEase;
5use Wikimedia\ScopedCallback;
107 if ( self::$instance ===
null ) {
108 self::$instance =
new self();
111 return self::$instance;
119 $this->checkDev = $check;
129 if ( $mtime ===
false ) {
130 AtEase::suppressWarnings();
131 $mtime = filemtime(
$path );
132 AtEase::restoreWarnings();
134 if ( $mtime ===
false ) {
135 $err = error_get_last();
136 throw new Exception(
"Unable to open file $path: {$err['message']}" );
140 $this->queued[
$path] = $mtime;
149 if ( !$this->queued ) {
153 if ( $this->finished ) {
155 "The following paths tried to load late: "
156 . implode(
', ', array_keys( $this->queued ) )
162 'registration' => self::CACHE_VERSION,
165 'checkDev' => $this->checkDev,
172 $cacheId = ObjectCache::detectLocalServerCache();
174 }
catch ( InvalidArgumentException $e ) {
180 md5( json_encode( $this->queued + $versions ) )
182 $data =
$cache->get( $key );
190 $data[
'globals'][
'wgAutoloadClasses'] += $data[
'autoload'];
191 unset( $data[
'autoload'] );
194 $cache->set( $key, $data, 60 * 60 * 24 );
207 return $this->queued;
224 $this->finished =
true;
233 'shell' => !Shell::isDisabled(),
252 PHP_MAJOR_VERSION .
'.' . PHP_MINOR_VERSION .
'.' . PHP_RELEASE_VERSION,
253 get_loaded_extensions(),
268 $autoloadClasses = [];
269 $autoloadNamespaces = [];
270 $autoloaderPaths = [];
273 $extDependencies = [];
277 $json = file_get_contents(
$path );
278 if ( $json ===
false ) {
279 throw new Exception(
"Unable to read $path, does it exist?" );
281 $info = json_decode( $json,
true );
282 if ( !is_array( $info ) ) {
283 throw new Exception(
"$path is not a valid JSON file." );
286 if ( !isset( $info[
'manifest_version'] ) ) {
288 "{$info['name']}'s extension.json or skin.json does not have manifest_version",
293 $info[
'manifest_version'] = 1;
295 $version = $info[
'manifest_version'];
296 if ( $version < self::OLDEST_MANIFEST_VERSION || $version > self::MANIFEST_VERSION ) {
297 $incompatible[] =
"$path: unsupported manifest_version: {$version}";
300 $dir = dirname(
$path );
301 self::exportAutoloadClassesAndNamespaces(
309 $requires = $processor->getRequirements( $info, $this->checkDev );
312 if ( is_array( $requires ) && $requires && isset( $info[
'name'] ) ) {
313 $extDependencies[$info[
'name']] = $requires;
317 $autoloaderPaths = array_merge( $autoloaderPaths,
318 $processor->getExtraAutoloaderPaths( $dir, $info ) );
320 $processor->extractInfo(
$path, $info, $version );
322 $data = $processor->getExtractedInfo();
323 $data[
'warnings'] = $warnings;
326 $incompatible = array_merge(
329 ->setLoadedExtensionsAndSkins( $data[
'credits'] )
330 ->checkArray( $extDependencies )
333 if ( $incompatible ) {
338 $data[
'globals'][
'wgAutoloadClasses'] = [];
339 $data[
'autoload'] = $autoloadClasses;
340 $data[
'autoloaderPaths'] = $autoloaderPaths;
341 $data[
'autoloaderNS'] = $autoloadNamespaces;
354 $dir, $info, &$autoloadClasses = [], &$autoloadNamespaces = []
356 if ( isset( $info[
'AutoloadClasses'] ) ) {
357 $autoload = self::processAutoLoader( $dir, $info[
'AutoloadClasses'] );
358 $GLOBALS[
'wgAutoloadClasses'] += $autoload;
359 $autoloadClasses += $autoload;
361 if ( isset( $info[
'AutoloadNamespaces'] ) ) {
362 $autoloadNamespaces += self::processAutoLoader( $dir, $info[
'AutoloadNamespaces'] );
368 foreach ( $info[
'globals'] as $key => $val ) {
371 if ( is_array( $val ) && isset( $val[self::MERGE_STRATEGY] ) ) {
372 $mergeStrategy = $val[self::MERGE_STRATEGY];
373 unset( $val[self::MERGE_STRATEGY] );
375 $mergeStrategy =
'array_merge';
385 if ( !is_array(
$GLOBALS[$key] ) || !is_array( $val ) ) {
390 switch ( $mergeStrategy ) {
391 case 'array_merge_recursive':
394 case 'array_replace_recursive':
397 case 'array_plus_2d':
407 throw new UnexpectedValueException(
"Unknown merge strategy '$mergeStrategy'" );
411 if ( isset( $info[
'autoloaderNS'] ) ) {
415 foreach ( $info[
'defines'] as $name => $val ) {
416 define( $name, $val );
418 foreach ( $info[
'autoloaderPaths'] as
$path ) {
419 if ( file_exists(
$path ) ) {
424 $this->loaded += $info[
'credits'];
425 if ( $info[
'attributes'] ) {
426 if ( !$this->attributes ) {
427 $this->attributes = $info[
'attributes'];
429 $this->attributes = array_merge_recursive( $this->attributes, $info[
'attributes'] );
433 foreach ( $info[
'callbacks'] as $name => $cb ) {
434 if ( !is_callable( $cb ) ) {
435 if ( is_array( $cb ) ) {
436 $cb =
'[ ' . implode(
', ', $cb ) .
' ]';
438 throw new UnexpectedValueException(
"callback '$cb' is not callable" );
440 $cb( $info[
'credits'][$name] );
468 public function isLoaded( $name, $constraint =
'*' ) {
469 $isLoaded = isset( $this->loaded[$name] );
470 if ( $constraint ===
'*' || !$isLoaded ) {
474 if ( !isset( $this->loaded[$name][
'version'] ) ) {
475 $msg =
"{$name} does not expose its version, but an extension or a skin"
476 .
" requires: {$constraint}.";
477 throw new LogicException( $msg );
480 return SemVer::satisfies( $this->loaded[$name][
'version'], $constraint );
488 return $this->testAttributes[$name] ??
489 $this->attributes[$name] ?? [];
502 if ( !defined(
'MW_PHPUNIT_TEST' ) ) {
503 throw new RuntimeException( __METHOD__ .
' can only be used in tests' );
506 if ( isset( $this->testAttributes[$name] ) ) {
507 throw new Exception(
"The attribute '$name' has already been overridden" );
509 $this->testAttributes[$name] = $value;
510 return new ScopedCallback(
function () use ( $name ) {
511 unset( $this->testAttributes[$name] );
521 return $this->loaded;
533 foreach ( $files as &
$file ) {
534 $file =
"$dir/$file";
$wgObjectCaches
Advanced object cache configuration.
int bool $wgExtensionInfoMTime
When loading extensions through the extension registration system, this can be used to invalidate the...
$wgVersion
MediaWiki version number.
$wgDevelopmentWarnings
If set to true MediaWiki will throw notices for some possible error conditions and for deprecated fun...
wfArrayPlus2d(array $baseArray, array $newValues)
Merges two (possibly) 2 dimensional arrays into the target array ($baseArray).
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
static string[] $psr4Namespaces
A BagOStuff object with no objects in it.
Copyright (C) 2018 Kunal Mehta legoktm@member.fsf.org
static processAutoLoader( $dir, array $files)
Fully expand autoloader paths.
isLoaded( $name, $constraint=' *')
Whether a thing has been loaded.
array $queued
List of paths that should be loaded.
const MERGE_STRATEGY
Special key that defines the merge strategy.
getQueue()
Get the current load queue.
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.
array $loaded
Array of loaded things, keyed by name, values are credits information.
const CACHE_VERSION
Bump whenever the registration cache needs resetting.
array $testAttributes
Attributes for testing.
clearQueue()
Clear the current load queue.
static exportAutoloadClassesAndNamespaces( $dir, $info, &$autoloadClasses=[], &$autoloadNamespaces=[])
Export autoload classes and namespaces for a given directory and parsed JSON info file.
static ExtensionRegistry $instance
const MANIFEST_VERSION_MW_VERSION
MediaWiki version constraint representing what the current highest MANIFEST_VERSION is supported in.
load( $path)
Loads and processes the given JSON file without delay.
const MEDIAWIKI_CORE
"requires" key that applies to MediaWiki core/$wgVersion
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 information about all things.
finish()
After this is called, no more extensions can be loaded.
buildVersionChecker()
Queries information about the software environment and constructs an appropiate version checker.
array $attributes
Items in the JSON file that aren't being set as globals.
bool $checkDev
Whether to check dev-requires.
Provides functions to check a set of extensions with dependencies against a set of loaded extensions ...
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.