mName === null && $this->mID === null ) { throw new MWException( __METHOD__ . ' has both names and IDs null' ); } elseif ( $this->mID === null ) { $where = array( 'cat_title' => $this->mName ); } elseif ( $this->mName === null ) { $where = array( 'cat_id' => $this->mID ); } else { # Already initialized return true; } $dbr = wfGetDB( DB_SLAVE ); $row = $dbr->selectRow( 'category', array( 'cat_id', 'cat_title', 'cat_pages', 'cat_subcats', 'cat_files' ), $where, __METHOD__ ); if ( !$row ) { # Okay, there were no contents. Nothing to initialize. if ( $this->mTitle ) { # If there is a title object but no record in the category table, # treat this as an empty category. $this->mID = false; $this->mName = $this->mTitle->getDBkey(); $this->mPages = 0; $this->mSubcats = 0; $this->mFiles = 0; return true; } else { return false; # Fail } } $this->mID = $row->cat_id; $this->mName = $row->cat_title; $this->mPages = $row->cat_pages; $this->mSubcats = $row->cat_subcats; $this->mFiles = $row->cat_files; # (bug 13683) If the count is negative, then 1) it's obviously wrong # and should not be kept, and 2) we *probably* don't have to scan many # rows to obtain the correct figure, so let's risk a one-time recount. if ( $this->mPages < 0 || $this->mSubcats < 0 || $this->mFiles < 0 ) { $this->refreshCounts(); } return true; } /** * Factory function. * * @param array $name A category name (no "Category:" prefix). It need * not be normalized, with spaces replaced by underscores. * @return mixed Category, or false on a totally invalid name */ public static function newFromName( $name ) { $cat = new self(); $title = Title::makeTitleSafe( NS_CATEGORY, $name ); if ( !is_object( $title ) ) { return false; } $cat->mTitle = $title; $cat->mName = $title->getDBkey(); return $cat; } /** * Factory function. * * @param Title $title Title for the category page * @return Category|bool On a totally invalid name */ public static function newFromTitle( $title ) { $cat = new self(); $cat->mTitle = $title; $cat->mName = $title->getDBkey(); return $cat; } /** * Factory function. * * @param int $id A category id * @return Category */ public static function newFromID( $id ) { $cat = new self(); $cat->mID = intval( $id ); return $cat; } /** * Factory function, for constructing a Category object from a result set * * @param object $row Result set row, must contain the cat_xxx fields. If the * fields are null, the resulting Category object will represent an empty * category if a title object was given. If the fields are null and no * title was given, this method fails and returns false. * @param Title $title Optional title object for the category represented by * the given row. May be provided if it is already known, to avoid having * to re-create a title object later. * @return Category */ public static function newFromRow( $row, $title = null ) { $cat = new self(); $cat->mTitle = $title; # NOTE: the row often results from a LEFT JOIN on categorylinks. This may result in # all the cat_xxx fields being null, if the category page exists, but nothing # was ever added to the category. This case should be treated link an empty # category, if possible. if ( $row->cat_title === null ) { if ( $title === null ) { # the name is probably somewhere in the row, for example as page_title, # but we can't know that here... return false; } else { # if we have a title object, fetch the category name from there $cat->mName = $title->getDBkey(); } $cat->mID = false; $cat->mSubcats = 0; $cat->mPages = 0; $cat->mFiles = 0; } else { $cat->mName = $row->cat_title; $cat->mID = $row->cat_id; $cat->mSubcats = $row->cat_subcats; $cat->mPages = $row->cat_pages; $cat->mFiles = $row->cat_files; } return $cat; } /** * @return mixed DB key name, or false on failure */ public function getName() { return $this->getX( 'mName' ); } /** * @return mixed Category ID, or false on failure */ public function getID() { return $this->getX( 'mID' ); } /** * @return mixed Total number of member pages, or false on failure */ public function getPageCount() { return $this->getX( 'mPages' ); } /** * @return mixed Number of subcategories, or false on failure */ public function getSubcatCount() { return $this->getX( 'mSubcats' ); } /** * @return mixed Number of member files, or false on failure */ public function getFileCount() { return $this->getX( 'mFiles' ); } /** * @return Title|bool Title for this category, or false on failure. */ public function getTitle() { if ( $this->mTitle ) { return $this->mTitle; } if ( !$this->initialize() ) { return false; } $this->mTitle = Title::makeTitleSafe( NS_CATEGORY, $this->mName ); return $this->mTitle; } /** * Fetch a TitleArray of up to $limit category members, beginning after the * category sort key $offset. * @param int $limit * @param string $offset * @return TitleArray TitleArray object for category members. */ public function getMembers( $limit = false, $offset = '' ) { $dbr = wfGetDB( DB_SLAVE ); $conds = array( 'cl_to' => $this->getName(), 'cl_from = page_id' ); $options = array( 'ORDER BY' => 'cl_sortkey' ); if ( $limit ) { $options['LIMIT'] = $limit; } if ( $offset !== '' ) { $conds[] = 'cl_sortkey > ' . $dbr->addQuotes( $offset ); } $result = TitleArray::newFromResult( $dbr->select( array( 'page', 'categorylinks' ), array( 'page_id', 'page_namespace', 'page_title', 'page_len', 'page_is_redirect', 'page_latest' ), $conds, __METHOD__, $options ) ); return $result; } /** * Generic accessor * @param string $key * @return bool */ private function getX( $key ) { if ( !$this->initialize() ) { return false; } return $this->{$key}; } /** * Refresh the counts for this category. * * @return bool True on success, false on failure */ public function refreshCounts() { if ( wfReadOnly() ) { return false; } # Note, we must use names for this, since categorylinks does. if ( $this->mName === null ) { if ( !$this->initialize() ) { return false; } } $dbw = wfGetDB( DB_MASTER ); $dbw->startAtomic( __METHOD__ ); # Insert the row if it doesn't exist yet (e.g., this is being run via # update.php from a pre-1.16 schema). TODO: This will cause lots and # lots of gaps on some non-MySQL DBMSes if you run populateCategory.php # repeatedly. Plus it's an extra query that's unneeded almost all the # time. This should be rewritten somehow, probably. $seqVal = $dbw->nextSequenceValue( 'category_cat_id_seq' ); $dbw->insert( 'category', array( 'cat_id' => $seqVal, 'cat_title' => $this->mName ), __METHOD__, 'IGNORE' ); $cond1 = $dbw->conditional( array( 'page_namespace' => NS_CATEGORY ), 1, 'NULL' ); $cond2 = $dbw->conditional( array( 'page_namespace' => NS_FILE ), 1, 'NULL' ); $result = $dbw->selectRow( array( 'categorylinks', 'page' ), array( 'pages' => 'COUNT(*)', 'subcats' => "COUNT($cond1)", 'files' => "COUNT($cond2)" ), array( 'cl_to' => $this->mName, 'page_id = cl_from' ), __METHOD__, array( 'LOCK IN SHARE MODE' ) ); $ret = $dbw->update( 'category', array( 'cat_pages' => $result->pages, 'cat_subcats' => $result->subcats, 'cat_files' => $result->files ), array( 'cat_title' => $this->mName ), __METHOD__ ); $dbw->endAtomic( __METHOD__ ); # Now we should update our local counts. $this->mPages = $result->pages; $this->mSubcats = $result->subcats; $this->mFiles = $result->files; return $ret; } }