MediaWiki REL1_39
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::ConfigRegistry,
33 MainConfigNames::ContentHandlers,
34 MainConfigNames::DefaultUserOptions,
35 MainConfigNames::ExtensionEntryPointListFiles,
36 MainConfigNames::ExtensionFunctions,
37 MainConfigNames::FeedClasses,
38 MainConfigNames::FileExtensions,
39 MainConfigNames::FilterLogTypes,
40 MainConfigNames::GrantPermissionGroups,
41 MainConfigNames::GrantPermissions,
42 MainConfigNames::GroupPermissions,
43 MainConfigNames::GroupsAddToSelf,
44 MainConfigNames::GroupsRemoveFromSelf,
45 MainConfigNames::HiddenPrefs,
46 MainConfigNames::ImplicitGroups,
47 MainConfigNames::JobClasses,
48 MainConfigNames::LogActions,
49 MainConfigNames::LogActionsHandlers,
50 MainConfigNames::LogHeaders,
51 MainConfigNames::LogNames,
52 MainConfigNames::LogRestrictions,
53 MainConfigNames::LogTypes,
54 MainConfigNames::MediaHandlers,
55 MainConfigNames::PasswordPolicy,
56 MainConfigNames::PrivilegedGroups,
57 MainConfigNames::RateLimits,
58 MainConfigNames::RawHtmlMessages,
59 MainConfigNames::ReauthenticateTime,
60 MainConfigNames::RecentChangesFlags,
61 MainConfigNames::RemoveCredentialsBlacklist,
62 MainConfigNames::RemoveGroups,
63 MainConfigNames::ResourceLoaderSources,
64 MainConfigNames::RevokePermissions,
65 MainConfigNames::SessionProviders,
66 MainConfigNames::SpecialPages
67 ];
68
74 protected const CORE_ATTRIBS = [
75 'ParsoidModules',
76 'RestRoutes',
77 'SkinOOUIThemes',
78 'SearchMappings',
79 'TrackingCategories',
80 'TempUserSerialProviders',
81 'TempUserSerialMappings',
82 ];
83
91 protected const MERGE_STRATEGIES = [
92 'wgAuthManagerAutoConfig' => 'array_plus_2d',
93 'wgCapitalLinkOverrides' => 'array_plus',
94 'wgExtraGenderNamespaces' => 'array_plus',
95 'wgGrantPermissions' => 'array_plus_2d',
96 'wgGroupPermissions' => 'array_plus_2d',
97 'wgHooks' => 'array_merge_recursive',
98 'wgNamespaceContentModels' => 'array_plus',
99 'wgNamespaceProtection' => 'array_plus',
100 'wgNamespacesWithSubpages' => 'array_plus',
101 'wgPasswordPolicy' => 'array_merge_recursive',
102 'wgRateLimits' => 'array_plus_2d',
103 'wgRevokePermissions' => 'array_plus_2d',
104 ];
105
111 protected const CREDIT_ATTRIBS = [
112 'type',
113 'author',
114 'description',
115 'descriptionmsg',
116 'license-name',
117 'name',
118 'namemsg',
119 'url',
120 'version',
121 ];
122
129 protected const NOT_ATTRIBS = [
130 'callback',
131 'config',
132 'config_prefix',
133 'load_composer_autoloader',
134 'manifest_version',
135 'namespaces',
136 'requires',
137 'AutoloadClasses',
138 'AutoloadNamespaces',
139 'ExtensionMessagesFiles',
140 'Hooks',
141 'MessagePosterModule',
142 'MessagesDirs',
143 'OOUIThemePaths',
144 'ParserTestFiles',
145 'QUnitTestModule',
146 'ResourceFileModulePaths',
147 'ResourceModuleSkinStyles',
148 'ResourceModules',
149 'ServiceWiringFiles',
150 ];
151
159 protected $globals = [
160 'wgExtensionMessagesFiles' => [],
161 'wgMessagesDirs' => [],
162 ];
163
169 protected $defines = [];
170
177 protected $callbacks = [];
178
182 protected $credits = [];
183
191 protected $autoload = [
192 'files' => [],
193 'classes' => [],
194 'namespaces' => [],
195 ];
196
203 protected $autoloadDev = [
204 'files' => [],
205 'classes' => [],
206 'namespaces' => [],
207 ];
208
215 protected $attributes = [];
216
223 protected $extAttributes = [];
224
232 public function extractInfoFromFile( string $path ) {
233 $json = file_get_contents( $path );
234 $info = json_decode( $json, true );
235
236 if ( !$info ) {
237 throw new RuntimeException( "Failed to load JSON data from $path" );
238 }
239
240 if ( !isset( $info['manifest_version'] ) ) {
242 "{$info['name']}'s extension.json or skin.json does not have manifest_version, " .
243 'this is deprecated since MediaWiki 1.29',
244 '1.29', false, false
245 );
246 $info['manifest_version'] = 1;
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->extractSkins( $dir, $info );
263 $this->extractSkinImportPaths( $dir, $info );
264 $this->extractNamespaces( $info );
265 $this->extractResourceLoaderModules( $dir, $info );
266 if ( isset( $info['ServiceWiringFiles'] ) ) {
268 'wgServiceWiringFiles',
269 $dir,
270 $info['ServiceWiringFiles']
271 );
272 }
273 if ( isset( $info['ParserTestFiles'] ) ) {
275 'wgParserTestFiles',
276 $dir,
277 $info['ParserTestFiles']
278 );
279 }
280 $name = $this->extractCredits( $path, $info );
281 if ( isset( $info['callback'] ) ) {
282 $this->callbacks[$name] = $info['callback'];
283 }
284
285 $this->extractAutoload( $info, $dir );
286
287 // config should be after all core globals are extracted,
288 // so duplicate setting detection will work fully
289 if ( $version >= 2 ) {
290 $this->extractConfig2( $info, $dir );
291 } else {
292 // $version === 1
293 $this->extractConfig1( $info );
294 }
295
296 // Record the extension name in the ParsoidModules property
297 if ( isset( $info['ParsoidModules'] ) ) {
298 foreach ( $info['ParsoidModules'] as &$module ) {
299 if ( is_string( $module ) ) {
300 $className = $module;
301 $module = [
302 'class' => $className,
303 ];
304 }
305 $module['name'] = $name;
306 }
307 }
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 $info = [
379 'globals' => $this->globals,
380 'defines' => $this->defines,
381 'callbacks' => $this->callbacks,
382 'credits' => $this->credits,
383 'attributes' => $this->attributes,
384 'autoloaderPaths' => $autoload['files'],
385 'autoloaderClasses' => $autoload['classes'],
386 'autoloaderNS' => $autoload['namespaces'],
387 ];
388 return $info;
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 // anyways (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
691 protected function extractMessagesDirs( $dir, array $info ) {
692 if ( isset( $info['MessagesDirs'] ) ) {
693 foreach ( $info['MessagesDirs'] as $name => $files ) {
694 foreach ( (array)$files as $file ) {
695 $this->globals["wgMessagesDirs"][$name][] = "$dir/$file";
696 }
697 }
698 }
699 }
700
707 protected function extractSkins( $dir, array $info ) {
708 if ( isset( $info['ValidSkinNames'] ) ) {
709 foreach ( $info['ValidSkinNames'] as $skinKey => $data ) {
710 if ( isset( $data['args'][0]['templateDirectory'] ) ) {
711 $templateDirectory = $data['args'][0]['templateDirectory'];
712 $correctedPath = $dir . '/' . $templateDirectory;
713 // Historically the template directory was relative to core
714 // but it really should've been relative to the skin directory.
715 // If the path exists relative to the skin directory, assume that
716 // is what was intended. Otherwise fall back on the previous behavior
717 // of having it relative to core.
718 if ( is_dir( $correctedPath ) ) {
719 $data['args'][0]['templateDirectory'] = $correctedPath;
720 } else {
721 $data['args'][0]['templateDirectory'] = $templateDirectory;
723 'Template directory should be relative to skin or omitted for skin ' . $skinKey,
724 '1.37'
725 );
726 }
727 } elseif ( isset( $data['args'][0] ) ) {
728 // If not set, we set a sensible default.
729 $data['args'][0]['templateDirectory'] = $dir . '/templates';
730 }
731 $this->globals['wgValidSkinNames'][$skinKey] = $data;
732 }
733 }
734 }
735
740 protected function extractSkinImportPaths( $dir, array $info ) {
741 if ( isset( $info['SkinLessImportPaths'] ) ) {
742 foreach ( $info['SkinLessImportPaths'] as $skin => $subpath ) {
743 $this->attributes['SkinLessImportPaths'][$skin] = "$dir/$subpath";
744 }
745 }
746 }
747
754 protected function extractCredits( $path, array $info ) {
755 $credits = [
756 'path' => $path,
757 'type' => 'other',
758 ];
759 foreach ( self::CREDIT_ATTRIBS as $attr ) {
760 if ( isset( $info[$attr] ) ) {
761 $credits[$attr] = $info[$attr];
762 }
763 }
764
765 $name = $credits['name'];
766
767 // If someone is loading the same thing twice, throw
768 // a nice error (T121493)
769 if ( isset( $this->credits[$name] ) ) {
770 $firstPath = $this->credits[$name]['path'];
771 $secondPath = $credits['path'];
772 throw new Exception( "It was attempted to load $name twice, from $firstPath and $secondPath." );
773 }
774
775 $this->credits[$name] = $credits;
776
777 return $name;
778 }
779
786 protected function extractConfig1( array $info ) {
787 if ( isset( $info['config'] ) ) {
788 if ( isset( $info['config']['_prefix'] ) ) {
789 $prefix = $info['config']['_prefix'];
790 unset( $info['config']['_prefix'] );
791 } else {
792 $prefix = 'wg';
793 }
794 foreach ( $info['config'] as $key => $val ) {
795 if ( $key[0] !== '@' ) {
796 $this->addConfigGlobal( "$prefix$key", $val, $info['name'] );
797 }
798 }
799 }
800 }
801
810 private function applyPath( array $value, string $dir ): array {
811 $result = [];
812
813 foreach ( $value as $k => $v ) {
814 $result[$k] = $dir . '/' . $v;
815 }
816 return $result;
817 }
818
826 protected function extractConfig2( array $info, $dir ) {
827 $prefix = $info['config_prefix'] ?? 'wg';
828 if ( isset( $info['config'] ) ) {
829 foreach ( $info['config'] as $key => $data ) {
830 if ( !array_key_exists( 'value', $data ) ) {
831 throw new UnexpectedValueException( "Missing value for config $key" );
832 }
833
834 $value = $data['value'];
835 if ( isset( $data['path'] ) && $data['path'] ) {
836 if ( is_array( $value ) ) {
837 $value = $this->applyPath( $value, $dir );
838 } else {
839 $value = "$dir/$value";
840 }
841 }
842 if ( isset( $data['merge_strategy'] ) ) {
843 $value[ExtensionRegistry::MERGE_STRATEGY] = $data['merge_strategy'];
844 }
845 $this->addConfigGlobal( "$prefix$key", $value, $info['name'] );
846 $data['providedby'] = $info['name'];
847 if ( isset( $info['ConfigRegistry'][0] ) ) {
848 $data['configregistry'] = array_keys( $info['ConfigRegistry'] )[0];
849 }
850 }
851 }
852 }
853
861 private function addConfigGlobal( $key, $value, $extName ) {
862 if ( array_key_exists( $key, $this->globals ) ) {
863 throw new RuntimeException(
864 "The configuration setting '$key' was already set by MediaWiki core or"
865 . " another extension, and cannot be set again by $extName." );
866 }
867 $this->globals[$key] = $value;
868 }
869
870 protected function extractPathBasedGlobal( $global, $dir, $paths ) {
871 foreach ( $paths as $path ) {
872 $this->globals[$global][] = "$dir/$path";
873 }
874 }
875
885 protected function storeToArrayRecursive( $path, $name, $value, &$array ) {
886 if ( !is_array( $value ) ) {
887 throw new InvalidArgumentException( "The value for '$name' should be an array (from $path)" );
888 }
889 if ( isset( $array[$name] ) ) {
890 $array[$name] = array_merge_recursive( $array[$name], $value );
891 } else {
892 $array[$name] = $value;
893 }
894 }
895
905 protected function storeToArray( $path, $name, $value, &$array ) {
906 if ( !is_array( $value ) ) {
907 throw new InvalidArgumentException( "The value for '$name' should be an array (from $path)" );
908 }
909 if ( isset( $array[$name] ) ) {
910 $array[$name] = array_merge( $array[$name], $value );
911 } else {
912 $array[$name] = $value;
913 }
914 }
915
924 public function getExtraAutoloaderPaths( $dir, array $info ) {
925 wfDeprecated( __METHOD__, '1.39' );
926 $paths = [];
927 if ( isset( $info['load_composer_autoloader'] ) && $info['load_composer_autoloader'] === true ) {
928 $paths[] = "$dir/vendor/autoload.php";
929 }
930 return $paths;
931 }
932
947 public function getExtractedAutoloadInfo( bool $includeDev = false ): array {
948 $autoload = $this->autoload;
949
950 if ( $includeDev ) {
951 $autoload['classes'] += $this->autoloadDev['classes'];
952 $autoload['namespaces'] += $this->autoloadDev['namespaces'];
953
954 // NOTE: This is here for completeness. Per MW 1.39,
955 // $this->autoloadDev['files'] is always empty.
956 // So avoid the performance hit of array_merge().
957 if ( !empty( $this->autoloadDev['files'] ) ) {
958 // NOTE: Don't use += with numeric keys!
959 // Could use PHPUtils::pushArray.
960 $autoload['files'] = array_merge(
961 $autoload['files'],
962 $this->autoloadDev['files']
963 );
964 }
965 }
966
967 return $autoload;
968 }
969
974 private function extractAutoload( array $info, string $dir ) {
975 if ( isset( $info['load_composer_autoloader'] ) && $info['load_composer_autoloader'] === true ) {
976 $file = "$dir/vendor/autoload.php";
977 if ( file_exists( $file ) ) {
978 $this->autoload['files'][] = $file;
979 }
980 }
981
982 if ( isset( $info['AutoloadClasses'] ) ) {
983 $paths = $this->applyPath( $info['AutoloadClasses'], $dir );
984 $this->autoload['classes'] += $paths;
985 }
986
987 if ( isset( $info['AutoloadNamespaces'] ) ) {
988 $paths = $this->applyPath( $info['AutoloadNamespaces'], $dir );
989 $this->autoload['namespaces'] += $paths;
990 }
991
992 if ( isset( $info['TestAutoloadClasses'] ) ) {
993 $paths = $this->applyPath( $info['TestAutoloadClasses'], $dir );
994 $this->autoloadDev['classes'] += $paths;
995 }
996
997 if ( isset( $info['TestAutoloadNamespaces'] ) ) {
998 $paths = $this->applyPath( $info['TestAutoloadNamespaces'], $dir );
999 $this->autoloadDev['namespaces'] += $paths;
1000 }
1001 }
1002}
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
Utility class for loading extension manifests and aggregating 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
Any thing else in the $info that hasn't already been processed.
extractInfoFromFile(string $path)
Extracts extension info from the given JSON file.
callable[] $callbacks
Things to be called once registration of these extensions are done keyed by the name of the extension...
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.
getExtraAutoloaderPaths( $dir, array $info)
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.
string[][] $autoload
Autoloader information.
extractPathBasedGlobal( $global, $dir, $paths)
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
Processors read associated arrays and register whatever is required.
Definition Processor.php:9
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42