MediaWiki
REL1_22
|
00001 <?php 00035 class ApiQueryBacklinks extends ApiQueryGeneratorBase { 00036 00040 private $rootTitle; 00041 00042 private $params, $contID, $redirID, $redirect; 00043 private $bl_ns, $bl_from, $bl_table, $bl_code, $bl_title, $bl_fields, $hasNS; 00044 00050 private $pageMap = array(); 00051 private $resultArr; 00052 00053 private $redirTitles = array(); 00054 private $continueStr = null; 00055 00056 // output element name, database column field prefix, database table 00057 private $backlinksSettings = array( 00058 'backlinks' => array( 00059 'code' => 'bl', 00060 'prefix' => 'pl', 00061 'linktbl' => 'pagelinks', 00062 'helpurl' => 'https://www.mediawiki.org/wiki/API:Backlinks', 00063 ), 00064 'embeddedin' => array( 00065 'code' => 'ei', 00066 'prefix' => 'tl', 00067 'linktbl' => 'templatelinks', 00068 'helpurl' => 'https://www.mediawiki.org/wiki/API:Embeddedin', 00069 ), 00070 'imageusage' => array( 00071 'code' => 'iu', 00072 'prefix' => 'il', 00073 'linktbl' => 'imagelinks', 00074 'helpurl' => 'https://www.mediawiki.org/wiki/API:Imageusage', 00075 ) 00076 ); 00077 00078 public function __construct( $query, $moduleName ) { 00079 $settings = $this->backlinksSettings[$moduleName]; 00080 $prefix = $settings['prefix']; 00081 $code = $settings['code']; 00082 $this->resultArr = array(); 00083 00084 parent::__construct( $query, $moduleName, $code ); 00085 $this->bl_ns = $prefix . '_namespace'; 00086 $this->bl_from = $prefix . '_from'; 00087 $this->bl_table = $settings['linktbl']; 00088 $this->bl_code = $code; 00089 $this->helpUrl = $settings['helpurl']; 00090 00091 $this->hasNS = $moduleName !== 'imageusage'; 00092 if ( $this->hasNS ) { 00093 $this->bl_title = $prefix . '_title'; 00094 $this->bl_fields = array( 00095 $this->bl_ns, 00096 $this->bl_title 00097 ); 00098 } else { 00099 $this->bl_title = $prefix . '_to'; 00100 $this->bl_fields = array( 00101 $this->bl_title 00102 ); 00103 } 00104 } 00105 00106 public function execute() { 00107 $this->run(); 00108 } 00109 00110 public function getCacheMode( $params ) { 00111 return 'public'; 00112 } 00113 00114 public function executeGenerator( $resultPageSet ) { 00115 $this->run( $resultPageSet ); 00116 } 00117 00122 private function prepareFirstQuery( $resultPageSet = null ) { 00123 /* SELECT page_id, page_title, page_namespace, page_is_redirect 00124 * FROM pagelinks, page WHERE pl_from=page_id 00125 * AND pl_title='Foo' AND pl_namespace=0 00126 * LIMIT 11 ORDER BY pl_from 00127 */ 00128 $this->addTables( array( $this->bl_table, 'page' ) ); 00129 $this->addWhere( "{$this->bl_from}=page_id" ); 00130 if ( is_null( $resultPageSet ) ) { 00131 $this->addFields( array( 'page_id', 'page_title', 'page_namespace' ) ); 00132 } else { 00133 $this->addFields( $resultPageSet->getPageTableFields() ); 00134 } 00135 00136 $this->addFields( 'page_is_redirect' ); 00137 $this->addWhereFld( $this->bl_title, $this->rootTitle->getDBkey() ); 00138 00139 if ( $this->hasNS ) { 00140 $this->addWhereFld( $this->bl_ns, $this->rootTitle->getNamespace() ); 00141 } 00142 $this->addWhereFld( 'page_namespace', $this->params['namespace'] ); 00143 00144 if ( !is_null( $this->contID ) ) { 00145 $op = $this->params['dir'] == 'descending' ? '<' : '>'; 00146 $this->addWhere( "{$this->bl_from}$op={$this->contID}" ); 00147 } 00148 00149 if ( $this->params['filterredir'] == 'redirects' ) { 00150 $this->addWhereFld( 'page_is_redirect', 1 ); 00151 } elseif ( $this->params['filterredir'] == 'nonredirects' && !$this->redirect ) { 00152 // bug 22245 - Check for !redirect, as filtering nonredirects, when getting what links to them is contradictory 00153 $this->addWhereFld( 'page_is_redirect', 0 ); 00154 } 00155 00156 $this->addOption( 'LIMIT', $this->params['limit'] + 1 ); 00157 $sort = ( $this->params['dir'] == 'descending' ? ' DESC' : '' ); 00158 $this->addOption( 'ORDER BY', $this->bl_from . $sort ); 00159 $this->addOption( 'STRAIGHT_JOIN' ); 00160 } 00161 00166 private function prepareSecondQuery( $resultPageSet = null ) { 00167 /* SELECT page_id, page_title, page_namespace, page_is_redirect, pl_title, pl_namespace 00168 FROM pagelinks, page WHERE pl_from=page_id 00169 AND (pl_title='Foo' AND pl_namespace=0) OR (pl_title='Bar' AND pl_namespace=1) 00170 ORDER BY pl_namespace, pl_title, pl_from LIMIT 11 00171 */ 00172 $db = $this->getDB(); 00173 $this->addTables( array( 'page', $this->bl_table ) ); 00174 $this->addWhere( "{$this->bl_from}=page_id" ); 00175 00176 if ( is_null( $resultPageSet ) ) { 00177 $this->addFields( array( 'page_id', 'page_title', 'page_namespace', 'page_is_redirect' ) ); 00178 } else { 00179 $this->addFields( $resultPageSet->getPageTableFields() ); 00180 } 00181 00182 $this->addFields( $this->bl_title ); 00183 if ( $this->hasNS ) { 00184 $this->addFields( $this->bl_ns ); 00185 } 00186 00187 // We can't use LinkBatch here because $this->hasNS may be false 00188 $titleWhere = array(); 00189 $allRedirNs = array(); 00190 $allRedirDBkey = array(); 00192 foreach ( $this->redirTitles as $t ) { 00193 $redirNs = $t->getNamespace(); 00194 $redirDBkey = $t->getDBkey(); 00195 $titleWhere[] = "{$this->bl_title} = " . $db->addQuotes( $redirDBkey ) . 00196 ( $this->hasNS ? " AND {$this->bl_ns} = {$redirNs}" : '' ); 00197 $allRedirNs[] = $redirNs; 00198 $allRedirDBkey[] = $redirDBkey; 00199 } 00200 $this->addWhere( $db->makeList( $titleWhere, LIST_OR ) ); 00201 $this->addWhereFld( 'page_namespace', $this->params['namespace'] ); 00202 00203 if ( !is_null( $this->redirID ) ) { 00204 $op = $this->params['dir'] == 'descending' ? '<' : '>'; 00206 $first = $this->redirTitles[0]; 00207 $title = $db->addQuotes( $first->getDBkey() ); 00208 $ns = $first->getNamespace(); 00209 $from = $this->redirID; 00210 if ( $this->hasNS ) { 00211 $this->addWhere( "{$this->bl_ns} $op $ns OR " . 00212 "({$this->bl_ns} = $ns AND " . 00213 "({$this->bl_title} $op $title OR " . 00214 "({$this->bl_title} = $title AND " . 00215 "{$this->bl_from} $op= $from)))" ); 00216 } else { 00217 $this->addWhere( "{$this->bl_title} $op $title OR " . 00218 "({$this->bl_title} = $title AND " . 00219 "{$this->bl_from} $op= $from)" ); 00220 } 00221 } 00222 if ( $this->params['filterredir'] == 'redirects' ) { 00223 $this->addWhereFld( 'page_is_redirect', 1 ); 00224 } elseif ( $this->params['filterredir'] == 'nonredirects' ) { 00225 $this->addWhereFld( 'page_is_redirect', 0 ); 00226 } 00227 00228 $this->addOption( 'LIMIT', $this->params['limit'] + 1 ); 00229 $orderBy = array(); 00230 $sort = ( $this->params['dir'] == 'descending' ? ' DESC' : '' ); 00231 // Don't order by namespace/title if it's constant in the WHERE clause 00232 if ( $this->hasNS && count( array_unique( $allRedirNs ) ) != 1 ) { 00233 $orderBy[] = $this->bl_ns . $sort; 00234 } 00235 if ( count( array_unique( $allRedirDBkey ) ) != 1 ) { 00236 $orderBy[] = $this->bl_title . $sort; 00237 } 00238 $orderBy[] = $this->bl_from . $sort; 00239 $this->addOption( 'ORDER BY', $orderBy ); 00240 $this->addOption( 'USE INDEX', array( 'page' => 'PRIMARY' ) ); 00241 } 00242 00247 private function run( $resultPageSet = null ) { 00248 $this->params = $this->extractRequestParams( false ); 00249 $this->redirect = isset( $this->params['redirect'] ) && $this->params['redirect']; 00250 $userMax = ( $this->redirect ? ApiBase::LIMIT_BIG1 / 2 : ApiBase::LIMIT_BIG1 ); 00251 $botMax = ( $this->redirect ? ApiBase::LIMIT_BIG2 / 2 : ApiBase::LIMIT_BIG2 ); 00252 00253 $result = $this->getResult(); 00254 00255 if ( $this->params['limit'] == 'max' ) { 00256 $this->params['limit'] = $this->getMain()->canApiHighLimits() ? $botMax : $userMax; 00257 $result->setParsedLimit( $this->getModuleName(), $this->params['limit'] ); 00258 } else { 00259 $this->params['limit'] = intval( $this->params['limit'] ); 00260 $this->validateLimit( 'limit', $this->params['limit'], 1, $userMax, $botMax ); 00261 } 00262 00263 $this->processContinue(); 00264 $this->prepareFirstQuery( $resultPageSet ); 00265 00266 $res = $this->select( __METHOD__ . '::firstQuery' ); 00267 00268 $count = 0; 00269 00270 foreach ( $res as $row ) { 00271 if ( ++ $count > $this->params['limit'] ) { 00272 // We've reached the one extra which shows that there are additional pages to be had. Stop here... 00273 // Continue string preserved in case the redirect query doesn't pass the limit 00274 $this->continueStr = $this->getContinueStr( $row->page_id ); 00275 break; 00276 } 00277 00278 if ( is_null( $resultPageSet ) ) { 00279 $this->extractRowInfo( $row ); 00280 } else { 00281 $this->pageMap[$row->page_namespace][$row->page_title] = $row->page_id; 00282 if ( $row->page_is_redirect ) { 00283 $this->redirTitles[] = Title::makeTitle( $row->page_namespace, $row->page_title ); 00284 } 00285 00286 $resultPageSet->processDbRow( $row ); 00287 } 00288 } 00289 00290 if ( $this->redirect && count( $this->redirTitles ) ) { 00291 $this->resetQueryParams(); 00292 $this->prepareSecondQuery( $resultPageSet ); 00293 $res = $this->select( __METHOD__ . '::secondQuery' ); 00294 $count = 0; 00295 foreach ( $res as $row ) { 00296 if ( ++$count > $this->params['limit'] ) { 00297 // We've reached the one extra which shows that there are additional pages to be had. Stop here... 00298 // We need to keep the parent page of this redir in 00299 if ( $this->hasNS ) { 00300 $parentID = $this->pageMap[$row->{$this->bl_ns}][$row->{$this->bl_title}]; 00301 } else { 00302 $parentID = $this->pageMap[NS_FILE][$row->{$this->bl_title}]; 00303 } 00304 $this->continueStr = $this->getContinueRedirStr( $parentID, $row->page_id ); 00305 break; 00306 } 00307 00308 if ( is_null( $resultPageSet ) ) { 00309 $this->extractRedirRowInfo( $row ); 00310 } else { 00311 $resultPageSet->processDbRow( $row ); 00312 } 00313 } 00314 } 00315 if ( is_null( $resultPageSet ) ) { 00316 // Try to add the result data in one go and pray that it fits 00317 $fit = $result->addValue( 'query', $this->getModuleName(), array_values( $this->resultArr ) ); 00318 if ( !$fit ) { 00319 // It didn't fit. Add elements one by one until the 00320 // result is full. 00321 foreach ( $this->resultArr as $pageID => $arr ) { 00322 // Add the basic entry without redirlinks first 00323 $fit = $result->addValue( 00324 array( 'query', $this->getModuleName() ), 00325 null, array_diff_key( $arr, array( 'redirlinks' => '' ) ) ); 00326 if ( !$fit ) { 00327 $this->continueStr = $this->getContinueStr( $pageID ); 00328 break; 00329 } 00330 00331 $hasRedirs = false; 00332 $redirLinks = isset( $arr['redirlinks'] ) ? $arr['redirlinks'] : array(); 00333 foreach ( (array)$redirLinks as $key => $redir ) { 00334 $fit = $result->addValue( 00335 array( 'query', $this->getModuleName(), $pageID, 'redirlinks' ), 00336 $key, $redir ); 00337 if ( !$fit ) { 00338 $this->continueStr = $this->getContinueRedirStr( $pageID, $redir['pageid'] ); 00339 break; 00340 } 00341 $hasRedirs = true; 00342 } 00343 if ( $hasRedirs ) { 00344 $result->setIndexedTagName_internal( 00345 array( 'query', $this->getModuleName(), $pageID, 'redirlinks' ), 00346 $this->bl_code ); 00347 } 00348 if ( !$fit ) { 00349 break; 00350 } 00351 } 00352 } 00353 00354 $result->setIndexedTagName_internal( 00355 array( 'query', $this->getModuleName() ), 00356 $this->bl_code 00357 ); 00358 } 00359 if ( !is_null( $this->continueStr ) ) { 00360 $this->setContinueEnumParameter( 'continue', $this->continueStr ); 00361 } 00362 } 00363 00364 private function extractRowInfo( $row ) { 00365 $this->pageMap[$row->page_namespace][$row->page_title] = $row->page_id; 00366 $t = Title::makeTitle( $row->page_namespace, $row->page_title ); 00367 $a = array( 'pageid' => intval( $row->page_id ) ); 00368 ApiQueryBase::addTitleInfo( $a, $t ); 00369 if ( $row->page_is_redirect ) { 00370 $a['redirect'] = ''; 00371 $this->redirTitles[] = $t; 00372 } 00373 // Put all the results in an array first 00374 $this->resultArr[$a['pageid']] = $a; 00375 } 00376 00377 private function extractRedirRowInfo( $row ) { 00378 $a['pageid'] = intval( $row->page_id ); 00379 ApiQueryBase::addTitleInfo( $a, Title::makeTitle( $row->page_namespace, $row->page_title ) ); 00380 if ( $row->page_is_redirect ) { 00381 $a['redirect'] = ''; 00382 } 00383 $ns = $this->hasNS ? $row->{$this->bl_ns} : NS_FILE; 00384 $parentID = $this->pageMap[$ns][$row->{$this->bl_title}]; 00385 // Put all the results in an array first 00386 $this->resultArr[$parentID]['redirlinks'][] = $a; 00387 $this->getResult()->setIndexedTagName( $this->resultArr[$parentID]['redirlinks'], $this->bl_code ); 00388 } 00389 00390 protected function processContinue() { 00391 if ( !is_null( $this->params['continue'] ) ) { 00392 $this->parseContinueParam(); 00393 } else { 00394 $this->rootTitle = $this->getTitleOrPageId( $this->params )->getTitle(); 00395 } 00396 00397 // only image titles are allowed for the root in imageinfo mode 00398 if ( !$this->hasNS && $this->rootTitle->getNamespace() !== NS_FILE ) { 00399 $this->dieUsage( "The title for {$this->getModuleName()} query must be an image", 'bad_image_title' ); 00400 } 00401 } 00402 00403 protected function parseContinueParam() { 00404 $continueList = explode( '|', $this->params['continue'] ); 00405 // expected format: 00406 // ns | key | id1 [| id2] 00407 // ns+key: root title 00408 // id1: first-level page ID to continue from 00409 // id2: second-level page ID to continue from 00410 00411 // null stuff out now so we know what's set and what isn't 00412 $this->rootTitle = $this->contID = $this->redirID = null; 00413 $rootNs = intval( $continueList[0] ); 00414 $this->dieContinueUsageIf( $rootNs === 0 && $continueList[0] !== '0' ); 00415 00416 $this->rootTitle = Title::makeTitleSafe( $rootNs, $continueList[1] ); 00417 $this->dieContinueUsageIf( !$this->rootTitle ); 00418 00419 $contID = intval( $continueList[2] ); 00420 $this->dieContinueUsageIf( $contID === 0 && $continueList[2] !== '0' ); 00421 00422 $this->contID = $contID; 00423 $id2 = isset( $continueList[3] ) ? $continueList[3] : null; 00424 $redirID = intval( $id2 ); 00425 00426 if ( $redirID === 0 && $id2 !== '0' ) { 00427 // This one isn't required 00428 return; 00429 } 00430 $this->redirID = $redirID; 00431 00432 } 00433 00434 protected function getContinueStr( $lastPageID ) { 00435 return $this->rootTitle->getNamespace() . 00436 '|' . $this->rootTitle->getDBkey() . 00437 '|' . $lastPageID; 00438 } 00439 00440 protected function getContinueRedirStr( $lastPageID, $lastRedirID ) { 00441 return $this->getContinueStr( $lastPageID ) . '|' . $lastRedirID; 00442 } 00443 00444 public function getAllowedParams() { 00445 $retval = array( 00446 'title' => array( 00447 ApiBase::PARAM_TYPE => 'string', 00448 ), 00449 'pageid' => array( 00450 ApiBase::PARAM_TYPE => 'integer', 00451 ), 00452 'continue' => null, 00453 'namespace' => array( 00454 ApiBase::PARAM_ISMULTI => true, 00455 ApiBase::PARAM_TYPE => 'namespace' 00456 ), 00457 'dir' => array( 00458 ApiBase::PARAM_DFLT => 'ascending', 00459 ApiBase::PARAM_TYPE => array( 00460 'ascending', 00461 'descending' 00462 ) 00463 ), 00464 'filterredir' => array( 00465 ApiBase::PARAM_DFLT => 'all', 00466 ApiBase::PARAM_TYPE => array( 00467 'all', 00468 'redirects', 00469 'nonredirects' 00470 ) 00471 ), 00472 'limit' => array( 00473 ApiBase::PARAM_DFLT => 10, 00474 ApiBase::PARAM_TYPE => 'limit', 00475 ApiBase::PARAM_MIN => 1, 00476 ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1, 00477 ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2 00478 ) 00479 ); 00480 if ( $this->getModuleName() == 'embeddedin' ) { 00481 return $retval; 00482 } 00483 $retval['redirect'] = false; 00484 return $retval; 00485 } 00486 00487 public function getParamDescription() { 00488 $retval = array( 00489 'title' => "Title to search. Cannot be used together with {$this->bl_code}pageid", 00490 'pageid' => "Pageid to search. Cannot be used together with {$this->bl_code}title", 00491 'continue' => 'When more results are available, use this to continue', 00492 'namespace' => 'The namespace to enumerate', 00493 'dir' => 'The direction in which to list', 00494 ); 00495 if ( $this->getModuleName() != 'embeddedin' ) { 00496 return array_merge( $retval, array( 00497 'redirect' => 'If linking page is a redirect, find all pages that link to that redirect as well. Maximum limit is halved.', 00498 'filterredir' => "How to filter for redirects. If set to nonredirects when {$this->bl_code}redirect is enabled, this is only applied to the second level", 00499 'limit' => "How many total pages to return. If {$this->bl_code}redirect is enabled, limit applies to each level separately (which means you may get up to 2 * limit results)." 00500 ) ); 00501 } 00502 return array_merge( $retval, array( 00503 'filterredir' => 'How to filter for redirects', 00504 'limit' => 'How many total pages to return' 00505 ) ); 00506 } 00507 00508 public function getResultProperties() { 00509 return array( 00510 '' => array( 00511 'pageid' => 'integer', 00512 'ns' => 'namespace', 00513 'title' => 'string', 00514 'redirect' => 'boolean' 00515 ) 00516 ); 00517 } 00518 00519 public function getDescription() { 00520 switch ( $this->getModuleName() ) { 00521 case 'backlinks': 00522 return 'Find all pages that link to the given page'; 00523 case 'embeddedin': 00524 return 'Find all pages that embed (transclude) the given title'; 00525 case 'imageusage': 00526 return 'Find all pages that use the given image title.'; 00527 default: 00528 ApiBase::dieDebug( __METHOD__, 'Unknown module name' ); 00529 } 00530 } 00531 00532 public function getPossibleErrors() { 00533 return array_merge( parent::getPossibleErrors(), 00534 $this->getTitleOrPageIdErrorMessage(), 00535 array( 00536 array( 'code' => 'bad_image_title', 'info' => "The title for {$this->getModuleName()} query must be an image" ), 00537 ) 00538 ); 00539 } 00540 00541 public function getExamples() { 00542 static $examples = array( 00543 'backlinks' => array( 00544 'api.php?action=query&list=backlinks&bltitle=Main%20Page', 00545 'api.php?action=query&generator=backlinks&gbltitle=Main%20Page&prop=info' 00546 ), 00547 'embeddedin' => array( 00548 'api.php?action=query&list=embeddedin&eititle=Template:Stub', 00549 'api.php?action=query&generator=embeddedin&geititle=Template:Stub&prop=info' 00550 ), 00551 'imageusage' => array( 00552 'api.php?action=query&list=imageusage&iutitle=File:Albert%20Einstein%20Head.jpg', 00553 'api.php?action=query&generator=imageusage&giutitle=File:Albert%20Einstein%20Head.jpg&prop=info' 00554 ) 00555 ); 00556 00557 return $examples[$this->getModuleName()]; 00558 } 00559 00560 public function getHelpUrls() { 00561 return $this->helpUrl; 00562 } 00563 }