From: Chad Horohoe Date: Fri, 29 Aug 2014 01:58:25 +0000 (-0700) Subject: Category finder style cleanups X-Git-Tag: 1.31.0-rc.0~14203^2 X-Git-Url: https://git.cyclocoop.org/%7B%24admin_url%7Dmembres/%7B%7B%20url_for%28%27vote%27%2C%20idvote=vote.voteid%29%20%7D%7D?a=commitdiff_plain;h=a7ab193d45814db08d017f260b99d4884c8efc26;p=lhc%2Fweb%2Fwiklou.git Category finder style cleanups * Renamed class to CategoryFinder (camel case) * Removed pointless empty constructor * Swapped wfProfileIn/Out for ProfileSection * Renamed scan_next_layer() to scanNextLayer() because underscores are ugly (no public callers) * Made scanNextLayer() and check() private, no callers outside class * Explicitly made run() and seed() public * Removed ugly underscores from seed() parameter * More useful variable name in single caller Change-Id: Iaffea7634c7b17ed5324b3b5c9b938c1a1348555 --- diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 04802f9e97..661f4d6410 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -37,7 +37,7 @@ $wgAutoloadLocalClasses = array( 'Autopromote' => 'includes/Autopromote.php', 'Block' => 'includes/Block.php', 'Category' => 'includes/Category.php', - 'Categoryfinder' => 'includes/Categoryfinder.php', + 'CategoryFinder' => 'includes/CategoryFinder.php', 'CategoryViewer' => 'includes/CategoryViewer.php', 'ChangeTags' => 'includes/ChangeTags.php', 'ChannelFeed' => 'includes/Feed.php', diff --git a/includes/CategoryFinder.php b/includes/CategoryFinder.php new file mode 100644 index 0000000000..9fd388352a --- /dev/null +++ b/includes/CategoryFinder.php @@ -0,0 +1,236 @@ + + * # Determines whether the article with the page_id 12345 is in both + * # "Category 1" and "Category 2" or their subcategories, respectively + * + * $cf = new CategoryFinder; + * $cf->seed( + * array( 12345 ), + * array( 'Category 1', 'Category 2' ), + * 'AND' + * ); + * $a = $cf->run(); + * print implode( ',' , $a ); + * + * + */ +class CategoryFinder { + /** @var int[] The original article IDs passed to the seed function */ + protected $articles = array(); + + /** @var array Array of DBKEY category names for categories that don't have a page */ + protected $deadend = array(); + + /** @var array Array of [ID => array()] */ + protected $parents = array(); + + /** @var array Array of article/category IDs */ + protected $next = array(); + + /** @var array Array of DBKEY category names */ + protected $targets = array(); + + /** @var array */ + protected $name2id = array(); + + /** @var string "AND" or "OR" */ + protected $mode; + + /** @var DatabaseBase Read-DB slave */ + protected $dbr; + + /** + * Initializes the instance. Do this prior to calling run(). + * @param array $articleIds Array of article IDs + * @param array $categories FIXME + * @param string $mode FIXME, default 'AND'. + * @todo FIXME: $categories/$mode + */ + public function seed( $articleIds, $categories, $mode = 'AND' ) { + $this->articles = $articleIds; + $this->next = $articleIds; + $this->mode = $mode; + + # Set the list of target categories; convert them to DBKEY form first + $this->targets = array(); + foreach ( $categories as $c ) { + $ct = Title::makeTitleSafe( NS_CATEGORY, $c ); + if ( $ct ) { + $c = $ct->getDBkey(); + $this->targets[$c] = $c; + } + } + } + + /** + * Iterates through the parent tree starting with the seed values, + * then checks the articles if they match the conditions + * @return array Array of page_ids (those given to seed() that match the conditions) + */ + public function run() { + $this->dbr = wfGetDB( DB_SLAVE ); + while ( count( $this->next ) > 0 ) { + $this->scanNextLayer(); + } + + # Now check if this applies to the individual articles + $ret = array(); + + foreach ( $this->articles as $article ) { + $conds = $this->targets; + if ( $this->check( $article, $conds ) ) { + # Matches the conditions + $ret[] = $article; + } + } + return $ret; + } + + /** + * This functions recurses through the parent representation, trying to match the conditions + * @param int $id The article/category to check + * @param array $conds The array of categories to match + * @param array $path Used to check for recursion loops + * @return bool Does this match the conditions? + */ + private function check( $id, &$conds, $path = array() ) { + // Check for loops and stop! + if ( in_array( $id, $path ) ) { + return false; + } + + $path[] = $id; + + # Shortcut (runtime paranoia): No conditions=all matched + if ( count( $conds ) == 0 ) { + return true; + } + + if ( !isset( $this->parents[$id] ) ) { + return false; + } + + # iterate through the parents + foreach ( $this->parents[$id] as $p ) { + $pname = $p->cl_to; + + # Is this a condition? + if ( isset( $conds[$pname] ) ) { + # This key is in the category list! + if ( $this->mode == 'OR' ) { + # One found, that's enough! + $conds = array(); + return true; + } else { + # Assuming "AND" as default + unset( $conds[$pname] ); + if ( count( $conds ) == 0 ) { + # All conditions met, done + return true; + } + } + } + + # Not done yet, try sub-parents + if ( !isset( $this->name2id[$pname] ) ) { + # No sub-parent + continue; + } + $done = $this->check( $this->name2id[$pname], $conds, $path ); + if ( $done || count( $conds ) == 0 ) { + # Subparents have done it! + return true; + } + } + return false; + } + + /** + * Scans a "parent layer" of the articles/categories in $this->next + */ + private function scanNextLayer() { + $profiler = new ProfileSection( __METHOD__ ); + + # Find all parents of the article currently in $this->next + $layer = array(); + $res = $this->dbr->select( + /* FROM */ 'categorylinks', + /* SELECT */ '*', + /* WHERE */ array( 'cl_from' => $this->next ), + __METHOD__ . '-1' + ); + foreach ( $res as $o ) { + $k = $o->cl_to; + + # Update parent tree + if ( !isset( $this->parents[$o->cl_from] ) ) { + $this->parents[$o->cl_from] = array(); + } + $this->parents[$o->cl_from][$k] = $o; + + # Ignore those we already have + if ( in_array( $k, $this->deadend ) ) { + continue; + } + + if ( isset( $this->name2id[$k] ) ) { + continue; + } + + # Hey, new category! + $layer[$k] = $k; + } + + $this->next = array(); + + # Find the IDs of all category pages in $layer, if they exist + if ( count( $layer ) > 0 ) { + $res = $this->dbr->select( + /* FROM */ 'page', + /* SELECT */ array( 'page_id', 'page_title' ), + /* WHERE */ array( 'page_namespace' => NS_CATEGORY, 'page_title' => $layer ), + __METHOD__ . '-2' + ); + foreach ( $res as $o ) { + $id = $o->page_id; + $name = $o->page_title; + $this->name2id[$name] = $id; + $this->next[] = $id; + unset( $layer[$name] ); + } + } + + # Mark dead ends + foreach ( $layer as $v ) { + $this->deadend[$v] = $v; + } + } +} diff --git a/includes/Categoryfinder.php b/includes/Categoryfinder.php deleted file mode 100644 index a5415afd3e..0000000000 --- a/includes/Categoryfinder.php +++ /dev/null @@ -1,241 +0,0 @@ - - * # Determines whether the article with the page_id 12345 is in both - * # "Category 1" and "Category 2" or their subcategories, respectively - * - * $cf = new Categoryfinder; - * $cf->seed( - * array( 12345 ), - * array( 'Category 1', 'Category 2' ), - * 'AND' - * ); - * $a = $cf->run(); - * print implode( ',' , $a ); - * - * - */ -class Categoryfinder { - /** @var int[] The original article IDs passed to the seed function */ - protected $articles = array(); - - /** @var array Array of DBKEY category names for categories that don't have a page */ - protected $deadend = array(); - - /** @var array Array of [ID => array()] */ - protected $parents = array(); - - /** @var array Array of article/category IDs */ - protected $next = array(); - - /** @var array Array of DBKEY category names */ - protected $targets = array(); - - /** @var array */ - protected $name2id = array(); - - /** @var string "AND" or "OR" */ - protected $mode; - - /** @var DatabaseBase Read-DB slave */ - protected $dbr; - - function __construct() { - } - - /** - * Initializes the instance. Do this prior to calling run(). - * @param array $article_ids Array of article IDs - * @param array $categories FIXME - * @param string $mode FIXME, default 'AND'. - * @todo FIXME: $categories/$mode - */ - function seed( $article_ids, $categories, $mode = 'AND' ) { - $this->articles = $article_ids; - $this->next = $article_ids; - $this->mode = $mode; - - # Set the list of target categories; convert them to DBKEY form first - $this->targets = array(); - foreach ( $categories as $c ) { - $ct = Title::makeTitleSafe( NS_CATEGORY, $c ); - if ( $ct ) { - $c = $ct->getDBkey(); - $this->targets[$c] = $c; - } - } - } - - /** - * Iterates through the parent tree starting with the seed values, - * then checks the articles if they match the conditions - * @return array Array of page_ids (those given to seed() that match the conditions) - */ - function run() { - $this->dbr = wfGetDB( DB_SLAVE ); - while ( count( $this->next ) > 0 ) { - $this->scan_next_layer(); - } - - # Now check if this applies to the individual articles - $ret = array(); - - foreach ( $this->articles as $article ) { - $conds = $this->targets; - if ( $this->check( $article, $conds ) ) { - # Matches the conditions - $ret[] = $article; - } - } - return $ret; - } - - /** - * This functions recurses through the parent representation, trying to match the conditions - * @param int $id The article/category to check - * @param array $conds The array of categories to match - * @param array $path Used to check for recursion loops - * @return bool Does this match the conditions? - */ - function check( $id, &$conds, $path = array() ) { - // Check for loops and stop! - if ( in_array( $id, $path ) ) { - return false; - } - - $path[] = $id; - - # Shortcut (runtime paranoia): No conditions=all matched - if ( count( $conds ) == 0 ) { - return true; - } - - if ( !isset( $this->parents[$id] ) ) { - return false; - } - - # iterate through the parents - foreach ( $this->parents[$id] as $p ) { - $pname = $p->cl_to; - - # Is this a condition? - if ( isset( $conds[$pname] ) ) { - # This key is in the category list! - if ( $this->mode == 'OR' ) { - # One found, that's enough! - $conds = array(); - return true; - } else { - # Assuming "AND" as default - unset( $conds[$pname] ); - if ( count( $conds ) == 0 ) { - # All conditions met, done - return true; - } - } - } - - # Not done yet, try sub-parents - if ( !isset( $this->name2id[$pname] ) ) { - # No sub-parent - continue; - } - $done = $this->check( $this->name2id[$pname], $conds, $path ); - if ( $done || count( $conds ) == 0 ) { - # Subparents have done it! - return true; - } - } - return false; - } - - /** - * Scans a "parent layer" of the articles/categories in $this->next - */ - function scan_next_layer() { - wfProfileIn( __METHOD__ ); - - # Find all parents of the article currently in $this->next - $layer = array(); - $res = $this->dbr->select( - /* FROM */ 'categorylinks', - /* SELECT */ '*', - /* WHERE */ array( 'cl_from' => $this->next ), - __METHOD__ . '-1' - ); - foreach ( $res as $o ) { - $k = $o->cl_to; - - # Update parent tree - if ( !isset( $this->parents[$o->cl_from] ) ) { - $this->parents[$o->cl_from] = array(); - } - $this->parents[$o->cl_from][$k] = $o; - - # Ignore those we already have - if ( in_array( $k, $this->deadend ) ) { - continue; - } - - if ( isset( $this->name2id[$k] ) ) { - continue; - } - - # Hey, new category! - $layer[$k] = $k; - } - - $this->next = array(); - - # Find the IDs of all category pages in $layer, if they exist - if ( count( $layer ) > 0 ) { - $res = $this->dbr->select( - /* FROM */ 'page', - /* SELECT */ array( 'page_id', 'page_title' ), - /* WHERE */ array( 'page_namespace' => NS_CATEGORY, 'page_title' => $layer ), - __METHOD__ . '-2' - ); - foreach ( $res as $o ) { - $id = $o->page_id; - $name = $o->page_title; - $this->name2id[$name] = $id; - $this->next[] = $id; - unset( $layer[$name] ); - } - } - - # Mark dead ends - foreach ( $layer as $v ) { - $this->deadend[$v] = $v; - } - - wfProfileOut( __METHOD__ ); - } -} diff --git a/includes/specials/SpecialRecentchanges.php b/includes/specials/SpecialRecentchanges.php index 5938f017c7..17d76648d7 100644 --- a/includes/specials/SpecialRecentchanges.php +++ b/includes/specials/SpecialRecentchanges.php @@ -595,9 +595,9 @@ class SpecialRecentChanges extends ChangesListSpecialPage { } # Look up - $c = new Categoryfinder; - $c->seed( $articles, $cats, $opts['categories_any'] ? 'OR' : 'AND' ); - $match = $c->run(); + $catFind = new CategoryFinder; + $catFind->seed( $articles, $cats, $opts['categories_any'] ? 'OR' : 'AND' ); + $match = $catFind->run(); # Filter $newrows = array();