/** * @param IDatabase $db * @return mixed */ public function doQuery($db) { $ids = array_map('intval', $this->ids); $queryInfo = array('tables' => array('revision', 'user'), 'fields' => array_merge(Revision::selectFields(), Revision::selectUserFields()), 'conds' => array('rev_page' => $this->title->getArticleID(), 'rev_id' => $ids), 'options' => array('ORDER BY' => 'rev_id DESC'), 'join_conds' => array('page' => Revision::pageJoinCond(), 'user' => Revision::userJoinCond())); ChangeTags::modifyDisplayQuery($queryInfo['tables'], $queryInfo['fields'], $queryInfo['conds'], $queryInfo['join_conds'], $queryInfo['options'], ''); return $db->select($queryInfo['tables'], $queryInfo['fields'], $queryInfo['conds'], __METHOD__, $queryInfo['options'], $queryInfo['join_conds']); }
/** * @param $db DatabaseBase * @return mixed */ public function doQuery($db) { $ids = array_map('intval', $this->ids); $live = $db->select(array('revision', 'page', 'user'), array_merge(Revision::selectFields(), Revision::selectUserFields()), array('rev_page' => $this->title->getArticleID(), 'rev_id' => $ids), __METHOD__, array('ORDER BY' => 'rev_id DESC'), array('page' => Revision::pageJoinCond(), 'user' => Revision::userJoinCond())); if ($live->numRows() >= count($ids)) { // All requested revisions are live, keeps things simple! return $live; } // Check if any requested revisions are available fully deleted. $archived = $db->select(array('archive'), '*', array('ar_rev_id' => $ids), __METHOD__, array('ORDER BY' => 'ar_rev_id DESC')); if ($archived->numRows() == 0) { return $live; } elseif ($live->numRows() == 0) { return $archived; } else { // Combine the two! Whee $rows = array(); foreach ($live as $row) { $rows[$row->rev_id] = $row; } foreach ($archived as $row) { $rows[$row->ar_rev_id] = $row; } krsort($rows); return new FakeResultWrapper(array_values($rows)); } }
/** * @param IDatabase $db * @return mixed */ public function doQuery($db) { $ids = array_map('intval', $this->ids); $queryInfo = ['tables' => ['revision', 'user'], 'fields' => array_merge(Revision::selectFields(), Revision::selectUserFields()), 'conds' => ['rev_page' => $this->title->getArticleID(), 'rev_id' => $ids], 'options' => ['ORDER BY' => 'rev_id DESC'], 'join_conds' => ['page' => Revision::pageJoinCond(), 'user' => Revision::userJoinCond()]]; ChangeTags::modifyDisplayQuery($queryInfo['tables'], $queryInfo['fields'], $queryInfo['conds'], $queryInfo['join_conds'], $queryInfo['options'], ''); $live = $db->select($queryInfo['tables'], $queryInfo['fields'], $queryInfo['conds'], __METHOD__, $queryInfo['options'], $queryInfo['join_conds']); if ($live->numRows() >= count($ids)) { // All requested revisions are live, keeps things simple! return $live; } $archiveQueryInfo = ['tables' => ['archive'], 'fields' => Revision::selectArchiveFields(), 'conds' => ['ar_rev_id' => $ids], 'options' => ['ORDER BY' => 'ar_rev_id DESC'], 'join_conds' => []]; ChangeTags::modifyDisplayQuery($archiveQueryInfo['tables'], $archiveQueryInfo['fields'], $archiveQueryInfo['conds'], $archiveQueryInfo['join_conds'], $archiveQueryInfo['options'], ''); // Check if any requested revisions are available fully deleted. $archived = $db->select($archiveQueryInfo['tables'], $archiveQueryInfo['fields'], $archiveQueryInfo['conds'], __METHOD__, $archiveQueryInfo['options'], $archiveQueryInfo['join_conds']); if ($archived->numRows() == 0) { return $live; } elseif ($live->numRows() == 0) { return $archived; } else { // Combine the two! Whee $rows = []; foreach ($live as $row) { $rows[$row->rev_id] = $row; } foreach ($archived as $row) { $rows[$row->ar_rev_id] = $row; } krsort($rows); return new FakeResultWrapper(array_values($rows)); } }
function getQueryInfo() { $conds = $this->mConds; $conds['rev_page'] = $this->articleID; $conds[] = "rev_timestamp < " . $this->mDb->addQuotes($this->maxTimestamp); return ['tables' => ['revision', 'page', 'user'], 'fields' => array_merge(Revision::selectFields(), Revision::selectUserFields()), 'conds' => $conds, 'join_conds' => ['page' => Revision::pageJoinCond(), 'user' => Revision::userJoinCond()]]; }
protected function run(ApiPageSet $resultPageSet = null) { $params = $this->extractRequestParams(false); // If any of those parameters are used, work in 'enumeration' mode. // Enum mode can only be used when exactly one page is provided. // Enumerating revisions on multiple pages make it extremely // difficult to manage continuations and require additional SQL indexes $enumRevMode = !is_null($params['user']) || !is_null($params['excludeuser']) || !is_null($params['limit']) || !is_null($params['startid']) || !is_null($params['endid']) || $params['dir'] === 'newer' || !is_null($params['start']) || !is_null($params['end']); $pageSet = $this->getPageSet(); $pageCount = $pageSet->getGoodTitleCount(); $revCount = $pageSet->getRevisionCount(); // Optimization -- nothing to do if ($revCount === 0 && $pageCount === 0) { // Nothing to do return; } if ($revCount > 0 && count($pageSet->getLiveRevisionIDs()) === 0) { // We're in revisions mode but all given revisions are deleted return; } if ($revCount > 0 && $enumRevMode) { $this->dieUsage('The revids= parameter may not be used with the list options ' . '(limit, startid, endid, dirNewer, start, end).', 'revids'); } if ($pageCount > 1 && $enumRevMode) { $this->dieUsage('titles, pageids or a generator was used to supply multiple pages, ' . 'but the limit, startid, endid, dirNewer, user, excludeuser, start ' . 'and end parameters may only be used on a single page.', 'multpages'); } // In non-enum mode, rvlimit can't be directly used. Use the maximum // allowed value. if (!$enumRevMode) { $this->setParsedLimit = false; $params['limit'] = 'max'; } $db = $this->getDB(); $this->addTables(array('revision', 'page')); $this->addJoinConds(array('page' => array('INNER JOIN', array('page_id = rev_page')))); if ($resultPageSet === null) { $this->parseParameters($params); $this->token = $params['token']; $this->addFields(Revision::selectFields()); if ($this->token !== null || $pageCount > 0) { $this->addFields(Revision::selectPageFields()); } } else { $this->limit = $this->getParameter('limit') ?: 10; $this->addFields(array('rev_id', 'rev_page')); } if ($this->fld_tags) { $this->addTables('tag_summary'); $this->addJoinConds(array('tag_summary' => array('LEFT JOIN', array('rev_id=ts_rev_id')))); $this->addFields('ts_tags'); } if (!is_null($params['tag'])) { $this->addTables('change_tag'); $this->addJoinConds(array('change_tag' => array('INNER JOIN', array('rev_id=ct_rev_id')))); $this->addWhereFld('ct_tag', $params['tag']); } if ($this->fetchContent) { // For each page we will request, the user must have read rights for that page $user = $this->getUser(); /** @var $title Title */ foreach ($pageSet->getGoodTitles() as $title) { if (!$title->userCan('read', $user)) { $this->dieUsage('The current user is not allowed to read ' . $title->getPrefixedText(), 'accessdenied'); } } $this->addTables('text'); $this->addJoinConds(array('text' => array('INNER JOIN', array('rev_text_id=old_id')))); $this->addFields('old_id'); $this->addFields(Revision::selectTextFields()); } // add user name, if needed if ($this->fld_user) { $this->addTables('user'); $this->addJoinConds(array('user' => Revision::userJoinCond())); $this->addFields(Revision::selectUserFields()); } if ($enumRevMode) { // This is mostly to prevent parameter errors (and optimize SQL?) if (!is_null($params['startid']) && !is_null($params['start'])) { $this->dieUsage('start and startid cannot be used together', 'badparams'); } if (!is_null($params['endid']) && !is_null($params['end'])) { $this->dieUsage('end and endid cannot be used together', 'badparams'); } if (!is_null($params['user']) && !is_null($params['excludeuser'])) { $this->dieUsage('user and excludeuser cannot be used together', 'badparams'); } // Continuing effectively uses startid. But we can't use rvstartid // directly, because there is no way to tell the client to ''not'' // send rvstart if it sent it in the original query. So instead we // send the continuation startid as rvcontinue, and ignore both // rvstart and rvstartid when that is supplied. if (!is_null($params['continue'])) { $params['startid'] = $params['continue']; $params['start'] = null; } // This code makes an assumption that sorting by rev_id and rev_timestamp produces // the same result. This way users may request revisions starting at a given time, // but to page through results use the rev_id returned after each page. // Switching to rev_id removes the potential problem of having more than // one row with the same timestamp for the same page. // The order needs to be the same as start parameter to avoid SQL filesort. if (is_null($params['startid']) && is_null($params['endid'])) { $this->addTimestampWhereRange('rev_timestamp', $params['dir'], $params['start'], $params['end']); } else { $this->addWhereRange('rev_id', $params['dir'], $params['startid'], $params['endid']); // One of start and end can be set // If neither is set, this does nothing $this->addTimestampWhereRange('rev_timestamp', $params['dir'], $params['start'], $params['end'], false); } // There is only one ID, use it $ids = array_keys($pageSet->getGoodTitles()); $this->addWhereFld('rev_page', reset($ids)); if (!is_null($params['user'])) { $this->addWhereFld('rev_user_text', $params['user']); } elseif (!is_null($params['excludeuser'])) { $this->addWhere('rev_user_text != ' . $db->addQuotes($params['excludeuser'])); } if (!is_null($params['user']) || !is_null($params['excludeuser'])) { // Paranoia: avoid brute force searches (bug 17342) if (!$this->getUser()->isAllowed('deletedhistory')) { $bitmask = Revision::DELETED_USER; } elseif (!$this->getUser()->isAllowedAny('suppressrevision', 'viewsuppressed')) { $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED; } else { $bitmask = 0; } if ($bitmask) { $this->addWhere($db->bitAnd('rev_deleted', $bitmask) . " != {$bitmask}"); } } } elseif ($revCount > 0) { $revs = $pageSet->getLiveRevisionIDs(); // Get all revision IDs $this->addWhereFld('rev_id', array_keys($revs)); if (!is_null($params['continue'])) { $this->addWhere('rev_id >= ' . intval($params['continue'])); } $this->addOption('ORDER BY', 'rev_id'); } elseif ($pageCount > 0) { $titles = $pageSet->getGoodTitles(); // When working in multi-page non-enumeration mode, // limit to the latest revision only $this->addWhere('page_latest=rev_id'); // Get all page IDs $this->addWhereFld('page_id', array_keys($titles)); // Every time someone relies on equality propagation, god kills a kitten :) $this->addWhereFld('rev_page', array_keys($titles)); if (!is_null($params['continue'])) { $cont = explode('|', $params['continue']); $this->dieContinueUsageIf(count($cont) != 2); $pageid = intval($cont[0]); $revid = intval($cont[1]); $this->addWhere("rev_page > {$pageid} OR " . "(rev_page = {$pageid} AND " . "rev_id >= {$revid})"); } $this->addOption('ORDER BY', array('rev_page', 'rev_id')); } else { ApiBase::dieDebug(__METHOD__, 'param validation?'); } $this->addOption('LIMIT', $this->limit + 1); $count = 0; $generated = array(); $res = $this->select(__METHOD__); foreach ($res as $row) { if (++$count > $this->limit) { // We've reached the one extra which shows that there are // additional pages to be had. Stop here... if ($enumRevMode) { $this->setContinueEnumParameter('continue', intval($row->rev_id)); } elseif ($revCount > 0) { $this->setContinueEnumParameter('continue', intval($row->rev_id)); } else { $this->setContinueEnumParameter('continue', intval($row->rev_page) . '|' . intval($row->rev_id)); } break; } if ($resultPageSet !== null) { $generated[] = $row->rev_id; } else { $revision = new Revision($row); $rev = $this->extractRevisionInfo($revision, $row); if ($this->token !== null) { $title = $revision->getTitle(); $tokenFunctions = $this->getTokenFunctions(); foreach ($this->token as $t) { $val = call_user_func($tokenFunctions[$t], $title->getArticleID(), $title, $revision); if ($val === false) { $this->setWarning("Action '{$t}' is not allowed for the current user"); } else { $rev[$t . 'token'] = $val; } } } $fit = $this->addPageSubItem($row->rev_page, $rev, 'rev'); if (!$fit) { if ($enumRevMode) { $this->setContinueEnumParameter('continue', intval($row->rev_id)); } elseif ($revCount > 0) { $this->setContinueEnumParameter('continue', intval($row->rev_id)); } else { $this->setContinueEnumParameter('continue', intval($row->rev_page) . '|' . intval($row->rev_id)); } break; } } } if ($resultPageSet !== null) { $resultPageSet->populateFromRevisionIDs($generated); } }
/** * @param IDatabase $db * @return mixed */ public function doQuery($db) { $conds = ['rev_page' => $this->title->getArticleID()]; if ($this->ids !== null) { $conds['rev_id'] = array_map('intval', $this->ids); } return $db->select(['revision', 'page', 'user'], array_merge(Revision::selectFields(), Revision::selectUserFields()), $conds, __METHOD__, ['ORDER BY' => 'rev_id DESC'], ['page' => Revision::pageJoinCond(), 'user' => Revision::userJoinCond()]); }
function getQueryInfo() { $queryInfo = array('tables' => array('revision', 'user'), 'fields' => array_merge(Revision::selectFields(), Revision::selectUserFields()), 'conds' => array_merge(array('rev_page' => $this->getWikiPage()->getId()), $this->conds), 'options' => array('USE INDEX' => array('revision' => 'page_timestamp')), 'join_conds' => array('user' => Revision::userJoinCond())); ChangeTags::modifyDisplayQuery($queryInfo['tables'], $queryInfo['fields'], $queryInfo['conds'], $queryInfo['join_conds'], $queryInfo['options'], $this->tagFilter); wfRunHooks('PageHistoryPager::getQueryInfo', array(&$this, &$queryInfo)); return $queryInfo; }
function getQueryInfo() { list($tables, $index, $userCond, $join_cond) = $this->getUserCond(); $user = $this->getUser(); $conds = array_merge($userCond, $this->getNamespaceCond()); // Paranoia: avoid brute force searches (bug 17342) if (!$user->isAllowed('deletedhistory')) { $conds[] = $this->mDb->bitAnd('rev_deleted', Revision::DELETED_USER) . ' = 0'; } elseif (!$user->isAllowed('suppressrevision')) { $conds[] = $this->mDb->bitAnd('rev_deleted', Revision::SUPPRESSED_USER) . ' != ' . Revision::SUPPRESSED_USER; } # Don't include orphaned revisions $join_cond['page'] = Revision::pageJoinCond(); # Get the current user name for accounts $join_cond['user'] = Revision::userJoinCond(); $queryInfo = array('tables' => $tables, 'fields' => array_merge(Revision::selectFields(), Revision::selectUserFields(), array('page_namespace', 'page_title', 'page_is_new', 'page_latest', 'page_is_redirect', 'page_len')), 'conds' => $conds, 'options' => array('USE INDEX' => array('revision' => $index)), 'join_conds' => $join_cond); ChangeTags::modifyDisplayQuery($queryInfo['tables'], $queryInfo['fields'], $queryInfo['conds'], $queryInfo['join_conds'], $queryInfo['options'], $this->tagFilter); wfRunHooks('ContribsPager::getQueryInfo', array(&$this, &$queryInfo)); return $queryInfo; }
public function execute() { $params = $this->extractRequestParams(false); // If any of those parameters are used, work in 'enumeration' mode. // Enum mode can only be used when exactly one page is provided. // Enumerating revisions on multiple pages make it extremely // difficult to manage continuations and require additional SQL indexes $enumRevMode = !is_null($params['user']) || !is_null($params['excludeuser']) || !is_null($params['limit']) || !is_null($params['startid']) || !is_null($params['endid']) || $params['dir'] === 'newer' || !is_null($params['start']) || !is_null($params['end']); $pageSet = $this->getPageSet(); $pageCount = $pageSet->getGoodTitleCount(); $revCount = $pageSet->getRevisionCount(); // Optimization -- nothing to do if ($revCount === 0 && $pageCount === 0) { return; } if ($revCount > 0 && $enumRevMode) { $this->dieUsage('The revids= parameter may not be used with the list options (limit, startid, endid, dirNewer, start, end).', 'revids'); } if ($pageCount > 1 && $enumRevMode) { $this->dieUsage('titles, pageids or a generator was used to supply multiple pages, but the limit, startid, endid, dirNewer, user, excludeuser, start and end parameters may only be used on a single page.', 'multpages'); } if (!is_null($params['difftotext'])) { $this->difftotext = $params['difftotext']; } elseif (!is_null($params['diffto'])) { if ($params['diffto'] == 'cur') { $params['diffto'] = 0; } if ((!ctype_digit($params['diffto']) || $params['diffto'] < 0) && $params['diffto'] != 'prev' && $params['diffto'] != 'next') { $this->dieUsage('rvdiffto must be set to a non-negative number, "prev", "next" or "cur"', 'diffto'); } // Check whether the revision exists and is readable, // DifferenceEngine returns a rather ambiguous empty // string if that's not the case if ($params['diffto'] != 0) { $difftoRev = Revision::newFromID($params['diffto']); if (!$difftoRev) { $this->dieUsageMsg(array('nosuchrevid', $params['diffto'])); } if ($difftoRev->isDeleted(Revision::DELETED_TEXT)) { $this->setWarning("Couldn't diff to r{$difftoRev->getID()}: content is hidden"); $params['diffto'] = null; } } $this->diffto = $params['diffto']; } $db = $this->getDB(); $this->addTables('page'); $this->addFields(Revision::selectFields()); $this->addWhere('page_id = rev_page'); $prop = array_flip($params['prop']); // Optional fields $this->fld_ids = isset($prop['ids']); // $this->addFieldsIf('rev_text_id', $this->fld_ids); // should this be exposed? $this->fld_flags = isset($prop['flags']); $this->fld_timestamp = isset($prop['timestamp']); $this->fld_comment = isset($prop['comment']); $this->fld_parsedcomment = isset($prop['parsedcomment']); $this->fld_size = isset($prop['size']); $this->fld_sha1 = isset($prop['sha1']); $this->fld_userid = isset($prop['userid']); $this->fld_user = isset($prop['user']); $this->token = $params['token']; // Possible indexes used $index = array(); $userMax = $this->fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1; $botMax = $this->fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2; $limit = $params['limit']; if ($limit == 'max') { $limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax; $this->getResult()->setParsedLimit($this->getModuleName(), $limit); } if (!is_null($this->token) || $pageCount > 0) { $this->addFields(Revision::selectPageFields()); } if (isset($prop['tags'])) { $this->fld_tags = true; $this->addTables('tag_summary'); $this->addJoinConds(array('tag_summary' => array('LEFT JOIN', array('rev_id=ts_rev_id')))); $this->addFields('ts_tags'); } if (!is_null($params['tag'])) { $this->addTables('change_tag'); $this->addJoinConds(array('change_tag' => array('INNER JOIN', array('rev_id=ct_rev_id')))); $this->addWhereFld('ct_tag', $params['tag']); global $wgOldChangeTagsIndex; $index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id'; } if (isset($prop['content']) || !is_null($this->difftotext)) { // For each page we will request, the user must have read rights for that page foreach ($pageSet->getGoodTitles() as $title) { if (!$title->userCan('read')) { $this->dieUsage('The current user is not allowed to read ' . $title->getPrefixedText(), 'accessdenied'); } } $this->addTables('text'); $this->addWhere('rev_text_id=old_id'); $this->addFields('old_id'); $this->addFields(Revision::selectTextFields()); $this->fld_content = isset($prop['content']); $this->expandTemplates = $params['expandtemplates']; $this->generateXML = $params['generatexml']; $this->parseContent = $params['parse']; if ($this->parseContent) { // Must manually initialize unset limit if (is_null($limit)) { $limit = 1; } // We are only going to parse 1 revision per request $this->validateLimit('limit', $limit, 1, 1, 1); } if (isset($params['section'])) { $this->section = $params['section']; } else { $this->section = false; } } // add user name, if needed if ($this->fld_user) { $this->addTables('user'); $this->addJoinConds(array('user' => Revision::userJoinCond())); $this->addFields(Revision::selectUserFields()); } // Bug 24166 - API error when using rvprop=tags $this->addTables('revision'); if ($enumRevMode) { // This is mostly to prevent parameter errors (and optimize SQL?) if (!is_null($params['startid']) && !is_null($params['start'])) { $this->dieUsage('start and startid cannot be used together', 'badparams'); } if (!is_null($params['endid']) && !is_null($params['end'])) { $this->dieUsage('end and endid cannot be used together', 'badparams'); } if (!is_null($params['user']) && !is_null($params['excludeuser'])) { $this->dieUsage('user and excludeuser cannot be used together', 'badparams'); } // Continuing effectively uses startid. But we can't use rvstartid // directly, because there is no way to tell the client to ''not'' // send rvstart if it sent it in the original query. So instead we // send the continuation startid as rvcontinue, and ignore both // rvstart and rvstartid when that is supplied. if (!is_null($params['continue'])) { $params['startid'] = $params['continue']; unset($params['start']); } // This code makes an assumption that sorting by rev_id and rev_timestamp produces // the same result. This way users may request revisions starting at a given time, // but to page through results use the rev_id returned after each page. // Switching to rev_id removes the potential problem of having more than // one row with the same timestamp for the same page. // The order needs to be the same as start parameter to avoid SQL filesort. if (is_null($params['startid']) && is_null($params['endid'])) { $this->addTimestampWhereRange('rev_timestamp', $params['dir'], $params['start'], $params['end']); } else { $this->addWhereRange('rev_id', $params['dir'], $params['startid'], $params['endid']); // One of start and end can be set // If neither is set, this does nothing $this->addTimestampWhereRange('rev_timestamp', $params['dir'], $params['start'], $params['end'], false); } // must manually initialize unset limit if (is_null($limit)) { $limit = 10; } $this->validateLimit('limit', $limit, 1, $userMax, $botMax); // There is only one ID, use it $ids = array_keys($pageSet->getGoodTitles()); $this->addWhereFld('rev_page', reset($ids)); if (!is_null($params['user'])) { $this->addWhereFld('rev_user_text', $params['user']); } elseif (!is_null($params['excludeuser'])) { $this->addWhere('rev_user_text != ' . $db->addQuotes($params['excludeuser'])); } if (!is_null($params['user']) || !is_null($params['excludeuser'])) { // Paranoia: avoid brute force searches (bug 17342) $this->addWhere($db->bitAnd('rev_deleted', Revision::DELETED_USER) . ' = 0'); } } elseif ($revCount > 0) { $max = $this->getMain()->canApiHighLimits() ? $botMax : $userMax; $revs = $pageSet->getRevisionIDs(); if (self::truncateArray($revs, $max)) { $this->setWarning("Too many values supplied for parameter 'revids': the limit is {$max}"); } // Get all revision IDs $this->addWhereFld('rev_id', array_keys($revs)); if (!is_null($params['continue'])) { $this->addWhere('rev_id >= ' . intval($params['continue'])); } $this->addOption('ORDER BY', 'rev_id'); // assumption testing -- we should never get more then $revCount rows. $limit = $revCount; } elseif ($pageCount > 0) { $max = $this->getMain()->canApiHighLimits() ? $botMax : $userMax; $titles = $pageSet->getGoodTitles(); if (self::truncateArray($titles, $max)) { $this->setWarning("Too many values supplied for parameter 'titles': the limit is {$max}"); } // When working in multi-page non-enumeration mode, // limit to the latest revision only $this->addWhere('page_id=rev_page'); $this->addWhere('page_latest=rev_id'); // Get all page IDs $this->addWhereFld('page_id', array_keys($titles)); // Every time someone relies on equality propagation, god kills a kitten :) $this->addWhereFld('rev_page', array_keys($titles)); if (!is_null($params['continue'])) { $cont = explode('|', $params['continue']); if (count($cont) != 2) { $this->dieUsage('Invalid continue param. You should pass the original ' . 'value returned by the previous query', '_badcontinue'); } $pageid = intval($cont[0]); $revid = intval($cont[1]); $this->addWhere("rev_page > {$pageid} OR " . "(rev_page = {$pageid} AND " . "rev_id >= {$revid})"); } $this->addOption('ORDER BY', array('rev_page', 'rev_id')); // assumption testing -- we should never get more then $pageCount rows. $limit = $pageCount; } else { ApiBase::dieDebug(__METHOD__, 'param validation?'); } $this->addOption('LIMIT', $limit + 1); $this->addOption('USE INDEX', $index); $count = 0; $res = $this->select(__METHOD__); foreach ($res as $row) { if (++$count > $limit) { // We've reached the one extra which shows that there are additional pages to be had. Stop here... if (!$enumRevMode) { ApiBase::dieDebug(__METHOD__, 'Got more rows then expected'); // bug report } $this->setContinueEnumParameter('continue', intval($row->rev_id)); break; } $fit = $this->addPageSubItem($row->rev_page, $this->extractRowInfo($row), 'rev'); if (!$fit) { if ($enumRevMode) { $this->setContinueEnumParameter('continue', intval($row->rev_id)); } elseif ($revCount > 0) { $this->setContinueEnumParameter('continue', intval($row->rev_id)); } else { $this->setContinueEnumParameter('continue', intval($row->rev_page) . '|' . intval($row->rev_id)); } break; } } }
function getQueryInfo() { $conds = $this->mConds; $conds['rev_page'] = $this->articleID; $conds[] = "rev_timestamp < {$this->maxTimestamp}"; return array( 'tables' => array( 'revision', 'page', 'user' ), 'fields' => array_merge( Revision::selectFields(), Revision::selectUserFields() ), 'conds' => $conds, 'join_conds' => array( 'page' => Revision::pageJoinCond(), 'user' => Revision::userJoinCond() ) ); }
protected function run(ApiPageSet $resultPageSet = null) { $params = $this->extractRequestParams(false); // If any of those parameters are used, work in 'enumeration' mode. // Enum mode can only be used when exactly one page is provided. // Enumerating revisions on multiple pages make it extremely // difficult to manage continuations and require additional SQL indexes $enumRevMode = $params['user'] !== null || $params['excludeuser'] !== null || $params['limit'] !== null || $params['startid'] !== null || $params['endid'] !== null || $params['dir'] === 'newer' || $params['start'] !== null || $params['end'] !== null; $pageSet = $this->getPageSet(); $pageCount = $pageSet->getGoodTitleCount(); $revCount = $pageSet->getRevisionCount(); // Optimization -- nothing to do if ($revCount === 0 && $pageCount === 0) { // Nothing to do return; } if ($revCount > 0 && count($pageSet->getLiveRevisionIDs()) === 0) { // We're in revisions mode but all given revisions are deleted return; } if ($revCount > 0 && $enumRevMode) { $this->dieUsage('The revids= parameter may not be used with the list options ' . '(limit, startid, endid, dirNewer, start, end).', 'revids'); } if ($pageCount > 1 && $enumRevMode) { $this->dieUsage('titles, pageids or a generator was used to supply multiple pages, ' . 'but the limit, startid, endid, dirNewer, user, excludeuser, start ' . 'and end parameters may only be used on a single page.', 'multpages'); } // In non-enum mode, rvlimit can't be directly used. Use the maximum // allowed value. if (!$enumRevMode) { $this->setParsedLimit = false; $params['limit'] = 'max'; } $db = $this->getDB(); $this->addTables(['revision', 'page']); $this->addJoinConds(['page' => ['INNER JOIN', ['page_id = rev_page']]]); if ($resultPageSet === null) { $this->parseParameters($params); $this->token = $params['token']; $this->addFields(Revision::selectFields()); if ($this->token !== null || $pageCount > 0) { $this->addFields(Revision::selectPageFields()); } } else { $this->limit = $this->getParameter('limit') ?: 10; $this->addFields(['rev_id', 'rev_timestamp', 'rev_page']); } if ($this->fld_tags) { $this->addTables('tag_summary'); $this->addJoinConds(['tag_summary' => ['LEFT JOIN', ['rev_id=ts_rev_id']]]); $this->addFields('ts_tags'); } if ($params['tag'] !== null) { $this->addTables('change_tag'); $this->addJoinConds(['change_tag' => ['INNER JOIN', ['rev_id=ct_rev_id']]]); $this->addWhereFld('ct_tag', $params['tag']); } if ($this->fetchContent) { // For each page we will request, the user must have read rights for that page $user = $this->getUser(); /** @var $title Title */ foreach ($pageSet->getGoodTitles() as $title) { if (!$title->userCan('read', $user)) { $this->dieUsage('The current user is not allowed to read ' . $title->getPrefixedText(), 'accessdenied'); } } $this->addTables('text'); $this->addJoinConds(['text' => ['INNER JOIN', ['rev_text_id=old_id']]]); $this->addFields('old_id'); $this->addFields(Revision::selectTextFields()); } // add user name, if needed if ($this->fld_user) { $this->addTables('user'); $this->addJoinConds(['user' => Revision::userJoinCond()]); $this->addFields(Revision::selectUserFields()); } if ($enumRevMode) { // Indexes targeted: // page_timestamp if we don't have rvuser // page_user_timestamp if we have a logged-in rvuser // page_timestamp or usertext_timestamp if we have an IP rvuser // This is mostly to prevent parameter errors (and optimize SQL?) if ($params['startid'] !== null && $params['start'] !== null) { $this->dieUsage('start and startid cannot be used together', 'badparams'); } if ($params['endid'] !== null && $params['end'] !== null) { $this->dieUsage('end and endid cannot be used together', 'badparams'); } if ($params['user'] !== null && $params['excludeuser'] !== null) { $this->dieUsage('user and excludeuser cannot be used together', 'badparams'); } if ($params['continue'] !== null) { $cont = explode('|', $params['continue']); $this->dieContinueUsageIf(count($cont) != 2); $op = $params['dir'] === 'newer' ? '>' : '<'; $continueTimestamp = $db->addQuotes($db->timestamp($cont[0])); $continueId = (int) $cont[1]; $this->dieContinueUsageIf($continueId != $cont[1]); $this->addWhere("rev_timestamp {$op} {$continueTimestamp} OR " . "(rev_timestamp = {$continueTimestamp} AND " . "rev_id {$op}= {$continueId})"); } $this->addTimestampWhereRange('rev_timestamp', $params['dir'], $params['start'], $params['end']); $this->addWhereRange('rev_id', $params['dir'], $params['startid'], $params['endid']); // There is only one ID, use it $ids = array_keys($pageSet->getGoodTitles()); $this->addWhereFld('rev_page', reset($ids)); if ($params['user'] !== null) { $user = User::newFromName($params['user']); if ($user && $user->getId() > 0) { $this->addWhereFld('rev_user', $user->getId()); } else { $this->addWhereFld('rev_user_text', $params['user']); } } elseif ($params['excludeuser'] !== null) { $user = User::newFromName($params['excludeuser']); if ($user && $user->getId() > 0) { $this->addWhere('rev_user != ' . $user->getId()); } else { $this->addWhere('rev_user_text != ' . $db->addQuotes($params['excludeuser'])); } } if ($params['user'] !== null || $params['excludeuser'] !== null) { // Paranoia: avoid brute force searches (bug 17342) if (!$this->getUser()->isAllowed('deletedhistory')) { $bitmask = Revision::DELETED_USER; } elseif (!$this->getUser()->isAllowedAny('suppressrevision', 'viewsuppressed')) { $bitmask = Revision::DELETED_USER | Revision::DELETED_RESTRICTED; } else { $bitmask = 0; } if ($bitmask) { $this->addWhere($db->bitAnd('rev_deleted', $bitmask) . " != {$bitmask}"); } } } elseif ($revCount > 0) { // Always targets the PRIMARY index $revs = $pageSet->getLiveRevisionIDs(); // Get all revision IDs $this->addWhereFld('rev_id', array_keys($revs)); if ($params['continue'] !== null) { $this->addWhere('rev_id >= ' . intval($params['continue'])); } $this->addOption('ORDER BY', 'rev_id'); } elseif ($pageCount > 0) { // Always targets the rev_page_id index $titles = $pageSet->getGoodTitles(); // When working in multi-page non-enumeration mode, // limit to the latest revision only $this->addWhere('page_latest=rev_id'); // Get all page IDs $this->addWhereFld('page_id', array_keys($titles)); // Every time someone relies on equality propagation, god kills a kitten :) $this->addWhereFld('rev_page', array_keys($titles)); if ($params['continue'] !== null) { $cont = explode('|', $params['continue']); $this->dieContinueUsageIf(count($cont) != 2); $pageid = intval($cont[0]); $revid = intval($cont[1]); $this->addWhere("rev_page > {$pageid} OR " . "(rev_page = {$pageid} AND " . "rev_id >= {$revid})"); } $this->addOption('ORDER BY', ['rev_page', 'rev_id']); } else { ApiBase::dieDebug(__METHOD__, 'param validation?'); } $this->addOption('LIMIT', $this->limit + 1); $count = 0; $generated = []; $hookData = []; $res = $this->select(__METHOD__, [], $hookData); foreach ($res as $row) { if (++$count > $this->limit) { // We've reached the one extra which shows that there are // additional pages to be had. Stop here... if ($enumRevMode) { $this->setContinueEnumParameter('continue', $row->rev_timestamp . '|' . intval($row->rev_id)); } elseif ($revCount > 0) { $this->setContinueEnumParameter('continue', intval($row->rev_id)); } else { $this->setContinueEnumParameter('continue', intval($row->rev_page) . '|' . intval($row->rev_id)); } break; } if ($resultPageSet !== null) { $generated[] = $row->rev_id; } else { $revision = new Revision($row); $rev = $this->extractRevisionInfo($revision, $row); if ($this->token !== null) { $title = $revision->getTitle(); $tokenFunctions = $this->getTokenFunctions(); foreach ($this->token as $t) { $val = call_user_func($tokenFunctions[$t], $title->getArticleID(), $title, $revision); if ($val === false) { $this->setWarning("Action '{$t}' is not allowed for the current user"); } else { $rev[$t . 'token'] = $val; } } } $fit = $this->processRow($row, $rev, $hookData) && $this->addPageSubItem($row->rev_page, $rev, 'rev'); if (!$fit) { if ($enumRevMode) { $this->setContinueEnumParameter('continue', $row->rev_timestamp . '|' . intval($row->rev_id)); } elseif ($revCount > 0) { $this->setContinueEnumParameter('continue', intval($row->rev_id)); } else { $this->setContinueEnumParameter('continue', intval($row->rev_page) . '|' . intval($row->rev_id)); } break; } } } if ($resultPageSet !== null) { $resultPageSet->populateFromRevisionIDs($generated); } }