MediaWiki  master
NamespaceInfo.php
Go to the documentation of this file.
1 <?php
29 
37 
44 
46  private $canonicalNamespaces = null;
47 
49  private $namespaceIndexes = false;
50 
52  private $validNamespaces = null;
53 
55  private $options;
56 
58  private $hookRunner;
59 
65  public const CANONICAL_NAMES = [
66  NS_MEDIA => 'Media',
67  NS_SPECIAL => 'Special',
68  NS_MAIN => '',
69  NS_TALK => 'Talk',
70  NS_USER => 'User',
71  NS_USER_TALK => 'User_talk',
72  NS_PROJECT => 'Project',
73  NS_PROJECT_TALK => 'Project_talk',
74  NS_FILE => 'File',
75  NS_FILE_TALK => 'File_talk',
76  NS_MEDIAWIKI => 'MediaWiki',
77  NS_MEDIAWIKI_TALK => 'MediaWiki_talk',
78  NS_TEMPLATE => 'Template',
79  NS_TEMPLATE_TALK => 'Template_talk',
80  NS_HELP => 'Help',
81  NS_HELP_TALK => 'Help_talk',
82  NS_CATEGORY => 'Category',
83  NS_CATEGORY_TALK => 'Category_talk',
84  ];
85 
89  public const CONSTRUCTOR_OPTIONS = [
90  MainConfigNames::CanonicalNamespaceNames,
91  MainConfigNames::CapitalLinkOverrides,
92  MainConfigNames::CapitalLinks,
93  MainConfigNames::ContentNamespaces,
94  MainConfigNames::ExtraNamespaces,
95  MainConfigNames::ExtraSignatureNamespaces,
96  MainConfigNames::NamespaceContentModels,
97  MainConfigNames::NamespacesWithSubpages,
98  MainConfigNames::NonincludableNamespaces,
99  ];
100 
105  public function __construct( ServiceOptions $options, HookContainer $hookContainer ) {
106  $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
107  $this->options = $options;
108  $this->hookRunner = new HookRunner( $hookContainer );
109  }
110 
123  private function isMethodValidFor( $index, $method ) {
124  if ( $index < NS_MAIN ) {
125  throw new MWException( "$method does not make any sense for given namespace $index" );
126  }
127  return true;
128  }
129 
139  private function makeValidNamespace( $index, $method ) {
140  if ( !(
141  is_int( $index )
142  // Namespace index numbers as strings
143  || ctype_digit( $index )
144  // Negative numbers as strings
145  || ( $index[0] === '-' && ctype_digit( substr( $index, 1 ) ) )
146  ) ) {
147  throw new InvalidArgumentException(
148  "$method called with non-integer (" . gettype( $index ) . ") namespace '$index'"
149  );
150  }
151 
152  return intval( $index );
153  }
154 
161  public function isMovable( $index ) {
162  $extensionRegistry = ExtensionRegistry::getInstance();
163  $extNamespaces = $extensionRegistry->getAttribute( 'ImmovableNamespaces' );
164 
165  $result = $index >= NS_MAIN && !in_array( $index, $extNamespaces );
166 
170  $this->hookRunner->onNamespaceIsMovable( $index, $result );
171 
172  return $result;
173  }
174 
181  public function isSubject( $index ) {
182  return !$this->isTalk( $index );
183  }
184 
191  public function isTalk( $index ) {
192  $index = $this->makeValidNamespace( $index, __METHOD__ );
193 
194  return $index > NS_MAIN
195  && $index % 2;
196  }
197 
206  public function getTalk( $index ) {
207  $index = $this->makeValidNamespace( $index, __METHOD__ );
208 
209  $this->isMethodValidFor( $index, __METHOD__ );
210  return $this->isTalk( $index )
211  ? $index
212  : $index + 1;
213  }
214 
224  public function getTalkPage( LinkTarget $target ): LinkTarget {
225  if ( $target->getText() === '' ) {
226  throw new MWException( 'Can\'t determine talk page associated with relative section link' );
227  }
228 
229  if ( $target->getInterwiki() !== '' ) {
230  throw new MWException( 'Can\'t determine talk page associated with interwiki link' );
231  }
232 
233  if ( $this->isTalk( $target->getNamespace() ) ) {
234  return $target;
235  }
236 
237  // NOTE: getTalk throws on bad namespaces!
238  return new TitleValue( $this->getTalk( $target->getNamespace() ), $target->getDBkey() );
239  }
240 
252  public function canHaveTalkPage( LinkTarget $target ) {
253  if ( $target->getText() === '' || $target->getInterwiki() !== '' ) {
254  return false;
255  }
256 
257  if ( $target->getNamespace() < NS_MAIN ) {
258  return false;
259  }
260 
261  return true;
262  }
263 
271  public function getSubject( $index ) {
272  $index = $this->makeValidNamespace( $index, __METHOD__ );
273 
274  # Handle special namespaces
275  if ( $index < NS_MAIN ) {
276  return $index;
277  }
278 
279  return $this->isTalk( $index )
280  ? $index - 1
281  : $index;
282  }
283 
288  public function getSubjectPage( LinkTarget $target ): LinkTarget {
289  if ( $this->isSubject( $target->getNamespace() ) ) {
290  return $target;
291  }
292  return new TitleValue( $this->getSubject( $target->getNamespace() ), $target->getDBkey() );
293  }
294 
304  public function getAssociated( $index ) {
305  $this->isMethodValidFor( $index, __METHOD__ );
306 
307  if ( $this->isSubject( $index ) ) {
308  return $this->getTalk( $index );
309  }
310  return $this->getSubject( $index );
311  }
312 
319  public function getAssociatedPage( LinkTarget $target ): LinkTarget {
320  if ( $target->getText() === '' ) {
321  throw new MWException( 'Can\'t determine talk page associated with relative section link' );
322  }
323 
324  if ( $target->getInterwiki() !== '' ) {
325  throw new MWException( 'Can\'t determine talk page associated with interwiki link' );
326  }
327 
328  return new TitleValue(
329  $this->getAssociated( $target->getNamespace() ), $target->getDBkey() );
330  }
331 
339  public function exists( $index ) {
340  $nslist = $this->getCanonicalNamespaces();
341  return isset( $nslist[$index] );
342  }
343 
357  public function equals( $ns1, $ns2 ) {
358  return $ns1 == $ns2;
359  }
360 
371  public function subjectEquals( $ns1, $ns2 ) {
372  return $this->getSubject( $ns1 ) == $this->getSubject( $ns2 );
373  }
374 
381  public function getCanonicalNamespaces() {
382  if ( $this->canonicalNamespaces === null ) {
383  $this->canonicalNamespaces =
384  [ NS_MAIN => '' ] + $this->options->get( MainConfigNames::CanonicalNamespaceNames );
385  $this->canonicalNamespaces +=
386  ExtensionRegistry::getInstance()->getAttribute( 'ExtensionNamespaces' );
387  if ( is_array( $this->options->get( MainConfigNames::ExtraNamespaces ) ) ) {
388  $this->canonicalNamespaces += $this->options->get( MainConfigNames::ExtraNamespaces );
389  }
390  $this->hookRunner->onCanonicalNamespaces( $this->canonicalNamespaces );
391  }
392  return $this->canonicalNamespaces;
393  }
394 
401  public function getCanonicalName( $index ) {
402  $nslist = $this->getCanonicalNamespaces();
403  return $nslist[$index] ?? false;
404  }
405 
413  public function getCanonicalIndex( $name ) {
414  if ( $this->namespaceIndexes === false ) {
415  $this->namespaceIndexes = [];
416  foreach ( $this->getCanonicalNamespaces() as $i => $text ) {
417  $this->namespaceIndexes[strtolower( $text )] = $i;
418  }
419  }
420  if ( array_key_exists( $name, $this->namespaceIndexes ) ) {
421  return $this->namespaceIndexes[$name];
422  } else {
423  return null;
424  }
425  }
426 
432  public function getValidNamespaces() {
433  if ( $this->validNamespaces === null ) {
434  $this->validNamespaces = [];
435  foreach ( array_keys( $this->getCanonicalNamespaces() ) as $ns ) {
436  if ( $ns >= 0 ) {
437  $this->validNamespaces[] = $ns;
438  }
439  }
440  // T109137: sort numerically
441  sort( $this->validNamespaces, SORT_NUMERIC );
442  }
443 
444  return $this->validNamespaces;
445  }
446 
453  public function hasTalkNamespace( $index ) {
454  return $index >= NS_MAIN;
455  }
456 
464  public function isContent( $index ) {
465  return $index == NS_MAIN ||
466  in_array( $index, $this->options->get( MainConfigNames::ContentNamespaces ) );
467  }
468 
476  public function wantSignatures( $index ) {
477  return $this->isTalk( $index ) ||
478  in_array( $index, $this->options->get( MainConfigNames::ExtraSignatureNamespaces ) );
479  }
480 
487  public function isWatchable( $index ) {
488  return $index >= NS_MAIN;
489  }
490 
498  public function hasSubpages( $index ) {
499  return !empty( $this->options->get( MainConfigNames::NamespacesWithSubpages )[$index] );
500  }
501 
506  public function getContentNamespaces() {
507  $contentNamespaces = $this->options->get( MainConfigNames::ContentNamespaces );
508  if ( !is_array( $contentNamespaces ) || $contentNamespaces === [] ) {
509  return [ NS_MAIN ];
510  } elseif ( !in_array( NS_MAIN, $contentNamespaces ) ) {
511  // always force NS_MAIN to be part of array (to match the algorithm used by isContent)
512  return array_merge( [ NS_MAIN ], $contentNamespaces );
513  } else {
514  return $contentNamespaces;
515  }
516  }
517 
524  public function getSubjectNamespaces() {
525  return array_filter(
526  $this->getValidNamespaces(),
527  [ $this, 'isSubject' ]
528  );
529  }
530 
537  public function getTalkNamespaces() {
538  return array_filter(
539  $this->getValidNamespaces(),
540  [ $this, 'isTalk' ]
541  );
542  }
543 
550  public function isCapitalized( $index ) {
551  // Turn NS_MEDIA into NS_FILE
552  $index = $index === NS_MEDIA ? NS_FILE : $index;
553 
554  // Make sure to get the subject of our namespace
555  $index = $this->getSubject( $index );
556 
557  // Some namespaces are special and should always be upper case
558  if ( in_array( $index, $this->alwaysCapitalizedNamespaces ) ) {
559  return true;
560  }
561  $overrides = $this->options->get( MainConfigNames::CapitalLinkOverrides );
562  if ( isset( $overrides[$index] ) ) {
563  // CapitalLinkOverrides is explicitly set
564  return $overrides[$index];
565  }
566  // Default to the global setting
567  return $this->options->get( MainConfigNames::CapitalLinks );
568  }
569 
577  public function hasGenderDistinction( $index ) {
578  return $index == NS_USER || $index == NS_USER_TALK;
579  }
580 
587  public function isNonincludable( $index ) {
588  $namespaces = $this->options->get( MainConfigNames::NonincludableNamespaces );
589  return $namespaces && in_array( $index, $namespaces );
590  }
591 
602  public function getNamespaceContentModel( $index ) {
603  return $this->options->get( MainConfigNames::NamespaceContentModels )[$index] ?? null;
604  }
605 
615  public function getRestrictionLevels( $index, User $user = null ) {
616  // PermissionManager is not injected because adding an explicit dependency
617  // breaks MW installer by adding a dependency chain on the database before
618  // it was set up. Also, the method is deprecated and will be soon removed.
619  wfDeprecated( __METHOD__, '1.34' );
620  return MediaWikiServices::getInstance()
621  ->getPermissionManager()
622  ->getNamespaceRestrictionLevels( $index, $user );
623  }
624 
634  public function getCategoryLinkType( $index ) {
635  $this->isMethodValidFor( $index, __METHOD__ );
636 
637  if ( $index == NS_CATEGORY ) {
638  return 'subcat';
639  } elseif ( $index == NS_FILE ) {
640  return 'file';
641  } else {
642  return 'page';
643  }
644  }
645 
653  public static function getCommonNamespaces() {
654  return array_keys( self::CANONICAL_NAMES );
655  }
656 }
const NS_HELP
Definition: Defines.php:76
const NS_USER
Definition: Defines.php:66
const NS_FILE
Definition: Defines.php:70
const NS_MEDIAWIKI_TALK
Definition: Defines.php:73
const NS_MAIN
Definition: Defines.php:64
const NS_PROJECT_TALK
Definition: Defines.php:69
const NS_MEDIAWIKI
Definition: Defines.php:72
const NS_TEMPLATE
Definition: Defines.php:74
const NS_SPECIAL
Definition: Defines.php:53
const NS_FILE_TALK
Definition: Defines.php:71
const NS_HELP_TALK
Definition: Defines.php:77
const NS_CATEGORY_TALK
Definition: Defines.php:79
const NS_MEDIA
Definition: Defines.php:52
const NS_TALK
Definition: Defines.php:65
const NS_USER_TALK
Definition: Defines.php:67
const NS_PROJECT
Definition: Defines.php:68
const NS_CATEGORY
Definition: Defines.php:78
const NS_TEMPLATE_TALK
Definition: Defines.php:75
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
if(!defined('MW_SETUP_CALLBACK'))
The persistent session ID (if any) loaded at startup.
Definition: WebStart.php:82
MediaWiki exception.
Definition: MWException.php:29
A class for passing options to services.
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:562
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
getAssociatedPage(LinkTarget $target)
ServiceOptions $options
hasGenderDistinction( $index)
Does the namespace (potentially) have different aliases for different genders.
__construct(ServiceOptions $options, HookContainer $hookContainer)
equals( $ns1, $ns2)
Returns whether the specified namespaces are the same namespace.
wantSignatures( $index)
Might pages in this namespace require the use of the Signature button on the edit toolbar?
getNamespaceContentModel( $index)
Get the default content model for a namespace This does not mean that all pages in that namespace hav...
subjectEquals( $ns1, $ns2)
Returns whether the specified namespaces share the same subject.
isCapitalized( $index)
Is the namespace first-letter capitalized?
getCanonicalName( $index)
Returns the canonical (English) name for a given index.
getContentNamespaces()
Get a list of all namespace indices which are considered to contain content.
string[] null $canonicalNamespaces
Canonical namespaces cache.
isNonincludable( $index)
It is not possible to use pages from this namespace as template?
getTalkNamespaces()
List all namespace indices which are considered talks, aka not a subject or special namespace.
getRestrictionLevels( $index, User $user=null)
Determine which restriction levels it makes sense to use in a namespace, optionally filtered by a use...
makeValidNamespace( $index, $method)
Throw if given index isn't an integer or integer-like string and so can't be a valid namespace.
hasTalkNamespace( $index)
Does this namespace ever have a talk namespace?
isSubject( $index)
Is the given namespace is a subject (non-talk) namespace?
HookRunner $hookRunner
$alwaysCapitalizedNamespaces
These namespaces should always be first-letter capitalized, now and forevermore.
getValidNamespaces()
Returns an array of the namespaces (by integer id) that exist on the wiki.
static getCommonNamespaces()
Retrieve the indexes for the namespaces defined by core.
exists( $index)
Returns whether the specified namespace exists.
isContent( $index)
Does this namespace contain content, for the purposes of calculating statistics, etc?
getSubjectPage(LinkTarget $target)
isMethodValidFor( $index, $method)
Throw an exception when trying to get the subject or talk page for a given namespace where it does no...
int[] null $validNamespaces
Valid namespaces cache.
getCanonicalNamespaces()
Returns array of all defined namespaces with their canonical (English) names.
const CANONICAL_NAMES
Definitions of the NS_ constants are in Defines.php.
getSubject( $index)
Get the subject namespace index for a given namespace Special namespaces (NS_MEDIA,...
hasSubpages( $index)
Does the namespace allow subpages? Note that this refers to structured handling of subpages,...
array false $namespaceIndexes
Canonical namespaces index cache.
canHaveTalkPage(LinkTarget $target)
Can the title have a corresponding talk page?
isWatchable( $index)
Can pages in a namespace be watched?
getTalk( $index)
Get the talk namespace index for a given namespace.
getSubjectNamespaces()
List all namespace indices which are considered subject, aka not a talk or special namespace.
isMovable( $index)
Can pages in the given namespace be moved?
getCategoryLinkType( $index)
Returns the link type to be used for categories.
getAssociated( $index)
Get the associated namespace.
getTalkPage(LinkTarget $target)
Get a LinkTarget referring to the talk page of $target.
const CONSTRUCTOR_OPTIONS
isTalk( $index)
Is the given namespace a talk namespace?
getCanonicalIndex( $name)
Returns the index for a given canonical name, or NULL The input must be converted to lower case first...
Represents a page (or page fragment) title within MediaWiki.
Definition: TitleValue.php:40
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:68
getInterwiki()
The interwiki component of this LinkTarget.
getNamespace()
Get the namespace index.
getText()
Get the main part of the link target, in text form.