MediaWiki master
ExtensionProcessor.php
Go to the documentation of this file.
1<?php
2
4
5use Exception;
6use InvalidArgumentException;
9use RuntimeException;
10use UnexpectedValueException;
11
18class ExtensionProcessor implements Processor {
19
25 protected static $globalSettings = [
77 ];
78
82 protected const CORE_ATTRIBS = [
83 'ParsoidModules',
84 'RestRoutes',
85 'SkinOOUIThemes',
86 'SkinCodexThemes',
87 'SearchMappings',
88 'TrackingCategories',
89 'LateJSConfigVarNames',
90 'TempUserSerialProviders',
91 'TempUserSerialMappings',
92 'DatabaseVirtualDomains',
93 'UserOptionsStoreProviders',
94 'NotificationHandlers',
95 'NotificationMiddleware',
96 'RecentChangeSources',
97 ];
98
105 protected const MERGE_STRATEGIES = [
106 'wgAddGroups' => 'array_merge_recursive',
107 'wgAuthManagerAutoConfig' => 'array_plus_2d',
108 'wgCapitalLinkOverrides' => 'array_plus',
109 'wgExtraGenderNamespaces' => 'array_plus',
110 'wgGrantPermissions' => 'array_plus_2d',
111 'wgGroupPermissions' => 'array_plus_2d',
112 'wgHooks' => 'array_merge_recursive',
113 'wgNamespaceContentModels' => 'array_plus',
114 'wgNamespaceProtection' => 'array_plus',
115 'wgNamespacesWithSubpages' => 'array_plus',
116 'wgPasswordPolicy' => 'array_merge_recursive',
117 'wgRateLimits' => 'array_plus_2d',
118 'wgRemoveGroups' => 'array_merge_recursive',
119 'wgRevokePermissions' => 'array_plus_2d',
120 ];
121
125 protected const CREDIT_ATTRIBS = [
126 'type',
127 'author',
128 'description',
129 'descriptionmsg',
130 'license-name',
131 'name',
132 'namemsg',
133 'url',
134 'version',
135 ];
136
141 protected const NOT_ATTRIBS = [
142 'callback',
143 'config',
144 'config_prefix',
145 'load_composer_autoloader',
146 'manifest_version',
147 'namespaces',
148 'requires',
149 'AutoloadClasses',
150 'AutoloadNamespaces',
151 'ExtensionMessagesFiles',
152 'TranslationAliasesDirs',
153 'ForeignResourcesDir',
154 'Hooks',
155 'DomainEventIngresses',
156 'MessagePosterModule',
157 'MessagesDirs',
158 'OOUIThemePaths',
159 'QUnitTestModule',
160 'ResourceFileModulePaths',
161 'ResourceModuleSkinStyles',
162 'ResourceModules',
163 'ServiceWiringFiles',
164 ];
165
173 protected $globals = [
174 'wgExtensionMessagesFiles' => [],
175 'wgRestAPIAdditionalRouteFiles' => [],
176 'wgMessagesDirs' => [],
177 'TranslationAliasesDirs' => [],
178 ];
179
185 protected $defines = [];
186
194 protected $callbacks = [];
195
199 protected $credits = [];
200
208 protected $autoload = [
209 'files' => [],
210 'classes' => [],
211 'namespaces' => [],
212 ];
213
220 protected $autoloadDev = [
221 'files' => [],
222 'classes' => [],
223 'namespaces' => [],
224 ];
225
232 protected $attributes = [];
233
240 protected $extAttributes = [];
241
249 public function extractInfoFromFile( string $path ) {
250 $json = file_get_contents( $path );
251 $info = json_decode( $json, true );
252
253 if ( !$info ) {
254 throw new RuntimeException( "Failed to load JSON data from $path" );
255 }
256
257 $this->extractInfo( $path, $info, $info['manifest_version'] );
258 }
259
265 public function extractInfo( $path, array $info, $version ) {
266 $dir = dirname( $path );
267 $this->extractHooks( $info, $path );
268 $this->extractDomainEventIngresses( $info, $path );
269 $this->extractExtensionMessagesFiles( $dir, $info );
270 $this->extractRestModuleFiles( $dir, $info );
271 $this->extractMessagesDirs( $dir, $info );
272 $this->extractTranslationAliasesDirs( $dir, $info );
273 $this->extractSkins( $dir, $info );
274 $this->extractSkinImportPaths( $dir, $info );
275 $this->extractNamespaces( $info );
276 $this->extractImplicitRights( $info );
277 $this->extractResourceLoaderModules( $dir, $info );
278 $this->extractInstallerTasks( $dir, $info );
279 if ( isset( $info['ServiceWiringFiles'] ) ) {
281 'wgServiceWiringFiles',
282 $dir,
283 $info['ServiceWiringFiles']
284 );
285 }
286 $name = $this->extractCredits( $path, $info );
287 if ( isset( $info['callback'] ) ) {
288 $this->callbacks[$name] = $info['callback'];
289 }
290
291 $this->extractAutoload( $info, $dir );
292
293 // config should be after all core globals are extracted,
294 // so duplicate setting detection will work fully
295 if ( $version >= 2 ) {
296 $this->extractConfig2( $info, $dir );
297 } else {
298 // $version === 1
299 $this->extractConfig1( $info );
300 }
301
302 // Record the extension name in the ParsoidModules property
303 if ( isset( $info['ParsoidModules'] ) ) {
304 foreach ( $info['ParsoidModules'] as &$module ) {
305 if ( is_string( $module ) ) {
306 $className = $module;
307 $module = [
308 'class' => $className,
309 ];
310 }
311 $module['name'] ??= $name;
312 $module['extension-name'] = $name;
313 }
314 }
315
316 $this->extractForeignResourcesDir( $info, $name, $dir );
317
318 if ( $version >= 2 ) {
319 $this->extractAttributes( $path, $info );
320 }
321
322 foreach ( $info as $key => $val ) {
323 // If it's a global setting,
324 if ( in_array( $key, self::$globalSettings ) ) {
325 $this->storeToArrayRecursive( $path, "wg$key", $val, $this->globals );
326 continue;
327 }
328 // Ignore anything that starts with a @
329 if ( $key[0] === '@' ) {
330 continue;
331 }
332
333 if ( $version >= 2 ) {
334 // Only allowed attributes are set
335 if ( in_array( $key, self::CORE_ATTRIBS ) ) {
336 $this->storeToArray( $path, $key, $val, $this->attributes );
337 }
338 } else {
339 // version === 1
340 if ( !in_array( $key, self::NOT_ATTRIBS )
341 && !in_array( $key, self::CREDIT_ATTRIBS )
342 ) {
343 // If it's not disallowed, it's an attribute
344 $this->storeToArrayRecursive( $path, $key, $val, $this->attributes );
345 }
346 }
347 }
348 }
349
354 protected function extractAttributes( $path, array $info ) {
355 if ( isset( $info['attributes'] ) ) {
356 foreach ( $info['attributes'] as $extName => $value ) {
357 $this->storeToArrayRecursive( $path, $extName, $value, $this->extAttributes );
358 }
359 }
360 }
361
363 public function getExtractedInfo( bool $includeDev = false ) {
364 // Make sure the merge strategies are set
365 foreach ( $this->globals as $key => $val ) {
366 if ( isset( self::MERGE_STRATEGIES[$key] ) ) {
367 $this->globals[$key][ExtensionRegistry::MERGE_STRATEGY] = self::MERGE_STRATEGIES[$key];
368 }
369 }
370
371 // Merge $this->extAttributes into $this->attributes depending on what is loaded
372 foreach ( $this->extAttributes as $extName => $value ) {
373 // Only set the attribute if $extName is loaded (and hence present in credits)
374 if ( isset( $this->credits[$extName] ) ) {
375 foreach ( $value as $attrName => $attrValue ) {
377 '', // Don't provide a path since it's impossible to generate an error here
378 $extName . $attrName,
379 $attrValue,
380 $this->attributes
381 );
382 }
383 unset( $this->extAttributes[$extName] );
384 }
385 }
386
387 $autoload = $this->getExtractedAutoloadInfo( $includeDev );
388
389 return [
390 'globals' => $this->globals,
391 'defines' => $this->defines,
392 'callbacks' => $this->callbacks,
393 'credits' => $this->credits,
394 'attributes' => $this->attributes,
395 'autoloaderPaths' => $autoload['files'],
396 'autoloaderClasses' => $autoload['classes'],
397 'autoloaderNS' => $autoload['namespaces'],
398 ];
399 }
400
402 public function getRequirements( array $info, $includeDev ) {
403 // Quick shortcuts
404 if ( !$includeDev || !isset( $info['dev-requires'] ) ) {
405 return $info['requires'] ?? [];
406 }
407
408 if ( !isset( $info['requires'] ) ) {
409 return $info['dev-requires'] ?? [];
410 }
411
412 // OK, we actually have to merge everything
413 $merged = [];
414
415 // Helper that combines version requirements by
416 // picking the non-null if one is, or combines
417 // the two. Note that it is not possible for
418 // both inputs to be null.
419 $pick = static function ( $a, $b ) {
420 if ( $a === null ) {
421 return $b;
422 } elseif ( $b === null ) {
423 return $a;
424 } else {
425 return "$a $b";
426 }
427 };
428
429 $req = $info['requires'];
430 $dev = $info['dev-requires'];
431 if ( isset( $req['MediaWiki'] ) || isset( $dev['MediaWiki'] ) ) {
432 $merged['MediaWiki'] = $pick(
433 $req['MediaWiki'] ?? null,
434 $dev['MediaWiki'] ?? null
435 );
436 }
437
438 $platform = array_merge(
439 array_keys( $req['platform'] ?? [] ),
440 array_keys( $dev['platform'] ?? [] )
441 );
442 if ( $platform ) {
443 foreach ( $platform as $pkey ) {
444 if ( $pkey === 'php' ) {
445 $value = $pick(
446 $req['platform']['php'] ?? null,
447 $dev['platform']['php'] ?? null
448 );
449 } else {
450 // Prefer dev value, but these should be constant
451 // anyway (ext-* and ability-*)
452 $value = $dev['platform'][$pkey] ?? $req['platform'][$pkey];
453 }
454 $merged['platform'][$pkey] = $value;
455 }
456 }
457
458 foreach ( [ 'extensions', 'skins' ] as $thing ) {
459 $things = array_merge(
460 array_keys( $req[$thing] ?? [] ),
461 array_keys( $dev[$thing] ?? [] )
462 );
463 foreach ( $things as $name ) {
464 $merged[$thing][$name] = $pick(
465 $req[$thing][$name] ?? null,
466 $dev[$thing][$name] ?? null
467 );
468 }
469 }
470
471 return $merged;
472 }
473
486 private function setArrayHookHandler(
487 array $callback,
488 array $hookHandlersAttr,
489 string $name,
490 string $path
491 ) {
492 if ( isset( $callback['handler'] ) ) {
493 $handlerName = $callback['handler'];
494 $handlerDefinition = $hookHandlersAttr[$handlerName] ?? false;
495 if ( !$handlerDefinition ) {
496 throw new UnexpectedValueException(
497 "Missing handler definition for $name in HookHandlers attribute in $path"
498 );
499 }
500 $callback['handler'] = $handlerDefinition;
501 $callback['extensionPath'] = $path;
502 $this->attributes['Hooks'][$name][] = $callback;
503 } else {
504 foreach ( $callback as $callable ) {
505 if ( is_array( $callable ) ) {
506 if ( isset( $callable['handler'] ) ) { // Non-legacy style handler
507 $this->setArrayHookHandler( $callable, $hookHandlersAttr, $name, $path );
508 } else { // Legacy style handler array
509 $this->globals['wgHooks'][$name][] = $callable;
510 }
511 } elseif ( is_string( $callable ) ) {
512 $this->setStringHookHandler( $callable, $hookHandlersAttr, $name, $path );
513 }
514 }
515 }
516 }
517
528 private function setStringHookHandler(
529 string $callback,
530 array $hookHandlersAttr,
531 string $name,
532 string $path
533 ) {
534 if ( isset( $hookHandlersAttr[$callback] ) ) {
535 $handler = [
536 'handler' => $hookHandlersAttr[$callback],
537 'extensionPath' => $path
538 ];
539 $this->attributes['Hooks'][$name][] = $handler;
540 } else { // legacy style handler
541 $this->globals['wgHooks'][$name][] = $callback;
542 }
543 }
544
553 protected function extractHooks( array $info, string $path ) {
554 $extName = $info['name'];
555 if ( isset( $info['Hooks'] ) ) {
556 $hookHandlersAttr = [];
557 foreach ( $info['HookHandlers'] ?? [] as $name => $def ) {
558 $hookHandlersAttr[$name] = [ 'name' => "$extName-$name" ] + $def;
559 }
560 foreach ( $info['Hooks'] as $name => $callback ) {
561 if ( is_string( $callback ) ) {
562 $this->setStringHookHandler( $callback, $hookHandlersAttr, $name, $path );
563 } elseif ( is_array( $callback ) ) {
564 $this->setArrayHookHandler( $callback, $hookHandlersAttr, $name, $path );
565 }
566 }
567 }
568 if ( isset( $info['DeprecatedHooks'] ) ) {
569 $deprecatedHooks = [];
570 foreach ( $info['DeprecatedHooks'] as $name => $deprecatedHookInfo ) {
571 $deprecatedHookInfo += [ 'component' => $extName ];
572 $deprecatedHooks[$name] = $deprecatedHookInfo;
573 }
574 if ( isset( $this->attributes['DeprecatedHooks'] ) ) {
575 $this->attributes['DeprecatedHooks'] += $deprecatedHooks;
576 } else {
577 $this->attributes['DeprecatedHooks'] = $deprecatedHooks;
578 }
579 }
580 }
581
588 protected function extractDomainEventIngresses( array $info, string $path ) {
589 $this->attributes['DomainEventIngresses'] ??= [];
590 foreach ( $info['DomainEventIngresses'] ?? [] as $subscriber ) {
591 $subscriber['extensionPath'] = $path;
592 $this->attributes['DomainEventIngresses'][] = $subscriber;
593 }
594 }
595
599 protected function extractNamespaces( array $info ) {
600 if ( isset( $info['namespaces'] ) ) {
601 foreach ( $info['namespaces'] as $ns ) {
602 if ( defined( $ns['constant'] ) ) {
603 // If the namespace constant is already defined, use it.
604 // This allows namespace IDs to be overwritten locally.
605 $id = constant( $ns['constant'] );
606 } else {
607 $id = $ns['id'];
608 }
609 $this->defines[ $ns['constant'] ] = $id;
610
611 if ( !( isset( $ns['conditional'] ) && $ns['conditional'] ) ) {
612 // If it is not conditional, register it
613 $this->attributes['ExtensionNamespaces'][$id] = $ns['name'];
614 }
615 if ( isset( $ns['movable'] ) && !$ns['movable'] ) {
616 $this->attributes['ImmovableNamespaces'][] = $id;
617 }
618 if ( isset( $ns['gender'] ) ) {
619 $this->globals['wgExtraGenderNamespaces'][$id] = $ns['gender'];
620 }
621 if ( isset( $ns['subpages'] ) && $ns['subpages'] ) {
622 $this->globals['wgNamespacesWithSubpages'][$id] = true;
623 }
624 if ( isset( $ns['content'] ) && $ns['content'] ) {
625 $this->globals['wgContentNamespaces'][] = $id;
626 }
627 if ( isset( $ns['defaultcontentmodel'] ) ) {
628 $this->globals['wgNamespaceContentModels'][$id] = $ns['defaultcontentmodel'];
629 }
630 if ( isset( $ns['protection'] ) ) {
631 $this->globals['wgNamespaceProtection'][$id] = $ns['protection'];
632 }
633 if ( isset( $ns['capitallinkoverride'] ) ) {
634 $this->globals['wgCapitalLinkOverrides'][$id] = $ns['capitallinkoverride'];
635 }
636 if ( isset( $ns['includable'] ) && !$ns['includable'] ) {
637 $this->globals['wgNonincludableNamespaces'][] = $id;
638 }
639 }
640 }
641 }
642
643 protected function extractResourceLoaderModules( string $dir, array $info ) {
644 $defaultPaths = $info['ResourceFileModulePaths'] ?? false;
645 if ( isset( $defaultPaths['localBasePath'] ) ) {
646 if ( $defaultPaths['localBasePath'] === '' ) {
647 // Avoid double slashes (e.g. /extensions/Example//path)
648 $defaultPaths['localBasePath'] = $dir;
649 } else {
650 $defaultPaths['localBasePath'] = "$dir/{$defaultPaths['localBasePath']}";
651 }
652 }
653
654 foreach ( [ 'ResourceModules', 'ResourceModuleSkinStyles', 'OOUIThemePaths' ] as $setting ) {
655 if ( isset( $info[$setting] ) ) {
656 foreach ( $info[$setting] as $name => $data ) {
657 if ( isset( $data['localBasePath'] ) ) {
658 if ( $data['localBasePath'] === '' ) {
659 // Avoid double slashes (e.g. /extensions/Example//path)
660 $data['localBasePath'] = $dir;
661 } else {
662 $data['localBasePath'] = "$dir/{$data['localBasePath']}";
663 }
664 }
665 if ( $defaultPaths ) {
666 $data += $defaultPaths;
667 }
668 $this->attributes[$setting][$name] = $data;
669 }
670 }
671 }
672
673 if ( isset( $info['QUnitTestModule'] ) ) {
674 $data = $info['QUnitTestModule'];
675 if ( isset( $data['localBasePath'] ) ) {
676 if ( $data['localBasePath'] === '' ) {
677 // Avoid double slashes (e.g. /extensions/Example//path)
678 $data['localBasePath'] = $dir;
679 } else {
680 $data['localBasePath'] = "$dir/{$data['localBasePath']}";
681 }
682 }
683 $this->attributes['QUnitTestModule']["test.{$info['name']}"] = $data;
684 }
685
686 if ( isset( $info['MessagePosterModule'] ) ) {
687 $data = $info['MessagePosterModule'];
688 $basePath = $data['localBasePath'] ?? '';
689 $baseDir = $basePath === '' ? $dir : "$dir/$basePath";
690 foreach ( $data['scripts'] ?? [] as $scripts ) {
691 $this->attributes['MessagePosterModule']['scripts'][] =
692 new FilePath( $scripts, $baseDir );
693 }
694 foreach ( $data['dependencies'] ?? [] as $dependency ) {
695 $this->attributes['MessagePosterModule']['dependencies'][] = $dependency;
696 }
697 }
698 }
699
700 protected function extractExtensionMessagesFiles( string $dir, array $info ) {
701 if ( isset( $info['ExtensionMessagesFiles'] ) ) {
702 foreach ( $info['ExtensionMessagesFiles'] as &$file ) {
703 $file = "$dir/$file";
704 }
705 $this->globals["wgExtensionMessagesFiles"] += $info['ExtensionMessagesFiles'];
706 }
707 }
708
709 protected function extractRestModuleFiles( string $dir, array $info ) {
711 if ( isset( $info['RestModuleFiles'] ) ) {
712 foreach ( $info['RestModuleFiles'] as &$file ) {
713 $this->globals["wg$var"][] = "$dir/$file";
714 }
715 }
716 }
717
725 protected function extractMessagesDirs( $dir, array $info ) {
726 if ( isset( $info['MessagesDirs'] ) ) {
727 foreach ( $info['MessagesDirs'] as $name => $files ) {
728 foreach ( (array)$files as $file ) {
729 $this->globals["wgMessagesDirs"][$name][] = "$dir/$file";
730 }
731 }
732 }
733 }
734
742 protected function extractTranslationAliasesDirs( $dir, array $info ) {
743 foreach ( $info['TranslationAliasesDirs'] ?? [] as $name => $files ) {
744 foreach ( (array)$files as $file ) {
745 $this->globals['wgTranslationAliasesDirs'][$name][] = "$dir/$file";
746 }
747 }
748 }
749
756 protected function extractSkins( $dir, array $info ) {
757 if ( isset( $info['ValidSkinNames'] ) ) {
758 foreach ( $info['ValidSkinNames'] as $skinKey => $data ) {
759 if ( isset( $data['args'][0] ) ) {
760 $templateDirectory = $data['args'][0]['templateDirectory'] ?? 'templates';
761 $data['args'][0]['templateDirectory'] = $dir . '/' . $templateDirectory;
762 }
763 $this->globals['wgValidSkinNames'][$skinKey] = $data;
764 }
765 }
766 }
767
771 protected function extractImplicitRights( array $info ) {
772 // Rate limits are only configurable for rights that are either in wgImplicitRights
773 // or in wgAvailableRights. Extensions that define rate limits should not have to
774 // explicitly add them to wgImplicitRights as well, we can do that automatically.
775
776 if ( isset( $info['RateLimits'] ) ) {
777 $rights = array_keys( $info['RateLimits'] );
778
779 if ( isset( $info['AvailableRights'] ) ) {
780 $rights = array_diff( $rights, $info['AvailableRights'] );
781 }
782
783 $this->globals['wgImplicitRights'] = array_merge(
784 $this->globals['wgImplicitRights'] ?? [],
785 $rights
786 );
787 }
788 }
789
794 protected function extractSkinImportPaths( $dir, array $info ) {
795 if ( isset( $info['SkinLessImportPaths'] ) ) {
796 foreach ( $info['SkinLessImportPaths'] as $skin => $subpath ) {
797 $this->attributes['SkinLessImportPaths'][$skin] = "$dir/$subpath";
798 }
799 }
800 }
801
809 protected function extractCredits( $path, array $info ) {
810 $credits = [
811 'path' => $path,
812 'type' => 'other',
813 ];
814 foreach ( self::CREDIT_ATTRIBS as $attr ) {
815 if ( isset( $info[$attr] ) ) {
816 $credits[$attr] = $info[$attr];
817 }
818 }
819
820 $name = $credits['name'];
821
822 // If someone is loading the same thing twice, throw
823 // a nice error (T121493)
824 if ( isset( $this->credits[$name] ) ) {
825 $firstPath = $this->credits[$name]['path'];
826 $secondPath = $credits['path'];
827 throw new InvalidArgumentException(
828 "It was attempted to load $name twice, from $firstPath and $secondPath."
829 );
830 }
831
832 $this->credits[$name] = $credits;
833
834 return $name;
835 }
836
837 protected function extractForeignResourcesDir( array $info, string $name, string $dir ): void {
838 if ( array_key_exists( 'ForeignResourcesDir', $info ) ) {
839 if ( !is_string( $info['ForeignResourcesDir'] ) ) {
840 throw new InvalidArgumentException( "Incorrect ForeignResourcesDir type, must be a string (in $name)" );
841 }
842 $this->attributes['ForeignResourcesDir'][$name] = "{$dir}/{$info['ForeignResourcesDir']}";
843 }
844 }
845
846 protected function extractInstallerTasks( string $path, array $info ): void {
847 if ( isset( $info['InstallerTasks'] ) ) {
848 // Use a fixed path for the schema base path for now. This could be
849 // made configurable if there were a use case for that.
850 $schemaBasePath = $path . '/sql';
851 foreach ( $info['InstallerTasks'] as $taskSpec ) {
852 $this->attributes['InstallerTasks'][]
853 = $taskSpec + [ 'schemaBasePath' => $schemaBasePath ];
854 }
855 }
856 }
857
865 protected function extractConfig1( array $info ) {
866 if ( isset( $info['config'] ) ) {
867 if ( isset( $info['config']['_prefix'] ) ) {
868 $prefix = $info['config']['_prefix'];
869 unset( $info['config']['_prefix'] );
870 } else {
871 $prefix = 'wg';
872 }
873 foreach ( $info['config'] as $key => $val ) {
874 if ( $key[0] !== '@' ) {
875 $this->addConfigGlobal( "$prefix$key", $val, $info['name'] );
876 }
877 }
878 }
879 }
880
890 private function applyPath( array $value, string $dir, bool $ensurePathSuffix = false ): array {
891 $result = [];
892
893 foreach ( $value as $k => $v ) {
894 if ( $ensurePathSuffix && !str_ends_with( $v, '/' ) ) {
895 $v .= '/';
896 }
897 $result[$k] = $dir . '/' . $v;
898 }
899
900 return $result;
901 }
902
911 protected function extractConfig2( array $info, $dir ) {
912 $prefix = $info['config_prefix'] ?? 'wg';
913 if ( isset( $info['config'] ) ) {
914 foreach ( $info['config'] as $key => $data ) {
915 if ( !array_key_exists( 'value', $data ) ) {
916 throw new UnexpectedValueException( "Missing value for config $key" );
917 }
918
919 $value = $data['value'];
920 if ( isset( $data['path'] ) && $data['path'] ) {
921 if ( is_array( $value ) ) {
922 $value = $this->applyPath( $value, $dir );
923 } else {
924 $value = "$dir/$value";
925 }
926 }
927 if ( isset( $data['merge_strategy'] ) ) {
928 $value[ExtensionRegistry::MERGE_STRATEGY] = $data['merge_strategy'];
929 }
930 $this->addConfigGlobal( "$prefix$key", $value, $info['name'] );
931 $data['providedby'] = $info['name'];
932 if ( isset( $info['ConfigRegistry'][0] ) ) {
933 $data['configregistry'] = array_keys( $info['ConfigRegistry'] )[0];
934 }
935 }
936 }
937 }
938
946 private function addConfigGlobal( $key, $value, $extName ) {
947 if ( array_key_exists( $key, $this->globals ) ) {
948 throw new RuntimeException(
949 "The configuration setting '$key' was already set by MediaWiki core or"
950 . " another extension, and cannot be set again by $extName." );
951 }
952 if ( isset( $value[ExtensionRegistry::MERGE_STRATEGY] ) &&
953 $value[ExtensionRegistry::MERGE_STRATEGY] === 'array_merge_recursive' ) {
955 "Using the array_merge_recursive merge strategy in extension.json and skin.json" .
956 " was deprecated in MediaWiki 1.42",
957 "1.42"
958 );
959 }
960 $this->globals[$key] = $value;
961 }
962
963 protected function extractPathBasedGlobal( string $global, string $dir, array $paths ) {
964 foreach ( $paths as $path ) {
965 $this->globals[$global][] = "$dir/$path";
966 }
967 }
968
977 protected function storeToArrayRecursive( $path, $name, $value, &$array ) {
978 if ( !is_array( $value ) ) {
979 throw new InvalidArgumentException( "The value for '$name' should be an array (from $path)" );
980 }
981 if ( isset( $array[$name] ) ) {
982 $array[$name] = array_merge_recursive( $array[$name], $value );
983 } else {
984 $array[$name] = $value;
985 }
986 }
987
998 protected function storeToArray( $path, $name, $value, &$array ) {
999 if ( !is_array( $value ) ) {
1000 throw new InvalidArgumentException( "The value for '$name' should be an array (from $path)" );
1001 }
1002 if ( isset( $array[$name] ) ) {
1003 $array[$name] = array_merge( $array[$name], $value );
1004 } else {
1005 $array[$name] = $value;
1006 }
1007 }
1008
1023 public function getExtractedAutoloadInfo( bool $includeDev = false ): array {
1024 $autoload = $this->autoload;
1025
1026 if ( $includeDev ) {
1027 $autoload['classes'] += $this->autoloadDev['classes'];
1028 $autoload['namespaces'] += $this->autoloadDev['namespaces'];
1029
1030 // NOTE: This is here for completeness. Per MW 1.39,
1031 // $this->autoloadDev['files'] is always empty.
1032 // So avoid the performance hit of array_merge().
1033 if ( !empty( $this->autoloadDev['files'] ) ) {
1034 // NOTE: Don't use += with numeric keys!
1035 // Could use PHPUtils::pushArray.
1036 $autoload['files'] = array_merge(
1037 $autoload['files'],
1038 $this->autoloadDev['files']
1039 );
1040 }
1041 }
1042
1043 return $autoload;
1044 }
1045
1046 private function extractAutoload( array $info, string $dir ) {
1047 if ( isset( $info['load_composer_autoloader'] ) && $info['load_composer_autoloader'] === true ) {
1048 $file = "$dir/vendor/autoload.php";
1049 if ( file_exists( $file ) ) {
1050 $this->autoload['files'][] = $file;
1051 }
1052 }
1053
1054 if ( isset( $info['AutoloadClasses'] ) ) {
1055 $paths = $this->applyPath( $info['AutoloadClasses'], $dir );
1056 $this->autoload['classes'] += $paths;
1057 }
1058
1059 if ( isset( $info['AutoloadNamespaces'] ) ) {
1060 $paths = $this->applyPath( $info['AutoloadNamespaces'], $dir, true );
1061 $this->autoload['namespaces'] += $paths;
1062 }
1063
1064 if ( isset( $info['TestAutoloadClasses'] ) ) {
1065 $paths = $this->applyPath( $info['TestAutoloadClasses'], $dir );
1066 $this->autoloadDev['classes'] += $paths;
1067 }
1068
1069 if ( isset( $info['TestAutoloadNamespaces'] ) ) {
1070 $paths = $this->applyPath( $info['TestAutoloadNamespaces'], $dir, true );
1071 $this->autoloadDev['namespaces'] += $paths;
1072 }
1073 }
1074}
1075
1077class_alias( ExtensionProcessor::class, 'ExtensionProcessor' );
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:69
A class containing constants representing the names of configuration variables.
const HiddenPrefs
Name constant for the HiddenPrefs setting, for use with Config::get()
const DefaultUserOptions
Name constant for the DefaultUserOptions setting, for use with Config::get()
const FeedClasses
Name constant for the FeedClasses setting, for use with Config::get()
const AvailableRights
Name constant for the AvailableRights setting, for use with Config::get()
const APIListModules
Name constant for the APIListModules setting, for use with Config::get()
const APIFormatModules
Name constant for the APIFormatModules setting, for use with Config::get()
const ImplicitGroups
Name constant for the ImplicitGroups setting, for use with Config::get()
const RemoveCredentialsBlacklist
Name constant for the RemoveCredentialsBlacklist setting, for use with Config::get()
const LogRestrictions
Name constant for the LogRestrictions setting, for use with Config::get()
const ExtensionFunctions
Name constant for the ExtensionFunctions setting, for use with Config::get()
const ActionFilteredLogs
Name constant for the ActionFilteredLogs setting, for use with Config::get()
const RevokePermissions
Name constant for the RevokePermissions setting, for use with Config::get()
const RecentChangesFlags
Name constant for the RecentChangesFlags setting, for use with Config::get()
const GroupsRemoveFromSelf
Name constant for the GroupsRemoveFromSelf setting, for use with Config::get()
const Actions
Name constant for the Actions setting, for use with Config::get()
const CentralIdLookupProviders
Name constant for the CentralIdLookupProviders setting, for use with Config::get()
const ExtensionEntryPointListFiles
Name constant for the ExtensionEntryPointListFiles setting, for use with Config::get()
const APIModules
Name constant for the APIModules setting, for use with Config::get()
const LogNames
Name constant for the LogNames setting, for use with Config::get()
const SpecialPages
Name constant for the SpecialPages setting, for use with Config::get()
const UserRegistrationProviders
Name constant for the UserRegistrationProviders setting, for use with Config::get()
const AuthManagerAutoConfig
Name constant for the AuthManagerAutoConfig setting, for use with Config::get()
const FilterLogTypes
Name constant for the FilterLogTypes setting, for use with Config::get()
const LogTypes
Name constant for the LogTypes setting, for use with Config::get()
const ResourceLoaderSources
Name constant for the ResourceLoaderSources setting, for use with Config::get()
const LogHeaders
Name constant for the LogHeaders setting, for use with Config::get()
const JobClasses
Name constant for the JobClasses setting, for use with Config::get()
const RateLimits
Name constant for the RateLimits setting, for use with Config::get()
const MediaHandlers
Name constant for the MediaHandlers setting, for use with Config::get()
const OutputPipelineStages
Name constant for the OutputPipelineStages setting, for use with Config::get()
const GrantRiskGroups
Name constant for the GrantRiskGroups setting, for use with Config::get()
const GrantPermissionGroups
Name constant for the GrantPermissionGroups setting, for use with Config::get()
const GroupPermissions
Name constant for the GroupPermissions setting, for use with Config::get()
const AddGroups
Name constant for the AddGroups setting, for use with Config::get()
const ConfigRegistry
Name constant for the ConfigRegistry setting, for use with Config::get()
const FileExtensions
Name constant for the FileExtensions setting, for use with Config::get()
const SessionProviders
Name constant for the SessionProviders setting, for use with Config::get()
const RawHtmlMessages
Name constant for the RawHtmlMessages setting, for use with Config::get()
const LogActionsHandlers
Name constant for the LogActionsHandlers setting, for use with Config::get()
const LogActions
Name constant for the LogActions setting, for use with Config::get()
const APIPropModules
Name constant for the APIPropModules setting, for use with Config::get()
const PrivilegedGroups
Name constant for the PrivilegedGroups setting, for use with Config::get()
const APIMetaModules
Name constant for the APIMetaModules setting, for use with Config::get()
const GroupsAddToSelf
Name constant for the GroupsAddToSelf setting, for use with Config::get()
const ContentHandlers
Name constant for the ContentHandlers setting, for use with Config::get()
const GrantPermissions
Name constant for the GrantPermissions setting, for use with Config::get()
const ChangeCredentialsBlacklist
Name constant for the ChangeCredentialsBlacklist setting, for use with Config::get()
const ReauthenticateTime
Name constant for the ReauthenticateTime setting, for use with Config::get()
const RemoveGroups
Name constant for the RemoveGroups setting, for use with Config::get()
const PasswordPolicy
Name constant for the PasswordPolicy setting, for use with Config::get()
const ConditionalUserOptions
Name constant for the ConditionalUserOptions setting, for use with Config::get()
const RestAPIAdditionalRouteFiles
Name constant for the RestAPIAdditionalRouteFiles setting, for use with Config::get()
Load extension manifests and then aggregate their contents.
string[][] $autoload
Autoloader information.
array $extAttributes
Extension attributes, keyed by name => settings.
extractDomainEventIngresses(array $info, string $path)
Extract domain event subscribers.
extractHooks(array $info, string $path)
Extract hook information from Hooks and HookHandler attributes.
getExtractedAutoloadInfo(bool $includeDev=false)
Returns the extracted autoload info.
array $attributes
Anything else in the $info that hasn't already been processed.
extractTranslationAliasesDirs( $dir, array $info)
Set localization related settings, which need to be expanded to use absolute paths.
extractMessagesDirs( $dir, array $info)
Set message-related settings, which need to be expanded to use absolute paths.
extractPathBasedGlobal(string $global, string $dir, array $paths)
extractExtensionMessagesFiles(string $dir, array $info)
const MERGE_STRATEGIES
Mapping of global settings to their specific merge strategies.
storeToArrayRecursive( $path, $name, $value, &$array)
Stores $value to $array; using array_merge_recursive() if $array already contains $name.
const CREDIT_ATTRIBS
Keys that are part of the extension credits.
extractForeignResourcesDir(array $info, string $name, string $dir)
extractResourceLoaderModules(string $dir, array $info)
array $defines
Things that should be define()'d.
storeToArray( $path, $name, $value, &$array)
Stores $value to $array; using array_merge() if $array already contains $name.
const CORE_ATTRIBS
Top-level attributes that come from MW core.
extractConfig2(array $info, $dir)
Set configuration settings for manifest_version == 2.
const NOT_ATTRIBS
Things that are not 'attributes', and are not in $globalSettings or CREDIT_ATTRIBS.
extractSkins( $dir, array $info)
Extract skins and handle path correction for templateDirectory.
extractInfoFromFile(string $path)
Extracts extension info from the given JSON file.
string[][] $autoloadDev
Autoloader information for development.
array $globals
Stuff that is going to be set to $GLOBALS.
extractInfo( $path, array $info, $version)
extractImplicitRights(array $info)
Extract any user rights that should be granted implicitly.
extractConfig1(array $info)
Set configuration settings for manifest_version == 1.
getRequirements(array $info, $includeDev)
Get the requirements for the provided info.1.26array Where keys are the name to have a constraint on,...
extractNamespaces(array $info)
Register namespaces with the appropriate global settings.
static array $globalSettings
Keys that should be set to $GLOBALS.
callable[] $callbacks
Things to be called once the registration of these extensions is done.
const MERGE_STRATEGY
Special key that defines the merge strategy.
A path to a bundled file (such as JavaScript or CSS), along with a remote and local base path.
Definition FilePath.php:20
Generic processor that reads associated arrays and registers whatever is required.
Definition Processor.php:11