[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/specials/ -> SpecialRandomInCategory.php (source)

   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  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1