MediaWiki REL1_40
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 'SkinCodexThemes',
79 'SearchMappings',
80 'TrackingCategories',
81 'LateJSConfigVarNames',
82 'TempUserSerialProviders',
83 'TempUserSerialMappings',
84 ];
85
93 protected const MERGE_STRATEGIES = [
94 'wgAuthManagerAutoConfig' => 'array_plus_2d',
95 'wgCapitalLinkOverrides' => 'array_plus',
96 'wgExtraGenderNamespaces' => 'array_plus',
97 'wgGrantPermissions' => 'array_plus_2d',
98 'wgGroupPermissions' => 'array_plus_2d',
99 'wgHooks' => 'array_merge_recursive',
100 'wgNamespaceContentModels' => 'array_plus',
101 'wgNamespaceProtection' => 'array_plus',
102 'wgNamespacesWithSubpages' => 'array_plus',
103 'wgPasswordPolicy' => 'array_merge_recursive',
104 'wgRateLimits' => 'array_plus_2d',
105 'wgRevokePermissions' => 'array_plus_2d',
106 ];
107
113 protected const CREDIT_ATTRIBS = [
114 'type',
115 'author',
116 'description',
117 'descriptionmsg',
118 'license-name',
119 'name',
120 'namemsg',
121 'url',
122 'version',
123 ];
124
131 protected const NOT_ATTRIBS = [
132 'callback',
133 'config',
134 'config_prefix',
135 'load_composer_autoloader',
136 'manifest_version',
137 'namespaces',
138 'requires',
139 'AutoloadClasses',
140 'AutoloadNamespaces',
141 'ExtensionMessagesFiles',
142 'Hooks',
143 'MessagePosterModule',
144 'MessagesDirs',
145 'OOUIThemePaths',
146 'QUnitTestModule',
147 'ResourceFileModulePaths',
148 'ResourceModuleSkinStyles',
149 'ResourceModules',
150 'ServiceWiringFiles',
151 ];
152
160 protected $globals = [
161 'wgExtensionMessagesFiles' => [],
162 'wgMessagesDirs' => [],
163 ];
164
170 protected $defines = [];
171
178 protected $callbacks = [];
179
183 protected $credits = [];
184
192 protected $autoload = [
193 'files' => [],
194 'classes' => [],
195 'namespaces' => [],
196 ];
197
204 protected $autoloadDev = [
205 'files' => [],
206 'classes' => [],
207 'namespaces' => [],
208 ];
209
216 protected $attributes = [];
217
224 protected $extAttributes = [];
225
233 public function extractInfoFromFile( string $path ) {
234 $json = file_get_contents( $path );
235 $info = json_decode( $json, true );
236
237 if ( !$info ) {
238 throw new RuntimeException( "Failed to load JSON data from $path" );
239 }
240
241 if ( !isset( $info['manifest_version'] ) ) {
243 "{$info['name']}'s extension.json or skin.json does not have manifest_version, " .
244 'this is deprecated since MediaWiki 1.29',
245 '1.29', false, false
246 );
247 $info['manifest_version'] = 1;
248 }
249
250 $this->extractInfo( $path, $info, $info['manifest_version'] );
251 }
252
258 public function extractInfo( $path, array $info, $version ) {
259 $dir = dirname( $path );
260 $this->extractHooks( $info, $path );
261 $this->extractExtensionMessagesFiles( $dir, $info );
262 $this->extractMessagesDirs( $dir, $info );
263 $this->extractSkins( $dir, $info );
264 $this->extractSkinImportPaths( $dir, $info );
265 $this->extractNamespaces( $info );
266 $this->extractResourceLoaderModules( $dir, $info );
267 if ( isset( $info['ServiceWiringFiles'] ) ) {
269 'wgServiceWiringFiles',
270 $dir,
271 $info['ServiceWiringFiles']
272 );
273 }
274 $name = $this->extractCredits( $path, $info );
275 if ( isset( $info['callback'] ) ) {
276 $this->callbacks[$name] = $info['callback'];
277 }
278
279 $this->extractAutoload( $info, $dir );
280
281 // config should be after all core globals are extracted,
282 // so duplicate setting detection will work fully
283 if ( $version >= 2 ) {
284 $this->extractConfig2( $info, $dir );
285 } else {
286 // $version === 1
287 $this->extractConfig1( $info );
288 }
289
290 // Record the extension name in the ParsoidModules property
291 if ( isset( $info['ParsoidModules'] ) ) {
292 foreach ( $info['ParsoidModules'] as &$module ) {
293 if ( is_string( $module ) ) {
294 $className = $module;
295 $module = [
296 'class' => $className,
297 ];
298 }
299 $module['name'] = $name;
300 }
301 }
302
303 if ( $version >= 2 ) {
304 $this->extractAttributes( $path, $info );
305 }
306
307 foreach ( $info as $key => $val ) {
308 // If it's a global setting,
309 if ( in_array( $key, self::$globalSettings ) ) {
310 $this->storeToArrayRecursive( $path, "wg$key", $val, $this->globals );
311 continue;
312 }
313 // Ignore anything that starts with a @
314 if ( $key[0] === '@' ) {
315 continue;
316 }
317
318 if ( $version >= 2 ) {
319 // Only allowed attributes are set
320 if ( in_array( $key, self::CORE_ATTRIBS ) ) {
321 $this->storeToArray( $path, $key, $val, $this->attributes );
322 }
323 } else {
324 // version === 1
325 if ( !in_array( $key, self::NOT_ATTRIBS )
326 && !in_array( $key, self::CREDIT_ATTRIBS )
327 ) {
328 // If it's not disallowed, it's an attribute
329 $this->storeToArrayRecursive( $path, $key, $val, $this->attributes );
330 }
331 }
332 }
333 }
334
339 protected function extractAttributes( $path, array $info ) {
340 if ( isset( $info['attributes'] ) ) {
341 foreach ( $info['attributes'] as $extName => $value ) {
342 $this->storeToArrayRecursive( $path, $extName, $value, $this->extAttributes );
343 }
344 }
345 }
346
347 public function getExtractedInfo( bool $includeDev = false ) {
348 // Make sure the merge strategies are set
349 foreach ( $this->globals as $key => $val ) {
350 if ( isset( self::MERGE_STRATEGIES[$key] ) ) {
351 $this->globals[$key][ExtensionRegistry::MERGE_STRATEGY] = self::MERGE_STRATEGIES[$key];
352 }
353 }
354
355 // Merge $this->extAttributes into $this->attributes depending on what is loaded
356 foreach ( $this->extAttributes as $extName => $value ) {
357 // Only set the attribute if $extName is loaded (and hence present in credits)
358 if ( isset( $this->credits[$extName] ) ) {
359 foreach ( $value as $attrName => $attrValue ) {
361 '', // Don't provide a path since it's impossible to generate an error here
362 $extName . $attrName,
363 $attrValue,
364 $this->attributes
365 );
366 }
367 unset( $this->extAttributes[$extName] );
368 }
369 }
370
371 $autoload = $this->getExtractedAutoloadInfo( $includeDev );
372 $info = [
373 'globals' => $this->globals,
374 'defines' => $this->defines,
375 'callbacks' => $this->callbacks,
376 'credits' => $this->credits,
377 'attributes' => $this->attributes,
378 'autoloaderPaths' => $autoload['files'],
379 'autoloaderClasses' => $autoload['classes'],
380 'autoloaderNS' => $autoload['namespaces'],
381 ];
382 return $info;
383 }
384
385 public function getRequirements( array $info, $includeDev ) {
386 // Quick shortcuts
387 if ( !$includeDev || !isset( $info['dev-requires'] ) ) {
388 return $info['requires'] ?? [];
389 }
390
391 if ( !isset( $info['requires'] ) ) {
392 return $info['dev-requires'] ?? [];
393 }
394
395 // OK, we actually have to merge everything
396 $merged = [];
397
398 // Helper that combines version requirements by
399 // picking the non-null if one is, or combines
400 // the two. Note that it is not possible for
401 // both inputs to be null.
402 $pick = static function ( $a, $b ) {
403 if ( $a === null ) {
404 return $b;
405 } elseif ( $b === null ) {
406 return $a;
407 } else {
408 return "$a $b";
409 }
410 };
411
412 $req = $info['requires'];
413 $dev = $info['dev-requires'];
414 if ( isset( $req['MediaWiki'] ) || isset( $dev['MediaWiki'] ) ) {
415 $merged['MediaWiki'] = $pick(
416 $req['MediaWiki'] ?? null,
417 $dev['MediaWiki'] ?? null
418 );
419 }
420
421 $platform = array_merge(
422 array_keys( $req['platform'] ?? [] ),
423 array_keys( $dev['platform'] ?? [] )
424 );
425 if ( $platform ) {
426 foreach ( $platform as $pkey ) {
427 if ( $pkey === 'php' ) {
428 $value = $pick(
429 $req['platform']['php'] ?? null,
430 $dev['platform']['php'] ?? null
431 );
432 } else {
433 // Prefer dev value, but these should be constant
434 // anyways (ext-* and ability-*)
435 $value = $dev['platform'][$pkey] ?? $req['platform'][$pkey];
436 }
437 $merged['platform'][$pkey] = $value;
438 }
439 }
440
441 foreach ( [ 'extensions', 'skins' ] as $thing ) {
442 $things = array_merge(
443 array_keys( $req[$thing] ?? [] ),
444 array_keys( $dev[$thing] ?? [] )
445 );
446 foreach ( $things as $name ) {
447 $merged[$thing][$name] = $pick(
448 $req[$thing][$name] ?? null,
449 $dev[$thing][$name] ?? null
450 );
451 }
452 }
453 return $merged;
454 }
455
467 private function setArrayHookHandler(
468 array $callback,
469 array $hookHandlersAttr,
470 string $name,
471 string $path
472 ) {
473 if ( isset( $callback['handler'] ) ) {
474 $handlerName = $callback['handler'];
475 $handlerDefinition = $hookHandlersAttr[$handlerName] ?? false;
476 if ( !$handlerDefinition ) {
477 throw new UnexpectedValueException(
478 "Missing handler definition for $name in HookHandlers attribute in $path"
479 );
480 }
481 $callback['handler'] = $handlerDefinition;
482 $callback['extensionPath'] = $path;
483 $this->attributes['Hooks'][$name][] = $callback;
484 } else {
485 foreach ( $callback as $callable ) {
486 if ( is_array( $callable ) ) {
487 if ( isset( $callable['handler'] ) ) { // Non-legacy style handler
488 $this->setArrayHookHandler( $callable, $hookHandlersAttr, $name, $path );
489 } else { // Legacy style handler array
490 $this->globals['wgHooks'][$name][] = $callable;
491 }
492 } elseif ( is_string( $callable ) ) {
493 $this->setStringHookHandler( $callable, $hookHandlersAttr, $name, $path );
494 }
495 }
496 }
497 }
498
509 private function setStringHookHandler(
510 string $callback,
511 array $hookHandlersAttr,
512 string $name,
513 string $path
514 ) {
515 if ( isset( $hookHandlersAttr[$callback] ) ) {
516 $handler = [
517 'handler' => $hookHandlersAttr[$callback],
518 'extensionPath' => $path
519 ];
520 $this->attributes['Hooks'][$name][] = $handler;
521 } else { // legacy style handler
522 $this->globals['wgHooks'][$name][] = $callback;
523 }
524 }
525
534 protected function extractHooks( array $info, string $path ) {
535 $extName = $info['name'];
536 if ( isset( $info['Hooks'] ) ) {
537 $hookHandlersAttr = [];
538 foreach ( $info['HookHandlers'] ?? [] as $name => $def ) {
539 $hookHandlersAttr[$name] = [ 'name' => "$extName-$name" ] + $def;
540 }
541 foreach ( $info['Hooks'] as $name => $callback ) {
542 if ( is_string( $callback ) ) {
543 $this->setStringHookHandler( $callback, $hookHandlersAttr, $name, $path );
544 } elseif ( is_array( $callback ) ) {
545 $this->setArrayHookHandler( $callback, $hookHandlersAttr, $name, $path );
546 }
547 }
548 }
549 if ( isset( $info['DeprecatedHooks'] ) ) {
550 $deprecatedHooks = [];
551 foreach ( $info['DeprecatedHooks'] as $name => $deprecatedHookInfo ) {
552 $deprecatedHookInfo += [ 'component' => $extName ];
553 $deprecatedHooks[$name] = $deprecatedHookInfo;
554 }
555 if ( isset( $this->attributes['DeprecatedHooks'] ) ) {
556 $this->attributes['DeprecatedHooks'] += $deprecatedHooks;
557 } else {
558 $this->attributes['DeprecatedHooks'] = $deprecatedHooks;
559 }
560 }
561 }
562
568 protected function extractNamespaces( array $info ) {
569 if ( isset( $info['namespaces'] ) ) {
570 foreach ( $info['namespaces'] as $ns ) {
571 if ( defined( $ns['constant'] ) ) {
572 // If the namespace constant is already defined, use it.
573 // This allows namespace IDs to be overwritten locally.
574 $id = constant( $ns['constant'] );
575 } else {
576 $id = $ns['id'];
577 }
578 $this->defines[ $ns['constant'] ] = $id;
579
580 if ( !( isset( $ns['conditional'] ) && $ns['conditional'] ) ) {
581 // If it is not conditional, register it
582 $this->attributes['ExtensionNamespaces'][$id] = $ns['name'];
583 }
584 if ( isset( $ns['movable'] ) && !$ns['movable'] ) {
585 $this->attributes['ImmovableNamespaces'][] = $id;
586 }
587 if ( isset( $ns['gender'] ) ) {
588 $this->globals['wgExtraGenderNamespaces'][$id] = $ns['gender'];
589 }
590 if ( isset( $ns['subpages'] ) && $ns['subpages'] ) {
591 $this->globals['wgNamespacesWithSubpages'][$id] = true;
592 }
593 if ( isset( $ns['content'] ) && $ns['content'] ) {
594 $this->globals['wgContentNamespaces'][] = $id;
595 }
596 if ( isset( $ns['defaultcontentmodel'] ) ) {
597 $this->globals['wgNamespaceContentModels'][$id] = $ns['defaultcontentmodel'];
598 }
599 if ( isset( $ns['protection'] ) ) {
600 $this->globals['wgNamespaceProtection'][$id] = $ns['protection'];
601 }
602 if ( isset( $ns['capitallinkoverride'] ) ) {
603 $this->globals['wgCapitalLinkOverrides'][$id] = $ns['capitallinkoverride'];
604 }
605 if ( isset( $ns['includable'] ) && !$ns['includable'] ) {
606 $this->globals['wgNonincludableNamespaces'][] = $id;
607 }
608 }
609 }
610 }
611
612 protected function extractResourceLoaderModules( $dir, array $info ) {
613 $defaultPaths = $info['ResourceFileModulePaths'] ?? false;
614 if ( isset( $defaultPaths['localBasePath'] ) ) {
615 if ( $defaultPaths['localBasePath'] === '' ) {
616 // Avoid double slashes (e.g. /extensions/Example//path)
617 $defaultPaths['localBasePath'] = $dir;
618 } else {
619 $defaultPaths['localBasePath'] = "$dir/{$defaultPaths['localBasePath']}";
620 }
621 }
622
623 foreach ( [ 'ResourceModules', 'ResourceModuleSkinStyles', 'OOUIThemePaths' ] as $setting ) {
624 if ( isset( $info[$setting] ) ) {
625 foreach ( $info[$setting] as $name => $data ) {
626 if ( isset( $data['localBasePath'] ) ) {
627 if ( $data['localBasePath'] === '' ) {
628 // Avoid double slashes (e.g. /extensions/Example//path)
629 $data['localBasePath'] = $dir;
630 } else {
631 $data['localBasePath'] = "$dir/{$data['localBasePath']}";
632 }
633 }
634 if ( $defaultPaths ) {
635 $data += $defaultPaths;
636 }
637 $this->attributes[$setting][$name] = $data;
638 }
639 }
640 }
641
642 if ( isset( $info['QUnitTestModule'] ) ) {
643 $data = $info['QUnitTestModule'];
644 if ( isset( $data['localBasePath'] ) ) {
645 if ( $data['localBasePath'] === '' ) {
646 // Avoid double slashes (e.g. /extensions/Example//path)
647 $data['localBasePath'] = $dir;
648 } else {
649 $data['localBasePath'] = "$dir/{$data['localBasePath']}";
650 }
651 }
652 // Satisfy PHPUnit ResourcesTest::testUnsatisfiableDependencies
653 $data['targets'] = [ 'test' ];
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
703 protected function extractSkins( $dir, array $info ) {
704 if ( isset( $info['ValidSkinNames'] ) ) {
705 foreach ( $info['ValidSkinNames'] as $skinKey => $data ) {
706 if ( isset( $data['args'][0]['templateDirectory'] ) ) {
707 $templateDirectory = $data['args'][0]['templateDirectory'];
708 $correctedPath = $dir . '/' . $templateDirectory;
709 // Historically the template directory was relative to core
710 // but it really should've been relative to the skin directory.
711 // If the path exists relative to the skin directory, assume that
712 // is what was intended. Otherwise fall back on the previous behavior
713 // of having it relative to core.
714 if ( is_dir( $correctedPath ) ) {
715 $data['args'][0]['templateDirectory'] = $correctedPath;
716 } else {
717 $data['args'][0]['templateDirectory'] = $templateDirectory;
719 'Template directory should be relative to skin or omitted for skin ' . $skinKey,
720 '1.37'
721 );
722 }
723 } elseif ( isset( $data['args'][0] ) ) {
724 // If not set, we set a sensible default.
725 $data['args'][0]['templateDirectory'] = $dir . '/templates';
726 }
727 $this->globals['wgValidSkinNames'][$skinKey] = $data;
728 }
729 }
730 }
731
736 protected function extractSkinImportPaths( $dir, array $info ) {
737 if ( isset( $info['SkinLessImportPaths'] ) ) {
738 foreach ( $info['SkinLessImportPaths'] as $skin => $subpath ) {
739 $this->attributes['SkinLessImportPaths'][$skin] = "$dir/$subpath";
740 }
741 }
742 }
743
750 protected function extractCredits( $path, array $info ) {
751 $credits = [
752 'path' => $path,
753 'type' => 'other',
754 ];
755 foreach ( self::CREDIT_ATTRIBS as $attr ) {
756 if ( isset( $info[$attr] ) ) {
757 $credits[$attr] = $info[$attr];
758 }
759 }
760
761 $name = $credits['name'];
762
763 // If someone is loading the same thing twice, throw
764 // a nice error (T121493)
765 if ( isset( $this->credits[$name] ) ) {
766 $firstPath = $this->credits[$name]['path'];
767 $secondPath = $credits['path'];
768 throw new Exception( "It was attempted to load $name twice, from $firstPath and $secondPath." );
769 }
770
771 $this->credits[$name] = $credits;
772
773 return $name;
774 }
775
782 protected function extractConfig1( array $info ) {
783 if ( isset( $info['config'] ) ) {
784 if ( isset( $info['config']['_prefix'] ) ) {
785 $prefix = $info['config']['_prefix'];
786 unset( $info['config']['_prefix'] );
787 } else {
788 $prefix = 'wg';
789 }
790 foreach ( $info['config'] as $key => $val ) {
791 if ( $key[0] !== '@' ) {
792 $this->addConfigGlobal( "$prefix$key", $val, $info['name'] );
793 }
794 }
795 }
796 }
797
806 private function applyPath( array $value, string $dir ): array {
807 $result = [];
808
809 foreach ( $value as $k => $v ) {
810 $result[$k] = $dir . '/' . $v;
811 }
812 return $result;
813 }
814
822 protected function extractConfig2( array $info, $dir ) {
823 $prefix = $info['config_prefix'] ?? 'wg';
824 if ( isset( $info['config'] ) ) {
825 foreach ( $info['config'] as $key => $data ) {
826 if ( !array_key_exists( 'value', $data ) ) {
827 throw new UnexpectedValueException( "Missing value for config $key" );
828 }
829
830 $value = $data['value'];
831 if ( isset( $data['path'] ) && $data['path'] ) {
832 if ( is_array( $value ) ) {
833 $value = $this->applyPath( $value, $dir );
834 } else {
835 $value = "$dir/$value";
836 }
837 }
838 if ( isset( $data['merge_strategy'] ) ) {
839 $value[ExtensionRegistry::MERGE_STRATEGY] = $data['merge_strategy'];
840 }
841 $this->addConfigGlobal( "$prefix$key", $value, $info['name'] );
842 $data['providedby'] = $info['name'];
843 if ( isset( $info['ConfigRegistry'][0] ) ) {
844 $data['configregistry'] = array_keys( $info['ConfigRegistry'] )[0];
845 }
846 }
847 }
848 }
849
857 private function addConfigGlobal( $key, $value, $extName ) {
858 if ( array_key_exists( $key, $this->globals ) ) {
859 throw new RuntimeException(
860 "The configuration setting '$key' was already set by MediaWiki core or"
861 . " another extension, and cannot be set again by $extName." );
862 }
863 $this->globals[$key] = $value;
864 }
865
866 protected function extractPathBasedGlobal( $global, $dir, $paths ) {
867 foreach ( $paths as $path ) {
868 $this->globals[$global][] = "$dir/$path";
869 }
870 }
871
881 protected function storeToArrayRecursive( $path, $name, $value, &$array ) {
882 if ( !is_array( $value ) ) {
883 throw new InvalidArgumentException( "The value for '$name' should be an array (from $path)" );
884 }
885 if ( isset( $array[$name] ) ) {
886 $array[$name] = array_merge_recursive( $array[$name], $value );
887 } else {
888 $array[$name] = $value;
889 }
890 }
891
901 protected function storeToArray( $path, $name, $value, &$array ) {
902 if ( !is_array( $value ) ) {
903 throw new InvalidArgumentException( "The value for '$name' should be an array (from $path)" );
904 }
905 if ( isset( $array[$name] ) ) {
906 $array[$name] = array_merge( $array[$name], $value );
907 } else {
908 $array[$name] = $value;
909 }
910 }
911
920 public function getExtraAutoloaderPaths( $dir, array $info ) {
921 wfDeprecated( __METHOD__, '1.39' );
922 $paths = [];
923 if ( isset( $info['load_composer_autoloader'] ) && $info['load_composer_autoloader'] === true ) {
924 $paths[] = "$dir/vendor/autoload.php";
925 }
926 return $paths;
927 }
928
943 public function getExtractedAutoloadInfo( bool $includeDev = false ): array {
944 $autoload = $this->autoload;
945
946 if ( $includeDev ) {
947 $autoload['classes'] += $this->autoloadDev['classes'];
948 $autoload['namespaces'] += $this->autoloadDev['namespaces'];
949
950 // NOTE: This is here for completeness. Per MW 1.39,
951 // $this->autoloadDev['files'] is always empty.
952 // So avoid the performance hit of array_merge().
953 if ( !empty( $this->autoloadDev['files'] ) ) {
954 // NOTE: Don't use += with numeric keys!
955 // Could use PHPUtils::pushArray.
956 $autoload['files'] = array_merge(
957 $autoload['files'],
958 $this->autoloadDev['files']
959 );
960 }
961 }
962
963 return $autoload;
964 }
965
970 private function extractAutoload( array $info, string $dir ) {
971 if ( isset( $info['load_composer_autoloader'] ) && $info['load_composer_autoloader'] === true ) {
972 $file = "$dir/vendor/autoload.php";
973 if ( file_exists( $file ) ) {
974 $this->autoload['files'][] = $file;
975 }
976 }
977
978 if ( isset( $info['AutoloadClasses'] ) ) {
979 $paths = $this->applyPath( $info['AutoloadClasses'], $dir );
980 $this->autoload['classes'] += $paths;
981 }
982
983 if ( isset( $info['AutoloadNamespaces'] ) ) {
984 $paths = $this->applyPath( $info['AutoloadNamespaces'], $dir );
985 $this->autoload['namespaces'] += $paths;
986 }
987
988 if ( isset( $info['TestAutoloadClasses'] ) ) {
989 $paths = $this->applyPath( $info['TestAutoloadClasses'], $dir );
990 $this->autoloadDev['classes'] += $paths;
991 }
992
993 if ( isset( $info['TestAutoloadNamespaces'] ) ) {
994 $paths = $this->applyPath( $info['TestAutoloadNamespaces'], $dir );
995 $this->autoloadDev['namespaces'] += $paths;
996 }
997 }
998}
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