]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/api/ApiQueryBacklinks.php
MediaWiki 1.14.0
[autoinstalls/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;
42
43         // output element name, database column field prefix, database table
44         private $backlinksSettings = array (
45                 'backlinks' => array (
46                         'code' => 'bl',
47                         'prefix' => 'pl',
48                         'linktbl' => 'pagelinks'
49                 ),
50                 'embeddedin' => array (
51                         'code' => 'ei',
52                         'prefix' => 'tl',
53                         'linktbl' => 'templatelinks'
54                 ),
55                 'imageusage' => array (
56                         'code' => 'iu',
57                         'prefix' => 'il',
58                         'linktbl' => 'imagelinks'
59                 )
60         );
61
62         public function __construct($query, $moduleName) {
63                 extract($this->backlinksSettings[$moduleName]);
64
65                 parent :: __construct($query, $moduleName, $code);
66                 $this->bl_ns = $prefix . '_namespace';
67                 $this->bl_from = $prefix . '_from';
68                 $this->bl_table = $linktbl;
69                 $this->bl_code = $code;
70
71                 $this->hasNS = $moduleName !== 'imageusage';
72                 if ($this->hasNS) {
73                         $this->bl_title = $prefix . '_title';
74                         $this->bl_sort = "{$this->bl_ns}, {$this->bl_title}, {$this->bl_from}";
75                         $this->bl_fields = array (
76                                 $this->bl_ns,
77                                 $this->bl_title
78                         );
79                 } else {
80                         $this->bl_title = $prefix . '_to';
81                         $this->bl_sort = "{$this->bl_title}, {$this->bl_from}";
82                         $this->bl_fields = array (
83                                 $this->bl_title
84                         );
85                 }
86         }
87
88         public function execute() {
89                 $this->run();
90         }
91
92         public function executeGenerator($resultPageSet) {
93                 $this->run($resultPageSet);
94         }
95
96         private function prepareFirstQuery($resultPageSet = null) {
97                 /* SELECT page_id, page_title, page_namespace, page_is_redirect
98                  * FROM pagelinks, page WHERE pl_from=page_id
99                  * AND pl_title='Foo' AND pl_namespace=0
100                  * LIMIT 11 ORDER BY pl_from
101                  */
102                 $db = $this->getDB();
103                 $this->addTables(array('page', $this->bl_table));
104                 $this->addWhere("{$this->bl_from}=page_id");
105                 if(is_null($resultPageSet))
106                         $this->addFields(array('page_id', 'page_title', 'page_namespace'));
107                 else
108                         $this->addFields($resultPageSet->getPageTableFields());
109                 $this->addFields('page_is_redirect');
110                 $this->addWhereFld($this->bl_title, $this->rootTitle->getDBKey());
111                 if($this->hasNS)
112                         $this->addWhereFld($this->bl_ns, $this->rootTitle->getNamespace());
113                 $this->addWhereFld('page_namespace', $this->params['namespace']);
114                 if(!is_null($this->contID))
115                         $this->addWhere("{$this->bl_from}>={$this->contID}");
116                 if($this->params['filterredir'] == 'redirects')
117                         $this->addWhereFld('page_is_redirect', 1);
118                 if($this->params['filterredir'] == 'nonredirects')
119                         $this->addWhereFld('page_is_redirect', 0);
120                 $this->addOption('LIMIT', $this->params['limit'] + 1);
121                 $this->addOption('ORDER BY', $this->bl_from);
122         }
123
124         private function prepareSecondQuery($resultPageSet = null) {
125                 /* SELECT page_id, page_title, page_namespace, page_is_redirect, pl_title, pl_namespace
126                    FROM pagelinks, page WHERE pl_from=page_id
127                    AND (pl_title='Foo' AND pl_namespace=0) OR (pl_title='Bar' AND pl_namespace=1)
128                    ORDER BY pl_namespace, pl_title, pl_from LIMIT 11
129                  */
130                 $db = $this->getDB();
131                 $this->addTables(array('page', $this->bl_table));
132                 $this->addWhere("{$this->bl_from}=page_id");
133                 if(is_null($resultPageSet))
134                         $this->addFields(array('page_id', 'page_title', 'page_namespace', 'page_is_redirect'));
135                 else
136                         $this->addFields($resultPageSet->getPageTableFields());
137                 $this->addFields($this->bl_title);
138                 if($this->hasNS)
139                         $this->addFields($this->bl_ns);
140                 $titleWhere = array();
141                 foreach($this->redirTitles as $t)
142                         $titleWhere[] = "({$this->bl_title} = ".$db->addQuotes($t->getDBKey()).
143                                         ($this->hasNS ? " AND {$this->bl_ns} = '{$t->getNamespace()}'" : "") .
144                                         ")";
145                 $this->addWhere($db->makeList($titleWhere, LIST_OR));
146                 $this->addWhereFld('page_namespace', $this->params['namespace']);
147                 if(!is_null($this->redirID))
148                 {
149                         $first = $this->redirTitles[0];
150                         $title = $db->strencode($first->getDBKey());
151                         $ns = $first->getNamespace();
152                         $from = $this->redirID;
153                         if($this->hasNS)
154                                 $this->addWhere("{$this->bl_ns} > $ns OR ".
155                                                 "({$this->bl_ns} = $ns AND ".
156                                                 "({$this->bl_title} > '$title' OR ".
157                                                 "({$this->bl_title} = '$title' AND ".
158                                                 "{$this->bl_from} >= $from)))");
159                         else
160                                 $this->addWhere("{$this->bl_title} > '$title' OR ".
161                                                 "({$this->bl_title} = '$title' AND ".
162                                                 "{$this->bl_from} >= $from)");
163                                 
164                 }
165                 if($this->params['filterredir'] == 'redirects')
166                         $this->addWhereFld('page_is_redirect', 1);
167                 if($this->params['filterredir'] == 'nonredirects')
168                         $this->addWhereFld('page_is_redirect', 0);
169                 $this->addOption('LIMIT', $this->params['limit'] + 1);
170                 $this->addOption('ORDER BY', $this->bl_sort);
171         }
172
173         private function run($resultPageSet = null) {
174                 $this->params = $this->extractRequestParams(false);
175                 $this->redirect = isset($this->params['redirect']) && $this->params['redirect'];
176                 $userMax = ( $this->redirect ? ApiBase::LIMIT_BIG1/2 : ApiBase::LIMIT_BIG1 );
177                 $botMax  = ( $this->redirect ? ApiBase::LIMIT_BIG2/2 : ApiBase::LIMIT_BIG2 );
178                 if( $this->params['limit'] == 'max' ) {
179                         $this->params['limit'] = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
180                         $this->getResult()->addValue( 'limits', $this->getModuleName(), $this->params['limit'] );
181                 }
182
183                 $this->processContinue();
184                 $this->prepareFirstQuery($resultPageSet);
185
186                 $db = $this->getDB();
187                 $res = $this->select(__METHOD__.'::firstQuery');
188
189                 $count = 0;
190                 $this->data = array ();
191                 $this->continueStr = null;
192                 $this->redirTitles = array();
193                 while ($row = $db->fetchObject($res)) {
194                         if (++ $count > $this->params['limit']) {
195                                 // We've reached the one extra which shows that there are additional pages to be had. Stop here...
196                                 // Continue string preserved in case the redirect query doesn't pass the limit
197                                 $this->continueStr = $this->getContinueStr($row->page_id);
198                                 break;
199                         }
200
201                         if (is_null($resultPageSet))
202                                 $this->extractRowInfo($row);
203                         else
204                         {
205                                 if($row->page_is_redirect)
206                                         $this->redirTitles[] = Title::makeTitle($row->page_namespace, $row->page_title);
207                                 $resultPageSet->processDbRow($row);
208                         }
209                 }
210                 $db->freeResult($res);
211
212                 if($this->redirect && count($this->redirTitles))
213                 {
214                         $this->resetQueryParams();
215                         $this->prepareSecondQuery($resultPageSet);
216                         $res = $this->select(__METHOD__.'::secondQuery');
217                         $count = 0;
218                         while($row = $db->fetchObject($res))
219                         {
220                                 if(++$count > $this->params['limit'])
221                                 {
222                                         // We've reached the one extra which shows that there are additional pages to be had. Stop here...
223                                         // We need to keep the parent page of this redir in
224                                         if($this->hasNS)
225                                                 $contTitle = Title::makeTitle($row->{$this->bl_ns}, $row->{$this->bl_title});
226                                         else
227                                                 $contTitle = Title::makeTitle(NS_FILE, $row->{$this->bl_title});
228                                         $this->continueStr = $this->getContinueRedirStr($contTitle->getArticleID(), $row->page_id);
229                                         break;
230                                 }
231
232                                 if(is_null($resultPageSet))
233                                         $this->extractRedirRowInfo($row);
234                                 else
235                                         $resultPageSet->processDbRow($row);
236                         }
237                         $db->freeResult($res);
238                 }
239                 if(!is_null($this->continueStr))
240                         $this->setContinueEnumParameter('continue', $this->continueStr);
241
242                 if (is_null($resultPageSet)) {
243                         $resultData = array();
244                         foreach($this->data as $ns => $a)
245                                 foreach($a as $title => $arr)
246                                         $resultData[] = $arr;
247                         $result = $this->getResult();
248                         $result->setIndexedTagName($resultData, $this->bl_code);
249                         $result->addValue('query', $this->getModuleName(), $resultData);
250                 }
251         }
252
253         private function extractRowInfo($row) {
254                 if(!isset($this->data[$row->page_namespace][$row->page_title])) {
255                         $this->data[$row->page_namespace][$row->page_title]['pageid'] = $row->page_id;
256                         ApiQueryBase::addTitleInfo($this->data[$row->page_namespace][$row->page_title], Title::makeTitle($row->page_namespace, $row->page_title));
257                         if($row->page_is_redirect)
258                         {
259                                 $this->data[$row->page_namespace][$row->page_title]['redirect'] = '';
260                                 $this->redirTitles[] = Title::makeTitle($row->page_namespace, $row->page_title);
261                         }
262                 }
263         }
264
265         private function extractRedirRowInfo($row)
266         {
267                 $a['pageid'] = $row->page_id;
268                 ApiQueryBase::addTitleInfo($a, Title::makeTitle($row->page_namespace, $row->page_title));
269                 if($row->page_is_redirect)
270                         $a['redirect'] = '';
271                 $ns = $this->hasNS ? $row->{$this->bl_ns} : NS_FILE;
272                 $this->data[$ns][$row->{$this->bl_title}]['redirlinks'][] = $a;
273                 $this->getResult()->setIndexedTagName($this->data[$ns][$row->{$this->bl_title}]['redirlinks'], $this->bl_code);
274         }
275
276         protected function processContinue() {
277                 if (!is_null($this->params['continue']))
278                         $this->parseContinueParam();
279                 else {
280                         if ( $this->params['title'] !== "" ) {
281                                 $title = Title::newFromText( $this->params['title'] );
282                                 if ( !$title ) {
283                                         $this->dieUsageMsg(array('invalidtitle', $this->params['title']));
284                                 } else {
285                                         $this->rootTitle = $title;
286                                 }
287                         } else {
288                                 $this->dieUsageMsg(array('missingparam', 'title'));
289                         }
290                 }
291
292                 // only image titles are allowed for the root in imageinfo mode
293                 if (!$this->hasNS && $this->rootTitle->getNamespace() !== NS_FILE)
294                         $this->dieUsage("The title for {$this->getModuleName()} query must be an image", 'bad_image_title');
295         }
296
297         protected function parseContinueParam() {
298                 $continueList = explode('|', $this->params['continue']);
299                 // expected format:
300                 // ns | key | id1 [| id2]
301                 // ns+key: root title
302                 // id1: first-level page ID to continue from
303                 // id2: second-level page ID to continue from
304
305                 // null stuff out now so we know what's set and what isn't
306                 $this->rootTitle = $this->contID = $this->redirID = null;
307                 $rootNs = intval($continueList[0]);
308                 if($rootNs === 0 && $continueList[0] !== '0')
309                         // Illegal continue parameter
310                         $this->dieUsage("Invalid continue param. You should pass the original value returned by the previous query", "_badcontinue");
311                 $this->rootTitle = Title::makeTitleSafe($rootNs, $continueList[1]);
312                 if(!$this->rootTitle)
313                         $this->dieUsage("Invalid continue param. You should pass the original value returned by the previous query", "_badcontinue");
314                 $contID = intval($continueList[2]);
315                 if($contID === 0 && $continueList[2] !== '0')
316                         $this->dieUsage("Invalid continue param. You should pass the original value returned by the previous query", "_badcontinue");
317                 $this->contID = $contID;
318                 $redirID = intval(@$continueList[3]);
319                 if($redirID === 0 && @$continueList[3] !== '0')
320                         // This one isn't required
321                         return;
322                 $this->redirID = $redirID;
323
324         }
325
326         protected function getContinueStr($lastPageID) {
327                 return $this->rootTitle->getNamespace() .
328                 '|' . $this->rootTitle->getDBkey() .
329                 '|' . $lastPageID;
330         }
331
332         protected function getContinueRedirStr($lastPageID, $lastRedirID) {
333                 return $this->getContinueStr($lastPageID) . '|' . $lastRedirID;
334         }
335
336         public function getAllowedParams() {
337                 $retval =  array (
338                         'title' => null,
339                         'continue' => null,
340                         'namespace' => array (
341                                 ApiBase :: PARAM_ISMULTI => true,
342                                 ApiBase :: PARAM_TYPE => 'namespace'
343                         ),
344                         'filterredir' => array(
345                                 ApiBase :: PARAM_DFLT => 'all',
346                                 ApiBase :: PARAM_TYPE => array(
347                                         'all',
348                                         'redirects',
349                                         'nonredirects'
350                                 )
351                         ),
352                         'limit' => array (
353                                 ApiBase :: PARAM_DFLT => 10,
354                                 ApiBase :: PARAM_TYPE => 'limit',
355                                 ApiBase :: PARAM_MIN => 1,
356                                 ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
357                                 ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
358                         )
359                 );
360                 if($this->getModuleName() == 'embeddedin')
361                         return $retval;
362                 $retval['redirect'] = false;
363                 return $retval;
364         }
365
366         public function getParamDescription() {
367                 $retval = array (
368                         'title' => 'Title to search. If null, titles= parameter will be used instead, but will be obsolete soon.',
369                         'continue' => 'When more results are available, use this to continue.',
370                         'namespace' => 'The namespace to enumerate.',
371                         'filterredir' => 'How to filter for redirects'
372                 );
373                 if($this->getModuleName() != 'embeddedin')
374                         return array_merge($retval, array(
375                                 'redirect' => 'If linking page is a redirect, find all pages that link to that redirect as well. Maximum limit is halved.',
376                                 'limit' => "How many total pages to return. If {$this->bl_code}redirect is enabled, limit applies to each level separately."
377                         ));
378                 return array_merge($retval, array(
379                         'limit' => "How many total pages to return."
380                 ));
381         }
382
383         public function getDescription() {
384                 switch ($this->getModuleName()) {
385                         case 'backlinks' :
386                                 return 'Find all pages that link to the given page';
387                         case 'embeddedin' :
388                                 return 'Find all pages that embed (transclude) the given title';
389                         case 'imageusage' :
390                                 return 'Find all pages that use the given image title.';
391                         default :
392                                 ApiBase :: dieDebug(__METHOD__, 'Unknown module name');
393                 }
394         }
395
396         protected function getExamples() {
397                 static $examples = array (
398                         'backlinks' => array (
399                                 "api.php?action=query&list=backlinks&bltitle=Main%20Page",
400                                 "api.php?action=query&generator=backlinks&gbltitle=Main%20Page&prop=info"
401                         ),
402                         'embeddedin' => array (
403                                 "api.php?action=query&list=embeddedin&eititle=Template:Stub",
404                                 "api.php?action=query&generator=embeddedin&geititle=Template:Stub&prop=info"
405                         ),
406                         'imageusage' => array (
407                                 "api.php?action=query&list=imageusage&iutitle=Image:Albert%20Einstein%20Head.jpg",
408                                 "api.php?action=query&generator=imageusage&giutitle=Image:Albert%20Einstein%20Head.jpg&prop=info"
409                         )
410                 );
411
412                 return $examples[$this->getModuleName()];
413         }
414
415         public function getVersion() {
416                 return __CLASS__ . ': $Id: ApiQueryBacklinks.php 46135 2009-01-24 13:03:40Z catrope $';
417         }
418 }