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