SpecialPage.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955
  1. <?php
  2. /**
  3. * SpecialPage: handling special pages and lists thereof.
  4. *
  5. * To add a special page in an extension, add to $wgSpecialPages either
  6. * an object instance or an array containing the name and constructor
  7. * parameters. The latter is preferred for performance reasons.
  8. *
  9. * The object instantiated must be either an instance of SpecialPage or a
  10. * sub-class thereof. It must have an execute() method, which sends the HTML
  11. * for the special page to $wgOut. The parent class has an execute() method
  12. * which distributes the call to the historical global functions. Additionally,
  13. * execute() also checks if the user has the necessary access privileges
  14. * and bails out if not.
  15. *
  16. * To add a core special page, use the similar static list in
  17. * SpecialPage::$mList. To remove a core static special page at runtime, use
  18. * a SpecialPage_initList hook.
  19. *
  20. * @file
  21. * @ingroup SpecialPage
  22. * @defgroup SpecialPage SpecialPage
  23. */
  24. /**
  25. * Parent special page class, also static functions for handling the special
  26. * page list.
  27. * @ingroup SpecialPage
  28. */
  29. class SpecialPage
  30. {
  31. /**#@+
  32. * @access private
  33. */
  34. /**
  35. * The canonical name of this special page
  36. * Also used for the default <h1> heading, @see getDescription()
  37. */
  38. var $mName;
  39. /**
  40. * The local name of this special page
  41. */
  42. var $mLocalName;
  43. /**
  44. * Minimum user level required to access this page, or "" for anyone.
  45. * Also used to categorise the pages in Special:Specialpages
  46. */
  47. var $mRestriction;
  48. /**
  49. * Listed in Special:Specialpages?
  50. */
  51. var $mListed;
  52. /**
  53. * Function name called by the default execute()
  54. */
  55. var $mFunction;
  56. /**
  57. * File which needs to be included before the function above can be called
  58. */
  59. var $mFile;
  60. /**
  61. * Whether or not this special page is being included from an article
  62. */
  63. var $mIncluding;
  64. /**
  65. * Whether the special page can be included in an article
  66. */
  67. var $mIncludable;
  68. /**
  69. * Query parameters that can be passed through redirects
  70. */
  71. var $mAllowedRedirectParams = array();
  72. /**
  73. * List of special pages, followed by parameters.
  74. * If the only parameter is a string, that is the page name.
  75. * Otherwise, it is an array. The format is one of:
  76. ** array( 'SpecialPage', name, right )
  77. ** array( 'IncludableSpecialPage', name, right, listed? )
  78. ** array( 'UnlistedSpecialPage', name, right )
  79. ** array( 'SpecialRedirectToSpecial', name, page to redirect to, special page param, ... )
  80. */
  81. static public $mList = array(
  82. # Maintenance Reports
  83. 'BrokenRedirects' => array( 'SpecialPage', 'BrokenRedirects' ),
  84. 'Deadendpages' => array( 'SpecialPage', 'Deadendpages' ),
  85. 'DoubleRedirects' => array( 'SpecialPage', 'DoubleRedirects' ),
  86. 'Longpages' => array( 'SpecialPage', 'Longpages' ),
  87. 'Ancientpages' => array( 'SpecialPage', 'Ancientpages' ),
  88. 'Lonelypages' => array( 'SpecialPage', 'Lonelypages' ),
  89. 'Fewestrevisions' => array( 'SpecialPage', 'Fewestrevisions' ),
  90. 'Withoutinterwiki' => array( 'SpecialPage', 'Withoutinterwiki' ),
  91. 'Protectedpages' => array( 'SpecialPage', 'Protectedpages' ),
  92. 'Protectedtitles' => array( 'SpecialPage', 'Protectedtitles' ),
  93. 'Shortpages' => array( 'SpecialPage', 'Shortpages' ),
  94. 'Uncategorizedcategories' => array( 'SpecialPage', 'Uncategorizedcategories' ),
  95. 'Uncategorizedimages' => array( 'SpecialPage', 'Uncategorizedimages' ),
  96. 'Uncategorizedpages' => array( 'SpecialPage', 'Uncategorizedpages' ),
  97. 'Uncategorizedtemplates' => array( 'SpecialPage', 'Uncategorizedtemplates' ),
  98. 'Unusedcategories' => array( 'SpecialPage', 'Unusedcategories' ),
  99. 'Unusedimages' => array( 'SpecialPage', 'Unusedimages' ),
  100. 'Unusedtemplates' => array( 'SpecialPage', 'Unusedtemplates' ),
  101. 'Unwatchedpages' => array( 'SpecialPage', 'Unwatchedpages', 'unwatchedpages' ),
  102. 'Wantedcategories' => array( 'SpecialPage', 'Wantedcategories' ),
  103. 'Wantedfiles' => array( 'SpecialPage', 'Wantedfiles' ),
  104. 'Wantedpages' => array( 'IncludableSpecialPage', 'Wantedpages' ),
  105. 'Wantedtemplates' => array( 'SpecialPage', 'Wantedtemplates' ),
  106. # List of pages
  107. 'Allpages' => 'SpecialAllpages',
  108. 'Prefixindex' => 'SpecialPrefixindex',
  109. 'Categories' => array( 'SpecialPage', 'Categories' ),
  110. 'Disambiguations' => array( 'SpecialPage', 'Disambiguations' ),
  111. 'Listredirects' => array( 'SpecialPage', 'Listredirects' ),
  112. # Login/create account
  113. 'Userlogin' => array( 'SpecialPage', 'Userlogin' ),
  114. 'CreateAccount' => array( 'SpecialRedirectToSpecial', 'CreateAccount', 'Userlogin', 'signup', array( 'uselang' ) ),
  115. # Users and rights
  116. 'Blockip' => array( 'SpecialPage', 'Blockip', 'block' ),
  117. 'Ipblocklist' => array( 'SpecialPage', 'Ipblocklist' ),
  118. 'Resetpass' => 'SpecialResetpass',
  119. 'DeletedContributions' => 'DeletedContributionsPage',
  120. 'Preferences' => array( 'SpecialPage', 'Preferences' ),
  121. 'Contributions' => 'SpecialContributions',
  122. 'Listgrouprights' => 'SpecialListGroupRights',
  123. 'Listusers' => array( 'SpecialPage', 'Listusers' ),
  124. 'Userrights' => 'UserrightsPage',
  125. # Recent changes and logs
  126. 'Newimages' => array( 'IncludableSpecialPage', 'Newimages' ),
  127. 'Log' => array( 'SpecialPage', 'Log' ),
  128. 'Watchlist' => array( 'SpecialPage', 'Watchlist' ),
  129. 'Newpages' => 'SpecialNewpages',
  130. 'Recentchanges' => 'SpecialRecentchanges',
  131. 'Recentchangeslinked' => 'SpecialRecentchangeslinked',
  132. 'Tags' => 'SpecialTags',
  133. # Media reports and uploads
  134. 'Listfiles' => array( 'SpecialPage', 'Listfiles' ),
  135. 'Filepath' => array( 'SpecialPage', 'Filepath' ),
  136. 'MIMEsearch' => array( 'SpecialPage', 'MIMEsearch' ),
  137. 'FileDuplicateSearch' => array( 'SpecialPage', 'FileDuplicateSearch' ),
  138. 'Upload' => array( 'SpecialPage', 'Upload' ),
  139. # Wiki data and tools
  140. 'Statistics' => 'SpecialStatistics',
  141. 'Allmessages' => array( 'SpecialPage', 'Allmessages' ),
  142. 'Version' => 'SpecialVersion',
  143. 'Lockdb' => array( 'SpecialPage', 'Lockdb', 'siteadmin' ),
  144. 'Unlockdb' => array( 'SpecialPage', 'Unlockdb', 'siteadmin' ),
  145. # Redirecting special pages
  146. 'LinkSearch' => array( 'SpecialPage', 'LinkSearch' ),
  147. 'Randompage' => 'Randompage',
  148. 'Randomredirect' => 'SpecialRandomredirect',
  149. # High use pages
  150. 'Mostlinkedcategories' => array( 'SpecialPage', 'Mostlinkedcategories' ),
  151. 'Mostimages' => array( 'SpecialPage', 'Mostimages' ),
  152. 'Mostlinked' => array( 'SpecialPage', 'Mostlinked' ),
  153. 'Mostlinkedtemplates' => array( 'SpecialPage', 'Mostlinkedtemplates' ),
  154. 'Mostcategories' => array( 'SpecialPage', 'Mostcategories' ),
  155. 'Mostrevisions' => array( 'SpecialPage', 'Mostrevisions' ),
  156. # Page tools
  157. 'Export' => 'SpecialExport',
  158. 'Import' => 'SpecialImport',
  159. 'Undelete' => array( 'SpecialPage', 'Undelete', 'deletedhistory' ),
  160. 'Whatlinkshere' => array( 'SpecialPage', 'Whatlinkshere' ),
  161. 'MergeHistory' => array( 'SpecialPage', 'MergeHistory', 'mergehistory' ),
  162. # Other
  163. 'Booksources' => 'SpecialBookSources',
  164. # Unlisted / redirects
  165. 'Blankpage' => array( 'UnlistedSpecialPage', 'Blankpage' ),
  166. 'Blockme' => array( 'UnlistedSpecialPage', 'Blockme' ),
  167. 'Emailuser' => array( 'UnlistedSpecialPage', 'Emailuser' ),
  168. 'Listadmins' => array( 'SpecialRedirectToSpecial', 'Listadmins', 'Listusers', 'sysop' ),
  169. 'Listbots' => array( 'SpecialRedirectToSpecial', 'Listbots', 'Listusers', 'bot' ),
  170. 'Movepage' => array( 'UnlistedSpecialPage', 'Movepage' ),
  171. 'Mycontributions' => array( 'SpecialMycontributions' ),
  172. 'Mypage' => array( 'SpecialMypage' ),
  173. 'Mytalk' => array( 'SpecialMytalk' ),
  174. 'Revisiondelete' => 'SpecialRevisionDelete',
  175. 'Specialpages' => array( 'UnlistedSpecialPage', 'Specialpages' ),
  176. 'Userlogout' => array( 'UnlistedSpecialPage', 'Userlogout' ),
  177. );
  178. static public $mAliases;
  179. static public $mListInitialised = false;
  180. /**#@-*/
  181. /**
  182. * Initialise the special page list
  183. * This must be called before accessing SpecialPage::$mList
  184. */
  185. static function initList() {
  186. global $wgSpecialPages;
  187. global $wgDisableCounters, $wgDisableInternalSearch, $wgEmailAuthentication;
  188. if ( self::$mListInitialised ) {
  189. return;
  190. }
  191. wfProfileIn( __METHOD__ );
  192. # Better to set this now, to avoid infinite recursion in carelessly written hooks
  193. self::$mListInitialised = true;
  194. if( !$wgDisableCounters ) {
  195. self::$mList['Popularpages'] = array( 'SpecialPage', 'Popularpages' );
  196. }
  197. if( !$wgDisableInternalSearch ) {
  198. self::$mList['Search'] = array( 'SpecialPage', 'Search' );
  199. }
  200. if( $wgEmailAuthentication ) {
  201. self::$mList['Confirmemail'] = 'EmailConfirmation';
  202. self::$mList['Invalidateemail'] = 'EmailInvalidation';
  203. }
  204. # Add extension special pages
  205. self::$mList = array_merge( self::$mList, $wgSpecialPages );
  206. # Run hooks
  207. # This hook can be used to remove undesired built-in special pages
  208. wfRunHooks( 'SpecialPage_initList', array( &self::$mList ) );
  209. wfProfileOut( __METHOD__ );
  210. }
  211. static function initAliasList() {
  212. if ( !is_null( self::$mAliases ) ) {
  213. return;
  214. }
  215. global $wgContLang;
  216. $aliases = $wgContLang->getSpecialPageAliases();
  217. $missingPages = self::$mList;
  218. self::$mAliases = array();
  219. foreach ( $aliases as $realName => $aliasList ) {
  220. foreach ( $aliasList as $alias ) {
  221. self::$mAliases[$wgContLang->caseFold( $alias )] = $realName;
  222. }
  223. unset( $missingPages[$realName] );
  224. }
  225. foreach ( $missingPages as $name => $stuff ) {
  226. self::$mAliases[$wgContLang->caseFold( $name )] = $name;
  227. }
  228. }
  229. /**
  230. * Given a special page alias, return the special page name.
  231. * Returns false if there is no such alias.
  232. */
  233. static function resolveAlias( $alias ) {
  234. global $wgContLang;
  235. if ( !self::$mListInitialised ) self::initList();
  236. if ( is_null( self::$mAliases ) ) self::initAliasList();
  237. $caseFoldedAlias = $wgContLang->caseFold( $alias );
  238. if ( isset( self::$mAliases[$caseFoldedAlias] ) ) {
  239. return self::$mAliases[$caseFoldedAlias];
  240. } else {
  241. return false;
  242. }
  243. }
  244. /**
  245. * Given a special page name with a possible subpage, return an array
  246. * where the first element is the special page name and the second is the
  247. * subpage.
  248. */
  249. static function resolveAliasWithSubpage( $alias ) {
  250. $bits = explode( '/', $alias, 2 );
  251. $name = self::resolveAlias( $bits[0] );
  252. if( !isset( $bits[1] ) ) { // bug 2087
  253. $par = NULL;
  254. } else {
  255. $par = $bits[1];
  256. }
  257. return array( $name, $par );
  258. }
  259. /**
  260. * Add a page to the list of valid special pages. This used to be the preferred
  261. * method for adding special pages in extensions. It's now suggested that you add
  262. * an associative record to $wgSpecialPages. This avoids autoloading SpecialPage.
  263. *
  264. * @param SpecialPage $page
  265. * @static
  266. */
  267. static function addPage( &$page ) {
  268. if ( !self::$mListInitialised ) {
  269. self::initList();
  270. }
  271. self::$mList[$page->mName] = $page;
  272. }
  273. /**
  274. * Add a page to a certain display group for Special:SpecialPages
  275. *
  276. * @param mixed $page (SpecialPage or string)
  277. * @param string $group
  278. * @static
  279. */
  280. static function setGroup( $page, $group ) {
  281. global $wgSpecialPageGroups;
  282. $name = is_object($page) ? $page->mName : $page;
  283. $wgSpecialPageGroups[$name] = $group;
  284. }
  285. /**
  286. * Add a page to a certain display group for Special:SpecialPages
  287. *
  288. * @param SpecialPage $page
  289. * @static
  290. */
  291. static function getGroup( &$page ) {
  292. global $wgSpecialPageGroups;
  293. static $specialPageGroupsCache = array();
  294. if( isset($specialPageGroupsCache[$page->mName]) ) {
  295. return $specialPageGroupsCache[$page->mName];
  296. }
  297. $group = wfMsg('specialpages-specialpagegroup-'.strtolower($page->mName));
  298. if( $group == ''
  299. || wfEmptyMsg('specialpages-specialpagegroup-'.strtolower($page->mName), $group ) ) {
  300. $group = isset($wgSpecialPageGroups[$page->mName])
  301. ? $wgSpecialPageGroups[$page->mName]
  302. : '-';
  303. }
  304. if( $group == '-' ) $group = 'other';
  305. $specialPageGroupsCache[$page->mName] = $group;
  306. return $group;
  307. }
  308. /**
  309. * Remove a special page from the list
  310. * Formerly used to disable expensive or dangerous special pages. The
  311. * preferred method is now to add a SpecialPage_initList hook.
  312. *
  313. * @static
  314. */
  315. static function removePage( $name ) {
  316. if ( !self::$mListInitialised ) {
  317. self::initList();
  318. }
  319. unset( self::$mList[$name] );
  320. }
  321. /**
  322. * Check if a given name exist as a special page or as a special page alias
  323. * @param $name string: name of a special page
  324. * @return boolean: true if a special page exists with this name
  325. */
  326. static function exists( $name ) {
  327. global $wgContLang;
  328. if ( !self::$mListInitialised ) {
  329. self::initList();
  330. }
  331. if( !self::$mAliases ) {
  332. self::initAliasList();
  333. }
  334. # Remove special pages inline parameters:
  335. $bits = explode( '/', $name );
  336. $name = $wgContLang->caseFold($bits[0]);
  337. return
  338. array_key_exists( $name, self::$mList )
  339. or array_key_exists( $name, self::$mAliases )
  340. ;
  341. }
  342. /**
  343. * Find the object with a given name and return it (or NULL)
  344. * @static
  345. * @param string $name
  346. */
  347. static function getPage( $name ) {
  348. if ( !self::$mListInitialised ) {
  349. self::initList();
  350. }
  351. if ( array_key_exists( $name, self::$mList ) ) {
  352. $rec = self::$mList[$name];
  353. if ( is_string( $rec ) ) {
  354. $className = $rec;
  355. self::$mList[$name] = new $className;
  356. } elseif ( is_array( $rec ) ) {
  357. $className = array_shift( $rec );
  358. self::$mList[$name] = wfCreateObject( $className, $rec );
  359. }
  360. return self::$mList[$name];
  361. } else {
  362. return NULL;
  363. }
  364. }
  365. /**
  366. * Get a special page with a given localised name, or NULL if there
  367. * is no such special page.
  368. */
  369. static function getPageByAlias( $alias ) {
  370. $realName = self::resolveAlias( $alias );
  371. if ( $realName ) {
  372. return self::getPage( $realName );
  373. } else {
  374. return NULL;
  375. }
  376. }
  377. /**
  378. * Return categorised listable special pages which are available
  379. * for the current user, and everyone.
  380. * @static
  381. */
  382. static function getUsablePages() {
  383. global $wgUser;
  384. if ( !self::$mListInitialised ) {
  385. self::initList();
  386. }
  387. $pages = array();
  388. foreach ( self::$mList as $name => $rec ) {
  389. $page = self::getPage( $name );
  390. if ( $page->isListed()
  391. && (
  392. !$page->isRestricted()
  393. || $page->userCanExecute( $wgUser )
  394. )
  395. ) {
  396. $pages[$name] = $page;
  397. }
  398. }
  399. return $pages;
  400. }
  401. /**
  402. * Return categorised listable special pages for all users
  403. * @static
  404. */
  405. static function getRegularPages() {
  406. if ( !self::$mListInitialised ) {
  407. self::initList();
  408. }
  409. $pages = array();
  410. foreach ( self::$mList as $name => $rec ) {
  411. $page = self::getPage( $name );
  412. if ( $page->isListed() && !$page->isRestricted() ) {
  413. $pages[$name] = $page;
  414. }
  415. }
  416. return $pages;
  417. }
  418. /**
  419. * Return categorised listable special pages which are available
  420. * for the current user, but not for everyone
  421. * @static
  422. */
  423. static function getRestrictedPages() {
  424. global $wgUser;
  425. if( !self::$mListInitialised ) {
  426. self::initList();
  427. }
  428. $pages = array();
  429. foreach( self::$mList as $name => $rec ) {
  430. $page = self::getPage( $name );
  431. if(
  432. $page->isListed()
  433. && $page->isRestricted()
  434. && $page->userCanExecute( $wgUser )
  435. ) {
  436. $pages[$name] = $page;
  437. }
  438. }
  439. return $pages;
  440. }
  441. /**
  442. * Execute a special page path.
  443. * The path may contain parameters, e.g. Special:Name/Params
  444. * Extracts the special page name and call the execute method, passing the parameters
  445. *
  446. * Returns a title object if the page is redirected, false if there was no such special
  447. * page, and true if it was successful.
  448. *
  449. * @param $title a title object
  450. * @param $including output is being captured for use in {{special:whatever}}
  451. */
  452. static function executePath( &$title, $including = false ) {
  453. global $wgOut, $wgTitle, $wgRequest;
  454. wfProfileIn( __METHOD__ );
  455. # FIXME: redirects broken due to this call
  456. $bits = explode( '/', $title->getDBkey(), 2 );
  457. $name = $bits[0];
  458. if( !isset( $bits[1] ) ) { // bug 2087
  459. $par = NULL;
  460. } else {
  461. $par = $bits[1];
  462. }
  463. $page = SpecialPage::getPageByAlias( $name );
  464. # Nonexistent?
  465. if ( !$page ) {
  466. if ( !$including ) {
  467. $wgOut->setArticleRelated( false );
  468. $wgOut->setRobotPolicy( 'noindex,nofollow' );
  469. $wgOut->setStatusCode( 404 );
  470. $wgOut->showErrorPage( 'nosuchspecialpage', 'nospecialpagetext' );
  471. }
  472. wfProfileOut( __METHOD__ );
  473. return false;
  474. }
  475. # Check for redirect
  476. if ( !$including ) {
  477. $redirect = $page->getRedirect( $par );
  478. if ( $redirect ) {
  479. $query = $page->getRedirectQuery();
  480. $url = $redirect->getFullUrl( $query );
  481. $wgOut->redirect( $url );
  482. wfProfileOut( __METHOD__ );
  483. return $redirect;
  484. }
  485. }
  486. # Redirect to canonical alias for GET commands
  487. # Not for POST, we'd lose the post data, so it's best to just distribute
  488. # the request. Such POST requests are possible for old extensions that
  489. # generate self-links without being aware that their default name has
  490. # changed.
  491. if ( !$including && $name != $page->getLocalName() && !$wgRequest->wasPosted() ) {
  492. $query = $_GET;
  493. unset( $query['title'] );
  494. $query = wfArrayToCGI( $query );
  495. $title = $page->getTitle( $par );
  496. $url = $title->getFullUrl( $query );
  497. $wgOut->redirect( $url );
  498. wfProfileOut( __METHOD__ );
  499. return $redirect;
  500. }
  501. if ( $including && !$page->includable() ) {
  502. wfProfileOut( __METHOD__ );
  503. return false;
  504. } elseif ( !$including ) {
  505. $wgTitle = $page->getTitle();
  506. }
  507. $page->including( $including );
  508. // Execute special page
  509. $profName = 'Special:' . $page->getName();
  510. wfProfileIn( $profName );
  511. $page->execute( $par );
  512. wfProfileOut( $profName );
  513. wfProfileOut( __METHOD__ );
  514. return true;
  515. }
  516. /**
  517. * Just like executePath() except it returns the HTML instead of outputting it
  518. * Returns false if there was no such special page, or a title object if it was
  519. * a redirect.
  520. * @static
  521. */
  522. static function capturePath( &$title ) {
  523. global $wgOut, $wgTitle;
  524. $oldTitle = $wgTitle;
  525. $oldOut = $wgOut;
  526. $wgOut = new OutputPage;
  527. $ret = SpecialPage::executePath( $title, true );
  528. if ( $ret === true ) {
  529. $ret = $wgOut->getHTML();
  530. }
  531. $wgTitle = $oldTitle;
  532. $wgOut = $oldOut;
  533. return $ret;
  534. }
  535. /**
  536. * Get the local name for a specified canonical name
  537. *
  538. * @param $name
  539. * @param mixed $subpage Boolean false, or string
  540. *
  541. * @return string
  542. */
  543. static function getLocalNameFor( $name, $subpage = false ) {
  544. global $wgContLang;
  545. $aliases = $wgContLang->getSpecialPageAliases();
  546. if ( isset( $aliases[$name][0] ) ) {
  547. $name = $aliases[$name][0];
  548. }
  549. if ( $subpage !== false && !is_null( $subpage ) ) {
  550. $name = "$name/$subpage";
  551. }
  552. return ucfirst( $name );
  553. }
  554. /**
  555. * Get a localised Title object for a specified special page name
  556. */
  557. static function getTitleFor( $name, $subpage = false ) {
  558. $name = self::getLocalNameFor( $name, $subpage );
  559. if ( $name ) {
  560. return Title::makeTitle( NS_SPECIAL, $name );
  561. } else {
  562. throw new MWException( "Invalid special page name \"$name\"" );
  563. }
  564. }
  565. /**
  566. * Get a localised Title object for a page name with a possibly unvalidated subpage
  567. */
  568. static function getSafeTitleFor( $name, $subpage = false ) {
  569. $name = self::getLocalNameFor( $name, $subpage );
  570. if ( $name ) {
  571. return Title::makeTitleSafe( NS_SPECIAL, $name );
  572. } else {
  573. return null;
  574. }
  575. }
  576. /**
  577. * Get a title for a given alias
  578. * @return Title or null if there is no such alias
  579. */
  580. static function getTitleForAlias( $alias ) {
  581. $name = self::resolveAlias( $alias );
  582. if ( $name ) {
  583. return self::getTitleFor( $name );
  584. } else {
  585. return null;
  586. }
  587. }
  588. /**
  589. * Default constructor for special pages
  590. * Derivative classes should call this from their constructor
  591. * Note that if the user does not have the required level, an error message will
  592. * be displayed by the default execute() method, without the global function ever
  593. * being called.
  594. *
  595. * If you override execute(), you can recover the default behaviour with userCanExecute()
  596. * and displayRestrictionError()
  597. *
  598. * @param string $name Name of the special page, as seen in links and URLs
  599. * @param string $restriction User right required, e.g. "block" or "delete"
  600. * @param boolean $listed Whether the page is listed in Special:Specialpages
  601. * @param string $function Function called by execute(). By default it is constructed from $name
  602. * @param string $file File which is included by execute(). It is also constructed from $name by default
  603. */
  604. function SpecialPage( $name = '', $restriction = '', $listed = true, $function = false, $file = 'default', $includable = false ) {
  605. $this->mName = $name;
  606. $this->mRestriction = $restriction;
  607. $this->mListed = $listed;
  608. $this->mIncludable = $includable;
  609. if ( $function == false ) {
  610. $this->mFunction = 'wfSpecial'.$name;
  611. } else {
  612. $this->mFunction = $function;
  613. }
  614. if ( $file === 'default' ) {
  615. $this->mFile = dirname(__FILE__) . "/specials/Special$name.php";
  616. } else {
  617. $this->mFile = $file;
  618. }
  619. }
  620. /**#@+
  621. * Accessor
  622. *
  623. * @deprecated
  624. */
  625. function getName() { return $this->mName; }
  626. function getRestriction() { return $this->mRestriction; }
  627. function getFile() { return $this->mFile; }
  628. function isListed() { return $this->mListed; }
  629. /**#@-*/
  630. /**#@+
  631. * Accessor and mutator
  632. */
  633. function name( $x = NULL ) { return wfSetVar( $this->mName, $x ); }
  634. function restrictions( $x = NULL) { return wfSetVar( $this->mRestrictions, $x ); }
  635. function listed( $x = NULL) { return wfSetVar( $this->mListed, $x ); }
  636. function func( $x = NULL) { return wfSetVar( $this->mFunction, $x ); }
  637. function file( $x = NULL) { return wfSetVar( $this->mFile, $x ); }
  638. function includable( $x = NULL ) { return wfSetVar( $this->mIncludable, $x ); }
  639. function including( $x = NULL ) { return wfSetVar( $this->mIncluding, $x ); }
  640. /**#@-*/
  641. /**
  642. * Get the localised name of the special page
  643. */
  644. function getLocalName() {
  645. if ( !isset( $this->mLocalName ) ) {
  646. $this->mLocalName = self::getLocalNameFor( $this->mName );
  647. }
  648. return $this->mLocalName;
  649. }
  650. /**
  651. * Can be overridden by subclasses with more complicated permissions
  652. * schemes.
  653. *
  654. * @return bool Should the page be displayed with the restricted-access
  655. * pages?
  656. */
  657. public function isRestricted() {
  658. global $wgGroupPermissions;
  659. // DWIM: If all anons can do something, then it is not restricted
  660. return $this->mRestriction != '' && empty($wgGroupPermissions['*'][$this->mRestriction]);
  661. }
  662. /**
  663. * Checks if the given user (identified by an object) can execute this
  664. * special page (as defined by $mRestriction). Can be overridden by sub-
  665. * classes with more complicated permissions schemes.
  666. *
  667. * @param User $user The user to check
  668. * @return bool Does the user have permission to view the page?
  669. */
  670. public function userCanExecute( $user ) {
  671. return $user->isAllowed( $this->mRestriction );
  672. }
  673. /**
  674. * Output an error message telling the user what access level they have to have
  675. */
  676. function displayRestrictionError() {
  677. global $wgOut;
  678. $wgOut->permissionRequired( $this->mRestriction );
  679. }
  680. /**
  681. * Sets headers - this should be called from the execute() method of all derived classes!
  682. */
  683. function setHeaders() {
  684. global $wgOut;
  685. $wgOut->setArticleRelated( false );
  686. $wgOut->setRobotPolicy( "noindex,nofollow" );
  687. $wgOut->setPageTitle( $this->getDescription() );
  688. }
  689. /**
  690. * Default execute method
  691. * Checks user permissions, calls the function given in mFunction
  692. *
  693. * This may be overridden by subclasses.
  694. */
  695. function execute( $par ) {
  696. global $wgUser;
  697. $this->setHeaders();
  698. if ( $this->userCanExecute( $wgUser ) ) {
  699. $func = $this->mFunction;
  700. // only load file if the function does not exist
  701. if(!is_callable($func) and $this->mFile) {
  702. require_once( $this->mFile );
  703. }
  704. $this->outputHeader();
  705. call_user_func( $func, $par, $this );
  706. } else {
  707. $this->displayRestrictionError();
  708. }
  709. }
  710. /**
  711. * Outputs a summary message on top of special pages
  712. * Per default the message key is the canonical name of the special page
  713. * May be overriden, i.e. by extensions to stick with the naming conventions
  714. * for message keys: 'extensionname-xxx'
  715. *
  716. * @param string message key of the summary
  717. */
  718. function outputHeader( $summaryMessageKey = '' ) {
  719. global $wgOut, $wgContLang;
  720. if( $summaryMessageKey == '' ) {
  721. $msg = $wgContLang->lc( $this->name() ) . '-summary';
  722. } else {
  723. $msg = $summaryMessageKey;
  724. }
  725. $out = wfMsgNoTrans( $msg );
  726. if ( ! wfEmptyMsg( $msg, $out ) and $out !== '' and ! $this->including() ) {
  727. $wgOut->wrapWikiMsg( "<div class='mw-specialpage-summary'>\n$1</div>", $msg );
  728. }
  729. }
  730. # Returns the name that goes in the <h1> in the special page itself, and also the name that
  731. # will be listed in Special:Specialpages
  732. #
  733. # Derived classes can override this, but usually it is easier to keep the default behaviour.
  734. # Messages can be added at run-time, see MessageCache.php
  735. function getDescription() {
  736. return wfMsg( strtolower( $this->mName ) );
  737. }
  738. /**
  739. * Get a self-referential title object
  740. */
  741. function getTitle( $subpage = false) {
  742. return self::getTitleFor( $this->mName, $subpage );
  743. }
  744. /**
  745. * Set whether this page is listed in Special:Specialpages, at run-time
  746. */
  747. function setListed( $listed ) {
  748. return wfSetVar( $this->mListed, $listed );
  749. }
  750. /**
  751. * If the special page is a redirect, then get the Title object it redirects to.
  752. * False otherwise.
  753. */
  754. function getRedirect( $subpage ) {
  755. return false;
  756. }
  757. /**
  758. * Return part of the request string for a special redirect page
  759. * This allows passing, e.g. action=history to Special:Mypage, etc.
  760. *
  761. * @return string
  762. */
  763. function getRedirectQuery() {
  764. global $wgRequest;
  765. $params = array();
  766. foreach( $this->mAllowedRedirectParams as $arg ) {
  767. if( $val = $wgRequest->getVal( $arg, false ) )
  768. $params[] = $arg . '=' . $val;
  769. }
  770. return count( $params ) ? implode( '&', $params ) : false;
  771. }
  772. }
  773. /**
  774. * Shortcut to construct a special page which is unlisted by default
  775. * @ingroup SpecialPage
  776. */
  777. class UnlistedSpecialPage extends SpecialPage
  778. {
  779. function UnlistedSpecialPage( $name, $restriction = '', $function = false, $file = 'default' ) {
  780. SpecialPage::SpecialPage( $name, $restriction, false, $function, $file );
  781. }
  782. }
  783. /**
  784. * Shortcut to construct an includable special page
  785. * @ingroup SpecialPage
  786. */
  787. class IncludableSpecialPage extends SpecialPage
  788. {
  789. function IncludableSpecialPage( $name, $restriction = '', $listed = true, $function = false, $file = 'default' ) {
  790. SpecialPage::SpecialPage( $name, $restriction, $listed, $function, $file, true );
  791. }
  792. }
  793. /**
  794. * Shortcut to construct a special page alias.
  795. * @ingroup SpecialPage
  796. */
  797. class SpecialRedirectToSpecial extends UnlistedSpecialPage {
  798. var $redirName, $redirSubpage;
  799. function __construct( $name, $redirName, $redirSubpage = false, $redirectParams = array() ) {
  800. parent::__construct( $name );
  801. $this->redirName = $redirName;
  802. $this->redirSubpage = $redirSubpage;
  803. $this->mAllowedRedirectParams = $redirectParams;
  804. }
  805. function getRedirect( $subpage ) {
  806. if ( $this->redirSubpage === false ) {
  807. return SpecialPage::getTitleFor( $this->redirName, $subpage );
  808. } else {
  809. return SpecialPage::getTitleFor( $this->redirName, $this->redirSubpage );
  810. }
  811. }
  812. }
  813. /** SpecialMypage, SpecialMytalk and SpecialMycontributions special pages
  814. * are used to get user independant links pointing to the user page, talk
  815. * page and list of contributions.
  816. * This can let us cache a single copy of any generated content for all
  817. * users.
  818. */
  819. /**
  820. * Shortcut to construct a special page pointing to current user user's page.
  821. * @ingroup SpecialPage
  822. */
  823. class SpecialMypage extends UnlistedSpecialPage {
  824. function __construct() {
  825. parent::__construct( 'Mypage' );
  826. $this->mAllowedRedirectParams = array( 'action' , 'preload' , 'editintro', 'section' );
  827. }
  828. function getRedirect( $subpage ) {
  829. global $wgUser;
  830. if ( strval( $subpage ) !== '' ) {
  831. return Title::makeTitle( NS_USER, $wgUser->getName() . '/' . $subpage );
  832. } else {
  833. return Title::makeTitle( NS_USER, $wgUser->getName() );
  834. }
  835. }
  836. }
  837. /**
  838. * Shortcut to construct a special page pointing to current user talk page.
  839. * @ingroup SpecialPage
  840. */
  841. class SpecialMytalk extends UnlistedSpecialPage {
  842. function __construct() {
  843. parent::__construct( 'Mytalk' );
  844. $this->mAllowedRedirectParams = array( 'action' , 'preload' , 'editintro', 'section' );
  845. }
  846. function getRedirect( $subpage ) {
  847. global $wgUser;
  848. if ( strval( $subpage ) !== '' ) {
  849. return Title::makeTitle( NS_USER_TALK, $wgUser->getName() . '/' . $subpage );
  850. } else {
  851. return Title::makeTitle( NS_USER_TALK, $wgUser->getName() );
  852. }
  853. }
  854. }
  855. /**
  856. * Shortcut to construct a special page pointing to current user contributions.
  857. * @ingroup SpecialPage
  858. */
  859. class SpecialMycontributions extends UnlistedSpecialPage {
  860. function __construct() {
  861. parent::__construct( 'Mycontributions' );
  862. }
  863. function getRedirect( $subpage ) {
  864. global $wgUser;
  865. return SpecialPage::getTitleFor( 'Contributions', $wgUser->getName() );
  866. }
  867. }