MediaWiki
REL1_19
|
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_sort, $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_sort = "{$this->bl_ns}, {$this->bl_title}, {$this->bl_from}"; 00095 $this->bl_fields = array( 00096 $this->bl_ns, 00097 $this->bl_title 00098 ); 00099 } else { 00100 $this->bl_title = $prefix . '_to'; 00101 $this->bl_sort = "{$this->bl_title}, {$this->bl_from}"; 00102 $this->bl_fields = array( 00103 $this->bl_title 00104 ); 00105 } 00106 } 00107 00108 public function execute() { 00109 $this->run(); 00110 } 00111 00112 public function getCacheMode( $params ) { 00113 return 'public'; 00114 } 00115 00116 public function executeGenerator( $resultPageSet ) { 00117 $this->run( $resultPageSet ); 00118 } 00119 00124 private function prepareFirstQuery( $resultPageSet = null ) { 00125 /* SELECT page_id, page_title, page_namespace, page_is_redirect 00126 * FROM pagelinks, page WHERE pl_from=page_id 00127 * AND pl_title='Foo' AND pl_namespace=0 00128 * LIMIT 11 ORDER BY pl_from 00129 */ 00130 $this->addTables( array( $this->bl_table, 'page' ) ); 00131 $this->addWhere( "{$this->bl_from}=page_id" ); 00132 if ( is_null( $resultPageSet ) ) { 00133 $this->addFields( array( 'page_id', 'page_title', 'page_namespace' ) ); 00134 } else { 00135 $this->addFields( $resultPageSet->getPageTableFields() ); 00136 } 00137 00138 $this->addFields( 'page_is_redirect' ); 00139 $this->addWhereFld( $this->bl_title, $this->rootTitle->getDBkey() ); 00140 00141 if ( $this->hasNS ) { 00142 $this->addWhereFld( $this->bl_ns, $this->rootTitle->getNamespace() ); 00143 } 00144 $this->addWhereFld( 'page_namespace', $this->params['namespace'] ); 00145 00146 if ( !is_null( $this->contID ) ) { 00147 $this->addWhere( "{$this->bl_from}>={$this->contID}" ); 00148 } 00149 00150 if ( $this->params['filterredir'] == 'redirects' ) { 00151 $this->addWhereFld( 'page_is_redirect', 1 ); 00152 } elseif ( $this->params['filterredir'] == 'nonredirects' && !$this->redirect ) { 00153 // bug 22245 - Check for !redirect, as filtering nonredirects, when getting what links to them is contradictory 00154 $this->addWhereFld( 'page_is_redirect', 0 ); 00155 } 00156 00157 $this->addOption( 'LIMIT', $this->params['limit'] + 1 ); 00158 $this->addOption( 'ORDER BY', $this->bl_from ); 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 foreach ( $this->redirTitles as $t ) { 00190 $titleWhere[] = "{$this->bl_title} = " . $db->addQuotes( $t->getDBkey() ) . 00191 ( $this->hasNS ? " AND {$this->bl_ns} = '{$t->getNamespace()}'" : '' ); 00192 } 00193 $this->addWhere( $db->makeList( $titleWhere, LIST_OR ) ); 00194 $this->addWhereFld( 'page_namespace', $this->params['namespace'] ); 00195 00196 if ( !is_null( $this->redirID ) ) { 00197 $first = $this->redirTitles[0]; 00198 $title = $db->strencode( $first->getDBkey() ); 00199 $ns = $first->getNamespace(); 00200 $from = $this->redirID; 00201 if ( $this->hasNS ) { 00202 $this->addWhere( "{$this->bl_ns} > $ns OR " . 00203 "({$this->bl_ns} = $ns AND " . 00204 "({$this->bl_title} > '$title' OR " . 00205 "({$this->bl_title} = '$title' AND " . 00206 "{$this->bl_from} >= $from)))" ); 00207 } else { 00208 $this->addWhere( "{$this->bl_title} > '$title' OR " . 00209 "({$this->bl_title} = '$title' AND " . 00210 "{$this->bl_from} >= $from)" ); 00211 } 00212 } 00213 if ( $this->params['filterredir'] == 'redirects' ) { 00214 $this->addWhereFld( 'page_is_redirect', 1 ); 00215 } elseif ( $this->params['filterredir'] == 'nonredirects' ) { 00216 $this->addWhereFld( 'page_is_redirect', 0 ); 00217 } 00218 00219 $this->addOption( 'LIMIT', $this->params['limit'] + 1 ); 00220 $this->addOption( 'ORDER BY', $this->bl_sort ); 00221 $this->addOption( 'USE INDEX', array( 'page' => 'PRIMARY' ) ); 00222 } 00223 00228 private function run( $resultPageSet = null ) { 00229 $this->params = $this->extractRequestParams( false ); 00230 $this->redirect = isset( $this->params['redirect'] ) && $this->params['redirect']; 00231 $userMax = ( $this->redirect ? ApiBase::LIMIT_BIG1 / 2 : ApiBase::LIMIT_BIG1 ); 00232 $botMax = ( $this->redirect ? ApiBase::LIMIT_BIG2 / 2 : ApiBase::LIMIT_BIG2 ); 00233 00234 $result = $this->getResult(); 00235 00236 if ( $this->params['limit'] == 'max' ) { 00237 $this->params['limit'] = $this->getMain()->canApiHighLimits() ? $botMax : $userMax; 00238 $result->setParsedLimit( $this->getModuleName(), $this->params['limit'] ); 00239 } 00240 00241 $this->processContinue(); 00242 $this->prepareFirstQuery( $resultPageSet ); 00243 00244 $res = $this->select( __METHOD__ . '::firstQuery' ); 00245 00246 $count = 0; 00247 00248 foreach ( $res as $row ) { 00249 if ( ++ $count > $this->params['limit'] ) { 00250 // We've reached the one extra which shows that there are additional pages to be had. Stop here... 00251 // Continue string preserved in case the redirect query doesn't pass the limit 00252 $this->continueStr = $this->getContinueStr( $row->page_id ); 00253 break; 00254 } 00255 00256 if ( is_null( $resultPageSet ) ) { 00257 $this->extractRowInfo( $row ); 00258 } else { 00259 $this->pageMap[$row->page_namespace][$row->page_title] = $row->page_id; 00260 if ( $row->page_is_redirect ) { 00261 $this->redirTitles[] = Title::makeTitle( $row->page_namespace, $row->page_title ); 00262 } 00263 00264 $resultPageSet->processDbRow( $row ); 00265 } 00266 } 00267 00268 if ( $this->redirect && count( $this->redirTitles ) ) { 00269 $this->resetQueryParams(); 00270 $this->prepareSecondQuery( $resultPageSet ); 00271 $res = $this->select( __METHOD__ . '::secondQuery' ); 00272 $count = 0; 00273 foreach ( $res as $row ) { 00274 if ( ++$count > $this->params['limit'] ) { 00275 // We've reached the one extra which shows that there are additional pages to be had. Stop here... 00276 // We need to keep the parent page of this redir in 00277 if ( $this->hasNS ) { 00278 $parentID = $this->pageMap[$row-> { $this->bl_ns } ][$row-> { $this->bl_title } ]; 00279 } else { 00280 $parentID = $this->pageMap[NS_IMAGE][$row-> { $this->bl_title } ]; 00281 } 00282 $this->continueStr = $this->getContinueRedirStr( $parentID, $row->page_id ); 00283 break; 00284 } 00285 00286 if ( is_null( $resultPageSet ) ) { 00287 $this->extractRedirRowInfo( $row ); 00288 } else { 00289 $resultPageSet->processDbRow( $row ); 00290 } 00291 } 00292 } 00293 if ( is_null( $resultPageSet ) ) { 00294 // Try to add the result data in one go and pray that it fits 00295 $fit = $result->addValue( 'query', $this->getModuleName(), array_values( $this->resultArr ) ); 00296 if ( !$fit ) { 00297 // It didn't fit. Add elements one by one until the 00298 // result is full. 00299 foreach ( $this->resultArr as $pageID => $arr ) { 00300 // Add the basic entry without redirlinks first 00301 $fit = $result->addValue( 00302 array( 'query', $this->getModuleName() ), 00303 null, array_diff_key( $arr, array( 'redirlinks' => '' ) ) ); 00304 if ( !$fit ) { 00305 $this->continueStr = $this->getContinueStr( $pageID ); 00306 break; 00307 } 00308 00309 $hasRedirs = false; 00310 $redirLinks = isset( $arr['redirlinks'] ) ? $arr['redirlinks'] : array(); 00311 foreach ( (array)$redirLinks as $key => $redir ) { 00312 $fit = $result->addValue( 00313 array( 'query', $this->getModuleName(), $pageID, 'redirlinks' ), 00314 $key, $redir ); 00315 if ( !$fit ) { 00316 $this->continueStr = $this->getContinueRedirStr( $pageID, $redir['pageid'] ); 00317 break; 00318 } 00319 $hasRedirs = true; 00320 } 00321 if ( $hasRedirs ) { 00322 $result->setIndexedTagName_internal( 00323 array( 'query', $this->getModuleName(), $pageID, 'redirlinks' ), 00324 $this->bl_code ); 00325 } 00326 if ( !$fit ) { 00327 break; 00328 } 00329 } 00330 } 00331 00332 $result->setIndexedTagName_internal( 00333 array( 'query', $this->getModuleName() ), 00334 $this->bl_code 00335 ); 00336 } 00337 if ( !is_null( $this->continueStr ) ) { 00338 $this->setContinueEnumParameter( 'continue', $this->continueStr ); 00339 } 00340 } 00341 00342 private function extractRowInfo( $row ) { 00343 $this->pageMap[$row->page_namespace][$row->page_title] = $row->page_id; 00344 $t = Title::makeTitle( $row->page_namespace, $row->page_title ); 00345 $a = array( 'pageid' => intval( $row->page_id ) ); 00346 ApiQueryBase::addTitleInfo( $a, $t ); 00347 if ( $row->page_is_redirect ) { 00348 $a['redirect'] = ''; 00349 $this->redirTitles[] = $t; 00350 } 00351 // Put all the results in an array first 00352 $this->resultArr[$a['pageid']] = $a; 00353 } 00354 00355 private function extractRedirRowInfo( $row ) { 00356 $a['pageid'] = intval( $row->page_id ); 00357 ApiQueryBase::addTitleInfo( $a, Title::makeTitle( $row->page_namespace, $row->page_title ) ); 00358 if ( $row->page_is_redirect ) { 00359 $a['redirect'] = ''; 00360 } 00361 $ns = $this->hasNS ? $row-> { $this->bl_ns } : NS_FILE; 00362 $parentID = $this->pageMap[$ns][$row-> { $this->bl_title } ]; 00363 // Put all the results in an array first 00364 $this->resultArr[$parentID]['redirlinks'][] = $a; 00365 $this->getResult()->setIndexedTagName( $this->resultArr[$parentID]['redirlinks'], $this->bl_code ); 00366 } 00367 00368 protected function processContinue() { 00369 if ( !is_null( $this->params['continue'] ) ) { 00370 $this->parseContinueParam(); 00371 } else { 00372 if ( $this->params['title'] !== '' ) { 00373 $title = Title::newFromText( $this->params['title'] ); 00374 if ( !$title ) { 00375 $this->dieUsageMsg( array( 'invalidtitle', $this->params['title'] ) ); 00376 } else { 00377 $this->rootTitle = $title; 00378 } 00379 } 00380 } 00381 00382 // only image titles are allowed for the root in imageinfo mode 00383 if ( !$this->hasNS && $this->rootTitle->getNamespace() !== NS_FILE ) { 00384 $this->dieUsage( "The title for {$this->getModuleName()} query must be an image", 'bad_image_title' ); 00385 } 00386 } 00387 00388 protected function parseContinueParam() { 00389 $continueList = explode( '|', $this->params['continue'] ); 00390 // expected format: 00391 // ns | key | id1 [| id2] 00392 // ns+key: root title 00393 // id1: first-level page ID to continue from 00394 // id2: second-level page ID to continue from 00395 00396 // null stuff out now so we know what's set and what isn't 00397 $this->rootTitle = $this->contID = $this->redirID = null; 00398 $rootNs = intval( $continueList[0] ); 00399 if ( $rootNs === 0 && $continueList[0] !== '0' ) { 00400 // Illegal continue parameter 00401 $this->dieUsage( 'Invalid continue param. You should pass the original value returned by the previous query', '_badcontinue' ); 00402 } 00403 $this->rootTitle = Title::makeTitleSafe( $rootNs, $continueList[1] ); 00404 00405 if ( !$this->rootTitle ) { 00406 $this->dieUsage( 'Invalid continue param. You should pass the original value returned by the previous query', '_badcontinue' ); 00407 } 00408 $contID = intval( $continueList[2] ); 00409 00410 if ( $contID === 0 && $continueList[2] !== '0' ) { 00411 $this->dieUsage( 'Invalid continue param. You should pass the original value returned by the previous query', '_badcontinue' ); 00412 } 00413 $this->contID = $contID; 00414 $id2 = isset( $continueList[3] ) ? $continueList[3] : null; 00415 $redirID = intval( $id2 ); 00416 00417 if ( $redirID === 0 && $id2 !== '0' ) { 00418 // This one isn't required 00419 return; 00420 } 00421 $this->redirID = $redirID; 00422 00423 } 00424 00425 protected function getContinueStr( $lastPageID ) { 00426 return $this->rootTitle->getNamespace() . 00427 '|' . $this->rootTitle->getDBkey() . 00428 '|' . $lastPageID; 00429 } 00430 00431 protected function getContinueRedirStr( $lastPageID, $lastRedirID ) { 00432 return $this->getContinueStr( $lastPageID ) . '|' . $lastRedirID; 00433 } 00434 00435 public function getAllowedParams() { 00436 $retval = array( 00437 'title' => array( 00438 ApiBase::PARAM_TYPE => 'string', 00439 ApiBase::PARAM_REQUIRED => true 00440 ), 00441 'continue' => null, 00442 'namespace' => array( 00443 ApiBase::PARAM_ISMULTI => true, 00444 ApiBase::PARAM_TYPE => 'namespace' 00445 ), 00446 'filterredir' => array( 00447 ApiBase::PARAM_DFLT => 'all', 00448 ApiBase::PARAM_TYPE => array( 00449 'all', 00450 'redirects', 00451 'nonredirects' 00452 ) 00453 ), 00454 'limit' => array( 00455 ApiBase::PARAM_DFLT => 10, 00456 ApiBase::PARAM_TYPE => 'limit', 00457 ApiBase::PARAM_MIN => 1, 00458 ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1, 00459 ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2 00460 ) 00461 ); 00462 if ( $this->getModuleName() == 'embeddedin' ) { 00463 return $retval; 00464 } 00465 $retval['redirect'] = false; 00466 return $retval; 00467 } 00468 00469 public function getParamDescription() { 00470 $retval = array( 00471 'title' => 'Title to search', 00472 'continue' => 'When more results are available, use this to continue', 00473 'namespace' => 'The namespace to enumerate', 00474 ); 00475 if ( $this->getModuleName() != 'embeddedin' ) { 00476 return array_merge( $retval, array( 00477 'redirect' => 'If linking page is a redirect, find all pages that link to that redirect as well. Maximum limit is halved.', 00478 '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", 00479 '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)." 00480 ) ); 00481 } 00482 return array_merge( $retval, array( 00483 'filterredir' => 'How to filter for redirects', 00484 'limit' => 'How many total pages to return' 00485 ) ); 00486 } 00487 00488 public function getDescription() { 00489 switch ( $this->getModuleName() ) { 00490 case 'backlinks': 00491 return 'Find all pages that link to the given page'; 00492 case 'embeddedin': 00493 return 'Find all pages that embed (transclude) the given title'; 00494 case 'imageusage': 00495 return 'Find all pages that use the given image title.'; 00496 default: 00497 ApiBase::dieDebug( __METHOD__, 'Unknown module name' ); 00498 } 00499 } 00500 00501 public function getPossibleErrors() { 00502 return array_merge( parent::getPossibleErrors(), array( 00503 array( 'invalidtitle', 'title' ), 00504 array( 'code' => 'bad_image_title', 'info' => "The title for {$this->getModuleName()} query must be an image" ), 00505 array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ), 00506 ) ); 00507 } 00508 00509 public function getExamples() { 00510 static $examples = array( 00511 'backlinks' => array( 00512 'api.php?action=query&list=backlinks&bltitle=Main%20Page', 00513 'api.php?action=query&generator=backlinks&gbltitle=Main%20Page&prop=info' 00514 ), 00515 'embeddedin' => array( 00516 'api.php?action=query&list=embeddedin&eititle=Template:Stub', 00517 'api.php?action=query&generator=embeddedin&geititle=Template:Stub&prop=info' 00518 ), 00519 'imageusage' => array( 00520 'api.php?action=query&list=imageusage&iutitle=File:Albert%20Einstein%20Head.jpg', 00521 'api.php?action=query&generator=imageusage&giutitle=File:Albert%20Einstein%20Head.jpg&prop=info' 00522 ) 00523 ); 00524 00525 return $examples[$this->getModuleName()]; 00526 } 00527 00528 public function getHelpUrls() { 00529 return $this->helpUrl; 00530 } 00531 00532 public function getVersion() { 00533 return __CLASS__ . ': $Id$'; 00534 } 00535 }