SpecialAllPages.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. <?php
  2. /**
  3. * Implements Special:Allpages
  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. * @ingroup SpecialPage
  22. */
  23. /**
  24. * Implements Special:Allpages
  25. *
  26. * @ingroup SpecialPage
  27. * @todo Rewrite using IndexPager
  28. */
  29. class SpecialAllPages extends IncludableSpecialPage {
  30. /**
  31. * Maximum number of pages to show on single subpage.
  32. *
  33. * @var int $maxPerPage
  34. */
  35. protected $maxPerPage = 345;
  36. /**
  37. * Determines, which message describes the input field 'nsfrom'.
  38. *
  39. * @var string $nsfromMsg
  40. */
  41. protected $nsfromMsg = 'allpagesfrom';
  42. /**
  43. * @param string $name Name of the special page, as seen in links and URLs (default: 'Allpages')
  44. */
  45. function __construct( $name = 'Allpages' ) {
  46. parent::__construct( $name );
  47. }
  48. /**
  49. * Entry point : initialise variables and call subfunctions.
  50. *
  51. * @param string $par Becomes "FOO" when called like Special:Allpages/FOO (default null)
  52. */
  53. function execute( $par ) {
  54. $request = $this->getRequest();
  55. $out = $this->getOutput();
  56. $this->setHeaders();
  57. $this->outputHeader();
  58. $out->allowClickjacking();
  59. # GET values
  60. $from = $request->getVal( 'from', null );
  61. $to = $request->getVal( 'to', null );
  62. $namespace = $request->getInt( 'namespace' );
  63. $miserMode = (bool)$this->getConfig()->get( 'MiserMode' );
  64. // Redirects filter is disabled in MiserMode
  65. $hideredirects = $request->getBool( 'hideredirects', false ) && !$miserMode;
  66. $namespaces = $this->getLanguage()->getNamespaces();
  67. $out->setPageTitle(
  68. ( $namespace > 0 && array_key_exists( $namespace, $namespaces ) ) ?
  69. $this->msg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) :
  70. $this->msg( 'allarticles' )
  71. );
  72. $out->addModuleStyles( 'mediawiki.special' );
  73. if ( $par !== null ) {
  74. $this->showChunk( $namespace, $par, $to, $hideredirects );
  75. } elseif ( $from !== null && $to === null ) {
  76. $this->showChunk( $namespace, $from, $to, $hideredirects );
  77. } else {
  78. $this->showToplevel( $namespace, $from, $to, $hideredirects );
  79. }
  80. }
  81. /**
  82. * Outputs the HTMLForm used on this page
  83. *
  84. * @param int $namespace A namespace constant (default NS_MAIN).
  85. * @param string $from DbKey we are starting listing at.
  86. * @param string $to DbKey we are ending listing at.
  87. * @param bool $hideRedirects Don't show redirects (default false)
  88. */
  89. protected function outputHTMLForm( $namespace = NS_MAIN,
  90. $from = '', $to = '', $hideRedirects = false
  91. ) {
  92. $miserMode = (bool)$this->getConfig()->get( 'MiserMode' );
  93. $formDescriptor = [
  94. 'from' => [
  95. 'type' => 'text',
  96. 'name' => 'from',
  97. 'id' => 'nsfrom',
  98. 'size' => 30,
  99. 'label-message' => 'allpagesfrom',
  100. 'default' => str_replace( '_', ' ', $from ),
  101. ],
  102. 'to' => [
  103. 'type' => 'text',
  104. 'name' => 'to',
  105. 'id' => 'nsto',
  106. 'size' => 30,
  107. 'label-message' => 'allpagesto',
  108. 'default' => str_replace( '_', ' ', $to ),
  109. ],
  110. 'namespace' => [
  111. 'type' => 'namespaceselect',
  112. 'name' => 'namespace',
  113. 'id' => 'namespace',
  114. 'label-message' => 'namespace',
  115. 'all' => null,
  116. 'default' => $namespace,
  117. ],
  118. 'hideredirects' => [
  119. 'type' => 'check',
  120. 'name' => 'hideredirects',
  121. 'id' => 'hidredirects',
  122. 'label-message' => 'allpages-hide-redirects',
  123. 'value' => $hideRedirects,
  124. ],
  125. ];
  126. if ( $miserMode ) {
  127. unset( $formDescriptor['hideredirects'] );
  128. }
  129. $context = new DerivativeContext( $this->getContext() );
  130. $context->setTitle( $this->getPageTitle() ); // Remove subpage
  131. $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $context );
  132. $htmlForm
  133. ->setMethod( 'get' )
  134. ->setWrapperLegendMsg( 'allpages' )
  135. ->setSubmitTextMsg( 'allpagessubmit' )
  136. ->prepareForm()
  137. ->displayForm( false );
  138. }
  139. /**
  140. * @param int $namespace (default NS_MAIN)
  141. * @param string $from List all pages from this name
  142. * @param string $to List all pages to this name
  143. * @param bool $hideredirects Don't show redirects (default false)
  144. */
  145. function showToplevel( $namespace = NS_MAIN, $from = '', $to = '', $hideredirects = false ) {
  146. $from = Title::makeTitleSafe( $namespace, $from );
  147. $to = Title::makeTitleSafe( $namespace, $to );
  148. $from = ( $from && $from->isLocal() ) ? $from->getDBkey() : null;
  149. $to = ( $to && $to->isLocal() ) ? $to->getDBkey() : null;
  150. $this->showChunk( $namespace, $from, $to, $hideredirects );
  151. }
  152. /**
  153. * @param int $namespace Namespace (Default NS_MAIN)
  154. * @param string|false $from List all pages from this name (default false)
  155. * @param string|false $to List all pages to this name (default false)
  156. * @param bool $hideredirects Don't show redirects (default false)
  157. */
  158. function showChunk( $namespace = NS_MAIN, $from = false, $to = false, $hideredirects = false ) {
  159. $output = $this->getOutput();
  160. $fromList = $this->getNamespaceKeyAndText( $namespace, $from );
  161. $toList = $this->getNamespaceKeyAndText( $namespace, $to );
  162. $namespaces = $this->getContext()->getLanguage()->getNamespaces();
  163. $n = 0;
  164. $prevTitle = null;
  165. if ( !$fromList || !$toList ) {
  166. $out = $this->msg( 'allpagesbadtitle' )->parseAsBlock();
  167. } elseif ( !array_key_exists( $namespace, $namespaces ) ) {
  168. // Show errormessage and reset to NS_MAIN
  169. $out = $this->msg( 'allpages-bad-ns', $namespace )->parse();
  170. $namespace = NS_MAIN;
  171. } else {
  172. list( $namespace, $fromKey, $from ) = $fromList;
  173. list( , $toKey, $to ) = $toList;
  174. $dbr = wfGetDB( DB_REPLICA );
  175. $filterConds = [ 'page_namespace' => $namespace ];
  176. if ( $hideredirects ) {
  177. $filterConds['page_is_redirect'] = 0;
  178. }
  179. $conds = $filterConds;
  180. $conds[] = 'page_title >= ' . $dbr->addQuotes( $fromKey );
  181. if ( $toKey !== "" ) {
  182. $conds[] = 'page_title <= ' . $dbr->addQuotes( $toKey );
  183. }
  184. $res = $dbr->select( 'page',
  185. [ 'page_namespace', 'page_title', 'page_is_redirect', 'page_id' ],
  186. $conds,
  187. __METHOD__,
  188. [
  189. 'ORDER BY' => 'page_title',
  190. 'LIMIT' => $this->maxPerPage + 1,
  191. 'USE INDEX' => 'name_title',
  192. ]
  193. );
  194. $linkRenderer = $this->getLinkRenderer();
  195. if ( $res->numRows() > 0 ) {
  196. $out = Html::openElement( 'ul', [ 'class' => 'mw-allpages-chunk' ] );
  197. while ( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) {
  198. $t = Title::newFromRow( $s );
  199. if ( $t ) {
  200. $out .= '<li' .
  201. ( $s->page_is_redirect ? ' class="allpagesredirect"' : '' ) .
  202. '>' .
  203. $linkRenderer->makeLink( $t ) .
  204. "</li>\n";
  205. } else {
  206. $out .= '<li>[[' . htmlspecialchars( $s->page_title ) . "]]</li>\n";
  207. }
  208. $n++;
  209. }
  210. $out .= Html::closeElement( 'ul' );
  211. if ( $res->numRows() > 2 ) {
  212. // Only apply CSS column styles if there's more than 2 entries.
  213. // Otherwise, rendering is broken as "mw-allpages-body"'s CSS column count is 3.
  214. $out = Html::rawElement( 'div', [ 'class' => 'mw-allpages-body' ], $out );
  215. }
  216. } else {
  217. $out = '';
  218. }
  219. if ( $fromKey !== '' && !$this->including() ) {
  220. # Get the first title from previous chunk
  221. $prevConds = $filterConds;
  222. $prevConds[] = 'page_title < ' . $dbr->addQuotes( $fromKey );
  223. $prevKey = $dbr->selectField(
  224. 'page',
  225. 'page_title',
  226. $prevConds,
  227. __METHOD__,
  228. [ 'ORDER BY' => 'page_title DESC', 'OFFSET' => $this->maxPerPage - 1 ]
  229. );
  230. if ( $prevKey === false ) {
  231. # The previous chunk is not complete, need to link to the very first title
  232. # available in the database
  233. $prevKey = $dbr->selectField(
  234. 'page',
  235. 'page_title',
  236. $prevConds,
  237. __METHOD__,
  238. [ 'ORDER BY' => 'page_title' ]
  239. );
  240. }
  241. if ( $prevKey !== false ) {
  242. $prevTitle = Title::makeTitle( $namespace, $prevKey );
  243. }
  244. }
  245. }
  246. if ( $this->including() ) {
  247. $output->addHTML( $out );
  248. return;
  249. }
  250. $navLinks = [];
  251. $self = $this->getPageTitle();
  252. $linkRenderer = $this->getLinkRenderer();
  253. // Generate a "previous page" link if needed
  254. if ( $prevTitle ) {
  255. $query = [ 'from' => $prevTitle->getText() ];
  256. if ( $namespace ) {
  257. $query['namespace'] = $namespace;
  258. }
  259. if ( $hideredirects ) {
  260. $query['hideredirects'] = $hideredirects;
  261. }
  262. $navLinks[] = $linkRenderer->makeKnownLink(
  263. $self,
  264. $this->msg( 'prevpage', $prevTitle->getText() )->text(),
  265. [],
  266. $query
  267. );
  268. }
  269. // Generate a "next page" link if needed
  270. if ( $n == $this->maxPerPage && $s = $res->fetchObject() ) {
  271. # $s is the first link of the next chunk
  272. $t = Title::makeTitle( $namespace, $s->page_title );
  273. $query = [ 'from' => $t->getText() ];
  274. if ( $namespace ) {
  275. $query['namespace'] = $namespace;
  276. }
  277. if ( $hideredirects ) {
  278. $query['hideredirects'] = $hideredirects;
  279. }
  280. $navLinks[] = $linkRenderer->makeKnownLink(
  281. $self,
  282. $this->msg( 'nextpage', $t->getText() )->text(),
  283. [],
  284. $query
  285. );
  286. }
  287. $this->outputHTMLForm( $namespace, $from, $to, $hideredirects );
  288. if ( count( $navLinks ) ) {
  289. // Add pagination links
  290. $pagination = Html::rawElement( 'div',
  291. [ 'class' => 'mw-allpages-nav' ],
  292. $this->getLanguage()->pipeList( $navLinks )
  293. );
  294. $output->addHTML( $pagination );
  295. $out .= Html::element( 'hr' ) . $pagination; // Footer
  296. }
  297. $output->addHTML( $out );
  298. }
  299. /**
  300. * @param int $ns The namespace of the article
  301. * @param string $text The name of the article
  302. * @return array|null [ int namespace, string dbkey, string pagename ] or null on error
  303. */
  304. protected function getNamespaceKeyAndText( $ns, $text ) {
  305. if ( $text == '' ) {
  306. # shortcut for common case
  307. return [ $ns, '', '' ];
  308. }
  309. $t = Title::makeTitleSafe( $ns, $text );
  310. if ( $t && $t->isLocal() ) {
  311. return [ $t->getNamespace(), $t->getDBkey(), $t->getText() ];
  312. } elseif ( $t ) {
  313. return null;
  314. }
  315. # try again, in case the problem was an empty pagename
  316. $text = preg_replace( '/(#|$)/', 'X$1', $text );
  317. $t = Title::makeTitleSafe( $ns, $text );
  318. if ( $t && $t->isLocal() ) {
  319. return [ $t->getNamespace(), '', '' ];
  320. } else {
  321. return null;
  322. }
  323. }
  324. /**
  325. * Return an array of subpages beginning with $search that this special page will accept.
  326. *
  327. * @param string $search Prefix to search for
  328. * @param int $limit Maximum number of results to return (usually 10)
  329. * @param int $offset Number of results to skip (usually 0)
  330. * @return string[] Matching subpages
  331. */
  332. public function prefixSearchSubpages( $search, $limit, $offset ) {
  333. return $this->prefixSearchString( $search, $limit, $offset );
  334. }
  335. protected function getGroupName() {
  336. return 'pages';
  337. }
  338. }