MediaWiki master
ExtensionProcessor.php
Go to the documentation of this file.
1<?php
2
5
12class ExtensionProcessor implements Processor {
13
19 protected static $globalSettings = [
20 MainConfigNames::ActionFilteredLogs,
21 MainConfigNames::Actions,
22 MainConfigNames::AddGroups,
23 MainConfigNames::APIFormatModules,
24 MainConfigNames::APIListModules,
25 MainConfigNames::APIMetaModules,
26 MainConfigNames::APIModules,
27 MainConfigNames::APIPropModules,
28 MainConfigNames::AuthManagerAutoConfig,
29 MainConfigNames::AvailableRights,
30 MainConfigNames::CentralIdLookupProviders,
31 MainConfigNames::ChangeCredentialsBlacklist,
32 MainConfigNames::ConditionalUserOptions,
33 MainConfigNames::ConfigRegistry,
34 MainConfigNames::ContentHandlers,
35 MainConfigNames::DefaultUserOptions,
36 MainConfigNames::ExtensionEntryPointListFiles,
37 MainConfigNames::ExtensionFunctions,
38 MainConfigNames::FeedClasses,
39 MainConfigNames::FileExtensions,
40 MainConfigNames::FilterLogTypes,
41 MainConfigNames::GrantPermissionGroups,
42 MainConfigNames::GrantPermissions,
43 MainConfigNames::GrantRiskGroups,
44 MainConfigNames::GroupPermissions,
45 MainConfigNames::GroupsAddToSelf,
46 MainConfigNames::GroupsRemoveFromSelf,
47 MainConfigNames::HiddenPrefs,
48 MainConfigNames::ImplicitGroups,
49 MainConfigNames::JobClasses,
50 MainConfigNames::LogActions,
51 MainConfigNames::LogActionsHandlers,
52 MainConfigNames::LogHeaders,
53 MainConfigNames::LogNames,
54 MainConfigNames::LogRestrictions,
55 MainConfigNames::LogTypes,
56 MainConfigNames::MediaHandlers,
57 MainConfigNames::PasswordPolicy,
58 MainConfigNames::PrivilegedGroups,
59 MainConfigNames::RateLimits,
60 MainConfigNames::RawHtmlMessages,
61 MainConfigNames::ReauthenticateTime,
62 MainConfigNames::RecentChangesFlags,
63 MainConfigNames::RemoveCredentialsBlacklist,
64 MainConfigNames::RemoveGroups,
65 MainConfigNames::ResourceLoaderSources,
66 MainConfigNames::RevokePermissions,
67 MainConfigNames::SessionProviders,
68 MainConfigNames::SpecialPages,
69 MainConfigNames::UserRegistrationProviders,
70 ];
71
77 protected const CORE_ATTRIBS = [
78 'ParsoidModules',
79 'RestRoutes',
80 'SkinOOUIThemes',
81 'SkinCodexThemes',
82 'SearchMappings',
83 'TrackingCategories',
84 'LateJSConfigVarNames',
85 'TempUserSerialProviders',
86 'TempUserSerialMappings',
87 'DatabaseVirtualDomains',
88 ];
89
97 protected const MERGE_STRATEGIES = [
98 'wgAuthManagerAutoConfig' => 'array_plus_2d',
99 'wgCapitalLinkOverrides' => 'array_plus',
100 'wgExtraGenderNamespaces' => 'array_plus',
101 'wgGrantPermissions' => 'array_plus_2d',
102 'wgGroupPermissions' => 'array_plus_2d',
103 'wgHooks' => 'array_merge_recursive',
104 'wgNamespaceContentModels' => 'array_plus',
105 'wgNamespaceProtection' => 'array_plus',
106 'wgNamespacesWithSubpages' => 'array_plus',
107 'wgPasswordPolicy' => 'array_merge_recursive',
108 'wgRateLimits' => 'array_plus_2d',
109 'wgRevokePermissions' => 'array_plus_2d',
110 ];
111
117 protected const CREDIT_ATTRIBS = [
118 'type',
119 'author',
120 'description',
121 'descriptionmsg',
122 'license-name',
123 'name',
124 'namemsg',
125 'url',
126 'version',
127 ];
128
135 protected const NOT_ATTRIBS = [
136 'callback',
137 'config',
138 'config_prefix',
139 'load_composer_autoloader',
140 'manifest_version',
141 'namespaces',
142 'requires',
143 'AutoloadClasses',
144 'AutoloadNamespaces',
145 'ExtensionMessagesFiles',
146 'TranslationAliasesDirs',
147 'ForeignResourcesDir',
148 'Hooks',
149 'MessagePosterModule',
150 'MessagesDirs',
151 'OOUIThemePaths',
152 'QUnitTestModule',
153 'ResourceFileModulePaths',
154 'ResourceModuleSkinStyles',
155 'ResourceModules',
156 'ServiceWiringFiles',
157 ];
158
166 protected $globals = [
167 'wgExtensionMessagesFiles' => [],
168 'wgMessagesDirs' => [],
169 'TranslationAliasesDirs' => [],
170 ];
171
177 protected $defines = [];
178
186 protected $callbacks = [];
187
191 protected $credits = [];
192
200 protected $autoload = [
201 'files' => [],
202 'classes' => [],
203 'namespaces' => [],
204 ];
205
212 protected $autoloadDev = [
213 'files' => [],
214 'classes' => [],
215 'namespaces' => [],
216 ];
217
224 protected $attributes = [];
225
232 protected $extAttributes = [];
233
241 public function extractInfoFromFile( string $path ) {
242 $json = file_get_contents( $path );
243 $info = json_decode( $json, true );
244
245 if ( !$info ) {
246 throw new RuntimeException( "Failed to load JSON data from $path" );
247 }
248
249 $this->extractInfo( $path, $info, $info['manifest_version'] );
250 }
251
257 public function extractInfo( $path, array $info, $version ) {
258 $dir = dirname( $path );
259 $this->extractHooks( $info, $path );
260 $this->extractExtensionMessagesFiles( $dir, $info );
261 $this->extractMessagesDirs( $dir, $info );
262 $this->extractTranslationAliasesDirs( $dir, $info );
263 $this->extractSkins( $dir, $info );
264 $this->extractSkinImportPaths( $dir, $info );
265 $this->extractNamespaces( $info );
266 $this->extractImplicitRights( $info );
267 $this->extractResourceLoaderModules( $dir, $info );
268 if ( isset( $info['ServiceWiringFiles'] ) ) {
270 'wgServiceWiringFiles',
271 $dir,
272 $info['ServiceWiringFiles']
273 );
274 }
275 $name = $this->extractCredits( $path, $info );
276 if ( isset( $info['callback'] ) ) {
277 $this->callbacks[$name] = $info['callback'];
278 }
279
280 $this->extractAutoload( $info, $dir );
281
282 // config should be after all core globals are extracted,
283 // so duplicate setting detection will work fully
284 if ( $version >= 2 ) {
285 $this->extractConfig2( $info, $dir );
286 } else {
287 // $version === 1
288 $this->extractConfig1( $info );
289 }
290
291 // Record the extension name in the ParsoidModules property
292 if ( isset( $info['ParsoidModules'] ) ) {
293 foreach ( $info['ParsoidModules'] as &$module ) {
294 if ( is_string( $module ) ) {
295 $className = $module;
296 $module = [
297 'class' => $className,
298 ];
299 }
300 $module['name'] = $name;
301 }
302 }
303
304 $this->extractForeignResourcesDir( $info, $name, $dir );
305
306 if ( $version >= 2 ) {
307 $this->extractAttributes( $path, $info );
308 }
309
310 foreach ( $info as $key => $val ) {
311 // If it's a global setting,
312 if ( in_array( $key, self::$globalSettings ) ) {
313 $this->storeToArrayRecursive( $path, "wg$key", $val, $this->globals );
314 continue;
315 }
316 // Ignore anything that starts with a @
317 if ( $key[0] === '@' ) {
318 continue;
319 }
320
321 if ( $version >= 2 ) {
322 // Only allowed attributes are set
323 if ( in_array( $key, self::CORE_ATTRIBS ) ) {
324 $this->storeToArray( $path, $key, $val, $this->attributes );
325 }
326 } else {
327 // version === 1
328 if ( !in_array( $key, self::NOT_ATTRIBS )
329 && !in_array( $key, self::CREDIT_ATTRIBS )
330 ) {
331 // If it's not disallowed, it's an attribute
332 $this->storeToArrayRecursive( $path, $key, $val, $this->attributes );
333 }
334 }
335 }
336 }
337
342 protected function extractAttributes( $path, array $info ) {
343 if ( isset( $info['attributes'] ) ) {
344 foreach ( $info['attributes'] as $extName => $value ) {
345 $this->storeToArrayRecursive( $path, $extName, $value, $this->extAttributes );
346 }
347 }
348 }
349
350 public function getExtractedInfo( bool $includeDev = false ) {
351 // Make sure the merge strategies are set
352 foreach ( $this->globals as $key => $val ) {
353 if ( isset( self::MERGE_STRATEGIES[$key] ) ) {
354 $this->globals[$key][ExtensionRegistry::MERGE_STRATEGY] = self::MERGE_STRATEGIES[$key];
355 }
356 }
357
358 // Merge $this->extAttributes into $this->attributes depending on what is loaded
359 foreach ( $this->extAttributes as $extName => $value ) {
360 // Only set the attribute if $extName is loaded (and hence present in credits)
361 if ( isset( $this->credits[$extName] ) ) {
362 foreach ( $value as $attrName => $attrValue ) {
364 '', // Don't provide a path since it's impossible to generate an error here
365 $extName . $attrName,
366 $attrValue,
367 $this->attributes
368 );
369 }
370 unset( $this->extAttributes[$extName] );
371 }
372 }
373
374 $autoload = $this->getExtractedAutoloadInfo( $includeDev );
375 return [
376 'globals' => $this->globals,
377 'defines' => $this->defines,
378 'callbacks' => $this->callbacks,
379 'credits' => $this->credits,
380 'attributes' => $this->attributes,
381 'autoloaderPaths' => $autoload['files'],
382 'autoloaderClasses' => $autoload['classes'],
383 'autoloaderNS' => $autoload['namespaces'],
384 ];
385 }
386
387 public function getRequirements( array $info, $includeDev ) {
388 // Quick shortcuts
389 if ( !$includeDev || !isset( $info['dev-requires'] ) ) {
390 return $info['requires'] ?? [];
391 }
392
393 if ( !isset( $info['requires'] ) ) {
394 return $info['dev-requires'] ?? [];
395 }
396
397 // OK, we actually have to merge everything
398 $merged = [];
399
400 // Helper that combines version requirements by
401 // picking the non-null if one is, or combines
402 // the two. Note that it is not possible for
403 // both inputs to be null.
404 $pick = static function ( $a, $b ) {
405 if ( $a === null ) {
406 return $b;
407 } elseif ( $b === null ) {
408 return $a;
409 } else {
410 return "$a $b";
411 }
412 };
413
414 $req = $info['requires'];
415 $dev = $info['dev-requires'];
416 if ( isset( $req['MediaWiki'] ) || isset( $dev['MediaWiki'] ) ) {
417 $merged['MediaWiki'] = $pick(
418 $req['MediaWiki'] ?? null,
419 $dev['MediaWiki'] ?? null
420 );
421 }
422
423 $platform = array_merge(
424 array_keys( $req['platform'] ?? [] ),
425 array_keys( $dev['platform'] ?? [] )
426 );
427 if ( $platform ) {
428 foreach ( $platform as $pkey ) {
429 if ( $pkey === 'php' ) {
430 $value = $pick(
431 $req['platform']['php'] ?? null,
432 $dev['platform']['php'] ?? null
433 );
434 } else {
435 // Prefer dev value, but these should be constant
436 // anyway (ext-* and ability-*)
437 $value = $dev['platform'][$pkey] ?? $req['platform'][$pkey];
438 }
439 $merged['platform'][$pkey] = $value;
440 }
441 }
442
443 foreach ( [ 'extensions', 'skins' ] as $thing ) {
444 $things = array_merge(
445 array_keys( $req[$thing] ?? [] ),
446 array_keys( $dev[$thing] ?? [] )
447 );
448 foreach ( $things as $name ) {
449 $merged[$thing][$name] = $pick(
450 $req[$thing][$name] ?? null,
451 $dev[$thing][$name] ?? null
452 );
453 }
454 }
455 return $merged;
456 }
457
469 private function setArrayHookHandler(
470 array $callback,
471 array $hookHandlersAttr,
472 string $name,
473 string $path
474 ) {
475 if ( isset( $callback['handler'] ) ) {
476 $handlerName = $callback['handler'];
477 $handlerDefinition = $hookHandlersAttr[$handlerName] ?? false;
478 if ( !$handlerDefinition ) {
479 throw new UnexpectedValueException(
480 "Missing handler definition for $name in HookHandlers attribute in $path"
481 );
482 }
483 $callback['handler'] = $handlerDefinition;
484 $callback['extensionPath'] = $path;
485 $this->attributes['Hooks'][$name][] = $callback;
486 } else {
487 foreach ( $callback as $callable ) {
488 if ( is_array( $callable ) ) {
489 if ( isset( $callable['handler'] ) ) { // Non-legacy style handler
490 $this->setArrayHookHandler( $callable, $hookHandlersAttr, $name, $path );
491 } else { // Legacy style handler array
492 $this->globals['wgHooks'][$name][] = $callable;
493 }
494 } elseif ( is_string( $callable ) ) {
495 $this->setStringHookHandler( $callable, $hookHandlersAttr, $name, $path );
496 }
497 }
498 }
499 }
500
511 private function setStringHookHandler(
512 string $callback,
513 array $hookHandlersAttr,
514 string $name,
515 string $path
516 ) {
517 if ( isset( $hookHandlersAttr[$callback] ) ) {
518 $handler = [
519 'handler' => $hookHandlersAttr[$callback],
520 'extensionPath' => $path
521 ];
522 $this->attributes['Hooks'][$name][] = $handler;
523 } else { // legacy style handler
524 $this->globals['wgHooks'][$name][] = $callback;
525 }
526 }
527
536 protected function extractHooks( array $info, string $path ) {
537 $extName = $info['name'];
538 if ( isset( $info['Hooks'] ) ) {
539 $hookHandlersAttr = [];
540 foreach ( $info['HookHandlers'] ?? [] as $name => $def ) {
541 $hookHandlersAttr[$name] = [ 'name' => "$extName-$name" ] + $def;
542 }
543 foreach ( $info['Hooks'] as $name => $callback ) {
544 if ( is_string( $callback ) ) {
545 $this->setStringHookHandler( $callback, $hookHandlersAttr, $name, $path );
546 } elseif ( is_array( $callback ) ) {
547 $this->setArrayHookHandler( $callback, $hookHandlersAttr, $name, $path );
548 }
549 }
550 }
551 if ( isset( $info['DeprecatedHooks'] ) ) {
552 $deprecatedHooks = [];
553 foreach ( $info['DeprecatedHooks'] as $name => $deprecatedHookInfo ) {
554 $deprecatedHookInfo += [ 'component' => $extName ];
555 $deprecatedHooks[$name] = $deprecatedHookInfo;
556 }
557 if ( isset( $this->attributes['DeprecatedHooks'] ) ) {
558 $this->attributes['DeprecatedHooks'] += $deprecatedHooks;
559 } else {
560 $this->attributes['DeprecatedHooks'] = $deprecatedHooks;
561 }
562 }
563 }
564
570 protected function extractNamespaces( array $info ) {
571 if ( isset( $info['namespaces'] ) ) {
572 foreach ( $info['namespaces'] as $ns ) {
573 if ( defined( $ns['constant'] ) ) {
574 // If the namespace constant is already defined, use it.
575 // This allows namespace IDs to be overwritten locally.
576 $id = constant( $ns['constant'] );
577 } else {
578 $id = $ns['id'];
579 }
580 $this->defines[ $ns['constant'] ] = $id;
581
582 if ( !( isset( $ns['conditional'] ) && $ns['conditional'] ) ) {
583 // If it is not conditional, register it
584 $this->attributes['ExtensionNamespaces'][$id] = $ns['name'];
585 }
586 if ( isset( $ns['movable'] ) && !$ns['movable'] ) {
587 $this->attributes['ImmovableNamespaces'][] = $id;
588 }
589 if ( isset( $ns['gender'] ) ) {
590 $this->globals['wgExtraGenderNamespaces'][$id] = $ns['gender'];
591 }
592 if ( isset( $ns['subpages'] ) && $ns['subpages'] ) {
593 $this->globals['wgNamespacesWithSubpages'][$id] = true;
594 }
595 if ( isset( $ns['content'] ) && $ns['content'] ) {
596 $this->globals['wgContentNamespaces'][] = $id;
597 }
598 if ( isset( $ns['defaultcontentmodel'] ) ) {
599 $this->globals['wgNamespaceContentModels'][$id] = $ns['defaultcontentmodel'];
600 }
601 if ( isset( $ns['protection'] ) ) {
602 $this->globals['wgNamespaceProtection'][$id] = $ns['protection'];
603 }
604 if ( isset( $ns['capitallinkoverride'] ) ) {
605 $this->globals['wgCapitalLinkOverrides'][$id] = $ns['capitallinkoverride'];
606 }
607 if ( isset( $ns['includable'] ) && !$ns['includable'] ) {
608 $this->globals['wgNonincludableNamespaces'][] = $id;
609 }
610 }
611 }
612 }
613
614 protected function extractResourceLoaderModules( $dir, array $info ) {
615 $defaultPaths = $info['ResourceFileModulePaths'] ?? false;
616 if ( isset( $defaultPaths['localBasePath'] ) ) {
617 if ( $defaultPaths['localBasePath'] === '' ) {
618 // Avoid double slashes (e.g. /extensions/Example//path)
619 $defaultPaths['localBasePath'] = $dir;
620 } else {
621 $defaultPaths['localBasePath'] = "$dir/{$defaultPaths['localBasePath']}";
622 }
623 }
624
625 foreach ( [ 'ResourceModules', 'ResourceModuleSkinStyles', 'OOUIThemePaths' ] as $setting ) {
626 if ( isset( $info[$setting] ) ) {
627 foreach ( $info[$setting] as $name => $data ) {
628 if ( isset( $data['localBasePath'] ) ) {
629 if ( $data['localBasePath'] === '' ) {
630 // Avoid double slashes (e.g. /extensions/Example//path)
631 $data['localBasePath'] = $dir;
632 } else {
633 $data['localBasePath'] = "$dir/{$data['localBasePath']}";
634 }
635 }
636 if ( $defaultPaths ) {
637 $data += $defaultPaths;
638 }
639 $this->attributes[$setting][$name] = $data;
640 }
641 }
642 }
643
644 if ( isset( $info['QUnitTestModule'] ) ) {
645 $data = $info['QUnitTestModule'];
646 if ( isset( $data['localBasePath'] ) ) {
647 if ( $data['localBasePath'] === '' ) {
648 // Avoid double slashes (e.g. /extensions/Example//path)
649 $data['localBasePath'] = $dir;
650 } else {
651 $data['localBasePath'] = "$dir/{$data['localBasePath']}";
652 }
653 }
654 $this->attributes['QUnitTestModules']["test.{$info['name']}"] = $data;
655 }
656
657 if ( isset( $info['MessagePosterModule'] ) ) {
658 $data = $info['MessagePosterModule'];
659 $basePath = $data['localBasePath'] ?? '';
660 $baseDir = $basePath === '' ? $dir : "$dir/$basePath";
661 foreach ( $data['scripts'] ?? [] as $scripts ) {
662 $this->attributes['MessagePosterModule']['scripts'][] =
663 new FilePath( $scripts, $baseDir );
664 }
665 foreach ( $data['dependencies'] ?? [] as $dependency ) {
666 $this->attributes['MessagePosterModule']['dependencies'][] = $dependency;
667 }
668 }
669 }
670
671 protected function extractExtensionMessagesFiles( $dir, array $info ) {
672 if ( isset( $info['ExtensionMessagesFiles'] ) ) {
673 foreach ( $info['ExtensionMessagesFiles'] as &$file ) {
674 $file = "$dir/$file";
675 }
676 $this->globals["wgExtensionMessagesFiles"] += $info['ExtensionMessagesFiles'];
677 }
678 }
679
687 protected function extractMessagesDirs( $dir, array $info ) {
688 if ( isset( $info['MessagesDirs'] ) ) {
689 foreach ( $info['MessagesDirs'] as $name => $files ) {
690 foreach ( (array)$files as $file ) {
691 $this->globals["wgMessagesDirs"][$name][] = "$dir/$file";
692 }
693 }
694 }
695 }
696
704 protected function extractTranslationAliasesDirs( $dir, array $info ) {
705 foreach ( $info['TranslationAliasesDirs'] ?? [] as $name => $files ) {
706 foreach ( (array)$files as $file ) {
707 $this->globals['wgTranslationAliasesDirs'][$name][] = "$dir/$file";
708 }
709 }
710 }
711
718 protected function extractSkins( $dir, array $info ) {
719 if ( isset( $info['ValidSkinNames'] ) ) {
720 foreach ( $info['ValidSkinNames'] as $skinKey => $data ) {
721 if ( isset( $data['args'][0] ) ) {
722 $templateDirectory = $data['args'][0]['templateDirectory'] ?? 'templates';
723 $data['args'][0]['templateDirectory'] = $dir . '/' . $templateDirectory;
724 }
725 $this->globals['wgValidSkinNames'][$skinKey] = $data;
726 }
727 }
728 }
729
735 protected function extractImplicitRights( array $info ) {
736 // Rate limits are only configurable for rights that are either in wgImplicitRights
737 // or in wgAvailableRights. Extensions that define rate limits should not have to
738 // explicitly add them to wgImplicitRights as well, we can do that automatically.
739
740 if ( isset( $info['RateLimits'] ) ) {
741 $rights = array_keys( $info['RateLimits'] );
742
743 if ( isset( $info['AvailableRights'] ) ) {
744 $rights = array_diff( $rights, $info['AvailableRights'] );
745 }
746
747 $this->globals['wgImplicitRights'] = array_merge(
748 $this->globals['wgImplicitRights'] ?? [],
749 $rights
750 );
751 }
752 }
753
758 protected function extractSkinImportPaths( $dir, array $info ) {
759 if ( isset( $info['SkinLessImportPaths'] ) ) {
760 foreach ( $info['SkinLessImportPaths'] as $skin => $subpath ) {
761 $this->attributes['SkinLessImportPaths'][$skin] = "$dir/$subpath";
762 }
763 }
764 }
765
772 protected function extractCredits( $path, array $info ) {
773 $credits = [
774 'path' => $path,
775 'type' => 'other',
776 ];
777 foreach ( self::CREDIT_ATTRIBS as $attr ) {
778 if ( isset( $info[$attr] ) ) {
779 $credits[$attr] = $info[$attr];
780 }
781 }
782
783 $name = $credits['name'];
784
785 // If someone is loading the same thing twice, throw
786 // a nice error (T121493)
787 if ( isset( $this->credits[$name] ) ) {
788 $firstPath = $this->credits[$name]['path'];
789 $secondPath = $credits['path'];
790 throw new InvalidArgumentException(
791 "It was attempted to load $name twice, from $firstPath and $secondPath."
792 );
793 }
794
795 $this->credits[$name] = $credits;
796
797 return $name;
798 }
799
800 protected function extractForeignResourcesDir( array $info, string $name, string $dir ): void {
801 if ( array_key_exists( 'ForeignResourcesDir', $info ) ) {
802 if ( !is_string( $info['ForeignResourcesDir'] ) ) {
803 throw new InvalidArgumentException( "Incorrect ForeignResourcesDir type, must be a string (in $name)" );
804 }
805 $this->attributes['ForeignResourcesDir'][$name] = "{$dir}/{$info['ForeignResourcesDir']}";
806 }
807 }
808
815 protected function extractConfig1( array $info ) {
816 if ( isset( $info['config'] ) ) {
817 if ( isset( $info['config']['_prefix'] ) ) {
818 $prefix = $info['config']['_prefix'];
819 unset( $info['config']['_prefix'] );
820 } else {
821 $prefix = 'wg';
822 }
823 foreach ( $info['config'] as $key => $val ) {
824 if ( $key[0] !== '@' ) {
825 $this->addConfigGlobal( "$prefix$key", $val, $info['name'] );
826 }
827 }
828 }
829 }
830
839 private function applyPath( array $value, string $dir ): array {
840 $result = [];
841
842 foreach ( $value as $k => $v ) {
843 $result[$k] = $dir . '/' . $v;
844 }
845 return $result;
846 }
847
855 protected function extractConfig2( array $info, $dir ) {
856 $prefix = $info['config_prefix'] ?? 'wg';
857 if ( isset( $info['config'] ) ) {
858 foreach ( $info['config'] as $key => $data ) {
859 if ( !array_key_exists( 'value', $data ) ) {
860 throw new UnexpectedValueException( "Missing value for config $key" );
861 }
862
863 $value = $data['value'];
864 if ( isset( $data['path'] ) && $data['path'] ) {
865 if ( is_array( $value ) ) {
866 $value = $this->applyPath( $value, $dir );
867 } else {
868 $value = "$dir/$value";
869 }
870 }
871 if ( isset( $data['merge_strategy'] ) ) {
872 $value[ExtensionRegistry::MERGE_STRATEGY] = $data['merge_strategy'];
873 }
874 $this->addConfigGlobal( "$prefix$key", $value, $info['name'] );
875 $data['providedby'] = $info['name'];
876 if ( isset( $info['ConfigRegistry'][0] ) ) {
877 $data['configregistry'] = array_keys( $info['ConfigRegistry'] )[0];
878 }
879 }
880 }
881 }
882
890 private function addConfigGlobal( $key, $value, $extName ) {
891 if ( array_key_exists( $key, $this->globals ) ) {
892 throw new RuntimeException(
893 "The configuration setting '$key' was already set by MediaWiki core or"
894 . " another extension, and cannot be set again by $extName." );
895 }
896 if ( isset( $value[ExtensionRegistry::MERGE_STRATEGY] ) &&
897 $value[ExtensionRegistry::MERGE_STRATEGY] === 'array_merge_recursive' ) {
899 "Using the array_merge_recursive merge strategy in extension.json and skin.json" .
900 " was deprecated in MediaWiki 1.42",
901 "1.42"
902 );
903 }
904 $this->globals[$key] = $value;
905 }
906
907 protected function extractPathBasedGlobal( $global, $dir, $paths ) {
908 foreach ( $paths as $path ) {
909 $this->globals[$global][] = "$dir/$path";
910 }
911 }
912
922 protected function storeToArrayRecursive( $path, $name, $value, &$array ) {
923 if ( !is_array( $value ) ) {
924 throw new InvalidArgumentException( "The value for '$name' should be an array (from $path)" );
925 }
926 if ( isset( $array[$name] ) ) {
927 $array[$name] = array_merge_recursive( $array[$name], $value );
928 } else {
929 $array[$name] = $value;
930 }
931 }
932
942 protected function storeToArray( $path, $name, $value, &$array ) {
943 if ( !is_array( $value ) ) {
944 throw new InvalidArgumentException( "The value for '$name' should be an array (from $path)" );
945 }
946 if ( isset( $array[$name] ) ) {
947 $array[$name] = array_merge( $array[$name], $value );
948 } else {
949 $array[$name] = $value;
950 }
951 }
952
967 public function getExtractedAutoloadInfo( bool $includeDev = false ): array {
968 $autoload = $this->autoload;
969
970 if ( $includeDev ) {
971 $autoload['classes'] += $this->autoloadDev['classes'];
972 $autoload['namespaces'] += $this->autoloadDev['namespaces'];
973
974 // NOTE: This is here for completeness. Per MW 1.39,
975 // $this->autoloadDev['files'] is always empty.
976 // So avoid the performance hit of array_merge().
977 if ( !empty( $this->autoloadDev['files'] ) ) {
978 // NOTE: Don't use += with numeric keys!
979 // Could use PHPUtils::pushArray.
980 $autoload['files'] = array_merge(
981 $autoload['files'],
982 $this->autoloadDev['files']
983 );
984 }
985 }
986
987 return $autoload;
988 }
989
994 private function extractAutoload( array $info, string $dir ) {
995 if ( isset( $info['load_composer_autoloader'] ) && $info['load_composer_autoloader'] === true ) {
996 $file = "$dir/vendor/autoload.php";
997 if ( file_exists( $file ) ) {
998 $this->autoload['files'][] = $file;
999 }
1000 }
1001
1002 if ( isset( $info['AutoloadClasses'] ) ) {
1003 $paths = $this->applyPath( $info['AutoloadClasses'], $dir );
1004 $this->autoload['classes'] += $paths;
1005 }
1006
1007 if ( isset( $info['AutoloadNamespaces'] ) ) {
1008 $paths = $this->applyPath( $info['AutoloadNamespaces'], $dir );
1009 $this->autoload['namespaces'] += $paths;
1010 }
1011
1012 if ( isset( $info['TestAutoloadClasses'] ) ) {
1013 $paths = $this->applyPath( $info['TestAutoloadClasses'], $dir );
1014 $this->autoloadDev['classes'] += $paths;
1015 }
1016
1017 if ( isset( $info['TestAutoloadNamespaces'] ) ) {
1018 $paths = $this->applyPath( $info['TestAutoloadNamespaces'], $dir );
1019 $this->autoloadDev['namespaces'] += $paths;
1020 }
1021 }
1022}
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
Load extension manifests and then aggregate their contents.
extractHooks(array $info, string $path)
Extract hook information from Hooks and HookHandler attributes.
extractNamespaces(array $info)
Register namespaces with the appropriate global settings.
extractCredits( $path, array $info)
array $attributes
Anything else in the $info that hasn't already been processed.
extractInfoFromFile(string $path)
Extracts extension info from the given JSON file.
extractTranslationAliasesDirs( $dir, array $info)
Set localization related settings, which need to be expanded to use absolute paths.
callable[] $callbacks
Things to be called once the registration of these extensions is done.
array $defines
Things that should be define()'d.
string[][] $autoloadDev
Autoloader information for development.
extractExtensionMessagesFiles( $dir, array $info)
extractConfig1(array $info)
Set configuration settings for manifest_version == 1.
array $globals
Stuff that is going to be set to $GLOBALS.
extractMessagesDirs( $dir, array $info)
Set message-related settings, which need to be expanded to use absolute paths.
extractConfig2(array $info, $dir)
Set configuration settings for manifest_version == 2.
getExtractedInfo(bool $includeDev=false)
extractResourceLoaderModules( $dir, array $info)
array $extAttributes
Extension attributes, keyed by name => settings.
extractSkins( $dir, array $info)
Extract skins and handle path correction for templateDirectory.
extractAttributes( $path, array $info)
getRequirements(array $info, $includeDev)
Get the requirements for the provided info.
extractSkinImportPaths( $dir, array $info)
extractInfo( $path, array $info, $version)
getExtractedAutoloadInfo(bool $includeDev=false)
Returns the extracted autoload info.
storeToArray( $path, $name, $value, &$array)
Stores $value to $array; using array_merge() if $array already contains $name.
extractImplicitRights(array $info)
Extract any user rights that should be granted implicitly.
string[][] $autoload
Autoloader information.
extractPathBasedGlobal( $global, $dir, $paths)
extractForeignResourcesDir(array $info, string $name, string $dir)
storeToArrayRecursive( $path, $name, $value, &$array)
Stores $value to $array; using array_merge_recursive() if $array already contains $name.
static array $globalSettings
Keys that should be set to $GLOBALS.
A class containing constants representing the names of configuration variables.
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:9