3 * Provide things related to namespaces.
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
22 use MediaWiki\MediaWikiServices
;
25 * This is a utility class with only static functions
26 * for dealing with namespaces that encodes all the
27 * "magic" behaviors of them based on index. The textual
28 * names of the namespaces are handled by Language.php.
30 * These are synonyms for the names given in the language file
31 * Users and translators should not change them
36 * These namespaces should always be first-letter capitalized, now and
37 * forevermore. Historically, they could've probably been lowercased too,
38 * but some things are just too ingrained now. :)
40 private static $alwaysCapitalizedNamespaces = [ NS_SPECIAL
, NS_USER
, NS_MEDIAWIKI
];
42 /** @var string[]|null Canonical namespaces cache */
43 private static $canonicalNamespaces = null;
45 /** @var array|false Canonical namespaces index cache */
46 private static $namespaceIndexes = false;
48 /** @var int[]|null Valid namespaces cache */
49 private static $validNamespaces = null;
52 * Throw an exception when trying to get the subject or talk page
53 * for a given namespace where it does not make sense.
54 * Special namespaces are defined in includes/Defines.php and have
55 * a value below 0 (ex: NS_SPECIAL = -1 , NS_MEDIA = -2)
58 * @param string $method
63 private static function isMethodValidFor( $index, $method ) {
64 if ( $index < NS_MAIN
) {
65 throw new MWException( "$method does not make any sense for given namespace $index" );
71 * Clear internal caches
73 * For use in unit testing when namespace configuration is changed.
77 public static function clearCaches() {
78 self
::$canonicalNamespaces = null;
79 self
::$namespaceIndexes = false;
80 self
::$validNamespaces = null;
84 * Can pages in the given namespace be moved?
86 * @param int $index Namespace index
89 public static function isMovable( $index ) {
90 global $wgAllowImageMoving;
92 $result = !( $index < NS_MAIN ||
( $index == NS_FILE
&& !$wgAllowImageMoving ) );
97 Hooks
::run( 'NamespaceIsMovable', [ $index, &$result ] );
103 * Is the given namespace is a subject (non-talk) namespace?
105 * @param int $index Namespace index
109 public static function isSubject( $index ) {
110 return !self
::isTalk( $index );
114 * Is the given namespace a talk namespace?
116 * @param int $index Namespace index
119 public static function isTalk( $index ) {
120 return $index > NS_MAIN
125 * Get the talk namespace index for a given namespace
127 * @param int $index Namespace index
130 public static function getTalk( $index ) {
131 self
::isMethodValidFor( $index, __METHOD__
);
132 return self
::isTalk( $index )
138 * Get the subject namespace index for a given namespace
139 * Special namespaces (NS_MEDIA, NS_SPECIAL) are always the subject.
141 * @param int $index Namespace index
144 public static function getSubject( $index ) {
145 # Handle special namespaces
146 if ( $index < NS_MAIN
) {
150 return self
::isTalk( $index )
156 * Get the associated namespace.
157 * For talk namespaces, returns the subject (non-talk) namespace
158 * For subject (non-talk) namespaces, returns the talk namespace
160 * @param int $index Namespace index
161 * @return int|null If no associated namespace could be found
163 public static function getAssociated( $index ) {
164 self
::isMethodValidFor( $index, __METHOD__
);
166 if ( self
::isSubject( $index ) ) {
167 return self
::getTalk( $index );
168 } elseif ( self
::isTalk( $index ) ) {
169 return self
::getSubject( $index );
176 * Returns whether the specified namespace exists
183 public static function exists( $index ) {
184 $nslist = self
::getCanonicalNamespaces();
185 return isset( $nslist[$index] );
189 * Returns whether the specified namespaces are the same namespace
191 * @note It's possible that in the future we may start using something
192 * other than just namespace indexes. Under that circumstance making use
193 * of this function rather than directly doing comparison will make
194 * sure that code will not potentially break.
196 * @param int $ns1 The first namespace index
197 * @param int $ns2 The second namespace index
202 public static function equals( $ns1, $ns2 ) {
207 * Returns whether the specified namespaces share the same subject.
208 * eg: NS_USER and NS_USER wil return true, as well
209 * NS_USER and NS_USER_TALK will return true.
211 * @param int $ns1 The first namespace index
212 * @param int $ns2 The second namespace index
217 public static function subjectEquals( $ns1, $ns2 ) {
218 return self
::getSubject( $ns1 ) == self
::getSubject( $ns2 );
222 * Returns array of all defined namespaces with their canonical
225 * @param bool $rebuild Rebuild namespace list (default = false). Used for testing.
226 * Deprecated since 1.31, use self::clearCaches() instead.
231 public static function getCanonicalNamespaces( $rebuild = false ) {
236 if ( self
::$canonicalNamespaces === null ) {
237 global $wgExtraNamespaces, $wgCanonicalNamespaceNames;
238 self
::$canonicalNamespaces = [ NS_MAIN
=> '' ] +
$wgCanonicalNamespaceNames;
239 // Add extension namespaces
240 self
::$canonicalNamespaces +
=
241 ExtensionRegistry
::getInstance()->getAttribute( 'ExtensionNamespaces' );
242 if ( is_array( $wgExtraNamespaces ) ) {
243 self
::$canonicalNamespaces +
= $wgExtraNamespaces;
245 Hooks
::run( 'CanonicalNamespaces', [ &self
::$canonicalNamespaces ] );
247 return self
::$canonicalNamespaces;
251 * Returns the canonical (English) name for a given index
253 * @param int $index Namespace index
254 * @return string|bool If no canonical definition.
256 public static function getCanonicalName( $index ) {
257 $nslist = self
::getCanonicalNamespaces();
258 return $nslist[$index] ??
false;
262 * Returns the index for a given canonical name, or NULL
263 * The input *must* be converted to lower case first
265 * @param string $name Namespace name
268 public static function getCanonicalIndex( $name ) {
269 if ( self
::$namespaceIndexes === false ) {
270 self
::$namespaceIndexes = [];
271 foreach ( self
::getCanonicalNamespaces() as $i => $text ) {
272 self
::$namespaceIndexes[strtolower( $text )] = $i;
275 if ( array_key_exists( $name, self
::$namespaceIndexes ) ) {
276 return self
::$namespaceIndexes[$name];
283 * Returns an array of the namespaces (by integer id) that exist on the
284 * wiki. Used primarily by the api in help documentation.
287 public static function getValidNamespaces() {
288 if ( is_null( self
::$validNamespaces ) ) {
289 foreach ( array_keys( self
::getCanonicalNamespaces() ) as $ns ) {
291 self
::$validNamespaces[] = $ns;
294 // T109137: sort numerically
295 sort( self
::$validNamespaces, SORT_NUMERIC
);
298 return self
::$validNamespaces;
302 * Does this namespace ever have a talk namespace?
304 * @deprecated since 1.30, use hasTalkNamespace() instead.
306 * @param int $index Namespace index
307 * @return bool True if this namespace either is or has a corresponding talk namespace.
309 public static function canTalk( $index ) {
310 wfDeprecated( __METHOD__
, '1.30' );
311 return self
::hasTalkNamespace( $index );
315 * Does this namespace ever have a talk namespace?
319 * @param int $index Namespace ID
320 * @return bool True if this namespace either is or has a corresponding talk namespace.
322 public static function hasTalkNamespace( $index ) {
323 return $index >= NS_MAIN
;
327 * Does this namespace contain content, for the purposes of calculating
330 * @param int $index Index to check
333 public static function isContent( $index ) {
334 global $wgContentNamespaces;
335 return $index == NS_MAIN ||
in_array( $index, $wgContentNamespaces );
339 * Might pages in this namespace require the use of the Signature button on
342 * @param int $index Index to check
345 public static function wantSignatures( $index ) {
346 global $wgExtraSignatureNamespaces;
347 return self
::isTalk( $index ) ||
in_array( $index, $wgExtraSignatureNamespaces );
351 * Can pages in a namespace be watched?
356 public static function isWatchable( $index ) {
357 return $index >= NS_MAIN
;
361 * Does the namespace allow subpages?
363 * @param int $index Index to check
366 public static function hasSubpages( $index ) {
367 global $wgNamespacesWithSubpages;
368 return !empty( $wgNamespacesWithSubpages[$index] );
372 * Get a list of all namespace indices which are considered to contain content
373 * @return array Array of namespace indices
375 public static function getContentNamespaces() {
376 global $wgContentNamespaces;
377 if ( !is_array( $wgContentNamespaces ) ||
$wgContentNamespaces === [] ) {
379 } elseif ( !in_array( NS_MAIN
, $wgContentNamespaces ) ) {
380 // always force NS_MAIN to be part of array (to match the algorithm used by isContent)
381 return array_merge( [ NS_MAIN
], $wgContentNamespaces );
383 return $wgContentNamespaces;
388 * List all namespace indices which are considered subject, aka not a talk
389 * or special namespace. See also MWNamespace::isSubject
391 * @return array Array of namespace indices
393 public static function getSubjectNamespaces() {
395 self
::getValidNamespaces(),
396 'MWNamespace::isSubject'
401 * List all namespace indices which are considered talks, aka not a subject
402 * or special namespace. See also MWNamespace::isTalk
404 * @return array Array of namespace indices
406 public static function getTalkNamespaces() {
408 self
::getValidNamespaces(),
409 'MWNamespace::isTalk'
414 * Is the namespace first-letter capitalized?
416 * @param int $index Index to check
419 public static function isCapitalized( $index ) {
420 global $wgCapitalLinks, $wgCapitalLinkOverrides;
421 // Turn NS_MEDIA into NS_FILE
422 $index = $index === NS_MEDIA ? NS_FILE
: $index;
424 // Make sure to get the subject of our namespace
425 $index = self
::getSubject( $index );
427 // Some namespaces are special and should always be upper case
428 if ( in_array( $index, self
::$alwaysCapitalizedNamespaces ) ) {
431 if ( isset( $wgCapitalLinkOverrides[$index] ) ) {
432 // $wgCapitalLinkOverrides is explicitly set
433 return $wgCapitalLinkOverrides[$index];
435 // Default to the global setting
436 return $wgCapitalLinks;
440 * Does the namespace (potentially) have different aliases for different
441 * genders. Not all languages make a distinction here.
444 * @param int $index Index to check
447 public static function hasGenderDistinction( $index ) {
448 return $index == NS_USER ||
$index == NS_USER_TALK
;
452 * It is not possible to use pages from this namespace as template?
455 * @param int $index Index to check
458 public static function isNonincludable( $index ) {
459 global $wgNonincludableNamespaces;
460 return $wgNonincludableNamespaces && in_array( $index, $wgNonincludableNamespaces );
464 * Get the default content model for a namespace
465 * This does not mean that all pages in that namespace have the model
467 * @note To determine the default model for a new page's main slot, or any slot in general,
468 * use SlotRoleHandler::getDefaultModel() together with SlotRoleRegistry::getRoleHandler().
471 * @param int $index Index to check
472 * @return null|string Default model name for the given namespace, if set
474 public static function getNamespaceContentModel( $index ) {
475 $config = MediaWikiServices
::getInstance()->getMainConfig();
476 $models = $config->get( 'NamespaceContentModels' );
477 return $models[$index] ??
null;
481 * Determine which restriction levels it makes sense to use in a namespace,
482 * optionally filtered by a user's rights.
485 * @param int $index Index to check
486 * @param User|null $user User to check
489 public static function getRestrictionLevels( $index, User
$user = null ) {
490 global $wgNamespaceProtection, $wgRestrictionLevels;
492 if ( !isset( $wgNamespaceProtection[$index] ) ) {
493 // All levels are valid if there's no namespace restriction.
494 // But still filter by user, if necessary
495 $levels = $wgRestrictionLevels;
497 $levels = array_values( array_filter( $levels, function ( $level ) use ( $user ) {
499 if ( $right == 'sysop' ) {
500 $right = 'editprotected'; // BC
502 if ( $right == 'autoconfirmed' ) {
503 $right = 'editsemiprotected'; // BC
505 return ( $right == '' ||
$user->isAllowed( $right ) );
511 // First, get the list of groups that can edit this namespace.
512 $namespaceGroups = [];
513 $combine = 'array_merge';
514 foreach ( (array)$wgNamespaceProtection[$index] as $right ) {
515 if ( $right == 'sysop' ) {
516 $right = 'editprotected'; // BC
518 if ( $right == 'autoconfirmed' ) {
519 $right = 'editsemiprotected'; // BC
521 if ( $right != '' ) {
522 $namespaceGroups = call_user_func( $combine, $namespaceGroups,
523 User
::getGroupsWithPermission( $right ) );
524 $combine = 'array_intersect';
528 // Now, keep only those restriction levels where there is at least one
529 // group that can edit the namespace but would be blocked by the
531 $usableLevels = [ '' ];
532 foreach ( $wgRestrictionLevels as $level ) {
534 if ( $right == 'sysop' ) {
535 $right = 'editprotected'; // BC
537 if ( $right == 'autoconfirmed' ) {
538 $right = 'editsemiprotected'; // BC
540 if ( $right != '' && ( !$user ||
$user->isAllowed( $right ) ) &&
541 array_diff( $namespaceGroups, User
::getGroupsWithPermission( $right ) )
543 $usableLevels[] = $level;
547 return $usableLevels;
551 * Returns the link type to be used for categories.
553 * This determines which section of a category page titles
554 * in the namespace will appear within.
557 * @param int $index Namespace index
558 * @return string One of 'subcat', 'file', 'page'
560 public static function getCategoryLinkType( $index ) {
561 self
::isMethodValidFor( $index, __METHOD__
);
563 if ( $index == NS_CATEGORY
) {
565 } elseif ( $index == NS_FILE
) {