]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/api/ApiQueryBacklinks.php
MediaWiki 1.15.0
[autoinstallsdev/mediawiki.git] / includes / api / ApiQueryBacklinks.php
1 <?php
2
3 /*
4  * Created on Oct 16, 2006
5  *
6  * API for MediaWiki 1.8+
7  *
8  * Copyright (C) 2006 Yuri Astrakhan <Firstname><Lastname>@gmail.com
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License along
21  * with this program; if not, write to the Free Software Foundation, Inc.,
22  * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23  * http://www.gnu.org/copyleft/gpl.html
24  */
25
26 if (!defined('MEDIAWIKI')) {
27         // Eclipse helper - will be ignored in production
28         require_once ("ApiQueryBase.php");
29 }
30
31 /**
32  * This is a three-in-one module to query:
33  *   * backlinks  - links pointing to the given page,
34  *   * embeddedin - what pages transclude the given page within themselves,
35  *   * imageusage - what pages use the given image
36  *
37  * @ingroup API
38  */
39 class ApiQueryBacklinks extends ApiQueryGeneratorBase {
40
41         private $params, $rootTitle, $contRedirs, $contLevel, $contTitle, $contID, $redirID, $redirect;
42         private $bl_ns, $bl_from, $bl_table, $bl_code, $bl_title, $bl_sort, $bl_fields, $hasNS;
43         private $pageMap, $resultArr;
44
45         // output element name, database column field prefix, database table
46         private $backlinksSettings = array (
47                 'backlinks' => array (
48                         'code' => 'bl',
49                         'prefix' => 'pl',
50                         'linktbl' => 'pagelinks'
51                 ),
52                 'embeddedin' => array (
53                         'code' => 'ei',
54                         'prefix' => 'tl',
55                         'linktbl' => 'templatelinks'
56                 ),
57                 'imageusage' => array (
58                         'code' => 'iu',
59                         'prefix' => 'il',
60                         'linktbl' => 'imagelinks'
61                 )
62         );
63
64         public function __construct($query, $moduleName) {
65                 extract($this->backlinksSettings[$moduleName]);
66                 $this->resultArr = array();
67
68                 parent :: __construct($query, $moduleName, $code);
69                 $this->bl_ns = $prefix . '_namespace';
70                 $this->bl_from = $prefix . '_from';
71                 $this->bl_table = $linktbl;
72                 $this->bl_code = $code;
73
74                 $this->hasNS = $moduleName !== 'imageusage';
75                 if ($this->hasNS) {
76                         $this->bl_title = $prefix . '_title';
77                         $this->bl_sort = "{$this->bl_ns}, {$this->bl_title}, {$this->bl_from}";
78                         $this->bl_fields = array (
79                                 $this->bl_ns,
80                                 $this->bl_title
81                         );
82                 } else {
83                         $this->bl_title = $prefix . '_to';
84                         $this->bl_sort = "{$this->bl_title}, {$this->bl_from}";
85                         $this->bl_fields = array (
86                                 $this->bl_title
87                         );
88                 }
89         }
90
91         public function execute() {
92                 $this->run();
93         }
94
95         public function executeGenerator($resultPageSet) {
96                 $this->run($resultPageSet);
97         }
98
99         private function prepareFirstQuery($resultPageSet = null) {
100                 /* SELECT page_id, page_title, page_namespace, page_is_redirect
101                  * FROM pagelinks, page WHERE pl_from=page_id
102                  * AND pl_title='Foo' AND pl_namespace=0
103                  * LIMIT 11 ORDER BY pl_from
104                  */
105                 $db = $this->getDB();
106                 $this->addTables(array('page', $this->bl_table));
107                 $this->addWhere("{$this->bl_from}=page_id");
108                 if(is_null($resultPageSet))
109                         $this->addFields(array('page_id', 'page_title', 'page_namespace'));
110                 else
111                         $this->addFields($resultPageSet->getPageTableFields());
112                 $this->addFields('page_is_redirect');
113                 $this->addWhereFld($this->bl_title, $this->rootTitle->getDBKey());
114                 if($this->hasNS)
115                         $this->addWhereFld($this->bl_ns, $this->rootTitle->getNamespace());
116                 $this->addWhereFld('page_namespace', $this->params['namespace']);
117                 if(!is_null($this->contID))
118                         $this->addWhere("{$this->bl_from}>={$this->contID}");
119                 if($this->params['filterredir'] == 'redirects')
120                         $this->addWhereFld('page_is_redirect', 1);
121                 if($this->params['filterredir'] == 'nonredirects')
122                         $this->addWhereFld('page_is_redirect', 0);
123                 $this->addOption('LIMIT', $this->params['limit'] + 1);
124                 $this->addOption('ORDER BY', $this->bl_from);
125         }
126
127         private function prepareSecondQuery($resultPageSet = null) {
128                 /* SELECT page_id, page_title, page_namespace, page_is_redirect, pl_title, pl_namespace
129                    FROM pagelinks, page WHERE pl_from=page_id
130                    AND (pl_title='Foo' AND pl_namespace=0) OR (pl_title='Bar' AND pl_namespace=1)
131                    ORDER BY pl_namespace, pl_title, pl_from LIMIT 11
132                  */
133                 $db = $this->getDB();
134                 $this->addTables(array('page', $this->bl_table));
135                 $this->addWhere("{$this->bl_from}=page_id");
136                 if(is_null($resultPageSet))
137                         $this->addFields(array('page_id', 'page_title', 'page_namespace', 'page_is_redirect'));
138                 else
139                         $this->addFields($resultPageSet->getPageTableFields());
140                 $this->addFields($this->bl_title);
141                 if($this->hasNS)
142                         $this->addFields($this->bl_ns);
143                 // We can't use LinkBatch here because $this->hasNS may be false
144                 $titleWhere = array();
145                 foreach($this->redirTitles as $t)
146                         $titleWhere[] = "{$this->bl_title} = ".$db->addQuotes($t->getDBKey()).
147                                         ($this->hasNS ? " AND {$this->bl_ns} = '{$t->getNamespace()}'" : "");
148                 $this->addWhere($db->makeList($titleWhere, LIST_OR));
149                 $this->addWhereFld('page_namespace', $this->params['namespace']);
150                 if(!is_null($this->redirID))
151                 {
152                         $first = $this->redirTitles[0];
153                         $title = $db->strencode($first->getDBKey());
154                         $ns = $first->getNamespace();
155                         $from = $this->redirID;
156                         if($this->hasNS)
157                                 $this->addWhere("{$this->bl_ns} > $ns OR ".
158                                                 "({$this->bl_ns} = $ns AND ".
159                                                 "({$this->bl_title} > '$title' OR ".
160                                                 "({$this->bl_title} = '$title' AND ".
161                                                 "{$this->bl_from} >= $from)))");
162                         else
163                                 $this->addWhere("{$this->bl_title} > '$title' OR ".
164                                                 "({$this->bl_title} = '$title' AND ".
165                                                 "{$this->bl_from} >= $from)");
166                                 
167                 }
168                 if($this->params['filterredir'] == 'redirects')
169                         $this->addWhereFld('page_is_redirect', 1);
170                 if($this->params['filterredir'] == 'nonredirects')
171                         $this->addWhereFld('page_is_redirect', 0);
172                 $this->addOption('LIMIT', $this->params['limit'] + 1);
173                 $this->addOption('ORDER BY', $this->bl_sort);
174                 $this->addOption('USE INDEX', array('page' => 'PRIMARY'));
175         }
176
177         private function run($resultPageSet = null) {
178                 $this->params = $this->extractRequestParams(false);
179                 $this->redirect = isset($this->params['redirect']) && $this->params['redirect'];
180                 $userMax = ( $this->redirect ? ApiBase::LIMIT_BIG1/2 : ApiBase::LIMIT_BIG1 );
181                 $botMax  = ( $this->redirect ? ApiBase::LIMIT_BIG2/2 : ApiBase::LIMIT_BIG2 );
182                 if( $this->params['limit'] == 'max' ) {
183                         $this->params['limit'] = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
184                         $this->getResult()->addValue( 'limits', $this->getModuleName(), $this->params['limit'] );
185                 }
186
187                 $this->processContinue();
188                 $this->prepareFirstQuery($resultPageSet);
189
190                 $db = $this->getDB();
191                 $res = $this->select(__METHOD__.'::firstQuery');
192
193                 $count = 0;
194                 $this->pageMap = array(); // Maps ns and title to pageid
195                 $this->continueStr = null;
196                 $this->redirTitles = array();
197                 while ($row = $db->fetchObject($res)) {
198                         if (++ $count > $this->params['limit']) {
199                                 // We've reached the one extra which shows that there are additional pages to be had. Stop here...
200                                 // Continue string preserved in case the redirect query doesn't pass the limit
201                                 $this->continueStr = $this->getContinueStr($row->page_id);
202                                 break;
203                         }
204
205                         if (is_null($resultPageSet))
206                                 $this->extractRowInfo($row);
207                         else
208                         {
209                                 $this->pageMap[$row->page_namespace][$row->page_title] = $row->page_id;
210                                 if($row->page_is_redirect)
211                                         $this->redirTitles[] = Title::makeTitle($row->page_namespace, $row->page_title);
212                                 $resultPageSet->processDbRow($row);
213                         }
214                 }
215                 $db->freeResult($res);
216
217                 if($this->redirect && count($this->redirTitles))
218                 {
219                         $this->resetQueryParams();
220                         $this->prepareSecondQuery($resultPageSet);
221                         $res = $this->select(__METHOD__.'::secondQuery');
222                         $count = 0;
223                         while($row = $db->fetchObject($res))
224                         {
225                                 if(++$count > $this->params['limit'])
226                                 {
227                                         // We've reached the one extra which shows that there are additional pages to be had. Stop here...
228                                         // We need to keep the parent page of this redir in
229                                         if($this->hasNS)
230                                                 $parentID = $this->pageMap[$row->{$this->bl_ns}][$row->{$this->bl_title}];
231                                         else
232                                                 $parentID = $this->pageMap[NS_IMAGE][$row->{$this->bl_title}];
233                                                 $this->continueStr = $this->getContinueRedirStr($parentID, $row->page_id);
234                                         break;
235                                 }
236
237                                 if(is_null($resultPageSet))
238                                         $this->extractRedirRowInfo($row);
239                                 else
240                                         $resultPageSet->processDbRow($row);
241                         }
242                         $db->freeResult($res);
243                 }
244                 if (is_null($resultPageSet)) {
245                         // Try to add the result data in one go and pray that it fits
246                         $fit = $this->getResult()->addValue('query', $this->getModuleName(), array_values($this->resultArr));
247                         if(!$fit)
248                         {
249                                 // It didn't fit. Add elements one by one until the
250                                 // result is full.
251                                 foreach($this->resultArr as $pageID => $arr)
252                                 {
253                                         // Add the basic entry without redirlinks first
254                                         $fit = $this->getResult()->addValue(
255                                                 array('query', $this->getModuleName()),
256                                                 null, array_diff_key($arr, array('redirlinks' => '')));
257                                         if(!$fit)
258                                         {
259                                                 $this->continueStr = $this->getContinueStr($pageID);
260                                                 break;
261                                         }
262
263                                         $hasRedirs = false;
264                                         foreach((array)@$arr['redirlinks'] as $key => $redir)
265                                         {
266                                                 $fit = $this->getResult()->addValue(
267                                                         array('query', $this->getModuleName(), $pageID, 'redirlinks'),
268                                                         $key, $redir);
269                                                 if(!$fit)
270                                                 {
271                                                         $this->continueStr = $this->getContinueRedirStr($pageID, $redir['pageid']);
272                                                         break;
273                                                 }
274                                                 $hasRedirs = true;
275                                         }
276                                         if($hasRedirs)
277                                                 $this->getResult()->setIndexedTagName_internal(
278                                                         array('query', $this->getModuleName(), $pageID, 'redirlinks'),
279                                                         $this->bl_code);
280                                         if(!$fit)
281                                                 break;
282                                 }
283                         }               
284
285                         $this->getResult()->setIndexedTagName_internal(
286                                          array('query', $this->getModuleName()),
287                                          $this->bl_code);
288                 }
289                 if(!is_null($this->continueStr))
290                         $this->setContinueEnumParameter('continue', $this->continueStr);
291         }
292
293         private function extractRowInfo($row) {
294                 $this->pageMap[$row->page_namespace][$row->page_title] = $row->page_id;
295                 $t = Title::makeTitle($row->page_namespace, $row->page_title);
296                 $a = array('pageid' => intval($row->page_id));
297                 ApiQueryBase::addTitleInfo($a, $t);
298                 if($row->page_is_redirect)
299                 {
300                         $a['redirect'] = '';
301                         $this->redirTitles[] = $t;
302                 }
303                 // Put all the results in an array first
304                 $this->resultArr[$a['pageid']] = $a;
305         }
306
307         private function extractRedirRowInfo($row)
308         {
309                 $a['pageid'] = intval($row->page_id);
310                 ApiQueryBase::addTitleInfo($a, Title::makeTitle($row->page_namespace, $row->page_title));
311                 if($row->page_is_redirect)
312                         $a['redirect'] = '';
313                 $ns = $this->hasNS ? $row->{$this->bl_ns} : NS_FILE;
314                 $parentID = $this->pageMap[$ns][$row->{$this->bl_title}];
315                 // Put all the results in an array first
316                 $this->resultArr[$parentID]['redirlinks'][] = $a;
317                 $this->getResult()->setIndexedTagName($this->resultArr[$parentID]['redirlinks'], $this->bl_code);
318         }
319
320         protected function processContinue() {
321                 if (!is_null($this->params['continue']))
322                         $this->parseContinueParam();
323                 else {
324                         if ( $this->params['title'] !== "" ) {
325                                 $title = Title::newFromText( $this->params['title'] );
326                                 if ( !$title ) {
327                                         $this->dieUsageMsg(array('invalidtitle', $this->params['title']));
328                                 } else {
329                                         $this->rootTitle = $title;
330                                 }
331                         } else {
332                                 $this->dieUsageMsg(array('missingparam', 'title'));
333                         }
334                 }
335
336                 // only image titles are allowed for the root in imageinfo mode
337                 if (!$this->hasNS && $this->rootTitle->getNamespace() !== NS_FILE)
338                         $this->dieUsage("The title for {$this->getModuleName()} query must be an image", 'bad_image_title');
339         }
340
341         protected function parseContinueParam() {
342                 $continueList = explode('|', $this->params['continue']);
343                 // expected format:
344                 // ns | key | id1 [| id2]
345                 // ns+key: root title
346                 // id1: first-level page ID to continue from
347                 // id2: second-level page ID to continue from
348
349                 // null stuff out now so we know what's set and what isn't
350                 $this->rootTitle = $this->contID = $this->redirID = null;
351                 $rootNs = intval($continueList[0]);
352                 if($rootNs === 0 && $continueList[0] !== '0')
353                         // Illegal continue parameter
354                         $this->dieUsage("Invalid continue param. You should pass the original value returned by the previous query", "_badcontinue");
355                 $this->rootTitle = Title::makeTitleSafe($rootNs, $continueList[1]);
356                 if(!$this->rootTitle)
357                         $this->dieUsage("Invalid continue param. You should pass the original value returned by the previous query", "_badcontinue");
358                 $contID = intval($continueList[2]);
359                 if($contID === 0 && $continueList[2] !== '0')
360                         $this->dieUsage("Invalid continue param. You should pass the original value returned by the previous query", "_badcontinue");
361                 $this->contID = $contID;
362                 $redirID = intval(@$continueList[3]);
363                 if($redirID === 0 && @$continueList[3] !== '0')
364                         // This one isn't required
365                         return;
366                 $this->redirID = $redirID;
367
368         }
369
370         protected function getContinueStr($lastPageID) {
371                 return $this->rootTitle->getNamespace() .
372                 '|' . $this->rootTitle->getDBkey() .
373                 '|' . $lastPageID;
374         }
375
376         protected function getContinueRedirStr($lastPageID, $lastRedirID) {
377                 return $this->getContinueStr($lastPageID) . '|' . $lastRedirID;
378         }
379
380         public function getAllowedParams() {
381                 $retval =  array (
382                         'title' => null,
383                         'continue' => null,
384                         'namespace' => array (
385                                 ApiBase :: PARAM_ISMULTI => true,
386                                 ApiBase :: PARAM_TYPE => 'namespace'
387                         ),
388                         'filterredir' => array(
389                                 ApiBase :: PARAM_DFLT => 'all',
390                                 ApiBase :: PARAM_TYPE => array(
391                                         'all',
392                                         'redirects',
393                                         'nonredirects'
394                                 )
395                         ),
396                         'limit' => array (
397                                 ApiBase :: PARAM_DFLT => 10,
398                                 ApiBase :: PARAM_TYPE => 'limit',
399                                 ApiBase :: PARAM_MIN => 1,
400                                 ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
401                                 ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
402                         )
403                 );
404                 if($this->getModuleName() == 'embeddedin')
405                         return $retval;
406                 $retval['redirect'] = false;
407                 return $retval;
408         }
409
410         public function getParamDescription() {
411                 $retval = array (
412                         'title' => 'Title to search. If null, titles= parameter will be used instead, but will be obsolete soon.',
413                         'continue' => 'When more results are available, use this to continue.',
414                         'namespace' => 'The namespace to enumerate.',
415                         'filterredir' => 'How to filter for redirects'
416                 );
417                 if($this->getModuleName() != 'embeddedin')
418                         return array_merge($retval, array(
419                                 'redirect' => 'If linking page is a redirect, find all pages that link to that redirect as well. Maximum limit is halved.',
420                                 'limit' => "How many total pages to return. If {$this->bl_code}redirect is enabled, limit applies to each level separately."
421                         ));
422                 return array_merge($retval, array(
423                         'limit' => "How many total pages to return."
424                 ));
425         }
426
427         public function getDescription() {
428                 switch ($this->getModuleName()) {
429                         case 'backlinks' :
430                                 return 'Find all pages that link to the given page';
431                         case 'embeddedin' :
432                                 return 'Find all pages that embed (transclude) the given title';
433                         case 'imageusage' :
434                                 return 'Find all pages that use the given image title.';
435                         default :
436                                 ApiBase :: dieDebug(__METHOD__, 'Unknown module name');
437                 }
438         }
439
440         protected function getExamples() {
441                 static $examples = array (
442                         'backlinks' => array (
443                                 "api.php?action=query&list=backlinks&bltitle=Main%20Page",
444                                 "api.php?action=query&generator=backlinks&gbltitle=Main%20Page&prop=info"
445                         ),
446                         'embeddedin' => array (
447                                 "api.php?action=query&list=embeddedin&eititle=Template:Stub",
448                                 "api.php?action=query&generator=embeddedin&geititle=Template:Stub&prop=info"
449                         ),
450                         'imageusage' => array (
451                                 "api.php?action=query&list=imageusage&iutitle=File:Albert%20Einstein%20Head.jpg",
452                                 "api.php?action=query&generator=imageusage&giutitle=File:Albert%20Einstein%20Head.jpg&prop=info"
453                         )
454                 );
455
456                 return $examples[$this->getModuleName()];
457         }
458
459         public function getVersion() {
460                 return __CLASS__ . ': $Id: ApiQueryBacklinks.php 50217 2009-05-05 13:12:16Z tstarling $';
461         }
462 }