MediaWiki REL1_35
CategoryFinder.php
Go to the documentation of this file.
1<?php
24
50 protected $articles = [];
51
53 protected $deadend = [];
54
56 protected $parents = [];
57
59 protected $next = [];
60
62 protected $maxdepth = -1;
63
65 protected $targets = [];
66
68 protected $name2id = [];
69
71 protected $mode;
72
74 protected $dbr;
75
76 public function __construct() {
77 wfDeprecated( __METHOD__, '1.31' );
78 }
79
91 public function seed( $articleIds, $categories, $mode = 'AND', $maxdepth = -1 ) {
92 $this->articles = $articleIds;
93 $this->next = $articleIds;
94 $this->mode = $mode;
95 $this->maxdepth = $maxdepth;
96
97 # Set the list of target categories; convert them to DBKEY form first
98 $this->targets = [];
99 foreach ( $categories as $c ) {
100 $ct = Title::makeTitleSafe( NS_CATEGORY, $c );
101 if ( $ct ) {
102 $c = $ct->getDBkey();
103 $this->targets[$c] = $c;
104 }
105 }
106 }
107
113 public function run() {
114 $this->dbr = wfGetDB( DB_REPLICA );
115
116 $i = 0;
117 $dig = true;
118 while ( count( $this->next ) && $dig ) {
119 $this->scanNextLayer();
120
121 // Is there any depth limit?
122 if ( $this->maxdepth !== -1 ) {
123 $dig = $i < $this->maxdepth;
124 $i++;
125 }
126 }
127
128 # Now check if this applies to the individual articles
129 $ret = [];
130
131 foreach ( $this->articles as $article ) {
132 $conds = $this->targets;
133 if ( $this->check( $article, $conds ) ) {
134 # Matches the conditions
135 $ret[] = $article;
136 }
137 }
138 return $ret;
139 }
140
145 public function getParents() {
146 return $this->parents;
147 }
148
156 private function check( $id, &$conds, $path = [] ) {
157 // Check for loops and stop!
158 if ( in_array( $id, $path ) ) {
159 return false;
160 }
161
162 $path[] = $id;
163
164 # Shortcut (runtime paranoia): No conditions=all matched
165 if ( count( $conds ) == 0 ) {
166 return true;
167 }
168
169 if ( !isset( $this->parents[$id] ) ) {
170 return false;
171 }
172
173 # iterate through the parents
174 foreach ( $this->parents[$id] as $p ) {
175 $pname = $p->cl_to;
176
177 # Is this a condition?
178 if ( isset( $conds[$pname] ) ) {
179 # This key is in the category list!
180 if ( $this->mode == 'OR' ) {
181 # One found, that's enough!
182 $conds = [];
183 return true;
184 } else {
185 # Assuming "AND" as default
186 unset( $conds[$pname] );
187 if ( count( $conds ) == 0 ) {
188 # All conditions met, done
189 return true;
190 }
191 }
192 }
193
194 # Not done yet, try sub-parents
195 if ( !isset( $this->name2id[$pname] ) ) {
196 # No sub-parent
197 continue;
198 }
199 $done = $this->check( $this->name2id[$pname], $conds, $path );
200 if ( $done || count( $conds ) == 0 ) {
201 # Subparents have done it!
202 return true;
203 }
204 }
205 return false;
206 }
207
211 private function scanNextLayer() {
212 # Find all parents of the article currently in $this->next
213 $layer = [];
214 $res = $this->dbr->select(
215 /* FROM */ 'categorylinks',
216 /* SELECT */ [ 'cl_to', 'cl_from' ],
217 /* WHERE */ [ 'cl_from' => $this->next ],
218 __METHOD__ . '-1'
219 );
220 foreach ( $res as $row ) {
221 $k = $row->cl_to;
222
223 # Update parent tree
224 if ( !isset( $this->parents[$row->cl_from] ) ) {
225 $this->parents[$row->cl_from] = [];
226 }
227 $this->parents[$row->cl_from][$k] = $row;
228
229 # Ignore those we already have
230 if ( in_array( $k, $this->deadend ) ) {
231 continue;
232 }
233
234 if ( isset( $this->name2id[$k] ) ) {
235 continue;
236 }
237
238 # Hey, new category!
239 $layer[$k] = $k;
240 }
241
242 $this->next = [];
243
244 # Find the IDs of all category pages in $layer, if they exist
245 if ( count( $layer ) > 0 ) {
246 $res = $this->dbr->select(
247 /* FROM */ 'page',
248 /* SELECT */ [ 'page_id', 'page_title' ],
249 /* WHERE */ [ 'page_namespace' => NS_CATEGORY, 'page_title' => $layer ],
250 __METHOD__ . '-2'
251 );
252 foreach ( $res as $row ) {
253 $id = $row->page_id;
254 $name = $row->page_title;
255 $this->name2id[$name] = $id;
256 $this->next[] = $id;
257 unset( $layer[$name] );
258 }
259 }
260
261 # Mark dead ends
262 foreach ( $layer as $v ) {
263 $this->deadend[$v] = $v;
264 }
265 }
266}
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
The "CategoryFinder" class takes a list of articles, creates an internal representation of all their ...
check( $id, &$conds, $path=[])
This functions recurses through the parent representation, trying to match the conditions.
array $targets
Array of DBKEY category names.
int[] $articles
The original article IDs passed to the seed function.
int $maxdepth
Max layer depth.
array $next
Array of article/category IDs.
array $parents
Array of [ ID => [] ].
array $deadend
Array of DBKEY category names for categories that don't have a page.
string $mode
"AND" or "OR"
run()
Iterates through the parent tree starting with the seed values, then checks the articles if they matc...
scanNextLayer()
Scans a "parent layer" of the articles/categories in $this->next.
seed( $articleIds, $categories, $mode='AND', $maxdepth=-1)
Initializes the instance.
getParents()
Get the parents.
IDatabase $dbr
Read-DB replica DB.
const NS_CATEGORY
Definition Defines.php:84
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
const DB_REPLICA
Definition defines.php:25