MediaWiki REL1_41
SpecialRandomPage.php
Go to the documentation of this file.
1<?php
25namespace MediaWiki\Specials;
26
32
39 private $namespaces; // namespaces to select pages from
40 protected $isRedir = false; // should the result be a redirect?
41 protected $extra = []; // Extra SQL statements
42
43 private IConnectionProvider $dbProvider;
44
49 public function __construct(
50 $dbProvider = null,
51 NamespaceInfo $nsInfo = null
52 ) {
53 parent::__construct( is_string( $dbProvider ) ? $dbProvider : 'Randompage' );
54 if ( !$dbProvider instanceof IConnectionProvider || !$nsInfo ) {
55 wfDeprecated( __METHOD__ . ' without injected services', '1.41' );
56 }
57 // This class is extended and therefor fallback to global state - T265308
59 $this->dbProvider = $dbProvider instanceof IConnectionProvider
60 ? $dbProvider
61 : $services->getDBLoadBalancerFactory();
62 $nsInfo ??= $services->getNamespaceInfo();
63 $this->namespaces = $nsInfo->getContentNamespaces();
64 }
65
66 public function getNamespaces() {
67 return $this->namespaces;
68 }
69
70 public function setNamespace( $ns ) {
71 if ( !$this->isValidNS( $ns ) ) {
72 $ns = NS_MAIN;
73 }
74 $this->namespaces = [ $ns ];
75 }
76
77 private function isValidNS( $ns ) {
78 return $ns !== false && $ns >= 0;
79 }
80
81 // select redirects instead of normal pages?
82 public function isRedirect() {
83 return $this->isRedir;
84 }
85
86 public function execute( $par ) {
87 $this->parsePar( $par );
88
89 $title = $this->getRandomTitle();
90
91 if ( $title === null ) {
92 $this->setHeaders();
93 // Message: randompage-nopages, randomredirect-nopages
94 $this->getOutput()->addWikiMsg( strtolower( $this->getName() ) . '-nopages',
95 $this->getNsList(), count( $this->namespaces ) );
96
97 return;
98 }
99
100 $redirectParam = $this->isRedirect() ? [ 'redirect' => 'no' ] : [];
101 $query = array_merge( $this->getRequest()->getValues(), $redirectParam );
102 unset( $query['title'] );
103 $this->getOutput()->redirect( $title->getFullURL( $query ) );
104 }
105
111 private function parsePar( $par ) {
112 // Testing for stringiness since we want to catch
113 // the empty string to mean main namespace only.
114 if ( is_string( $par ) ) {
115 $ns = $this->getContentLanguage()->getNsIndex( $par );
116 if ( $ns === false && strpos( $par, ',' ) !== false ) {
117 $nsList = [];
118 // Comma separated list
119 $parSplit = explode( ',', $par );
120 foreach ( $parSplit as $potentialNs ) {
121 $ns = $this->getContentLanguage()->getNsIndex( $potentialNs );
122 if ( $this->isValidNS( $ns ) ) {
123 $nsList[] = $ns;
124 }
125 // Remove duplicate values, and re-index array
126 $nsList = array_unique( $nsList );
127 $nsList = array_values( $nsList );
128 if ( $nsList !== [] ) {
129 $this->namespaces = $nsList;
130 }
131 }
132 } else {
133 // Note, that the case of $par being something
134 // like "main" which is not a namespace, falls
135 // through to here, and sets NS_MAIN, allowing
136 // Special:Random/main or Special:Random/article
137 // to work as expected.
138 $this->setNamespace( $this->getContentLanguage()->getNsIndex( $par ) );
139 }
140 }
141 }
142
148 private function getNsList() {
149 $contLang = $this->getContentLanguage();
150 $nsNames = [];
151 foreach ( $this->namespaces as $n ) {
152 if ( $n === NS_MAIN ) {
153 $nsNames[] = $this->msg( 'blanknamespace' )->plain();
154 } else {
155 $nsNames[] = $contLang->getNsText( $n );
156 }
157 }
158
159 return $contLang->commaList( $nsNames );
160 }
161
166 public function getRandomTitle() {
167 $randstr = wfRandom();
168 $title = null;
169
170 if ( !$this->getHookRunner()->onSpecialRandomGetRandomTitle(
171 $randstr, $this->isRedir, $this->namespaces,
172 // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
173 $this->extra, $title )
174 ) {
175 return $title;
176 }
177
178 $row = $this->selectRandomPageFromDB( $randstr, __METHOD__ );
179
180 /* If we picked a value that was higher than any in
181 * the DB, wrap around and select the page with the
182 * lowest value instead! One might think this would
183 * skew the distribution, but in fact it won't cause
184 * any more bias than what the page_random scheme
185 * causes anyway. Trust me, I'm a mathematician. :)
186 */
187 if ( !$row ) {
188 $row = $this->selectRandomPageFromDB( "0", __METHOD__ );
189 }
190
191 if ( $row ) {
192 return Title::makeTitleSafe( $row->page_namespace, $row->page_title );
193 }
194
195 return null;
196 }
197
198 protected function getQueryInfo( $randstr ) {
199 $redirect = $this->isRedirect() ? 1 : 0;
200 $tables = [ 'page' ];
201 $conds = array_merge( [
202 'page_namespace' => $this->namespaces,
203 'page_is_redirect' => $redirect,
204 'page_random >= ' . $randstr
205 ], $this->extra );
206 $joinConds = [];
207
208 // Allow extensions to modify the query
209 $this->getHookRunner()->onRandomPageQuery( $tables, $conds, $joinConds );
210
211 return [
212 'tables' => $tables,
213 'fields' => [ 'page_title', 'page_namespace' ],
214 'conds' => $conds,
215 'options' => [
216 'ORDER BY' => 'page_random',
217 'LIMIT' => 1,
218 ],
219 'join_conds' => $joinConds
220 ];
221 }
222
223 private function selectRandomPageFromDB( $randstr, $fname = __METHOD__ ) {
224 $dbr = $this->dbProvider->getReplicaDatabase();
225
226 $query = $this->getQueryInfo( $randstr );
227 $res = $dbr->select(
228 $query['tables'],
229 $query['fields'],
230 $query['conds'],
231 $fname,
232 $query['options'],
233 $query['join_conds']
234 );
235
236 return $res->fetchObject();
237 }
238
239 protected function getGroupName() {
240 return 'redirects';
241 }
242}
243
248class_alias( SpecialRandomPage::class, 'RandomPage' );
249
254class_alias( SpecialRandomPage::class, 'SpecialRandomPage' );
const NS_MAIN
Definition Defines.php:64
wfRandom()
Get a random decimal value in the domain of [0, 1), in a way not likely to give duplicate values for ...
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
Parent class for all special pages.
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
getRequest()
Get the WebRequest being used for this instance.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getOutput()
Get the OutputPage being used for this instance.
getContentLanguage()
Shortcut to get content language.
getName()
Get the name of this Special Page.
Special page to direct the user to a random page.
execute( $par)
Default execute method Checks user permissions.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
__construct( $dbProvider=null, NamespaceInfo $nsInfo=null)
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Represents a title within MediaWiki.
Definition Title.php:76
Provide primary and replica IDatabase connections.
This program is free software; you can redistribute it and/or modify it under the terms of the GNU Ge...