MediaWiki  master
ExtensionProcessor.php
Go to the documentation of this file.
1 <?php
2 
5 
12 class 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  MainConfigNames::UserRegistrationProviders,
68  ];
69 
75  protected const CORE_ATTRIBS = [
76  'ParsoidModules',
77  'RestRoutes',
78  'SkinOOUIThemes',
79  'SkinCodexThemes',
80  'SearchMappings',
81  'TrackingCategories',
82  'LateJSConfigVarNames',
83  'TempUserSerialProviders',
84  'TempUserSerialMappings',
85  ];
86 
94  protected const MERGE_STRATEGIES = [
95  'wgAuthManagerAutoConfig' => 'array_plus_2d',
96  'wgCapitalLinkOverrides' => 'array_plus',
97  'wgExtraGenderNamespaces' => 'array_plus',
98  'wgGrantPermissions' => 'array_plus_2d',
99  'wgGroupPermissions' => 'array_plus_2d',
100  'wgHooks' => 'array_merge_recursive',
101  'wgNamespaceContentModels' => 'array_plus',
102  'wgNamespaceProtection' => 'array_plus',
103  'wgNamespacesWithSubpages' => 'array_plus',
104  'wgPasswordPolicy' => 'array_merge_recursive',
105  'wgRateLimits' => 'array_plus_2d',
106  'wgRevokePermissions' => 'array_plus_2d',
107  ];
108 
114  protected const CREDIT_ATTRIBS = [
115  'type',
116  'author',
117  'description',
118  'descriptionmsg',
119  'license-name',
120  'name',
121  'namemsg',
122  'url',
123  'version',
124  ];
125 
132  protected const NOT_ATTRIBS = [
133  'callback',
134  'config',
135  'config_prefix',
136  'load_composer_autoloader',
137  'manifest_version',
138  'namespaces',
139  'requires',
140  'AutoloadClasses',
141  'AutoloadNamespaces',
142  'ExtensionMessagesFiles',
143  'ForeignResourcesDir',
144  'Hooks',
145  'MessagePosterModule',
146  'MessagesDirs',
147  'OOUIThemePaths',
148  'QUnitTestModule',
149  'ResourceFileModulePaths',
150  'ResourceModuleSkinStyles',
151  'ResourceModules',
152  'ServiceWiringFiles',
153  ];
154 
162  protected $globals = [
163  'wgExtensionMessagesFiles' => [],
164  'wgMessagesDirs' => [],
165  ];
166 
172  protected $defines = [];
173 
181  protected $callbacks = [];
182 
186  protected $credits = [];
187 
195  protected $autoload = [
196  'files' => [],
197  'classes' => [],
198  'namespaces' => [],
199  ];
200 
207  protected $autoloadDev = [
208  'files' => [],
209  'classes' => [],
210  'namespaces' => [],
211  ];
212 
219  protected $attributes = [];
220 
227  protected $extAttributes = [];
228 
236  public function extractInfoFromFile( string $path ) {
237  $json = file_get_contents( $path );
238  $info = json_decode( $json, true );
239 
240  if ( !$info ) {
241  throw new RuntimeException( "Failed to load JSON data from $path" );
242  }
243 
244  $this->extractInfo( $path, $info, $info['manifest_version'] );
245  }
246 
252  public function extractInfo( $path, array $info, $version ) {
253  $dir = dirname( $path );
254  $this->extractHooks( $info, $path );
255  $this->extractExtensionMessagesFiles( $dir, $info );
256  $this->extractMessagesDirs( $dir, $info );
257  $this->extractSkins( $dir, $info );
258  $this->extractSkinImportPaths( $dir, $info );
259  $this->extractNamespaces( $info );
260  $this->extractImplicitRights( $info );
261  $this->extractResourceLoaderModules( $dir, $info );
262  if ( isset( $info['ServiceWiringFiles'] ) ) {
263  $this->extractPathBasedGlobal(
264  'wgServiceWiringFiles',
265  $dir,
266  $info['ServiceWiringFiles']
267  );
268  }
269  $name = $this->extractCredits( $path, $info );
270  if ( isset( $info['callback'] ) ) {
271  $this->callbacks[$name] = $info['callback'];
272  }
273 
274  $this->extractAutoload( $info, $dir );
275 
276  // config should be after all core globals are extracted,
277  // so duplicate setting detection will work fully
278  if ( $version >= 2 ) {
279  $this->extractConfig2( $info, $dir );
280  } else {
281  // $version === 1
282  $this->extractConfig1( $info );
283  }
284 
285  // Record the extension name in the ParsoidModules property
286  if ( isset( $info['ParsoidModules'] ) ) {
287  foreach ( $info['ParsoidModules'] as &$module ) {
288  if ( is_string( $module ) ) {
289  $className = $module;
290  $module = [
291  'class' => $className,
292  ];
293  }
294  $module['name'] = $name;
295  }
296  }
297 
298  $this->extractForeignResourcesDir( $info, $name, $dir );
299 
300  if ( $version >= 2 ) {
301  $this->extractAttributes( $path, $info );
302  }
303 
304  foreach ( $info as $key => $val ) {
305  // If it's a global setting,
306  if ( in_array( $key, self::$globalSettings ) ) {
307  $this->storeToArrayRecursive( $path, "wg$key", $val, $this->globals );
308  continue;
309  }
310  // Ignore anything that starts with a @
311  if ( $key[0] === '@' ) {
312  continue;
313  }
314 
315  if ( $version >= 2 ) {
316  // Only allowed attributes are set
317  if ( in_array( $key, self::CORE_ATTRIBS ) ) {
318  $this->storeToArray( $path, $key, $val, $this->attributes );
319  }
320  } else {
321  // version === 1
322  if ( !in_array( $key, self::NOT_ATTRIBS )
323  && !in_array( $key, self::CREDIT_ATTRIBS )
324  ) {
325  // If it's not disallowed, it's an attribute
326  $this->storeToArrayRecursive( $path, $key, $val, $this->attributes );
327  }
328  }
329  }
330  }
331 
336  protected function extractAttributes( $path, array $info ) {
337  if ( isset( $info['attributes'] ) ) {
338  foreach ( $info['attributes'] as $extName => $value ) {
339  $this->storeToArrayRecursive( $path, $extName, $value, $this->extAttributes );
340  }
341  }
342  }
343 
344  public function getExtractedInfo( bool $includeDev = false ) {
345  // Make sure the merge strategies are set
346  foreach ( $this->globals as $key => $val ) {
347  if ( isset( self::MERGE_STRATEGIES[$key] ) ) {
348  $this->globals[$key][ExtensionRegistry::MERGE_STRATEGY] = self::MERGE_STRATEGIES[$key];
349  }
350  }
351 
352  // Merge $this->extAttributes into $this->attributes depending on what is loaded
353  foreach ( $this->extAttributes as $extName => $value ) {
354  // Only set the attribute if $extName is loaded (and hence present in credits)
355  if ( isset( $this->credits[$extName] ) ) {
356  foreach ( $value as $attrName => $attrValue ) {
357  $this->storeToArrayRecursive(
358  '', // Don't provide a path since it's impossible to generate an error here
359  $extName . $attrName,
360  $attrValue,
361  $this->attributes
362  );
363  }
364  unset( $this->extAttributes[$extName] );
365  }
366  }
367 
368  $autoload = $this->getExtractedAutoloadInfo( $includeDev );
369  return [
370  'globals' => $this->globals,
371  'defines' => $this->defines,
372  'callbacks' => $this->callbacks,
373  'credits' => $this->credits,
374  'attributes' => $this->attributes,
375  'autoloaderPaths' => $autoload['files'],
376  'autoloaderClasses' => $autoload['classes'],
377  'autoloaderNS' => $autoload['namespaces'],
378  ];
379  }
380 
381  public function getRequirements( array $info, $includeDev ) {
382  // Quick shortcuts
383  if ( !$includeDev || !isset( $info['dev-requires'] ) ) {
384  return $info['requires'] ?? [];
385  }
386 
387  if ( !isset( $info['requires'] ) ) {
388  return $info['dev-requires'] ?? [];
389  }
390 
391  // OK, we actually have to merge everything
392  $merged = [];
393 
394  // Helper that combines version requirements by
395  // picking the non-null if one is, or combines
396  // the two. Note that it is not possible for
397  // both inputs to be null.
398  $pick = static function ( $a, $b ) {
399  if ( $a === null ) {
400  return $b;
401  } elseif ( $b === null ) {
402  return $a;
403  } else {
404  return "$a $b";
405  }
406  };
407 
408  $req = $info['requires'];
409  $dev = $info['dev-requires'];
410  if ( isset( $req['MediaWiki'] ) || isset( $dev['MediaWiki'] ) ) {
411  $merged['MediaWiki'] = $pick(
412  $req['MediaWiki'] ?? null,
413  $dev['MediaWiki'] ?? null
414  );
415  }
416 
417  $platform = array_merge(
418  array_keys( $req['platform'] ?? [] ),
419  array_keys( $dev['platform'] ?? [] )
420  );
421  if ( $platform ) {
422  foreach ( $platform as $pkey ) {
423  if ( $pkey === 'php' ) {
424  $value = $pick(
425  $req['platform']['php'] ?? null,
426  $dev['platform']['php'] ?? null
427  );
428  } else {
429  // Prefer dev value, but these should be constant
430  // anyway (ext-* and ability-*)
431  $value = $dev['platform'][$pkey] ?? $req['platform'][$pkey];
432  }
433  $merged['platform'][$pkey] = $value;
434  }
435  }
436 
437  foreach ( [ 'extensions', 'skins' ] as $thing ) {
438  $things = array_merge(
439  array_keys( $req[$thing] ?? [] ),
440  array_keys( $dev[$thing] ?? [] )
441  );
442  foreach ( $things as $name ) {
443  $merged[$thing][$name] = $pick(
444  $req[$thing][$name] ?? null,
445  $dev[$thing][$name] ?? null
446  );
447  }
448  }
449  return $merged;
450  }
451 
463  private function setArrayHookHandler(
464  array $callback,
465  array $hookHandlersAttr,
466  string $name,
467  string $path
468  ) {
469  if ( isset( $callback['handler'] ) ) {
470  $handlerName = $callback['handler'];
471  $handlerDefinition = $hookHandlersAttr[$handlerName] ?? false;
472  if ( !$handlerDefinition ) {
473  throw new UnexpectedValueException(
474  "Missing handler definition for $name in HookHandlers attribute in $path"
475  );
476  }
477  $callback['handler'] = $handlerDefinition;
478  $callback['extensionPath'] = $path;
479  $this->attributes['Hooks'][$name][] = $callback;
480  } else {
481  foreach ( $callback as $callable ) {
482  if ( is_array( $callable ) ) {
483  if ( isset( $callable['handler'] ) ) { // Non-legacy style handler
484  $this->setArrayHookHandler( $callable, $hookHandlersAttr, $name, $path );
485  } else { // Legacy style handler array
486  $this->globals['wgHooks'][$name][] = $callable;
487  }
488  } elseif ( is_string( $callable ) ) {
489  $this->setStringHookHandler( $callable, $hookHandlersAttr, $name, $path );
490  }
491  }
492  }
493  }
494 
505  private function setStringHookHandler(
506  string $callback,
507  array $hookHandlersAttr,
508  string $name,
509  string $path
510  ) {
511  if ( isset( $hookHandlersAttr[$callback] ) ) {
512  $handler = [
513  'handler' => $hookHandlersAttr[$callback],
514  'extensionPath' => $path
515  ];
516  $this->attributes['Hooks'][$name][] = $handler;
517  } else { // legacy style handler
518  $this->globals['wgHooks'][$name][] = $callback;
519  }
520  }
521 
530  protected function extractHooks( array $info, string $path ) {
531  $extName = $info['name'];
532  if ( isset( $info['Hooks'] ) ) {
533  $hookHandlersAttr = [];
534  foreach ( $info['HookHandlers'] ?? [] as $name => $def ) {
535  $hookHandlersAttr[$name] = [ 'name' => "$extName-$name" ] + $def;
536  }
537  foreach ( $info['Hooks'] as $name => $callback ) {
538  if ( is_string( $callback ) ) {
539  $this->setStringHookHandler( $callback, $hookHandlersAttr, $name, $path );
540  } elseif ( is_array( $callback ) ) {
541  $this->setArrayHookHandler( $callback, $hookHandlersAttr, $name, $path );
542  }
543  }
544  }
545  if ( isset( $info['DeprecatedHooks'] ) ) {
546  $deprecatedHooks = [];
547  foreach ( $info['DeprecatedHooks'] as $name => $deprecatedHookInfo ) {
548  $deprecatedHookInfo += [ 'component' => $extName ];
549  $deprecatedHooks[$name] = $deprecatedHookInfo;
550  }
551  if ( isset( $this->attributes['DeprecatedHooks'] ) ) {
552  $this->attributes['DeprecatedHooks'] += $deprecatedHooks;
553  } else {
554  $this->attributes['DeprecatedHooks'] = $deprecatedHooks;
555  }
556  }
557  }
558 
564  protected function extractNamespaces( array $info ) {
565  if ( isset( $info['namespaces'] ) ) {
566  foreach ( $info['namespaces'] as $ns ) {
567  if ( defined( $ns['constant'] ) ) {
568  // If the namespace constant is already defined, use it.
569  // This allows namespace IDs to be overwritten locally.
570  $id = constant( $ns['constant'] );
571  } else {
572  $id = $ns['id'];
573  }
574  $this->defines[ $ns['constant'] ] = $id;
575 
576  if ( !( isset( $ns['conditional'] ) && $ns['conditional'] ) ) {
577  // If it is not conditional, register it
578  $this->attributes['ExtensionNamespaces'][$id] = $ns['name'];
579  }
580  if ( isset( $ns['movable'] ) && !$ns['movable'] ) {
581  $this->attributes['ImmovableNamespaces'][] = $id;
582  }
583  if ( isset( $ns['gender'] ) ) {
584  $this->globals['wgExtraGenderNamespaces'][$id] = $ns['gender'];
585  }
586  if ( isset( $ns['subpages'] ) && $ns['subpages'] ) {
587  $this->globals['wgNamespacesWithSubpages'][$id] = true;
588  }
589  if ( isset( $ns['content'] ) && $ns['content'] ) {
590  $this->globals['wgContentNamespaces'][] = $id;
591  }
592  if ( isset( $ns['defaultcontentmodel'] ) ) {
593  $this->globals['wgNamespaceContentModels'][$id] = $ns['defaultcontentmodel'];
594  }
595  if ( isset( $ns['protection'] ) ) {
596  $this->globals['wgNamespaceProtection'][$id] = $ns['protection'];
597  }
598  if ( isset( $ns['capitallinkoverride'] ) ) {
599  $this->globals['wgCapitalLinkOverrides'][$id] = $ns['capitallinkoverride'];
600  }
601  if ( isset( $ns['includable'] ) && !$ns['includable'] ) {
602  $this->globals['wgNonincludableNamespaces'][] = $id;
603  }
604  }
605  }
606  }
607 
608  protected function extractResourceLoaderModules( $dir, array $info ) {
609  $defaultPaths = $info['ResourceFileModulePaths'] ?? false;
610  if ( isset( $defaultPaths['localBasePath'] ) ) {
611  if ( $defaultPaths['localBasePath'] === '' ) {
612  // Avoid double slashes (e.g. /extensions/Example//path)
613  $defaultPaths['localBasePath'] = $dir;
614  } else {
615  $defaultPaths['localBasePath'] = "$dir/{$defaultPaths['localBasePath']}";
616  }
617  }
618 
619  foreach ( [ 'ResourceModules', 'ResourceModuleSkinStyles', 'OOUIThemePaths' ] as $setting ) {
620  if ( isset( $info[$setting] ) ) {
621  foreach ( $info[$setting] as $name => $data ) {
622  if ( isset( $data['localBasePath'] ) ) {
623  if ( $data['localBasePath'] === '' ) {
624  // Avoid double slashes (e.g. /extensions/Example//path)
625  $data['localBasePath'] = $dir;
626  } else {
627  $data['localBasePath'] = "$dir/{$data['localBasePath']}";
628  }
629  }
630  if ( $defaultPaths ) {
631  $data += $defaultPaths;
632  }
633  $this->attributes[$setting][$name] = $data;
634  }
635  }
636  }
637 
638  if ( isset( $info['QUnitTestModule'] ) ) {
639  $data = $info['QUnitTestModule'];
640  if ( isset( $data['localBasePath'] ) ) {
641  if ( $data['localBasePath'] === '' ) {
642  // Avoid double slashes (e.g. /extensions/Example//path)
643  $data['localBasePath'] = $dir;
644  } else {
645  $data['localBasePath'] = "$dir/{$data['localBasePath']}";
646  }
647  }
648  // Satisfy PHPUnit ResourcesTest::testUnsatisfiableDependencies
649  $data['targets'] = [ 'test' ];
650  $this->attributes['QUnitTestModules']["test.{$info['name']}"] = $data;
651  }
652 
653  if ( isset( $info['MessagePosterModule'] ) ) {
654  $data = $info['MessagePosterModule'];
655  $basePath = $data['localBasePath'] ?? '';
656  $baseDir = $basePath === '' ? $dir : "$dir/$basePath";
657  foreach ( $data['scripts'] ?? [] as $scripts ) {
658  $this->attributes['MessagePosterModule']['scripts'][] =
659  new FilePath( $scripts, $baseDir );
660  }
661  foreach ( $data['dependencies'] ?? [] as $dependency ) {
662  $this->attributes['MessagePosterModule']['dependencies'][] = $dependency;
663  }
664  }
665  }
666 
667  protected function extractExtensionMessagesFiles( $dir, array $info ) {
668  if ( isset( $info['ExtensionMessagesFiles'] ) ) {
669  foreach ( $info['ExtensionMessagesFiles'] as &$file ) {
670  $file = "$dir/$file";
671  }
672  $this->globals["wgExtensionMessagesFiles"] += $info['ExtensionMessagesFiles'];
673  }
674  }
675 
683  protected function extractMessagesDirs( $dir, array $info ) {
684  if ( isset( $info['MessagesDirs'] ) ) {
685  foreach ( $info['MessagesDirs'] as $name => $files ) {
686  foreach ( (array)$files as $file ) {
687  $this->globals["wgMessagesDirs"][$name][] = "$dir/$file";
688  }
689  }
690  }
691  }
692 
699  protected function extractSkins( $dir, array $info ) {
700  if ( isset( $info['ValidSkinNames'] ) ) {
701  foreach ( $info['ValidSkinNames'] as $skinKey => $data ) {
702  if ( isset( $data['args'][0] ) ) {
703  $templateDirectory = $data['args'][0]['templateDirectory'] ?? 'templates';
704  $data['args'][0]['templateDirectory'] = $dir . '/' . $templateDirectory;
705  }
706  $this->globals['wgValidSkinNames'][$skinKey] = $data;
707  }
708  }
709  }
710 
716  protected function extractImplicitRights( array $info ) {
717  // Rate limits are only configurable for rights that are either in wgImplicitRights
718  // or in wgAvailableRights. Extensions that define rate limits should not have to
719  // explicitly add them to wgImplicitRights as well, we can do that automatically.
720 
721  if ( isset( $info['RateLimits'] ) ) {
722  $rights = array_keys( $info['RateLimits'] );
723 
724  if ( isset( $info['AvailableRights'] ) ) {
725  $rights = array_diff( $rights, $info['AvailableRights'] );
726  }
727 
728  $this->globals['wgImplicitRights'] = array_merge(
729  $this->globals['wgImplicitRights'] ?? [],
730  $rights
731  );
732  }
733  }
734 
739  protected function extractSkinImportPaths( $dir, array $info ) {
740  if ( isset( $info['SkinLessImportPaths'] ) ) {
741  foreach ( $info['SkinLessImportPaths'] as $skin => $subpath ) {
742  $this->attributes['SkinLessImportPaths'][$skin] = "$dir/$subpath";
743  }
744  }
745  }
746 
753  protected function extractCredits( $path, array $info ) {
754  $credits = [
755  'path' => $path,
756  'type' => 'other',
757  ];
758  foreach ( self::CREDIT_ATTRIBS as $attr ) {
759  if ( isset( $info[$attr] ) ) {
760  $credits[$attr] = $info[$attr];
761  }
762  }
763 
764  $name = $credits['name'];
765 
766  // If someone is loading the same thing twice, throw
767  // a nice error (T121493)
768  if ( isset( $this->credits[$name] ) ) {
769  $firstPath = $this->credits[$name]['path'];
770  $secondPath = $credits['path'];
771  throw new Exception( "It was attempted to load $name twice, from $firstPath and $secondPath." );
772  }
773 
774  $this->credits[$name] = $credits;
775 
776  return $name;
777  }
778 
779  protected function extractForeignResourcesDir( array $info, string $name, string $dir ): void {
780  if ( array_key_exists( 'ForeignResourcesDir', $info ) ) {
781  if ( !is_string( $info['ForeignResourcesDir'] ) ) {
782  throw new Exception( "Incorrect ForeignResourcesDir type, must be a string (in $name)" );
783  }
784  $this->attributes['ForeignResourcesDir'][$name] = "{$dir}/{$info['ForeignResourcesDir']}";
785  }
786  }
787 
794  protected function extractConfig1( array $info ) {
795  if ( isset( $info['config'] ) ) {
796  if ( isset( $info['config']['_prefix'] ) ) {
797  $prefix = $info['config']['_prefix'];
798  unset( $info['config']['_prefix'] );
799  } else {
800  $prefix = 'wg';
801  }
802  foreach ( $info['config'] as $key => $val ) {
803  if ( $key[0] !== '@' ) {
804  $this->addConfigGlobal( "$prefix$key", $val, $info['name'] );
805  }
806  }
807  }
808  }
809 
818  private function applyPath( array $value, string $dir ): array {
819  $result = [];
820 
821  foreach ( $value as $k => $v ) {
822  $result[$k] = $dir . '/' . $v;
823  }
824  return $result;
825  }
826 
834  protected function extractConfig2( array $info, $dir ) {
835  $prefix = $info['config_prefix'] ?? 'wg';
836  if ( isset( $info['config'] ) ) {
837  foreach ( $info['config'] as $key => $data ) {
838  if ( !array_key_exists( 'value', $data ) ) {
839  throw new UnexpectedValueException( "Missing value for config $key" );
840  }
841 
842  $value = $data['value'];
843  if ( isset( $data['path'] ) && $data['path'] ) {
844  if ( is_array( $value ) ) {
845  $value = $this->applyPath( $value, $dir );
846  } else {
847  $value = "$dir/$value";
848  }
849  }
850  if ( isset( $data['merge_strategy'] ) ) {
851  $value[ExtensionRegistry::MERGE_STRATEGY] = $data['merge_strategy'];
852  }
853  $this->addConfigGlobal( "$prefix$key", $value, $info['name'] );
854  $data['providedby'] = $info['name'];
855  if ( isset( $info['ConfigRegistry'][0] ) ) {
856  $data['configregistry'] = array_keys( $info['ConfigRegistry'] )[0];
857  }
858  }
859  }
860  }
861 
869  private function addConfigGlobal( $key, $value, $extName ) {
870  if ( array_key_exists( $key, $this->globals ) ) {
871  throw new RuntimeException(
872  "The configuration setting '$key' was already set by MediaWiki core or"
873  . " another extension, and cannot be set again by $extName." );
874  }
875  $this->globals[$key] = $value;
876  }
877 
878  protected function extractPathBasedGlobal( $global, $dir, $paths ) {
879  foreach ( $paths as $path ) {
880  $this->globals[$global][] = "$dir/$path";
881  }
882  }
883 
893  protected function storeToArrayRecursive( $path, $name, $value, &$array ) {
894  if ( !is_array( $value ) ) {
895  throw new InvalidArgumentException( "The value for '$name' should be an array (from $path)" );
896  }
897  if ( isset( $array[$name] ) ) {
898  $array[$name] = array_merge_recursive( $array[$name], $value );
899  } else {
900  $array[$name] = $value;
901  }
902  }
903 
913  protected function storeToArray( $path, $name, $value, &$array ) {
914  if ( !is_array( $value ) ) {
915  throw new InvalidArgumentException( "The value for '$name' should be an array (from $path)" );
916  }
917  if ( isset( $array[$name] ) ) {
918  $array[$name] = array_merge( $array[$name], $value );
919  } else {
920  $array[$name] = $value;
921  }
922  }
923 
932  public function getExtraAutoloaderPaths( $dir, array $info ) {
933  wfDeprecated( __METHOD__, '1.39' );
934  $paths = [];
935  if ( isset( $info['load_composer_autoloader'] ) && $info['load_composer_autoloader'] === true ) {
936  $paths[] = "$dir/vendor/autoload.php";
937  }
938  return $paths;
939  }
940 
955  public function getExtractedAutoloadInfo( bool $includeDev = false ): array {
956  $autoload = $this->autoload;
957 
958  if ( $includeDev ) {
959  $autoload['classes'] += $this->autoloadDev['classes'];
960  $autoload['namespaces'] += $this->autoloadDev['namespaces'];
961 
962  // NOTE: This is here for completeness. Per MW 1.39,
963  // $this->autoloadDev['files'] is always empty.
964  // So avoid the performance hit of array_merge().
965  if ( !empty( $this->autoloadDev['files'] ) ) {
966  // NOTE: Don't use += with numeric keys!
967  // Could use PHPUtils::pushArray.
968  $autoload['files'] = array_merge(
969  $autoload['files'],
970  $this->autoloadDev['files']
971  );
972  }
973  }
974 
975  return $autoload;
976  }
977 
982  private function extractAutoload( array $info, string $dir ) {
983  if ( isset( $info['load_composer_autoloader'] ) && $info['load_composer_autoloader'] === true ) {
984  $file = "$dir/vendor/autoload.php";
985  if ( file_exists( $file ) ) {
986  $this->autoload['files'][] = $file;
987  }
988  }
989 
990  if ( isset( $info['AutoloadClasses'] ) ) {
991  $paths = $this->applyPath( $info['AutoloadClasses'], $dir );
992  $this->autoload['classes'] += $paths;
993  }
994 
995  if ( isset( $info['AutoloadNamespaces'] ) ) {
996  $paths = $this->applyPath( $info['AutoloadNamespaces'], $dir );
997  $this->autoload['namespaces'] += $paths;
998  }
999 
1000  if ( isset( $info['TestAutoloadClasses'] ) ) {
1001  $paths = $this->applyPath( $info['TestAutoloadClasses'], $dir );
1002  $this->autoloadDev['classes'] += $paths;
1003  }
1004 
1005  if ( isset( $info['TestAutoloadNamespaces'] ) ) {
1006  $paths = $this->applyPath( $info['TestAutoloadNamespaces'], $dir );
1007  $this->autoloadDev['namespaces'] += $paths;
1008  }
1009  }
1010 }
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
if(!defined('MW_SETUP_CALLBACK'))
Definition: WebStart.php:88
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.
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.
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.
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.
const MERGE_STRATEGY
Special key that defines the merge strategy.
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
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42