private $mInterwikiTitles = [];
/** @var Title[] */
private $mPendingRedirectIDs = [];
+ private $mPendingRedirectSpecialPages = []; // [dbkey] => [ Title $from, Title $to ]
private $mResolvedRedirectTitles = [];
private $mConvertedTitles = [];
private $mGoodRevIDs = [];
// Get validated and normalized title objects
$linkBatch = $this->processTitlesArray( $titles );
if ( $linkBatch->isEmpty() ) {
+ // There might be special-page redirects
+ $this->resolvePendingRedirects();
return;
}
// Repeat until all redirects have been resolved
// The infinite loop is prevented by keeping all known pages in $this->mAllPages
- while ( $this->mPendingRedirectIDs ) {
+ while ( $this->mPendingRedirectIDs || $this->mPendingRedirectSpecialPages ) {
// Resolve redirects by querying the pagelinks table, and repeat the process
// Create a new linkBatch object for the next pass
$linkBatch = $this->getRedirectTargets();
$titlesToResolve = [];
$db = $this->getDB();
- $res = $db->select(
- 'redirect',
- [
- 'rd_from',
- 'rd_namespace',
- 'rd_fragment',
- 'rd_interwiki',
- 'rd_title'
- ], [ 'rd_from' => array_keys( $this->mPendingRedirectIDs ) ],
- __METHOD__
- );
- foreach ( $res as $row ) {
- $rdfrom = intval( $row->rd_from );
- $from = $this->mPendingRedirectIDs[$rdfrom]->getPrefixedText();
- $to = Title::makeTitle(
- $row->rd_namespace,
- $row->rd_title,
- $row->rd_fragment,
- $row->rd_interwiki
- );
- $this->mResolvedRedirectTitles[$from] = $this->mPendingRedirectIDs[$rdfrom];
- unset( $this->mPendingRedirectIDs[$rdfrom] );
- if ( $to->isExternal() ) {
- $this->mInterwikiTitles[$to->getPrefixedText()] = $to->getInterwiki();
- } elseif ( !isset( $this->mAllPages[$to->getNamespace()][$to->getDBkey()] ) ) {
- $titlesToResolve[] = $to;
+ if ( $this->mPendingRedirectIDs ) {
+ $res = $db->select(
+ 'redirect',
+ [
+ 'rd_from',
+ 'rd_namespace',
+ 'rd_fragment',
+ 'rd_interwiki',
+ 'rd_title'
+ ], [ 'rd_from' => array_keys( $this->mPendingRedirectIDs ) ],
+ __METHOD__
+ );
+ foreach ( $res as $row ) {
+ $rdfrom = intval( $row->rd_from );
+ $from = $this->mPendingRedirectIDs[$rdfrom]->getPrefixedText();
+ $to = Title::makeTitle(
+ $row->rd_namespace,
+ $row->rd_title,
+ $row->rd_fragment,
+ $row->rd_interwiki
+ );
+ $this->mResolvedRedirectTitles[$from] = $this->mPendingRedirectIDs[$rdfrom];
+ unset( $this->mPendingRedirectIDs[$rdfrom] );
+ if ( $to->isExternal() ) {
+ $this->mInterwikiTitles[$to->getPrefixedText()] = $to->getInterwiki();
+ } elseif ( !isset( $this->mAllPages[$to->getNamespace()][$to->getDBkey()] ) ) {
+ $titlesToResolve[] = $to;
+ }
+ $this->mRedirectTitles[$from] = $to;
}
- $this->mRedirectTitles[$from] = $to;
- }
- if ( $this->mPendingRedirectIDs ) {
- // We found pages that aren't in the redirect table
- // Add them
- foreach ( $this->mPendingRedirectIDs as $id => $title ) {
- $page = WikiPage::factory( $title );
- $rt = $page->insertRedirect();
- if ( !$rt ) {
- // What the hell. Let's just ignore this
- continue;
+ if ( $this->mPendingRedirectIDs ) {
+ // We found pages that aren't in the redirect table
+ // Add them
+ foreach ( $this->mPendingRedirectIDs as $id => $title ) {
+ $page = WikiPage::factory( $title );
+ $rt = $page->insertRedirect();
+ if ( !$rt ) {
+ // What the hell. Let's just ignore this
+ continue;
+ }
+ if ( $rt->isExternal() ) {
+ $this->mInterwikiTitles[$rt->getPrefixedText()] = $rt->getInterwiki();
+ } elseif ( !isset( $this->mAllPages[$rt->getNamespace()][$rt->getDBkey()] ) ) {
+ $titlesToResolve[] = $rt;
+ }
+ $from = $title->getPrefixedText();
+ $this->mResolvedRedirectTitles[$from] = $title;
+ $this->mRedirectTitles[$from] = $rt;
+ unset( $this->mPendingRedirectIDs[$id] );
}
- if ( $rt->isExternal() ) {
- $this->mInterwikiTitles[$rt->getPrefixedText()] = $rt->getInterwiki();
- } elseif ( !isset( $this->mAllPages[$rt->getNamespace()][$rt->getDBkey()] ) ) {
- $titlesToResolve[] = $rt;
+ }
+ }
+
+ if ( $this->mPendingRedirectSpecialPages ) {
+ foreach ( $this->mPendingRedirectSpecialPages as $key => list( $from, $to ) ) {
+ $fromKey = $from->getPrefixedText();
+ $this->mResolvedRedirectTitles[$fromKey] = $from;
+ $this->mRedirectTitles[$fromKey] = $to;
+ if ( $to->isExternal() ) {
+ $this->mInterwikiTitles[$to->getPrefixedText()] = $to->getInterwiki();
+ } elseif ( !isset( $this->mAllPages[$to->getNamespace()][$to->getDBkey()] ) ) {
+ $titlesToResolve[] = $to;
}
- $from = $title->getPrefixedText();
- $this->mResolvedRedirectTitles[$from] = $title;
- $this->mRedirectTitles[$from] = $rt;
- unset( $this->mPendingRedirectIDs[$id] );
}
+ $this->mPendingRedirectSpecialPages = [];
+
+ // Set private caching since we don't know what criteria the
+ // special pages used to decide on these redirects.
+ $this->mCacheMode = 'private';
}
return $this->processTitlesArray( $titlesToResolve );
$dbkey = $titleObj->getDBkey();
if ( !isset( $this->mAllSpecials[$ns][$dbkey] ) ) {
$this->mAllSpecials[$ns][$dbkey] = $this->mFakePageId;
- $this->mSpecialTitles[$this->mFakePageId] = $titleObj;
- $this->mFakePageId--;
+ $target = null;
+ if ( $ns === NS_SPECIAL && $this->mResolveRedirects ) {
+ $special = SpecialPageFactory::getPage( $dbkey );
+ if ( $special instanceof RedirectSpecialArticle ) {
+ // Only RedirectSpecialArticle is intended to redirect to an article, other kinds of
+ // RedirectSpecialPage are probably applying weird URL parameters we don't want to handle.
+ $context = new DerivativeContext( $this );
+ $context->setTitle( $titleObj );
+ $context->setRequest( new FauxRequest );
+ $special->setContext( $context );
+ list( /* $alias */, $subpage ) = SpecialPageFactory::resolveAlias( $dbkey );
+ $target = $special->getRedirect( $subpage );
+ }
+ }
+ if ( $target ) {
+ $this->mPendingRedirectSpecialPages[$dbkey] = [ $titleObj, $target ];
+ } else {
+ $this->mSpecialTitles[$this->mFakePageId] = $titleObj;
+ $this->mFakePageId--;
+ }
}
} else {
// Regular page
$pageSet->getNormalizedTitlesAsResult()
);
}
+
+ public function testSpecialRedirects() {
+ $id1 = self::editPage( 'UTApiPageSet', 'UTApiPageSet in the default language' )
+ ->value['revision']->getTitle()->getArticleID();
+ $id2 = self::editPage( 'UTApiPageSet/de', 'UTApiPageSet in German' )
+ ->value['revision']->getTitle()->getArticleID();
+
+ $user = $this->getTestUser()->getUser();
+ $userName = $user->getName();
+ $userDbkey = str_replace( ' ', '_', $userName );
+ $request = new FauxRequest( [
+ 'titles' => join( '|', [
+ 'Special:MyContributions',
+ 'Special:MyPage',
+ 'Special:MyTalk/subpage',
+ 'Special:MyLanguage/UTApiPageSet',
+ ] ),
+ ] );
+ $context = new RequestContext();
+ $context->setRequest( $request );
+ $context->setUser( $user );
+
+ $main = new ApiMain( $context );
+ $pageSet = new ApiPageSet( $main );
+ $pageSet->execute();
+
+ $this->assertEquals( [
+ ], $pageSet->getRedirectTitlesAsResult() );
+ $this->assertEquals( [
+ [ 'ns' => -1, 'title' => 'Special:MyContributions', 'special' => true ],
+ [ 'ns' => -1, 'title' => 'Special:MyPage', 'special' => true ],
+ [ 'ns' => -1, 'title' => 'Special:MyTalk/subpage', 'special' => true ],
+ [ 'ns' => -1, 'title' => 'Special:MyLanguage/UTApiPageSet', 'special' => true ],
+ ], $pageSet->getInvalidTitlesAndRevisions() );
+ $this->assertEquals( [
+ ], $pageSet->getAllTitlesByNamespace() );
+
+ $request->setVal( 'redirects', 1 );
+ $main = new ApiMain( $context );
+ $pageSet = new ApiPageSet( $main );
+ $pageSet->execute();
+
+ $this->assertEquals( [
+ [ 'from' => 'Special:MyPage', 'to' => "User:$userName" ],
+ [ 'from' => 'Special:MyTalk/subpage', 'to' => "User talk:$userName/subpage" ],
+ [ 'from' => 'Special:MyLanguage/UTApiPageSet', 'to' => 'UTApiPageSet' ],
+ ], $pageSet->getRedirectTitlesAsResult() );
+ $this->assertEquals( [
+ [ 'ns' => -1, 'title' => 'Special:MyContributions', 'special' => true ],
+ [ 'ns' => 2, 'title' => "User:$userName", 'missing' => true ],
+ [ 'ns' => 3, 'title' => "User talk:$userName/subpage", 'missing' => true ],
+ ], $pageSet->getInvalidTitlesAndRevisions() );
+ $this->assertEquals( [
+ 0 => [ 'UTApiPageSet' => $id1 ],
+ 2 => [ $userDbkey => -2 ],
+ 3 => [ "$userDbkey/subpage" => -3 ],
+ ], $pageSet->getAllTitlesByNamespace() );
+
+ $context->setLanguage( 'de' );
+ $main = new ApiMain( $context );
+ $pageSet = new ApiPageSet( $main );
+ $pageSet->execute();
+
+ $this->assertEquals( [
+ [ 'from' => 'Special:MyPage', 'to' => "User:$userName" ],
+ [ 'from' => 'Special:MyTalk/subpage', 'to' => "User talk:$userName/subpage" ],
+ [ 'from' => 'Special:MyLanguage/UTApiPageSet', 'to' => 'UTApiPageSet/de' ],
+ ], $pageSet->getRedirectTitlesAsResult() );
+ $this->assertEquals( [
+ [ 'ns' => -1, 'title' => 'Special:MyContributions', 'special' => true ],
+ [ 'ns' => 2, 'title' => "User:$userName", 'missing' => true ],
+ [ 'ns' => 3, 'title' => "User talk:$userName/subpage", 'missing' => true ],
+ ], $pageSet->getInvalidTitlesAndRevisions() );
+ $this->assertEquals( [
+ 0 => [ 'UTApiPageSet/de' => $id2 ],
+ 2 => [ $userDbkey => -2 ],
+ 3 => [ "$userDbkey/subpage" => -3 ],
+ ], $pageSet->getAllTitlesByNamespace() );
+ }
}