MediaWiki  master
NamespaceInfo.php
Go to the documentation of this file.
1 <?php
29 
37 
43  private $alwaysCapitalizedNamespaces = [ NS_SPECIAL, NS_USER, NS_MEDIAWIKI ];
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:564
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)
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.
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...
hasTalkNamespace( $index)
Does this namespace ever have a talk namespace?
isSubject( $index)
Is the given namespace is a subject (non-talk) namespace?
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)
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,...
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:70
getInterwiki()
The interwiki component of this LinkTarget.
getNamespace()
Get the namespace index.
getText()
Get the main part of the link target, in text form.