[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Implements Special:RandomInCategory 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 * @author Brian Wolff 23 */ 24 25 /** 26 * Special page to direct the user to a random page 27 * 28 * @note The method used here is rather biased. It is assumed that 29 * the use of this page will be people wanting to get a random page 30 * out of a maintenance category, to fix it up. The method used by 31 * this page should return different pages in an unpredictable fashion 32 * which is hoped to be sufficient, even if some pages are selected 33 * more often than others. 34 * 35 * A more unbiased method could be achieved by adding a cl_random field 36 * to the categorylinks table. 37 * 38 * The method used here is as follows: 39 * * Find the smallest and largest timestamp in the category 40 * * Pick a random timestamp in between 41 * * Pick an offset between 0 and 30 42 * * Get the offset'ed page that is newer than the timestamp selected 43 * The offset is meant to counter the fact the timestamps aren't usually 44 * uniformly distributed, so if things are very non-uniform at least we 45 * won't have the same page selected 99% of the time. 46 * 47 * @ingroup SpecialPage 48 */ 49 class SpecialRandomInCategory extends FormSpecialPage { 50 protected $extra = array(); // Extra SQL statements 51 protected $category = false; // Title object of category 52 protected $maxOffset = 30; // Max amount to fudge randomness by. 53 private $maxTimestamp = null; 54 private $minTimestamp = null; 55 56 public function __construct( $name = 'RandomInCategory' ) { 57 parent::__construct( $name ); 58 } 59 60 /** 61 * Set which category to use. 62 * @param Title $cat 63 */ 64 public function setCategory( Title $cat ) { 65 $this->category = $cat; 66 $this->maxTimestamp = null; 67 $this->minTimestamp = null; 68 } 69 70 protected function getFormFields() { 71 $form = array( 72 'category' => array( 73 'type' => 'text', 74 'label-message' => 'randomincategory-category', 75 'required' => true, 76 ) 77 ); 78 79 return $form; 80 } 81 82 public function requiresWrite() { 83 return false; 84 } 85 86 public function requiresUnblock() { 87 return false; 88 } 89 90 protected function setParameter( $par ) { 91 // if subpage present, fake form submission 92 $this->onSubmit( array( 'category' => $par ) ); 93 } 94 95 public function onSubmit( array $data ) { 96 $cat = false; 97 98 $categoryStr = $data['category']; 99 100 if ( $categoryStr ) { 101 $cat = Title::newFromText( $categoryStr, NS_CATEGORY ); 102 } 103 104 if ( $cat && $cat->getNamespace() !== NS_CATEGORY ) { 105 // Someone searching for something like "Wikipedia:Foo" 106 $cat = Title::makeTitleSafe( NS_CATEGORY, $categoryStr ); 107 } 108 109 if ( $cat ) { 110 $this->setCategory( $cat ); 111 } 112 113 if ( !$this->category && $categoryStr ) { 114 $msg = $this->msg( 'randomincategory-invalidcategory', 115 wfEscapeWikiText( $categoryStr ) ); 116 117 return Status::newFatal( $msg ); 118 119 } elseif ( !$this->category ) { 120 return; // no data sent 121 } 122 123 $title = $this->getRandomTitle(); 124 125 if ( is_null( $title ) ) { 126 $msg = $this->msg( 'randomincategory-nopages', 127 $this->category->getText() ); 128 129 return Status::newFatal( $msg ); 130 } 131 132 $this->getOutput()->redirect( $title->getFullURL() ); 133 } 134 135 /** 136 * Choose a random title. 137 * @return Title|null Title object (or null if nothing to choose from) 138 */ 139 public function getRandomTitle() { 140 // Convert to float, since we do math with the random number. 141 $rand = (float)wfRandom(); 142 $title = null; 143 144 // Given that timestamps are rather unevenly distributed, we also 145 // use an offset between 0 and 30 to make any biases less noticeable. 146 $offset = mt_rand( 0, $this->maxOffset ); 147 148 if ( mt_rand( 0, 1 ) ) { 149 $up = true; 150 } else { 151 $up = false; 152 } 153 154 $row = $this->selectRandomPageFromDB( $rand, $offset, $up ); 155 156 // Try again without the timestamp offset (wrap around the end) 157 if ( !$row ) { 158 $row = $this->selectRandomPageFromDB( false, $offset, $up ); 159 } 160 161 // Maybe the category is really small and offset too high 162 if ( !$row ) { 163 $row = $this->selectRandomPageFromDB( $rand, 0, $up ); 164 } 165 166 // Just get the first entry. 167 if ( !$row ) { 168 $row = $this->selectRandomPageFromDB( false, 0, true ); 169 } 170 171 if ( $row ) { 172 return Title::makeTitle( $row->page_namespace, $row->page_title ); 173 } 174 175 return null; 176 } 177 178 /** 179 * @param float $rand Random number between 0 and 1 180 * @param int $offset Extra offset to fudge randomness 181 * @param bool $up True to get the result above the random number, false for below 182 * 183 * @note The $up parameter is supposed to counteract what would happen if there 184 * was a large gap in the distribution of cl_timestamp values. This way instead 185 * of things to the right of the gap being favoured, both sides of the gap 186 * are favoured. 187 * @return array Query information. 188 */ 189 protected function getQueryInfo( $rand, $offset, $up ) { 190 $op = $up ? '>=' : '<='; 191 $dir = $up ? 'ASC' : 'DESC'; 192 if ( !$this->category instanceof Title ) { 193 throw new MWException( 'No category set' ); 194 } 195 $qi = array( 196 'tables' => array( 'categorylinks', 'page' ), 197 'fields' => array( 'page_title', 'page_namespace' ), 198 'conds' => array_merge( array( 199 'cl_to' => $this->category->getDBKey(), 200 ), $this->extra ), 201 'options' => array( 202 'ORDER BY' => 'cl_timestamp ' . $dir, 203 'LIMIT' => 1, 204 'OFFSET' => $offset 205 ), 206 'join_conds' => array( 207 'page' => array( 'INNER JOIN', 'cl_from = page_id' ) 208 ) 209 ); 210 211 $dbr = wfGetDB( DB_SLAVE ); 212 $minClTime = $this->getTimestampOffset( $rand ); 213 if ( $minClTime ) { 214 $qi['conds'][] = 'cl_timestamp ' . $op . ' ' . 215 $dbr->addQuotes( $dbr->timestamp( $minClTime ) ); 216 } 217 218 return $qi; 219 } 220 221 /** 222 * @param float $rand Random number between 0 and 1 223 * 224 * @return int|bool A random (unix) timestamp from the range of the category or false on failure 225 */ 226 protected function getTimestampOffset( $rand ) { 227 if ( $rand === false ) { 228 return false; 229 } 230 if ( !$this->minTimestamp || !$this->maxTimestamp ) { 231 try { 232 list( $this->minTimestamp, $this->maxTimestamp ) = $this->getMinAndMaxForCat( $this->category ); 233 } catch ( MWException $e ) { 234 // Possibly no entries in category. 235 return false; 236 } 237 } 238 239 $ts = ( $this->maxTimestamp - $this->minTimestamp ) * $rand + $this->minTimestamp; 240 241 return intval( $ts ); 242 } 243 244 /** 245 * Get the lowest and highest timestamp for a category. 246 * 247 * @param Title $category 248 * @return array The lowest and highest timestamp 249 * @throws MWException If category has no entries. 250 */ 251 protected function getMinAndMaxForCat( Title $category ) { 252 $dbr = wfGetDB( DB_SLAVE ); 253 $res = $dbr->selectRow( 254 'categorylinks', 255 array( 256 'low' => 'MIN( cl_timestamp )', 257 'high' => 'MAX( cl_timestamp )' 258 ), 259 array( 260 'cl_to' => $this->category->getDBKey(), 261 ), 262 __METHOD__, 263 array( 264 'LIMIT' => 1 265 ) 266 ); 267 if ( !$res ) { 268 throw new MWException( 'No entries in category' ); 269 } 270 271 return array( wfTimestamp( TS_UNIX, $res->low ), wfTimestamp( TS_UNIX, $res->high ) ); 272 } 273 274 /** 275 * @param float $rand A random number that is converted to a random timestamp 276 * @param int $offset A small offset to make the result seem more "random" 277 * @param bool $up Get the result above the random value 278 * @param string $fname The name of the calling method 279 * @return array Info for the title selected. 280 */ 281 private function selectRandomPageFromDB( $rand, $offset, $up, $fname = __METHOD__ ) { 282 $dbr = wfGetDB( DB_SLAVE ); 283 284 $query = $this->getQueryInfo( $rand, $offset, $up ); 285 $res = $dbr->select( 286 $query['tables'], 287 $query['fields'], 288 $query['conds'], 289 $fname, 290 $query['options'], 291 $query['join_conds'] 292 ); 293 294 return $res->fetchObject(); 295 } 296 297 protected function getGroupName() { 298 return 'redirects'; 299 } 300 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |