20 MainConfigNames::ActionFilteredLogs,
21 MainConfigNames::Actions,
22 MainConfigNames::AddGroups,
23 MainConfigNames::APIFormatModules,
24 MainConfigNames::APIListModules,
25 MainConfigNames::APIMetaModules,
26 MainConfigNames::APIModules,
27 MainConfigNames::APIPropModules,
28 MainConfigNames::AuthManagerAutoConfig,
29 MainConfigNames::AvailableRights,
30 MainConfigNames::CentralIdLookupProviders,
31 MainConfigNames::ChangeCredentialsBlacklist,
32 MainConfigNames::ConfigRegistry,
33 MainConfigNames::ContentHandlers,
34 MainConfigNames::DefaultUserOptions,
35 MainConfigNames::ExtensionEntryPointListFiles,
36 MainConfigNames::ExtensionFunctions,
37 MainConfigNames::FeedClasses,
38 MainConfigNames::FileExtensions,
39 MainConfigNames::FilterLogTypes,
40 MainConfigNames::GrantPermissionGroups,
41 MainConfigNames::GrantPermissions,
42 MainConfigNames::GroupPermissions,
43 MainConfigNames::GroupsAddToSelf,
44 MainConfigNames::GroupsRemoveFromSelf,
45 MainConfigNames::HiddenPrefs,
46 MainConfigNames::ImplicitGroups,
47 MainConfigNames::JobClasses,
48 MainConfigNames::LogActions,
49 MainConfigNames::LogActionsHandlers,
50 MainConfigNames::LogHeaders,
51 MainConfigNames::LogNames,
52 MainConfigNames::LogRestrictions,
53 MainConfigNames::LogTypes,
54 MainConfigNames::MediaHandlers,
55 MainConfigNames::PasswordPolicy,
56 MainConfigNames::PrivilegedGroups,
57 MainConfigNames::RateLimits,
58 MainConfigNames::RawHtmlMessages,
59 MainConfigNames::ReauthenticateTime,
60 MainConfigNames::RecentChangesFlags,
61 MainConfigNames::RemoveCredentialsBlacklist,
62 MainConfigNames::RemoveGroups,
63 MainConfigNames::ResourceLoaderSources,
64 MainConfigNames::RevokePermissions,
65 MainConfigNames::SessionProviders,
66 MainConfigNames::SpecialPages,
67 MainConfigNames::UserRegistrationProviders,
75 protected const CORE_ATTRIBS = [
82 'LateJSConfigVarNames',
83 'TempUserSerialProviders',
84 'TempUserSerialMappings',
94 protected const MERGE_STRATEGIES = [
95 'wgAuthManagerAutoConfig' =>
'array_plus_2d',
96 'wgCapitalLinkOverrides' =>
'array_plus',
97 'wgExtraGenderNamespaces' =>
'array_plus',
98 'wgGrantPermissions' =>
'array_plus_2d',
99 'wgGroupPermissions' =>
'array_plus_2d',
100 'wgHooks' =>
'array_merge_recursive',
101 'wgNamespaceContentModels' =>
'array_plus',
102 'wgNamespaceProtection' =>
'array_plus',
103 'wgNamespacesWithSubpages' =>
'array_plus',
104 'wgPasswordPolicy' =>
'array_merge_recursive',
105 'wgRateLimits' =>
'array_plus_2d',
106 'wgRevokePermissions' =>
'array_plus_2d',
114 protected const CREDIT_ATTRIBS = [
132 protected const NOT_ATTRIBS = [
136 'load_composer_autoloader',
141 'AutoloadNamespaces',
142 'ExtensionMessagesFiles',
143 'ForeignResourcesDir',
145 'MessagePosterModule',
149 'ResourceFileModulePaths',
150 'ResourceModuleSkinStyles',
152 'ServiceWiringFiles',
163 'wgExtensionMessagesFiles' => [],
164 'wgMessagesDirs' => [],
237 $json = file_get_contents(
$path );
238 $info = json_decode( $json,
true );
241 throw new RuntimeException(
"Failed to load JSON data from $path" );
244 $this->
extractInfo( $path, $info, $info[
'manifest_version'] );
253 $dir = dirname(
$path );
262 if ( isset( $info[
'ServiceWiringFiles'] ) ) {
264 'wgServiceWiringFiles',
266 $info[
'ServiceWiringFiles']
270 if ( isset( $info[
'callback'] ) ) {
271 $this->callbacks[$name] = $info[
'callback'];
274 $this->extractAutoload( $info, $dir );
278 if ( $version >= 2 ) {
286 if ( isset( $info[
'ParsoidModules'] ) ) {
287 foreach ( $info[
'ParsoidModules'] as &$module ) {
288 if ( is_string( $module ) ) {
289 $className = $module;
291 'class' => $className,
294 $module[
'name'] = $name;
300 if ( $version >= 2 ) {
304 foreach ( $info as $key => $val ) {
306 if ( in_array( $key, self::$globalSettings ) ) {
311 if ( $key[0] ===
'@' ) {
315 if ( $version >= 2 ) {
317 if ( in_array( $key, self::CORE_ATTRIBS ) ) {
318 $this->
storeToArray( $path, $key, $val, $this->attributes );
322 if ( !in_array( $key, self::NOT_ATTRIBS )
323 && !in_array( $key, self::CREDIT_ATTRIBS )
337 if ( isset( $info[
'attributes'] ) ) {
338 foreach ( $info[
'attributes'] as $extName => $value ) {
346 foreach ( $this->globals as $key => $val ) {
347 if ( isset( self::MERGE_STRATEGIES[$key] ) ) {
353 foreach ( $this->extAttributes as $extName => $value ) {
355 if ( isset( $this->credits[$extName] ) ) {
356 foreach ( $value as $attrName => $attrValue ) {
359 $extName . $attrName,
364 unset( $this->extAttributes[$extName] );
376 'autoloaderClasses' =>
$autoload[
'classes'],
377 'autoloaderNS' =>
$autoload[
'namespaces'],
383 if ( !$includeDev || !isset( $info[
'dev-requires'] ) ) {
384 return $info[
'requires'] ?? [];
387 if ( !isset( $info[
'requires'] ) ) {
388 return $info[
'dev-requires'] ?? [];
398 $pick =
static function ( $a, $b ) {
401 } elseif ( $b ===
null ) {
408 $req = $info[
'requires'];
409 $dev = $info[
'dev-requires'];
410 if ( isset( $req[
'MediaWiki'] ) || isset( $dev[
'MediaWiki'] ) ) {
411 $merged[
'MediaWiki'] = $pick(
412 $req[
'MediaWiki'] ??
null,
413 $dev[
'MediaWiki'] ??
null
417 $platform = array_merge(
418 array_keys( $req[
'platform'] ?? [] ),
419 array_keys( $dev[
'platform'] ?? [] )
422 foreach ( $platform as $pkey ) {
423 if ( $pkey ===
'php' ) {
425 $req[
'platform'][
'php'] ??
null,
426 $dev[
'platform'][
'php'] ??
null
431 $value = $dev[
'platform'][$pkey] ?? $req[
'platform'][$pkey];
433 $merged[
'platform'][$pkey] = $value;
437 foreach ( [
'extensions',
'skins' ] as $thing ) {
438 $things = array_merge(
439 array_keys( $req[$thing] ?? [] ),
440 array_keys( $dev[$thing] ?? [] )
442 foreach ( $things as $name ) {
443 $merged[$thing][$name] = $pick(
444 $req[$thing][$name] ??
null,
445 $dev[$thing][$name] ??
null
463 private function setArrayHookHandler(
465 array $hookHandlersAttr,
469 if ( isset( $callback[
'handler'] ) ) {
470 $handlerName = $callback[
'handler'];
471 $handlerDefinition = $hookHandlersAttr[$handlerName] ??
false;
472 if ( !$handlerDefinition ) {
473 throw new UnexpectedValueException(
474 "Missing handler definition for $name in HookHandlers attribute in $path"
477 $callback[
'handler'] = $handlerDefinition;
478 $callback[
'extensionPath'] =
$path;
479 $this->attributes[
'Hooks'][$name][] = $callback;
481 foreach ( $callback as $callable ) {
482 if ( is_array( $callable ) ) {
483 if ( isset( $callable[
'handler'] ) ) {
484 $this->setArrayHookHandler( $callable, $hookHandlersAttr, $name,
$path );
486 $this->globals[
'wgHooks'][$name][] = $callable;
488 } elseif ( is_string( $callable ) ) {
489 $this->setStringHookHandler( $callable, $hookHandlersAttr, $name,
$path );
505 private function setStringHookHandler(
507 array $hookHandlersAttr,
511 if ( isset( $hookHandlersAttr[$callback] ) ) {
513 'handler' => $hookHandlersAttr[$callback],
514 'extensionPath' =>
$path
516 $this->attributes[
'Hooks'][$name][] = $handler;
518 $this->globals[
'wgHooks'][$name][] = $callback;
531 $extName = $info[
'name'];
532 if ( isset( $info[
'Hooks'] ) ) {
533 $hookHandlersAttr = [];
534 foreach ( $info[
'HookHandlers'] ?? [] as $name => $def ) {
535 $hookHandlersAttr[$name] = [
'name' =>
"$extName-$name" ] + $def;
537 foreach ( $info[
'Hooks'] as $name => $callback ) {
538 if ( is_string( $callback ) ) {
539 $this->setStringHookHandler( $callback, $hookHandlersAttr, $name,
$path );
540 } elseif ( is_array( $callback ) ) {
541 $this->setArrayHookHandler( $callback, $hookHandlersAttr, $name,
$path );
545 if ( isset( $info[
'DeprecatedHooks'] ) ) {
546 $deprecatedHooks = [];
547 foreach ( $info[
'DeprecatedHooks'] as $name => $deprecatedHookInfo ) {
548 $deprecatedHookInfo += [
'component' => $extName ];
549 $deprecatedHooks[$name] = $deprecatedHookInfo;
551 if ( isset( $this->attributes[
'DeprecatedHooks'] ) ) {
552 $this->attributes[
'DeprecatedHooks'] += $deprecatedHooks;
554 $this->attributes[
'DeprecatedHooks'] = $deprecatedHooks;
565 if ( isset( $info[
'namespaces'] ) ) {
566 foreach ( $info[
'namespaces'] as $ns ) {
567 if ( defined( $ns[
'constant'] ) ) {
570 $id = constant( $ns[
'constant'] );
574 $this->defines[ $ns[
'constant'] ] = $id;
576 if ( !( isset( $ns[
'conditional'] ) && $ns[
'conditional'] ) ) {
578 $this->attributes[
'ExtensionNamespaces'][$id] = $ns[
'name'];
580 if ( isset( $ns[
'movable'] ) && !$ns[
'movable'] ) {
581 $this->attributes[
'ImmovableNamespaces'][] = $id;
583 if ( isset( $ns[
'gender'] ) ) {
584 $this->globals[
'wgExtraGenderNamespaces'][$id] = $ns[
'gender'];
586 if ( isset( $ns[
'subpages'] ) && $ns[
'subpages'] ) {
587 $this->globals[
'wgNamespacesWithSubpages'][$id] =
true;
589 if ( isset( $ns[
'content'] ) && $ns[
'content'] ) {
590 $this->globals[
'wgContentNamespaces'][] = $id;
592 if ( isset( $ns[
'defaultcontentmodel'] ) ) {
593 $this->globals[
'wgNamespaceContentModels'][$id] = $ns[
'defaultcontentmodel'];
595 if ( isset( $ns[
'protection'] ) ) {
596 $this->globals[
'wgNamespaceProtection'][$id] = $ns[
'protection'];
598 if ( isset( $ns[
'capitallinkoverride'] ) ) {
599 $this->globals[
'wgCapitalLinkOverrides'][$id] = $ns[
'capitallinkoverride'];
601 if ( isset( $ns[
'includable'] ) && !$ns[
'includable'] ) {
602 $this->globals[
'wgNonincludableNamespaces'][] = $id;
609 $defaultPaths = $info[
'ResourceFileModulePaths'] ??
false;
610 if ( isset( $defaultPaths[
'localBasePath'] ) ) {
611 if ( $defaultPaths[
'localBasePath'] ===
'' ) {
613 $defaultPaths[
'localBasePath'] = $dir;
615 $defaultPaths[
'localBasePath'] =
"$dir/{$defaultPaths['localBasePath']}";
619 foreach ( [
'ResourceModules',
'ResourceModuleSkinStyles',
'OOUIThemePaths' ] as $setting ) {
620 if ( isset( $info[$setting] ) ) {
621 foreach ( $info[$setting] as $name => $data ) {
622 if ( isset( $data[
'localBasePath'] ) ) {
623 if ( $data[
'localBasePath'] ===
'' ) {
625 $data[
'localBasePath'] = $dir;
627 $data[
'localBasePath'] =
"$dir/{$data['localBasePath']}";
630 if ( $defaultPaths ) {
631 $data += $defaultPaths;
633 $this->attributes[$setting][$name] = $data;
638 if ( isset( $info[
'QUnitTestModule'] ) ) {
639 $data = $info[
'QUnitTestModule'];
640 if ( isset( $data[
'localBasePath'] ) ) {
641 if ( $data[
'localBasePath'] ===
'' ) {
643 $data[
'localBasePath'] = $dir;
645 $data[
'localBasePath'] =
"$dir/{$data['localBasePath']}";
649 $data[
'targets'] = [
'test' ];
650 $this->attributes[
'QUnitTestModules'][
"test.{$info['name']}"] = $data;
653 if ( isset( $info[
'MessagePosterModule'] ) ) {
654 $data = $info[
'MessagePosterModule'];
655 $basePath = $data[
'localBasePath'] ??
'';
656 $baseDir = $basePath ===
'' ? $dir :
"$dir/$basePath";
657 foreach ( $data[
'scripts'] ?? [] as $scripts ) {
658 $this->attributes[
'MessagePosterModule'][
'scripts'][] =
661 foreach ( $data[
'dependencies'] ?? [] as $dependency ) {
662 $this->attributes[
'MessagePosterModule'][
'dependencies'][] = $dependency;
668 if ( isset( $info[
'ExtensionMessagesFiles'] ) ) {
669 foreach ( $info[
'ExtensionMessagesFiles'] as &
$file ) {
670 $file =
"$dir/$file";
672 $this->globals[
"wgExtensionMessagesFiles"] += $info[
'ExtensionMessagesFiles'];
684 if ( isset( $info[
'MessagesDirs'] ) ) {
685 foreach ( $info[
'MessagesDirs'] as $name => $files ) {
686 foreach ( (array)$files as
$file ) {
687 $this->globals[
"wgMessagesDirs"][$name][] =
"$dir/$file";
700 if ( isset( $info[
'ValidSkinNames'] ) ) {
701 foreach ( $info[
'ValidSkinNames'] as $skinKey => $data ) {
702 if ( isset( $data[
'args'][0] ) ) {
703 $templateDirectory = $data[
'args'][0][
'templateDirectory'] ??
'templates';
704 $data[
'args'][0][
'templateDirectory'] = $dir .
'/' . $templateDirectory;
706 $this->globals[
'wgValidSkinNames'][$skinKey] = $data;
721 if ( isset( $info[
'RateLimits'] ) ) {
722 $rights = array_keys( $info[
'RateLimits'] );
724 if ( isset( $info[
'AvailableRights'] ) ) {
725 $rights = array_diff( $rights, $info[
'AvailableRights'] );
728 $this->globals[
'wgImplicitRights'] = array_merge(
729 $this->globals[
'wgImplicitRights'] ?? [],
740 if ( isset( $info[
'SkinLessImportPaths'] ) ) {
741 foreach ( $info[
'SkinLessImportPaths'] as $skin => $subpath ) {
742 $this->attributes[
'SkinLessImportPaths'][$skin] =
"$dir/$subpath";
758 foreach ( self::CREDIT_ATTRIBS as $attr ) {
759 if ( isset( $info[$attr] ) ) {
768 if ( isset( $this->credits[$name] ) ) {
769 $firstPath = $this->credits[$name][
'path'];
771 throw new Exception(
"It was attempted to load $name twice, from $firstPath and $secondPath." );
780 if ( array_key_exists(
'ForeignResourcesDir', $info ) ) {
781 if ( !is_string( $info[
'ForeignResourcesDir'] ) ) {
782 throw new Exception(
"Incorrect ForeignResourcesDir type, must be a string (in $name)" );
784 $this->attributes[
'ForeignResourcesDir'][$name] =
"{$dir}/{$info['ForeignResourcesDir']}";
795 if ( isset( $info[
'config'] ) ) {
796 if ( isset( $info[
'config'][
'_prefix'] ) ) {
797 $prefix = $info[
'config'][
'_prefix'];
798 unset( $info[
'config'][
'_prefix'] );
802 foreach ( $info[
'config'] as $key => $val ) {
803 if ( $key[0] !==
'@' ) {
804 $this->addConfigGlobal(
"$prefix$key", $val, $info[
'name'] );
818 private function applyPath( array $value,
string $dir ): array {
821 foreach ( $value as $k => $v ) {
822 $result[$k] = $dir .
'/' . $v;
835 $prefix = $info[
'config_prefix'] ??
'wg';
836 if ( isset( $info[
'config'] ) ) {
837 foreach ( $info[
'config'] as $key => $data ) {
838 if ( !array_key_exists(
'value', $data ) ) {
839 throw new UnexpectedValueException(
"Missing value for config $key" );
842 $value = $data[
'value'];
843 if ( isset( $data[
'path'] ) && $data[
'path'] ) {
844 if ( is_array( $value ) ) {
845 $value = $this->applyPath( $value, $dir );
847 $value =
"$dir/$value";
850 if ( isset( $data[
'merge_strategy'] ) ) {
853 $this->addConfigGlobal(
"$prefix$key", $value, $info[
'name'] );
854 $data[
'providedby'] = $info[
'name'];
855 if ( isset( $info[
'ConfigRegistry'][0] ) ) {
856 $data[
'configregistry'] = array_keys( $info[
'ConfigRegistry'] )[0];
869 private function addConfigGlobal( $key, $value, $extName ) {
870 if ( array_key_exists( $key, $this->globals ) ) {
871 throw new RuntimeException(
872 "The configuration setting '$key' was already set by MediaWiki core or"
873 .
" another extension, and cannot be set again by $extName." );
875 $this->globals[$key] = $value;
879 foreach ( $paths as
$path ) {
880 $this->globals[$global][] =
"$dir/$path";
894 if ( !is_array( $value ) ) {
895 throw new InvalidArgumentException(
"The value for '$name' should be an array (from $path)" );
897 if ( isset( $array[$name] ) ) {
898 $array[$name] = array_merge_recursive( $array[$name], $value );
900 $array[$name] = $value;
914 if ( !is_array( $value ) ) {
915 throw new InvalidArgumentException(
"The value for '$name' should be an array (from $path)" );
917 if ( isset( $array[$name] ) ) {
918 $array[$name] = array_merge( $array[$name], $value );
920 $array[$name] = $value;
935 if ( isset( $info[
'load_composer_autoloader'] ) && $info[
'load_composer_autoloader'] ===
true ) {
936 $paths[] =
"$dir/vendor/autoload.php";
956 $autoload = $this->autoload;
959 $autoload[
'classes'] += $this->autoloadDev[
'classes'];
960 $autoload[
'namespaces'] += $this->autoloadDev[
'namespaces'];
965 if ( !empty( $this->autoloadDev[
'files'] ) ) {
968 $autoload[
'files'] = array_merge(
970 $this->autoloadDev[
'files']
982 private function extractAutoload( array $info,
string $dir ) {
983 if ( isset( $info[
'load_composer_autoloader'] ) && $info[
'load_composer_autoloader'] ===
true ) {
984 $file =
"$dir/vendor/autoload.php";
985 if ( file_exists(
$file ) ) {
986 $this->autoload[
'files'][] =
$file;
990 if ( isset( $info[
'AutoloadClasses'] ) ) {
991 $paths = $this->applyPath( $info[
'AutoloadClasses'], $dir );
992 $this->autoload[
'classes'] += $paths;
995 if ( isset( $info[
'AutoloadNamespaces'] ) ) {
996 $paths = $this->applyPath( $info[
'AutoloadNamespaces'], $dir );
997 $this->autoload[
'namespaces'] += $paths;
1000 if ( isset( $info[
'TestAutoloadClasses'] ) ) {
1001 $paths = $this->applyPath( $info[
'TestAutoloadClasses'], $dir );
1002 $this->autoloadDev[
'classes'] += $paths;
1005 if ( isset( $info[
'TestAutoloadNamespaces'] ) ) {
1006 $paths = $this->applyPath( $info[
'TestAutoloadNamespaces'], $dir );
1007 $this->autoloadDev[
'namespaces'] += $paths;
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
if(!defined('MW_SETUP_CALLBACK'))
Load extension manifests and then aggregate their contents.
extractHooks(array $info, string $path)
Extract hook information from Hooks and HookHandler attributes.
extractNamespaces(array $info)
Register namespaces with the appropriate global settings.
extractCredits( $path, array $info)
array $attributes
Anything else in the $info that hasn't already been processed.
extractInfoFromFile(string $path)
Extracts extension info from the given JSON file.
callable[] $callbacks
Things to be called once the registration of these extensions is done.
array $defines
Things that should be define()'d.
string[][] $autoloadDev
Autoloader information for development.
extractExtensionMessagesFiles( $dir, array $info)
extractConfig1(array $info)
Set configuration settings for manifest_version == 1.
array $globals
Stuff that is going to be set to $GLOBALS.
extractMessagesDirs( $dir, array $info)
Set message-related settings, which need to be expanded to use absolute paths.
extractConfig2(array $info, $dir)
Set configuration settings for manifest_version == 2.
getExtractedInfo(bool $includeDev=false)
extractResourceLoaderModules( $dir, array $info)
array $extAttributes
Extension attributes, keyed by name => settings.
extractSkins( $dir, array $info)
Extract skins and handle path correction for templateDirectory.
getExtraAutoloaderPaths( $dir, array $info)
extractAttributes( $path, array $info)
getRequirements(array $info, $includeDev)
Get the requirements for the provided info.
extractSkinImportPaths( $dir, array $info)
extractInfo( $path, array $info, $version)
getExtractedAutoloadInfo(bool $includeDev=false)
Returns the extracted autoload info.
storeToArray( $path, $name, $value, &$array)
Stores $value to $array; using array_merge() if $array already contains $name.
extractImplicitRights(array $info)
Extract any user rights that should be granted implicitly.
string[][] $autoload
Autoloader information.
extractPathBasedGlobal( $global, $dir, $paths)
extractForeignResourcesDir(array $info, string $name, string $dir)
storeToArrayRecursive( $path, $name, $value, &$array)
Stores $value to $array; using array_merge_recursive() if $array already contains $name.
static array $globalSettings
Keys that should be set to $GLOBALS.
const MERGE_STRATEGY
Special key that defines the merge strategy.
A class containing constants representing the names of configuration variables.
Generic processor that reads associated arrays and registers whatever is required.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.