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 ];
95
102 protected const MERGE_STRATEGIES = [
103 'wgAuthManagerAutoConfig' => 'array_plus_2d',
104 'wgCapitalLinkOverrides' => 'array_plus',
105 'wgExtraGenderNamespaces' => 'array_plus',
106 'wgGrantPermissions' => 'array_plus_2d',
107 'wgGroupPermissions' => 'array_plus_2d',
108 'wgHooks' => 'array_merge_recursive',
109 'wgNamespaceContentModels' => 'array_plus',
110 'wgNamespaceProtection' => 'array_plus',
111 'wgNamespacesWithSubpages' => 'array_plus',
112 'wgPasswordPolicy' => 'array_merge_recursive',
113 'wgRateLimits' => 'array_plus_2d',
114 'wgRevokePermissions' => 'array_plus_2d',
115 ];
116
120 protected const CREDIT_ATTRIBS = [
121 'type',
122 'author',
123 'description',
124 'descriptionmsg',
125 'license-name',
126 'name',
127 'namemsg',
128 'url',
129 'version',
130 ];
131
136 protected const NOT_ATTRIBS = [
137 'callback',
138 'config',
139 'config_prefix',
140 'load_composer_autoloader',
141 'manifest_version',
142 'namespaces',
143 'requires',
144 'AutoloadClasses',
145 'AutoloadNamespaces',
146 'ExtensionMessagesFiles',
147 'TranslationAliasesDirs',
148 'ForeignResourcesDir',
149 'Hooks',
150 'DomainEventSubscribers',
151 'MessagePosterModule',
152 'MessagesDirs',
153 'OOUIThemePaths',
154 'QUnitTestModule',
155 'ResourceFileModulePaths',
156 'ResourceModuleSkinStyles',
157 'ResourceModules',
158 'ServiceWiringFiles',
159 ];
160
168 protected $globals = [
169 'wgExtensionMessagesFiles' => [],
170 'wgRestAPIAdditionalRouteFiles' => [],
171 'wgMessagesDirs' => [],
172 'TranslationAliasesDirs' => [],
173 ];
174
180 protected $defines = [];
181
189 protected $callbacks = [];
190
194 protected $credits = [];
195
203 protected $autoload = [
204 'files' => [],
205 'classes' => [],
206 'namespaces' => [],
207 ];
208
215 protected $autoloadDev = [
216 'files' => [],
217 'classes' => [],
218 'namespaces' => [],
219 ];
220
227 protected $attributes = [];
228
235 protected $extAttributes = [];
236
244 public function extractInfoFromFile( string $path ) {
245 $json = file_get_contents( $path );
246 $info = json_decode( $json, true );
247
248 if ( !$info ) {
249 throw new RuntimeException( "Failed to load JSON data from $path" );
250 }
251
252 $this->extractInfo( $path, $info, $info['manifest_version'] );
253 }
254
260 public function extractInfo( $path, array $info, $version ) {
261 $dir = dirname( $path );
262 $this->extractHooks( $info, $path );
263 $this->extractDomainEventSubscribers( $info, $path );
264 $this->extractExtensionMessagesFiles( $dir, $info );
265 $this->extractRestModuleFiles( $dir, $info );
266 $this->extractMessagesDirs( $dir, $info );
267 $this->extractTranslationAliasesDirs( $dir, $info );
268 $this->extractSkins( $dir, $info );
269 $this->extractSkinImportPaths( $dir, $info );
270 $this->extractNamespaces( $info );
271 $this->extractImplicitRights( $info );
272 $this->extractResourceLoaderModules( $dir, $info );
273 $this->extractInstallerTasks( $dir, $info );
274 if ( isset( $info['ServiceWiringFiles'] ) ) {
276 'wgServiceWiringFiles',
277 $dir,
278 $info['ServiceWiringFiles']
279 );
280 }
281 $name = $this->extractCredits( $path, $info );
282 if ( isset( $info['callback'] ) ) {
283 $this->callbacks[$name] = $info['callback'];
284 }
285
286 $this->extractAutoload( $info, $dir );
287
288 // config should be after all core globals are extracted,
289 // so duplicate setting detection will work fully
290 if ( $version >= 2 ) {
291 $this->extractConfig2( $info, $dir );
292 } else {
293 // $version === 1
294 $this->extractConfig1( $info );
295 }
296
297 // Record the extension name in the ParsoidModules property
298 if ( isset( $info['ParsoidModules'] ) ) {
299 foreach ( $info['ParsoidModules'] as &$module ) {
300 if ( is_string( $module ) ) {
301 $className = $module;
302 $module = [
303 'class' => $className,
304 ];
305 }
306 $module['name'] = $name;
307 }
308 }
309
310 $this->extractForeignResourcesDir( $info, $name, $dir );
311
312 if ( $version >= 2 ) {
313 $this->extractAttributes( $path, $info );
314 }
315
316 foreach ( $info as $key => $val ) {
317 // If it's a global setting,
318 if ( in_array( $key, self::$globalSettings ) ) {
319 $this->storeToArrayRecursive( $path, "wg$key", $val, $this->globals );
320 continue;
321 }
322 // Ignore anything that starts with a @
323 if ( $key[0] === '@' ) {
324 continue;
325 }
326
327 if ( $version >= 2 ) {
328 // Only allowed attributes are set
329 if ( in_array( $key, self::CORE_ATTRIBS ) ) {
330 $this->storeToArray( $path, $key, $val, $this->attributes );
331 }
332 } else {
333 // version === 1
334 if ( !in_array( $key, self::NOT_ATTRIBS )
335 && !in_array( $key, self::CREDIT_ATTRIBS )
336 ) {
337 // If it's not disallowed, it's an attribute
338 $this->storeToArrayRecursive( $path, $key, $val, $this->attributes );
339 }
340 }
341 }
342 }
343
348 protected function extractAttributes( $path, array $info ) {
349 if ( isset( $info['attributes'] ) ) {
350 foreach ( $info['attributes'] as $extName => $value ) {
351 $this->storeToArrayRecursive( $path, $extName, $value, $this->extAttributes );
352 }
353 }
354 }
355
356 public function getExtractedInfo( bool $includeDev = false ) {
357 // Make sure the merge strategies are set
358 foreach ( $this->globals as $key => $val ) {
359 if ( isset( self::MERGE_STRATEGIES[$key] ) ) {
360 $this->globals[$key][ExtensionRegistry::MERGE_STRATEGY] = self::MERGE_STRATEGIES[$key];
361 }
362 }
363
364 // Merge $this->extAttributes into $this->attributes depending on what is loaded
365 foreach ( $this->extAttributes as $extName => $value ) {
366 // Only set the attribute if $extName is loaded (and hence present in credits)
367 if ( isset( $this->credits[$extName] ) ) {
368 foreach ( $value as $attrName => $attrValue ) {
370 '', // Don't provide a path since it's impossible to generate an error here
371 $extName . $attrName,
372 $attrValue,
373 $this->attributes
374 );
375 }
376 unset( $this->extAttributes[$extName] );
377 }
378 }
379
380 $autoload = $this->getExtractedAutoloadInfo( $includeDev );
381
382 return [
383 'globals' => $this->globals,
384 'defines' => $this->defines,
385 'callbacks' => $this->callbacks,
386 'credits' => $this->credits,
387 'attributes' => $this->attributes,
388 'autoloaderPaths' => $autoload['files'],
389 'autoloaderClasses' => $autoload['classes'],
390 'autoloaderNS' => $autoload['namespaces'],
391 ];
392 }
393
394 public function getRequirements( array $info, $includeDev ) {
395 // Quick shortcuts
396 if ( !$includeDev || !isset( $info['dev-requires'] ) ) {
397 return $info['requires'] ?? [];
398 }
399
400 if ( !isset( $info['requires'] ) ) {
401 return $info['dev-requires'] ?? [];
402 }
403
404 // OK, we actually have to merge everything
405 $merged = [];
406
407 // Helper that combines version requirements by
408 // picking the non-null if one is, or combines
409 // the two. Note that it is not possible for
410 // both inputs to be null.
411 $pick = static function ( $a, $b ) {
412 if ( $a === null ) {
413 return $b;
414 } elseif ( $b === null ) {
415 return $a;
416 } else {
417 return "$a $b";
418 }
419 };
420
421 $req = $info['requires'];
422 $dev = $info['dev-requires'];
423 if ( isset( $req['MediaWiki'] ) || isset( $dev['MediaWiki'] ) ) {
424 $merged['MediaWiki'] = $pick(
425 $req['MediaWiki'] ?? null,
426 $dev['MediaWiki'] ?? null
427 );
428 }
429
430 $platform = array_merge(
431 array_keys( $req['platform'] ?? [] ),
432 array_keys( $dev['platform'] ?? [] )
433 );
434 if ( $platform ) {
435 foreach ( $platform as $pkey ) {
436 if ( $pkey === 'php' ) {
437 $value = $pick(
438 $req['platform']['php'] ?? null,
439 $dev['platform']['php'] ?? null
440 );
441 } else {
442 // Prefer dev value, but these should be constant
443 // anyway (ext-* and ability-*)
444 $value = $dev['platform'][$pkey] ?? $req['platform'][$pkey];
445 }
446 $merged['platform'][$pkey] = $value;
447 }
448 }
449
450 foreach ( [ 'extensions', 'skins' ] as $thing ) {
451 $things = array_merge(
452 array_keys( $req[$thing] ?? [] ),
453 array_keys( $dev[$thing] ?? [] )
454 );
455 foreach ( $things as $name ) {
456 $merged[$thing][$name] = $pick(
457 $req[$thing][$name] ?? null,
458 $dev[$thing][$name] ?? null
459 );
460 }
461 }
462
463 return $merged;
464 }
465
478 private function setArrayHookHandler(
479 array $callback,
480 array $hookHandlersAttr,
481 string $name,
482 string $path
483 ) {
484 if ( isset( $callback['handler'] ) ) {
485 $handlerName = $callback['handler'];
486 $handlerDefinition = $hookHandlersAttr[$handlerName] ?? false;
487 if ( !$handlerDefinition ) {
488 throw new UnexpectedValueException(
489 "Missing handler definition for $name in HookHandlers attribute in $path"
490 );
491 }
492 $callback['handler'] = $handlerDefinition;
493 $callback['extensionPath'] = $path;
494 $this->attributes['Hooks'][$name][] = $callback;
495 } else {
496 foreach ( $callback as $callable ) {
497 if ( is_array( $callable ) ) {
498 if ( isset( $callable['handler'] ) ) { // Non-legacy style handler
499 $this->setArrayHookHandler( $callable, $hookHandlersAttr, $name, $path );
500 } else { // Legacy style handler array
501 $this->globals['wgHooks'][$name][] = $callable;
502 }
503 } elseif ( is_string( $callable ) ) {
504 $this->setStringHookHandler( $callable, $hookHandlersAttr, $name, $path );
505 }
506 }
507 }
508 }
509
520 private function setStringHookHandler(
521 string $callback,
522 array $hookHandlersAttr,
523 string $name,
524 string $path
525 ) {
526 if ( isset( $hookHandlersAttr[$callback] ) ) {
527 $handler = [
528 'handler' => $hookHandlersAttr[$callback],
529 'extensionPath' => $path
530 ];
531 $this->attributes['Hooks'][$name][] = $handler;
532 } else { // legacy style handler
533 $this->globals['wgHooks'][$name][] = $callback;
534 }
535 }
536
545 protected function extractHooks( array $info, string $path ) {
546 $extName = $info['name'];
547 if ( isset( $info['Hooks'] ) ) {
548 $hookHandlersAttr = [];
549 foreach ( $info['HookHandlers'] ?? [] as $name => $def ) {
550 $hookHandlersAttr[$name] = [ 'name' => "$extName-$name" ] + $def;
551 }
552 foreach ( $info['Hooks'] as $name => $callback ) {
553 if ( is_string( $callback ) ) {
554 $this->setStringHookHandler( $callback, $hookHandlersAttr, $name, $path );
555 } elseif ( is_array( $callback ) ) {
556 $this->setArrayHookHandler( $callback, $hookHandlersAttr, $name, $path );
557 }
558 }
559 }
560 if ( isset( $info['DeprecatedHooks'] ) ) {
561 $deprecatedHooks = [];
562 foreach ( $info['DeprecatedHooks'] as $name => $deprecatedHookInfo ) {
563 $deprecatedHookInfo += [ 'component' => $extName ];
564 $deprecatedHooks[$name] = $deprecatedHookInfo;
565 }
566 if ( isset( $this->attributes['DeprecatedHooks'] ) ) {
567 $this->attributes['DeprecatedHooks'] += $deprecatedHooks;
568 } else {
569 $this->attributes['DeprecatedHooks'] = $deprecatedHooks;
570 }
571 }
572 }
573
580 protected function extractDomainEventSubscribers( array $info, string $path ) {
581 $this->attributes['DomainEventSubscribers'] ??= [];
582 foreach ( $info['DomainEventSubscribers'] ?? [] as $subscriber ) {
583 $subscriber['extensionPath'] = $path;
584 $this->attributes['DomainEventSubscribers'][] = $subscriber;
585 }
586 }
587
593 protected function extractNamespaces( array $info ) {
594 if ( isset( $info['namespaces'] ) ) {
595 foreach ( $info['namespaces'] as $ns ) {
596 if ( defined( $ns['constant'] ) ) {
597 // If the namespace constant is already defined, use it.
598 // This allows namespace IDs to be overwritten locally.
599 $id = constant( $ns['constant'] );
600 } else {
601 $id = $ns['id'];
602 }
603 $this->defines[ $ns['constant'] ] = $id;
604
605 if ( !( isset( $ns['conditional'] ) && $ns['conditional'] ) ) {
606 // If it is not conditional, register it
607 $this->attributes['ExtensionNamespaces'][$id] = $ns['name'];
608 }
609 if ( isset( $ns['movable'] ) && !$ns['movable'] ) {
610 $this->attributes['ImmovableNamespaces'][] = $id;
611 }
612 if ( isset( $ns['gender'] ) ) {
613 $this->globals['wgExtraGenderNamespaces'][$id] = $ns['gender'];
614 }
615 if ( isset( $ns['subpages'] ) && $ns['subpages'] ) {
616 $this->globals['wgNamespacesWithSubpages'][$id] = true;
617 }
618 if ( isset( $ns['content'] ) && $ns['content'] ) {
619 $this->globals['wgContentNamespaces'][] = $id;
620 }
621 if ( isset( $ns['defaultcontentmodel'] ) ) {
622 $this->globals['wgNamespaceContentModels'][$id] = $ns['defaultcontentmodel'];
623 }
624 if ( isset( $ns['protection'] ) ) {
625 $this->globals['wgNamespaceProtection'][$id] = $ns['protection'];
626 }
627 if ( isset( $ns['capitallinkoverride'] ) ) {
628 $this->globals['wgCapitalLinkOverrides'][$id] = $ns['capitallinkoverride'];
629 }
630 if ( isset( $ns['includable'] ) && !$ns['includable'] ) {
631 $this->globals['wgNonincludableNamespaces'][] = $id;
632 }
633 }
634 }
635 }
636
637 protected function extractResourceLoaderModules( $dir, array $info ) {
638 $defaultPaths = $info['ResourceFileModulePaths'] ?? false;
639 if ( isset( $defaultPaths['localBasePath'] ) ) {
640 if ( $defaultPaths['localBasePath'] === '' ) {
641 // Avoid double slashes (e.g. /extensions/Example//path)
642 $defaultPaths['localBasePath'] = $dir;
643 } else {
644 $defaultPaths['localBasePath'] = "$dir/{$defaultPaths['localBasePath']}";
645 }
646 }
647
648 foreach ( [ 'ResourceModules', 'ResourceModuleSkinStyles', 'OOUIThemePaths' ] as $setting ) {
649 if ( isset( $info[$setting] ) ) {
650 foreach ( $info[$setting] as $name => $data ) {
651 if ( isset( $data['localBasePath'] ) ) {
652 if ( $data['localBasePath'] === '' ) {
653 // Avoid double slashes (e.g. /extensions/Example//path)
654 $data['localBasePath'] = $dir;
655 } else {
656 $data['localBasePath'] = "$dir/{$data['localBasePath']}";
657 }
658 }
659 if ( $defaultPaths ) {
660 $data += $defaultPaths;
661 }
662 $this->attributes[$setting][$name] = $data;
663 }
664 }
665 }
666
667 if ( isset( $info['QUnitTestModule'] ) ) {
668 $data = $info['QUnitTestModule'];
669 if ( isset( $data['localBasePath'] ) ) {
670 if ( $data['localBasePath'] === '' ) {
671 // Avoid double slashes (e.g. /extensions/Example//path)
672 $data['localBasePath'] = $dir;
673 } else {
674 $data['localBasePath'] = "$dir/{$data['localBasePath']}";
675 }
676 }
677 $this->attributes['QUnitTestModules']["test.{$info['name']}"] = $data;
678 }
679
680 if ( isset( $info['MessagePosterModule'] ) ) {
681 $data = $info['MessagePosterModule'];
682 $basePath = $data['localBasePath'] ?? '';
683 $baseDir = $basePath === '' ? $dir : "$dir/$basePath";
684 foreach ( $data['scripts'] ?? [] as $scripts ) {
685 $this->attributes['MessagePosterModule']['scripts'][] =
686 new FilePath( $scripts, $baseDir );
687 }
688 foreach ( $data['dependencies'] ?? [] as $dependency ) {
689 $this->attributes['MessagePosterModule']['dependencies'][] = $dependency;
690 }
691 }
692 }
693
694 protected function extractExtensionMessagesFiles( $dir, array $info ) {
695 if ( isset( $info['ExtensionMessagesFiles'] ) ) {
696 foreach ( $info['ExtensionMessagesFiles'] as &$file ) {
697 $file = "$dir/$file";
698 }
699 $this->globals["wgExtensionMessagesFiles"] += $info['ExtensionMessagesFiles'];
700 }
701 }
702
703 protected function extractRestModuleFiles( $dir, array $info ) {
705 if ( isset( $info['RestModuleFiles'] ) ) {
706 foreach ( $info['RestModuleFiles'] as &$file ) {
707 $this->globals["wg$var"][] = "$dir/$file";
708 }
709 }
710 }
711
719 protected function extractMessagesDirs( $dir, array $info ) {
720 if ( isset( $info['MessagesDirs'] ) ) {
721 foreach ( $info['MessagesDirs'] as $name => $files ) {
722 foreach ( (array)$files as $file ) {
723 $this->globals["wgMessagesDirs"][$name][] = "$dir/$file";
724 }
725 }
726 }
727 }
728
736 protected function extractTranslationAliasesDirs( $dir, array $info ) {
737 foreach ( $info['TranslationAliasesDirs'] ?? [] as $name => $files ) {
738 foreach ( (array)$files as $file ) {
739 $this->globals['wgTranslationAliasesDirs'][$name][] = "$dir/$file";
740 }
741 }
742 }
743
750 protected function extractSkins( $dir, array $info ) {
751 if ( isset( $info['ValidSkinNames'] ) ) {
752 foreach ( $info['ValidSkinNames'] as $skinKey => $data ) {
753 if ( isset( $data['args'][0] ) ) {
754 $templateDirectory = $data['args'][0]['templateDirectory'] ?? 'templates';
755 $data['args'][0]['templateDirectory'] = $dir . '/' . $templateDirectory;
756 }
757 $this->globals['wgValidSkinNames'][$skinKey] = $data;
758 }
759 }
760 }
761
767 protected function extractImplicitRights( array $info ) {
768 // Rate limits are only configurable for rights that are either in wgImplicitRights
769 // or in wgAvailableRights. Extensions that define rate limits should not have to
770 // explicitly add them to wgImplicitRights as well, we can do that automatically.
771
772 if ( isset( $info['RateLimits'] ) ) {
773 $rights = array_keys( $info['RateLimits'] );
774
775 if ( isset( $info['AvailableRights'] ) ) {
776 $rights = array_diff( $rights, $info['AvailableRights'] );
777 }
778
779 $this->globals['wgImplicitRights'] = array_merge(
780 $this->globals['wgImplicitRights'] ?? [],
781 $rights
782 );
783 }
784 }
785
790 protected function extractSkinImportPaths( $dir, array $info ) {
791 if ( isset( $info['SkinLessImportPaths'] ) ) {
792 foreach ( $info['SkinLessImportPaths'] as $skin => $subpath ) {
793 $this->attributes['SkinLessImportPaths'][$skin] = "$dir/$subpath";
794 }
795 }
796 }
797
805 protected function extractCredits( $path, array $info ) {
806 $credits = [
807 'path' => $path,
808 'type' => 'other',
809 ];
810 foreach ( self::CREDIT_ATTRIBS as $attr ) {
811 if ( isset( $info[$attr] ) ) {
812 $credits[$attr] = $info[$attr];
813 }
814 }
815
816 $name = $credits['name'];
817
818 // If someone is loading the same thing twice, throw
819 // a nice error (T121493)
820 if ( isset( $this->credits[$name] ) ) {
821 $firstPath = $this->credits[$name]['path'];
822 $secondPath = $credits['path'];
823 throw new InvalidArgumentException(
824 "It was attempted to load $name twice, from $firstPath and $secondPath."
825 );
826 }
827
828 $this->credits[$name] = $credits;
829
830 return $name;
831 }
832
833 protected function extractForeignResourcesDir( array $info, string $name, string $dir ): void {
834 if ( array_key_exists( 'ForeignResourcesDir', $info ) ) {
835 if ( !is_string( $info['ForeignResourcesDir'] ) ) {
836 throw new InvalidArgumentException( "Incorrect ForeignResourcesDir type, must be a string (in $name)" );
837 }
838 $this->attributes['ForeignResourcesDir'][$name] = "{$dir}/{$info['ForeignResourcesDir']}";
839 }
840 }
841
842 protected function extractInstallerTasks( string $path, array $info ): void {
843 if ( isset( $info['InstallerTasks'] ) ) {
844 // Use a fixed path for the schema base path for now. This could be
845 // made configurable if there were a use case for that.
846 $schemaBasePath = $path . '/sql';
847 foreach ( $info['InstallerTasks'] as $taskSpec ) {
848 $this->attributes['InstallerTasks'][]
849 = $taskSpec + [ 'schemaBasePath' => $schemaBasePath ];
850 }
851 }
852 }
853
861 protected function extractConfig1( array $info ) {
862 if ( isset( $info['config'] ) ) {
863 if ( isset( $info['config']['_prefix'] ) ) {
864 $prefix = $info['config']['_prefix'];
865 unset( $info['config']['_prefix'] );
866 } else {
867 $prefix = 'wg';
868 }
869 foreach ( $info['config'] as $key => $val ) {
870 if ( $key[0] !== '@' ) {
871 $this->addConfigGlobal( "$prefix$key", $val, $info['name'] );
872 }
873 }
874 }
875 }
876
885 private function applyPath( array $value, string $dir ): array {
886 $result = [];
887
888 foreach ( $value as $k => $v ) {
889 $result[$k] = $dir . '/' . $v;
890 }
891
892 return $result;
893 }
894
903 protected function extractConfig2( array $info, $dir ) {
904 $prefix = $info['config_prefix'] ?? 'wg';
905 if ( isset( $info['config'] ) ) {
906 foreach ( $info['config'] as $key => $data ) {
907 if ( !array_key_exists( 'value', $data ) ) {
908 throw new UnexpectedValueException( "Missing value for config $key" );
909 }
910
911 $value = $data['value'];
912 if ( isset( $data['path'] ) && $data['path'] ) {
913 if ( is_array( $value ) ) {
914 $value = $this->applyPath( $value, $dir );
915 } else {
916 $value = "$dir/$value";
917 }
918 }
919 if ( isset( $data['merge_strategy'] ) ) {
920 $value[ExtensionRegistry::MERGE_STRATEGY] = $data['merge_strategy'];
921 }
922 $this->addConfigGlobal( "$prefix$key", $value, $info['name'] );
923 $data['providedby'] = $info['name'];
924 if ( isset( $info['ConfigRegistry'][0] ) ) {
925 $data['configregistry'] = array_keys( $info['ConfigRegistry'] )[0];
926 }
927 }
928 }
929 }
930
938 private function addConfigGlobal( $key, $value, $extName ) {
939 if ( array_key_exists( $key, $this->globals ) ) {
940 throw new RuntimeException(
941 "The configuration setting '$key' was already set by MediaWiki core or"
942 . " another extension, and cannot be set again by $extName." );
943 }
944 if ( isset( $value[ExtensionRegistry::MERGE_STRATEGY] ) &&
945 $value[ExtensionRegistry::MERGE_STRATEGY] === 'array_merge_recursive' ) {
947 "Using the array_merge_recursive merge strategy in extension.json and skin.json" .
948 " was deprecated in MediaWiki 1.42",
949 "1.42"
950 );
951 }
952 $this->globals[$key] = $value;
953 }
954
955 protected function extractPathBasedGlobal( $global, $dir, $paths ) {
956 foreach ( $paths as $path ) {
957 $this->globals[$global][] = "$dir/$path";
958 }
959 }
960
970 protected function storeToArrayRecursive( $path, $name, $value, &$array ) {
971 if ( !is_array( $value ) ) {
972 throw new InvalidArgumentException( "The value for '$name' should be an array (from $path)" );
973 }
974 if ( isset( $array[$name] ) ) {
975 $array[$name] = array_merge_recursive( $array[$name], $value );
976 } else {
977 $array[$name] = $value;
978 }
979 }
980
991 protected function storeToArray( $path, $name, $value, &$array ) {
992 if ( !is_array( $value ) ) {
993 throw new InvalidArgumentException( "The value for '$name' should be an array (from $path)" );
994 }
995 if ( isset( $array[$name] ) ) {
996 $array[$name] = array_merge( $array[$name], $value );
997 } else {
998 $array[$name] = $value;
999 }
1000 }
1001
1016 public function getExtractedAutoloadInfo( bool $includeDev = false ): array {
1017 $autoload = $this->autoload;
1018
1019 if ( $includeDev ) {
1020 $autoload['classes'] += $this->autoloadDev['classes'];
1021 $autoload['namespaces'] += $this->autoloadDev['namespaces'];
1022
1023 // NOTE: This is here for completeness. Per MW 1.39,
1024 // $this->autoloadDev['files'] is always empty.
1025 // So avoid the performance hit of array_merge().
1026 if ( !empty( $this->autoloadDev['files'] ) ) {
1027 // NOTE: Don't use += with numeric keys!
1028 // Could use PHPUtils::pushArray.
1029 $autoload['files'] = array_merge(
1030 $autoload['files'],
1031 $this->autoloadDev['files']
1032 );
1033 }
1034 }
1035
1036 return $autoload;
1037 }
1038
1043 private function extractAutoload( array $info, string $dir ) {
1044 if ( isset( $info['load_composer_autoloader'] ) && $info['load_composer_autoloader'] === true ) {
1045 $file = "$dir/vendor/autoload.php";
1046 if ( file_exists( $file ) ) {
1047 $this->autoload['files'][] = $file;
1048 }
1049 }
1050
1051 if ( isset( $info['AutoloadClasses'] ) ) {
1052 $paths = $this->applyPath( $info['AutoloadClasses'], $dir );
1053 $this->autoload['classes'] += $paths;
1054 }
1055
1056 if ( isset( $info['AutoloadNamespaces'] ) ) {
1057 $paths = $this->applyPath( $info['AutoloadNamespaces'], $dir );
1058 $this->autoload['namespaces'] += $paths;
1059 }
1060
1061 if ( isset( $info['TestAutoloadClasses'] ) ) {
1062 $paths = $this->applyPath( $info['TestAutoloadClasses'], $dir );
1063 $this->autoloadDev['classes'] += $paths;
1064 }
1065
1066 if ( isset( $info['TestAutoloadNamespaces'] ) ) {
1067 $paths = $this->applyPath( $info['TestAutoloadNamespaces'], $dir );
1068 $this->autoloadDev['namespaces'] += $paths;
1069 }
1070 }
1071}
1072
1074class_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:81
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.
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.
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)
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.
extractNamespaces(array $info)
Register namespaces with the appropriate global settings.
extractDomainEventSubscribers(array $info, string $path)
Extract domain event subscribers.
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:34
Generic processor that reads associated arrays and registers whatever is required.
Definition Processor.php:11