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::OutputPipelineStages,
58 MainConfigNames::PasswordPolicy,
59 MainConfigNames::PrivilegedGroups,
60 MainConfigNames::RateLimits,
61 MainConfigNames::RawHtmlMessages,
62 MainConfigNames::ReauthenticateTime,
63 MainConfigNames::RecentChangesFlags,
64 MainConfigNames::RemoveCredentialsBlacklist,
65 MainConfigNames::RemoveGroups,
66 MainConfigNames::ResourceLoaderSources,
67 MainConfigNames::RevokePermissions,
68 MainConfigNames::SessionProviders,
69 MainConfigNames::SpecialPages,
70 MainConfigNames::UserRegistrationProviders,
71 ];
72
78 protected const CORE_ATTRIBS = [
79 'ParsoidModules',
80 'RestRoutes',
81 'SkinOOUIThemes',
82 'SkinCodexThemes',
83 'SearchMappings',
84 'TrackingCategories',
85 'LateJSConfigVarNames',
86 'TempUserSerialProviders',
87 'TempUserSerialMappings',
88 'DatabaseVirtualDomains',
89 'UserOptionsStoreProviders',
90 ];
91
99 protected const MERGE_STRATEGIES = [
100 'wgAuthManagerAutoConfig' => 'array_plus_2d',
101 'wgCapitalLinkOverrides' => 'array_plus',
102 'wgExtraGenderNamespaces' => 'array_plus',
103 'wgGrantPermissions' => 'array_plus_2d',
104 'wgGroupPermissions' => 'array_plus_2d',
105 'wgHooks' => 'array_merge_recursive',
106 'wgNamespaceContentModels' => 'array_plus',
107 'wgNamespaceProtection' => 'array_plus',
108 'wgNamespacesWithSubpages' => 'array_plus',
109 'wgPasswordPolicy' => 'array_merge_recursive',
110 'wgRateLimits' => 'array_plus_2d',
111 'wgRevokePermissions' => 'array_plus_2d',
112 ];
113
119 protected const CREDIT_ATTRIBS = [
120 'type',
121 'author',
122 'description',
123 'descriptionmsg',
124 'license-name',
125 'name',
126 'namemsg',
127 'url',
128 'version',
129 ];
130
137 protected const NOT_ATTRIBS = [
138 'callback',
139 'config',
140 'config_prefix',
141 'load_composer_autoloader',
142 'manifest_version',
143 'namespaces',
144 'requires',
145 'AutoloadClasses',
146 'AutoloadNamespaces',
147 'ExtensionMessagesFiles',
148 'TranslationAliasesDirs',
149 'ForeignResourcesDir',
150 'Hooks',
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->extractExtensionMessagesFiles( $dir, $info );
264 $this->extractRestModuleFiles( $dir, $info );
265 $this->extractMessagesDirs( $dir, $info );
266 $this->extractTranslationAliasesDirs( $dir, $info );
267 $this->extractSkins( $dir, $info );
268 $this->extractSkinImportPaths( $dir, $info );
269 $this->extractNamespaces( $info );
270 $this->extractImplicitRights( $info );
271 $this->extractResourceLoaderModules( $dir, $info );
272 if ( isset( $info['ServiceWiringFiles'] ) ) {
274 'wgServiceWiringFiles',
275 $dir,
276 $info['ServiceWiringFiles']
277 );
278 }
279 $name = $this->extractCredits( $path, $info );
280 if ( isset( $info['callback'] ) ) {
281 $this->callbacks[$name] = $info['callback'];
282 }
283
284 $this->extractAutoload( $info, $dir );
285
286 // config should be after all core globals are extracted,
287 // so duplicate setting detection will work fully
288 if ( $version >= 2 ) {
289 $this->extractConfig2( $info, $dir );
290 } else {
291 // $version === 1
292 $this->extractConfig1( $info );
293 }
294
295 // Record the extension name in the ParsoidModules property
296 if ( isset( $info['ParsoidModules'] ) ) {
297 foreach ( $info['ParsoidModules'] as &$module ) {
298 if ( is_string( $module ) ) {
299 $className = $module;
300 $module = [
301 'class' => $className,
302 ];
303 }
304 $module['name'] = $name;
305 }
306 }
307
308 $this->extractForeignResourcesDir( $info, $name, $dir );
309
310 if ( $version >= 2 ) {
311 $this->extractAttributes( $path, $info );
312 }
313
314 foreach ( $info as $key => $val ) {
315 // If it's a global setting,
316 if ( in_array( $key, self::$globalSettings ) ) {
317 $this->storeToArrayRecursive( $path, "wg$key", $val, $this->globals );
318 continue;
319 }
320 // Ignore anything that starts with a @
321 if ( $key[0] === '@' ) {
322 continue;
323 }
324
325 if ( $version >= 2 ) {
326 // Only allowed attributes are set
327 if ( in_array( $key, self::CORE_ATTRIBS ) ) {
328 $this->storeToArray( $path, $key, $val, $this->attributes );
329 }
330 } else {
331 // version === 1
332 if ( !in_array( $key, self::NOT_ATTRIBS )
333 && !in_array( $key, self::CREDIT_ATTRIBS )
334 ) {
335 // If it's not disallowed, it's an attribute
336 $this->storeToArrayRecursive( $path, $key, $val, $this->attributes );
337 }
338 }
339 }
340 }
341
346 protected function extractAttributes( $path, array $info ) {
347 if ( isset( $info['attributes'] ) ) {
348 foreach ( $info['attributes'] as $extName => $value ) {
349 $this->storeToArrayRecursive( $path, $extName, $value, $this->extAttributes );
350 }
351 }
352 }
353
354 public function getExtractedInfo( bool $includeDev = false ) {
355 // Make sure the merge strategies are set
356 foreach ( $this->globals as $key => $val ) {
357 if ( isset( self::MERGE_STRATEGIES[$key] ) ) {
358 $this->globals[$key][ExtensionRegistry::MERGE_STRATEGY] = self::MERGE_STRATEGIES[$key];
359 }
360 }
361
362 // Merge $this->extAttributes into $this->attributes depending on what is loaded
363 foreach ( $this->extAttributes as $extName => $value ) {
364 // Only set the attribute if $extName is loaded (and hence present in credits)
365 if ( isset( $this->credits[$extName] ) ) {
366 foreach ( $value as $attrName => $attrValue ) {
368 '', // Don't provide a path since it's impossible to generate an error here
369 $extName . $attrName,
370 $attrValue,
371 $this->attributes
372 );
373 }
374 unset( $this->extAttributes[$extName] );
375 }
376 }
377
378 $autoload = $this->getExtractedAutoloadInfo( $includeDev );
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 return $merged;
460 }
461
473 private function setArrayHookHandler(
474 array $callback,
475 array $hookHandlersAttr,
476 string $name,
477 string $path
478 ) {
479 if ( isset( $callback['handler'] ) ) {
480 $handlerName = $callback['handler'];
481 $handlerDefinition = $hookHandlersAttr[$handlerName] ?? false;
482 if ( !$handlerDefinition ) {
483 throw new UnexpectedValueException(
484 "Missing handler definition for $name in HookHandlers attribute in $path"
485 );
486 }
487 $callback['handler'] = $handlerDefinition;
488 $callback['extensionPath'] = $path;
489 $this->attributes['Hooks'][$name][] = $callback;
490 } else {
491 foreach ( $callback as $callable ) {
492 if ( is_array( $callable ) ) {
493 if ( isset( $callable['handler'] ) ) { // Non-legacy style handler
494 $this->setArrayHookHandler( $callable, $hookHandlersAttr, $name, $path );
495 } else { // Legacy style handler array
496 $this->globals['wgHooks'][$name][] = $callable;
497 }
498 } elseif ( is_string( $callable ) ) {
499 $this->setStringHookHandler( $callable, $hookHandlersAttr, $name, $path );
500 }
501 }
502 }
503 }
504
515 private function setStringHookHandler(
516 string $callback,
517 array $hookHandlersAttr,
518 string $name,
519 string $path
520 ) {
521 if ( isset( $hookHandlersAttr[$callback] ) ) {
522 $handler = [
523 'handler' => $hookHandlersAttr[$callback],
524 'extensionPath' => $path
525 ];
526 $this->attributes['Hooks'][$name][] = $handler;
527 } else { // legacy style handler
528 $this->globals['wgHooks'][$name][] = $callback;
529 }
530 }
531
540 protected function extractHooks( array $info, string $path ) {
541 $extName = $info['name'];
542 if ( isset( $info['Hooks'] ) ) {
543 $hookHandlersAttr = [];
544 foreach ( $info['HookHandlers'] ?? [] as $name => $def ) {
545 $hookHandlersAttr[$name] = [ 'name' => "$extName-$name" ] + $def;
546 }
547 foreach ( $info['Hooks'] as $name => $callback ) {
548 if ( is_string( $callback ) ) {
549 $this->setStringHookHandler( $callback, $hookHandlersAttr, $name, $path );
550 } elseif ( is_array( $callback ) ) {
551 $this->setArrayHookHandler( $callback, $hookHandlersAttr, $name, $path );
552 }
553 }
554 }
555 if ( isset( $info['DeprecatedHooks'] ) ) {
556 $deprecatedHooks = [];
557 foreach ( $info['DeprecatedHooks'] as $name => $deprecatedHookInfo ) {
558 $deprecatedHookInfo += [ 'component' => $extName ];
559 $deprecatedHooks[$name] = $deprecatedHookInfo;
560 }
561 if ( isset( $this->attributes['DeprecatedHooks'] ) ) {
562 $this->attributes['DeprecatedHooks'] += $deprecatedHooks;
563 } else {
564 $this->attributes['DeprecatedHooks'] = $deprecatedHooks;
565 }
566 }
567 }
568
574 protected function extractNamespaces( array $info ) {
575 if ( isset( $info['namespaces'] ) ) {
576 foreach ( $info['namespaces'] as $ns ) {
577 if ( defined( $ns['constant'] ) ) {
578 // If the namespace constant is already defined, use it.
579 // This allows namespace IDs to be overwritten locally.
580 $id = constant( $ns['constant'] );
581 } else {
582 $id = $ns['id'];
583 }
584 $this->defines[ $ns['constant'] ] = $id;
585
586 if ( !( isset( $ns['conditional'] ) && $ns['conditional'] ) ) {
587 // If it is not conditional, register it
588 $this->attributes['ExtensionNamespaces'][$id] = $ns['name'];
589 }
590 if ( isset( $ns['movable'] ) && !$ns['movable'] ) {
591 $this->attributes['ImmovableNamespaces'][] = $id;
592 }
593 if ( isset( $ns['gender'] ) ) {
594 $this->globals['wgExtraGenderNamespaces'][$id] = $ns['gender'];
595 }
596 if ( isset( $ns['subpages'] ) && $ns['subpages'] ) {
597 $this->globals['wgNamespacesWithSubpages'][$id] = true;
598 }
599 if ( isset( $ns['content'] ) && $ns['content'] ) {
600 $this->globals['wgContentNamespaces'][] = $id;
601 }
602 if ( isset( $ns['defaultcontentmodel'] ) ) {
603 $this->globals['wgNamespaceContentModels'][$id] = $ns['defaultcontentmodel'];
604 }
605 if ( isset( $ns['protection'] ) ) {
606 $this->globals['wgNamespaceProtection'][$id] = $ns['protection'];
607 }
608 if ( isset( $ns['capitallinkoverride'] ) ) {
609 $this->globals['wgCapitalLinkOverrides'][$id] = $ns['capitallinkoverride'];
610 }
611 if ( isset( $ns['includable'] ) && !$ns['includable'] ) {
612 $this->globals['wgNonincludableNamespaces'][] = $id;
613 }
614 }
615 }
616 }
617
618 protected function extractResourceLoaderModules( $dir, array $info ) {
619 $defaultPaths = $info['ResourceFileModulePaths'] ?? false;
620 if ( isset( $defaultPaths['localBasePath'] ) ) {
621 if ( $defaultPaths['localBasePath'] === '' ) {
622 // Avoid double slashes (e.g. /extensions/Example//path)
623 $defaultPaths['localBasePath'] = $dir;
624 } else {
625 $defaultPaths['localBasePath'] = "$dir/{$defaultPaths['localBasePath']}";
626 }
627 }
628
629 foreach ( [ 'ResourceModules', 'ResourceModuleSkinStyles', 'OOUIThemePaths' ] as $setting ) {
630 if ( isset( $info[$setting] ) ) {
631 foreach ( $info[$setting] as $name => $data ) {
632 if ( isset( $data['localBasePath'] ) ) {
633 if ( $data['localBasePath'] === '' ) {
634 // Avoid double slashes (e.g. /extensions/Example//path)
635 $data['localBasePath'] = $dir;
636 } else {
637 $data['localBasePath'] = "$dir/{$data['localBasePath']}";
638 }
639 }
640 if ( $defaultPaths ) {
641 $data += $defaultPaths;
642 }
643 $this->attributes[$setting][$name] = $data;
644 }
645 }
646 }
647
648 if ( isset( $info['QUnitTestModule'] ) ) {
649 $data = $info['QUnitTestModule'];
650 if ( isset( $data['localBasePath'] ) ) {
651 if ( $data['localBasePath'] === '' ) {
652 // Avoid double slashes (e.g. /extensions/Example//path)
653 $data['localBasePath'] = $dir;
654 } else {
655 $data['localBasePath'] = "$dir/{$data['localBasePath']}";
656 }
657 }
658 $this->attributes['QUnitTestModules']["test.{$info['name']}"] = $data;
659 }
660
661 if ( isset( $info['MessagePosterModule'] ) ) {
662 $data = $info['MessagePosterModule'];
663 $basePath = $data['localBasePath'] ?? '';
664 $baseDir = $basePath === '' ? $dir : "$dir/$basePath";
665 foreach ( $data['scripts'] ?? [] as $scripts ) {
666 $this->attributes['MessagePosterModule']['scripts'][] =
667 new FilePath( $scripts, $baseDir );
668 }
669 foreach ( $data['dependencies'] ?? [] as $dependency ) {
670 $this->attributes['MessagePosterModule']['dependencies'][] = $dependency;
671 }
672 }
673 }
674
675 protected function extractExtensionMessagesFiles( $dir, array $info ) {
676 if ( isset( $info['ExtensionMessagesFiles'] ) ) {
677 foreach ( $info['ExtensionMessagesFiles'] as &$file ) {
678 $file = "$dir/$file";
679 }
680 $this->globals["wgExtensionMessagesFiles"] += $info['ExtensionMessagesFiles'];
681 }
682 }
683
684 protected function extractRestModuleFiles( $dir, array $info ) {
685 $var = MainConfigNames::RestAPIAdditionalRouteFiles;
686 if ( isset( $info['RestModuleFiles'] ) ) {
687 foreach ( $info['RestModuleFiles'] as &$file ) {
688 $this->globals["wg$var"][] = "$dir/$file";
689 }
690 }
691 }
692
700 protected function extractMessagesDirs( $dir, array $info ) {
701 if ( isset( $info['MessagesDirs'] ) ) {
702 foreach ( $info['MessagesDirs'] as $name => $files ) {
703 foreach ( (array)$files as $file ) {
704 $this->globals["wgMessagesDirs"][$name][] = "$dir/$file";
705 }
706 }
707 }
708 }
709
717 protected function extractTranslationAliasesDirs( $dir, array $info ) {
718 foreach ( $info['TranslationAliasesDirs'] ?? [] as $name => $files ) {
719 foreach ( (array)$files as $file ) {
720 $this->globals['wgTranslationAliasesDirs'][$name][] = "$dir/$file";
721 }
722 }
723 }
724
731 protected function extractSkins( $dir, array $info ) {
732 if ( isset( $info['ValidSkinNames'] ) ) {
733 foreach ( $info['ValidSkinNames'] as $skinKey => $data ) {
734 if ( isset( $data['args'][0] ) ) {
735 $templateDirectory = $data['args'][0]['templateDirectory'] ?? 'templates';
736 $data['args'][0]['templateDirectory'] = $dir . '/' . $templateDirectory;
737 }
738 $this->globals['wgValidSkinNames'][$skinKey] = $data;
739 }
740 }
741 }
742
748 protected function extractImplicitRights( array $info ) {
749 // Rate limits are only configurable for rights that are either in wgImplicitRights
750 // or in wgAvailableRights. Extensions that define rate limits should not have to
751 // explicitly add them to wgImplicitRights as well, we can do that automatically.
752
753 if ( isset( $info['RateLimits'] ) ) {
754 $rights = array_keys( $info['RateLimits'] );
755
756 if ( isset( $info['AvailableRights'] ) ) {
757 $rights = array_diff( $rights, $info['AvailableRights'] );
758 }
759
760 $this->globals['wgImplicitRights'] = array_merge(
761 $this->globals['wgImplicitRights'] ?? [],
762 $rights
763 );
764 }
765 }
766
771 protected function extractSkinImportPaths( $dir, array $info ) {
772 if ( isset( $info['SkinLessImportPaths'] ) ) {
773 foreach ( $info['SkinLessImportPaths'] as $skin => $subpath ) {
774 $this->attributes['SkinLessImportPaths'][$skin] = "$dir/$subpath";
775 }
776 }
777 }
778
785 protected function extractCredits( $path, array $info ) {
786 $credits = [
787 'path' => $path,
788 'type' => 'other',
789 ];
790 foreach ( self::CREDIT_ATTRIBS as $attr ) {
791 if ( isset( $info[$attr] ) ) {
792 $credits[$attr] = $info[$attr];
793 }
794 }
795
796 $name = $credits['name'];
797
798 // If someone is loading the same thing twice, throw
799 // a nice error (T121493)
800 if ( isset( $this->credits[$name] ) ) {
801 $firstPath = $this->credits[$name]['path'];
802 $secondPath = $credits['path'];
803 throw new InvalidArgumentException(
804 "It was attempted to load $name twice, from $firstPath and $secondPath."
805 );
806 }
807
808 $this->credits[$name] = $credits;
809
810 return $name;
811 }
812
813 protected function extractForeignResourcesDir( array $info, string $name, string $dir ): void {
814 if ( array_key_exists( 'ForeignResourcesDir', $info ) ) {
815 if ( !is_string( $info['ForeignResourcesDir'] ) ) {
816 throw new InvalidArgumentException( "Incorrect ForeignResourcesDir type, must be a string (in $name)" );
817 }
818 $this->attributes['ForeignResourcesDir'][$name] = "{$dir}/{$info['ForeignResourcesDir']}";
819 }
820 }
821
828 protected function extractConfig1( array $info ) {
829 if ( isset( $info['config'] ) ) {
830 if ( isset( $info['config']['_prefix'] ) ) {
831 $prefix = $info['config']['_prefix'];
832 unset( $info['config']['_prefix'] );
833 } else {
834 $prefix = 'wg';
835 }
836 foreach ( $info['config'] as $key => $val ) {
837 if ( $key[0] !== '@' ) {
838 $this->addConfigGlobal( "$prefix$key", $val, $info['name'] );
839 }
840 }
841 }
842 }
843
852 private function applyPath( array $value, string $dir ): array {
853 $result = [];
854
855 foreach ( $value as $k => $v ) {
856 $result[$k] = $dir . '/' . $v;
857 }
858 return $result;
859 }
860
868 protected function extractConfig2( array $info, $dir ) {
869 $prefix = $info['config_prefix'] ?? 'wg';
870 if ( isset( $info['config'] ) ) {
871 foreach ( $info['config'] as $key => $data ) {
872 if ( !array_key_exists( 'value', $data ) ) {
873 throw new UnexpectedValueException( "Missing value for config $key" );
874 }
875
876 $value = $data['value'];
877 if ( isset( $data['path'] ) && $data['path'] ) {
878 if ( is_array( $value ) ) {
879 $value = $this->applyPath( $value, $dir );
880 } else {
881 $value = "$dir/$value";
882 }
883 }
884 if ( isset( $data['merge_strategy'] ) ) {
885 $value[ExtensionRegistry::MERGE_STRATEGY] = $data['merge_strategy'];
886 }
887 $this->addConfigGlobal( "$prefix$key", $value, $info['name'] );
888 $data['providedby'] = $info['name'];
889 if ( isset( $info['ConfigRegistry'][0] ) ) {
890 $data['configregistry'] = array_keys( $info['ConfigRegistry'] )[0];
891 }
892 }
893 }
894 }
895
903 private function addConfigGlobal( $key, $value, $extName ) {
904 if ( array_key_exists( $key, $this->globals ) ) {
905 throw new RuntimeException(
906 "The configuration setting '$key' was already set by MediaWiki core or"
907 . " another extension, and cannot be set again by $extName." );
908 }
909 if ( isset( $value[ExtensionRegistry::MERGE_STRATEGY] ) &&
910 $value[ExtensionRegistry::MERGE_STRATEGY] === 'array_merge_recursive' ) {
912 "Using the array_merge_recursive merge strategy in extension.json and skin.json" .
913 " was deprecated in MediaWiki 1.42",
914 "1.42"
915 );
916 }
917 $this->globals[$key] = $value;
918 }
919
920 protected function extractPathBasedGlobal( $global, $dir, $paths ) {
921 foreach ( $paths as $path ) {
922 $this->globals[$global][] = "$dir/$path";
923 }
924 }
925
935 protected function storeToArrayRecursive( $path, $name, $value, &$array ) {
936 if ( !is_array( $value ) ) {
937 throw new InvalidArgumentException( "The value for '$name' should be an array (from $path)" );
938 }
939 if ( isset( $array[$name] ) ) {
940 $array[$name] = array_merge_recursive( $array[$name], $value );
941 } else {
942 $array[$name] = $value;
943 }
944 }
945
955 protected function storeToArray( $path, $name, $value, &$array ) {
956 if ( !is_array( $value ) ) {
957 throw new InvalidArgumentException( "The value for '$name' should be an array (from $path)" );
958 }
959 if ( isset( $array[$name] ) ) {
960 $array[$name] = array_merge( $array[$name], $value );
961 } else {
962 $array[$name] = $value;
963 }
964 }
965
980 public function getExtractedAutoloadInfo( bool $includeDev = false ): array {
981 $autoload = $this->autoload;
982
983 if ( $includeDev ) {
984 $autoload['classes'] += $this->autoloadDev['classes'];
985 $autoload['namespaces'] += $this->autoloadDev['namespaces'];
986
987 // NOTE: This is here for completeness. Per MW 1.39,
988 // $this->autoloadDev['files'] is always empty.
989 // So avoid the performance hit of array_merge().
990 if ( !empty( $this->autoloadDev['files'] ) ) {
991 // NOTE: Don't use += with numeric keys!
992 // Could use PHPUtils::pushArray.
993 $autoload['files'] = array_merge(
994 $autoload['files'],
995 $this->autoloadDev['files']
996 );
997 }
998 }
999
1000 return $autoload;
1001 }
1002
1007 private function extractAutoload( array $info, string $dir ) {
1008 if ( isset( $info['load_composer_autoloader'] ) && $info['load_composer_autoloader'] === true ) {
1009 $file = "$dir/vendor/autoload.php";
1010 if ( file_exists( $file ) ) {
1011 $this->autoload['files'][] = $file;
1012 }
1013 }
1014
1015 if ( isset( $info['AutoloadClasses'] ) ) {
1016 $paths = $this->applyPath( $info['AutoloadClasses'], $dir );
1017 $this->autoload['classes'] += $paths;
1018 }
1019
1020 if ( isset( $info['AutoloadNamespaces'] ) ) {
1021 $paths = $this->applyPath( $info['AutoloadNamespaces'], $dir );
1022 $this->autoload['namespaces'] += $paths;
1023 }
1024
1025 if ( isset( $info['TestAutoloadClasses'] ) ) {
1026 $paths = $this->applyPath( $info['TestAutoloadClasses'], $dir );
1027 $this->autoloadDev['classes'] += $paths;
1028 }
1029
1030 if ( isset( $info['TestAutoloadNamespaces'] ) ) {
1031 $paths = $this->applyPath( $info['TestAutoloadNamespaces'], $dir );
1032 $this->autoloadDev['namespaces'] += $paths;
1033 }
1034 }
1035}
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)
extractRestModuleFiles( $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