MediaWiki  1.32.0
ExtensionProcessor.php
Go to the documentation of this file.
1 <?php
2 
3 class ExtensionProcessor implements Processor {
4 
10  protected static $globalSettings = [
11  'ActionFilteredLogs',
12  'Actions',
13  'AddGroups',
14  'APIFormatModules',
15  'APIListModules',
16  'APIMetaModules',
17  'APIModules',
18  'APIPropModules',
19  'AuthManagerAutoConfig',
20  'AvailableRights',
21  'CentralIdLookupProviders',
22  'ChangeCredentialsBlacklist',
23  'ConfigRegistry',
24  'ContentHandlers',
25  'DefaultUserOptions',
26  'ExtensionEntryPointListFiles',
27  'ExtensionFunctions',
28  'FeedClasses',
29  'FileExtensions',
30  'FilterLogTypes',
31  'GrantPermissionGroups',
32  'GrantPermissions',
33  'GroupPermissions',
34  'GroupsAddToSelf',
35  'GroupsRemoveFromSelf',
36  'HiddenPrefs',
37  'ImplicitGroups',
38  'JobClasses',
39  'LogActions',
40  'LogActionsHandlers',
41  'LogHeaders',
42  'LogNames',
43  'LogRestrictions',
44  'LogTypes',
45  'MediaHandlers',
46  'PasswordPolicy',
47  'RateLimits',
48  'RawHtmlMessages',
49  'RecentChangesFlags',
50  'RemoveCredentialsBlacklist',
51  'RemoveGroups',
52  'ResourceLoaderSources',
53  'RevokePermissions',
54  'SessionProviders',
55  'SpecialPages',
56  'ValidSkinNames',
57  ];
58 
64  protected static $coreAttributes = [
65  'SkinOOUIThemes',
66  'TrackingCategories',
67  ];
68 
76  protected static $mergeStrategies = [
77  'wgAuthManagerAutoConfig' => 'array_plus_2d',
78  'wgCapitalLinkOverrides' => 'array_plus',
79  'wgExtensionCredits' => 'array_merge_recursive',
80  'wgExtraGenderNamespaces' => 'array_plus',
81  'wgGrantPermissions' => 'array_plus_2d',
82  'wgGroupPermissions' => 'array_plus_2d',
83  'wgHooks' => 'array_merge_recursive',
84  'wgNamespaceContentModels' => 'array_plus',
85  'wgNamespaceProtection' => 'array_plus',
86  'wgNamespacesWithSubpages' => 'array_plus',
87  'wgPasswordPolicy' => 'array_merge_recursive',
88  'wgRateLimits' => 'array_plus_2d',
89  'wgRevokePermissions' => 'array_plus_2d',
90  ];
91 
97  protected static $creditsAttributes = [
98  'name',
99  'namemsg',
100  'author',
101  'version',
102  'url',
103  'description',
104  'descriptionmsg',
105  'license-name',
106  ];
107 
114  protected static $notAttributes = [
115  'callback',
116  'Hooks',
117  'namespaces',
118  'ResourceFileModulePaths',
119  'ResourceModules',
120  'ResourceModuleSkinStyles',
121  'ExtensionMessagesFiles',
122  'MessagesDirs',
123  'type',
124  'config',
125  'config_prefix',
126  'ServiceWiringFiles',
127  'ParserTestFiles',
128  'AutoloadClasses',
129  'manifest_version',
130  'load_composer_autoloader',
131  ];
132 
140  protected $globals = [
141  'wgExtensionMessagesFiles' => [],
142  'wgMessagesDirs' => [],
143  ];
144 
150  protected $defines = [];
151 
158  protected $callbacks = [];
159 
163  protected $credits = [];
164 
168  protected $config = [];
169 
176  protected $attributes = [];
177 
184  protected $extAttributes = [];
185 
192  public function extractInfo( $path, array $info, $version ) {
193  $dir = dirname( $path );
194  $this->extractHooks( $info );
195  $this->extractExtensionMessagesFiles( $dir, $info );
196  $this->extractMessagesDirs( $dir, $info );
197  $this->extractNamespaces( $info );
198  $this->extractResourceLoaderModules( $dir, $info );
199  if ( isset( $info['ServiceWiringFiles'] ) ) {
200  $this->extractPathBasedGlobal(
201  'wgServiceWiringFiles',
202  $dir,
203  $info['ServiceWiringFiles']
204  );
205  }
206  if ( isset( $info['ParserTestFiles'] ) ) {
207  $this->extractPathBasedGlobal(
208  'wgParserTestFiles',
209  $dir,
210  $info['ParserTestFiles']
211  );
212  }
213  $name = $this->extractCredits( $path, $info );
214  if ( isset( $info['callback'] ) ) {
215  $this->callbacks[$name] = $info['callback'];
216  }
217 
218  // config should be after all core globals are extracted,
219  // so duplicate setting detection will work fully
220  if ( $version === 2 ) {
221  $this->extractConfig2( $info, $dir );
222  } else {
223  // $version === 1
224  $this->extractConfig1( $info );
225  }
226 
227  if ( $version === 2 ) {
228  $this->extractAttributes( $path, $info );
229  }
230 
231  foreach ( $info as $key => $val ) {
232  // If it's a global setting,
233  if ( in_array( $key, self::$globalSettings ) ) {
234  $this->storeToArray( $path, "wg$key", $val, $this->globals );
235  continue;
236  }
237  // Ignore anything that starts with a @
238  if ( $key[0] === '@' ) {
239  continue;
240  }
241 
242  if ( $version === 2 ) {
243  // Only whitelisted attributes are set
244  if ( in_array( $key, self::$coreAttributes ) ) {
245  $this->storeToArray( $path, $key, $val, $this->attributes );
246  }
247  } else {
248  // version === 1
249  if ( !in_array( $key, self::$notAttributes )
250  && !in_array( $key, self::$creditsAttributes )
251  ) {
252  // If it's not blacklisted, it's an attribute
253  $this->storeToArray( $path, $key, $val, $this->attributes );
254  }
255  }
256 
257  }
258  }
259 
264  protected function extractAttributes( $path, array $info ) {
265  if ( isset( $info['attributes'] ) ) {
266  foreach ( $info['attributes'] as $extName => $value ) {
267  $this->storeToArray( $path, $extName, $value, $this->extAttributes );
268  }
269  }
270  }
271 
272  public function getExtractedInfo() {
273  // Make sure the merge strategies are set
274  foreach ( $this->globals as $key => $val ) {
275  if ( isset( self::$mergeStrategies[$key] ) ) {
276  $this->globals[$key][ExtensionRegistry::MERGE_STRATEGY] = self::$mergeStrategies[$key];
277  }
278  }
279 
280  // Merge $this->extAttributes into $this->attributes depending on what is loaded
281  foreach ( $this->extAttributes as $extName => $value ) {
282  // Only set the attribute if $extName is loaded (and hence present in credits)
283  if ( isset( $this->credits[$extName] ) ) {
284  foreach ( $value as $attrName => $attrValue ) {
285  $this->storeToArray(
286  '', // Don't provide a path since it's impossible to generate an error here
287  $extName . $attrName,
288  $attrValue,
289  $this->attributes
290  );
291  }
292  unset( $this->extAttributes[$extName] );
293  }
294  }
295 
296  return [
297  'globals' => $this->globals,
298  'config' => $this->config,
299  'defines' => $this->defines,
300  'callbacks' => $this->callbacks,
301  'credits' => $this->credits,
302  'attributes' => $this->attributes,
303  ];
304  }
305 
306  public function getRequirements( array $info ) {
307  return $info['requires'] ?? [];
308  }
309 
310  protected function extractHooks( array $info ) {
311  if ( isset( $info['Hooks'] ) ) {
312  foreach ( $info['Hooks'] as $name => $value ) {
313  if ( is_array( $value ) ) {
314  foreach ( $value as $callback ) {
315  $this->globals['wgHooks'][$name][] = $callback;
316  }
317  } else {
318  $this->globals['wgHooks'][$name][] = $value;
319  }
320  }
321  }
322  }
323 
329  protected function extractNamespaces( array $info ) {
330  if ( isset( $info['namespaces'] ) ) {
331  foreach ( $info['namespaces'] as $ns ) {
332  if ( defined( $ns['constant'] ) ) {
333  // If the namespace constant is already defined, use it.
334  // This allows namespace IDs to be overwritten locally.
335  $id = constant( $ns['constant'] );
336  } else {
337  $id = $ns['id'];
338  $this->defines[ $ns['constant'] ] = $id;
339  }
340 
341  if ( !( isset( $ns['conditional'] ) && $ns['conditional'] ) ) {
342  // If it is not conditional, register it
343  $this->attributes['ExtensionNamespaces'][$id] = $ns['name'];
344  }
345  if ( isset( $ns['gender'] ) ) {
346  $this->globals['wgExtraGenderNamespaces'][$id] = $ns['gender'];
347  }
348  if ( isset( $ns['subpages'] ) && $ns['subpages'] ) {
349  $this->globals['wgNamespacesWithSubpages'][$id] = true;
350  }
351  if ( isset( $ns['content'] ) && $ns['content'] ) {
352  $this->globals['wgContentNamespaces'][] = $id;
353  }
354  if ( isset( $ns['defaultcontentmodel'] ) ) {
355  $this->globals['wgNamespaceContentModels'][$id] = $ns['defaultcontentmodel'];
356  }
357  if ( isset( $ns['protection'] ) ) {
358  $this->globals['wgNamespaceProtection'][$id] = $ns['protection'];
359  }
360  if ( isset( $ns['capitallinkoverride'] ) ) {
361  $this->globals['wgCapitalLinkOverrides'][$id] = $ns['capitallinkoverride'];
362  }
363  }
364  }
365  }
366 
367  protected function extractResourceLoaderModules( $dir, array $info ) {
368  $defaultPaths = $info['ResourceFileModulePaths'] ?? false;
369  if ( isset( $defaultPaths['localBasePath'] ) ) {
370  if ( $defaultPaths['localBasePath'] === '' ) {
371  // Avoid double slashes (e.g. /extensions/Example//path)
372  $defaultPaths['localBasePath'] = $dir;
373  } else {
374  $defaultPaths['localBasePath'] = "$dir/{$defaultPaths['localBasePath']}";
375  }
376  }
377 
378  foreach ( [ 'ResourceModules', 'ResourceModuleSkinStyles' ] as $setting ) {
379  if ( isset( $info[$setting] ) ) {
380  foreach ( $info[$setting] as $name => $data ) {
381  if ( isset( $data['localBasePath'] ) ) {
382  if ( $data['localBasePath'] === '' ) {
383  // Avoid double slashes (e.g. /extensions/Example//path)
384  $data['localBasePath'] = $dir;
385  } else {
386  $data['localBasePath'] = "$dir/{$data['localBasePath']}";
387  }
388  }
389  if ( $defaultPaths ) {
390  $data += $defaultPaths;
391  }
392  $this->globals["wg$setting"][$name] = $data;
393  }
394  }
395  }
396  }
397 
398  protected function extractExtensionMessagesFiles( $dir, array $info ) {
399  if ( isset( $info['ExtensionMessagesFiles'] ) ) {
400  foreach ( $info['ExtensionMessagesFiles'] as &$file ) {
401  $file = "$dir/$file";
402  }
403  $this->globals["wgExtensionMessagesFiles"] += $info['ExtensionMessagesFiles'];
404  }
405  }
406 
414  protected function extractMessagesDirs( $dir, array $info ) {
415  if ( isset( $info['MessagesDirs'] ) ) {
416  foreach ( $info['MessagesDirs'] as $name => $files ) {
417  foreach ( (array)$files as $file ) {
418  $this->globals["wgMessagesDirs"][$name][] = "$dir/$file";
419  }
420  }
421  }
422  }
423 
430  protected function extractCredits( $path, array $info ) {
431  $credits = [
432  'path' => $path,
433  'type' => $info['type'] ?? 'other',
434  ];
435  foreach ( self::$creditsAttributes as $attr ) {
436  if ( isset( $info[$attr] ) ) {
437  $credits[$attr] = $info[$attr];
438  }
439  }
440 
441  $name = $credits['name'];
442 
443  // If someone is loading the same thing twice, throw
444  // a nice error (T121493)
445  if ( isset( $this->credits[$name] ) ) {
446  $firstPath = $this->credits[$name]['path'];
447  $secondPath = $credits['path'];
448  throw new Exception( "It was attempted to load $name twice, from $firstPath and $secondPath." );
449  }
450 
451  $this->credits[$name] = $credits;
452  $this->globals['wgExtensionCredits'][$credits['type']][] = $credits;
453 
454  return $name;
455  }
456 
463  protected function extractConfig1( array $info ) {
464  if ( isset( $info['config'] ) ) {
465  if ( isset( $info['config']['_prefix'] ) ) {
466  $prefix = $info['config']['_prefix'];
467  unset( $info['config']['_prefix'] );
468  } else {
469  $prefix = 'wg';
470  }
471  foreach ( $info['config'] as $key => $val ) {
472  if ( $key[0] !== '@' ) {
473  $this->addConfigGlobal( "$prefix$key", $val, $info['name'] );
474  }
475  }
476  }
477  }
478 
486  protected function extractConfig2( array $info, $dir ) {
487  if ( isset( $info['config_prefix'] ) ) {
488  $prefix = $info['config_prefix'];
489  } else {
490  $prefix = 'wg';
491  }
492  if ( isset( $info['config'] ) ) {
493  foreach ( $info['config'] as $key => $data ) {
494  $value = $data['value'];
495  if ( isset( $data['merge_strategy'] ) ) {
496  $value[ExtensionRegistry::MERGE_STRATEGY] = $data['merge_strategy'];
497  }
498  if ( isset( $data['path'] ) && $data['path'] ) {
499  $value = "$dir/$value";
500  }
501  $this->addConfigGlobal( "$prefix$key", $value, $info['name'] );
502  $data['providedby'] = $info['name'];
503  if ( isset( $info['ConfigRegistry'][0] ) ) {
504  $data['configregistry'] = array_keys( $info['ConfigRegistry'] )[0];
505  }
506  $this->config[$key] = $data;
507  }
508  }
509  }
510 
518  private function addConfigGlobal( $key, $value, $extName ) {
519  if ( array_key_exists( $key, $this->globals ) ) {
520  throw new RuntimeException(
521  "The configuration setting '$key' was already set by MediaWiki core or"
522  . " another extension, and cannot be set again by $extName." );
523  }
524  $this->globals[$key] = $value;
525  }
526 
527  protected function extractPathBasedGlobal( $global, $dir, $paths ) {
528  foreach ( $paths as $path ) {
529  $this->globals[$global][] = "$dir/$path";
530  }
531  }
532 
540  protected function storeToArray( $path, $name, $value, &$array ) {
541  if ( !is_array( $value ) ) {
542  throw new InvalidArgumentException( "The value for '$name' should be an array (from $path)" );
543  }
544  if ( isset( $array[$name] ) ) {
545  $array[$name] = array_merge_recursive( $array[$name], $value );
546  } else {
547  $array[$name] = $value;
548  }
549  }
550 
551  public function getExtraAutoloaderPaths( $dir, array $info ) {
552  $paths = [];
553  if ( isset( $info['load_composer_autoloader'] ) && $info['load_composer_autoloader'] === true ) {
554  $paths[] = "$dir/vendor/autoload.php";
555  }
556  return $paths;
557  }
558 }
ExtensionProcessor\getRequirements
getRequirements(array $info)
Get the requirements for the provided info.
Definition: ExtensionProcessor.php:306
ExtensionProcessor\$coreAttributes
static string[] $coreAttributes
Top-level attributes that come from MW core.
Definition: ExtensionProcessor.php:64
ExtensionProcessor\$callbacks
callable[] $callbacks
Things to be called once registration of these extensions are done keyed by the name of the extension...
Definition: ExtensionProcessor.php:158
ExtensionProcessor\$mergeStrategies
static array $mergeStrategies
Mapping of global settings to their specific merge strategies.
Definition: ExtensionProcessor.php:76
ExtensionProcessor\extractResourceLoaderModules
extractResourceLoaderModules( $dir, array $info)
Definition: ExtensionProcessor.php:367
ExtensionProcessor\extractConfig1
extractConfig1(array $info)
Set configuration settings for manifest_version == 1.
Definition: ExtensionProcessor.php:463
ExtensionProcessor\storeToArray
storeToArray( $path, $name, $value, &$array)
Definition: ExtensionProcessor.php:540
Processor
Processors read associated arrays and register whatever is required.
Definition: Processor.php:9
ExtensionProcessor\addConfigGlobal
addConfigGlobal( $key, $value, $extName)
Helper function to set a value to a specific global, if it isn't set already.
Definition: ExtensionProcessor.php:518
ExtensionProcessor\extractExtensionMessagesFiles
extractExtensionMessagesFiles( $dir, array $info)
Definition: ExtensionProcessor.php:398
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
ExtensionRegistry\MERGE_STRATEGY
const MERGE_STRATEGY
Special key that defines the merge strategy.
Definition: ExtensionRegistry.php:48
ExtensionProcessor\extractHooks
extractHooks(array $info)
Definition: ExtensionProcessor.php:310
ExtensionProcessor
Definition: ExtensionProcessor.php:3
ExtensionProcessor\extractPathBasedGlobal
extractPathBasedGlobal( $global, $dir, $paths)
Definition: ExtensionProcessor.php:527
ExtensionProcessor\extractConfig2
extractConfig2(array $info, $dir)
Set configuration settings for manifest_version == 2.
Definition: ExtensionProcessor.php:486
array
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
ExtensionProcessor\$globals
array $globals
Stuff that is going to be set to $GLOBALS.
Definition: ExtensionProcessor.php:140
ExtensionProcessor\getExtraAutoloaderPaths
getExtraAutoloaderPaths( $dir, array $info)
Get the path for additional autoloaders, e.g.
Definition: ExtensionProcessor.php:551
ExtensionProcessor\$config
array $config
Definition: ExtensionProcessor.php:168
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
$value
$value
Definition: styleTest.css.php:49
ExtensionProcessor\$credits
array $credits
Definition: ExtensionProcessor.php:163
globals
globals will be eliminated from MediaWiki replaced by an application object which would be passed to constructors Whether that would be an convenient solution remains to be but certainly PHP makes such object oriented programming models easier than they were in previous versions For the time being MediaWiki programmers will have to work in an environment with some global context At the time of globals were initialised on startup by MediaWiki of these were configuration which are documented in DefaultSettings php There is no comprehensive documentation for the remaining globals
Definition: globals.txt:25
ExtensionProcessor\extractNamespaces
extractNamespaces(array $info)
Register namespaces with the appropriate global settings.
Definition: ExtensionProcessor.php:329
ExtensionProcessor\getExtractedInfo
getExtractedInfo()
Definition: ExtensionProcessor.php:272
ExtensionProcessor\$extAttributes
array $extAttributes
Extension attributes, keyed by name => settings.
Definition: ExtensionProcessor.php:184
ExtensionProcessor\$globalSettings
static array $globalSettings
Keys that should be set to $GLOBALS.
Definition: ExtensionProcessor.php:10
ExtensionProcessor\$creditsAttributes
static array $creditsAttributes
Keys that are part of the extension credits.
Definition: ExtensionProcessor.php:97
ExtensionProcessor\extractAttributes
extractAttributes( $path, array $info)
Definition: ExtensionProcessor.php:264
ExtensionProcessor\extractCredits
extractCredits( $path, array $info)
Definition: ExtensionProcessor.php:430
$path
$path
Definition: NoLocalSettings.php:25
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
ExtensionProcessor\extractInfo
extractInfo( $path, array $info, $version)
Definition: ExtensionProcessor.php:192
ExtensionProcessor\$defines
array $defines
Things that should be define()'d.
Definition: ExtensionProcessor.php:150
ExtensionProcessor\$attributes
array $attributes
Any thing else in the $info that hasn't already been processed.
Definition: ExtensionProcessor.php:176
ExtensionProcessor\$notAttributes
static array $notAttributes
Things that are not 'attributes', but are not in $globalSettings or $creditsAttributes.
Definition: ExtensionProcessor.php:114
ExtensionProcessor\extractMessagesDirs
extractMessagesDirs( $dir, array $info)
Set message-related settings, which need to be expanded to use absolute paths.
Definition: ExtensionProcessor.php:414