00001 <?php
00024 class Categoryfinder {
00025 var $articles = array(); # The original article IDs passed to the seed function
00026 var $deadend = array(); # Array of DBKEY category names for categories that don't have a page
00027 var $parents = array(); # Array of [ID => array()]
00028 var $next = array(); # Array of article/category IDs
00029 var $targets = array(); # Array of DBKEY category names
00030 var $name2id = array();
00031 var $mode; # "AND" or "OR"
00032 var $dbr; # Read-DB slave
00033
00037 function __construct() {
00038 }
00039
00046 function seed( $article_ids, $categories, $mode = "AND" ) {
00047 $this->articles = $article_ids;
00048 $this->next = $article_ids;
00049 $this->mode = $mode;
00050
00051 # Set the list of target categories; convert them to DBKEY form first
00052 $this->targets = array();
00053 foreach ( $categories as $c ) {
00054 $ct = Title::makeTitleSafe( NS_CATEGORY, $c );
00055 if ( $ct ) {
00056 $c = $ct->getDBkey();
00057 $this->targets[$c] = $c;
00058 }
00059 }
00060 }
00061
00067 function run () {
00068 $this->dbr = wfGetDB( DB_SLAVE );
00069 while ( count ( $this->next ) > 0 ) {
00070 $this->scan_next_layer();
00071 }
00072
00073 # Now check if this applies to the individual articles
00074 $ret = array();
00075
00076 foreach ( $this->articles as $article ) {
00077 $conds = $this->targets;
00078 if ( $this->check( $article, $conds ) ) {
00079 # Matches the conditions
00080 $ret[] = $article;
00081 }
00082 }
00083 return $ret;
00084 }
00085
00093 function check( $id , &$conds, $path = array() ) {
00094 // Check for loops and stop!
00095 if ( in_array( $id, $path ) ) {
00096 return false;
00097 }
00098
00099 $path[] = $id;
00100
00101 # Shortcut (runtime paranoia): No contitions=all matched
00102 if ( count( $conds ) == 0 ) {
00103 return true;
00104 }
00105
00106 if ( !isset( $this->parents[$id] ) ) {
00107 return false;
00108 }
00109
00110 # iterate through the parents
00111 foreach ( $this->parents[$id] as $p ) {
00112 $pname = $p->cl_to ;
00113
00114 # Is this a condition?
00115 if ( isset( $conds[$pname] ) ) {
00116 # This key is in the category list!
00117 if ( $this->mode == "OR" ) {
00118 # One found, that's enough!
00119 $conds = array();
00120 return true;
00121 } else {
00122 # Assuming "AND" as default
00123 unset( $conds[$pname] ) ;
00124 if ( count( $conds ) == 0 ) {
00125 # All conditions met, done
00126 return true;
00127 }
00128 }
00129 }
00130
00131 # Not done yet, try sub-parents
00132 if ( !isset( $this->name2id[$pname] ) ) {
00133 # No sub-parent
00134 continue ;
00135 }
00136 $done = $this->check( $this->name2id[$pname], $conds, $path );
00137 if ( $done || count( $conds ) == 0 ) {
00138 # Subparents have done it!
00139 return true;
00140 }
00141 }
00142 return false;
00143 }
00144
00148 function scan_next_layer() {
00149 # Find all parents of the article currently in $this->next
00150 $layer = array();
00151 $res = $this->dbr->select(
00152 'categorylinks',
00153 '*',
00154 array( 'cl_from' => $this->next ),
00155 __METHOD__ . "-1"
00156 );
00157 while ( $o = $this->dbr->fetchObject( $res ) ) {
00158 $k = $o->cl_to ;
00159
00160 # Update parent tree
00161 if ( !isset( $this->parents[$o->cl_from] ) ) {
00162 $this->parents[$o->cl_from] = array();
00163 }
00164 $this->parents[$o->cl_from][$k] = $o;
00165
00166 # Ignore those we already have
00167 if ( in_array ( $k , $this->deadend ) ) continue;
00168
00169 if ( isset ( $this->name2id[$k] ) ) continue;
00170
00171 # Hey, new category!
00172 $layer[$k] = $k;
00173 }
00174
00175 $this->next = array();
00176
00177 # Find the IDs of all category pages in $layer, if they exist
00178 if ( count ( $layer ) > 0 ) {
00179 $res = $this->dbr->select(
00180 'page',
00181 array( 'page_id', 'page_title' ),
00182 array( 'page_namespace' => NS_CATEGORY , 'page_title' => $layer ),
00183 __METHOD__ . "-2"
00184 );
00185 while ( $o = $this->dbr->fetchObject( $res ) ) {
00186 $id = $o->page_id;
00187 $name = $o->page_title;
00188 $this->name2id[$name] = $id;
00189 $this->next[] = $id;
00190 unset( $layer[$name] );
00191 }
00192 }
00193
00194 # Mark dead ends
00195 foreach ( $layer as $v ) {
00196 $this->deadend[$v] = $v;
00197 }
00198 }
00199
00200 }