示例#1
0
 /**
  * @param ApiPageSet $resultPageSet
  */
 private function run($resultPageSet = null)
 {
     $params = $this->extractRequestParams();
     $search = $params['search'];
     $limit = $params['limit'];
     $namespaces = $params['namespace'];
     $searcher = new TitlePrefixSearch();
     $titles = $searcher->searchWithVariants($search, $limit, $namespaces);
     if ($resultPageSet) {
         $resultPageSet->populateFromTitles($titles);
     } else {
         $result = $this->getResult();
         foreach ($titles as $title) {
             if (!$limit--) {
                 break;
             }
             $vals = array('ns' => intval($title->getNamespace()), 'title' => $title->getPrefixedText());
             if ($title->isSpecialPage()) {
                 $vals['special'] = '';
             } else {
                 $vals['pageid'] = intval($title->getArticleId());
             }
             $fit = $result->addValue(array('query', $this->getModuleName()), null, $vals);
             if (!$fit) {
                 break;
             }
         }
         $result->setIndexedTagName_internal(array('query', $this->getModuleName()), $this->getModulePrefix());
     }
 }
 /**
  * @param ApiPageSet $resultPageSet
  */
 private function run($resultPageSet = null)
 {
     $params = $this->extractRequestParams();
     $search = $params['search'];
     $limit = $params['limit'];
     $namespaces = $params['namespace'];
     $offset = $params['offset'];
     $searchEngine = MediaWikiServices::getInstance()->newSearchEngine();
     $searchEngine->setLimitOffset($limit + 1, $offset);
     $searchEngine->setNamespaces($namespaces);
     $titles = $searchEngine->extractTitles($searchEngine->completionSearchWithVariants($search));
     if ($resultPageSet) {
         $resultPageSet->setRedirectMergePolicy(function (array $current, array $new) {
             if (!isset($current['index']) || $new['index'] < $current['index']) {
                 $current['index'] = $new['index'];
             }
             return $current;
         });
         if (count($titles) > $limit) {
             $this->setContinueEnumParameter('offset', $offset + $params['limit']);
             array_pop($titles);
         }
         $resultPageSet->populateFromTitles($titles);
         foreach ($titles as $index => $title) {
             $resultPageSet->setGeneratorData($title, ['index' => $index + $offset + 1]);
         }
     } else {
         $result = $this->getResult();
         $count = 0;
         foreach ($titles as $title) {
             if (++$count > $limit) {
                 $this->setContinueEnumParameter('offset', $offset + $params['limit']);
                 break;
             }
             $vals = ['ns' => intval($title->getNamespace()), 'title' => $title->getPrefixedText()];
             if ($title->isSpecialPage()) {
                 $vals['special'] = true;
             } else {
                 $vals['pageid'] = intval($title->getArticleID());
             }
             $fit = $result->addValue(['query', $this->getModuleName()], null, $vals);
             if (!$fit) {
                 $this->setContinueEnumParameter('offset', $offset + $count - 1);
                 break;
             }
         }
         $result->addIndexedTagName(['query', $this->getModuleName()], $this->getModulePrefix());
     }
 }
示例#3
0
 protected function createPageSetWithRedirect()
 {
     $target = Title::makeTitle(NS_MAIN, 'UTRedirectTarget');
     $sourceA = Title::makeTitle(NS_MAIN, 'UTRedirectSourceA');
     $sourceB = Title::makeTitle(NS_MAIN, 'UTRedirectSourceB');
     self::editPage('UTRedirectTarget', 'api page set test');
     self::editPage('UTRedirectSourceA', '#REDIRECT [[UTRedirectTarget]]');
     self::editPage('UTRedirectSourceB', '#REDIRECT [[UTRedirectTarget]]');
     $request = new FauxRequest(['redirects' => 1]);
     $context = new RequestContext();
     $context->setRequest($request);
     $main = new ApiMain($context);
     $pageSet = new ApiPageSet($main);
     $pageSet->setGeneratorData($sourceA, ['index' => 1]);
     $pageSet->setGeneratorData($sourceB, ['index' => 3]);
     $pageSet->populateFromTitles([$sourceA, $sourceB]);
     return [$target, $pageSet];
 }
 /**
  * @param ApiPageSet $resultPageSet
  */
 private function run($resultPageSet = null)
 {
     $params = $this->extractRequestParams();
     $search = $params['search'];
     $limit = $params['limit'];
     $namespaces = $params['namespace'];
     $offset = $params['offset'];
     $searcher = new TitlePrefixSearch();
     $titles = $searcher->searchWithVariants($search, $limit + 1, $namespaces, $offset);
     if ($resultPageSet) {
         if (count($titles) > $limit) {
             $this->setContinueEnumParameter('offset', $offset + $params['limit']);
             array_pop($titles);
         }
         $resultPageSet->populateFromTitles($titles);
         foreach ($titles as $index => $title) {
             $resultPageSet->setGeneratorData($title, array('index' => $index + $offset + 1));
         }
     } else {
         $result = $this->getResult();
         $count = 0;
         foreach ($titles as $title) {
             if (++$count > $limit) {
                 $this->setContinueEnumParameter('offset', $offset + $params['limit']);
                 break;
             }
             $vals = array('ns' => intval($title->getNamespace()), 'title' => $title->getPrefixedText());
             if ($title->isSpecialPage()) {
                 $vals['special'] = true;
             } else {
                 $vals['pageid'] = intval($title->getArticleId());
             }
             $fit = $result->addValue(array('query', $this->getModuleName()), null, $vals);
             if (!$fit) {
                 $this->setContinueEnumParameter('offset', $offset + $count - 1);
                 break;
             }
         }
         $result->addIndexedTagName(array('query', $this->getModuleName()), $this->getModulePrefix());
     }
 }
 /**
  * @param ApiPageSet $resultPageSet
  * @return void
  */
 private function run($resultPageSet = null)
 {
     global $wgContLang;
     $params = $this->extractRequestParams();
     // Extract parameters
     $limit = $params['limit'];
     $query = $params['search'];
     $what = $params['what'];
     $interwiki = $params['interwiki'];
     $searchInfo = array_flip($params['info']);
     $prop = array_flip($params['prop']);
     // Deprecated parameters
     if (isset($prop['hasrelated'])) {
         $this->logFeatureUsage('action=search&srprop=hasrelated');
         $this->setWarning('srprop=hasrelated has been deprecated');
     }
     if (isset($prop['score'])) {
         $this->logFeatureUsage('action=search&srprop=score');
         $this->setWarning('srprop=score has been deprecated');
     }
     // Create search engine instance and set options
     $search = isset($params['backend']) && $params['backend'] != self::BACKEND_NULL_PARAM ? SearchEngine::create($params['backend']) : SearchEngine::create();
     $search->setLimitOffset($limit + 1, $params['offset']);
     $search->setNamespaces($params['namespace']);
     $search->setFeatureData('rewrite', (bool) $params['enablerewrites']);
     $query = $search->transformSearchTerm($query);
     $query = $search->replacePrefixes($query);
     // Perform the actual search
     if ($what == 'text') {
         $matches = $search->searchText($query);
     } elseif ($what == 'title') {
         $matches = $search->searchTitle($query);
     } elseif ($what == 'nearmatch') {
         // near matches must receive the user input as provided, otherwise
         // the near matches within namespaces are lost.
         $matches = SearchEngine::getNearMatchResultSet($params['search']);
     } else {
         // We default to title searches; this is a terrible legacy
         // of the way we initially set up the MySQL fulltext-based
         // search engine with separate title and text fields.
         // In the future, the default should be for a combined index.
         $what = 'title';
         $matches = $search->searchTitle($query);
         // Not all search engines support a separate title search,
         // for instance the Lucene-based engine we use on Wikipedia.
         // In this case, fall back to full-text search (which will
         // include titles in it!)
         if (is_null($matches)) {
             $what = 'text';
             $matches = $search->searchText($query);
         }
     }
     if (is_null($matches)) {
         $this->dieUsage("{$what} search is disabled", "search-{$what}-disabled");
     } elseif ($matches instanceof Status && !$matches->isGood()) {
         $this->dieUsage($matches->getWikiText(), 'search-error');
     }
     if ($resultPageSet === null) {
         $apiResult = $this->getResult();
         // Add search meta data to result
         if (isset($searchInfo['totalhits'])) {
             $totalhits = $matches->getTotalHits();
             if ($totalhits !== null) {
                 $apiResult->addValue(array('query', 'searchinfo'), 'totalhits', $totalhits);
             }
         }
         if (isset($searchInfo['suggestion']) && $matches->hasSuggestion()) {
             $apiResult->addValue(array('query', 'searchinfo'), 'suggestion', $matches->getSuggestionQuery());
             $apiResult->addValue(array('query', 'searchinfo'), 'suggestionsnippet', $matches->getSuggestionSnippet());
         }
         if (isset($searchInfo['rewrittenquery']) && $matches->hasRewrittenQuery()) {
             $apiResult->addValue(array('query', 'searchinfo'), 'rewrittenquery', $matches->getQueryAfterRewrite());
             $apiResult->addValue(array('query', 'searchinfo'), 'rewrittenquerysnippet', $matches->getQueryAfterRewriteSnippet());
         }
     }
     // Add the search results to the result
     $terms = $wgContLang->convertForSearchResult($matches->termMatches());
     $titles = array();
     $count = 0;
     $result = $matches->next();
     while ($result) {
         if (++$count > $limit) {
             // We've reached the one extra which shows that there are
             // additional items to be had. Stop here...
             $this->setContinueEnumParameter('offset', $params['offset'] + $params['limit']);
             break;
         }
         // Silently skip broken and missing titles
         if ($result->isBrokenTitle() || $result->isMissingRevision()) {
             $result = $matches->next();
             continue;
         }
         $title = $result->getTitle();
         if ($resultPageSet === null) {
             $vals = array();
             ApiQueryBase::addTitleInfo($vals, $title);
             if (isset($prop['snippet'])) {
                 $vals['snippet'] = $result->getTextSnippet($terms);
             }
             if (isset($prop['size'])) {
                 $vals['size'] = $result->getByteSize();
             }
             if (isset($prop['wordcount'])) {
                 $vals['wordcount'] = $result->getWordCount();
             }
             if (isset($prop['timestamp'])) {
                 $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $result->getTimestamp());
             }
             if (isset($prop['titlesnippet'])) {
                 $vals['titlesnippet'] = $result->getTitleSnippet();
             }
             if (isset($prop['categorysnippet'])) {
                 $vals['categorysnippet'] = $result->getCategorySnippet();
             }
             if (!is_null($result->getRedirectTitle())) {
                 if (isset($prop['redirecttitle'])) {
                     $vals['redirecttitle'] = $result->getRedirectTitle()->getPrefixedText();
                 }
                 if (isset($prop['redirectsnippet'])) {
                     $vals['redirectsnippet'] = $result->getRedirectSnippet();
                 }
             }
             if (!is_null($result->getSectionTitle())) {
                 if (isset($prop['sectiontitle'])) {
                     $vals['sectiontitle'] = $result->getSectionTitle()->getFragment();
                 }
                 if (isset($prop['sectionsnippet'])) {
                     $vals['sectionsnippet'] = $result->getSectionSnippet();
                 }
             }
             if (isset($prop['isfilematch'])) {
                 $vals['isfilematch'] = $result->isFileMatch();
             }
             // Add item to results and see whether it fits
             $fit = $apiResult->addValue(array('query', $this->getModuleName()), null, $vals);
             if (!$fit) {
                 $this->setContinueEnumParameter('offset', $params['offset'] + $count - 1);
                 break;
             }
         } else {
             $titles[] = $title;
         }
         $result = $matches->next();
     }
     $hasInterwikiResults = false;
     $totalhits = null;
     if ($interwiki && $resultPageSet === null && $matches->hasInterwikiResults()) {
         foreach ($matches->getInterwikiResults() as $matches) {
             $matches = $matches->getInterwikiResults();
             $hasInterwikiResults = true;
             // Include number of results if requested
             if ($resultPageSet === null && isset($searchInfo['totalhits'])) {
                 $totalhits += $matches->getTotalHits();
             }
             $result = $matches->next();
             while ($result) {
                 $title = $result->getTitle();
                 if ($resultPageSet === null) {
                     $vals = array('namespace' => $result->getInterwikiNamespaceText(), 'title' => $title->getText(), 'url' => $title->getFullUrl());
                     // Add item to results and see whether it fits
                     $fit = $apiResult->addValue(array('query', 'interwiki' . $this->getModuleName(), $result->getInterwikiPrefix()), null, $vals);
                     if (!$fit) {
                         // We hit the limit. We can't really provide any meaningful
                         // pagination info so just bail out
                         break;
                     }
                 } else {
                     $titles[] = $title;
                 }
                 $result = $matches->next();
             }
         }
         if ($totalhits !== null) {
             $apiResult->addValue(array('query', 'interwikisearchinfo'), 'totalhits', $totalhits);
         }
     }
     if ($resultPageSet === null) {
         $apiResult->addIndexedTagName(array('query', $this->getModuleName()), 'p');
         if ($hasInterwikiResults) {
             $apiResult->addIndexedTagName(array('query', 'interwiki' . $this->getModuleName()), 'p');
         }
     } else {
         $resultPageSet->populateFromTitles($titles);
         $offset = $params['offset'] + 1;
         foreach ($titles as $index => $title) {
             $resultPageSet->setGeneratorData($title, array('index' => $index + $offset));
         }
     }
 }
 /**
  * @param ApiPageSet $resultPageSet
  * @return void
  */
 protected function run(ApiPageSet $resultPageSet = null)
 {
     $user = $this->getUser();
     // Before doing anything at all, let's check permissions
     if (!$user->isAllowed('deletedhistory')) {
         $this->dieUsage('You don\'t have permission to view deleted revision information', 'permissiondenied');
     }
     $db = $this->getDB();
     $params = $this->extractRequestParams(false);
     $result = $this->getResult();
     // If the user wants no namespaces, they get no pages.
     if ($params['namespace'] === []) {
         if ($resultPageSet === null) {
             $result->addValue('query', $this->getModuleName(), []);
         }
         return;
     }
     // This module operates in two modes:
     // 'user': List deleted revs by a certain user
     // 'all': List all deleted revs in NS
     $mode = 'all';
     if (!is_null($params['user'])) {
         $mode = 'user';
     }
     if ($mode == 'user') {
         foreach (['from', 'to', 'prefix', 'excludeuser'] as $param) {
             if (!is_null($params[$param])) {
                 $p = $this->getModulePrefix();
                 $this->dieUsage("The '{$p}{$param}' parameter cannot be used with '{$p}user'", 'badparams');
             }
         }
     } else {
         foreach (['start', 'end'] as $param) {
             if (!is_null($params[$param])) {
                 $p = $this->getModulePrefix();
                 $this->dieUsage("The '{$p}{$param}' parameter may only be used with '{$p}user'", 'badparams');
             }
         }
     }
     // If we're generating titles only, we can use DISTINCT for a better
     // query. But we can't do that in 'user' mode (wrong index), and we can
     // only do it when sorting ASC (because MySQL apparently can't use an
     // index backwards for grouping even though it can for ORDER BY, WTF?)
     $dir = $params['dir'];
     $optimizeGenerateTitles = false;
     if ($mode === 'all' && $params['generatetitles'] && $resultPageSet !== null) {
         if ($dir === 'newer') {
             $optimizeGenerateTitles = true;
         } else {
             $p = $this->getModulePrefix();
             $this->setWarning("For better performance when generating titles, set {$p}dir=newer");
         }
     }
     $this->addTables('archive');
     if ($resultPageSet === null) {
         $this->parseParameters($params);
         $this->addFields(Revision::selectArchiveFields());
         $this->addFields(['ar_title', 'ar_namespace']);
     } else {
         $this->limit = $this->getParameter('limit') ?: 10;
         $this->addFields(['ar_title', 'ar_namespace']);
         if ($optimizeGenerateTitles) {
             $this->addOption('DISTINCT');
         } else {
             $this->addFields(['ar_timestamp', 'ar_rev_id', 'ar_id']);
         }
     }
     if ($this->fld_tags) {
         $this->addTables('tag_summary');
         $this->addJoinConds(['tag_summary' => ['LEFT JOIN', ['ar_rev_id=ts_rev_id']]]);
         $this->addFields('ts_tags');
     }
     if (!is_null($params['tag'])) {
         $this->addTables('change_tag');
         $this->addJoinConds(['change_tag' => ['INNER JOIN', ['ar_rev_id=ct_rev_id']]]);
         $this->addWhereFld('ct_tag', $params['tag']);
     }
     if ($this->fetchContent) {
         // Modern MediaWiki has the content for deleted revs in the 'text'
         // table using fields old_text and old_flags. But revisions deleted
         // pre-1.5 store the content in the 'archive' table directly using
         // fields ar_text and ar_flags, and no corresponding 'text' row. So
         // we have to LEFT JOIN and fetch all four fields.
         $this->addTables('text');
         $this->addJoinConds(['text' => ['LEFT JOIN', ['ar_text_id=old_id']]]);
         $this->addFields(['ar_text', 'ar_flags', 'old_text', 'old_flags']);
         // This also means stricter restrictions
         if (!$user->isAllowedAny('undelete', 'deletedtext')) {
             $this->dieUsage('You don\'t have permission to view deleted revision content', 'permissiondenied');
         }
     }
     $miser_ns = null;
     if ($mode == 'all') {
         if ($params['namespace'] !== null) {
             $namespaces = $params['namespace'];
         } else {
             $namespaces = MWNamespace::getValidNamespaces();
         }
         $this->addWhereFld('ar_namespace', $namespaces);
         // For from/to/prefix, we have to consider the potential
         // transformations of the title in all specified namespaces.
         // Generally there will be only one transformation, but wikis with
         // some namespaces case-sensitive could have two.
         if ($params['from'] !== null || $params['to'] !== null) {
             $isDirNewer = $dir === 'newer';
             $after = $isDirNewer ? '>=' : '<=';
             $before = $isDirNewer ? '<=' : '>=';
             $where = [];
             foreach ($namespaces as $ns) {
                 $w = [];
                 if ($params['from'] !== null) {
                     $w[] = 'ar_title' . $after . $db->addQuotes($this->titlePartToKey($params['from'], $ns));
                 }
                 if ($params['to'] !== null) {
                     $w[] = 'ar_title' . $before . $db->addQuotes($this->titlePartToKey($params['to'], $ns));
                 }
                 $w = $db->makeList($w, LIST_AND);
                 $where[$w][] = $ns;
             }
             if (count($where) == 1) {
                 $where = key($where);
                 $this->addWhere($where);
             } else {
                 $where2 = [];
                 foreach ($where as $w => $ns) {
                     $where2[] = $db->makeList([$w, 'ar_namespace' => $ns], LIST_AND);
                 }
                 $this->addWhere($db->makeList($where2, LIST_OR));
             }
         }
         if (isset($params['prefix'])) {
             $where = [];
             foreach ($namespaces as $ns) {
                 $w = 'ar_title' . $db->buildLike($this->titlePartToKey($params['prefix'], $ns), $db->anyString());
                 $where[$w][] = $ns;
             }
             if (count($where) == 1) {
                 $where = key($where);
                 $this->addWhere($where);
             } else {
                 $where2 = [];
                 foreach ($where as $w => $ns) {
                     $where2[] = $db->makeList([$w, 'ar_namespace' => $ns], LIST_AND);
                 }
                 $this->addWhere($db->makeList($where2, LIST_OR));
             }
         }
     } else {
         if ($this->getConfig()->get('MiserMode')) {
             $miser_ns = $params['namespace'];
         } else {
             $this->addWhereFld('ar_namespace', $params['namespace']);
         }
         $this->addTimestampWhereRange('ar_timestamp', $dir, $params['start'], $params['end']);
     }
     if (!is_null($params['user'])) {
         $this->addWhereFld('ar_user_text', $params['user']);
     } elseif (!is_null($params['excludeuser'])) {
         $this->addWhere('ar_user_text != ' . $db->addQuotes($params['excludeuser']));
     }
     if (!is_null($params['user']) || !is_null($params['excludeuser'])) {
         // Paranoia: avoid brute force searches (bug 17342)
         // (shouldn't be able to get here without 'deletedhistory', but
         // check it again just in case)
         if (!$user->isAllowed('deletedhistory')) {
             $bitmask = Revision::DELETED_USER;
         } elseif (!$user->isAllowedAny('suppressrevision', 'viewsuppressed')) {
             $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED;
         } else {
             $bitmask = 0;
         }
         if ($bitmask) {
             $this->addWhere($db->bitAnd('ar_deleted', $bitmask) . " != {$bitmask}");
         }
     }
     if (!is_null($params['continue'])) {
         $cont = explode('|', $params['continue']);
         $op = $dir == 'newer' ? '>' : '<';
         if ($optimizeGenerateTitles) {
             $this->dieContinueUsageIf(count($cont) != 2);
             $ns = intval($cont[0]);
             $this->dieContinueUsageIf(strval($ns) !== $cont[0]);
             $title = $db->addQuotes($cont[1]);
             $this->addWhere("ar_namespace {$op} {$ns} OR " . "(ar_namespace = {$ns} AND ar_title {$op}= {$title})");
         } elseif ($mode == 'all') {
             $this->dieContinueUsageIf(count($cont) != 4);
             $ns = intval($cont[0]);
             $this->dieContinueUsageIf(strval($ns) !== $cont[0]);
             $title = $db->addQuotes($cont[1]);
             $ts = $db->addQuotes($db->timestamp($cont[2]));
             $ar_id = (int) $cont[3];
             $this->dieContinueUsageIf(strval($ar_id) !== $cont[3]);
             $this->addWhere("ar_namespace {$op} {$ns} OR " . "(ar_namespace = {$ns} AND " . "(ar_title {$op} {$title} OR " . "(ar_title = {$title} AND " . "(ar_timestamp {$op} {$ts} OR " . "(ar_timestamp = {$ts} AND " . "ar_id {$op}= {$ar_id})))))");
         } else {
             $this->dieContinueUsageIf(count($cont) != 2);
             $ts = $db->addQuotes($db->timestamp($cont[0]));
             $ar_id = (int) $cont[1];
             $this->dieContinueUsageIf(strval($ar_id) !== $cont[1]);
             $this->addWhere("ar_timestamp {$op} {$ts} OR " . "(ar_timestamp = {$ts} AND " . "ar_id {$op}= {$ar_id})");
         }
     }
     $this->addOption('LIMIT', $this->limit + 1);
     $sort = $dir == 'newer' ? '' : ' DESC';
     $orderby = [];
     if ($optimizeGenerateTitles) {
         // Targeting index name_title_timestamp
         if ($params['namespace'] === null || count(array_unique($params['namespace'])) > 1) {
             $orderby[] = "ar_namespace {$sort}";
         }
         $orderby[] = "ar_title {$sort}";
     } elseif ($mode == 'all') {
         // Targeting index name_title_timestamp
         if ($params['namespace'] === null || count(array_unique($params['namespace'])) > 1) {
             $orderby[] = "ar_namespace {$sort}";
         }
         $orderby[] = "ar_title {$sort}";
         $orderby[] = "ar_timestamp {$sort}";
         $orderby[] = "ar_id {$sort}";
     } else {
         // Targeting index usertext_timestamp
         // 'user' is always constant.
         $orderby[] = "ar_timestamp {$sort}";
         $orderby[] = "ar_id {$sort}";
     }
     $this->addOption('ORDER BY', $orderby);
     $res = $this->select(__METHOD__);
     $pageMap = [];
     // Maps ns&title to array index
     $count = 0;
     $nextIndex = 0;
     $generated = [];
     foreach ($res as $row) {
         if (++$count > $this->limit) {
             // We've had enough
             if ($optimizeGenerateTitles) {
                 $this->setContinueEnumParameter('continue', "{$row->ar_namespace}|{$row->ar_title}");
             } elseif ($mode == 'all') {
                 $this->setContinueEnumParameter('continue', "{$row->ar_namespace}|{$row->ar_title}|{$row->ar_timestamp}|{$row->ar_id}");
             } else {
                 $this->setContinueEnumParameter('continue', "{$row->ar_timestamp}|{$row->ar_id}");
             }
             break;
         }
         // Miser mode namespace check
         if ($miser_ns !== null && !in_array($row->ar_namespace, $miser_ns)) {
             continue;
         }
         if ($resultPageSet !== null) {
             if ($params['generatetitles']) {
                 $key = "{$row->ar_namespace}:{$row->ar_title}";
                 if (!isset($generated[$key])) {
                     $generated[$key] = Title::makeTitle($row->ar_namespace, $row->ar_title);
                 }
             } else {
                 $generated[] = $row->ar_rev_id;
             }
         } else {
             $revision = Revision::newFromArchiveRow($row);
             $rev = $this->extractRevisionInfo($revision, $row);
             if (!isset($pageMap[$row->ar_namespace][$row->ar_title])) {
                 $index = $nextIndex++;
                 $pageMap[$row->ar_namespace][$row->ar_title] = $index;
                 $title = $revision->getTitle();
                 $a = ['pageid' => $title->getArticleID(), 'revisions' => [$rev]];
                 ApiResult::setIndexedTagName($a['revisions'], 'rev');
                 ApiQueryBase::addTitleInfo($a, $title);
                 $fit = $result->addValue(['query', $this->getModuleName()], $index, $a);
             } else {
                 $index = $pageMap[$row->ar_namespace][$row->ar_title];
                 $fit = $result->addValue(['query', $this->getModuleName(), $index, 'revisions'], null, $rev);
             }
             if (!$fit) {
                 if ($mode == 'all') {
                     $this->setContinueEnumParameter('continue', "{$row->ar_namespace}|{$row->ar_title}|{$row->ar_timestamp}|{$row->ar_id}");
                 } else {
                     $this->setContinueEnumParameter('continue', "{$row->ar_timestamp}|{$row->ar_id}");
                 }
                 break;
             }
         }
     }
     if ($resultPageSet !== null) {
         if ($params['generatetitles']) {
             $resultPageSet->populateFromTitles($generated);
         } else {
             $resultPageSet->populateFromRevisionIDs($generated);
         }
     } else {
         $result->addIndexedTagName(['query', $this->getModuleName()], 'page');
     }
 }