MWNamespace.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. <?php
  2. /**
  3. * Provide things related to namespaces.
  4. *
  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.
  9. *
  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.
  14. *
  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
  19. *
  20. * @file
  21. */
  22. /**
  23. * This is a utility class with only static functions
  24. * for dealing with namespaces that encodes all the
  25. * "magic" behaviors of them based on index. The textual
  26. * names of the namespaces are handled by Language.php.
  27. *
  28. * These are synonyms for the names given in the language file
  29. * Users and translators should not change them
  30. */
  31. class MWNamespace {
  32. /**
  33. * These namespaces should always be first-letter capitalized, now and
  34. * forevermore. Historically, they could've probably been lowercased too,
  35. * but some things are just too ingrained now. :)
  36. */
  37. private static $alwaysCapitalizedNamespaces = [ NS_SPECIAL, NS_USER, NS_MEDIAWIKI ];
  38. /** @var string[]|null Canonical namespaces cache */
  39. private static $canonicalNamespaces = null;
  40. /** @var array|false Canonical namespaces index cache */
  41. private static $namespaceIndexes = false;
  42. /** @var int[]|null Valid namespaces cache */
  43. private static $validNamespaces = null;
  44. /**
  45. * Throw an exception when trying to get the subject or talk page
  46. * for a given namespace where it does not make sense.
  47. * Special namespaces are defined in includes/Defines.php and have
  48. * a value below 0 (ex: NS_SPECIAL = -1 , NS_MEDIA = -2)
  49. *
  50. * @param int $index
  51. * @param string $method
  52. *
  53. * @throws MWException
  54. * @return bool
  55. */
  56. private static function isMethodValidFor( $index, $method ) {
  57. if ( $index < NS_MAIN ) {
  58. throw new MWException( "$method does not make any sense for given namespace $index" );
  59. }
  60. return true;
  61. }
  62. /**
  63. * Clear internal caches
  64. *
  65. * For use in unit testing when namespace configuration is changed.
  66. *
  67. * @since 1.31
  68. */
  69. public static function clearCaches() {
  70. self::$canonicalNamespaces = null;
  71. self::$namespaceIndexes = false;
  72. self::$validNamespaces = null;
  73. }
  74. /**
  75. * Can pages in the given namespace be moved?
  76. *
  77. * @param int $index Namespace index
  78. * @return bool
  79. */
  80. public static function isMovable( $index ) {
  81. global $wgAllowImageMoving;
  82. $result = !( $index < NS_MAIN || ( $index == NS_FILE && !$wgAllowImageMoving ) );
  83. /**
  84. * @since 1.20
  85. */
  86. Hooks::run( 'NamespaceIsMovable', [ $index, &$result ] );
  87. return $result;
  88. }
  89. /**
  90. * Is the given namespace is a subject (non-talk) namespace?
  91. *
  92. * @param int $index Namespace index
  93. * @return bool
  94. * @since 1.19
  95. */
  96. public static function isSubject( $index ) {
  97. return !self::isTalk( $index );
  98. }
  99. /**
  100. * Is the given namespace a talk namespace?
  101. *
  102. * @param int $index Namespace index
  103. * @return bool
  104. */
  105. public static function isTalk( $index ) {
  106. return $index > NS_MAIN
  107. && $index % 2;
  108. }
  109. /**
  110. * Get the talk namespace index for a given namespace
  111. *
  112. * @param int $index Namespace index
  113. * @return int
  114. */
  115. public static function getTalk( $index ) {
  116. self::isMethodValidFor( $index, __METHOD__ );
  117. return self::isTalk( $index )
  118. ? $index
  119. : $index + 1;
  120. }
  121. /**
  122. * Get the subject namespace index for a given namespace
  123. * Special namespaces (NS_MEDIA, NS_SPECIAL) are always the subject.
  124. *
  125. * @param int $index Namespace index
  126. * @return int
  127. */
  128. public static function getSubject( $index ) {
  129. # Handle special namespaces
  130. if ( $index < NS_MAIN ) {
  131. return $index;
  132. }
  133. return self::isTalk( $index )
  134. ? $index - 1
  135. : $index;
  136. }
  137. /**
  138. * Get the associated namespace.
  139. * For talk namespaces, returns the subject (non-talk) namespace
  140. * For subject (non-talk) namespaces, returns the talk namespace
  141. *
  142. * @param int $index Namespace index
  143. * @return int|null If no associated namespace could be found
  144. */
  145. public static function getAssociated( $index ) {
  146. self::isMethodValidFor( $index, __METHOD__ );
  147. if ( self::isSubject( $index ) ) {
  148. return self::getTalk( $index );
  149. } elseif ( self::isTalk( $index ) ) {
  150. return self::getSubject( $index );
  151. } else {
  152. return null;
  153. }
  154. }
  155. /**
  156. * Returns whether the specified namespace exists
  157. *
  158. * @param int $index
  159. *
  160. * @return bool
  161. * @since 1.19
  162. */
  163. public static function exists( $index ) {
  164. $nslist = self::getCanonicalNamespaces();
  165. return isset( $nslist[$index] );
  166. }
  167. /**
  168. * Returns whether the specified namespaces are the same namespace
  169. *
  170. * @note It's possible that in the future we may start using something
  171. * other than just namespace indexes. Under that circumstance making use
  172. * of this function rather than directly doing comparison will make
  173. * sure that code will not potentially break.
  174. *
  175. * @param int $ns1 The first namespace index
  176. * @param int $ns2 The second namespace index
  177. *
  178. * @return bool
  179. * @since 1.19
  180. */
  181. public static function equals( $ns1, $ns2 ) {
  182. return $ns1 == $ns2;
  183. }
  184. /**
  185. * Returns whether the specified namespaces share the same subject.
  186. * eg: NS_USER and NS_USER wil return true, as well
  187. * NS_USER and NS_USER_TALK will return true.
  188. *
  189. * @param int $ns1 The first namespace index
  190. * @param int $ns2 The second namespace index
  191. *
  192. * @return bool
  193. * @since 1.19
  194. */
  195. public static function subjectEquals( $ns1, $ns2 ) {
  196. return self::getSubject( $ns1 ) == self::getSubject( $ns2 );
  197. }
  198. /**
  199. * Returns array of all defined namespaces with their canonical
  200. * (English) names.
  201. *
  202. * @param bool $rebuild Rebuild namespace list (default = false). Used for testing.
  203. * Deprecated since 1.31, use self::clearCaches() instead.
  204. *
  205. * @return array
  206. * @since 1.17
  207. */
  208. public static function getCanonicalNamespaces( $rebuild = false ) {
  209. if ( $rebuild ) {
  210. self::clearCaches();
  211. }
  212. if ( self::$canonicalNamespaces === null ) {
  213. global $wgExtraNamespaces, $wgCanonicalNamespaceNames;
  214. self::$canonicalNamespaces = [ NS_MAIN => '' ] + $wgCanonicalNamespaceNames;
  215. // Add extension namespaces
  216. self::$canonicalNamespaces +=
  217. ExtensionRegistry::getInstance()->getAttribute( 'ExtensionNamespaces' );
  218. if ( is_array( $wgExtraNamespaces ) ) {
  219. self::$canonicalNamespaces += $wgExtraNamespaces;
  220. }
  221. Hooks::run( 'CanonicalNamespaces', [ &self::$canonicalNamespaces ] );
  222. }
  223. return self::$canonicalNamespaces;
  224. }
  225. /**
  226. * Returns the canonical (English) name for a given index
  227. *
  228. * @param int $index Namespace index
  229. * @return string|bool If no canonical definition.
  230. */
  231. public static function getCanonicalName( $index ) {
  232. $nslist = self::getCanonicalNamespaces();
  233. if ( isset( $nslist[$index] ) ) {
  234. return $nslist[$index];
  235. } else {
  236. return false;
  237. }
  238. }
  239. /**
  240. * Returns the index for a given canonical name, or NULL
  241. * The input *must* be converted to lower case first
  242. *
  243. * @param string $name Namespace name
  244. * @return int
  245. */
  246. public static function getCanonicalIndex( $name ) {
  247. if ( self::$namespaceIndexes === false ) {
  248. self::$namespaceIndexes = [];
  249. foreach ( self::getCanonicalNamespaces() as $i => $text ) {
  250. self::$namespaceIndexes[strtolower( $text )] = $i;
  251. }
  252. }
  253. if ( array_key_exists( $name, self::$namespaceIndexes ) ) {
  254. return self::$namespaceIndexes[$name];
  255. } else {
  256. return null;
  257. }
  258. }
  259. /**
  260. * Returns an array of the namespaces (by integer id) that exist on the
  261. * wiki. Used primarily by the api in help documentation.
  262. * @return array
  263. */
  264. public static function getValidNamespaces() {
  265. if ( is_null( self::$validNamespaces ) ) {
  266. foreach ( array_keys( self::getCanonicalNamespaces() ) as $ns ) {
  267. if ( $ns >= 0 ) {
  268. self::$validNamespaces[] = $ns;
  269. }
  270. }
  271. // T109137: sort numerically
  272. sort( self::$validNamespaces, SORT_NUMERIC );
  273. }
  274. return self::$validNamespaces;
  275. }
  276. /**
  277. * Does this namespace ever have a talk namespace?
  278. *
  279. * @deprecated since 1.30, use hasTalkNamespace() instead.
  280. *
  281. * @param int $index Namespace index
  282. * @return bool True if this namespace either is or has a corresponding talk namespace.
  283. */
  284. public static function canTalk( $index ) {
  285. return self::hasTalkNamespace( $index );
  286. }
  287. /**
  288. * Does this namespace ever have a talk namespace?
  289. *
  290. * @since 1.30
  291. *
  292. * @param int $index Namespace ID
  293. * @return bool True if this namespace either is or has a corresponding talk namespace.
  294. */
  295. public static function hasTalkNamespace( $index ) {
  296. return $index >= NS_MAIN;
  297. }
  298. /**
  299. * Does this namespace contain content, for the purposes of calculating
  300. * statistics, etc?
  301. *
  302. * @param int $index Index to check
  303. * @return bool
  304. */
  305. public static function isContent( $index ) {
  306. global $wgContentNamespaces;
  307. return $index == NS_MAIN || in_array( $index, $wgContentNamespaces );
  308. }
  309. /**
  310. * Might pages in this namespace require the use of the Signature button on
  311. * the edit toolbar?
  312. *
  313. * @param int $index Index to check
  314. * @return bool
  315. */
  316. public static function wantSignatures( $index ) {
  317. global $wgExtraSignatureNamespaces;
  318. return self::isTalk( $index ) || in_array( $index, $wgExtraSignatureNamespaces );
  319. }
  320. /**
  321. * Can pages in a namespace be watched?
  322. *
  323. * @param int $index
  324. * @return bool
  325. */
  326. public static function isWatchable( $index ) {
  327. return $index >= NS_MAIN;
  328. }
  329. /**
  330. * Does the namespace allow subpages?
  331. *
  332. * @param int $index Index to check
  333. * @return bool
  334. */
  335. public static function hasSubpages( $index ) {
  336. global $wgNamespacesWithSubpages;
  337. return !empty( $wgNamespacesWithSubpages[$index] );
  338. }
  339. /**
  340. * Get a list of all namespace indices which are considered to contain content
  341. * @return array Array of namespace indices
  342. */
  343. public static function getContentNamespaces() {
  344. global $wgContentNamespaces;
  345. if ( !is_array( $wgContentNamespaces ) || $wgContentNamespaces === [] ) {
  346. return [ NS_MAIN ];
  347. } elseif ( !in_array( NS_MAIN, $wgContentNamespaces ) ) {
  348. // always force NS_MAIN to be part of array (to match the algorithm used by isContent)
  349. return array_merge( [ NS_MAIN ], $wgContentNamespaces );
  350. } else {
  351. return $wgContentNamespaces;
  352. }
  353. }
  354. /**
  355. * List all namespace indices which are considered subject, aka not a talk
  356. * or special namespace. See also MWNamespace::isSubject
  357. *
  358. * @return array Array of namespace indices
  359. */
  360. public static function getSubjectNamespaces() {
  361. return array_filter(
  362. self::getValidNamespaces(),
  363. 'MWNamespace::isSubject'
  364. );
  365. }
  366. /**
  367. * List all namespace indices which are considered talks, aka not a subject
  368. * or special namespace. See also MWNamespace::isTalk
  369. *
  370. * @return array Array of namespace indices
  371. */
  372. public static function getTalkNamespaces() {
  373. return array_filter(
  374. self::getValidNamespaces(),
  375. 'MWNamespace::isTalk'
  376. );
  377. }
  378. /**
  379. * Is the namespace first-letter capitalized?
  380. *
  381. * @param int $index Index to check
  382. * @return bool
  383. */
  384. public static function isCapitalized( $index ) {
  385. global $wgCapitalLinks, $wgCapitalLinkOverrides;
  386. // Turn NS_MEDIA into NS_FILE
  387. $index = $index === NS_MEDIA ? NS_FILE : $index;
  388. // Make sure to get the subject of our namespace
  389. $index = self::getSubject( $index );
  390. // Some namespaces are special and should always be upper case
  391. if ( in_array( $index, self::$alwaysCapitalizedNamespaces ) ) {
  392. return true;
  393. }
  394. if ( isset( $wgCapitalLinkOverrides[$index] ) ) {
  395. // $wgCapitalLinkOverrides is explicitly set
  396. return $wgCapitalLinkOverrides[$index];
  397. }
  398. // Default to the global setting
  399. return $wgCapitalLinks;
  400. }
  401. /**
  402. * Does the namespace (potentially) have different aliases for different
  403. * genders. Not all languages make a distinction here.
  404. *
  405. * @since 1.18
  406. * @param int $index Index to check
  407. * @return bool
  408. */
  409. public static function hasGenderDistinction( $index ) {
  410. return $index == NS_USER || $index == NS_USER_TALK;
  411. }
  412. /**
  413. * It is not possible to use pages from this namespace as template?
  414. *
  415. * @since 1.20
  416. * @param int $index Index to check
  417. * @return bool
  418. */
  419. public static function isNonincludable( $index ) {
  420. global $wgNonincludableNamespaces;
  421. return $wgNonincludableNamespaces && in_array( $index, $wgNonincludableNamespaces );
  422. }
  423. /**
  424. * Get the default content model for a namespace
  425. * This does not mean that all pages in that namespace have the model
  426. *
  427. * @since 1.21
  428. * @param int $index Index to check
  429. * @return null|string Default model name for the given namespace, if set
  430. */
  431. public static function getNamespaceContentModel( $index ) {
  432. global $wgNamespaceContentModels;
  433. return isset( $wgNamespaceContentModels[$index] )
  434. ? $wgNamespaceContentModels[$index]
  435. : null;
  436. }
  437. /**
  438. * Determine which restriction levels it makes sense to use in a namespace,
  439. * optionally filtered by a user's rights.
  440. *
  441. * @since 1.23
  442. * @param int $index Index to check
  443. * @param User $user User to check
  444. * @return array
  445. */
  446. public static function getRestrictionLevels( $index, User $user = null ) {
  447. global $wgNamespaceProtection, $wgRestrictionLevels;
  448. if ( !isset( $wgNamespaceProtection[$index] ) ) {
  449. // All levels are valid if there's no namespace restriction.
  450. // But still filter by user, if necessary
  451. $levels = $wgRestrictionLevels;
  452. if ( $user ) {
  453. $levels = array_values( array_filter( $levels, function ( $level ) use ( $user ) {
  454. $right = $level;
  455. if ( $right == 'sysop' ) {
  456. $right = 'editprotected'; // BC
  457. }
  458. if ( $right == 'autoconfirmed' ) {
  459. $right = 'editsemiprotected'; // BC
  460. }
  461. return ( $right == '' || $user->isAllowed( $right ) );
  462. } ) );
  463. }
  464. return $levels;
  465. }
  466. // First, get the list of groups that can edit this namespace.
  467. $namespaceGroups = [];
  468. $combine = 'array_merge';
  469. foreach ( (array)$wgNamespaceProtection[$index] as $right ) {
  470. if ( $right == 'sysop' ) {
  471. $right = 'editprotected'; // BC
  472. }
  473. if ( $right == 'autoconfirmed' ) {
  474. $right = 'editsemiprotected'; // BC
  475. }
  476. if ( $right != '' ) {
  477. $namespaceGroups = call_user_func( $combine, $namespaceGroups,
  478. User::getGroupsWithPermission( $right ) );
  479. $combine = 'array_intersect';
  480. }
  481. }
  482. // Now, keep only those restriction levels where there is at least one
  483. // group that can edit the namespace but would be blocked by the
  484. // restriction.
  485. $usableLevels = [ '' ];
  486. foreach ( $wgRestrictionLevels as $level ) {
  487. $right = $level;
  488. if ( $right == 'sysop' ) {
  489. $right = 'editprotected'; // BC
  490. }
  491. if ( $right == 'autoconfirmed' ) {
  492. $right = 'editsemiprotected'; // BC
  493. }
  494. if ( $right != '' && ( !$user || $user->isAllowed( $right ) ) &&
  495. array_diff( $namespaceGroups, User::getGroupsWithPermission( $right ) )
  496. ) {
  497. $usableLevels[] = $level;
  498. }
  499. }
  500. return $usableLevels;
  501. }
  502. }