MediaWiki 1.43.1
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 'MessagePosterModule',
151 'MessagesDirs',
152 'OOUIThemePaths',
153 'QUnitTestModule',
154 'ResourceFileModulePaths',
155 'ResourceModuleSkinStyles',
156 'ResourceModules',
157 'ServiceWiringFiles',
158 ];
159
167 protected $globals = [
168 'wgExtensionMessagesFiles' => [],
169 'wgRestAPIAdditionalRouteFiles' => [],
170 'wgMessagesDirs' => [],
171 'TranslationAliasesDirs' => [],
172 ];
173
179 protected $defines = [];
180
188 protected $callbacks = [];
189
193 protected $credits = [];
194
202 protected $autoload = [
203 'files' => [],
204 'classes' => [],
205 'namespaces' => [],
206 ];
207
214 protected $autoloadDev = [
215 'files' => [],
216 'classes' => [],
217 'namespaces' => [],
218 ];
219
226 protected $attributes = [];
227
234 protected $extAttributes = [];
235
243 public function extractInfoFromFile( string $path ) {
244 $json = file_get_contents( $path );
245 $info = json_decode( $json, true );
246
247 if ( !$info ) {
248 throw new RuntimeException( "Failed to load JSON data from $path" );
249 }
250
251 $this->extractInfo( $path, $info, $info['manifest_version'] );
252 }
253
259 public function extractInfo( $path, array $info, $version ) {
260 $dir = dirname( $path );
261 $this->extractHooks( $info, $path );
262 $this->extractExtensionMessagesFiles( $dir, $info );
263 $this->extractRestModuleFiles( $dir, $info );
264 $this->extractMessagesDirs( $dir, $info );
265 $this->extractTranslationAliasesDirs( $dir, $info );
266 $this->extractSkins( $dir, $info );
267 $this->extractSkinImportPaths( $dir, $info );
268 $this->extractNamespaces( $info );
269 $this->extractImplicitRights( $info );
270 $this->extractResourceLoaderModules( $dir, $info );
271 if ( isset( $info['ServiceWiringFiles'] ) ) {
273 'wgServiceWiringFiles',
274 $dir,
275 $info['ServiceWiringFiles']
276 );
277 }
278 $name = $this->extractCredits( $path, $info );
279 if ( isset( $info['callback'] ) ) {
280 $this->callbacks[$name] = $info['callback'];
281 }
282
283 $this->extractAutoload( $info, $dir );
284
285 // config should be after all core globals are extracted,
286 // so duplicate setting detection will work fully
287 if ( $version >= 2 ) {
288 $this->extractConfig2( $info, $dir );
289 } else {
290 // $version === 1
291 $this->extractConfig1( $info );
292 }
293
294 // Record the extension name in the ParsoidModules property
295 if ( isset( $info['ParsoidModules'] ) ) {
296 foreach ( $info['ParsoidModules'] as &$module ) {
297 if ( is_string( $module ) ) {
298 $className = $module;
299 $module = [
300 'class' => $className,
301 ];
302 }
303 $module['name'] = $name;
304 }
305 }
306
307 $this->extractForeignResourcesDir( $info, $name, $dir );
308
309 if ( $version >= 2 ) {
310 $this->extractAttributes( $path, $info );
311 }
312
313 foreach ( $info as $key => $val ) {
314 // If it's a global setting,
315 if ( in_array( $key, self::$globalSettings ) ) {
316 $this->storeToArrayRecursive( $path, "wg$key", $val, $this->globals );
317 continue;
318 }
319 // Ignore anything that starts with a @
320 if ( $key[0] === '@' ) {
321 continue;
322 }
323
324 if ( $version >= 2 ) {
325 // Only allowed attributes are set
326 if ( in_array( $key, self::CORE_ATTRIBS ) ) {
327 $this->storeToArray( $path, $key, $val, $this->attributes );
328 }
329 } else {
330 // version === 1
331 if ( !in_array( $key, self::NOT_ATTRIBS )
332 && !in_array( $key, self::CREDIT_ATTRIBS )
333 ) {
334 // If it's not disallowed, it's an attribute
335 $this->storeToArrayRecursive( $path, $key, $val, $this->attributes );
336 }
337 }
338 }
339 }
340
345 protected function extractAttributes( $path, array $info ) {
346 if ( isset( $info['attributes'] ) ) {
347 foreach ( $info['attributes'] as $extName => $value ) {
348 $this->storeToArrayRecursive( $path, $extName, $value, $this->extAttributes );
349 }
350 }
351 }
352
353 public function getExtractedInfo( bool $includeDev = false ) {
354 // Make sure the merge strategies are set
355 foreach ( $this->globals as $key => $val ) {
356 if ( isset( self::MERGE_STRATEGIES[$key] ) ) {
357 $this->globals[$key][ExtensionRegistry::MERGE_STRATEGY] = self::MERGE_STRATEGIES[$key];
358 }
359 }
360
361 // Merge $this->extAttributes into $this->attributes depending on what is loaded
362 foreach ( $this->extAttributes as $extName => $value ) {
363 // Only set the attribute if $extName is loaded (and hence present in credits)
364 if ( isset( $this->credits[$extName] ) ) {
365 foreach ( $value as $attrName => $attrValue ) {
367 '', // Don't provide a path since it's impossible to generate an error here
368 $extName . $attrName,
369 $attrValue,
370 $this->attributes
371 );
372 }
373 unset( $this->extAttributes[$extName] );
374 }
375 }
376
377 $autoload = $this->getExtractedAutoloadInfo( $includeDev );
378
379 return [
380 'globals' => $this->globals,
381 'defines' => $this->defines,
382 'callbacks' => $this->callbacks,
383 'credits' => $this->credits,
384 'attributes' => $this->attributes,
385 'autoloaderPaths' => $autoload['files'],
386 'autoloaderClasses' => $autoload['classes'],
387 'autoloaderNS' => $autoload['namespaces'],
388 ];
389 }
390
391 public function getRequirements( array $info, $includeDev ) {
392 // Quick shortcuts
393 if ( !$includeDev || !isset( $info['dev-requires'] ) ) {
394 return $info['requires'] ?? [];
395 }
396
397 if ( !isset( $info['requires'] ) ) {
398 return $info['dev-requires'] ?? [];
399 }
400
401 // OK, we actually have to merge everything
402 $merged = [];
403
404 // Helper that combines version requirements by
405 // picking the non-null if one is, or combines
406 // the two. Note that it is not possible for
407 // both inputs to be null.
408 $pick = static function ( $a, $b ) {
409 if ( $a === null ) {
410 return $b;
411 } elseif ( $b === null ) {
412 return $a;
413 } else {
414 return "$a $b";
415 }
416 };
417
418 $req = $info['requires'];
419 $dev = $info['dev-requires'];
420 if ( isset( $req['MediaWiki'] ) || isset( $dev['MediaWiki'] ) ) {
421 $merged['MediaWiki'] = $pick(
422 $req['MediaWiki'] ?? null,
423 $dev['MediaWiki'] ?? null
424 );
425 }
426
427 $platform = array_merge(
428 array_keys( $req['platform'] ?? [] ),
429 array_keys( $dev['platform'] ?? [] )
430 );
431 if ( $platform ) {
432 foreach ( $platform as $pkey ) {
433 if ( $pkey === 'php' ) {
434 $value = $pick(
435 $req['platform']['php'] ?? null,
436 $dev['platform']['php'] ?? null
437 );
438 } else {
439 // Prefer dev value, but these should be constant
440 // anyway (ext-* and ability-*)
441 $value = $dev['platform'][$pkey] ?? $req['platform'][$pkey];
442 }
443 $merged['platform'][$pkey] = $value;
444 }
445 }
446
447 foreach ( [ 'extensions', 'skins' ] as $thing ) {
448 $things = array_merge(
449 array_keys( $req[$thing] ?? [] ),
450 array_keys( $dev[$thing] ?? [] )
451 );
452 foreach ( $things as $name ) {
453 $merged[$thing][$name] = $pick(
454 $req[$thing][$name] ?? null,
455 $dev[$thing][$name] ?? null
456 );
457 }
458 }
459
460 return $merged;
461 }
462
475 private function setArrayHookHandler(
476 array $callback,
477 array $hookHandlersAttr,
478 string $name,
479 string $path
480 ) {
481 if ( isset( $callback['handler'] ) ) {
482 $handlerName = $callback['handler'];
483 $handlerDefinition = $hookHandlersAttr[$handlerName] ?? false;
484 if ( !$handlerDefinition ) {
485 throw new UnexpectedValueException(
486 "Missing handler definition for $name in HookHandlers attribute in $path"
487 );
488 }
489 $callback['handler'] = $handlerDefinition;
490 $callback['extensionPath'] = $path;
491 $this->attributes['Hooks'][$name][] = $callback;
492 } else {
493 foreach ( $callback as $callable ) {
494 if ( is_array( $callable ) ) {
495 if ( isset( $callable['handler'] ) ) { // Non-legacy style handler
496 $this->setArrayHookHandler( $callable, $hookHandlersAttr, $name, $path );
497 } else { // Legacy style handler array
498 $this->globals['wgHooks'][$name][] = $callable;
499 }
500 } elseif ( is_string( $callable ) ) {
501 $this->setStringHookHandler( $callable, $hookHandlersAttr, $name, $path );
502 }
503 }
504 }
505 }
506
517 private function setStringHookHandler(
518 string $callback,
519 array $hookHandlersAttr,
520 string $name,
521 string $path
522 ) {
523 if ( isset( $hookHandlersAttr[$callback] ) ) {
524 $handler = [
525 'handler' => $hookHandlersAttr[$callback],
526 'extensionPath' => $path
527 ];
528 $this->attributes['Hooks'][$name][] = $handler;
529 } else { // legacy style handler
530 $this->globals['wgHooks'][$name][] = $callback;
531 }
532 }
533
542 protected function extractHooks( array $info, string $path ) {
543 $extName = $info['name'];
544 if ( isset( $info['Hooks'] ) ) {
545 $hookHandlersAttr = [];
546 foreach ( $info['HookHandlers'] ?? [] as $name => $def ) {
547 $hookHandlersAttr[$name] = [ 'name' => "$extName-$name" ] + $def;
548 }
549 foreach ( $info['Hooks'] as $name => $callback ) {
550 if ( is_string( $callback ) ) {
551 $this->setStringHookHandler( $callback, $hookHandlersAttr, $name, $path );
552 } elseif ( is_array( $callback ) ) {
553 $this->setArrayHookHandler( $callback, $hookHandlersAttr, $name, $path );
554 }
555 }
556 }
557 if ( isset( $info['DeprecatedHooks'] ) ) {
558 $deprecatedHooks = [];
559 foreach ( $info['DeprecatedHooks'] as $name => $deprecatedHookInfo ) {
560 $deprecatedHookInfo += [ 'component' => $extName ];
561 $deprecatedHooks[$name] = $deprecatedHookInfo;
562 }
563 if ( isset( $this->attributes['DeprecatedHooks'] ) ) {
564 $this->attributes['DeprecatedHooks'] += $deprecatedHooks;
565 } else {
566 $this->attributes['DeprecatedHooks'] = $deprecatedHooks;
567 }
568 }
569 }
570
576 protected function extractNamespaces( array $info ) {
577 if ( isset( $info['namespaces'] ) ) {
578 foreach ( $info['namespaces'] as $ns ) {
579 if ( defined( $ns['constant'] ) ) {
580 // If the namespace constant is already defined, use it.
581 // This allows namespace IDs to be overwritten locally.
582 $id = constant( $ns['constant'] );
583 } else {
584 $id = $ns['id'];
585 }
586 $this->defines[ $ns['constant'] ] = $id;
587
588 if ( !( isset( $ns['conditional'] ) && $ns['conditional'] ) ) {
589 // If it is not conditional, register it
590 $this->attributes['ExtensionNamespaces'][$id] = $ns['name'];
591 }
592 if ( isset( $ns['movable'] ) && !$ns['movable'] ) {
593 $this->attributes['ImmovableNamespaces'][] = $id;
594 }
595 if ( isset( $ns['gender'] ) ) {
596 $this->globals['wgExtraGenderNamespaces'][$id] = $ns['gender'];
597 }
598 if ( isset( $ns['subpages'] ) && $ns['subpages'] ) {
599 $this->globals['wgNamespacesWithSubpages'][$id] = true;
600 }
601 if ( isset( $ns['content'] ) && $ns['content'] ) {
602 $this->globals['wgContentNamespaces'][] = $id;
603 }
604 if ( isset( $ns['defaultcontentmodel'] ) ) {
605 $this->globals['wgNamespaceContentModels'][$id] = $ns['defaultcontentmodel'];
606 }
607 if ( isset( $ns['protection'] ) ) {
608 $this->globals['wgNamespaceProtection'][$id] = $ns['protection'];
609 }
610 if ( isset( $ns['capitallinkoverride'] ) ) {
611 $this->globals['wgCapitalLinkOverrides'][$id] = $ns['capitallinkoverride'];
612 }
613 if ( isset( $ns['includable'] ) && !$ns['includable'] ) {
614 $this->globals['wgNonincludableNamespaces'][] = $id;
615 }
616 }
617 }
618 }
619
620 protected function extractResourceLoaderModules( $dir, array $info ) {
621 $defaultPaths = $info['ResourceFileModulePaths'] ?? false;
622 if ( isset( $defaultPaths['localBasePath'] ) ) {
623 if ( $defaultPaths['localBasePath'] === '' ) {
624 // Avoid double slashes (e.g. /extensions/Example//path)
625 $defaultPaths['localBasePath'] = $dir;
626 } else {
627 $defaultPaths['localBasePath'] = "$dir/{$defaultPaths['localBasePath']}";
628 }
629 }
630
631 foreach ( [ 'ResourceModules', 'ResourceModuleSkinStyles', 'OOUIThemePaths' ] as $setting ) {
632 if ( isset( $info[$setting] ) ) {
633 foreach ( $info[$setting] as $name => $data ) {
634 if ( isset( $data['localBasePath'] ) ) {
635 if ( $data['localBasePath'] === '' ) {
636 // Avoid double slashes (e.g. /extensions/Example//path)
637 $data['localBasePath'] = $dir;
638 } else {
639 $data['localBasePath'] = "$dir/{$data['localBasePath']}";
640 }
641 }
642 if ( $defaultPaths ) {
643 $data += $defaultPaths;
644 }
645 $this->attributes[$setting][$name] = $data;
646 }
647 }
648 }
649
650 if ( isset( $info['QUnitTestModule'] ) ) {
651 $data = $info['QUnitTestModule'];
652 if ( isset( $data['localBasePath'] ) ) {
653 if ( $data['localBasePath'] === '' ) {
654 // Avoid double slashes (e.g. /extensions/Example//path)
655 $data['localBasePath'] = $dir;
656 } else {
657 $data['localBasePath'] = "$dir/{$data['localBasePath']}";
658 }
659 }
660 $this->attributes['QUnitTestModules']["test.{$info['name']}"] = $data;
661 }
662
663 if ( isset( $info['MessagePosterModule'] ) ) {
664 $data = $info['MessagePosterModule'];
665 $basePath = $data['localBasePath'] ?? '';
666 $baseDir = $basePath === '' ? $dir : "$dir/$basePath";
667 foreach ( $data['scripts'] ?? [] as $scripts ) {
668 $this->attributes['MessagePosterModule']['scripts'][] =
669 new FilePath( $scripts, $baseDir );
670 }
671 foreach ( $data['dependencies'] ?? [] as $dependency ) {
672 $this->attributes['MessagePosterModule']['dependencies'][] = $dependency;
673 }
674 }
675 }
676
677 protected function extractExtensionMessagesFiles( $dir, array $info ) {
678 if ( isset( $info['ExtensionMessagesFiles'] ) ) {
679 foreach ( $info['ExtensionMessagesFiles'] as &$file ) {
680 $file = "$dir/$file";
681 }
682 $this->globals["wgExtensionMessagesFiles"] += $info['ExtensionMessagesFiles'];
683 }
684 }
685
686 protected function extractRestModuleFiles( $dir, array $info ) {
688 if ( isset( $info['RestModuleFiles'] ) ) {
689 foreach ( $info['RestModuleFiles'] as &$file ) {
690 $this->globals["wg$var"][] = "$dir/$file";
691 }
692 }
693 }
694
702 protected function extractMessagesDirs( $dir, array $info ) {
703 if ( isset( $info['MessagesDirs'] ) ) {
704 foreach ( $info['MessagesDirs'] as $name => $files ) {
705 foreach ( (array)$files as $file ) {
706 $this->globals["wgMessagesDirs"][$name][] = "$dir/$file";
707 }
708 }
709 }
710 }
711
719 protected function extractTranslationAliasesDirs( $dir, array $info ) {
720 foreach ( $info['TranslationAliasesDirs'] ?? [] as $name => $files ) {
721 foreach ( (array)$files as $file ) {
722 $this->globals['wgTranslationAliasesDirs'][$name][] = "$dir/$file";
723 }
724 }
725 }
726
733 protected function extractSkins( $dir, array $info ) {
734 if ( isset( $info['ValidSkinNames'] ) ) {
735 foreach ( $info['ValidSkinNames'] as $skinKey => $data ) {
736 if ( isset( $data['args'][0] ) ) {
737 $templateDirectory = $data['args'][0]['templateDirectory'] ?? 'templates';
738 $data['args'][0]['templateDirectory'] = $dir . '/' . $templateDirectory;
739 }
740 $this->globals['wgValidSkinNames'][$skinKey] = $data;
741 }
742 }
743 }
744
750 protected function extractImplicitRights( array $info ) {
751 // Rate limits are only configurable for rights that are either in wgImplicitRights
752 // or in wgAvailableRights. Extensions that define rate limits should not have to
753 // explicitly add them to wgImplicitRights as well, we can do that automatically.
754
755 if ( isset( $info['RateLimits'] ) ) {
756 $rights = array_keys( $info['RateLimits'] );
757
758 if ( isset( $info['AvailableRights'] ) ) {
759 $rights = array_diff( $rights, $info['AvailableRights'] );
760 }
761
762 $this->globals['wgImplicitRights'] = array_merge(
763 $this->globals['wgImplicitRights'] ?? [],
764 $rights
765 );
766 }
767 }
768
773 protected function extractSkinImportPaths( $dir, array $info ) {
774 if ( isset( $info['SkinLessImportPaths'] ) ) {
775 foreach ( $info['SkinLessImportPaths'] as $skin => $subpath ) {
776 $this->attributes['SkinLessImportPaths'][$skin] = "$dir/$subpath";
777 }
778 }
779 }
780
788 protected function extractCredits( $path, array $info ) {
789 $credits = [
790 'path' => $path,
791 'type' => 'other',
792 ];
793 foreach ( self::CREDIT_ATTRIBS as $attr ) {
794 if ( isset( $info[$attr] ) ) {
795 $credits[$attr] = $info[$attr];
796 }
797 }
798
799 $name = $credits['name'];
800
801 // If someone is loading the same thing twice, throw
802 // a nice error (T121493)
803 if ( isset( $this->credits[$name] ) ) {
804 $firstPath = $this->credits[$name]['path'];
805 $secondPath = $credits['path'];
806 throw new InvalidArgumentException(
807 "It was attempted to load $name twice, from $firstPath and $secondPath."
808 );
809 }
810
811 $this->credits[$name] = $credits;
812
813 return $name;
814 }
815
816 protected function extractForeignResourcesDir( array $info, string $name, string $dir ): void {
817 if ( array_key_exists( 'ForeignResourcesDir', $info ) ) {
818 if ( !is_string( $info['ForeignResourcesDir'] ) ) {
819 throw new InvalidArgumentException( "Incorrect ForeignResourcesDir type, must be a string (in $name)" );
820 }
821 $this->attributes['ForeignResourcesDir'][$name] = "{$dir}/{$info['ForeignResourcesDir']}";
822 }
823 }
824
832 protected function extractConfig1( array $info ) {
833 if ( isset( $info['config'] ) ) {
834 if ( isset( $info['config']['_prefix'] ) ) {
835 $prefix = $info['config']['_prefix'];
836 unset( $info['config']['_prefix'] );
837 } else {
838 $prefix = 'wg';
839 }
840 foreach ( $info['config'] as $key => $val ) {
841 if ( $key[0] !== '@' ) {
842 $this->addConfigGlobal( "$prefix$key", $val, $info['name'] );
843 }
844 }
845 }
846 }
847
856 private function applyPath( array $value, string $dir ): array {
857 $result = [];
858
859 foreach ( $value as $k => $v ) {
860 $result[$k] = $dir . '/' . $v;
861 }
862
863 return $result;
864 }
865
874 protected function extractConfig2( array $info, $dir ) {
875 $prefix = $info['config_prefix'] ?? 'wg';
876 if ( isset( $info['config'] ) ) {
877 foreach ( $info['config'] as $key => $data ) {
878 if ( !array_key_exists( 'value', $data ) ) {
879 throw new UnexpectedValueException( "Missing value for config $key" );
880 }
881
882 $value = $data['value'];
883 if ( isset( $data['path'] ) && $data['path'] ) {
884 if ( is_array( $value ) ) {
885 $value = $this->applyPath( $value, $dir );
886 } else {
887 $value = "$dir/$value";
888 }
889 }
890 if ( isset( $data['merge_strategy'] ) ) {
891 $value[ExtensionRegistry::MERGE_STRATEGY] = $data['merge_strategy'];
892 }
893 $this->addConfigGlobal( "$prefix$key", $value, $info['name'] );
894 $data['providedby'] = $info['name'];
895 if ( isset( $info['ConfigRegistry'][0] ) ) {
896 $data['configregistry'] = array_keys( $info['ConfigRegistry'] )[0];
897 }
898 }
899 }
900 }
901
909 private function addConfigGlobal( $key, $value, $extName ) {
910 if ( array_key_exists( $key, $this->globals ) ) {
911 throw new RuntimeException(
912 "The configuration setting '$key' was already set by MediaWiki core or"
913 . " another extension, and cannot be set again by $extName." );
914 }
915 if ( isset( $value[ExtensionRegistry::MERGE_STRATEGY] ) &&
916 $value[ExtensionRegistry::MERGE_STRATEGY] === 'array_merge_recursive' ) {
918 "Using the array_merge_recursive merge strategy in extension.json and skin.json" .
919 " was deprecated in MediaWiki 1.42",
920 "1.42"
921 );
922 }
923 $this->globals[$key] = $value;
924 }
925
926 protected function extractPathBasedGlobal( $global, $dir, $paths ) {
927 foreach ( $paths as $path ) {
928 $this->globals[$global][] = "$dir/$path";
929 }
930 }
931
941 protected function storeToArrayRecursive( $path, $name, $value, &$array ) {
942 if ( !is_array( $value ) ) {
943 throw new InvalidArgumentException( "The value for '$name' should be an array (from $path)" );
944 }
945 if ( isset( $array[$name] ) ) {
946 $array[$name] = array_merge_recursive( $array[$name], $value );
947 } else {
948 $array[$name] = $value;
949 }
950 }
951
962 protected function storeToArray( $path, $name, $value, &$array ) {
963 if ( !is_array( $value ) ) {
964 throw new InvalidArgumentException( "The value for '$name' should be an array (from $path)" );
965 }
966 if ( isset( $array[$name] ) ) {
967 $array[$name] = array_merge( $array[$name], $value );
968 } else {
969 $array[$name] = $value;
970 }
971 }
972
987 public function getExtractedAutoloadInfo( bool $includeDev = false ): array {
988 $autoload = $this->autoload;
989
990 if ( $includeDev ) {
991 $autoload['classes'] += $this->autoloadDev['classes'];
992 $autoload['namespaces'] += $this->autoloadDev['namespaces'];
993
994 // NOTE: This is here for completeness. Per MW 1.39,
995 // $this->autoloadDev['files'] is always empty.
996 // So avoid the performance hit of array_merge().
997 if ( !empty( $this->autoloadDev['files'] ) ) {
998 // NOTE: Don't use += with numeric keys!
999 // Could use PHPUtils::pushArray.
1000 $autoload['files'] = array_merge(
1001 $autoload['files'],
1002 $this->autoloadDev['files']
1003 );
1004 }
1005 }
1006
1007 return $autoload;
1008 }
1009
1014 private function extractAutoload( array $info, string $dir ) {
1015 if ( isset( $info['load_composer_autoloader'] ) && $info['load_composer_autoloader'] === true ) {
1016 $file = "$dir/vendor/autoload.php";
1017 if ( file_exists( $file ) ) {
1018 $this->autoload['files'][] = $file;
1019 }
1020 }
1021
1022 if ( isset( $info['AutoloadClasses'] ) ) {
1023 $paths = $this->applyPath( $info['AutoloadClasses'], $dir );
1024 $this->autoload['classes'] += $paths;
1025 }
1026
1027 if ( isset( $info['AutoloadNamespaces'] ) ) {
1028 $paths = $this->applyPath( $info['AutoloadNamespaces'], $dir );
1029 $this->autoload['namespaces'] += $paths;
1030 }
1031
1032 if ( isset( $info['TestAutoloadClasses'] ) ) {
1033 $paths = $this->applyPath( $info['TestAutoloadClasses'], $dir );
1034 $this->autoloadDev['classes'] += $paths;
1035 }
1036
1037 if ( isset( $info['TestAutoloadNamespaces'] ) ) {
1038 $paths = $this->applyPath( $info['TestAutoloadNamespaces'], $dir );
1039 $this->autoloadDev['namespaces'] += $paths;
1040 }
1041 }
1042}
1043
1045class_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.
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