MediaWiki REL1_30
SpecialRandomInCategory.php
Go to the documentation of this file.
1<?php
51 protected $extra = []; // Extra SQL statements
53 protected $category = false; // Title object of category
55 protected $maxOffset = 30; // Max amount to fudge randomness by.
57 private $maxTimestamp = null;
59 private $minTimestamp = null;
60
61 public function __construct( $name = 'RandomInCategory' ) {
62 parent::__construct( $name );
63 }
64
69 public function setCategory( Title $cat ) {
70 $this->category = $cat;
71 $this->maxTimestamp = null;
72 $this->minTimestamp = null;
73 }
74
75 protected function getFormFields() {
76 $this->addHelpLink( 'Help:RandomInCategory' );
77
78 return [
79 'category' => [
80 'type' => 'title',
81 'namespace' => NS_CATEGORY,
82 'relative' => true,
83 'label-message' => 'randomincategory-category',
84 'required' => true,
85 ]
86 ];
87 }
88
89 public function requiresWrite() {
90 return false;
91 }
92
93 public function requiresUnblock() {
94 return false;
95 }
96
97 protected function getDisplayFormat() {
98 return 'ooui';
99 }
100
101 protected function alterForm( HTMLForm $form ) {
102 $form->setSubmitTextMsg( 'randomincategory-submit' );
103 }
104
105 protected function setParameter( $par ) {
106 // if subpage present, fake form submission
107 $this->onSubmit( [ 'category' => $par ] );
108 }
109
110 public function onSubmit( array $data ) {
111 $cat = false;
112
113 $categoryStr = $data['category'];
114
115 if ( $categoryStr ) {
116 $cat = Title::newFromText( $categoryStr, NS_CATEGORY );
117 }
118
119 if ( $cat && $cat->getNamespace() !== NS_CATEGORY ) {
120 // Someone searching for something like "Wikipedia:Foo"
121 $cat = Title::makeTitleSafe( NS_CATEGORY, $categoryStr );
122 }
123
124 if ( $cat ) {
125 $this->setCategory( $cat );
126 }
127
128 if ( !$this->category && $categoryStr ) {
129 $msg = $this->msg( 'randomincategory-invalidcategory',
130 wfEscapeWikiText( $categoryStr ) );
131
132 return Status::newFatal( $msg );
133
134 } elseif ( !$this->category ) {
135 return false; // no data sent
136 }
137
138 $title = $this->getRandomTitle();
139
140 if ( is_null( $title ) ) {
141 $msg = $this->msg( 'randomincategory-nopages',
142 $this->category->getText() );
143
144 return Status::newFatal( $msg );
145 }
146
147 $this->getOutput()->redirect( $title->getFullURL() );
148 }
149
154 public function getRandomTitle() {
155 // Convert to float, since we do math with the random number.
156 $rand = (float)wfRandom();
157 $title = null;
158
159 // Given that timestamps are rather unevenly distributed, we also
160 // use an offset between 0 and 30 to make any biases less noticeable.
161 $offset = mt_rand( 0, $this->maxOffset );
162
163 if ( mt_rand( 0, 1 ) ) {
164 $up = true;
165 } else {
166 $up = false;
167 }
168
169 $row = $this->selectRandomPageFromDB( $rand, $offset, $up );
170
171 // Try again without the timestamp offset (wrap around the end)
172 if ( !$row ) {
173 $row = $this->selectRandomPageFromDB( false, $offset, $up );
174 }
175
176 // Maybe the category is really small and offset too high
177 if ( !$row ) {
178 $row = $this->selectRandomPageFromDB( $rand, 0, $up );
179 }
180
181 // Just get the first entry.
182 if ( !$row ) {
183 $row = $this->selectRandomPageFromDB( false, 0, true );
184 }
185
186 if ( $row ) {
187 return Title::makeTitle( $row->page_namespace, $row->page_title );
188 }
189
190 return null;
191 }
192
204 protected function getQueryInfo( $rand, $offset, $up ) {
205 $op = $up ? '>=' : '<=';
206 $dir = $up ? 'ASC' : 'DESC';
207 if ( !$this->category instanceof Title ) {
208 throw new MWException( 'No category set' );
209 }
210 $qi = [
211 'tables' => [ 'categorylinks', 'page' ],
212 'fields' => [ 'page_title', 'page_namespace' ],
213 'conds' => array_merge( [
214 'cl_to' => $this->category->getDBkey(),
215 ], $this->extra ),
216 'options' => [
217 'ORDER BY' => 'cl_timestamp ' . $dir,
218 'LIMIT' => 1,
219 'OFFSET' => $offset
220 ],
221 'join_conds' => [
222 'page' => [ 'INNER JOIN', 'cl_from = page_id' ]
223 ]
224 ];
225
227 $minClTime = $this->getTimestampOffset( $rand );
228 if ( $minClTime ) {
229 $qi['conds'][] = 'cl_timestamp ' . $op . ' ' .
230 $dbr->addQuotes( $dbr->timestamp( $minClTime ) );
231 }
232
233 return $qi;
234 }
235
241 protected function getTimestampOffset( $rand ) {
242 if ( $rand === false ) {
243 return false;
244 }
245 if ( !$this->minTimestamp || !$this->maxTimestamp ) {
246 try {
247 list( $this->minTimestamp, $this->maxTimestamp ) = $this->getMinAndMaxForCat( $this->category );
248 } catch ( Exception $e ) {
249 // Possibly no entries in category.
250 return false;
251 }
252 }
253
254 $ts = ( $this->maxTimestamp - $this->minTimestamp ) * $rand + $this->minTimestamp;
255
256 return intval( $ts );
257 }
258
266 protected function getMinAndMaxForCat( Title $category ) {
268 $res = $dbr->selectRow(
269 'categorylinks',
270 [
271 'low' => 'MIN( cl_timestamp )',
272 'high' => 'MAX( cl_timestamp )'
273 ],
274 [
275 'cl_to' => $this->category->getDBkey(),
276 ],
277 __METHOD__,
278 [
279 'LIMIT' => 1
280 ]
281 );
282 if ( !$res ) {
283 throw new MWException( 'No entries in category' );
284 }
285
286 return [ wfTimestamp( TS_UNIX, $res->low ), wfTimestamp( TS_UNIX, $res->high ) ];
287 }
288
296 private function selectRandomPageFromDB( $rand, $offset, $up, $fname = __METHOD__ ) {
298
299 $query = $this->getQueryInfo( $rand, $offset, $up );
300 $res = $dbr->select(
301 $query['tables'],
302 $query['fields'],
303 $query['conds'],
304 $fname,
305 $query['options'],
306 $query['join_conds']
307 );
308
309 return $res->fetchObject();
310 }
311
312 protected function getGroupName() {
313 return 'redirects';
314 }
315}
$dir
Definition Autoload.php:8
wfRandom()
Get a random decimal value between 0 and 1, in a way not likely to give duplicate values for any real...
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined.
Definition Setup.php:36
Special page which uses an HTMLForm to handle processing.
string $par
The sub-page of the special page.
Object handling generic submission, CSRF protection, layout and other logic for UI forms.
Definition HTMLForm.php:128
MediaWiki exception.
getOutput()
Get the OutputPage being used for this instance.
msg( $key)
Wrapper around wfMessage that sets the current context.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Special page to direct the user to a random page.
setParameter( $par)
Maybe do something interesting with the subpage parameter.
requiresWrite()
Whether this action requires the wiki not to be locked.
__construct( $name='RandomInCategory')
getDisplayFormat()
Get display format for the form.
requiresUnblock()
Whether this action cannot be executed by a blocked user.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
getRandomTitle()
Choose a random title.
alterForm(HTMLForm $form)
Play with the HTMLForm if you need to more substantially.
onSubmit(array $data)
Process the form on POST submission.
getMinAndMaxForCat(Title $category)
Get the lowest and highest timestamp for a category.
setCategory(Title $cat)
Set which category to use.
getQueryInfo( $rand, $offset, $up)
selectRandomPageFromDB( $rand, $offset, $up, $fname=__METHOD__)
getFormFields()
Get an HTMLForm descriptor array.
Represents a title within MediaWiki.
Definition Title.php:39
if(! $regexes) $dbr
Definition cleanup.php:94
$res
Definition database.txt:21
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition hooks.txt:1610
returning false will NOT prevent logging $e
Definition hooks.txt:2146
const NS_CATEGORY
Definition Defines.php:79
const DB_REPLICA
Definition defines.php:25