From: Aryeh Gregor Date: Sun, 5 Aug 2018 08:36:32 +0000 (+0300) Subject: NamespaceInfo service to replace MWNamespace X-Git-Tag: 1.34.0-rc.0~2072^2 X-Git-Url: http://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=commitdiff_plain;h=76661cf129e0dea40edefbd7d35a3f09130572a1 NamespaceInfo service to replace MWNamespace MWNamespace::clearCaches() has been removed entirely, along with the $rebuild parameter to MWNamespace::getCanonicalNamespaces(). The rest of MWNamespace is deprecated. Diff best viewed with -C1 so git notices that NamespaceInfo is a copy of MWNamespace. Depends-On: Icb7a4a2a5d19fb1f2453b4b57a5271196b0e316d Depends-On: Ib3c914fc99394e4876ac9fe27317a1eafa2ff69e Change-Id: I1a03d4e146f5414ae73c7d1a5807c873323e8abc --- diff --git a/RELEASE-NOTES-1.33 b/RELEASE-NOTES-1.33 index 419560d050..80b0d2ed1a 100644 --- a/RELEASE-NOTES-1.33 +++ b/RELEASE-NOTES-1.33 @@ -37,6 +37,7 @@ Some specific notes for MediaWiki 1.33 upgrades are below: For notes on 1.32.x and older releases, see HISTORY. === Configuration changes for system administrators in 1.33 === + ==== New configuration ==== * $wgEnablePartialBlocks – This enables the Partial Blocks feature, which gives accounts with block permissions the ability to block users, IPs, and IP ranges @@ -114,6 +115,7 @@ For notes on 1.32.x and older releases, see HISTORY. associated with this entry in the patrol log. === External library changes in 1.33 === + ==== New external libraries ==== * Added wikimedia/password-blacklist 0.1.4. * Added guzzlehttp/guzzle 6.3.3. @@ -353,6 +355,13 @@ because of Phabricator reports. * MessageBlobStore::getBlob(), deprecated in 1.27, has been removed. Use ::getBlobs() instead. * The .background-size() LESS mixin, deprecated in 1.27, has been removed. +* MWNamespace::clearCaches() has been removed. So has the $rebuild parameter + to MWNamespace::getCanonicalNamespaces(), which was deprecated since 1.31. + Instead, create a new NamespaceInfo, such as by calling + resetServiceForTesting( 'NamespaceInfo' ) on a MediaWikiServices. + For classes that inherit from MediaWikiTestCase and used setMwGlobals() to + modify a variable that affects namespaces, caches will automatically be + reset and any calls to MWNamespace::clearCaches() can be removed entirely. === Deprecations in 1.33 === * The configuration option $wgUseESI has been deprecated, and is expected @@ -421,6 +430,7 @@ because of Phabricator reports. * Block::isValid is deprecated, since it is no longer needed in core. * Calling Maintenance::hasArg() as well as Maintenance::getArg() with no parameter has been deprecated. Please pass the argument number 0. +* The MWNamespace class is deprecated. Use MediaWikiServices::getNamespaceInfo. === Other changes in 1.33 === * (T201747) Html::openElement() warns if given an element name with a space diff --git a/autoload.php b/autoload.php index b22aeab527..5fda2171eb 100644 --- a/autoload.php +++ b/autoload.php @@ -1015,6 +1015,7 @@ $wgAutoloadLocalClasses = [ 'NamespaceAwareForeignTitleFactory' => __DIR__ . '/includes/title/NamespaceAwareForeignTitleFactory.php', 'NamespaceDupes' => __DIR__ . '/maintenance/namespaceDupes.php', 'NamespaceImportTitleFactory' => __DIR__ . '/includes/title/NamespaceImportTitleFactory.php', + 'NamespaceInfo' => __DIR__ . '/includes/title/NamespaceInfo.php', 'NewFilesPager' => __DIR__ . '/includes/specials/pagers/NewFilesPager.php', 'NewPagesPager' => __DIR__ . '/includes/specials/pagers/NewPagesPager.php', 'NewUsersLogFormatter' => __DIR__ . '/includes/logging/NewUsersLogFormatter.php', diff --git a/docs/hooks.txt b/docs/hooks.txt index 21e535c95b..d3d04ba78c 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -2333,7 +2333,7 @@ namespace. $index: Integer; the index of the namespace being checked. &$result: Boolean; whether MediaWiki currently thinks that pages in this namespace are movable. Hooks may change this value to override the return - value of MWNamespace::isMovable(). + value of NamespaceInfo::isMovable(). 'NewDifferenceEngine': Called when a new DifferenceEngine object is made $title: the diff page title (nullable) diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index cedba704e2..6f64e7f377 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -3992,13 +3992,12 @@ $wgRedirectSources = false; $wgCapitalLinks = true; /** - * @since 1.16 - This can now be set per-namespace. Some special namespaces (such - * as Special, see MWNamespace::$alwaysCapitalizedNamespaces for the full list) must be - * true by default (and setting them has no effect), due to various things that - * require them to be so. Also, since Talk namespaces need to directly mirror their - * associated content namespaces, the values for those are ignored in favor of the - * subject namespace's setting. Setting for NS_MEDIA is taken automatically from - * NS_FILE. + * @since 1.16 - This can now be set per-namespace. Some special namespaces (such as Special, see + * NamespaceInfo::$alwaysCapitalizedNamespaces for the full list) must be true by default (and + * setting them has no effect), due to various things that require them to be so. Also, since Talk + * namespaces need to directly mirror their associated content namespaces, the values for those are + * ignored in favor of the subject namespace's setting. Setting for NS_MEDIA is taken automatically + * from NS_FILE. * * @par Example: * @code diff --git a/includes/MWNamespace.php b/includes/MWNamespace.php index b40da00360..a36a12fcd3 100644 --- a/includes/MWNamespace.php +++ b/includes/MWNamespace.php @@ -22,64 +22,9 @@ use MediaWiki\MediaWikiServices; /** - * This is a utility class with only static functions - * for dealing with namespaces that encodes all the - * "magic" behaviors of them based on index. The textual - * names of the namespaces are handled by Language.php. - * - * These are synonyms for the names given in the language file - * Users and translators should not change them + * @deprecated since 1.33, use NamespaceInfo instead */ class MWNamespace { - - /** - * These namespaces should always be first-letter capitalized, now and - * forevermore. Historically, they could've probably been lowercased too, - * but some things are just too ingrained now. :) - */ - private static $alwaysCapitalizedNamespaces = [ NS_SPECIAL, NS_USER, NS_MEDIAWIKI ]; - - /** @var string[]|null Canonical namespaces cache */ - private static $canonicalNamespaces = null; - - /** @var array|false Canonical namespaces index cache */ - private static $namespaceIndexes = false; - - /** @var int[]|null Valid namespaces cache */ - private static $validNamespaces = null; - - /** - * Throw an exception when trying to get the subject or talk page - * for a given namespace where it does not make sense. - * Special namespaces are defined in includes/Defines.php and have - * a value below 0 (ex: NS_SPECIAL = -1 , NS_MEDIA = -2) - * - * @param int $index - * @param string $method - * - * @throws MWException - * @return bool - */ - private static function isMethodValidFor( $index, $method ) { - if ( $index < NS_MAIN ) { - throw new MWException( "$method does not make any sense for given namespace $index" ); - } - return true; - } - - /** - * Clear internal caches - * - * For use in unit testing when namespace configuration is changed. - * - * @since 1.31 - */ - public static function clearCaches() { - self::$canonicalNamespaces = null; - self::$namespaceIndexes = false; - self::$validNamespaces = null; - } - /** * Can pages in the given namespace be moved? * @@ -87,16 +32,7 @@ class MWNamespace { * @return bool */ public static function isMovable( $index ) { - global $wgAllowImageMoving; - - $result = !( $index < NS_MAIN || ( $index == NS_FILE && !$wgAllowImageMoving ) ); - - /** - * @since 1.20 - */ - Hooks::run( 'NamespaceIsMovable', [ $index, &$result ] ); - - return $result; + return MediaWikiServices::getInstance()->getNamespaceInfo()->isMovable( $index ); } /** @@ -107,7 +43,7 @@ class MWNamespace { * @since 1.19 */ public static function isSubject( $index ) { - return !self::isTalk( $index ); + return MediaWikiServices::getInstance()->getNamespaceInfo()->isSubject( $index ); } /** @@ -117,8 +53,7 @@ class MWNamespace { * @return bool */ public static function isTalk( $index ) { - return $index > NS_MAIN - && $index % 2; + return MediaWikiServices::getInstance()->getNamespaceInfo()->isTalk( $index ); } /** @@ -128,10 +63,7 @@ class MWNamespace { * @return int */ public static function getTalk( $index ) { - self::isMethodValidFor( $index, __METHOD__ ); - return self::isTalk( $index ) - ? $index - : $index + 1; + return MediaWikiServices::getInstance()->getNamespaceInfo()->getTalk( $index ); } /** @@ -142,14 +74,7 @@ class MWNamespace { * @return int */ public static function getSubject( $index ) { - # Handle special namespaces - if ( $index < NS_MAIN ) { - return $index; - } - - return self::isTalk( $index ) - ? $index - 1 - : $index; + return MediaWikiServices::getInstance()->getNamespaceInfo()->getSubject( $index ); } /** @@ -161,15 +86,7 @@ class MWNamespace { * @return int|null If no associated namespace could be found */ public static function getAssociated( $index ) { - self::isMethodValidFor( $index, __METHOD__ ); - - if ( self::isSubject( $index ) ) { - return self::getTalk( $index ); - } elseif ( self::isTalk( $index ) ) { - return self::getSubject( $index ); - } else { - return null; - } + return MediaWikiServices::getInstance()->getNamespaceInfo()->getAssociated( $index ); } /** @@ -181,8 +98,7 @@ class MWNamespace { * @since 1.19 */ public static function exists( $index ) { - $nslist = self::getCanonicalNamespaces(); - return isset( $nslist[$index] ); + return MediaWikiServices::getInstance()->getNamespaceInfo()->exists( $index ); } /** @@ -200,7 +116,7 @@ class MWNamespace { * @since 1.19 */ public static function equals( $ns1, $ns2 ) { - return $ns1 == $ns2; + return MediaWikiServices::getInstance()->getNamespaceInfo()->equals( $ns1, $ns2 ); } /** @@ -215,36 +131,19 @@ class MWNamespace { * @since 1.19 */ public static function subjectEquals( $ns1, $ns2 ) { - return self::getSubject( $ns1 ) == self::getSubject( $ns2 ); + return MediaWikiServices::getInstance()->getNamespaceInfo()-> + subjectEquals( $ns1, $ns2 ); } /** * Returns array of all defined namespaces with their canonical * (English) names. * - * @param bool $rebuild Rebuild namespace list (default = false). Used for testing. - * Deprecated since 1.31, use self::clearCaches() instead. - * * @return array * @since 1.17 */ - public static function getCanonicalNamespaces( $rebuild = false ) { - if ( $rebuild ) { - self::clearCaches(); - } - - if ( self::$canonicalNamespaces === null ) { - global $wgExtraNamespaces, $wgCanonicalNamespaceNames; - self::$canonicalNamespaces = [ NS_MAIN => '' ] + $wgCanonicalNamespaceNames; - // Add extension namespaces - self::$canonicalNamespaces += - ExtensionRegistry::getInstance()->getAttribute( 'ExtensionNamespaces' ); - if ( is_array( $wgExtraNamespaces ) ) { - self::$canonicalNamespaces += $wgExtraNamespaces; - } - Hooks::run( 'CanonicalNamespaces', [ &self::$canonicalNamespaces ] ); - } - return self::$canonicalNamespaces; + public static function getCanonicalNamespaces() { + return MediaWikiServices::getInstance()->getNamespaceInfo()->getCanonicalNamespaces(); } /** @@ -254,8 +153,7 @@ class MWNamespace { * @return string|bool If no canonical definition. */ public static function getCanonicalName( $index ) { - $nslist = self::getCanonicalNamespaces(); - return $nslist[$index] ?? false; + return MediaWikiServices::getInstance()->getNamespaceInfo()->getCanonicalName( $index ); } /** @@ -266,17 +164,7 @@ class MWNamespace { * @return int */ public static function getCanonicalIndex( $name ) { - if ( self::$namespaceIndexes === false ) { - self::$namespaceIndexes = []; - foreach ( self::getCanonicalNamespaces() as $i => $text ) { - self::$namespaceIndexes[strtolower( $text )] = $i; - } - } - if ( array_key_exists( $name, self::$namespaceIndexes ) ) { - return self::$namespaceIndexes[$name]; - } else { - return null; - } + return MediaWikiServices::getInstance()->getNamespaceInfo()->getCanonicalIndex( $name ); } /** @@ -285,17 +173,7 @@ class MWNamespace { * @return array */ public static function getValidNamespaces() { - if ( is_null( self::$validNamespaces ) ) { - foreach ( array_keys( self::getCanonicalNamespaces() ) as $ns ) { - if ( $ns >= 0 ) { - self::$validNamespaces[] = $ns; - } - } - // T109137: sort numerically - sort( self::$validNamespaces, SORT_NUMERIC ); - } - - return self::$validNamespaces; + return MediaWikiServices::getInstance()->getNamespaceInfo()->getValidNamespaces(); } /** @@ -320,7 +198,7 @@ class MWNamespace { * @return bool True if this namespace either is or has a corresponding talk namespace. */ public static function hasTalkNamespace( $index ) { - return $index >= NS_MAIN; + return MediaWikiServices::getInstance()->getNamespaceInfo()->hasTalkNamespace( $index ); } /** @@ -331,8 +209,7 @@ class MWNamespace { * @return bool */ public static function isContent( $index ) { - global $wgContentNamespaces; - return $index == NS_MAIN || in_array( $index, $wgContentNamespaces ); + return MediaWikiServices::getInstance()->getNamespaceInfo()->isContent( $index ); } /** @@ -343,8 +220,7 @@ class MWNamespace { * @return bool */ public static function wantSignatures( $index ) { - global $wgExtraSignatureNamespaces; - return self::isTalk( $index ) || in_array( $index, $wgExtraSignatureNamespaces ); + return MediaWikiServices::getInstance()->getNamespaceInfo()->wantSignatures( $index ); } /** @@ -354,7 +230,7 @@ class MWNamespace { * @return bool */ public static function isWatchable( $index ) { - return $index >= NS_MAIN; + return MediaWikiServices::getInstance()->getNamespaceInfo()->isWatchable( $index ); } /** @@ -364,8 +240,7 @@ class MWNamespace { * @return bool */ public static function hasSubpages( $index ) { - global $wgNamespacesWithSubpages; - return !empty( $wgNamespacesWithSubpages[$index] ); + return MediaWikiServices::getInstance()->getNamespaceInfo()->hasSubpages( $index ); } /** @@ -373,15 +248,7 @@ class MWNamespace { * @return array Array of namespace indices */ public static function getContentNamespaces() { - global $wgContentNamespaces; - if ( !is_array( $wgContentNamespaces ) || $wgContentNamespaces === [] ) { - return [ NS_MAIN ]; - } elseif ( !in_array( NS_MAIN, $wgContentNamespaces ) ) { - // always force NS_MAIN to be part of array (to match the algorithm used by isContent) - return array_merge( [ NS_MAIN ], $wgContentNamespaces ); - } else { - return $wgContentNamespaces; - } + return MediaWikiServices::getInstance()->getNamespaceInfo()->getContentNamespaces(); } /** @@ -391,10 +258,7 @@ class MWNamespace { * @return array Array of namespace indices */ public static function getSubjectNamespaces() { - return array_filter( - self::getValidNamespaces(), - 'MWNamespace::isSubject' - ); + return MediaWikiServices::getInstance()->getNamespaceInfo()->getSubjectNamespaces(); } /** @@ -404,10 +268,7 @@ class MWNamespace { * @return array Array of namespace indices */ public static function getTalkNamespaces() { - return array_filter( - self::getValidNamespaces(), - 'MWNamespace::isTalk' - ); + return MediaWikiServices::getInstance()->getNamespaceInfo()->getTalkNamespaces(); } /** @@ -417,23 +278,7 @@ class MWNamespace { * @return bool */ public static function isCapitalized( $index ) { - global $wgCapitalLinks, $wgCapitalLinkOverrides; - // Turn NS_MEDIA into NS_FILE - $index = $index === NS_MEDIA ? NS_FILE : $index; - - // Make sure to get the subject of our namespace - $index = self::getSubject( $index ); - - // Some namespaces are special and should always be upper case - if ( in_array( $index, self::$alwaysCapitalizedNamespaces ) ) { - return true; - } - if ( isset( $wgCapitalLinkOverrides[$index] ) ) { - // $wgCapitalLinkOverrides is explicitly set - return $wgCapitalLinkOverrides[$index]; - } - // Default to the global setting - return $wgCapitalLinks; + return MediaWikiServices::getInstance()->getNamespaceInfo()->isCapitalized( $index ); } /** @@ -445,7 +290,8 @@ class MWNamespace { * @return bool */ public static function hasGenderDistinction( $index ) { - return $index == NS_USER || $index == NS_USER_TALK; + return MediaWikiServices::getInstance()->getNamespaceInfo()-> + hasGenderDistinction( $index ); } /** @@ -456,8 +302,7 @@ class MWNamespace { * @return bool */ public static function isNonincludable( $index ) { - global $wgNonincludableNamespaces; - return $wgNonincludableNamespaces && in_array( $index, $wgNonincludableNamespaces ); + return MediaWikiServices::getInstance()->getNamespaceInfo()->isNonincludable( $index ); } /** @@ -472,9 +317,8 @@ class MWNamespace { * @return null|string Default model name for the given namespace, if set */ public static function getNamespaceContentModel( $index ) { - $config = MediaWikiServices::getInstance()->getMainConfig(); - $models = $config->get( 'NamespaceContentModels' ); - return $models[$index] ?? null; + return MediaWikiServices::getInstance()->getNamespaceInfo()-> + getNamespaceContentModel( $index ); } /** @@ -487,64 +331,8 @@ class MWNamespace { * @return array */ public static function getRestrictionLevels( $index, User $user = null ) { - global $wgNamespaceProtection, $wgRestrictionLevels; - - if ( !isset( $wgNamespaceProtection[$index] ) ) { - // All levels are valid if there's no namespace restriction. - // But still filter by user, if necessary - $levels = $wgRestrictionLevels; - if ( $user ) { - $levels = array_values( array_filter( $levels, function ( $level ) use ( $user ) { - $right = $level; - if ( $right == 'sysop' ) { - $right = 'editprotected'; // BC - } - if ( $right == 'autoconfirmed' ) { - $right = 'editsemiprotected'; // BC - } - return ( $right == '' || $user->isAllowed( $right ) ); - } ) ); - } - return $levels; - } - - // First, get the list of groups that can edit this namespace. - $namespaceGroups = []; - $combine = 'array_merge'; - foreach ( (array)$wgNamespaceProtection[$index] as $right ) { - if ( $right == 'sysop' ) { - $right = 'editprotected'; // BC - } - if ( $right == 'autoconfirmed' ) { - $right = 'editsemiprotected'; // BC - } - if ( $right != '' ) { - $namespaceGroups = call_user_func( $combine, $namespaceGroups, - User::getGroupsWithPermission( $right ) ); - $combine = 'array_intersect'; - } - } - - // Now, keep only those restriction levels where there is at least one - // group that can edit the namespace but would be blocked by the - // restriction. - $usableLevels = [ '' ]; - foreach ( $wgRestrictionLevels as $level ) { - $right = $level; - if ( $right == 'sysop' ) { - $right = 'editprotected'; // BC - } - if ( $right == 'autoconfirmed' ) { - $right = 'editsemiprotected'; // BC - } - if ( $right != '' && ( !$user || $user->isAllowed( $right ) ) && - array_diff( $namespaceGroups, User::getGroupsWithPermission( $right ) ) - ) { - $usableLevels[] = $level; - } - } - - return $usableLevels; + return MediaWikiServices::getInstance()->getNamespaceInfo()-> + getRestrictionLevels( $index, $user ); } /** @@ -558,14 +346,7 @@ class MWNamespace { * @return string One of 'subcat', 'file', 'page' */ public static function getCategoryLinkType( $index ) { - self::isMethodValidFor( $index, __METHOD__ ); - - if ( $index == NS_CATEGORY ) { - return 'subcat'; - } elseif ( $index == NS_FILE ) { - return 'file'; - } else { - return 'page'; - } + return MediaWikiServices::getInstance()->getNamespaceInfo()-> + getCategoryLinkType( $index ); } } diff --git a/includes/MediaWikiServices.php b/includes/MediaWikiServices.php index 6bf5d1d408..473cbe5b87 100644 --- a/includes/MediaWikiServices.php +++ b/includes/MediaWikiServices.php @@ -39,6 +39,7 @@ use MediaWiki\Linker\LinkRenderer; use MediaWiki\Linker\LinkRendererFactory; use MWException; use MimeAnalyzer; +use NamespaceInfo; use ObjectCache; use Parser; use ParserCache; @@ -676,6 +677,15 @@ class MediaWikiServices extends ServiceContainer { } /** + * @since 1.33 + * @return NamespaceInfo + */ + public function getNamespaceInfo() : NamespaceInfo { + return $this->getService( 'NamespaceInfo' ); + } + + /** + * @since 1.32 * @return OldRevisionImporter */ public function getOldRevisionImporter() { diff --git a/includes/MovePage.php b/includes/MovePage.php index 2edd669f13..24178acdad 100644 --- a/includes/MovePage.php +++ b/includes/MovePage.php @@ -272,7 +272,8 @@ class MovePage { [ 'cl_from' => $pageid ], __METHOD__ ); - $type = MWNamespace::getCategoryLinkType( $this->newTitle->getNamespace() ); + $type = MediaWikiServices::getInstance()->getNamespaceInfo()-> + getCategoryLinkType( $this->newTitle->getNamespace() ); foreach ( $prefixes as $prefixRow ) { $prefix = $prefixRow->cl_sortkey_prefix; $catTo = $prefixRow->cl_to; diff --git a/includes/ServiceWiring.php b/includes/ServiceWiring.php index a7e8c0bc68..750c964830 100644 --- a/includes/ServiceWiring.php +++ b/includes/ServiceWiring.php @@ -338,6 +338,10 @@ return [ ); }, + 'NamespaceInfo' => function ( MediaWikiServices $services ) : NamespaceInfo { + return new NamespaceInfo( $services->getMainConfig() ); + }, + 'OldRevisionImporter' => function ( MediaWikiServices $services ) : OldRevisionImporter { return new ImportableOldRevisionImporter( true, diff --git a/includes/deferred/LinksUpdate.php b/includes/deferred/LinksUpdate.php index 14f86b7c5e..9d3309bad5 100644 --- a/includes/deferred/LinksUpdate.php +++ b/includes/deferred/LinksUpdate.php @@ -615,7 +615,8 @@ class LinksUpdate extends DataUpdate implements EnqueueableDataUpdate { $nt = Title::makeTitleSafe( NS_CATEGORY, $name ); $contLang->findVariantLink( $name, $nt, true ); - $type = MWNamespace::getCategoryLinkType( $this->mTitle->getNamespace() ); + $type = MediaWikiServices::getInstance()->getNamespaceInfo()-> + getCategoryLinkType( $this->mTitle->getNamespace() ); # Treat custom sortkeys as a prefix, so that if multiple # things are forced to sort as '*' or something, they'll diff --git a/includes/page/WikiPage.php b/includes/page/WikiPage.php index 655fa2711f..96ed8ee470 100644 --- a/includes/page/WikiPage.php +++ b/includes/page/WikiPage.php @@ -3602,7 +3602,8 @@ class WikiPage implements Page, IDBAccessObject { */ public function updateCategoryCounts( array $added, array $deleted, $id = 0 ) { $id = $id ?: $this->getId(); - $type = MWNamespace::getCategoryLinkType( $this->getTitle()->getNamespace() ); + $type = MediaWikiServices::getInstance()->getNamespaceInfo()-> + getCategoryLinkType( $this->getTitle()->getNamespace() ); $addFields = [ 'cat_pages = cat_pages + 1' ]; $removeFields = [ 'cat_pages = cat_pages - 1' ]; diff --git a/includes/title/NamespaceInfo.php b/includes/title/NamespaceInfo.php new file mode 100644 index 0000000000..3726202d31 --- /dev/null +++ b/includes/title/NamespaceInfo.php @@ -0,0 +1,526 @@ +config = $config; + } + + /** + * Throw an exception when trying to get the subject or talk page + * for a given namespace where it does not make sense. + * Special namespaces are defined in includes/Defines.php and have + * a value below 0 (ex: NS_SPECIAL = -1 , NS_MEDIA = -2) + * + * @param int $index + * @param string $method + * + * @throws MWException + * @return bool + */ + private function isMethodValidFor( $index, $method ) { + if ( $index < NS_MAIN ) { + throw new MWException( "$method does not make any sense for given namespace $index" ); + } + return true; + } + + /** + * Can pages in the given namespace be moved? + * + * @param int $index Namespace index + * @return bool + */ + public function isMovable( $index ) { + $result = !( $index < NS_MAIN || + ( $index == NS_FILE && !$this->config->get( 'AllowImageMoving' ) ) ); + + /** + * @since 1.20 + */ + Hooks::run( 'NamespaceIsMovable', [ $index, &$result ] ); + + return $result; + } + + /** + * Is the given namespace is a subject (non-talk) namespace? + * + * @param int $index Namespace index + * @return bool + */ + public function isSubject( $index ) { + return !$this->isTalk( $index ); + } + + /** + * Is the given namespace a talk namespace? + * + * @param int $index Namespace index + * @return bool + */ + public function isTalk( $index ) { + return $index > NS_MAIN + && $index % 2; + } + + /** + * Get the talk namespace index for a given namespace + * + * @param int $index Namespace index + * @return int + */ + public function getTalk( $index ) { + $this->isMethodValidFor( $index, __METHOD__ ); + return $this->isTalk( $index ) + ? $index + : $index + 1; + } + + /** + * Get the subject namespace index for a given namespace + * Special namespaces (NS_MEDIA, NS_SPECIAL) are always the subject. + * + * @param int $index Namespace index + * @return int + */ + public function getSubject( $index ) { + # Handle special namespaces + if ( $index < NS_MAIN ) { + return $index; + } + + return $this->isTalk( $index ) + ? $index - 1 + : $index; + } + + /** + * Get the associated namespace. + * For talk namespaces, returns the subject (non-talk) namespace + * For subject (non-talk) namespaces, returns the talk namespace + * + * @param int $index Namespace index + * @return int|null If no associated namespace could be found + */ + public function getAssociated( $index ) { + $this->isMethodValidFor( $index, __METHOD__ ); + + if ( $this->isSubject( $index ) ) { + return $this->getTalk( $index ); + } elseif ( $this->isTalk( $index ) ) { + return $this->getSubject( $index ); + } else { + return null; + } + } + + /** + * Returns whether the specified namespace exists + * + * @param int $index + * + * @return bool + */ + public function exists( $index ) { + $nslist = $this->getCanonicalNamespaces(); + return isset( $nslist[$index] ); + } + + /** + * Returns whether the specified namespaces are the same namespace + * + * @note It's possible that in the future we may start using something + * other than just namespace indexes. Under that circumstance making use + * of this function rather than directly doing comparison will make + * sure that code will not potentially break. + * + * @param int $ns1 The first namespace index + * @param int $ns2 The second namespace index + * + * @return bool + */ + public function equals( $ns1, $ns2 ) { + return $ns1 == $ns2; + } + + /** + * Returns whether the specified namespaces share the same subject. + * eg: NS_USER and NS_USER wil return true, as well + * NS_USER and NS_USER_TALK will return true. + * + * @param int $ns1 The first namespace index + * @param int $ns2 The second namespace index + * + * @return bool + */ + public function subjectEquals( $ns1, $ns2 ) { + return $this->getSubject( $ns1 ) == $this->getSubject( $ns2 ); + } + + /** + * Returns array of all defined namespaces with their canonical + * (English) names. + * + * @return array + */ + public function getCanonicalNamespaces() { + if ( $this->canonicalNamespaces === null ) { + $this->canonicalNamespaces = + [ NS_MAIN => '' ] + $this->config->get( 'CanonicalNamespaceNames' ); + $this->canonicalNamespaces += + ExtensionRegistry::getInstance()->getAttribute( 'ExtensionNamespaces' ); + if ( is_array( $this->config->get( 'ExtraNamespaces' ) ) ) { + $this->canonicalNamespaces += $this->config->get( 'ExtraNamespaces' ); + } + Hooks::run( 'CanonicalNamespaces', [ &$this->canonicalNamespaces ] ); + } + return $this->canonicalNamespaces; + } + + /** + * Returns the canonical (English) name for a given index + * + * @param int $index Namespace index + * @return string|bool If no canonical definition. + */ + public function getCanonicalName( $index ) { + $nslist = $this->getCanonicalNamespaces(); + return $nslist[$index] ?? false; + } + + /** + * Returns the index for a given canonical name, or NULL + * The input *must* be converted to lower case first + * + * @param string $name Namespace name + * @return int + */ + public function getCanonicalIndex( $name ) { + if ( $this->namespaceIndexes === false ) { + $this->namespaceIndexes = []; + foreach ( $this->getCanonicalNamespaces() as $i => $text ) { + $this->namespaceIndexes[strtolower( $text )] = $i; + } + } + if ( array_key_exists( $name, $this->namespaceIndexes ) ) { + return $this->namespaceIndexes[$name]; + } else { + return null; + } + } + + /** + * Returns an array of the namespaces (by integer id) that exist on the + * wiki. Used primarily by the api in help documentation. + * @return array + */ + public function getValidNamespaces() { + if ( is_null( $this->validNamespaces ) ) { + foreach ( array_keys( $this->getCanonicalNamespaces() ) as $ns ) { + if ( $ns >= 0 ) { + $this->validNamespaces[] = $ns; + } + } + // T109137: sort numerically + sort( $this->validNamespaces, SORT_NUMERIC ); + } + + return $this->validNamespaces; + } + + /* + + /** + * Does this namespace ever have a talk namespace? + * + * @param int $index Namespace ID + * @return bool True if this namespace either is or has a corresponding talk namespace. + */ + public function hasTalkNamespace( $index ) { + return $index >= NS_MAIN; + } + + /** + * Does this namespace contain content, for the purposes of calculating + * statistics, etc? + * + * @param int $index Index to check + * @return bool + */ + public function isContent( $index ) { + return $index == NS_MAIN || in_array( $index, $this->config->get( 'ContentNamespaces' ) ); + } + + /** + * Might pages in this namespace require the use of the Signature button on + * the edit toolbar? + * + * @param int $index Index to check + * @return bool + */ + public function wantSignatures( $index ) { + return $this->isTalk( $index ) || + in_array( $index, $this->config->get( 'ExtraSignatureNamespaces' ) ); + } + + /** + * Can pages in a namespace be watched? + * + * @param int $index + * @return bool + */ + public function isWatchable( $index ) { + return $index >= NS_MAIN; + } + + /** + * Does the namespace allow subpages? + * + * @param int $index Index to check + * @return bool + */ + public function hasSubpages( $index ) { + return !empty( $this->config->get( 'NamespacesWithSubpages' )[$index] ); + } + + /** + * Get a list of all namespace indices which are considered to contain content + * @return array Array of namespace indices + */ + public function getContentNamespaces() { + $contentNamespaces = $this->config->get( 'ContentNamespaces' ); + if ( !is_array( $contentNamespaces ) || $contentNamespaces === [] ) { + return [ NS_MAIN ]; + } elseif ( !in_array( NS_MAIN, $contentNamespaces ) ) { + // always force NS_MAIN to be part of array (to match the algorithm used by isContent) + return array_merge( [ NS_MAIN ], $contentNamespaces ); + } else { + return $contentNamespaces; + } + } + + /** + * List all namespace indices which are considered subject, aka not a talk + * or special namespace. See also NamespaceInfo::isSubject + * + * @return array Array of namespace indices + */ + public function getSubjectNamespaces() { + return array_filter( + $this->getValidNamespaces(), + [ $this, 'isSubject' ] + ); + } + + /** + * List all namespace indices which are considered talks, aka not a subject + * or special namespace. See also NamespaceInfo::isTalk + * + * @return array Array of namespace indices + */ + public function getTalkNamespaces() { + return array_filter( + $this->getValidNamespaces(), + [ $this, 'isTalk' ] + ); + } + + /** + * Is the namespace first-letter capitalized? + * + * @param int $index Index to check + * @return bool + */ + public function isCapitalized( $index ) { + // Turn NS_MEDIA into NS_FILE + $index = $index === NS_MEDIA ? NS_FILE : $index; + + // Make sure to get the subject of our namespace + $index = $this->getSubject( $index ); + + // Some namespaces are special and should always be upper case + if ( in_array( $index, $this->alwaysCapitalizedNamespaces ) ) { + return true; + } + $overrides = $this->config->get( 'CapitalLinkOverrides' ); + if ( isset( $overrides[$index] ) ) { + // CapitalLinkOverrides is explicitly set + return $overrides[$index]; + } + // Default to the global setting + return $this->config->get( 'CapitalLinks' ); + } + + /** + * Does the namespace (potentially) have different aliases for different + * genders. Not all languages make a distinction here. + * + * @param int $index Index to check + * @return bool + */ + public function hasGenderDistinction( $index ) { + return $index == NS_USER || $index == NS_USER_TALK; + } + + /** + * It is not possible to use pages from this namespace as template? + * + * @param int $index Index to check + * @return bool + */ + public function isNonincludable( $index ) { + $namespaces = $this->config->get( 'NonincludableNamespaces' ); + return $namespaces && in_array( $index, $namespaces ); + } + + /** + * Get the default content model for a namespace + * This does not mean that all pages in that namespace have the model + * + * @note To determine the default model for a new page's main slot, or any slot in general, + * use SlotRoleHandler::getDefaultModel() together with SlotRoleRegistry::getRoleHandler(). + * + * @param int $index Index to check + * @return null|string Default model name for the given namespace, if set + */ + public function getNamespaceContentModel( $index ) { + return $this->config->get( 'NamespaceContentModels' )[$index] ?? null; + } + + /** + * Determine which restriction levels it makes sense to use in a namespace, + * optionally filtered by a user's rights. + * + * @param int $index Index to check + * @param User|null $user User to check + * @return array + */ + public function getRestrictionLevels( $index, User $user = null ) { + if ( !isset( $this->config->get( 'NamespaceProtection' )[$index] ) ) { + // All levels are valid if there's no namespace restriction. + // But still filter by user, if necessary + $levels = $this->config->get( 'RestrictionLevels' ); + if ( $user ) { + $levels = array_values( array_filter( $levels, function ( $level ) use ( $user ) { + $right = $level; + if ( $right == 'sysop' ) { + $right = 'editprotected'; // BC + } + if ( $right == 'autoconfirmed' ) { + $right = 'editsemiprotected'; // BC + } + return ( $right == '' || $user->isAllowed( $right ) ); + } ) ); + } + return $levels; + } + + // First, get the list of groups that can edit this namespace. + $namespaceGroups = []; + $combine = 'array_merge'; + foreach ( (array)$this->config->get( 'NamespaceProtection' )[$index] as $right ) { + if ( $right == 'sysop' ) { + $right = 'editprotected'; // BC + } + if ( $right == 'autoconfirmed' ) { + $right = 'editsemiprotected'; // BC + } + if ( $right != '' ) { + $namespaceGroups = call_user_func( $combine, $namespaceGroups, + User::getGroupsWithPermission( $right ) ); + $combine = 'array_intersect'; + } + } + + // Now, keep only those restriction levels where there is at least one + // group that can edit the namespace but would be blocked by the + // restriction. + $usableLevels = [ '' ]; + foreach ( $this->config->get( 'RestrictionLevels' ) as $level ) { + $right = $level; + if ( $right == 'sysop' ) { + $right = 'editprotected'; // BC + } + if ( $right == 'autoconfirmed' ) { + $right = 'editsemiprotected'; // BC + } + if ( $right != '' && ( !$user || $user->isAllowed( $right ) ) && + array_diff( $namespaceGroups, User::getGroupsWithPermission( $right ) ) + ) { + $usableLevels[] = $level; + } + } + + return $usableLevels; + } + + /** + * Returns the link type to be used for categories. + * + * This determines which section of a category page titles + * in the namespace will appear within. + * + * @param int $index Namespace index + * @return string One of 'subcat', 'file', 'page' + */ + public function getCategoryLinkType( $index ) { + $this->isMethodValidFor( $index, __METHOD__ ); + + if ( $index == NS_CATEGORY ) { + return 'subcat'; + } elseif ( $index == NS_FILE ) { + return 'file'; + } else { + return 'page'; + } + } +} diff --git a/maintenance/updateCollation.php b/maintenance/updateCollation.php index ab40e4863e..ebace75eab 100644 --- a/maintenance/updateCollation.php +++ b/maintenance/updateCollation.php @@ -26,6 +26,7 @@ require_once __DIR__ . '/Maintenance.php'; +use MediaWiki\MediaWikiServices; use Wikimedia\Rdbms\IDatabase; /** @@ -186,7 +187,8 @@ TEXT } # cl_type will be wrong for lots of pages if cl_collation is 0, # so let's update it while we're here. - $type = MWNamespace::getCategoryLinkType( $title->getNamespace() ); + $type = MediaWikiServices::getInstance()->getNamespaceInfo()-> + getCategoryLinkType( $title->getNamespace() ); $newSortKey = $collation->getSortKey( $title->getCategorySortkey( $prefix ) ); if ( $verboseStats ) { diff --git a/tests/parser/ParserTestRunner.php b/tests/parser/ParserTestRunner.php index 1c93261a86..b40b76927e 100644 --- a/tests/parser/ParserTestRunner.php +++ b/tests/parser/ParserTestRunner.php @@ -388,10 +388,10 @@ class ParserTestRunner { 100 => 'MemoryAlpha', 101 => 'MemoryAlpha_talk' ]; - // Changing wgExtraNamespaces invalidates caches in MWNamespace and - // any live Language object, both on setup and teardown + // Changing wgExtraNamespaces invalidates caches in NamespaceInfo and any live Language + // object, both on setup and teardown $reset = function () { - MWNamespace::clearCaches(); + MediaWikiServices::getInstance()->resetServiceForTesting( 'NamespaceInfo' ); MediaWikiServices::getInstance()->getContentLanguage()->resetNamespaces(); }; $setup[] = $reset; diff --git a/tests/phpunit/MediaWikiTestCase.php b/tests/phpunit/MediaWikiTestCase.php index 8a38f42b41..c35f5d6357 100644 --- a/tests/phpunit/MediaWikiTestCase.php +++ b/tests/phpunit/MediaWikiTestCase.php @@ -544,6 +544,24 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { $this->tmpFiles = array_merge( $this->tmpFiles, (array)$files ); } + // @todo Make const when we no longer support HHVM (T192166) + private static $namespaceAffectingSettings = [ + 'wgAllowImageMoving', + 'wgCanonicalNamespaceNames', + 'wgCapitalLinkOverrides', + 'wgCapitalLinks', + 'wgContentNamespaces', + 'wgExtensionMessagesFiles', + 'wgExtensionNamespaces', + 'wgExtraNamespaces', + 'wgExtraSignatureNamespaces', + 'wgNamespaceContentModels', + 'wgNamespaceProtection', + 'wgNamespacesWithSubpages', + 'wgNonincludableNamespaces', + 'wgRestrictionLevels', + ]; + protected function tearDown() { global $wgRequest, $wgSQLMode; @@ -588,8 +606,8 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { ini_set( $name, $value ); } if ( - array_key_exists( 'wgExtraNamespaces', $this->mwGlobals ) || - in_array( 'wgExtraNamespaces', $this->mwGlobalsToUnset ) + array_intersect( self::$namespaceAffectingSettings, array_keys( $this->mwGlobals ) ) || + array_intersect( self::$namespaceAffectingSettings, $this->mwGlobalsToUnset ) ) { $this->resetNamespaces(); } @@ -731,7 +749,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { $GLOBALS[$key] = $value; } - if ( array_key_exists( 'wgExtraNamespaces', $pairs ) ) { + if ( array_intersect( self::$namespaceAffectingSettings, array_keys( $pairs ) ) ) { $this->resetNamespaces(); } } @@ -762,14 +780,7 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase { . 'instance has been replaced by test code.' ); } - MWNamespace::clearCaches(); Language::clearCaches(); - - // We can't have the TitleFormatter holding on to an old Language object either - // @todo We shouldn't need to reset all the aliases here. - $this->localServices->resetServiceForTesting( 'TitleFormatter' ); - $this->localServices->resetServiceForTesting( 'TitleParser' ); - $this->localServices->resetServiceForTesting( '_MediaWikiTitleCodec' ); } /** diff --git a/tests/phpunit/includes/MWNamespaceTest.php b/tests/phpunit/includes/MWNamespaceTest.php deleted file mode 100644 index c95b1eb432..0000000000 --- a/tests/phpunit/includes/MWNamespaceTest.php +++ /dev/null @@ -1,609 +0,0 @@ -setMwGlobals( [ - 'wgContentNamespaces' => [ NS_MAIN ], - 'wgNamespacesWithSubpages' => [ - NS_TALK => true, - NS_USER => true, - NS_USER_TALK => true, - ], - 'wgCapitalLinks' => true, - 'wgCapitalLinkOverrides' => [], - 'wgNonincludableNamespaces' => [], - ] ); - } - - /** - * @todo Write more texts, handle $wgAllowImageMoving setting - * @covers MWNamespace::isMovable - */ - public function testIsMovable() { - $this->assertFalse( MWNamespace::isMovable( NS_SPECIAL ) ); - } - - private function assertIsSubject( $ns ) { - $this->assertTrue( MWNamespace::isSubject( $ns ) ); - } - - private function assertIsNotSubject( $ns ) { - $this->assertFalse( MWNamespace::isSubject( $ns ) ); - } - - /** - * Please make sure to change testIsTalk() if you change the assertions below - * @covers MWNamespace::isSubject - */ - public function testIsSubject() { - // Special namespaces - $this->assertIsSubject( NS_MEDIA ); - $this->assertIsSubject( NS_SPECIAL ); - - // Subject pages - $this->assertIsSubject( NS_MAIN ); - $this->assertIsSubject( NS_USER ); - $this->assertIsSubject( 100 ); # user defined - - // Talk pages - $this->assertIsNotSubject( NS_TALK ); - $this->assertIsNotSubject( NS_USER_TALK ); - $this->assertIsNotSubject( 101 ); # user defined - } - - private function assertIsTalk( $ns ) { - $this->assertTrue( MWNamespace::isTalk( $ns ) ); - } - - private function assertIsNotTalk( $ns ) { - $this->assertFalse( MWNamespace::isTalk( $ns ) ); - } - - /** - * Reverse of testIsSubject(). - * Please update testIsSubject() if you change assertions below - * @covers MWNamespace::isTalk - */ - public function testIsTalk() { - // Special namespaces - $this->assertIsNotTalk( NS_MEDIA ); - $this->assertIsNotTalk( NS_SPECIAL ); - - // Subject pages - $this->assertIsNotTalk( NS_MAIN ); - $this->assertIsNotTalk( NS_USER ); - $this->assertIsNotTalk( 100 ); # user defined - - // Talk pages - $this->assertIsTalk( NS_TALK ); - $this->assertIsTalk( NS_USER_TALK ); - $this->assertIsTalk( 101 ); # user defined - } - - /** - * @covers MWNamespace::getSubject - */ - public function testGetSubject() { - // Special namespaces are their own subjects - $this->assertEquals( NS_MEDIA, MWNamespace::getSubject( NS_MEDIA ) ); - $this->assertEquals( NS_SPECIAL, MWNamespace::getSubject( NS_SPECIAL ) ); - - $this->assertEquals( NS_MAIN, MWNamespace::getSubject( NS_TALK ) ); - $this->assertEquals( NS_USER, MWNamespace::getSubject( NS_USER_TALK ) ); - } - - /** - * Regular getTalk() calls - * Namespaces without a talk page (NS_MEDIA, NS_SPECIAL) are tested in - * the function testGetTalkExceptions() - * @covers MWNamespace::getTalk - */ - public function testGetTalk() { - $this->assertEquals( NS_TALK, MWNamespace::getTalk( NS_MAIN ) ); - $this->assertEquals( NS_TALK, MWNamespace::getTalk( NS_TALK ) ); - $this->assertEquals( NS_USER_TALK, MWNamespace::getTalk( NS_USER ) ); - $this->assertEquals( NS_USER_TALK, MWNamespace::getTalk( NS_USER_TALK ) ); - } - - /** - * Exceptions with getTalk() - * NS_MEDIA does not have talk pages. MediaWiki raise an exception for them. - * @expectedException MWException - * @covers MWNamespace::getTalk - */ - public function testGetTalkExceptionsForNsMedia() { - $this->assertNull( MWNamespace::getTalk( NS_MEDIA ) ); - } - - /** - * Exceptions with getTalk() - * NS_SPECIAL does not have talk pages. MediaWiki raise an exception for them. - * @expectedException MWException - * @covers MWNamespace::getTalk - */ - public function testGetTalkExceptionsForNsSpecial() { - $this->assertNull( MWNamespace::getTalk( NS_SPECIAL ) ); - } - - /** - * Regular getAssociated() calls - * Namespaces without an associated page (NS_MEDIA, NS_SPECIAL) are tested in - * the function testGetAssociatedExceptions() - * @covers MWNamespace::getAssociated - */ - public function testGetAssociated() { - $this->assertEquals( NS_TALK, MWNamespace::getAssociated( NS_MAIN ) ); - $this->assertEquals( NS_MAIN, MWNamespace::getAssociated( NS_TALK ) ); - } - - # ## Exceptions with getAssociated() - # ## NS_MEDIA and NS_SPECIAL do not have talk pages. MediaWiki raises - # ## an exception for them. - /** - * @expectedException MWException - * @covers MWNamespace::getAssociated - */ - public function testGetAssociatedExceptionsForNsMedia() { - $this->assertNull( MWNamespace::getAssociated( NS_MEDIA ) ); - } - - /** - * @expectedException MWException - * @covers MWNamespace::getAssociated - */ - public function testGetAssociatedExceptionsForNsSpecial() { - $this->assertNull( MWNamespace::getAssociated( NS_SPECIAL ) ); - } - - /** - * Test MWNamespace::equals - * Note if we add a namespace registration system with keys like 'MAIN' - * we should add tests here for equivilance on things like 'MAIN' == 0 - * and 'MAIN' == NS_MAIN. - * @covers MWNamespace::equals - */ - public function testEquals() { - $this->assertTrue( MWNamespace::equals( NS_MAIN, NS_MAIN ) ); - $this->assertTrue( MWNamespace::equals( NS_MAIN, 0 ) ); // In case we make NS_MAIN 'MAIN' - $this->assertTrue( MWNamespace::equals( NS_USER, NS_USER ) ); - $this->assertTrue( MWNamespace::equals( NS_USER, 2 ) ); - $this->assertTrue( MWNamespace::equals( NS_USER_TALK, NS_USER_TALK ) ); - $this->assertTrue( MWNamespace::equals( NS_SPECIAL, NS_SPECIAL ) ); - $this->assertFalse( MWNamespace::equals( NS_MAIN, NS_TALK ) ); - $this->assertFalse( MWNamespace::equals( NS_USER, NS_USER_TALK ) ); - $this->assertFalse( MWNamespace::equals( NS_PROJECT, NS_TEMPLATE ) ); - } - - /** - * @covers MWNamespace::subjectEquals - */ - public function testSubjectEquals() { - $this->assertSameSubject( NS_MAIN, NS_MAIN ); - $this->assertSameSubject( NS_MAIN, 0 ); // In case we make NS_MAIN 'MAIN' - $this->assertSameSubject( NS_USER, NS_USER ); - $this->assertSameSubject( NS_USER, 2 ); - $this->assertSameSubject( NS_USER_TALK, NS_USER_TALK ); - $this->assertSameSubject( NS_SPECIAL, NS_SPECIAL ); - $this->assertSameSubject( NS_MAIN, NS_TALK ); - $this->assertSameSubject( NS_USER, NS_USER_TALK ); - - $this->assertDifferentSubject( NS_PROJECT, NS_TEMPLATE ); - $this->assertDifferentSubject( NS_SPECIAL, NS_MAIN ); - } - - /** - * @covers MWNamespace::subjectEquals - */ - public function testSpecialAndMediaAreDifferentSubjects() { - $this->assertDifferentSubject( - NS_MEDIA, NS_SPECIAL, - "NS_MEDIA and NS_SPECIAL are different subject namespaces" - ); - $this->assertDifferentSubject( - NS_SPECIAL, NS_MEDIA, - "NS_SPECIAL and NS_MEDIA are different subject namespaces" - ); - } - - public function provideHasTalkNamespace() { - return [ - [ NS_MEDIA, false ], - [ NS_SPECIAL, false ], - - [ NS_MAIN, true ], - [ NS_TALK, true ], - [ NS_USER, true ], - [ NS_USER_TALK, true ], - - [ 100, true ], - [ 101, true ], - ]; - } - - /** - * @dataProvider provideHasTalkNamespace - * @covers MWNamespace::hasTalkNamespace - * - * @param int $index - * @param bool $expected - */ - public function testHasTalkNamespace( $index, $expected ) { - $actual = MWNamespace::hasTalkNamespace( $index ); - $this->assertSame( $actual, $expected, "NS $index" ); - } - - /** - * @dataProvider provideHasTalkNamespace - * @covers MWNamespace::canTalk - * - * @param int $index - * @param bool $expected - */ - public function testCanTalk( $index, $expected ) { - $this->hideDeprecated( 'MWNamespace::canTalk' ); - $actual = MWNamespace::canTalk( $index ); - $this->assertSame( $actual, $expected, "NS $index" ); - } - - private function assertIsContent( $ns ) { - $this->assertTrue( MWNamespace::isContent( $ns ) ); - } - - private function assertIsNotContent( $ns ) { - $this->assertFalse( MWNamespace::isContent( $ns ) ); - } - - /** - * @covers MWNamespace::isContent - */ - public function testIsContent() { - // NS_MAIN is a content namespace per DefaultSettings.php - // and per function definition. - - $this->assertIsContent( NS_MAIN ); - - // Other namespaces which are not expected to be content - - $this->assertIsNotContent( NS_MEDIA ); - $this->assertIsNotContent( NS_SPECIAL ); - $this->assertIsNotContent( NS_TALK ); - $this->assertIsNotContent( NS_USER ); - $this->assertIsNotContent( NS_CATEGORY ); - $this->assertIsNotContent( 100 ); - } - - /** - * Similar to testIsContent() but alters the $wgContentNamespaces - * global variable. - * @covers MWNamespace::isContent - */ - public function testIsContentAdvanced() { - global $wgContentNamespaces; - - // Test that user defined namespace #252 is not content - $this->assertIsNotContent( 252 ); - - // Bless namespace # 252 as a content namespace - $wgContentNamespaces[] = 252; - - $this->assertIsContent( 252 ); - - // Makes sure NS_MAIN was not impacted - $this->assertIsContent( NS_MAIN ); - } - - private function assertIsWatchable( $ns ) { - $this->assertTrue( MWNamespace::isWatchable( $ns ) ); - } - - private function assertIsNotWatchable( $ns ) { - $this->assertFalse( MWNamespace::isWatchable( $ns ) ); - } - - /** - * @covers MWNamespace::isWatchable - */ - public function testIsWatchable() { - // Specials namespaces are not watchable - $this->assertIsNotWatchable( NS_MEDIA ); - $this->assertIsNotWatchable( NS_SPECIAL ); - - // Core defined namespaces are watchables - $this->assertIsWatchable( NS_MAIN ); - $this->assertIsWatchable( NS_TALK ); - - // Additional, user defined namespaces are watchables - $this->assertIsWatchable( 100 ); - $this->assertIsWatchable( 101 ); - } - - private function assertHasSubpages( $ns ) { - $this->assertTrue( MWNamespace::hasSubpages( $ns ) ); - } - - private function assertHasNotSubpages( $ns ) { - $this->assertFalse( MWNamespace::hasSubpages( $ns ) ); - } - - /** - * @covers MWNamespace::hasSubpages - */ - public function testHasSubpages() { - global $wgNamespacesWithSubpages; - - // Special namespaces: - $this->assertHasNotSubpages( NS_MEDIA ); - $this->assertHasNotSubpages( NS_SPECIAL ); - - // Namespaces without subpages - $this->assertHasNotSubpages( NS_MAIN ); - - $wgNamespacesWithSubpages[NS_MAIN] = true; - $this->assertHasSubpages( NS_MAIN ); - - $wgNamespacesWithSubpages[NS_MAIN] = false; - $this->assertHasNotSubpages( NS_MAIN ); - - // Some namespaces with subpages - $this->assertHasSubpages( NS_TALK ); - $this->assertHasSubpages( NS_USER ); - $this->assertHasSubpages( NS_USER_TALK ); - } - - /** - * @covers MWNamespace::getContentNamespaces - */ - public function testGetContentNamespaces() { - global $wgContentNamespaces; - - $this->assertEquals( - [ NS_MAIN ], - MWNamespace::getContentNamespaces(), - '$wgContentNamespaces is an array with only NS_MAIN by default' - ); - - # test !is_array( $wgcontentNamespaces ) - $wgContentNamespaces = ''; - $this->assertEquals( [ NS_MAIN ], MWNamespace::getContentNamespaces() ); - - $wgContentNamespaces = false; - $this->assertEquals( [ NS_MAIN ], MWNamespace::getContentNamespaces() ); - - $wgContentNamespaces = null; - $this->assertEquals( [ NS_MAIN ], MWNamespace::getContentNamespaces() ); - - $wgContentNamespaces = 5; - $this->assertEquals( [ NS_MAIN ], MWNamespace::getContentNamespaces() ); - - # test $wgContentNamespaces === [] - $wgContentNamespaces = []; - $this->assertEquals( [ NS_MAIN ], MWNamespace::getContentNamespaces() ); - - # test !in_array( NS_MAIN, $wgContentNamespaces ) - $wgContentNamespaces = [ NS_USER, NS_CATEGORY ]; - $this->assertEquals( - [ NS_MAIN, NS_USER, NS_CATEGORY ], - MWNamespace::getContentNamespaces(), - 'NS_MAIN is forced in $wgContentNamespaces even if unwanted' - ); - - # test other cases, return $wgcontentNamespaces as is - $wgContentNamespaces = [ NS_MAIN ]; - $this->assertEquals( - [ NS_MAIN ], - MWNamespace::getContentNamespaces() - ); - - $wgContentNamespaces = [ NS_MAIN, NS_USER, NS_CATEGORY ]; - $this->assertEquals( - [ NS_MAIN, NS_USER, NS_CATEGORY ], - MWNamespace::getContentNamespaces() - ); - } - - /** - * @covers MWNamespace::getSubjectNamespaces - */ - public function testGetSubjectNamespaces() { - $subjectsNS = MWNamespace::getSubjectNamespaces(); - $this->assertContains( NS_MAIN, $subjectsNS, - "Talk namespaces should have NS_MAIN" ); - $this->assertNotContains( NS_TALK, $subjectsNS, - "Talk namespaces should have NS_TALK" ); - - $this->assertNotContains( NS_MEDIA, $subjectsNS, - "Talk namespaces should not have NS_MEDIA" ); - $this->assertNotContains( NS_SPECIAL, $subjectsNS, - "Talk namespaces should not have NS_SPECIAL" ); - } - - /** - * @covers MWNamespace::getTalkNamespaces - */ - public function testGetTalkNamespaces() { - $talkNS = MWNamespace::getTalkNamespaces(); - $this->assertContains( NS_TALK, $talkNS, - "Subject namespaces should have NS_TALK" ); - $this->assertNotContains( NS_MAIN, $talkNS, - "Subject namespaces should not have NS_MAIN" ); - - $this->assertNotContains( NS_MEDIA, $talkNS, - "Subject namespaces should not have NS_MEDIA" ); - $this->assertNotContains( NS_SPECIAL, $talkNS, - "Subject namespaces should not have NS_SPECIAL" ); - } - - private function assertIsCapitalized( $ns ) { - $this->assertTrue( MWNamespace::isCapitalized( $ns ) ); - } - - private function assertIsNotCapitalized( $ns ) { - $this->assertFalse( MWNamespace::isCapitalized( $ns ) ); - } - - /** - * Some namespaces are always capitalized per code definition - * in MWNamespace::$alwaysCapitalizedNamespaces - * @covers MWNamespace::isCapitalized - */ - public function testIsCapitalizedHardcodedAssertions() { - // NS_MEDIA and NS_FILE are treated the same - $this->assertEquals( - MWNamespace::isCapitalized( NS_MEDIA ), - MWNamespace::isCapitalized( NS_FILE ), - 'NS_MEDIA and NS_FILE have same capitalization rendering' - ); - - // Boths are capitalized by default - $this->assertIsCapitalized( NS_MEDIA ); - $this->assertIsCapitalized( NS_FILE ); - - // Always capitalized namespaces - // @see MWNamespace::$alwaysCapitalizedNamespaces - $this->assertIsCapitalized( NS_SPECIAL ); - $this->assertIsCapitalized( NS_USER ); - $this->assertIsCapitalized( NS_MEDIAWIKI ); - } - - /** - * Follows up for testIsCapitalizedHardcodedAssertions() but alter the - * global $wgCapitalLink setting to have extended coverage. - * - * MWNamespace::isCapitalized() rely on two global settings: - * $wgCapitalLinkOverrides = []; by default - * $wgCapitalLinks = true; by default - * This function test $wgCapitalLinks - * - * Global setting correctness is tested against the NS_PROJECT and - * NS_PROJECT_TALK namespaces since they are not hardcoded nor specials - * @covers MWNamespace::isCapitalized - */ - public function testIsCapitalizedWithWgCapitalLinks() { - global $wgCapitalLinks; - - $this->assertIsCapitalized( NS_PROJECT ); - $this->assertIsCapitalized( NS_PROJECT_TALK ); - - $wgCapitalLinks = false; - - // hardcoded namespaces (see above function) are still capitalized: - $this->assertIsCapitalized( NS_SPECIAL ); - $this->assertIsCapitalized( NS_USER ); - $this->assertIsCapitalized( NS_MEDIAWIKI ); - - // setting is correctly applied - $this->assertIsNotCapitalized( NS_PROJECT ); - $this->assertIsNotCapitalized( NS_PROJECT_TALK ); - } - - /** - * Counter part for MWNamespace::testIsCapitalizedWithWgCapitalLinks() now - * testing the $wgCapitalLinkOverrides global. - * - * @todo split groups of assertions in autonomous testing functions - * @covers MWNamespace::isCapitalized - */ - public function testIsCapitalizedWithWgCapitalLinkOverrides() { - global $wgCapitalLinkOverrides; - - // Test default settings - $this->assertIsCapitalized( NS_PROJECT ); - $this->assertIsCapitalized( NS_PROJECT_TALK ); - - // hardcoded namespaces (see above function) are capitalized: - $this->assertIsCapitalized( NS_SPECIAL ); - $this->assertIsCapitalized( NS_USER ); - $this->assertIsCapitalized( NS_MEDIAWIKI ); - - // Hardcoded namespaces remains capitalized - $wgCapitalLinkOverrides[NS_SPECIAL] = false; - $wgCapitalLinkOverrides[NS_USER] = false; - $wgCapitalLinkOverrides[NS_MEDIAWIKI] = false; - - $this->assertIsCapitalized( NS_SPECIAL ); - $this->assertIsCapitalized( NS_USER ); - $this->assertIsCapitalized( NS_MEDIAWIKI ); - - $wgCapitalLinkOverrides[NS_PROJECT] = false; - $this->assertIsNotCapitalized( NS_PROJECT ); - - $wgCapitalLinkOverrides[NS_PROJECT] = true; - $this->assertIsCapitalized( NS_PROJECT ); - - unset( $wgCapitalLinkOverrides[NS_PROJECT] ); - $this->assertIsCapitalized( NS_PROJECT ); - } - - /** - * @covers MWNamespace::hasGenderDistinction - */ - public function testHasGenderDistinction() { - // Namespaces with gender distinctions - $this->assertTrue( MWNamespace::hasGenderDistinction( NS_USER ) ); - $this->assertTrue( MWNamespace::hasGenderDistinction( NS_USER_TALK ) ); - - // Other ones, "genderless" - $this->assertFalse( MWNamespace::hasGenderDistinction( NS_MEDIA ) ); - $this->assertFalse( MWNamespace::hasGenderDistinction( NS_SPECIAL ) ); - $this->assertFalse( MWNamespace::hasGenderDistinction( NS_MAIN ) ); - $this->assertFalse( MWNamespace::hasGenderDistinction( NS_TALK ) ); - } - - /** - * @covers MWNamespace::isNonincludable - */ - public function testIsNonincludable() { - global $wgNonincludableNamespaces; - - $wgNonincludableNamespaces = [ NS_USER ]; - - $this->assertTrue( MWNamespace::isNonincludable( NS_USER ) ); - $this->assertFalse( MWNamespace::isNonincludable( NS_TEMPLATE ) ); - } - - private function assertSameSubject( $ns1, $ns2, $msg = '' ) { - $this->assertTrue( MWNamespace::subjectEquals( $ns1, $ns2 ), $msg ); - } - - private function assertDifferentSubject( $ns1, $ns2, $msg = '' ) { - $this->assertFalse( MWNamespace::subjectEquals( $ns1, $ns2 ), $msg ); - } - - public function provideGetCategoryLinkType() { - return [ - [ NS_MAIN, 'page' ], - [ NS_TALK, 'page' ], - [ NS_USER, 'page' ], - [ NS_USER_TALK, 'page' ], - - [ NS_FILE, 'file' ], - [ NS_FILE_TALK, 'page' ], - - [ NS_CATEGORY, 'subcat' ], - [ NS_CATEGORY_TALK, 'page' ], - - [ 100, 'page' ], - [ 101, 'page' ], - ]; - } - - /** - * @dataProvider provideGetCategoryLinkType - * @covers MWNamespace::getCategoryLinkType - * - * @param int $index - * @param string $expected - */ - public function testGetCategoryLinkType( $index, $expected ) { - $actual = MWNamespace::getCategoryLinkType( $index ); - $this->assertSame( $expected, $actual, "NS $index" ); - } -} diff --git a/tests/phpunit/includes/title/NamespaceInfoTest.php b/tests/phpunit/includes/title/NamespaceInfoTest.php new file mode 100644 index 0000000000..cc7df8d31f --- /dev/null +++ b/tests/phpunit/includes/title/NamespaceInfoTest.php @@ -0,0 +1,613 @@ +setMwGlobals( [ + 'wgContentNamespaces' => [ NS_MAIN ], + 'wgNamespacesWithSubpages' => [ + NS_TALK => true, + NS_USER => true, + NS_USER_TALK => true, + ], + 'wgCapitalLinks' => true, + 'wgCapitalLinkOverrides' => [], + 'wgNonincludableNamespaces' => [], + ] ); + + $this->obj = MediaWikiServices::getInstance()->getNamespaceInfo(); + } + + /** + * @todo Write more texts, handle $wgAllowImageMoving setting + * @covers NamespaceInfo::isMovable + */ + public function testIsMovable() { + $this->assertFalse( $this->obj->isMovable( NS_SPECIAL ) ); + } + + private function assertIsSubject( $ns ) { + $this->assertTrue( $this->obj->isSubject( $ns ) ); + } + + private function assertIsNotSubject( $ns ) { + $this->assertFalse( $this->obj->isSubject( $ns ) ); + } + + /** + * Please make sure to change testIsTalk() if you change the assertions below + * @covers NamespaceInfo::isSubject + */ + public function testIsSubject() { + // Special namespaces + $this->assertIsSubject( NS_MEDIA ); + $this->assertIsSubject( NS_SPECIAL ); + + // Subject pages + $this->assertIsSubject( NS_MAIN ); + $this->assertIsSubject( NS_USER ); + $this->assertIsSubject( 100 ); # user defined + + // Talk pages + $this->assertIsNotSubject( NS_TALK ); + $this->assertIsNotSubject( NS_USER_TALK ); + $this->assertIsNotSubject( 101 ); # user defined + } + + private function assertIsTalk( $ns ) { + $this->assertTrue( $this->obj->isTalk( $ns ) ); + } + + private function assertIsNotTalk( $ns ) { + $this->assertFalse( $this->obj->isTalk( $ns ) ); + } + + /** + * Reverse of testIsSubject(). + * Please update testIsSubject() if you change assertions below + * @covers NamespaceInfo::isTalk + */ + public function testIsTalk() { + // Special namespaces + $this->assertIsNotTalk( NS_MEDIA ); + $this->assertIsNotTalk( NS_SPECIAL ); + + // Subject pages + $this->assertIsNotTalk( NS_MAIN ); + $this->assertIsNotTalk( NS_USER ); + $this->assertIsNotTalk( 100 ); # user defined + + // Talk pages + $this->assertIsTalk( NS_TALK ); + $this->assertIsTalk( NS_USER_TALK ); + $this->assertIsTalk( 101 ); # user defined + } + + /** + * @covers NamespaceInfo::getSubject + */ + public function testGetSubject() { + // Special namespaces are their own subjects + $this->assertEquals( NS_MEDIA, $this->obj->getSubject( NS_MEDIA ) ); + $this->assertEquals( NS_SPECIAL, $this->obj->getSubject( NS_SPECIAL ) ); + + $this->assertEquals( NS_MAIN, $this->obj->getSubject( NS_TALK ) ); + $this->assertEquals( NS_USER, $this->obj->getSubject( NS_USER_TALK ) ); + } + + /** + * Regular getTalk() calls + * Namespaces without a talk page (NS_MEDIA, NS_SPECIAL) are tested in + * the function testGetTalkExceptions() + * @covers NamespaceInfo::getTalk + */ + public function testGetTalk() { + $this->assertEquals( NS_TALK, $this->obj->getTalk( NS_MAIN ) ); + $this->assertEquals( NS_TALK, $this->obj->getTalk( NS_TALK ) ); + $this->assertEquals( NS_USER_TALK, $this->obj->getTalk( NS_USER ) ); + $this->assertEquals( NS_USER_TALK, $this->obj->getTalk( NS_USER_TALK ) ); + } + + /** + * Exceptions with getTalk() + * NS_MEDIA does not have talk pages. MediaWiki raise an exception for them. + * @expectedException MWException + * @covers NamespaceInfo::getTalk + */ + public function testGetTalkExceptionsForNsMedia() { + $this->assertNull( $this->obj->getTalk( NS_MEDIA ) ); + } + + /** + * Exceptions with getTalk() + * NS_SPECIAL does not have talk pages. MediaWiki raise an exception for them. + * @expectedException MWException + * @covers NamespaceInfo::getTalk + */ + public function testGetTalkExceptionsForNsSpecial() { + $this->assertNull( $this->obj->getTalk( NS_SPECIAL ) ); + } + + /** + * Regular getAssociated() calls + * Namespaces without an associated page (NS_MEDIA, NS_SPECIAL) are tested in + * the function testGetAssociatedExceptions() + * @covers NamespaceInfo::getAssociated + */ + public function testGetAssociated() { + $this->assertEquals( NS_TALK, $this->obj->getAssociated( NS_MAIN ) ); + $this->assertEquals( NS_MAIN, $this->obj->getAssociated( NS_TALK ) ); + } + + # ## Exceptions with getAssociated() + # ## NS_MEDIA and NS_SPECIAL do not have talk pages. MediaWiki raises + # ## an exception for them. + /** + * @expectedException MWException + * @covers NamespaceInfo::getAssociated + */ + public function testGetAssociatedExceptionsForNsMedia() { + $this->assertNull( $this->obj->getAssociated( NS_MEDIA ) ); + } + + /** + * @expectedException MWException + * @covers NamespaceInfo::getAssociated + */ + public function testGetAssociatedExceptionsForNsSpecial() { + $this->assertNull( $this->obj->getAssociated( NS_SPECIAL ) ); + } + + /** + * Note if we add a namespace registration system with keys like 'MAIN' + * we should add tests here for equivilance on things like 'MAIN' == 0 + * and 'MAIN' == NS_MAIN. + * @covers NamespaceInfo::equals + */ + public function testEquals() { + $this->assertTrue( $this->obj->equals( NS_MAIN, NS_MAIN ) ); + $this->assertTrue( $this->obj->equals( NS_MAIN, 0 ) ); // In case we make NS_MAIN 'MAIN' + $this->assertTrue( $this->obj->equals( NS_USER, NS_USER ) ); + $this->assertTrue( $this->obj->equals( NS_USER, 2 ) ); + $this->assertTrue( $this->obj->equals( NS_USER_TALK, NS_USER_TALK ) ); + $this->assertTrue( $this->obj->equals( NS_SPECIAL, NS_SPECIAL ) ); + $this->assertFalse( $this->obj->equals( NS_MAIN, NS_TALK ) ); + $this->assertFalse( $this->obj->equals( NS_USER, NS_USER_TALK ) ); + $this->assertFalse( $this->obj->equals( NS_PROJECT, NS_TEMPLATE ) ); + } + + /** + * @covers NamespaceInfo::subjectEquals + */ + public function testSubjectEquals() { + $this->assertSameSubject( NS_MAIN, NS_MAIN ); + $this->assertSameSubject( NS_MAIN, 0 ); // In case we make NS_MAIN 'MAIN' + $this->assertSameSubject( NS_USER, NS_USER ); + $this->assertSameSubject( NS_USER, 2 ); + $this->assertSameSubject( NS_USER_TALK, NS_USER_TALK ); + $this->assertSameSubject( NS_SPECIAL, NS_SPECIAL ); + $this->assertSameSubject( NS_MAIN, NS_TALK ); + $this->assertSameSubject( NS_USER, NS_USER_TALK ); + + $this->assertDifferentSubject( NS_PROJECT, NS_TEMPLATE ); + $this->assertDifferentSubject( NS_SPECIAL, NS_MAIN ); + } + + /** + * @covers NamespaceInfo::subjectEquals + */ + public function testSpecialAndMediaAreDifferentSubjects() { + $this->assertDifferentSubject( + NS_MEDIA, NS_SPECIAL, + "NS_MEDIA and NS_SPECIAL are different subject namespaces" + ); + $this->assertDifferentSubject( + NS_SPECIAL, NS_MEDIA, + "NS_SPECIAL and NS_MEDIA are different subject namespaces" + ); + } + + public function provideHasTalkNamespace() { + return [ + [ NS_MEDIA, false ], + [ NS_SPECIAL, false ], + + [ NS_MAIN, true ], + [ NS_TALK, true ], + [ NS_USER, true ], + [ NS_USER_TALK, true ], + + [ 100, true ], + [ 101, true ], + ]; + } + + /** + * @dataProvider provideHasTalkNamespace + * @covers NamespaceInfo::hasTalkNamespace + * + * @param int $index + * @param bool $expected + */ + public function testHasTalkNamespace( $index, $expected ) { + $actual = $this->obj->hasTalkNamespace( $index ); + $this->assertSame( $actual, $expected, "NS $index" ); + } + + /** + * @dataProvider provideHasTalkNamespace + * @covers MWNamespace::canTalk + * + * @param int $index + * @param bool $expected + */ + public function testCanTalk( $index, $expected ) { + $this->hideDeprecated( 'MWNamespace::canTalk' ); + $actual = MWNamespace::canTalk( $index ); + $this->assertSame( $actual, $expected, "NS $index" ); + } + + private function assertIsContent( $ns ) { + $this->assertTrue( $this->obj->isContent( $ns ) ); + } + + private function assertIsNotContent( $ns ) { + $this->assertFalse( $this->obj->isContent( $ns ) ); + } + + /** + * @covers NamespaceInfo::isContent + */ + public function testIsContent() { + // NS_MAIN is a content namespace per DefaultSettings.php + // and per function definition. + + $this->assertIsContent( NS_MAIN ); + + // Other namespaces which are not expected to be content + + $this->assertIsNotContent( NS_MEDIA ); + $this->assertIsNotContent( NS_SPECIAL ); + $this->assertIsNotContent( NS_TALK ); + $this->assertIsNotContent( NS_USER ); + $this->assertIsNotContent( NS_CATEGORY ); + $this->assertIsNotContent( 100 ); + } + + /** + * Similar to testIsContent() but alters the $wgContentNamespaces + * global variable. + * @covers NamespaceInfo::isContent + */ + public function testIsContentAdvanced() { + global $wgContentNamespaces; + + // Test that user defined namespace #252 is not content + $this->assertIsNotContent( 252 ); + + // Bless namespace # 252 as a content namespace + $wgContentNamespaces[] = 252; + + $this->assertIsContent( 252 ); + + // Makes sure NS_MAIN was not impacted + $this->assertIsContent( NS_MAIN ); + } + + private function assertIsWatchable( $ns ) { + $this->assertTrue( $this->obj->isWatchable( $ns ) ); + } + + private function assertIsNotWatchable( $ns ) { + $this->assertFalse( $this->obj->isWatchable( $ns ) ); + } + + /** + * @covers NamespaceInfo::isWatchable + */ + public function testIsWatchable() { + // Specials namespaces are not watchable + $this->assertIsNotWatchable( NS_MEDIA ); + $this->assertIsNotWatchable( NS_SPECIAL ); + + // Core defined namespaces are watchables + $this->assertIsWatchable( NS_MAIN ); + $this->assertIsWatchable( NS_TALK ); + + // Additional, user defined namespaces are watchables + $this->assertIsWatchable( 100 ); + $this->assertIsWatchable( 101 ); + } + + private function assertHasSubpages( $ns ) { + $this->assertTrue( $this->obj->hasSubpages( $ns ) ); + } + + private function assertHasNotSubpages( $ns ) { + $this->assertFalse( $this->obj->hasSubpages( $ns ) ); + } + + /** + * @covers NamespaceInfo::hasSubpages + */ + public function testHasSubpages() { + global $wgNamespacesWithSubpages; + + // Special namespaces: + $this->assertHasNotSubpages( NS_MEDIA ); + $this->assertHasNotSubpages( NS_SPECIAL ); + + // Namespaces without subpages + $this->assertHasNotSubpages( NS_MAIN ); + + $wgNamespacesWithSubpages[NS_MAIN] = true; + $this->assertHasSubpages( NS_MAIN ); + + $wgNamespacesWithSubpages[NS_MAIN] = false; + $this->assertHasNotSubpages( NS_MAIN ); + + // Some namespaces with subpages + $this->assertHasSubpages( NS_TALK ); + $this->assertHasSubpages( NS_USER ); + $this->assertHasSubpages( NS_USER_TALK ); + } + + /** + * @covers NamespaceInfo::getContentNamespaces + */ + public function testGetContentNamespaces() { + global $wgContentNamespaces; + + $this->assertEquals( + [ NS_MAIN ], + $this->obj->getContentNamespaces(), + '$wgContentNamespaces is an array with only NS_MAIN by default' + ); + + # test !is_array( $wgcontentNamespaces ) + $wgContentNamespaces = ''; + $this->assertEquals( [ NS_MAIN ], $this->obj->getContentNamespaces() ); + + $wgContentNamespaces = false; + $this->assertEquals( [ NS_MAIN ], $this->obj->getContentNamespaces() ); + + $wgContentNamespaces = null; + $this->assertEquals( [ NS_MAIN ], $this->obj->getContentNamespaces() ); + + $wgContentNamespaces = 5; + $this->assertEquals( [ NS_MAIN ], $this->obj->getContentNamespaces() ); + + # test $wgContentNamespaces === [] + $wgContentNamespaces = []; + $this->assertEquals( [ NS_MAIN ], $this->obj->getContentNamespaces() ); + + # test !in_array( NS_MAIN, $wgContentNamespaces ) + $wgContentNamespaces = [ NS_USER, NS_CATEGORY ]; + $this->assertEquals( + [ NS_MAIN, NS_USER, NS_CATEGORY ], + $this->obj->getContentNamespaces(), + 'NS_MAIN is forced in $wgContentNamespaces even if unwanted' + ); + + # test other cases, return $wgcontentNamespaces as is + $wgContentNamespaces = [ NS_MAIN ]; + $this->assertEquals( + [ NS_MAIN ], + $this->obj->getContentNamespaces() + ); + + $wgContentNamespaces = [ NS_MAIN, NS_USER, NS_CATEGORY ]; + $this->assertEquals( + [ NS_MAIN, NS_USER, NS_CATEGORY ], + $this->obj->getContentNamespaces() + ); + } + + /** + * @covers NamespaceInfo::getSubjectNamespaces + */ + public function testGetSubjectNamespaces() { + $subjectsNS = $this->obj->getSubjectNamespaces(); + $this->assertContains( NS_MAIN, $subjectsNS, + "Talk namespaces should have NS_MAIN" ); + $this->assertNotContains( NS_TALK, $subjectsNS, + "Talk namespaces should have NS_TALK" ); + + $this->assertNotContains( NS_MEDIA, $subjectsNS, + "Talk namespaces should not have NS_MEDIA" ); + $this->assertNotContains( NS_SPECIAL, $subjectsNS, + "Talk namespaces should not have NS_SPECIAL" ); + } + + /** + * @covers NamespaceInfo::getTalkNamespaces + */ + public function testGetTalkNamespaces() { + $talkNS = $this->obj->getTalkNamespaces(); + $this->assertContains( NS_TALK, $talkNS, + "Subject namespaces should have NS_TALK" ); + $this->assertNotContains( NS_MAIN, $talkNS, + "Subject namespaces should not have NS_MAIN" ); + + $this->assertNotContains( NS_MEDIA, $talkNS, + "Subject namespaces should not have NS_MEDIA" ); + $this->assertNotContains( NS_SPECIAL, $talkNS, + "Subject namespaces should not have NS_SPECIAL" ); + } + + private function assertIsCapitalized( $ns ) { + $this->assertTrue( $this->obj->isCapitalized( $ns ) ); + } + + private function assertIsNotCapitalized( $ns ) { + $this->assertFalse( $this->obj->isCapitalized( $ns ) ); + } + + /** + * Some namespaces are always capitalized per code definition + * in NamespaceInfo::$alwaysCapitalizedNamespaces + * @covers NamespaceInfo::isCapitalized + */ + public function testIsCapitalizedHardcodedAssertions() { + // NS_MEDIA and NS_FILE are treated the same + $this->assertEquals( + $this->obj->isCapitalized( NS_MEDIA ), + $this->obj->isCapitalized( NS_FILE ), + 'NS_MEDIA and NS_FILE have same capitalization rendering' + ); + + // Boths are capitalized by default + $this->assertIsCapitalized( NS_MEDIA ); + $this->assertIsCapitalized( NS_FILE ); + + // Always capitalized namespaces + // @see NamespaceInfo::$alwaysCapitalizedNamespaces + $this->assertIsCapitalized( NS_SPECIAL ); + $this->assertIsCapitalized( NS_USER ); + $this->assertIsCapitalized( NS_MEDIAWIKI ); + } + + /** + * Follows up for testIsCapitalizedHardcodedAssertions() but alter the + * global $wgCapitalLink setting to have extended coverage. + * + * NamespaceInfo::isCapitalized() rely on two global settings: + * $wgCapitalLinkOverrides = []; by default + * $wgCapitalLinks = true; by default + * This function test $wgCapitalLinks + * + * Global setting correctness is tested against the NS_PROJECT and + * NS_PROJECT_TALK namespaces since they are not hardcoded nor specials + * @covers NamespaceInfo::isCapitalized + */ + public function testIsCapitalizedWithWgCapitalLinks() { + $this->assertIsCapitalized( NS_PROJECT ); + $this->assertIsCapitalized( NS_PROJECT_TALK ); + + $this->setMwGlobals( 'wgCapitalLinks', false ); + + // hardcoded namespaces (see above function) are still capitalized: + $this->assertIsCapitalized( NS_SPECIAL ); + $this->assertIsCapitalized( NS_USER ); + $this->assertIsCapitalized( NS_MEDIAWIKI ); + + // setting is correctly applied + $this->assertIsNotCapitalized( NS_PROJECT ); + $this->assertIsNotCapitalized( NS_PROJECT_TALK ); + } + + /** + * Counter part for NamespaceInfo::testIsCapitalizedWithWgCapitalLinks() now + * testing the $wgCapitalLinkOverrides global. + * + * @todo split groups of assertions in autonomous testing functions + * @covers NamespaceInfo::isCapitalized + */ + public function testIsCapitalizedWithWgCapitalLinkOverrides() { + global $wgCapitalLinkOverrides; + + // Test default settings + $this->assertIsCapitalized( NS_PROJECT ); + $this->assertIsCapitalized( NS_PROJECT_TALK ); + + // hardcoded namespaces (see above function) are capitalized: + $this->assertIsCapitalized( NS_SPECIAL ); + $this->assertIsCapitalized( NS_USER ); + $this->assertIsCapitalized( NS_MEDIAWIKI ); + + // Hardcoded namespaces remains capitalized + $wgCapitalLinkOverrides[NS_SPECIAL] = false; + $wgCapitalLinkOverrides[NS_USER] = false; + $wgCapitalLinkOverrides[NS_MEDIAWIKI] = false; + + $this->assertIsCapitalized( NS_SPECIAL ); + $this->assertIsCapitalized( NS_USER ); + $this->assertIsCapitalized( NS_MEDIAWIKI ); + + $wgCapitalLinkOverrides[NS_PROJECT] = false; + $this->assertIsNotCapitalized( NS_PROJECT ); + + $wgCapitalLinkOverrides[NS_PROJECT] = true; + $this->assertIsCapitalized( NS_PROJECT ); + + unset( $wgCapitalLinkOverrides[NS_PROJECT] ); + $this->assertIsCapitalized( NS_PROJECT ); + } + + /** + * @covers NamespaceInfo::hasGenderDistinction + */ + public function testHasGenderDistinction() { + // Namespaces with gender distinctions + $this->assertTrue( $this->obj->hasGenderDistinction( NS_USER ) ); + $this->assertTrue( $this->obj->hasGenderDistinction( NS_USER_TALK ) ); + + // Other ones, "genderless" + $this->assertFalse( $this->obj->hasGenderDistinction( NS_MEDIA ) ); + $this->assertFalse( $this->obj->hasGenderDistinction( NS_SPECIAL ) ); + $this->assertFalse( $this->obj->hasGenderDistinction( NS_MAIN ) ); + $this->assertFalse( $this->obj->hasGenderDistinction( NS_TALK ) ); + } + + /** + * @covers NamespaceInfo::isNonincludable + */ + public function testIsNonincludable() { + global $wgNonincludableNamespaces; + + $wgNonincludableNamespaces = [ NS_USER ]; + + $this->assertTrue( $this->obj->isNonincludable( NS_USER ) ); + $this->assertFalse( $this->obj->isNonincludable( NS_TEMPLATE ) ); + } + + private function assertSameSubject( $ns1, $ns2, $msg = '' ) { + $this->assertTrue( $this->obj->subjectEquals( $ns1, $ns2 ), $msg ); + } + + private function assertDifferentSubject( $ns1, $ns2, $msg = '' ) { + $this->assertFalse( $this->obj->subjectEquals( $ns1, $ns2 ), $msg ); + } + + public function provideGetCategoryLinkType() { + return [ + [ NS_MAIN, 'page' ], + [ NS_TALK, 'page' ], + [ NS_USER, 'page' ], + [ NS_USER_TALK, 'page' ], + + [ NS_FILE, 'file' ], + [ NS_FILE_TALK, 'page' ], + + [ NS_CATEGORY, 'subcat' ], + [ NS_CATEGORY_TALK, 'page' ], + + [ 100, 'page' ], + [ 101, 'page' ], + ]; + } + + /** + * @dataProvider provideGetCategoryLinkType + * @covers NamespaceInfo::getCategoryLinkType + * + * @param int $index + * @param string $expected + */ + public function testGetCategoryLinkType( $index, $expected ) { + $actual = $this->obj->getCategoryLinkType( $index ); + $this->assertSame( $expected, $actual, "NS $index" ); + } +}