46 private $linksMigration;
52 private $bl_ns, $bl_from, $bl_from_ns, $bl_table, $bl_code, $bl_title, $hasNS;
62 private $pageMap = [];
65 private $redirTitles = [];
66 private $continueStr =
null;
69 private $backlinksSettings = [
73 'linktbl' =>
'pagelinks',
74 'helpurl' =>
'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Backlinks',
79 'linktbl' =>
'templatelinks',
80 'helpurl' =>
'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Embeddedin',
85 'linktbl' =>
'imagelinks',
86 'helpurl' =>
'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Imageusage',
96 $settings = $this->backlinksSettings[$moduleName];
97 $prefix = $settings[
'prefix'];
98 $code = $settings[
'code'];
99 $this->resultArr = [];
101 parent::__construct( $query, $moduleName, $code );
102 $this->bl_table = $settings[
'linktbl'];
103 $this->hasNS = $moduleName !==
'imageusage';
104 $this->linksMigration = $linksMigration;
105 if ( isset( $this->linksMigration::$mapping[$this->bl_table] ) ) {
106 [ $this->bl_ns, $this->bl_title ] = $this->linksMigration->
getTitleFields( $this->bl_table );
108 $this->bl_ns = $prefix .
'_namespace';
109 if ( $this->hasNS ) {
110 $this->bl_title = $prefix .
'_title';
112 $this->bl_title = $prefix .
'_to';
115 $this->bl_from = $prefix .
'_from';
116 $this->bl_from_ns = $prefix .
'_from_namespace';
117 $this->bl_code = $code;
118 $this->helpUrl = $settings[
'helpurl'];
130 $this->run( $resultPageSet );
137 private function runFirstQuery( $resultPageSet =
null ) {
138 $this->
addTables( [ $this->bl_table,
'page' ] );
139 $this->
addWhere(
"{$this->bl_from}=page_id" );
140 if ( $resultPageSet ===
null ) {
141 $this->
addFields( [
'page_id',
'page_title',
'page_namespace' ] );
143 $this->
addFields( $resultPageSet->getPageTableFields() );
145 $this->
addFields( [
'page_is_redirect',
'from_ns' =>
'page_namespace' ] );
147 if ( isset( $this->linksMigration::$mapping[$this->bl_table] ) ) {
148 $conds = $this->linksMigration->getLinksConditions( $this->bl_table, $this->rootTitle );
151 $this->
addWhereFld( $this->bl_title, $this->rootTitle->getDBkey() );
152 if ( $this->hasNS ) {
153 $this->
addWhereFld( $this->bl_ns, $this->rootTitle->getNamespace() );
157 $this->
addWhereFld( $this->bl_from_ns, $this->params[
'namespace'] );
159 if ( count( $this->cont ) >= 2 ) {
160 $db = $this->
getDB();
161 $op = $this->params[
'dir'] ==
'descending' ?
'<=' :
'>=';
162 if ( $this->params[
'namespace'] !==
null && count( $this->params[
'namespace'] ) > 1 ) {
163 $this->
addWhere( $db->buildComparison( $op, [
164 $this->bl_from_ns => $this->cont[0],
165 $this->bl_from => $this->cont[1],
168 $this->
addWhere( $db->buildComparison( $op, [ $this->bl_from => $this->cont[1] ] ) );
172 if ( $this->params[
'filterredir'] ==
'redirects' ) {
174 } elseif ( $this->params[
'filterredir'] ==
'nonredirects' && !$this->redirect ) {
180 $this->
addOption(
'LIMIT', $this->params[
'limit'] + 1 );
181 $sort = ( $this->params[
'dir'] ==
'descending' ?
' DESC' :
'' );
183 if ( $this->params[
'namespace'] !==
null && count( $this->params[
'namespace'] ) > 1 ) {
184 $orderBy[] = $this->bl_from_ns . $sort;
186 $orderBy[] = $this->bl_from . $sort;
187 $this->
addOption(
'ORDER BY', $orderBy );
192 if ( $resultPageSet ===
null ) {
197 foreach (
$res as $row ) {
198 if ( ++$count > $this->params[
'limit'] ) {
202 $this->continueStr =
"{$row->from_ns}|{$row->page_id}";
207 if ( count( $this->cont ) < 2 ) {
208 $this->cont[] = $row->from_ns;
209 $this->cont[] = $row->page_id;
212 $this->pageMap[$row->page_namespace][$row->page_title] = $row->page_id;
213 $t = Title::makeTitle( $row->page_namespace, $row->page_title );
214 if ( $row->page_is_redirect ) {
215 $this->redirTitles[] =
$t;
218 if ( $resultPageSet ===
null ) {
219 $a = [
'pageid' => (int)$row->page_id ];
221 if ( $row->page_is_redirect ) {
222 $a[
'redirect'] =
true;
225 $this->resultArr[$a[
'pageid']] = $a;
227 $resultPageSet->processDbRow( $row );
238 private function runSecondQuery( $resultPageSet =
null ) {
239 $db = $this->
getDB();
240 $this->
addTables( [ $this->bl_table,
'page' ] );
241 $this->
addWhere(
"{$this->bl_from}=page_id" );
243 if ( $resultPageSet ===
null ) {
244 $this->
addFields( [
'page_id',
'page_title',
'page_namespace',
'page_is_redirect' ] );
246 $this->
addFields( $resultPageSet->getPageTableFields() );
249 $this->
addFields( [ $this->bl_title,
'from_ns' =>
'page_namespace' ] );
250 if ( $this->hasNS ) {
259 foreach ( $this->redirTitles as
$t ) {
260 $redirNs =
$t->getNamespace();
261 $redirDBkey =
$t->getDBkey();
262 $titleWhere[] =
"{$this->bl_title} = " . $db->addQuotes( $redirDBkey ) .
263 ( $this->hasNS ?
" AND {$this->bl_ns} = {$redirNs}" :
'' );
264 $allRedirNs[$redirNs] =
true;
265 $allRedirDBkey[$redirDBkey] =
true;
268 $this->
addWhereFld(
'page_namespace', $this->params[
'namespace'] );
270 if ( count( $this->cont ) >= 6 ) {
271 $op = $this->params[
'dir'] ==
'descending' ?
'<=' :
'>=';
274 if ( $this->hasNS && count( $allRedirNs ) > 1 ) {
275 $conds[ $this->bl_ns ] = $this->cont[2];
277 if ( count( $allRedirDBkey ) > 1 ) {
278 $conds[ $this->bl_title ] = $this->cont[3];
282 if ( $this->params[
'namespace'] !==
null && count( $this->params[
'namespace'] ) > 1 ) {
283 $conds[ $this->bl_from_ns ] = $this->cont[4];
285 $conds[ $this->bl_from ] = $this->cont[5];
287 $this->
addWhere( $db->buildComparison( $op, $conds ) );
289 if ( $this->params[
'filterredir'] ==
'redirects' ) {
291 } elseif ( $this->params[
'filterredir'] ==
'nonredirects' ) {
295 $this->
addOption(
'LIMIT', $this->params[
'limit'] + 1 );
297 $sort = ( $this->params[
'dir'] ==
'descending' ?
' DESC' :
'' );
299 if ( $this->hasNS && count( $allRedirNs ) > 1 ) {
300 $orderBy[] = $this->bl_ns . $sort;
302 if ( count( $allRedirDBkey ) > 1 ) {
303 $orderBy[] = $this->bl_title . $sort;
305 if ( $this->params[
'namespace'] !==
null && count( $this->params[
'namespace'] ) > 1 ) {
306 $orderBy[] = $this->bl_from_ns . $sort;
308 $orderBy[] = $this->bl_from . $sort;
309 $this->
addOption(
'ORDER BY', $orderBy );
310 $this->
addOption(
'USE INDEX', [
'page' =>
'PRIMARY' ] );
316 if ( $resultPageSet ===
null ) {
321 foreach (
$res as $row ) {
322 $ns = $this->hasNS ? $row->{$this->bl_ns} :
NS_FILE;
324 if ( ++$count > $this->params[
'limit'] ) {
329 $title = $row->{$this->bl_title};
330 $this->continueStr = implode(
'|', array_slice( $this->cont, 0, 2 ) ) .
331 "|$ns|$title|{$row->from_ns}|{$row->page_id}";
336 if ( count( $this->cont ) < 6 ) {
338 $this->cont[] = $row->{$this->bl_title};
339 $this->cont[] = $row->from_ns;
340 $this->cont[] = $row->page_id;
343 if ( $resultPageSet ===
null ) {
344 $a = [
'pageid' => (int)$row->page_id ];
346 if ( $row->page_is_redirect ) {
347 $a[
'redirect'] =
true;
349 $parentID = $this->pageMap[$ns][$row->{$this->bl_title}];
351 $this->resultArr[$parentID][
'redirlinks'][$row->page_id] = $a;
353 $resultPageSet->processDbRow( $row );
362 private function run( $resultPageSet =
null ) {
363 $this->params = $this->extractRequestParams(
false );
364 $this->redirect = isset( $this->params[
'redirect'] ) && $this->params[
'redirect'];
368 $result = $this->getResult();
370 if ( $this->params[
'limit'] ==
'max' ) {
371 $this->params[
'limit'] = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
372 $result->addParsedLimit( $this->getModuleName(), $this->params[
'limit'] );
374 $this->params[
'limit'] = $this->getMain()->getParamValidator()->validateValue(
375 $this,
'limit', (
int)$this->params[
'limit'], [
376 ParamValidator::PARAM_TYPE =>
'limit',
377 IntegerDef::PARAM_MIN => 1,
378 IntegerDef::PARAM_MAX => $userMax,
379 IntegerDef::PARAM_MAX2 => $botMax,
380 IntegerDef::PARAM_IGNORE_RANGE =>
true,
385 $this->rootTitle = $this->getTitleFromTitleOrPageId( $this->params );
388 if ( !$this->hasNS && $this->rootTitle->getNamespace() !==
NS_FILE ) {
390 [
'apierror-imageusage-badtitle', $this->getModuleName() ],
398 if ( $this->params[
'continue'] !==
null ) {
399 $cont = explode(
'|', $this->params[
'continue'] );
401 switch ( count( $cont ) ) {
404 $this->cont[7] = (int)$cont[7];
405 $this->dieContinueUsageIf( $cont[7] !== (
string)$this->cont[7] );
411 $this->cont[6] = (int)$cont[6];
412 $this->dieContinueUsageIf( $cont[6] !== (
string)$this->cont[6] );
418 $this->cont[2] = (int)$cont[2];
419 $this->dieContinueUsageIf( $cont[2] !== (
string)$this->cont[2] );
422 $this->cont[3] = $cont[3];
425 $this->cont[4] = (int)$cont[4];
426 $this->dieContinueUsageIf( $cont[4] !== (
string)$this->cont[4] );
429 $this->cont[5] = (int)$cont[5];
430 $this->dieContinueUsageIf( $cont[5] !== (
string)$this->cont[5] );
436 $this->cont[0] = (int)$cont[0];
437 $this->dieContinueUsageIf( $cont[0] !== (
string)$this->cont[0] );
440 $this->cont[1] = (int)$cont[1];
441 $this->dieContinueUsageIf( $cont[1] !== (
string)$this->cont[1] );
447 $this->dieContinueUsageIf(
true );
450 ksort( $this->cont );
453 $this->runFirstQuery( $resultPageSet );
454 if ( $this->redirect && count( $this->redirTitles ) ) {
455 $this->resetQueryParams();
456 $this->runSecondQuery( $resultPageSet );
460 $this->cont += [ 0, 0, 0,
'', 0, 0, 0 ];
462 if ( $resultPageSet ===
null ) {
464 $code = $this->bl_code;
465 $data = array_map(
static function ( $arr ) use ( $code ) {
466 if ( isset( $arr[
'redirlinks'] ) ) {
467 $arr[
'redirlinks'] = array_values( $arr[
'redirlinks'] );
471 }, array_values( $this->resultArr ) );
472 $fit = $result->addValue(
'query', $this->getModuleName(), $data );
476 ksort( $this->resultArr );
478 if ( count( $this->cont ) >= 7 ) {
479 $startAt = $this->cont[6];
481 $startAt = array_key_first( $this->resultArr );
484 foreach ( $this->resultArr as $pageID => $arr ) {
485 if ( $pageID < $startAt ) {
490 $fit = $result->addValue(
491 [
'query', $this->getModuleName() ],
492 $idx, array_diff_key( $arr, [
'redirlinks' =>
'' ] ) );
494 $this->continueStr = implode(
'|', array_slice( $this->cont, 0, 6 ) ) .
500 $redirLinks = isset( $arr[
'redirlinks'] ) ? (array)$arr[
'redirlinks'] : [];
501 ksort( $redirLinks );
503 if ( count( $this->cont ) >= 8 && $pageID == $startAt ) {
504 $redirStartAt = $this->cont[7];
506 $redirStartAt = array_key_first( $redirLinks );
508 foreach ( $redirLinks as $key => $redir ) {
509 if ( $key < $redirStartAt ) {
513 $fit = $result->addValue(
514 [
'query', $this->getModuleName(), $idx,
'redirlinks' ],
517 $this->continueStr = implode(
'|', array_slice( $this->cont, 0, 6 ) ) .
524 $result->addIndexedTagName(
525 [
'query', $this->getModuleName(), $idx,
'redirlinks' ],
536 $result->addIndexedTagName(
537 [
'query', $this->getModuleName() ],
541 if ( $this->continueStr !==
null ) {
542 $this->setContinueEnumParameter(
'continue', $this->continueStr );
549 ParamValidator::PARAM_TYPE =>
'string',
552 ParamValidator::PARAM_TYPE =>
'integer',
558 ParamValidator::PARAM_ISMULTI =>
true,
559 ParamValidator::PARAM_TYPE =>
'namespace'
562 ParamValidator::PARAM_DEFAULT =>
'ascending',
563 ParamValidator::PARAM_TYPE => [
569 ParamValidator::PARAM_DEFAULT =>
'all',
570 ParamValidator::PARAM_TYPE => [
577 ParamValidator::PARAM_DEFAULT => 10,
578 ParamValidator::PARAM_TYPE =>
'limit',
579 IntegerDef::PARAM_MIN => 1,
584 if ( $this->getModuleName() !==
'embeddedin' ) {
585 $retval[
'redirect'] =
false;
592 $title = Title::newMainPage()->getPrefixedText();
593 $mp = rawurlencode(
$title );
596 "action=query&list=backlinks&bltitle={$mp}"
597 =>
'apihelp-query+backlinks-example-simple',
598 "action=query&generator=backlinks&gbltitle={$mp}&prop=info"
599 =>
'apihelp-query+backlinks-example-generator',
602 'action=query&list=embeddedin&eititle=Template:Stub'
603 =>
'apihelp-query+embeddedin-example-simple',
604 'action=query&generator=embeddedin&geititle=Template:Stub&prop=info'
605 =>
'apihelp-query+embeddedin-example-generator',
608 'action=query&list=imageusage&iutitle=File:Albert%20Einstein%20Head.jpg'
609 =>
'apihelp-query+imageusage-example-simple',
610 'action=query&generator=imageusage&giutitle=File:Albert%20Einstein%20Head.jpg&prop=info'
611 =>
'apihelp-query+imageusage-example-generator',
615 return $examples[$this->getModuleName()];
619 return $this->helpUrl;