Merge iwtransclusion branch into trunk
[lhc/web/wiklou.git] / includes / GlobalUsageQuery.php
1 <?php
2 /**
3 * A helper class to query the globalimagelinks table
4 *
5 */
6 class GlobalUsageQuery {
7 private $limit = 50;
8 private $offset;
9 private $hasMore = false;
10 private $filterLocal = false;
11 private $result;
12 private $continue;
13 private $reversed = false;
14 private $target = null;
15
16 /**
17 * @param $target mixed Title or db key, or array of db keys of target(s)
18 */
19 public function __construct( $target ) {
20 global $wgGlobalDatabase;
21 $this->db = wfGetDB( DB_SLAVE, array(), $wgGlobalDatabase );
22 if ( $target instanceof Title && $target->getNamespace( ) == NS_FILE ) {
23 $this->target = $target->getDBKey();
24 } else {
25 $this->target = $target;
26 }
27 $this->offset = array();
28
29 }
30
31 /**
32 * Set the offset parameter
33 *
34 * @param $offset string offset
35 * @param $reversed bool True if this is the upper offset
36 */
37 public function setOffset( $offset, $reversed = null ) {
38 if ( !is_null( $reversed ) ) {
39 $this->reversed = $reversed;
40 }
41
42 if ( !is_array( $offset ) ) {
43 $offset = explode( '|', $offset );
44 }
45
46 if ( count( $offset ) == 3 ) {
47 $this->offset = $offset;
48 return true;
49 } else {
50 return false;
51 }
52 }
53 /**
54 * Return the offset set by the user
55 *
56 * @return array offset
57 */
58 public function getOffsetString() {
59 return implode( '|', $this->offset );
60 }
61 /**
62 * Is the result reversed
63 *
64 * @return bool
65 */
66 public function isReversed() {
67 return $this->reversed;
68 }
69
70 /**
71 * Returns the string used for continuation in a file search
72 *
73 * @return string
74 *
75 */
76 public function getContinueFileString() {
77 if ( $this->hasMore() ) {
78 return "{$this->lastRow->gil_to}|{$this->lastRow->gil_wiki}|{$this->lastRow->gil_page}";
79 } else {
80 return '';
81 }
82 }
83
84 /**
85 * Returns the string used for continuation in a template search
86 *
87 * @return string
88 *
89 */
90 public function getContinueTemplateString() {
91 if ( $this->hasMore() ) {
92 return "{$this->lastRow->gtl_to_title}|{$this->lastRow->gtl_from_wiki}|{$this->lastRow->gtl_from_page}";
93 } else {
94 return '';
95 }
96 }
97
98 /**
99 * Set the maximum amount of items to return. Capped at 500.
100 *
101 * @param $limit int The limit
102 */
103 public function setLimit( $limit ) {
104 $this->limit = min( $limit, 500 );
105 }
106 /**
107 * Returns the user set limit
108 */
109 public function getLimit() {
110 return $this->limit;
111 }
112
113 /**
114 * Set whether to filter out the local usage
115 */
116 public function filterLocal( $value = true ) {
117 $this->filterLocal = $value;
118 }
119
120 /**
121 * Executes the query for a file search
122 */
123 public function searchTemplate() {
124 global $wgLocalInterwiki;
125
126 /* Construct a where clause */
127 // Add target template(s)
128 $where = array( 'gtl_to_prefix' => $wgLocalInterwiki,
129 'gtl_to_namespace' => $this->target->getNamespace( ),
130 'gtl_to_title' => $this->target->getDBkey( )
131 );
132
133 // Set the continuation condition
134 $order = 'ASC';
135 if ( $this->offset ) {
136 $qTo = $this->db->addQuotes( $this->offset[0] );
137 $qWiki = $this->db->addQuotes( $this->offset[1] );
138 $qPage = intval( $this->offset[2] );
139
140 // Check which limit we got in order to determine which way to traverse rows
141 if ( $this->reversed ) {
142 // Reversed traversal; do not include offset row
143 $op1 = '<';
144 $op2 = '<';
145 $order = 'DESC';
146 } else {
147 // Normal traversal; include offset row
148 $op1 = '>';
149 $op2 = '>=';
150 $order = 'ASC';
151 }
152
153 $where[] = "(gtl_to_title $op1 $qTo) OR " .
154 "(gtl_to_title = $qTo AND gtl_from_wiki $op1 $qWiki) OR " .
155 "(gtl_to_title = $qTo AND gtl_from_wiki = $qWiki AND gtl_from_page $op2 $qPage)";
156 }
157
158 /* Perform select (Duh.) */
159 $res = $this->db->select(
160 array(
161 'globaltemplatelinks',
162 'globalnamespaces'
163 ),
164 array(
165 'gtl_to_title',
166 'gtl_from_wiki',
167 'gtl_from_page',
168 'gtl_from_namespace',
169 'gtl_from_title'
170 ),
171 $where,
172 __METHOD__,
173 array(
174 'ORDER BY' => "gtl_to_title $order, gtl_from_wiki $order, gtl_from_page $order",
175 // Select an extra row to check whether we have more rows available
176 'LIMIT' => $this->limit + 1,
177 ),
178 array(
179 'gtl_from_namespace = gn_namespace'
180 )
181 );
182
183 /* Process result */
184 // Always return the result in the same order; regardless whether reversed was specified
185 // reversed is really only used to determine from which direction the offset is
186 $rows = array();
187 foreach ( $res as $row ) {
188 $rows[] = $row;
189 }
190 if ( $this->reversed ) {
191 $rows = array_reverse( $rows );
192 }
193
194 // Build the result array
195 $count = 0;
196 $this->hasMore = false;
197 $this->result = array();
198 foreach ( $rows as $row ) {
199 $count++;
200 if ( $count > $this->limit ) {
201 // We've reached the extra row that indicates that there are more rows
202 $this->hasMore = true;
203 $this->lastRow = $row;
204 break;
205 }
206
207 if ( !isset( $this->result[$row->gtl_to_title] ) ) {
208 $this->result[$row->gtl_to_title] = array();
209 }
210 if ( !isset( $this->result[$row->gtl_to_title][$row->gtl_from_wiki] ) ) {
211 $this->result[$row->gtl_to_title][$row->gtl_from_wiki] = array();
212 }
213
214 $this->result[$row->gtl_to_title][$row->gtl_from_wiki][] = array(
215 'template' => $row->gtl_to_title,
216 'id' => $row->gtl_from_page,
217 'namespace' => $row->gn_namespacetext,
218 'title' => $row->gtl_from_title,
219 'wiki' => $row->gtl_from_wiki,
220 );
221 }
222 }
223
224 /**
225 * Executes the query for a template search
226 */
227 public function searchFile() {
228 /* Construct a where clause */
229 // Add target image(s)
230 $where = array( 'gil_to' => $this->target );
231
232 if ( $this->filterLocal ) {
233 // Don't show local file usage
234 $where[] = 'gil_wiki != ' . $this->db->addQuotes( wfWikiId() );
235 }
236
237 // Set the continuation condition
238 $order = 'ASC';
239 if ( $this->offset ) {
240 $qTo = $this->db->addQuotes( $this->offset[0] );
241 $qWiki = $this->db->addQuotes( $this->offset[1] );
242 $qPage = intval( $this->offset[2] );
243
244 // Check which limit we got in order to determine which way to traverse rows
245 if ( $this->reversed ) {
246 // Reversed traversal; do not include offset row
247 $op1 = '<';
248 $op2 = '<';
249 $order = 'DESC';
250 } else {
251 // Normal traversal; include offset row
252 $op1 = '>';
253 $op2 = '>=';
254 $order = 'ASC';
255 }
256
257 $where[] = "(gil_to $op1 $qTo) OR " .
258 "(gil_to = $qTo AND gil_wiki $op1 $qWiki) OR " .
259 "(gil_to = $qTo AND gil_wiki = $qWiki AND gil_page $op2 $qPage)";
260 }
261
262 /* Perform select (Duh.) */
263 $res = $this->db->select( 'globalimagelinks',
264 array(
265 'gil_to',
266 'gil_wiki',
267 'gil_page',
268 'gil_page_namespace',
269 'gil_page_title'
270 ),
271 $where,
272 __METHOD__,
273 array(
274 'ORDER BY' => "gil_to $order, gil_wiki $order, gil_page $order",
275 // Select an extra row to check whether we have more rows available
276 'LIMIT' => $this->limit + 1,
277 )
278 );
279
280 /* Process result */
281 // Always return the result in the same order; regardless whether reversed was specified
282 // reversed is really only used to determine from which direction the offset is
283 $rows = array();
284 foreach ( $res as $row ) {
285 $rows[] = $row;
286 }
287 if ( $this->reversed ) {
288 $rows = array_reverse( $rows );
289 }
290
291 // Build the result array
292 $count = 0;
293 $this->hasMore = false;
294 $this->result = array();
295 foreach ( $rows as $row ) {
296 $count++;
297 if ( $count > $this->limit ) {
298 // We've reached the extra row that indicates that there are more rows
299 $this->hasMore = true;
300 $this->lastRow = $row;
301 break;
302 }
303
304 if ( !isset( $this->result[$row->gil_to] ) ) {
305 $this->result[$row->gil_to] = array();
306 }
307 if ( !isset( $this->result[$row->gil_to][$row->gil_wiki] ) ) {
308 $this->result[$row->gil_to][$row->gil_wiki] = array();
309 }
310
311 $this->result[$row->gil_to][$row->gil_wiki][] = array(
312 'image' => $row->gil_to,
313 'id' => $row->gil_page,
314 'namespace' => $row->gil_page_namespace,
315 'title' => $row->gil_page_title,
316 'wiki' => $row->gil_wiki,
317 );
318 }
319 }
320
321 /**
322 * Returns the result set. The result is a 4 dimensional array
323 * (file, wiki, page), whose items are arrays with keys:
324 * - image or template: File name or template name
325 * - id: Page id
326 * - namespace: Page namespace text
327 * - title: Unprefixed page title
328 * - wiki: Wiki id
329 *
330 * @return array Result set
331 */
332 public function getResult() {
333 return $this->result;
334 }
335 /**
336 * Returns a 3 dimensional array with the result of the first file. Useful
337 * if only one resource was queried.
338 *
339 * For further information see documentation of getResult()
340 *
341 * @return array Result set
342 */
343 public function getSingleResult() {
344 if ( $this->result ) {
345 return current( $this->result );
346 } else {
347 return array();
348 }
349 }
350
351 /**
352 * Returns whether there are more results
353 *
354 * @return bool
355 */
356 public function hasMore() {
357 return $this->hasMore;
358 }
359
360 /**
361 * Returns the result length
362 *
363 * @return int
364 */
365 public function count() {
366 return count( $this->result );
367 }
368 }