[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * 4 * 5 * Created on Oct 16, 2006 6 * 7 * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com" 8 * 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License as published by 11 * the Free Software Foundation; either version 2 of the License, or 12 * (at your option) any later version. 13 * 14 * This program is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 * GNU General Public License for more details. 18 * 19 * You should have received a copy of the GNU General Public License along 20 * with this program; if not, write to the Free Software Foundation, Inc., 21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 22 * http://www.gnu.org/copyleft/gpl.html 23 * 24 * @file 25 */ 26 27 /** 28 * This is a three-in-one module to query: 29 * * backlinks - links pointing to the given page, 30 * * embeddedin - what pages transclude the given page within themselves, 31 * * imageusage - what pages use the given image 32 * 33 * @ingroup API 34 */ 35 class ApiQueryBacklinks extends ApiQueryGeneratorBase { 36 37 /** 38 * @var Title 39 */ 40 private $rootTitle; 41 42 private $params, $contID, $redirID, $redirect; 43 private $bl_ns, $bl_from, $bl_table, $bl_code, $bl_title, $bl_fields, $hasNS; 44 45 /** 46 * Maps ns and title to pageid 47 * 48 * @var array 49 */ 50 private $pageMap = array(); 51 private $resultArr; 52 53 private $redirTitles = array(); 54 private $continueStr = null; 55 56 // output element name, database column field prefix, database table 57 private $backlinksSettings = array( 58 'backlinks' => array( 59 'code' => 'bl', 60 'prefix' => 'pl', 61 'linktbl' => 'pagelinks', 62 'helpurl' => 'https://www.mediawiki.org/wiki/API:Backlinks', 63 ), 64 'embeddedin' => array( 65 'code' => 'ei', 66 'prefix' => 'tl', 67 'linktbl' => 'templatelinks', 68 'helpurl' => 'https://www.mediawiki.org/wiki/API:Embeddedin', 69 ), 70 'imageusage' => array( 71 'code' => 'iu', 72 'prefix' => 'il', 73 'linktbl' => 'imagelinks', 74 'helpurl' => 'https://www.mediawiki.org/wiki/API:Imageusage', 75 ) 76 ); 77 78 public function __construct( ApiQuery $query, $moduleName ) { 79 $settings = $this->backlinksSettings[$moduleName]; 80 $prefix = $settings['prefix']; 81 $code = $settings['code']; 82 $this->resultArr = array(); 83 84 parent::__construct( $query, $moduleName, $code ); 85 $this->bl_ns = $prefix . '_namespace'; 86 $this->bl_from = $prefix . '_from'; 87 $this->bl_table = $settings['linktbl']; 88 $this->bl_code = $code; 89 $this->helpUrl = $settings['helpurl']; 90 91 $this->hasNS = $moduleName !== 'imageusage'; 92 if ( $this->hasNS ) { 93 $this->bl_title = $prefix . '_title'; 94 $this->bl_fields = array( 95 $this->bl_ns, 96 $this->bl_title 97 ); 98 } else { 99 $this->bl_title = $prefix . '_to'; 100 $this->bl_fields = array( 101 $this->bl_title 102 ); 103 } 104 } 105 106 public function execute() { 107 $this->run(); 108 } 109 110 public function getCacheMode( $params ) { 111 return 'public'; 112 } 113 114 public function executeGenerator( $resultPageSet ) { 115 $this->run( $resultPageSet ); 116 } 117 118 /** 119 * @param ApiPageSet $resultPageSet 120 * @return void 121 */ 122 private function prepareFirstQuery( $resultPageSet = null ) { 123 /* SELECT page_id, page_title, page_namespace, page_is_redirect 124 * FROM pagelinks, page WHERE pl_from=page_id 125 * AND pl_title='Foo' AND pl_namespace=0 126 * LIMIT 11 ORDER BY pl_from 127 */ 128 $this->addTables( array( $this->bl_table, 'page' ) ); 129 $this->addWhere( "{$this->bl_from}=page_id" ); 130 if ( is_null( $resultPageSet ) ) { 131 $this->addFields( array( 'page_id', 'page_title', 'page_namespace' ) ); 132 } else { 133 $this->addFields( $resultPageSet->getPageTableFields() ); 134 } 135 136 $this->addFields( 'page_is_redirect' ); 137 $this->addWhereFld( $this->bl_title, $this->rootTitle->getDBkey() ); 138 139 if ( $this->hasNS ) { 140 $this->addWhereFld( $this->bl_ns, $this->rootTitle->getNamespace() ); 141 } 142 $this->addWhereFld( 'page_namespace', $this->params['namespace'] ); 143 144 if ( !is_null( $this->contID ) ) { 145 $op = $this->params['dir'] == 'descending' ? '<' : '>'; 146 $this->addWhere( "{$this->bl_from}$op={$this->contID}" ); 147 } 148 149 if ( $this->params['filterredir'] == 'redirects' ) { 150 $this->addWhereFld( 'page_is_redirect', 1 ); 151 } elseif ( $this->params['filterredir'] == 'nonredirects' && !$this->redirect ) { 152 // bug 22245 - Check for !redirect, as filtering nonredirects, when 153 // getting what links to them is contradictory 154 $this->addWhereFld( 'page_is_redirect', 0 ); 155 } 156 157 $this->addOption( 'LIMIT', $this->params['limit'] + 1 ); 158 $sort = ( $this->params['dir'] == 'descending' ? ' DESC' : '' ); 159 $this->addOption( 'ORDER BY', $this->bl_from . $sort ); 160 $this->addOption( 'STRAIGHT_JOIN' ); 161 } 162 163 /** 164 * @param ApiPageSet $resultPageSet 165 * @return void 166 */ 167 private function prepareSecondQuery( $resultPageSet = null ) { 168 /* SELECT page_id, page_title, page_namespace, page_is_redirect, pl_title, pl_namespace 169 FROM pagelinks, page WHERE pl_from=page_id 170 AND (pl_title='Foo' AND pl_namespace=0) OR (pl_title='Bar' AND pl_namespace=1) 171 ORDER BY pl_namespace, pl_title, pl_from LIMIT 11 172 */ 173 $db = $this->getDB(); 174 $this->addTables( array( 'page', $this->bl_table ) ); 175 $this->addWhere( "{$this->bl_from}=page_id" ); 176 177 if ( is_null( $resultPageSet ) ) { 178 $this->addFields( array( 'page_id', 'page_title', 'page_namespace', 'page_is_redirect' ) ); 179 } else { 180 $this->addFields( $resultPageSet->getPageTableFields() ); 181 } 182 183 $this->addFields( $this->bl_title ); 184 if ( $this->hasNS ) { 185 $this->addFields( $this->bl_ns ); 186 } 187 188 // We can't use LinkBatch here because $this->hasNS may be false 189 $titleWhere = array(); 190 $allRedirNs = array(); 191 $allRedirDBkey = array(); 192 /** @var $t Title */ 193 foreach ( $this->redirTitles as $t ) { 194 $redirNs = $t->getNamespace(); 195 $redirDBkey = $t->getDBkey(); 196 $titleWhere[] = "{$this->bl_title} = " . $db->addQuotes( $redirDBkey ) . 197 ( $this->hasNS ? " AND {$this->bl_ns} = {$redirNs}" : '' ); 198 $allRedirNs[] = $redirNs; 199 $allRedirDBkey[] = $redirDBkey; 200 } 201 $this->addWhere( $db->makeList( $titleWhere, LIST_OR ) ); 202 $this->addWhereFld( 'page_namespace', $this->params['namespace'] ); 203 204 if ( !is_null( $this->redirID ) ) { 205 $op = $this->params['dir'] == 'descending' ? '<' : '>'; 206 /** @var $first Title */ 207 $first = $this->redirTitles[0]; 208 $title = $db->addQuotes( $first->getDBkey() ); 209 $ns = $first->getNamespace(); 210 $from = $this->redirID; 211 if ( $this->hasNS ) { 212 $this->addWhere( "{$this->bl_ns} $op $ns OR " . 213 "({$this->bl_ns} = $ns AND " . 214 "({$this->bl_title} $op $title OR " . 215 "({$this->bl_title} = $title AND " . 216 "{$this->bl_from} $op= $from)))" ); 217 } else { 218 $this->addWhere( "{$this->bl_title} $op $title OR " . 219 "({$this->bl_title} = $title AND " . 220 "{$this->bl_from} $op= $from)" ); 221 } 222 } 223 if ( $this->params['filterredir'] == 'redirects' ) { 224 $this->addWhereFld( 'page_is_redirect', 1 ); 225 } elseif ( $this->params['filterredir'] == 'nonredirects' ) { 226 $this->addWhereFld( 'page_is_redirect', 0 ); 227 } 228 229 $this->addOption( 'LIMIT', $this->params['limit'] + 1 ); 230 $orderBy = array(); 231 $sort = ( $this->params['dir'] == 'descending' ? ' DESC' : '' ); 232 // Don't order by namespace/title if it's constant in the WHERE clause 233 if ( $this->hasNS && count( array_unique( $allRedirNs ) ) != 1 ) { 234 $orderBy[] = $this->bl_ns . $sort; 235 } 236 if ( count( array_unique( $allRedirDBkey ) ) != 1 ) { 237 $orderBy[] = $this->bl_title . $sort; 238 } 239 $orderBy[] = $this->bl_from . $sort; 240 $this->addOption( 'ORDER BY', $orderBy ); 241 $this->addOption( 'USE INDEX', array( 'page' => 'PRIMARY' ) ); 242 } 243 244 /** 245 * @param ApiPageSet $resultPageSet 246 * @return void 247 */ 248 private function run( $resultPageSet = null ) { 249 $this->params = $this->extractRequestParams( false ); 250 $this->redirect = isset( $this->params['redirect'] ) && $this->params['redirect']; 251 $userMax = ( $this->redirect ? ApiBase::LIMIT_BIG1 / 2 : ApiBase::LIMIT_BIG1 ); 252 $botMax = ( $this->redirect ? ApiBase::LIMIT_BIG2 / 2 : ApiBase::LIMIT_BIG2 ); 253 254 $result = $this->getResult(); 255 256 if ( $this->params['limit'] == 'max' ) { 257 $this->params['limit'] = $this->getMain()->canApiHighLimits() ? $botMax : $userMax; 258 $result->setParsedLimit( $this->getModuleName(), $this->params['limit'] ); 259 } else { 260 $this->params['limit'] = intval( $this->params['limit'] ); 261 $this->validateLimit( 'limit', $this->params['limit'], 1, $userMax, $botMax ); 262 } 263 264 $this->processContinue(); 265 $this->prepareFirstQuery( $resultPageSet ); 266 267 $res = $this->select( __METHOD__ . '::firstQuery' ); 268 269 $count = 0; 270 271 foreach ( $res as $row ) { 272 if ( ++$count > $this->params['limit'] ) { 273 // We've reached the one extra which shows that there are 274 // additional pages to be had. Stop here... 275 // Continue string preserved in case the redirect query doesn't pass the limit 276 $this->continueStr = $this->getContinueStr( $row->page_id ); 277 break; 278 } 279 280 if ( is_null( $resultPageSet ) ) { 281 $this->extractRowInfo( $row ); 282 } else { 283 $this->pageMap[$row->page_namespace][$row->page_title] = $row->page_id; 284 if ( $row->page_is_redirect ) { 285 $this->redirTitles[] = Title::makeTitle( $row->page_namespace, $row->page_title ); 286 } 287 288 $resultPageSet->processDbRow( $row ); 289 } 290 } 291 292 if ( $this->redirect && count( $this->redirTitles ) ) { 293 $this->resetQueryParams(); 294 $this->prepareSecondQuery( $resultPageSet ); 295 $res = $this->select( __METHOD__ . '::secondQuery' ); 296 $count = 0; 297 foreach ( $res as $row ) { 298 if ( ++$count > $this->params['limit'] ) { 299 // We've reached the one extra which shows that there are 300 // additional pages to be had. Stop here... 301 // We need to keep the parent page of this redir in 302 if ( $this->hasNS ) { 303 $parentID = $this->pageMap[$row->{$this->bl_ns}][$row->{$this->bl_title}]; 304 } else { 305 $parentID = $this->pageMap[NS_FILE][$row->{$this->bl_title}]; 306 } 307 $this->continueStr = $this->getContinueRedirStr( $parentID, $row->page_id ); 308 break; 309 } 310 311 if ( is_null( $resultPageSet ) ) { 312 $this->extractRedirRowInfo( $row ); 313 } else { 314 $resultPageSet->processDbRow( $row ); 315 } 316 } 317 } 318 if ( is_null( $resultPageSet ) ) { 319 // Try to add the result data in one go and pray that it fits 320 $fit = $result->addValue( 'query', $this->getModuleName(), array_values( $this->resultArr ) ); 321 if ( !$fit ) { 322 // It didn't fit. Add elements one by one until the 323 // result is full. 324 foreach ( $this->resultArr as $pageID => $arr ) { 325 // Add the basic entry without redirlinks first 326 $fit = $result->addValue( 327 array( 'query', $this->getModuleName() ), 328 null, array_diff_key( $arr, array( 'redirlinks' => '' ) ) ); 329 if ( !$fit ) { 330 $this->continueStr = $this->getContinueStr( $pageID ); 331 break; 332 } 333 334 $hasRedirs = false; 335 $redirLinks = isset( $arr['redirlinks'] ) ? $arr['redirlinks'] : array(); 336 foreach ( (array)$redirLinks as $key => $redir ) { 337 $fit = $result->addValue( 338 array( 'query', $this->getModuleName(), $pageID, 'redirlinks' ), 339 $key, $redir ); 340 if ( !$fit ) { 341 $this->continueStr = $this->getContinueRedirStr( $pageID, $redir['pageid'] ); 342 break; 343 } 344 $hasRedirs = true; 345 } 346 if ( $hasRedirs ) { 347 $result->setIndexedTagName_internal( 348 array( 'query', $this->getModuleName(), $pageID, 'redirlinks' ), 349 $this->bl_code ); 350 } 351 if ( !$fit ) { 352 break; 353 } 354 } 355 } 356 357 $result->setIndexedTagName_internal( 358 array( 'query', $this->getModuleName() ), 359 $this->bl_code 360 ); 361 } 362 if ( !is_null( $this->continueStr ) ) { 363 $this->setContinueEnumParameter( 'continue', $this->continueStr ); 364 } 365 } 366 367 private function extractRowInfo( $row ) { 368 $this->pageMap[$row->page_namespace][$row->page_title] = $row->page_id; 369 $t = Title::makeTitle( $row->page_namespace, $row->page_title ); 370 $a = array( 'pageid' => intval( $row->page_id ) ); 371 ApiQueryBase::addTitleInfo( $a, $t ); 372 if ( $row->page_is_redirect ) { 373 $a['redirect'] = ''; 374 $this->redirTitles[] = $t; 375 } 376 // Put all the results in an array first 377 $this->resultArr[$a['pageid']] = $a; 378 } 379 380 private function extractRedirRowInfo( $row ) { 381 $a['pageid'] = intval( $row->page_id ); 382 ApiQueryBase::addTitleInfo( $a, Title::makeTitle( $row->page_namespace, $row->page_title ) ); 383 if ( $row->page_is_redirect ) { 384 $a['redirect'] = ''; 385 } 386 $ns = $this->hasNS ? $row->{$this->bl_ns} : NS_FILE; 387 $parentID = $this->pageMap[$ns][$row->{$this->bl_title}]; 388 // Put all the results in an array first 389 $this->resultArr[$parentID]['redirlinks'][] = $a; 390 $this->getResult()->setIndexedTagName( 391 $this->resultArr[$parentID]['redirlinks'], 392 $this->bl_code 393 ); 394 } 395 396 protected function processContinue() { 397 if ( !is_null( $this->params['continue'] ) ) { 398 $this->parseContinueParam(); 399 } else { 400 $this->rootTitle = $this->getTitleOrPageId( $this->params )->getTitle(); 401 } 402 403 // only image titles are allowed for the root in imageinfo mode 404 if ( !$this->hasNS && $this->rootTitle->getNamespace() !== NS_FILE ) { 405 $this->dieUsage( 406 "The title for {$this->getModuleName()} query must be an image", 407 'bad_image_title' 408 ); 409 } 410 } 411 412 protected function parseContinueParam() { 413 $continueList = explode( '|', $this->params['continue'] ); 414 // expected format: 415 // ns | key | id1 [| id2] 416 // ns+key: root title 417 // id1: first-level page ID to continue from 418 // id2: second-level page ID to continue from 419 420 // null stuff out now so we know what's set and what isn't 421 $this->rootTitle = $this->contID = $this->redirID = null; 422 $rootNs = intval( $continueList[0] ); 423 $this->dieContinueUsageIf( $rootNs === 0 && $continueList[0] !== '0' ); 424 425 $this->rootTitle = Title::makeTitleSafe( $rootNs, $continueList[1] ); 426 $this->dieContinueUsageIf( !$this->rootTitle ); 427 428 $contID = intval( $continueList[2] ); 429 $this->dieContinueUsageIf( $contID === 0 && $continueList[2] !== '0' ); 430 431 $this->contID = $contID; 432 $id2 = isset( $continueList[3] ) ? $continueList[3] : null; 433 $redirID = intval( $id2 ); 434 435 if ( $redirID === 0 && $id2 !== '0' ) { 436 // This one isn't required 437 return; 438 } 439 $this->redirID = $redirID; 440 } 441 442 protected function getContinueStr( $lastPageID ) { 443 return $this->rootTitle->getNamespace() . 444 '|' . $this->rootTitle->getDBkey() . 445 '|' . $lastPageID; 446 } 447 448 protected function getContinueRedirStr( $lastPageID, $lastRedirID ) { 449 return $this->getContinueStr( $lastPageID ) . '|' . $lastRedirID; 450 } 451 452 public function getAllowedParams() { 453 $retval = array( 454 'title' => array( 455 ApiBase::PARAM_TYPE => 'string', 456 ), 457 'pageid' => array( 458 ApiBase::PARAM_TYPE => 'integer', 459 ), 460 'continue' => null, 461 'namespace' => array( 462 ApiBase::PARAM_ISMULTI => true, 463 ApiBase::PARAM_TYPE => 'namespace' 464 ), 465 'dir' => array( 466 ApiBase::PARAM_DFLT => 'ascending', 467 ApiBase::PARAM_TYPE => array( 468 'ascending', 469 'descending' 470 ) 471 ), 472 'filterredir' => array( 473 ApiBase::PARAM_DFLT => 'all', 474 ApiBase::PARAM_TYPE => array( 475 'all', 476 'redirects', 477 'nonredirects' 478 ) 479 ), 480 'limit' => array( 481 ApiBase::PARAM_DFLT => 10, 482 ApiBase::PARAM_TYPE => 'limit', 483 ApiBase::PARAM_MIN => 1, 484 ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1, 485 ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2 486 ) 487 ); 488 if ( $this->getModuleName() == 'embeddedin' ) { 489 return $retval; 490 } 491 $retval['redirect'] = false; 492 493 return $retval; 494 } 495 496 public function getParamDescription() { 497 $retval = array( 498 'title' => "Title to search. Cannot be used together with {$this->bl_code}pageid", 499 'pageid' => "Pageid to search. Cannot be used together with {$this->bl_code}title", 500 'continue' => 'When more results are available, use this to continue', 501 'namespace' => 'The namespace to enumerate', 502 'dir' => 'The direction in which to list', 503 ); 504 if ( $this->getModuleName() != 'embeddedin' ) { 505 return array_merge( $retval, array( 506 'redirect' => 'If linking page is a redirect, find all pages ' . 507 'that link to that redirect as well. Maximum limit is halved.', 508 'filterredir' => 'How to filter for redirects. If set to ' . 509 "nonredirects when {$this->bl_code}redirect is enabled, " . 510 'this is only applied to the second level', 511 'limit' => 'How many total pages to return. If ' . 512 "{$this->bl_code}redirect is enabled, limit applies to each " . 513 'level separately (which means you may get up to 2 * limit results).' 514 ) ); 515 } 516 517 return array_merge( $retval, array( 518 'filterredir' => 'How to filter for redirects', 519 'limit' => 'How many total pages to return' 520 ) ); 521 } 522 523 public function getDescription() { 524 switch ( $this->getModuleName() ) { 525 case 'backlinks': 526 return 'Find all pages that link to the given page.'; 527 case 'embeddedin': 528 return 'Find all pages that embed (transclude) the given title.'; 529 case 'imageusage': 530 return 'Find all pages that use the given image title.'; 531 default: 532 ApiBase::dieDebug( __METHOD__, 'Unknown module name.' ); 533 } 534 } 535 536 public function getExamples() { 537 static $examples = array( 538 'backlinks' => array( 539 'api.php?action=query&list=backlinks&bltitle=Main%20Page', 540 'api.php?action=query&generator=backlinks&gbltitle=Main%20Page&prop=info' 541 ), 542 'embeddedin' => array( 543 'api.php?action=query&list=embeddedin&eititle=Template:Stub', 544 'api.php?action=query&generator=embeddedin&geititle=Template:Stub&prop=info' 545 ), 546 'imageusage' => array( 547 'api.php?action=query&list=imageusage&iutitle=File:Albert%20Einstein%20Head.jpg', 548 'api.php?action=query&generator=imageusage&giutitle=File:Albert%20Einstein%20Head.jpg&prop=info' 549 ) 550 ); 551 552 return $examples[$this->getModuleName()]; 553 } 554 555 public function getHelpUrls() { 556 return $this->helpUrl; 557 } 558 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |