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 return self
::hasTalkNamespace( $index );
314 * Does this namespace ever have a talk namespace?
318 * @param int $index Namespace ID
319 * @return bool True if this namespace either is or has a corresponding talk namespace.
321 public static function hasTalkNamespace( $index ) {
322 return $index >= NS_MAIN
;
326 * Does this namespace contain content, for the purposes of calculating
329 * @param int $index Index to check
332 public static function isContent( $index ) {
333 global $wgContentNamespaces;
334 return $index == NS_MAIN ||
in_array( $index, $wgContentNamespaces );
338 * Might pages in this namespace require the use of the Signature button on
341 * @param int $index Index to check
344 public static function wantSignatures( $index ) {
345 global $wgExtraSignatureNamespaces;
346 return self
::isTalk( $index ) ||
in_array( $index, $wgExtraSignatureNamespaces );
350 * Can pages in a namespace be watched?
355 public static function isWatchable( $index ) {
356 return $index >= NS_MAIN
;
360 * Does the namespace allow subpages?
362 * @param int $index Index to check
365 public static function hasSubpages( $index ) {
366 global $wgNamespacesWithSubpages;
367 return !empty( $wgNamespacesWithSubpages[$index] );
371 * Get a list of all namespace indices which are considered to contain content
372 * @return array Array of namespace indices
374 public static function getContentNamespaces() {
375 global $wgContentNamespaces;
376 if ( !is_array( $wgContentNamespaces ) ||
$wgContentNamespaces === [] ) {
378 } elseif ( !in_array( NS_MAIN
, $wgContentNamespaces ) ) {
379 // always force NS_MAIN to be part of array (to match the algorithm used by isContent)
380 return array_merge( [ NS_MAIN
], $wgContentNamespaces );
382 return $wgContentNamespaces;
387 * List all namespace indices which are considered subject, aka not a talk
388 * or special namespace. See also MWNamespace::isSubject
390 * @return array Array of namespace indices
392 public static function getSubjectNamespaces() {
394 self
::getValidNamespaces(),
395 'MWNamespace::isSubject'
400 * List all namespace indices which are considered talks, aka not a subject
401 * or special namespace. See also MWNamespace::isTalk
403 * @return array Array of namespace indices
405 public static function getTalkNamespaces() {
407 self
::getValidNamespaces(),
408 'MWNamespace::isTalk'
413 * Is the namespace first-letter capitalized?
415 * @param int $index Index to check
418 public static function isCapitalized( $index ) {
419 global $wgCapitalLinks, $wgCapitalLinkOverrides;
420 // Turn NS_MEDIA into NS_FILE
421 $index = $index === NS_MEDIA ? NS_FILE
: $index;
423 // Make sure to get the subject of our namespace
424 $index = self
::getSubject( $index );
426 // Some namespaces are special and should always be upper case
427 if ( in_array( $index, self
::$alwaysCapitalizedNamespaces ) ) {
430 if ( isset( $wgCapitalLinkOverrides[$index] ) ) {
431 // $wgCapitalLinkOverrides is explicitly set
432 return $wgCapitalLinkOverrides[$index];
434 // Default to the global setting
435 return $wgCapitalLinks;
439 * Does the namespace (potentially) have different aliases for different
440 * genders. Not all languages make a distinction here.
443 * @param int $index Index to check
446 public static function hasGenderDistinction( $index ) {
447 return $index == NS_USER ||
$index == NS_USER_TALK
;
451 * It is not possible to use pages from this namespace as template?
454 * @param int $index Index to check
457 public static function isNonincludable( $index ) {
458 global $wgNonincludableNamespaces;
459 return $wgNonincludableNamespaces && in_array( $index, $wgNonincludableNamespaces );
463 * Get the default content model for a namespace
464 * This does not mean that all pages in that namespace have the model
466 * @note To determine the default model for a new page's main slot, or any slot in general,
467 * use SlotRoleHandler::getDefaultModel() together with SlotRoleRegistry::getRoleHandler().
470 * @param int $index Index to check
471 * @return null|string Default model name for the given namespace, if set
473 public static function getNamespaceContentModel( $index ) {
474 $config = MediaWikiServices
::getInstance()->getMainConfig();
475 $models = $config->get( 'NamespaceContentModels' );
476 return $models[$index] ??
null;
480 * Determine which restriction levels it makes sense to use in a namespace,
481 * optionally filtered by a user's rights.
484 * @param int $index Index to check
485 * @param User|null $user User to check
488 public static function getRestrictionLevels( $index, User
$user = null ) {
489 global $wgNamespaceProtection, $wgRestrictionLevels;
491 if ( !isset( $wgNamespaceProtection[$index] ) ) {
492 // All levels are valid if there's no namespace restriction.
493 // But still filter by user, if necessary
494 $levels = $wgRestrictionLevels;
496 $levels = array_values( array_filter( $levels, function ( $level ) use ( $user ) {
498 if ( $right == 'sysop' ) {
499 $right = 'editprotected'; // BC
501 if ( $right == 'autoconfirmed' ) {
502 $right = 'editsemiprotected'; // BC
504 return ( $right == '' ||
$user->isAllowed( $right ) );
510 // First, get the list of groups that can edit this namespace.
511 $namespaceGroups = [];
512 $combine = 'array_merge';
513 foreach ( (array)$wgNamespaceProtection[$index] as $right ) {
514 if ( $right == 'sysop' ) {
515 $right = 'editprotected'; // BC
517 if ( $right == 'autoconfirmed' ) {
518 $right = 'editsemiprotected'; // BC
520 if ( $right != '' ) {
521 $namespaceGroups = call_user_func( $combine, $namespaceGroups,
522 User
::getGroupsWithPermission( $right ) );
523 $combine = 'array_intersect';
527 // Now, keep only those restriction levels where there is at least one
528 // group that can edit the namespace but would be blocked by the
530 $usableLevels = [ '' ];
531 foreach ( $wgRestrictionLevels as $level ) {
533 if ( $right == 'sysop' ) {
534 $right = 'editprotected'; // BC
536 if ( $right == 'autoconfirmed' ) {
537 $right = 'editsemiprotected'; // BC
539 if ( $right != '' && ( !$user ||
$user->isAllowed( $right ) ) &&
540 array_diff( $namespaceGroups, User
::getGroupsWithPermission( $right ) )
542 $usableLevels[] = $level;
546 return $usableLevels;
550 * Returns the link type to be used for categories.
552 * This determines which section of a category page titles
553 * in the namespace will appear within.
556 * @param int $index Namespace index
557 * @return string One of 'subcat', 'file', 'page'
559 public static function getCategoryLinkType( $index ) {
560 self
::isMethodValidFor( $index, __METHOD__
);
562 if ( $index == NS_CATEGORY
) {
564 } elseif ( $index == NS_FILE
) {