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 ];
97
104 protected const MERGE_STRATEGIES = [
105 'wgAddGroups' => 'array_merge_recursive',
106 'wgAuthManagerAutoConfig' => 'array_plus_2d',
107 'wgCapitalLinkOverrides' => 'array_plus',
108 'wgExtraGenderNamespaces' => 'array_plus',
109 'wgGrantPermissions' => 'array_plus_2d',
110 'wgGroupPermissions' => 'array_plus_2d',
111 'wgHooks' => 'array_merge_recursive',
112 'wgNamespaceContentModels' => 'array_plus',
113 'wgNamespaceProtection' => 'array_plus',
114 'wgNamespacesWithSubpages' => 'array_plus',
115 'wgPasswordPolicy' => 'array_merge_recursive',
116 'wgRateLimits' => 'array_plus_2d',
117 'wgRemoveGroups' => 'array_merge_recursive',
118 'wgRevokePermissions' => 'array_plus_2d',
119 ];
120
124 protected const CREDIT_ATTRIBS = [
125 'type',
126 'author',
127 'description',
128 'descriptionmsg',
129 'license-name',
130 'name',
131 'namemsg',
132 'url',
133 'version',
134 ];
135
140 protected const NOT_ATTRIBS = [
141 'callback',
142 'config',
143 'config_prefix',
144 'load_composer_autoloader',
145 'manifest_version',
146 'namespaces',
147 'requires',
148 'AutoloadClasses',
149 'AutoloadNamespaces',
150 'ExtensionMessagesFiles',
151 'TranslationAliasesDirs',
152 'ForeignResourcesDir',
153 'Hooks',
154 'DomainEventIngresses',
155 'MessagePosterModule',
156 'MessagesDirs',
157 'OOUIThemePaths',
158 'QUnitTestModule',
159 'ResourceFileModulePaths',
160 'ResourceModuleSkinStyles',
161 'ResourceModules',
162 'ServiceWiringFiles',
163 ];
164
172 protected $globals = [
173 'wgExtensionMessagesFiles' => [],
174 'wgRestAPIAdditionalRouteFiles' => [],
175 'wgMessagesDirs' => [],
176 'TranslationAliasesDirs' => [],
177 ];
178
184 protected $defines = [];
185
193 protected $callbacks = [];
194
198 protected $credits = [];
199
207 protected $autoload = [
208 'files' => [],
209 'classes' => [],
210 'namespaces' => [],
211 ];
212
219 protected $autoloadDev = [
220 'files' => [],
221 'classes' => [],
222 'namespaces' => [],
223 ];
224
231 protected $attributes = [];
232
239 protected $extAttributes = [];
240
248 public function extractInfoFromFile( string $path ) {
249 $json = file_get_contents( $path );
250 $info = json_decode( $json, true );
251
252 if ( !$info ) {
253 throw new RuntimeException( "Failed to load JSON data from $path" );
254 }
255
256 $this->extractInfo( $path, $info, $info['manifest_version'] );
257 }
258
264 public function extractInfo( $path, array $info, $version ) {
265 $dir = dirname( $path );
266 $this->extractHooks( $info, $path );
267 $this->extractDomainEventIngresses( $info, $path );
268 $this->extractExtensionMessagesFiles( $dir, $info );
269 $this->extractRestModuleFiles( $dir, $info );
270 $this->extractMessagesDirs( $dir, $info );
271 $this->extractTranslationAliasesDirs( $dir, $info );
272 $this->extractSkins( $dir, $info );
273 $this->extractSkinImportPaths( $dir, $info );
274 $this->extractNamespaces( $info );
275 $this->extractImplicitRights( $info );
276 $this->extractResourceLoaderModules( $dir, $info );
277 $this->extractInstallerTasks( $dir, $info );
278 if ( isset( $info['ServiceWiringFiles'] ) ) {
280 'wgServiceWiringFiles',
281 $dir,
282 $info['ServiceWiringFiles']
283 );
284 }
285 $name = $this->extractCredits( $path, $info );
286 if ( isset( $info['callback'] ) ) {
287 $this->callbacks[$name] = $info['callback'];
288 }
289
290 $this->extractAutoload( $info, $dir );
291
292 // config should be after all core globals are extracted,
293 // so duplicate setting detection will work fully
294 if ( $version >= 2 ) {
295 $this->extractConfig2( $info, $dir );
296 } else {
297 // $version === 1
298 $this->extractConfig1( $info );
299 }
300
301 // Record the extension name in the ParsoidModules property
302 if ( isset( $info['ParsoidModules'] ) ) {
303 foreach ( $info['ParsoidModules'] as &$module ) {
304 if ( is_string( $module ) ) {
305 $className = $module;
306 $module = [
307 'class' => $className,
308 ];
309 }
310 $module['name'] ??= $name;
311 }
312 }
313
314 $this->extractForeignResourcesDir( $info, $name, $dir );
315
316 if ( $version >= 2 ) {
317 $this->extractAttributes( $path, $info );
318 }
319
320 foreach ( $info as $key => $val ) {
321 // If it's a global setting,
322 if ( in_array( $key, self::$globalSettings ) ) {
323 $this->storeToArrayRecursive( $path, "wg$key", $val, $this->globals );
324 continue;
325 }
326 // Ignore anything that starts with a @
327 if ( $key[0] === '@' ) {
328 continue;
329 }
330
331 if ( $version >= 2 ) {
332 // Only allowed attributes are set
333 if ( in_array( $key, self::CORE_ATTRIBS ) ) {
334 $this->storeToArray( $path, $key, $val, $this->attributes );
335 }
336 } else {
337 // version === 1
338 if ( !in_array( $key, self::NOT_ATTRIBS )
339 && !in_array( $key, self::CREDIT_ATTRIBS )
340 ) {
341 // If it's not disallowed, it's an attribute
342 $this->storeToArrayRecursive( $path, $key, $val, $this->attributes );
343 }
344 }
345 }
346 }
347
352 protected function extractAttributes( $path, array $info ) {
353 if ( isset( $info['attributes'] ) ) {
354 foreach ( $info['attributes'] as $extName => $value ) {
355 $this->storeToArrayRecursive( $path, $extName, $value, $this->extAttributes );
356 }
357 }
358 }
359
360 public function getExtractedInfo( bool $includeDev = false ) {
361 // Make sure the merge strategies are set
362 foreach ( $this->globals as $key => $val ) {
363 if ( isset( self::MERGE_STRATEGIES[$key] ) ) {
364 $this->globals[$key][ExtensionRegistry::MERGE_STRATEGY] = self::MERGE_STRATEGIES[$key];
365 }
366 }
367
368 // Merge $this->extAttributes into $this->attributes depending on what is loaded
369 foreach ( $this->extAttributes as $extName => $value ) {
370 // Only set the attribute if $extName is loaded (and hence present in credits)
371 if ( isset( $this->credits[$extName] ) ) {
372 foreach ( $value as $attrName => $attrValue ) {
374 '', // Don't provide a path since it's impossible to generate an error here
375 $extName . $attrName,
376 $attrValue,
377 $this->attributes
378 );
379 }
380 unset( $this->extAttributes[$extName] );
381 }
382 }
383
384 $autoload = $this->getExtractedAutoloadInfo( $includeDev );
385
386 return [
387 'globals' => $this->globals,
388 'defines' => $this->defines,
389 'callbacks' => $this->callbacks,
390 'credits' => $this->credits,
391 'attributes' => $this->attributes,
392 'autoloaderPaths' => $autoload['files'],
393 'autoloaderClasses' => $autoload['classes'],
394 'autoloaderNS' => $autoload['namespaces'],
395 ];
396 }
397
398 public function getRequirements( array $info, $includeDev ) {
399 // Quick shortcuts
400 if ( !$includeDev || !isset( $info['dev-requires'] ) ) {
401 return $info['requires'] ?? [];
402 }
403
404 if ( !isset( $info['requires'] ) ) {
405 return $info['dev-requires'] ?? [];
406 }
407
408 // OK, we actually have to merge everything
409 $merged = [];
410
411 // Helper that combines version requirements by
412 // picking the non-null if one is, or combines
413 // the two. Note that it is not possible for
414 // both inputs to be null.
415 $pick = static function ( $a, $b ) {
416 if ( $a === null ) {
417 return $b;
418 } elseif ( $b === null ) {
419 return $a;
420 } else {
421 return "$a $b";
422 }
423 };
424
425 $req = $info['requires'];
426 $dev = $info['dev-requires'];
427 if ( isset( $req['MediaWiki'] ) || isset( $dev['MediaWiki'] ) ) {
428 $merged['MediaWiki'] = $pick(
429 $req['MediaWiki'] ?? null,
430 $dev['MediaWiki'] ?? null
431 );
432 }
433
434 $platform = array_merge(
435 array_keys( $req['platform'] ?? [] ),
436 array_keys( $dev['platform'] ?? [] )
437 );
438 if ( $platform ) {
439 foreach ( $platform as $pkey ) {
440 if ( $pkey === 'php' ) {
441 $value = $pick(
442 $req['platform']['php'] ?? null,
443 $dev['platform']['php'] ?? null
444 );
445 } else {
446 // Prefer dev value, but these should be constant
447 // anyway (ext-* and ability-*)
448 $value = $dev['platform'][$pkey] ?? $req['platform'][$pkey];
449 }
450 $merged['platform'][$pkey] = $value;
451 }
452 }
453
454 foreach ( [ 'extensions', 'skins' ] as $thing ) {
455 $things = array_merge(
456 array_keys( $req[$thing] ?? [] ),
457 array_keys( $dev[$thing] ?? [] )
458 );
459 foreach ( $things as $name ) {
460 $merged[$thing][$name] = $pick(
461 $req[$thing][$name] ?? null,
462 $dev[$thing][$name] ?? null
463 );
464 }
465 }
466
467 return $merged;
468 }
469
482 private function setArrayHookHandler(
483 array $callback,
484 array $hookHandlersAttr,
485 string $name,
486 string $path
487 ) {
488 if ( isset( $callback['handler'] ) ) {
489 $handlerName = $callback['handler'];
490 $handlerDefinition = $hookHandlersAttr[$handlerName] ?? false;
491 if ( !$handlerDefinition ) {
492 throw new UnexpectedValueException(
493 "Missing handler definition for $name in HookHandlers attribute in $path"
494 );
495 }
496 $callback['handler'] = $handlerDefinition;
497 $callback['extensionPath'] = $path;
498 $this->attributes['Hooks'][$name][] = $callback;
499 } else {
500 foreach ( $callback as $callable ) {
501 if ( is_array( $callable ) ) {
502 if ( isset( $callable['handler'] ) ) { // Non-legacy style handler
503 $this->setArrayHookHandler( $callable, $hookHandlersAttr, $name, $path );
504 } else { // Legacy style handler array
505 $this->globals['wgHooks'][$name][] = $callable;
506 }
507 } elseif ( is_string( $callable ) ) {
508 $this->setStringHookHandler( $callable, $hookHandlersAttr, $name, $path );
509 }
510 }
511 }
512 }
513
524 private function setStringHookHandler(
525 string $callback,
526 array $hookHandlersAttr,
527 string $name,
528 string $path
529 ) {
530 if ( isset( $hookHandlersAttr[$callback] ) ) {
531 $handler = [
532 'handler' => $hookHandlersAttr[$callback],
533 'extensionPath' => $path
534 ];
535 $this->attributes['Hooks'][$name][] = $handler;
536 } else { // legacy style handler
537 $this->globals['wgHooks'][$name][] = $callback;
538 }
539 }
540
549 protected function extractHooks( array $info, string $path ) {
550 $extName = $info['name'];
551 if ( isset( $info['Hooks'] ) ) {
552 $hookHandlersAttr = [];
553 foreach ( $info['HookHandlers'] ?? [] as $name => $def ) {
554 $hookHandlersAttr[$name] = [ 'name' => "$extName-$name" ] + $def;
555 }
556 foreach ( $info['Hooks'] as $name => $callback ) {
557 if ( is_string( $callback ) ) {
558 $this->setStringHookHandler( $callback, $hookHandlersAttr, $name, $path );
559 } elseif ( is_array( $callback ) ) {
560 $this->setArrayHookHandler( $callback, $hookHandlersAttr, $name, $path );
561 }
562 }
563 }
564 if ( isset( $info['DeprecatedHooks'] ) ) {
565 $deprecatedHooks = [];
566 foreach ( $info['DeprecatedHooks'] as $name => $deprecatedHookInfo ) {
567 $deprecatedHookInfo += [ 'component' => $extName ];
568 $deprecatedHooks[$name] = $deprecatedHookInfo;
569 }
570 if ( isset( $this->attributes['DeprecatedHooks'] ) ) {
571 $this->attributes['DeprecatedHooks'] += $deprecatedHooks;
572 } else {
573 $this->attributes['DeprecatedHooks'] = $deprecatedHooks;
574 }
575 }
576 }
577
584 protected function extractDomainEventIngresses( array $info, string $path ) {
585 $this->attributes['DomainEventIngresses'] ??= [];
586 foreach ( $info['DomainEventIngresses'] ?? [] as $subscriber ) {
587 $subscriber['extensionPath'] = $path;
588 $this->attributes['DomainEventIngresses'][] = $subscriber;
589 }
590 }
591
595 protected function extractNamespaces( array $info ) {
596 if ( isset( $info['namespaces'] ) ) {
597 foreach ( $info['namespaces'] as $ns ) {
598 if ( defined( $ns['constant'] ) ) {
599 // If the namespace constant is already defined, use it.
600 // This allows namespace IDs to be overwritten locally.
601 $id = constant( $ns['constant'] );
602 } else {
603 $id = $ns['id'];
604 }
605 $this->defines[ $ns['constant'] ] = $id;
606
607 if ( !( isset( $ns['conditional'] ) && $ns['conditional'] ) ) {
608 // If it is not conditional, register it
609 $this->attributes['ExtensionNamespaces'][$id] = $ns['name'];
610 }
611 if ( isset( $ns['movable'] ) && !$ns['movable'] ) {
612 $this->attributes['ImmovableNamespaces'][] = $id;
613 }
614 if ( isset( $ns['gender'] ) ) {
615 $this->globals['wgExtraGenderNamespaces'][$id] = $ns['gender'];
616 }
617 if ( isset( $ns['subpages'] ) && $ns['subpages'] ) {
618 $this->globals['wgNamespacesWithSubpages'][$id] = true;
619 }
620 if ( isset( $ns['content'] ) && $ns['content'] ) {
621 $this->globals['wgContentNamespaces'][] = $id;
622 }
623 if ( isset( $ns['defaultcontentmodel'] ) ) {
624 $this->globals['wgNamespaceContentModels'][$id] = $ns['defaultcontentmodel'];
625 }
626 if ( isset( $ns['protection'] ) ) {
627 $this->globals['wgNamespaceProtection'][$id] = $ns['protection'];
628 }
629 if ( isset( $ns['capitallinkoverride'] ) ) {
630 $this->globals['wgCapitalLinkOverrides'][$id] = $ns['capitallinkoverride'];
631 }
632 if ( isset( $ns['includable'] ) && !$ns['includable'] ) {
633 $this->globals['wgNonincludableNamespaces'][] = $id;
634 }
635 }
636 }
637 }
638
639 protected function extractResourceLoaderModules( $dir, array $info ) {
640 $defaultPaths = $info['ResourceFileModulePaths'] ?? false;
641 if ( isset( $defaultPaths['localBasePath'] ) ) {
642 if ( $defaultPaths['localBasePath'] === '' ) {
643 // Avoid double slashes (e.g. /extensions/Example//path)
644 $defaultPaths['localBasePath'] = $dir;
645 } else {
646 $defaultPaths['localBasePath'] = "$dir/{$defaultPaths['localBasePath']}";
647 }
648 }
649
650 foreach ( [ 'ResourceModules', 'ResourceModuleSkinStyles', 'OOUIThemePaths' ] as $setting ) {
651 if ( isset( $info[$setting] ) ) {
652 foreach ( $info[$setting] as $name => $data ) {
653 if ( isset( $data['localBasePath'] ) ) {
654 if ( $data['localBasePath'] === '' ) {
655 // Avoid double slashes (e.g. /extensions/Example//path)
656 $data['localBasePath'] = $dir;
657 } else {
658 $data['localBasePath'] = "$dir/{$data['localBasePath']}";
659 }
660 }
661 if ( $defaultPaths ) {
662 $data += $defaultPaths;
663 }
664 $this->attributes[$setting][$name] = $data;
665 }
666 }
667 }
668
669 if ( isset( $info['QUnitTestModule'] ) ) {
670 $data = $info['QUnitTestModule'];
671 if ( isset( $data['localBasePath'] ) ) {
672 if ( $data['localBasePath'] === '' ) {
673 // Avoid double slashes (e.g. /extensions/Example//path)
674 $data['localBasePath'] = $dir;
675 } else {
676 $data['localBasePath'] = "$dir/{$data['localBasePath']}";
677 }
678 }
679 $this->attributes['QUnitTestModule']["test.{$info['name']}"] = $data;
680 }
681
682 if ( isset( $info['MessagePosterModule'] ) ) {
683 $data = $info['MessagePosterModule'];
684 $basePath = $data['localBasePath'] ?? '';
685 $baseDir = $basePath === '' ? $dir : "$dir/$basePath";
686 foreach ( $data['scripts'] ?? [] as $scripts ) {
687 $this->attributes['MessagePosterModule']['scripts'][] =
688 new FilePath( $scripts, $baseDir );
689 }
690 foreach ( $data['dependencies'] ?? [] as $dependency ) {
691 $this->attributes['MessagePosterModule']['dependencies'][] = $dependency;
692 }
693 }
694 }
695
696 protected function extractExtensionMessagesFiles( $dir, array $info ) {
697 if ( isset( $info['ExtensionMessagesFiles'] ) ) {
698 foreach ( $info['ExtensionMessagesFiles'] as &$file ) {
699 $file = "$dir/$file";
700 }
701 $this->globals["wgExtensionMessagesFiles"] += $info['ExtensionMessagesFiles'];
702 }
703 }
704
705 protected function extractRestModuleFiles( $dir, array $info ) {
707 if ( isset( $info['RestModuleFiles'] ) ) {
708 foreach ( $info['RestModuleFiles'] as &$file ) {
709 $this->globals["wg$var"][] = "$dir/$file";
710 }
711 }
712 }
713
721 protected function extractMessagesDirs( $dir, array $info ) {
722 if ( isset( $info['MessagesDirs'] ) ) {
723 foreach ( $info['MessagesDirs'] as $name => $files ) {
724 foreach ( (array)$files as $file ) {
725 $this->globals["wgMessagesDirs"][$name][] = "$dir/$file";
726 }
727 }
728 }
729 }
730
738 protected function extractTranslationAliasesDirs( $dir, array $info ) {
739 foreach ( $info['TranslationAliasesDirs'] ?? [] as $name => $files ) {
740 foreach ( (array)$files as $file ) {
741 $this->globals['wgTranslationAliasesDirs'][$name][] = "$dir/$file";
742 }
743 }
744 }
745
752 protected function extractSkins( $dir, array $info ) {
753 if ( isset( $info['ValidSkinNames'] ) ) {
754 foreach ( $info['ValidSkinNames'] as $skinKey => $data ) {
755 if ( isset( $data['args'][0] ) ) {
756 $templateDirectory = $data['args'][0]['templateDirectory'] ?? 'templates';
757 $data['args'][0]['templateDirectory'] = $dir . '/' . $templateDirectory;
758 }
759 $this->globals['wgValidSkinNames'][$skinKey] = $data;
760 }
761 }
762 }
763
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
1039 private function extractAutoload( array $info, string $dir ) {
1040 if ( isset( $info['load_composer_autoloader'] ) && $info['load_composer_autoloader'] === true ) {
1041 $file = "$dir/vendor/autoload.php";
1042 if ( file_exists( $file ) ) {
1043 $this->autoload['files'][] = $file;
1044 }
1045 }
1046
1047 if ( isset( $info['AutoloadClasses'] ) ) {
1048 $paths = $this->applyPath( $info['AutoloadClasses'], $dir );
1049 $this->autoload['classes'] += $paths;
1050 }
1051
1052 if ( isset( $info['AutoloadNamespaces'] ) ) {
1053 $paths = $this->applyPath( $info['AutoloadNamespaces'], $dir );
1054 $this->autoload['namespaces'] += $paths;
1055 }
1056
1057 if ( isset( $info['TestAutoloadClasses'] ) ) {
1058 $paths = $this->applyPath( $info['TestAutoloadClasses'], $dir );
1059 $this->autoloadDev['classes'] += $paths;
1060 }
1061
1062 if ( isset( $info['TestAutoloadNamespaces'] ) ) {
1063 $paths = $this->applyPath( $info['TestAutoloadNamespaces'], $dir );
1064 $this->autoloadDev['namespaces'] += $paths;
1065 }
1066 }
1067}
1068
1070class_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:82
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.
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.
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