Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 104 |
|
0.00% |
0 / 15 |
CRAP | |
0.00% |
0 / 1 |
GlobalUsageQuery | |
0.00% |
0 / 104 |
|
0.00% |
0 / 15 |
1406 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
setOffset | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
getOffsetString | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isReversed | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getContinueString | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
setLimit | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getLimit | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
filterLocal | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
filterNamespaces | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
filterSites | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 73 |
|
0.00% |
0 / 1 |
272 | |||
getResult | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSingleImageResult | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
hasMore | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
count | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\GlobalUsage; |
4 | |
5 | use MediaWiki\Title\Title; |
6 | use MediaWiki\WikiMap\WikiMap; |
7 | use stdClass; |
8 | use Wikimedia\Rdbms\IDatabase; |
9 | use Wikimedia\Rdbms\SelectQueryBuilder; |
10 | |
11 | /** |
12 | * A helper class to query the globalimagelinks table |
13 | * |
14 | */ |
15 | class GlobalUsageQuery { |
16 | /** @var int */ |
17 | private $limit = 50; |
18 | /** @var array */ |
19 | private $offset; |
20 | /** @var bool */ |
21 | private $hasMore = false; |
22 | /** @var bool */ |
23 | private $filterLocal = false; |
24 | /** @var array[][][] */ |
25 | private $result; |
26 | /** @var bool|null */ |
27 | private $reversed = false; |
28 | |
29 | /** @var int[] namespace ID(s) desired */ |
30 | private $filterNamespaces; |
31 | |
32 | /** @var string[] sites desired */ |
33 | private $filterSites; |
34 | |
35 | /** |
36 | * @var Title|array |
37 | */ |
38 | private $target; |
39 | |
40 | /** @var stdClass|null */ |
41 | private $lastRow; |
42 | |
43 | /** |
44 | * @var IDatabase |
45 | */ |
46 | private $db; |
47 | |
48 | /** |
49 | * @param mixed $target Title or array of db keys of target(s). |
50 | * If a title, can be a category or a file |
51 | */ |
52 | public function __construct( $target ) { |
53 | $this->db = GlobalUsage::getGlobalDB( DB_REPLICA ); |
54 | if ( $target instanceof Title ) { |
55 | $this->target = $target; |
56 | } elseif ( is_array( $target ) ) { |
57 | // List of files to query |
58 | $this->target = $target; |
59 | } else { |
60 | $this->target = Title::makeTitleSafe( NS_FILE, $target ); |
61 | } |
62 | $this->offset = []; |
63 | } |
64 | |
65 | /** |
66 | * Set the offset parameter |
67 | * |
68 | * @param string $offset offset |
69 | * @param bool|null $reversed True if this is the upper offset |
70 | * @return bool |
71 | */ |
72 | public function setOffset( $offset, $reversed = null ) { |
73 | if ( $reversed !== null ) { |
74 | $this->reversed = $reversed; |
75 | } |
76 | |
77 | if ( !is_array( $offset ) ) { |
78 | $offset = explode( '|', $offset ); |
79 | } |
80 | |
81 | if ( count( $offset ) == 3 ) { |
82 | $this->offset = $offset; |
83 | return true; |
84 | } else { |
85 | return false; |
86 | } |
87 | } |
88 | |
89 | /** |
90 | * Return the offset set by the user |
91 | * |
92 | * @return string offset |
93 | */ |
94 | public function getOffsetString() { |
95 | return implode( '|', $this->offset ); |
96 | } |
97 | |
98 | /** |
99 | * Is the result reversed |
100 | * |
101 | * @return bool |
102 | */ |
103 | public function isReversed() { |
104 | return $this->reversed; |
105 | } |
106 | |
107 | /** |
108 | * Returns the string used for continuation |
109 | * |
110 | * @return string |
111 | * |
112 | */ |
113 | public function getContinueString() { |
114 | if ( $this->hasMore() ) { |
115 | return "{$this->lastRow->gil_to}|{$this->lastRow->gil_wiki}|{$this->lastRow->gil_page}"; |
116 | } else { |
117 | return ''; |
118 | } |
119 | } |
120 | |
121 | /** |
122 | * Set the maximum amount of items to return. Capped at 500. |
123 | * |
124 | * @param int $limit The limit |
125 | */ |
126 | public function setLimit( $limit ) { |
127 | $this->limit = min( $limit, 500 ); |
128 | } |
129 | |
130 | /** |
131 | * Returns the user set limit |
132 | * @return int |
133 | */ |
134 | public function getLimit() { |
135 | return $this->limit; |
136 | } |
137 | |
138 | /** |
139 | * Set whether to filter out the local usage |
140 | * @param bool $value |
141 | */ |
142 | public function filterLocal( $value = true ) { |
143 | $this->filterLocal = $value; |
144 | } |
145 | |
146 | /** |
147 | * Return results only for these namespaces. |
148 | * @param int[] $namespaces numeric namespace IDs |
149 | */ |
150 | public function filterNamespaces( $namespaces ) { |
151 | $this->filterNamespaces = $namespaces; |
152 | } |
153 | |
154 | /** |
155 | * Return results only for these sites. |
156 | * @param string[] $sites wiki site names |
157 | */ |
158 | public function filterSites( $sites ) { |
159 | $this->filterSites = $sites; |
160 | } |
161 | |
162 | /** |
163 | * Executes the query |
164 | */ |
165 | public function execute() { |
166 | /* Construct the SQL query */ |
167 | $queryBuilder = $this->db->newSelectQueryBuilder() |
168 | ->select( [ |
169 | 'gil_to', |
170 | 'gil_wiki', |
171 | 'gil_page', |
172 | 'gil_page_namespace_id', |
173 | 'gil_page_namespace', |
174 | 'gil_page_title' |
175 | ] ) |
176 | ->from( 'globalimagelinks' ) |
177 | // Select an extra row to check whether we have more rows available |
178 | ->limit( $this->limit + 1 ) |
179 | ->caller( __METHOD__ ); |
180 | |
181 | // Add target image(s) |
182 | if ( is_array( $this->target ) ) { |
183 | // array of dbkey strings |
184 | $namespace = NS_FILE; |
185 | $queryIn = $this->target; |
186 | } else { |
187 | // a Title object |
188 | $namespace = $this->target->getNamespace(); |
189 | $queryIn = $this->target->getDbKey(); |
190 | } |
191 | switch ( $namespace ) { |
192 | case NS_FILE: |
193 | $queryBuilder->where( [ 'gil_to' => $queryIn ] ); |
194 | break; |
195 | case NS_CATEGORY: |
196 | $queryBuilder->join( 'categorylinks', null, 'page_id = cl_from' ); |
197 | $queryBuilder->join( 'page', null, 'page_title = gil_to' ); |
198 | $queryBuilder->where( [ |
199 | 'cl_to' => $queryIn, |
200 | 'page_namespace' => NS_FILE, |
201 | ] ); |
202 | break; |
203 | default: |
204 | return; |
205 | } |
206 | |
207 | if ( $this->filterLocal ) { |
208 | // Don't show local file usage |
209 | $queryBuilder->andWhere( $this->db->expr( 'gil_wiki', '!=', WikiMap::getCurrentWikiId() ) ); |
210 | } |
211 | |
212 | if ( $this->filterNamespaces ) { |
213 | $queryBuilder->andWhere( [ 'gil_page_namespace_id' => $this->filterNamespaces ] ); |
214 | } |
215 | |
216 | if ( $this->filterSites ) { |
217 | $queryBuilder->andWhere( [ 'gil_wiki' => $this->filterSites ] ); |
218 | } |
219 | |
220 | // Set the continuation condition |
221 | if ( $this->offset ) { |
222 | // Check which limit we got in order to determine which way to traverse rows |
223 | if ( $this->reversed ) { |
224 | // Reversed traversal; do not include offset row |
225 | $op = '<'; |
226 | $queryBuilder->orderBy( [ 'gil_to', 'gil_wiki', 'gil_page' ], SelectQueryBuilder::SORT_DESC ); |
227 | } else { |
228 | // Normal traversal; include offset row |
229 | $op = '>='; |
230 | } |
231 | |
232 | $queryBuilder->andWhere( $this->db->buildComparison( $op, [ |
233 | 'gil_to' => $this->offset[0], |
234 | 'gil_wiki' => $this->offset[1], |
235 | 'gil_page' => intval( $this->offset[2] ), |
236 | ] ) ); |
237 | } |
238 | |
239 | $res = $queryBuilder->fetchResultSet(); |
240 | |
241 | /* Process result */ |
242 | // Always return the result in the same order; regardless whether reversed was specified |
243 | // reversed is really only used to determine from which direction the offset is |
244 | $rows = []; |
245 | $count = 0; |
246 | $this->hasMore = false; |
247 | foreach ( $res as $row ) { |
248 | $count++; |
249 | if ( $count > $this->limit ) { |
250 | // We've reached the extra row that indicates that there are more rows |
251 | $this->hasMore = true; |
252 | $this->lastRow = $row; |
253 | break; |
254 | } |
255 | $rows[] = $row; |
256 | } |
257 | if ( $this->reversed ) { |
258 | $rows = array_reverse( $rows ); |
259 | } |
260 | |
261 | // Build the result array |
262 | $this->result = []; |
263 | foreach ( $rows as $row ) { |
264 | if ( !isset( $this->result[$row->gil_to] ) ) { |
265 | $this->result[$row->gil_to] = []; |
266 | } |
267 | if ( !isset( $this->result[$row->gil_to][$row->gil_wiki] ) ) { |
268 | $this->result[$row->gil_to][$row->gil_wiki] = []; |
269 | } |
270 | |
271 | $this->result[$row->gil_to][$row->gil_wiki][] = [ |
272 | 'image' => $row->gil_to, |
273 | 'id' => $row->gil_page, |
274 | 'namespace_id' => $row->gil_page_namespace_id, |
275 | 'namespace' => $row->gil_page_namespace, |
276 | 'title' => $row->gil_page_title, |
277 | 'wiki' => $row->gil_wiki, |
278 | ]; |
279 | } |
280 | } |
281 | |
282 | /** |
283 | * Returns the result set. The result is a 4 dimensional array |
284 | * (file, wiki, page), whose items are arrays with keys: |
285 | * - image: File name |
286 | * - id: Page id |
287 | * - namespace: Page namespace text |
288 | * - title: Unprefixed page title |
289 | * - wiki: Wiki id |
290 | * |
291 | * @return array Result set |
292 | */ |
293 | public function getResult() { |
294 | return $this->result; |
295 | } |
296 | |
297 | /** |
298 | * Returns a 3 dimensional array with the result of the first file. Useful |
299 | * if only one image was queried. |
300 | * |
301 | * For further information see documentation of getResult() |
302 | * |
303 | * @return array Result set |
304 | */ |
305 | public function getSingleImageResult() { |
306 | if ( $this->result ) { |
307 | return current( $this->result ); |
308 | } else { |
309 | return []; |
310 | } |
311 | } |
312 | |
313 | /** |
314 | * Returns whether there are more results |
315 | * |
316 | * @return bool |
317 | */ |
318 | public function hasMore() { |
319 | return $this->hasMore; |
320 | } |
321 | |
322 | /** |
323 | * Returns the result length |
324 | * |
325 | * @return int |
326 | */ |
327 | public function count() { |
328 | return count( $this->result ); |
329 | } |
330 | } |