[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/ -> Category.php (source)

   1  <?php
   2  /**
   3   * Representation for a category.
   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   * @author Simetrical
  22   */
  23  
  24  /**
  25   * Category objects are immutable, strictly speaking. If you call methods that change the database,
  26   * like to refresh link counts, the objects will be appropriately reinitialized.
  27   * Member variables are lazy-initialized.
  28   *
  29   * @todo Move some stuff from CategoryPage.php to here, and use that.
  30   */
  31  class Category {
  32      /** Name of the category, normalized to DB-key form */
  33      private $mName = null;
  34      private $mID = null;
  35      /**
  36       * Category page title
  37       * @var Title
  38       */
  39      private $mTitle = null;
  40      /** Counts of membership (cat_pages, cat_subcats, cat_files) */
  41      private $mPages = null, $mSubcats = null, $mFiles = null;
  42  
  43  	private function __construct() {
  44      }
  45  
  46      /**
  47       * Set up all member variables using a database query.
  48       * @throws MWException
  49       * @return bool True on success, false on failure.
  50       */
  51  	protected function initialize() {
  52          if ( $this->mName === null && $this->mID === null ) {
  53              throw new MWException( __METHOD__ . ' has both names and IDs null' );
  54          } elseif ( $this->mID === null ) {
  55              $where = array( 'cat_title' => $this->mName );
  56          } elseif ( $this->mName === null ) {
  57              $where = array( 'cat_id' => $this->mID );
  58          } else {
  59              # Already initialized
  60              return true;
  61          }
  62  
  63          wfProfileIn( __METHOD__ );
  64  
  65          $dbr = wfGetDB( DB_SLAVE );
  66          $row = $dbr->selectRow(
  67              'category',
  68              array( 'cat_id', 'cat_title', 'cat_pages', 'cat_subcats', 'cat_files' ),
  69              $where,
  70              __METHOD__
  71          );
  72  
  73          wfProfileOut( __METHOD__ );
  74  
  75          if ( !$row ) {
  76              # Okay, there were no contents.  Nothing to initialize.
  77              if ( $this->mTitle ) {
  78                  # If there is a title object but no record in the category table,
  79                  # treat this as an empty category.
  80                  $this->mID = false;
  81                  $this->mName = $this->mTitle->getDBkey();
  82                  $this->mPages = 0;
  83                  $this->mSubcats = 0;
  84                  $this->mFiles = 0;
  85  
  86                  return true;
  87              } else {
  88                  return false; # Fail
  89              }
  90          }
  91  
  92          $this->mID = $row->cat_id;
  93          $this->mName = $row->cat_title;
  94          $this->mPages = $row->cat_pages;
  95          $this->mSubcats = $row->cat_subcats;
  96          $this->mFiles = $row->cat_files;
  97  
  98          # (bug 13683) If the count is negative, then 1) it's obviously wrong
  99          # and should not be kept, and 2) we *probably* don't have to scan many
 100          # rows to obtain the correct figure, so let's risk a one-time recount.
 101          if ( $this->mPages < 0 || $this->mSubcats < 0 || $this->mFiles < 0 ) {
 102              $this->refreshCounts();
 103          }
 104  
 105          return true;
 106      }
 107  
 108      /**
 109       * Factory function.
 110       *
 111       * @param array $name A category name (no "Category:" prefix).  It need
 112       *   not be normalized, with spaces replaced by underscores.
 113       * @return mixed Category, or false on a totally invalid name
 114       */
 115  	public static function newFromName( $name ) {
 116          $cat = new self();
 117          $title = Title::makeTitleSafe( NS_CATEGORY, $name );
 118  
 119          if ( !is_object( $title ) ) {
 120              return false;
 121          }
 122  
 123          $cat->mTitle = $title;
 124          $cat->mName = $title->getDBkey();
 125  
 126          return $cat;
 127      }
 128  
 129      /**
 130       * Factory function.
 131       *
 132       * @param Title $title Title for the category page
 133       * @return Category|bool On a totally invalid name
 134       */
 135  	public static function newFromTitle( $title ) {
 136          $cat = new self();
 137  
 138          $cat->mTitle = $title;
 139          $cat->mName = $title->getDBkey();
 140  
 141          return $cat;
 142      }
 143  
 144      /**
 145       * Factory function.
 146       *
 147       * @param int $id A category id
 148       * @return Category
 149       */
 150  	public static function newFromID( $id ) {
 151          $cat = new self();
 152          $cat->mID = intval( $id );
 153          return $cat;
 154      }
 155  
 156      /**
 157       * Factory function, for constructing a Category object from a result set
 158       *
 159       * @param object $row Result set row, must contain the cat_xxx fields. If the
 160       *   fields are null, the resulting Category object will represent an empty
 161       *   category if a title object was given. If the fields are null and no
 162       *   title was given, this method fails and returns false.
 163       * @param Title $title Optional title object for the category represented by
 164       *   the given row. May be provided if it is already known, to avoid having
 165       *   to re-create a title object later.
 166       * @return Category
 167       */
 168  	public static function newFromRow( $row, $title = null ) {
 169          $cat = new self();
 170          $cat->mTitle = $title;
 171  
 172          # NOTE: the row often results from a LEFT JOIN on categorylinks. This may result in
 173          #       all the cat_xxx fields being null, if the category page exists, but nothing
 174          #       was ever added to the category. This case should be treated link an empty
 175          #       category, if possible.
 176  
 177          if ( $row->cat_title === null ) {
 178              if ( $title === null ) {
 179                  # the name is probably somewhere in the row, for example as page_title,
 180                  # but we can't know that here...
 181                  return false;
 182              } else {
 183                  # if we have a title object, fetch the category name from there
 184                  $cat->mName = $title->getDBkey();
 185              }
 186  
 187              $cat->mID = false;
 188              $cat->mSubcats = 0;
 189              $cat->mPages = 0;
 190              $cat->mFiles = 0;
 191          } else {
 192              $cat->mName = $row->cat_title;
 193              $cat->mID = $row->cat_id;
 194              $cat->mSubcats = $row->cat_subcats;
 195              $cat->mPages = $row->cat_pages;
 196              $cat->mFiles = $row->cat_files;
 197          }
 198  
 199          return $cat;
 200      }
 201  
 202      /**
 203       * @return mixed DB key name, or false on failure
 204       */
 205  	public function getName() {
 206          return $this->getX( 'mName' );
 207      }
 208  
 209      /**
 210       * @return mixed Category ID, or false on failure
 211       */
 212  	public function getID() {
 213          return $this->getX( 'mID' );
 214      }
 215  
 216      /**
 217       * @return mixed Total number of member pages, or false on failure
 218       */
 219  	public function getPageCount() {
 220          return $this->getX( 'mPages' );
 221      }
 222  
 223      /**
 224       * @return mixed Number of subcategories, or false on failure
 225       */
 226  	public function getSubcatCount() {
 227          return $this->getX( 'mSubcats' );
 228      }
 229  
 230      /**
 231       * @return mixed Number of member files, or false on failure
 232       */
 233  	public function getFileCount() {
 234          return $this->getX( 'mFiles' );
 235      }
 236  
 237      /**
 238       * @return Title|bool Title for this category, or false on failure.
 239       */
 240  	public function getTitle() {
 241          if ( $this->mTitle ) {
 242              return $this->mTitle;
 243          }
 244  
 245          if ( !$this->initialize() ) {
 246              return false;
 247          }
 248  
 249          $this->mTitle = Title::makeTitleSafe( NS_CATEGORY, $this->mName );
 250          return $this->mTitle;
 251      }
 252  
 253      /**
 254       * Fetch a TitleArray of up to $limit category members, beginning after the
 255       * category sort key $offset.
 256       * @param int $limit
 257       * @param string $offset
 258       * @return TitleArray TitleArray object for category members.
 259       */
 260  	public function getMembers( $limit = false, $offset = '' ) {
 261          wfProfileIn( __METHOD__ );
 262  
 263          $dbr = wfGetDB( DB_SLAVE );
 264  
 265          $conds = array( 'cl_to' => $this->getName(), 'cl_from = page_id' );
 266          $options = array( 'ORDER BY' => 'cl_sortkey' );
 267  
 268          if ( $limit ) {
 269              $options['LIMIT'] = $limit;
 270          }
 271  
 272          if ( $offset !== '' ) {
 273              $conds[] = 'cl_sortkey > ' . $dbr->addQuotes( $offset );
 274          }
 275  
 276          $result = TitleArray::newFromResult(
 277              $dbr->select(
 278                  array( 'page', 'categorylinks' ),
 279                  array( 'page_id', 'page_namespace', 'page_title', 'page_len',
 280                      'page_is_redirect', 'page_latest' ),
 281                  $conds,
 282                  __METHOD__,
 283                  $options
 284              )
 285          );
 286  
 287          wfProfileOut( __METHOD__ );
 288  
 289          return $result;
 290      }
 291  
 292      /**
 293       * Generic accessor
 294       * @param string $key
 295       * @return bool
 296       */
 297  	private function getX( $key ) {
 298          if ( !$this->initialize() ) {
 299              return false;
 300          }
 301          return $this->{$key};
 302      }
 303  
 304      /**
 305       * Refresh the counts for this category.
 306       *
 307       * @return bool True on success, false on failure
 308       */
 309  	public function refreshCounts() {
 310          if ( wfReadOnly() ) {
 311              return false;
 312          }
 313  
 314          # Note, we must use names for this, since categorylinks does.
 315          if ( $this->mName === null ) {
 316              if ( !$this->initialize() ) {
 317                  return false;
 318              }
 319          }
 320  
 321          wfProfileIn( __METHOD__ );
 322  
 323          $dbw = wfGetDB( DB_MASTER );
 324          $dbw->startAtomic( __METHOD__ );
 325  
 326          # Insert the row if it doesn't exist yet (e.g., this is being run via
 327          # update.php from a pre-1.16 schema).  TODO: This will cause lots and
 328          # lots of gaps on some non-MySQL DBMSes if you run populateCategory.php
 329          # repeatedly.  Plus it's an extra query that's unneeded almost all the
 330          # time.  This should be rewritten somehow, probably.
 331          $seqVal = $dbw->nextSequenceValue( 'category_cat_id_seq' );
 332          $dbw->insert(
 333              'category',
 334              array(
 335                  'cat_id' => $seqVal,
 336                  'cat_title' => $this->mName
 337              ),
 338              __METHOD__,
 339              'IGNORE'
 340          );
 341  
 342          $cond1 = $dbw->conditional( array( 'page_namespace' => NS_CATEGORY ), 1, 'NULL' );
 343          $cond2 = $dbw->conditional( array( 'page_namespace' => NS_FILE ), 1, 'NULL' );
 344          $result = $dbw->selectRow(
 345              array( 'categorylinks', 'page' ),
 346              array( 'pages' => 'COUNT(*)',
 347                  'subcats' => "COUNT($cond1)",
 348                  'files' => "COUNT($cond2)"
 349              ),
 350              array( 'cl_to' => $this->mName, 'page_id = cl_from' ),
 351              __METHOD__,
 352              array( 'LOCK IN SHARE MODE' )
 353          );
 354          $ret = $dbw->update(
 355              'category',
 356              array(
 357                  'cat_pages' => $result->pages,
 358                  'cat_subcats' => $result->subcats,
 359                  'cat_files' => $result->files
 360              ),
 361              array( 'cat_title' => $this->mName ),
 362              __METHOD__
 363          );
 364          $dbw->endAtomic( __METHOD__ );
 365  
 366          wfProfileOut( __METHOD__ );
 367  
 368          # Now we should update our local counts.
 369          $this->mPages = $result->pages;
 370          $this->mSubcats = $result->subcats;
 371          $this->mFiles = $result->files;
 372  
 373          return $ret;
 374      }
 375  }


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