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::RateLimits,
57  MainConfigNames::RawHtmlMessages,
58  MainConfigNames::ReauthenticateTime,
59  MainConfigNames::RecentChangesFlags,
60  MainConfigNames::RemoveCredentialsBlacklist,
61  MainConfigNames::RemoveGroups,
62  MainConfigNames::ResourceLoaderSources,
63  MainConfigNames::RevokePermissions,
64  MainConfigNames::SessionProviders,
65  MainConfigNames::SpecialPages
66  ];
67 
73  protected const CORE_ATTRIBS = [
74  'ParsoidModules',
75  'RestRoutes',
76  'SkinOOUIThemes',
77  'SearchMappings',
78  'TrackingCategories',
79  'LateJSConfigVarNames',
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  'QUnitTestModule',
145  'ResourceFileModulePaths',
146  'ResourceModuleSkinStyles',
147  'ResourceModules',
148  'ServiceWiringFiles',
149  ];
150 
158  protected $globals = [
159  'wgExtensionMessagesFiles' => [],
160  'wgMessagesDirs' => [],
161  ];
162 
168  protected $defines = [];
169 
176  protected $callbacks = [];
177 
181  protected $credits = [];
182 
190  protected $autoload = [
191  'files' => [],
192  'classes' => [],
193  'namespaces' => [],
194  ];
195 
202  protected $autoloadDev = [
203  'files' => [],
204  'classes' => [],
205  'namespaces' => [],
206  ];
207 
214  protected $attributes = [];
215 
222  protected $extAttributes = [];
223 
231  public function extractInfoFromFile( string $path ) {
232  $json = file_get_contents( $path );
233  $info = json_decode( $json, true );
234 
235  if ( !$info ) {
236  throw new RuntimeException( "Failed to load JSON data from $path" );
237  }
238 
239  if ( !isset( $info['manifest_version'] ) ) {
241  "{$info['name']}'s extension.json or skin.json does not have manifest_version, " .
242  'this is deprecated since MediaWiki 1.29',
243  '1.29', false, false
244  );
245  $info['manifest_version'] = 1;
246  }
247 
248  $this->extractInfo( $path, $info, $info['manifest_version'] );
249  }
250 
256  public function extractInfo( $path, array $info, $version ) {
257  $dir = dirname( $path );
258  $this->extractHooks( $info, $path );
259  $this->extractExtensionMessagesFiles( $dir, $info );
260  $this->extractMessagesDirs( $dir, $info );
261  $this->extractSkins( $dir, $info );
262  $this->extractSkinImportPaths( $dir, $info );
263  $this->extractNamespaces( $info );
264  $this->extractResourceLoaderModules( $dir, $info );
265  if ( isset( $info['ServiceWiringFiles'] ) ) {
266  $this->extractPathBasedGlobal(
267  'wgServiceWiringFiles',
268  $dir,
269  $info['ServiceWiringFiles']
270  );
271  }
272  $name = $this->extractCredits( $path, $info );
273  if ( isset( $info['callback'] ) ) {
274  $this->callbacks[$name] = $info['callback'];
275  }
276 
277  $this->extractAutoload( $info, $dir );
278 
279  // config should be after all core globals are extracted,
280  // so duplicate setting detection will work fully
281  if ( $version >= 2 ) {
282  $this->extractConfig2( $info, $dir );
283  } else {
284  // $version === 1
285  $this->extractConfig1( $info );
286  }
287 
288  // Record the extension name in the ParsoidModules property
289  if ( isset( $info['ParsoidModules'] ) ) {
290  foreach ( $info['ParsoidModules'] as &$module ) {
291  if ( is_string( $module ) ) {
292  $className = $module;
293  $module = [
294  'class' => $className,
295  ];
296  }
297  $module['name'] = $name;
298  }
299  }
300 
301  if ( $version >= 2 ) {
302  $this->extractAttributes( $path, $info );
303  }
304 
305  foreach ( $info as $key => $val ) {
306  // If it's a global setting,
307  if ( in_array( $key, self::$globalSettings ) ) {
308  $this->storeToArrayRecursive( $path, "wg$key", $val, $this->globals );
309  continue;
310  }
311  // Ignore anything that starts with a @
312  if ( $key[0] === '@' ) {
313  continue;
314  }
315 
316  if ( $version >= 2 ) {
317  // Only allowed attributes are set
318  if ( in_array( $key, self::CORE_ATTRIBS ) ) {
319  $this->storeToArray( $path, $key, $val, $this->attributes );
320  }
321  } else {
322  // version === 1
323  if ( !in_array( $key, self::NOT_ATTRIBS )
324  && !in_array( $key, self::CREDIT_ATTRIBS )
325  ) {
326  // If it's not disallowed, it's an attribute
327  $this->storeToArrayRecursive( $path, $key, $val, $this->attributes );
328  }
329  }
330  }
331  }
332 
337  protected function extractAttributes( $path, array $info ) {
338  if ( isset( $info['attributes'] ) ) {
339  foreach ( $info['attributes'] as $extName => $value ) {
340  $this->storeToArrayRecursive( $path, $extName, $value, $this->extAttributes );
341  }
342  }
343  }
344 
345  public function getExtractedInfo( bool $includeDev = false ) {
346  // Make sure the merge strategies are set
347  foreach ( $this->globals as $key => $val ) {
348  if ( isset( self::MERGE_STRATEGIES[$key] ) ) {
349  $this->globals[$key][ExtensionRegistry::MERGE_STRATEGY] = self::MERGE_STRATEGIES[$key];
350  }
351  }
352 
353  // Merge $this->extAttributes into $this->attributes depending on what is loaded
354  foreach ( $this->extAttributes as $extName => $value ) {
355  // Only set the attribute if $extName is loaded (and hence present in credits)
356  if ( isset( $this->credits[$extName] ) ) {
357  foreach ( $value as $attrName => $attrValue ) {
358  $this->storeToArrayRecursive(
359  '', // Don't provide a path since it's impossible to generate an error here
360  $extName . $attrName,
361  $attrValue,
362  $this->attributes
363  );
364  }
365  unset( $this->extAttributes[$extName] );
366  }
367  }
368 
369  $autoload = $this->getExtractedAutoloadInfo( $includeDev );
370  $info = [
371  'globals' => $this->globals,
372  'defines' => $this->defines,
373  'callbacks' => $this->callbacks,
374  'credits' => $this->credits,
375  'attributes' => $this->attributes,
376  'autoloaderPaths' => $autoload['files'],
377  'autoloaderClasses' => $autoload['classes'],
378  'autoloaderNS' => $autoload['namespaces'],
379  ];
380  return $info;
381  }
382 
383  public function getRequirements( array $info, $includeDev ) {
384  // Quick shortcuts
385  if ( !$includeDev || !isset( $info['dev-requires'] ) ) {
386  return $info['requires'] ?? [];
387  }
388 
389  if ( !isset( $info['requires'] ) ) {
390  return $info['dev-requires'] ?? [];
391  }
392 
393  // OK, we actually have to merge everything
394  $merged = [];
395 
396  // Helper that combines version requirements by
397  // picking the non-null if one is, or combines
398  // the two. Note that it is not possible for
399  // both inputs to be null.
400  $pick = static function ( $a, $b ) {
401  if ( $a === null ) {
402  return $b;
403  } elseif ( $b === null ) {
404  return $a;
405  } else {
406  return "$a $b";
407  }
408  };
409 
410  $req = $info['requires'];
411  $dev = $info['dev-requires'];
412  if ( isset( $req['MediaWiki'] ) || isset( $dev['MediaWiki'] ) ) {
413  $merged['MediaWiki'] = $pick(
414  $req['MediaWiki'] ?? null,
415  $dev['MediaWiki'] ?? null
416  );
417  }
418 
419  $platform = array_merge(
420  array_keys( $req['platform'] ?? [] ),
421  array_keys( $dev['platform'] ?? [] )
422  );
423  if ( $platform ) {
424  foreach ( $platform as $pkey ) {
425  if ( $pkey === 'php' ) {
426  $value = $pick(
427  $req['platform']['php'] ?? null,
428  $dev['platform']['php'] ?? null
429  );
430  } else {
431  // Prefer dev value, but these should be constant
432  // anyways (ext-* and ability-*)
433  $value = $dev['platform'][$pkey] ?? $req['platform'][$pkey];
434  }
435  $merged['platform'][$pkey] = $value;
436  }
437  }
438 
439  foreach ( [ 'extensions', 'skins' ] as $thing ) {
440  $things = array_merge(
441  array_keys( $req[$thing] ?? [] ),
442  array_keys( $dev[$thing] ?? [] )
443  );
444  foreach ( $things as $name ) {
445  $merged[$thing][$name] = $pick(
446  $req[$thing][$name] ?? null,
447  $dev[$thing][$name] ?? null
448  );
449  }
450  }
451  return $merged;
452  }
453 
465  private function setArrayHookHandler(
466  array $callback,
467  array $hookHandlersAttr,
468  string $name,
469  string $path
470  ) {
471  if ( isset( $callback['handler'] ) ) {
472  $handlerName = $callback['handler'];
473  $handlerDefinition = $hookHandlersAttr[$handlerName] ?? false;
474  if ( !$handlerDefinition ) {
475  throw new UnexpectedValueException(
476  "Missing handler definition for $name in HookHandlers attribute in $path"
477  );
478  }
479  $callback['handler'] = $handlerDefinition;
480  $callback['extensionPath'] = $path;
481  $this->attributes['Hooks'][$name][] = $callback;
482  } else {
483  foreach ( $callback as $callable ) {
484  if ( is_array( $callable ) ) {
485  if ( isset( $callable['handler'] ) ) { // Non-legacy style handler
486  $this->setArrayHookHandler( $callable, $hookHandlersAttr, $name, $path );
487  } else { // Legacy style handler array
488  $this->globals['wgHooks'][$name][] = $callable;
489  }
490  } elseif ( is_string( $callable ) ) {
491  $this->setStringHookHandler( $callable, $hookHandlersAttr, $name, $path );
492  }
493  }
494  }
495  }
496 
507  private function setStringHookHandler(
508  string $callback,
509  array $hookHandlersAttr,
510  string $name,
511  string $path
512  ) {
513  if ( isset( $hookHandlersAttr[$callback] ) ) {
514  $handler = [
515  'handler' => $hookHandlersAttr[$callback],
516  'extensionPath' => $path
517  ];
518  $this->attributes['Hooks'][$name][] = $handler;
519  } else { // legacy style handler
520  $this->globals['wgHooks'][$name][] = $callback;
521  }
522  }
523 
532  protected function extractHooks( array $info, string $path ) {
533  $extName = $info['name'];
534  if ( isset( $info['Hooks'] ) ) {
535  $hookHandlersAttr = [];
536  foreach ( $info['HookHandlers'] ?? [] as $name => $def ) {
537  $hookHandlersAttr[$name] = [ 'name' => "$extName-$name" ] + $def;
538  }
539  foreach ( $info['Hooks'] as $name => $callback ) {
540  if ( is_string( $callback ) ) {
541  $this->setStringHookHandler( $callback, $hookHandlersAttr, $name, $path );
542  } elseif ( is_array( $callback ) ) {
543  $this->setArrayHookHandler( $callback, $hookHandlersAttr, $name, $path );
544  }
545  }
546  }
547  if ( isset( $info['DeprecatedHooks'] ) ) {
548  $deprecatedHooks = [];
549  foreach ( $info['DeprecatedHooks'] as $name => $deprecatedHookInfo ) {
550  $deprecatedHookInfo += [ 'component' => $extName ];
551  $deprecatedHooks[$name] = $deprecatedHookInfo;
552  }
553  if ( isset( $this->attributes['DeprecatedHooks'] ) ) {
554  $this->attributes['DeprecatedHooks'] += $deprecatedHooks;
555  } else {
556  $this->attributes['DeprecatedHooks'] = $deprecatedHooks;
557  }
558  }
559  }
560 
566  protected function extractNamespaces( array $info ) {
567  if ( isset( $info['namespaces'] ) ) {
568  foreach ( $info['namespaces'] as $ns ) {
569  if ( defined( $ns['constant'] ) ) {
570  // If the namespace constant is already defined, use it.
571  // This allows namespace IDs to be overwritten locally.
572  $id = constant( $ns['constant'] );
573  } else {
574  $id = $ns['id'];
575  }
576  $this->defines[ $ns['constant'] ] = $id;
577 
578  if ( !( isset( $ns['conditional'] ) && $ns['conditional'] ) ) {
579  // If it is not conditional, register it
580  $this->attributes['ExtensionNamespaces'][$id] = $ns['name'];
581  }
582  if ( isset( $ns['movable'] ) && !$ns['movable'] ) {
583  $this->attributes['ImmovableNamespaces'][] = $id;
584  }
585  if ( isset( $ns['gender'] ) ) {
586  $this->globals['wgExtraGenderNamespaces'][$id] = $ns['gender'];
587  }
588  if ( isset( $ns['subpages'] ) && $ns['subpages'] ) {
589  $this->globals['wgNamespacesWithSubpages'][$id] = true;
590  }
591  if ( isset( $ns['content'] ) && $ns['content'] ) {
592  $this->globals['wgContentNamespaces'][] = $id;
593  }
594  if ( isset( $ns['defaultcontentmodel'] ) ) {
595  $this->globals['wgNamespaceContentModels'][$id] = $ns['defaultcontentmodel'];
596  }
597  if ( isset( $ns['protection'] ) ) {
598  $this->globals['wgNamespaceProtection'][$id] = $ns['protection'];
599  }
600  if ( isset( $ns['capitallinkoverride'] ) ) {
601  $this->globals['wgCapitalLinkOverrides'][$id] = $ns['capitallinkoverride'];
602  }
603  if ( isset( $ns['includable'] ) && !$ns['includable'] ) {
604  $this->globals['wgNonincludableNamespaces'][] = $id;
605  }
606  }
607  }
608  }
609 
610  protected function extractResourceLoaderModules( $dir, array $info ) {
611  $defaultPaths = $info['ResourceFileModulePaths'] ?? false;
612  if ( isset( $defaultPaths['localBasePath'] ) ) {
613  if ( $defaultPaths['localBasePath'] === '' ) {
614  // Avoid double slashes (e.g. /extensions/Example//path)
615  $defaultPaths['localBasePath'] = $dir;
616  } else {
617  $defaultPaths['localBasePath'] = "$dir/{$defaultPaths['localBasePath']}";
618  }
619  }
620 
621  foreach ( [ 'ResourceModules', 'ResourceModuleSkinStyles', 'OOUIThemePaths' ] as $setting ) {
622  if ( isset( $info[$setting] ) ) {
623  foreach ( $info[$setting] as $name => $data ) {
624  if ( isset( $data['localBasePath'] ) ) {
625  if ( $data['localBasePath'] === '' ) {
626  // Avoid double slashes (e.g. /extensions/Example//path)
627  $data['localBasePath'] = $dir;
628  } else {
629  $data['localBasePath'] = "$dir/{$data['localBasePath']}";
630  }
631  }
632  if ( $defaultPaths ) {
633  $data += $defaultPaths;
634  }
635  $this->attributes[$setting][$name] = $data;
636  }
637  }
638  }
639 
640  if ( isset( $info['QUnitTestModule'] ) ) {
641  $data = $info['QUnitTestModule'];
642  if ( isset( $data['localBasePath'] ) ) {
643  if ( $data['localBasePath'] === '' ) {
644  // Avoid double slashes (e.g. /extensions/Example//path)
645  $data['localBasePath'] = $dir;
646  } else {
647  $data['localBasePath'] = "$dir/{$data['localBasePath']}";
648  }
649  }
650  // Satisfy PHPUnit ResourcesTest::testUnsatisfiableDependencies
651  $data['targets'] = [ 'test' ];
652  $this->attributes['QUnitTestModules']["test.{$info['name']}"] = $data;
653  }
654 
655  if ( isset( $info['MessagePosterModule'] ) ) {
656  $data = $info['MessagePosterModule'];
657  $basePath = $data['localBasePath'] ?? '';
658  $baseDir = $basePath === '' ? $dir : "$dir/$basePath";
659  foreach ( $data['scripts'] ?? [] as $scripts ) {
660  $this->attributes['MessagePosterModule']['scripts'][] =
661  new FilePath( $scripts, $baseDir );
662  }
663  foreach ( $data['dependencies'] ?? [] as $dependency ) {
664  $this->attributes['MessagePosterModule']['dependencies'][] = $dependency;
665  }
666  }
667  }
668 
669  protected function extractExtensionMessagesFiles( $dir, array $info ) {
670  if ( isset( $info['ExtensionMessagesFiles'] ) ) {
671  foreach ( $info['ExtensionMessagesFiles'] as &$file ) {
672  $file = "$dir/$file";
673  }
674  $this->globals["wgExtensionMessagesFiles"] += $info['ExtensionMessagesFiles'];
675  }
676  }
677 
685  protected function extractMessagesDirs( $dir, array $info ) {
686  if ( isset( $info['MessagesDirs'] ) ) {
687  foreach ( $info['MessagesDirs'] as $name => $files ) {
688  foreach ( (array)$files as $file ) {
689  $this->globals["wgMessagesDirs"][$name][] = "$dir/$file";
690  }
691  }
692  }
693  }
694 
701  protected function extractSkins( $dir, array $info ) {
702  if ( isset( $info['ValidSkinNames'] ) ) {
703  foreach ( $info['ValidSkinNames'] as $skinKey => $data ) {
704  if ( isset( $data['args'][0]['templateDirectory'] ) ) {
705  $templateDirectory = $data['args'][0]['templateDirectory'];
706  $correctedPath = $dir . '/' . $templateDirectory;
707  // Historically the template directory was relative to core
708  // but it really should've been relative to the skin directory.
709  // If the path exists relative to the skin directory, assume that
710  // is what was intended. Otherwise fall back on the previous behavior
711  // of having it relative to core.
712  if ( is_dir( $correctedPath ) ) {
713  $data['args'][0]['templateDirectory'] = $correctedPath;
714  } else {
715  $data['args'][0]['templateDirectory'] = $templateDirectory;
717  'Template directory should be relative to skin or omitted for skin ' . $skinKey,
718  '1.37'
719  );
720  }
721  } elseif ( isset( $data['args'][0] ) ) {
722  // If not set, we set a sensible default.
723  $data['args'][0]['templateDirectory'] = $dir . '/templates';
724  }
725  $this->globals['wgValidSkinNames'][$skinKey] = $data;
726  }
727  }
728  }
729 
734  protected function extractSkinImportPaths( $dir, array $info ) {
735  if ( isset( $info['SkinLessImportPaths'] ) ) {
736  foreach ( $info['SkinLessImportPaths'] as $skin => $subpath ) {
737  $this->attributes['SkinLessImportPaths'][$skin] = "$dir/$subpath";
738  }
739  }
740  }
741 
748  protected function extractCredits( $path, array $info ) {
749  $credits = [
750  'path' => $path,
751  'type' => 'other',
752  ];
753  foreach ( self::CREDIT_ATTRIBS as $attr ) {
754  if ( isset( $info[$attr] ) ) {
755  $credits[$attr] = $info[$attr];
756  }
757  }
758 
759  $name = $credits['name'];
760 
761  // If someone is loading the same thing twice, throw
762  // a nice error (T121493)
763  if ( isset( $this->credits[$name] ) ) {
764  $firstPath = $this->credits[$name]['path'];
765  $secondPath = $credits['path'];
766  throw new Exception( "It was attempted to load $name twice, from $firstPath and $secondPath." );
767  }
768 
769  $this->credits[$name] = $credits;
770 
771  return $name;
772  }
773 
780  protected function extractConfig1( array $info ) {
781  if ( isset( $info['config'] ) ) {
782  if ( isset( $info['config']['_prefix'] ) ) {
783  $prefix = $info['config']['_prefix'];
784  unset( $info['config']['_prefix'] );
785  } else {
786  $prefix = 'wg';
787  }
788  foreach ( $info['config'] as $key => $val ) {
789  if ( $key[0] !== '@' ) {
790  $this->addConfigGlobal( "$prefix$key", $val, $info['name'] );
791  }
792  }
793  }
794  }
795 
804  private function applyPath( array $value, string $dir ): array {
805  $result = [];
806 
807  foreach ( $value as $k => $v ) {
808  $result[$k] = $dir . '/' . $v;
809  }
810  return $result;
811  }
812 
820  protected function extractConfig2( array $info, $dir ) {
821  $prefix = $info['config_prefix'] ?? 'wg';
822  if ( isset( $info['config'] ) ) {
823  foreach ( $info['config'] as $key => $data ) {
824  if ( !array_key_exists( 'value', $data ) ) {
825  throw new UnexpectedValueException( "Missing value for config $key" );
826  }
827 
828  $value = $data['value'];
829  if ( isset( $data['path'] ) && $data['path'] ) {
830  if ( is_array( $value ) ) {
831  $value = $this->applyPath( $value, $dir );
832  } else {
833  $value = "$dir/$value";
834  }
835  }
836  if ( isset( $data['merge_strategy'] ) ) {
837  $value[ExtensionRegistry::MERGE_STRATEGY] = $data['merge_strategy'];
838  }
839  $this->addConfigGlobal( "$prefix$key", $value, $info['name'] );
840  $data['providedby'] = $info['name'];
841  if ( isset( $info['ConfigRegistry'][0] ) ) {
842  $data['configregistry'] = array_keys( $info['ConfigRegistry'] )[0];
843  }
844  }
845  }
846  }
847 
855  private function addConfigGlobal( $key, $value, $extName ) {
856  if ( array_key_exists( $key, $this->globals ) ) {
857  throw new RuntimeException(
858  "The configuration setting '$key' was already set by MediaWiki core or"
859  . " another extension, and cannot be set again by $extName." );
860  }
861  $this->globals[$key] = $value;
862  }
863 
864  protected function extractPathBasedGlobal( $global, $dir, $paths ) {
865  foreach ( $paths as $path ) {
866  $this->globals[$global][] = "$dir/$path";
867  }
868  }
869 
879  protected function storeToArrayRecursive( $path, $name, $value, &$array ) {
880  if ( !is_array( $value ) ) {
881  throw new InvalidArgumentException( "The value for '$name' should be an array (from $path)" );
882  }
883  if ( isset( $array[$name] ) ) {
884  $array[$name] = array_merge_recursive( $array[$name], $value );
885  } else {
886  $array[$name] = $value;
887  }
888  }
889 
899  protected function storeToArray( $path, $name, $value, &$array ) {
900  if ( !is_array( $value ) ) {
901  throw new InvalidArgumentException( "The value for '$name' should be an array (from $path)" );
902  }
903  if ( isset( $array[$name] ) ) {
904  $array[$name] = array_merge( $array[$name], $value );
905  } else {
906  $array[$name] = $value;
907  }
908  }
909 
918  public function getExtraAutoloaderPaths( $dir, array $info ) {
919  wfDeprecated( __METHOD__, '1.39' );
920  $paths = [];
921  if ( isset( $info['load_composer_autoloader'] ) && $info['load_composer_autoloader'] === true ) {
922  $paths[] = "$dir/vendor/autoload.php";
923  }
924  return $paths;
925  }
926 
941  public function getExtractedAutoloadInfo( bool $includeDev = false ): array {
942  $autoload = $this->autoload;
943 
944  if ( $includeDev ) {
945  $autoload['classes'] += $this->autoloadDev['classes'];
946  $autoload['namespaces'] += $this->autoloadDev['namespaces'];
947 
948  // NOTE: This is here for completeness. Per MW 1.39,
949  // $this->autoloadDev['files'] is always empty.
950  // So avoid the performance hit of array_merge().
951  if ( !empty( $this->autoloadDev['files'] ) ) {
952  // NOTE: Don't use += with numeric keys!
953  // Could use PHPUtils::pushArray.
954  $autoload['files'] = array_merge(
955  $autoload['files'],
956  $this->autoloadDev['files']
957  );
958  }
959  }
960 
961  return $autoload;
962  }
963 
968  private function extractAutoload( array $info, string $dir ) {
969  if ( isset( $info['load_composer_autoloader'] ) && $info['load_composer_autoloader'] === true ) {
970  $file = "$dir/vendor/autoload.php";
971  if ( file_exists( $file ) ) {
972  $this->autoload['files'][] = $file;
973  }
974  }
975 
976  if ( isset( $info['AutoloadClasses'] ) ) {
977  $paths = $this->applyPath( $info['AutoloadClasses'], $dir );
978  $this->autoload['classes'] += $paths;
979  }
980 
981  if ( isset( $info['AutoloadNamespaces'] ) ) {
982  $paths = $this->applyPath( $info['AutoloadNamespaces'], $dir );
983  $this->autoload['namespaces'] += $paths;
984  }
985 
986  if ( isset( $info['TestAutoloadClasses'] ) ) {
987  $paths = $this->applyPath( $info['TestAutoloadClasses'], $dir );
988  $this->autoloadDev['classes'] += $paths;
989  }
990 
991  if ( isset( $info['TestAutoloadNamespaces'] ) ) {
992  $paths = $this->applyPath( $info['TestAutoloadNamespaces'], $dir );
993  $this->autoloadDev['namespaces'] += $paths;
994  }
995  }
996 }
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.
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
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