mNames = array_diff( array_map( array( 'CategoryListBase', 'setNamesCallback' ), $names ), array( false ) ); } /** * @param string $name Name of a putative category * @return mixed Normalized name, or false if the name was invalid. */ private static function setNamesCallback( $name ) { $title = Title::newFromText( "Category:$name" ); if( !is_object( $title ) ) { return false; } return $title->getDBKey(); } /** * Set up all member variables using a database query. * @return bool True on success, false on failure. */ protected function initialize() { if( $this->mNames === null && $this->mIDs === null ) { throw new MWException( __METHOD__.' has both names and IDs null' ); } $dbr = wfGetDB( DB_SLAVE ); if( $this->mIDs === null ) { $where = array( 'cat_title' => $this->mNames ); } elseif( $this->mNames === null ) { $where = array( 'cat_id' => $this->mIDs ); } else { # Already initialized return true; } $res = $dbr->select( 'category', array( 'cat_id', 'cat_title', 'cat_pages', 'cat_subcats', 'cat_files' ), $where, __METHOD__ ); if( !$res->fetchRow() ) { # Okay, there were no contents. Nothing to initialize. return false; } $res->rewind(); $this->mIDs = $this->mNames = $this->mPages = $this->mSubcats = $this->mFiles = array(); while( $row = $res->fetchRow() ) { $this->mIDs []= $row['cat_id']; $this->mNames []= $row['cat_title']; $this->mPages []= $row['cat_pages']; $this->mSubcats []= $row['cat_subcats']; $this->mFiles []= $row['cat_files']; } $res->free(); } } /** @todo make iterable. */ class CategoryList extends CategoryListBase { /** * Factory function. Any provided elements that don't correspond to a cat- * egory that actually exists will be silently dropped. FIXME: Is this * sane error-handling? * * @param array $names An array of category names. They need not be norma- * lized, with spaces replaced by underscores. * @return CategoryList */ public static function newFromNames( $names ) { $cat = new self(); $cat->setNames( $names ); return $cat; } /** * Factory function. Any provided elements that don't correspond to a cat- * egory that actually exists will be silently dropped. FIXME: Is this * sane error-handling? * * @param array $ids An array of category ids * @return CategoryList */ public static function newFromIDs( $ids ) { if( !is_array( $ids ) ) { throw new MWException( __METHOD__.' passed non-array' ); } $cat = new self(); $cat->mIds = $ids; return $cat; } /** @return array Simple array of DB key names */ public function getNames() { $this->initialize(); return $this->mNames; } /** * FIXME: Is this a good return type? * * @return array Associative array of DB key name => ID */ public function getIDs() { $this->initialize(); return array_fill_keys( $this->mNames, $this->mIDs ); } /** * FIXME: Is this a good return type? * * @return array Associative array of DB key name => array(pages, subcats, * files) */ public function getCounts() { $this->initialize(); $ret = array(); foreach( array_keys( $this->mNames ) as $i ) { $ret[$this->mNames[$i]] = array( $this->mPages[$i], $this->mSubcats[$i], $this->mFiles[$i] ); } return $ret; } } class Category extends CategoryListBase { /** * 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(); $cat->setNames( array( $name ) ); if( count( $cat->mNames ) !== 1 ) { return false; } return $cat; } /** * Factory function. * * @param array $id A category id * @return Category */ public static function newFromIDs( $id ) { $cat = new self(); $cat->mIDs = array( $id ); return $cat; } /** @return mixed DB key name, or false on failure */ public function getName() { return $this->getX( 'mNames' ); } /** @return mixed Category ID, or false on failure */ public function getID() { return $this->getX( 'mIDs' ); } /** @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' ); } /** * This is not implemented in the base class, because arrays of Titles are * evil. * * @return mixed The Title for this category, or false on failure. */ public function getTitle() { if( !$this->initialize() ) { return false; } return Title::makeTitleSafe( NS_CATEGORY, $this->mNames[0] ); } /** Generic accessor */ private function getX( $key ) { if( !$this->initialize() ) { return false; } return $this->{$key}[0]; } /** * Override the parent class so that we can return false if things muck * up, i.e., the name/ID we got was invalid. Currently CategoryList si- * lently eats errors so as not to kill the whole array for one bad name. * * @return bool True on success, false on failure. */ protected function initialize() { parent::initialize(); if( count( $this->mNames ) != 1 || count( $this->mIDs ) != 1 ) { return false; } return true; } /** * Refresh the counts for this category. * * FIXME: If there were some way to do this in MySQL 4 without an UPDATE * for every row, it would be nice to move this to the parent class. * * @return bool True on success, false on failure */ public function refreshCounts() { if( wfReadOnly() ) { return false; } $dbw = wfGetDB( DB_MASTER ); $dbw->begin(); # Note, we must use names for this, since categorylinks does. if( $this->mNames === null ) { if( !$this->initialize() ) { return false; } } else { # Let's be sure that the row exists in the table. We don't need to # do this if we got the row from the table in initialization! $dbw->insert( 'category', array( 'cat_title' => $this->mNames[0] ), __METHOD__, 'IGNORE' ); } $cond1 = $dbw->conditional( 'page_namespace='.NS_CATEGORY, 1, 'NULL' ); $cond2 = $dbw->conditional( 'page_namespace='.NS_IMAGE, 1, 'NULL' ); $result = $dbw->selectRow( array( 'categorylinks', 'page' ), array( 'COUNT(*) AS pages', "COUNT($cond1) AS subcats", "COUNT($cond2) AS files" ), array( 'cl_to' => $this->mNames[0], 'page_id = cl_from' ), __METHOD__, '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->mNames[0] ), __METHOD__ ); $dbw->commit(); # Now we should update our local counts. $this->mPages = array( $result->pages ); $this->mSubcats = array( $result->subcats ); $this->mFiles = array( $result->files ); return $ret; } }