/** * Populates the search index with content from all pages */ protected function populateSearchIndex() { $res = $this->db->select('page', 'MAX(page_id) AS count'); $s = $this->db->fetchObject($res); $count = $s->count; $this->output("Rebuilding index fields for {$count} pages...\n"); $n = 0; $fields = array_merge(Revision::selectPageFields(), Revision::selectFields(), Revision::selectTextFields()); while ($n < $count) { if ($n) { $this->output($n . "\n"); } $end = $n + self::RTI_CHUNK_SIZE - 1; $res = $this->db->select(['page', 'revision', 'text'], $fields, ["page_id BETWEEN {$n} AND {$end}", 'page_latest = rev_id', 'rev_text_id = old_id'], __METHOD__); foreach ($res as $s) { try { $title = Title::makeTitle($s->page_namespace, $s->page_title); $rev = new Revision($s); $content = $rev->getContent(); $u = new SearchUpdate($s->page_id, $title, $content); $u->doUpdate(); } catch (MWContentSerializationException $ex) { $this->output("Failed to deserialize content of revision {$s->rev_id} of page " . "`" . $title->getPrefixedDBkey() . "`!\n"); } } $n += self::RTI_CHUNK_SIZE; } }
/** * @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)); } }
/** * @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)); } }
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()]]; }
/** * @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']); }
/** * @covers Revision::selectFields */ public function testSelectFields() { $fields = Revision::selectFields(); $this->assertTrue(in_array('rev_id', $fields), 'missing rev_id in list of fields'); $this->assertTrue(in_array('rev_page', $fields), 'missing rev_page in list of fields'); $this->assertTrue(in_array('rev_timestamp', $fields), 'missing rev_timestamp in list of fields'); $this->assertTrue(in_array('rev_user', $fields), 'missing rev_user in list of fields'); $this->assertFalse(in_array('rev_content_model', $fields), 'missing rev_content_model in list of fields'); $this->assertFalse(in_array('rev_content_format', $fields), 'missing rev_content_format in list of fields'); }
public function execute() { $db = wfGetDB(DB_MASTER); if (!$db->tableExists('revision')) { $this->error("revision table does not exist", true); } $this->output("Populating rev_len column\n"); $start = $db->selectField('revision', 'MIN(rev_id)', false, __FUNCTION__); $end = $db->selectField('revision', 'MAX(rev_id)', false, __FUNCTION__); if (is_null($start) || is_null($end)) { $this->output("...revision table seems to be empty.\n"); $db->insert('updatelog', array('ul_key' => 'populate rev_len'), __METHOD__, 'IGNORE'); return; } # Do remaining chunks $blockStart = intval($start); $blockEnd = intval($start) + $this->mBatchSize - 1; $count = 0; $missing = 0; while ($blockStart <= $end) { $this->output("...doing rev_id from {$blockStart} to {$blockEnd}\n"); $res = $db->select('revision', Revision::selectFields(), array("rev_id >= {$blockStart}", "rev_id <= {$blockEnd}", "rev_len IS NULL"), __METHOD__); # Go through and update rev_len from these rows. foreach ($res as $row) { $rev = new Revision($row); $text = $rev->getRawText(); if (!is_string($text)) { # This should not happen, but sometimes does (bug 20757) $this->output("Text of revision {$row->rev_id} unavailable!\n"); $missing++; } else { # Update the row... $db->update('revision', array('rev_len' => strlen($text)), array('rev_id' => $row->rev_id), __METHOD__); $count++; } } $blockStart += $this->mBatchSize; $blockEnd += $this->mBatchSize; wfWaitForSlaves(5); } $logged = $db->insert('updatelog', array('ul_key' => 'populate rev_len'), __METHOD__, 'IGNORE'); if ($logged) { $this->output("rev_len population complete ... {$count} rows changed ({$missing} missing)\n"); return true; } else { $this->output("Could not insert rev_len population row.\n"); return false; } }
public function doDBUpdates() { $db = $this->getDB(DB_MASTER); if (!$db->tableExists('revision')) { $this->error("revision table does not exist", true); } else { if (!$db->fieldExists('revision', 'rev_sha1', __METHOD__)) { $this->output("rev_sha1 column does not exist\n\n", true); return false; } } $this->output("Populating rev_len column\n"); $start = $db->selectField('revision', 'MIN(rev_id)', false, __METHOD__); $end = $db->selectField('revision', 'MAX(rev_id)', false, __METHOD__); if (!$start || !$end) { $this->output("...revision table seems to be empty.\n"); return true; } # Do remaining chunks $blockStart = intval($start); $blockEnd = intval($start) + $this->mBatchSize - 1; $count = 0; $missing = 0; $fields = Revision::selectFields(); while ($blockStart <= $end) { $this->output("...doing rev_id from {$blockStart} to {$blockEnd}\n"); $res = $db->select('revision', $fields, array("rev_id >= {$blockStart}", "rev_id <= {$blockEnd}", "rev_len IS NULL"), __METHOD__); # Go through and update rev_len from these rows. foreach ($res as $row) { $rev = new Revision($row); $content = $rev->getContent(); if (!$content) { # This should not happen, but sometimes does (bug 20757) $this->output("Content of revision {$row->rev_id} unavailable!\n"); $missing++; } else { # Update the row... $db->update('revision', array('rev_len' => $content->getSize()), array('rev_id' => $row->rev_id), __METHOD__); $count++; } } $blockStart += $this->mBatchSize; $blockEnd += $this->mBatchSize; wfWaitForSlaves(); } $this->output("rev_len population complete ... {$count} rows changed ({$missing} missing)\n"); return true; }
public function execute() { global $wgUser; $dbr = wfGetDB(DB_SLAVE); $ret = $dbr->select(array('flaggedpages', 'revision', 'page'), array_merge(Revision::selectFields(), array($dbr->tableName('page') . '.*')), array('fp_pending_since IS NOT NULL', 'page_id = fp_page_id', 'rev_page = fp_page_id', 'rev_timestamp >= fp_pending_since'), __METHOD__, array('ORDER BY' => 'fp_pending_since DESC')); foreach ($ret as $row) { $title = Title::newFromRow($row); $article = new Article($title); $rev = new Revision($row); // Trigger cache regeneration $start = microtime(true); FRInclusionCache::getRevIncludes($article, $rev, $wgUser, 'regen'); $elapsed = intval((microtime(true) - $start) * 1000); $this->cachePendingRevsLog($title->getPrefixedDBkey() . " rev:" . $rev->getId() . " {$elapsed}ms"); } }
public function run() { $page = WikiPage::newFromID($this->params['pageId'], WikiPage::READ_LATEST); if (!$page) { $this->setLastError("Could not find page #{$this->params['pageId']}"); return false; // deleted? } $dbw = wfGetDB(DB_MASTER); // Use a named lock so that jobs for this page see each others' changes $lockKey = "CategoryMembershipUpdates:{$page->getId()}"; $scopedLock = $dbw->getScopedLockAndFlush($lockKey, __METHOD__, 10); if (!$scopedLock) { $this->setLastError("Could not acquire lock '{$lockKey}'"); return false; } $dbr = wfGetDB(DB_SLAVE, ['recentchanges']); // Wait till the slave is caught up so that jobs for this page see each others' changes if (!wfGetLB()->safeWaitForMasterPos($dbr)) { $this->setLastError("Timed out while waiting for slave to catch up"); return false; } // Clear any stale REPEATABLE-READ snapshot $dbr->commit(__METHOD__, 'flush'); $cutoffUnix = wfTimestamp(TS_UNIX, $this->params['revTimestamp']); // Using ENQUEUE_FUDGE_SEC handles jobs inserted out of revision order due to the delay // between COMMIT and actual enqueueing of the CategoryMembershipChangeJob job. $cutoffUnix -= self::ENQUEUE_FUDGE_SEC; // Get the newest revision that has a SRC_CATEGORIZE row... $row = $dbr->selectRow(['revision', 'recentchanges'], ['rev_timestamp', 'rev_id'], ['rev_page' => $page->getId(), 'rev_timestamp >= ' . $dbr->addQuotes($dbr->timestamp($cutoffUnix))], __METHOD__, ['ORDER BY' => 'rev_timestamp DESC, rev_id DESC'], ['recentchanges' => ['INNER JOIN', ['rc_this_oldid = rev_id', 'rc_source' => RecentChange::SRC_CATEGORIZE, 'rc_cur_id = rev_page', 'rc_timestamp >= rev_timestamp']]]); // Only consider revisions newer than any such revision if ($row) { $cutoffUnix = wfTimestamp(TS_UNIX, $row->rev_timestamp); $lastRevId = (int) $row->rev_id; } else { $lastRevId = 0; } // Find revisions to this page made around and after this revision which lack category // notifications in recent changes. This lets jobs pick up were the last one left off. $encCutoff = $dbr->addQuotes($dbr->timestamp($cutoffUnix)); $res = $dbr->select('revision', Revision::selectFields(), ['rev_page' => $page->getId(), "rev_timestamp > {$encCutoff}" . " OR (rev_timestamp = {$encCutoff} AND rev_id > {$lastRevId})"], __METHOD__, ['ORDER BY' => 'rev_timestamp ASC, rev_id ASC']); // Apply all category updates in revision timestamp order foreach ($res as $row) { $this->notifyUpdatesForRevision($page, Revision::newFromRow($row)); } return true; }
public function run() { $page = WikiPage::newFromID($this->params['pageId'], WikiPage::READ_LATEST); if (!$page) { $this->setLastError("Could not find page #{$this->params['pageId']}"); return false; // deleted? } $dbw = wfGetDB(DB_MASTER); // Use a named lock so that jobs for this page see each others' changes $fname = __METHOD__; $lockKey = "CategoryMembershipUpdates:{$page->getId()}"; if (!$dbw->lock($lockKey, $fname, 10)) { $this->setLastError("Could not acquire lock '{$lockKey}'"); return false; } $unlocker = new ScopedCallback(function () use($dbw, $lockKey, $fname) { $dbw->unlock($lockKey, $fname); }); // Sanity: clear any DB transaction snapshot $dbw->commit(__METHOD__, 'flush'); $cutoffUnix = wfTimestamp(TS_UNIX, $this->params['revTimestamp']); // Using ENQUEUE_FUDGE_SEC handles jobs inserted out of revision order due to the delay // between COMMIT and actual enqueueing of the CategoryMembershipChangeJob job. $cutoffUnix -= self::ENQUEUE_FUDGE_SEC; // Get the newest revision that has a SRC_CATEGORIZE row... $row = $dbw->selectRow(array('revision', 'recentchanges'), array('rev_timestamp', 'rev_id'), array('rev_page' => $page->getId(), 'rev_timestamp >= ' . $dbw->addQuotes($dbw->timestamp($cutoffUnix))), __METHOD__, array('ORDER BY' => 'rev_timestamp DESC, rev_id DESC'), array('recentchanges' => array('INNER JOIN', array('rc_this_oldid = rev_id', 'rc_source' => RecentChange::SRC_CATEGORIZE, 'rc_cur_id = rev_page', 'rc_timestamp >= rev_timestamp')))); // Only consider revisions newer than any such revision if ($row) { $cutoffUnix = wfTimestamp(TS_UNIX, $row->rev_timestamp); $lastRevId = (int) $row->rev_id; } else { $lastRevId = 0; } // Find revisions to this page made around and after this revision which lack category // notifications in recent changes. This lets jobs pick up were the last one left off. $encCutoff = $dbw->addQuotes($dbw->timestamp($cutoffUnix)); $res = $dbw->select('revision', Revision::selectFields(), array('rev_page' => $page->getId(), "rev_timestamp > {$encCutoff}" . " OR (rev_timestamp = {$encCutoff} AND rev_id > {$lastRevId})"), __METHOD__, array('ORDER BY' => 'rev_timestamp ASC, rev_id ASC')); // Apply all category updates in revision timestamp order foreach ($res as $row) { $this->notifyUpdatesForRevision($page, Revision::newFromRow($row)); } ScopedCallback::consume($unlocker); return true; }
public function doDBUpdates() { $db = $this->getDB(DB_MASTER); if (!$db->tableExists('revision')) { $this->error("revision table does not exist", true); } elseif (!$db->tableExists('archive')) { $this->error("archive table does not exist", true); } elseif (!$db->fieldExists('revision', 'rev_len', __METHOD__)) { $this->output("rev_len column does not exist\n\n", true); return false; } $this->output("Populating rev_len column\n"); $rev = $this->doLenUpdates('revision', 'rev_id', 'rev', Revision::selectFields()); $this->output("Populating ar_len column\n"); $ar = $this->doLenUpdates('archive', 'ar_id', 'ar', Revision::selectArchiveFields()); $this->output("rev_len and ar_len population complete [{$rev} revision rows, {$ar} archive rows].\n"); return true; }
function EditOwn($title, $user, $action, &$result) { static $cache = array(); global $wgEditOwnExcludedNamespaces, $wgEditOwnActions; if (!is_array($wgEditOwnExcludedNamespaces)) { // Prevent PHP from whining $wgEditOwnExcludedNamespaces = array(); } if (!in_array($action, $wgEditOwnActions) || $user->isAllowed('editall') || in_array($title->getNamespace(), $wgEditOwnExcludedNamespaces)) { $result = null; return true; } if (isset($cache[$user->getName()][$title->getArticleId()])) { $result = $cache[$user->getName()][$title->getArticleId()]; return is_null($result); } if (!$title->exists()) { // Creation is allowed $cache[$user->getName()][$title->getArticleId()] = null; $result = null; return true; } // Since there's no easy way to get the first revision, // we'll just do a DB query $dbr = wfGetDb(DB_SLAVE); $res = $dbr->select('revision', Revision::selectFields(), array('rev_page' => $title->getArticleId()), __METHOD__, array('ORDER BY' => 'rev_timestamp', 'LIMIT' => 1)); $row = $dbr->fetchObject($res); if (!$row) { // Title with no revs, weird... allow creation $cache[$user->getName()][$title->getArticleId()] = null; $result = null; return true; } $rev = new Revision($row); if ($user->getName() == $rev->getRawUserText()) { $cache[$user->getName()][$title->getArticleId()] = null; $result = null; return true; } $cache[$user->getName()][$title->getArticleId()] = false; $result = false; return false; }
/** * @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()]); }
/** * Back-end article deletion * Deletes the article with database consistency, writes logs, purges caches * * @since 1.19 * * @param string $reason Delete reason for deletion log * @param bool $suppress Suppress all revisions and log the deletion in * the suppression log instead of the deletion log * @param int $u1 Unused * @param bool $u2 Unused * @param array|string &$error Array of errors to append to * @param User $user The deleting user * @param array $tags Tags to apply to the deletion action * @return Status Status object; if successful, $status->value is the log_id of the * deletion log entry. If the page couldn't be deleted because it wasn't * found, $status is a non-fatal 'cannotdelete' error */ public function doDeleteArticleReal($reason, $suppress = false, $u1 = null, $u2 = null, &$error = '', User $user = null, $tags = []) { global $wgUser, $wgContentHandlerUseDB; wfDebug(__METHOD__ . "\n"); $status = Status::newGood(); if ($this->mTitle->getDBkey() === '') { $status->error('cannotdelete', wfEscapeWikiText($this->getTitle()->getPrefixedText())); return $status; } $user = is_null($user) ? $wgUser : $user; if (!Hooks::run('ArticleDelete', [&$this, &$user, &$reason, &$error, &$status, $suppress])) { if ($status->isOK()) { // Hook aborted but didn't set a fatal status $status->fatal('delete-hook-aborted'); } return $status; } $dbw = wfGetDB(DB_MASTER); $dbw->startAtomic(__METHOD__); $this->loadPageData(self::READ_LATEST); $id = $this->getId(); // T98706: lock the page from various other updates but avoid using // WikiPage::READ_LOCKING as that will carry over the FOR UPDATE to // the revisions queries (which also JOIN on user). Only lock the page // row and CAS check on page_latest to see if the trx snapshot matches. $lockedLatest = $this->lockAndGetLatest(); if ($id == 0 || $this->getLatest() != $lockedLatest) { $dbw->endAtomic(__METHOD__); // Page not there or trx snapshot is stale $status->error('cannotdelete', wfEscapeWikiText($this->getTitle()->getPrefixedText())); return $status; } // Given the lock above, we can be confident in the title and page ID values $namespace = $this->getTitle()->getNamespace(); $dbKey = $this->getTitle()->getDBkey(); // At this point we are now comitted to returning an OK // status unless some DB query error or other exception comes up. // This way callers don't have to call rollback() if $status is bad // unless they actually try to catch exceptions (which is rare). // we need to remember the old content so we can use it to generate all deletion updates. $revision = $this->getRevision(); try { $content = $this->getContent(Revision::RAW); } catch (Exception $ex) { wfLogWarning(__METHOD__ . ': failed to load content during deletion! ' . $ex->getMessage()); $content = null; } $fields = Revision::selectFields(); $bitfield = false; // Bitfields to further suppress the content if ($suppress) { $bitfield = Revision::SUPPRESSED_ALL; $fields = array_diff($fields, ['rev_deleted']); } // For now, shunt the revision data into the archive table. // Text is *not* removed from the text table; bulk storage // is left intact to avoid breaking block-compression or // immutable storage schemes. // In the future, we may keep revisions and mark them with // the rev_deleted field, which is reserved for this purpose. // Get all of the page revisions $res = $dbw->select('revision', $fields, ['rev_page' => $id], __METHOD__, 'FOR UPDATE'); // Build their equivalent archive rows $rowsInsert = []; foreach ($res as $row) { $rowInsert = ['ar_namespace' => $namespace, 'ar_title' => $dbKey, 'ar_comment' => $row->rev_comment, 'ar_user' => $row->rev_user, 'ar_user_text' => $row->rev_user_text, 'ar_timestamp' => $row->rev_timestamp, 'ar_minor_edit' => $row->rev_minor_edit, 'ar_rev_id' => $row->rev_id, 'ar_parent_id' => $row->rev_parent_id, 'ar_text_id' => $row->rev_text_id, 'ar_text' => '', 'ar_flags' => '', 'ar_len' => $row->rev_len, 'ar_page_id' => $id, 'ar_deleted' => $suppress ? $bitfield : $row->rev_deleted, 'ar_sha1' => $row->rev_sha1]; if ($wgContentHandlerUseDB) { $rowInsert['ar_content_model'] = $row->rev_content_model; $rowInsert['ar_content_format'] = $row->rev_content_format; } $rowsInsert[] = $rowInsert; } // Copy them into the archive table $dbw->insert('archive', $rowsInsert, __METHOD__); // Save this so we can pass it to the ArticleDeleteComplete hook. $archivedRevisionCount = $dbw->affectedRows(); // Clone the title and wikiPage, so we have the information we need when // we log and run the ArticleDeleteComplete hook. $logTitle = clone $this->mTitle; $wikiPageBeforeDelete = clone $this; // Now that it's safely backed up, delete it $dbw->delete('page', ['page_id' => $id], __METHOD__); $dbw->delete('revision', ['rev_page' => $id], __METHOD__); // Log the deletion, if the page was suppressed, put it in the suppression log instead $logtype = $suppress ? 'suppress' : 'delete'; $logEntry = new ManualLogEntry($logtype, 'delete'); $logEntry->setPerformer($user); $logEntry->setTarget($logTitle); $logEntry->setComment($reason); $logEntry->setTags($tags); $logid = $logEntry->insert(); $dbw->onTransactionPreCommitOrIdle(function () use($dbw, $logEntry, $logid) { // Bug 56776: avoid deadlocks (especially from FileDeleteForm) $logEntry->publish($logid); }, __METHOD__); $dbw->endAtomic(__METHOD__); $this->doDeleteUpdates($id, $content, $revision); Hooks::run('ArticleDeleteComplete', [&$wikiPageBeforeDelete, &$user, $reason, $id, $content, $logEntry, $archivedRevisionCount]); $status->value = $logid; // Show log excerpt on 404 pages rather than just a link $cache = ObjectCache::getMainStashInstance(); $key = wfMemcKey('page-recent-delete', md5($logTitle->getPrefixedText())); $cache->set($key, 1, $cache::TTL_DAY); return $status; }
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; }
function loadFromDB($dbname, $page_id, $rev_id) { $db = wfGetDB(DB_SLAVE, 'stats', $dbname); $fields = Revision::selectPageFields(); $fields = array_merge($fields, Revision::selectFields()); $fields[] = " date_format(rev_timestamp, '%Y-%m-%d %H:%i:%s') as ts "; $oRow = $db->selectRow(array('revision', 'page'), $fields, array("rev_page = page_id", 'page_id' => $page_id, 'rev_id' => $rev_id), __METHOD__); if (empty($oRow)) { $fields = array('ar_namespace as page_namespace', 'ar_title as page_title', 'ar_comment as rev_comment', 'ar_user as rev_user', 'ar_user_text as rev_user_text', 'ar_timestamp as rev_timestamp', 'ar_minor_edit as rev_minor_edit', 'ar_rev_id as rev_id', 'ar_text_id as rev_text_id', 'ar_len as rev_len', 'ar_page_id as page_id', 'ar_page_id as rev_page', 'ar_deleted as rev_deleted', '0 as rev_parent_id', 'date_format(ar_timestamp, \'%Y-%m-%d %H:%i:%s\') as ts'); $conditions = array('ar_page_id' => $page_id, 'ar_rev_id' => $rev_id); $oRow = $db->selectRow('archive', $fields, $conditions, __METHOD__); } return $oRow; }
/** * @param ApiPageSet $resultPageSet * @return void */ protected function run(ApiPageSet $resultPageSet = null) { $db = $this->getDB(); $params = $this->extractRequestParams(false); $result = $this->getResult(); $this->requireMaxOneParameter($params, 'user', 'excludeuser'); // Namespace check is likely to be desired, but can't be done // efficiently in SQL. $miser_ns = null; $needPageTable = false; if ($params['namespace'] !== null) { $params['namespace'] = array_unique($params['namespace']); sort($params['namespace']); if ($params['namespace'] != MWNamespace::getValidNamespaces()) { $needPageTable = true; if ($this->getConfig()->get('MiserMode')) { $miser_ns = $params['namespace']; } else { $this->addWhere(array('page_namespace' => $params['namespace'])); } } } $this->addTables('revision'); if ($resultPageSet === null) { $this->parseParameters($params); $this->addTables('page'); $this->addJoinConds(array('page' => array('INNER JOIN', array('rev_page = page_id')))); $this->addFields(Revision::selectFields()); $this->addFields(Revision::selectPageFields()); // Review this depeneding on the outcome of T113901 $this->addOption('STRAIGHT_JOIN'); } else { $this->limit = $this->getParameter('limit') ?: 10; $this->addFields(array('rev_timestamp', 'rev_id')); if ($params['generatetitles']) { $this->addFields(array('rev_page')); } if ($needPageTable) { $this->addTables('page'); $this->addJoinConds(array('page' => array('INNER JOIN', array('rev_page = page_id')))); $this->addFieldsIf(array('page_namespace'), (bool) $miser_ns); // Review this depeneding on the outcome of T113901 $this->addOption('STRAIGHT_JOIN'); } } 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 ($this->fetchContent) { $this->addTables('text'); $this->addJoinConds(array('text' => array('INNER JOIN', array('rev_text_id=old_id')))); $this->addFields('old_id'); $this->addFields(Revision::selectTextFields()); } if ($params['user'] !== null) { $id = User::idFromName($params['user']); if ($id) { $this->addWhereFld('rev_user', $id); } else { $this->addWhereFld('rev_user_text', $params['user']); } } elseif ($params['excludeuser'] !== null) { $id = User::idFromName($params['excludeuser']); if ($id) { $this->addWhere('rev_user != ' . $id); } 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}"); } } $dir = $params['dir']; if ($params['continue'] !== null) { $op = $dir == 'newer' ? '>' : '<'; $cont = explode('|', $params['continue']); $this->dieContinueUsageIf(count($cont) != 2); $ts = $db->addQuotes($db->timestamp($cont[0])); $rev_id = (int) $cont[1]; $this->dieContinueUsageIf(strval($rev_id) !== $cont[1]); $this->addWhere("rev_timestamp {$op} {$ts} OR " . "(rev_timestamp = {$ts} AND " . "rev_id {$op}= {$rev_id})"); } $this->addOption('LIMIT', $this->limit + 1); $sort = $dir == 'newer' ? '' : ' DESC'; $orderby = array(); // Targeting index rev_timestamp, user_timestamp, or usertext_timestamp // But 'user' is always constant for the latter two, so it doesn't matter here. $orderby[] = "rev_timestamp {$sort}"; $orderby[] = "rev_id {$sort}"; $this->addOption('ORDER BY', $orderby); $res = $this->select(__METHOD__); $pageMap = array(); // Maps rev_page to array index $count = 0; $nextIndex = 0; $generated = array(); foreach ($res as $row) { if (++$count > $this->limit) { // We've had enough $this->setContinueEnumParameter('continue', "{$row->rev_timestamp}|{$row->rev_id}"); break; } // Miser mode namespace check if ($miser_ns !== null && !in_array($row->page_namespace, $miser_ns)) { continue; } if ($resultPageSet !== null) { if ($params['generatetitles']) { $generated[$row->rev_page] = $row->rev_page; } else { $generated[] = $row->rev_id; } } else { $revision = Revision::newFromRow($row); $rev = $this->extractRevisionInfo($revision, $row); if (!isset($pageMap[$row->rev_page])) { $index = $nextIndex++; $pageMap[$row->rev_page] = $index; $title = $revision->getTitle(); $a = array('pageid' => $title->getArticleID(), 'revisions' => array($rev)); ApiResult::setIndexedTagName($a['revisions'], 'rev'); ApiQueryBase::addTitleInfo($a, $title); $fit = $result->addValue(array('query', $this->getModuleName()), $index, $a); } else { $index = $pageMap[$row->rev_page]; $fit = $result->addValue(array('query', $this->getModuleName(), $index, 'revisions'), null, $rev); } if (!$fit) { $this->setContinueEnumParameter('continue', "{$row->rev_timestamp}|{$row->rev_id}"); break; } } } if ($resultPageSet !== null) { if ($params['generatetitles']) { $resultPageSet->populateFromPageIDs($generated); } else { $resultPageSet->populateFromRevisionIDs($generated); } } else { $result->addIndexedTagName(array('query', $this->getModuleName()), 'page'); } }
function getQueryInfo() { $queryInfo = array('tables' => array('revision'), 'fields' => Revision::selectFields(), 'conds' => array_merge(array('rev_page' => $this->historyPage->getTitle()->getArticleID()), $this->conds), 'options' => array('USE INDEX' => array('revision' => 'page_timestamp')), 'join_conds' => array('tag_summary' => array('LEFT JOIN', 'ts_rev_id=rev_id'))); ChangeTags::modifyDisplayQuery($queryInfo['tables'], $queryInfo['fields'], $queryInfo['conds'], $queryInfo['join_conds'], $queryInfo['options'], $this->tagFilter); wfRunHooks('PageHistoryPager::getQueryInfo', array(&$this, &$queryInfo)); return $queryInfo; }
/** * Get select fields for FlaggedRevision DB row (flaggedrevs/revision tables) * @return array */ public static function selectFields() { return array_merge(Revision::selectFields(), array('fr_rev_id', 'fr_page_id', 'fr_rev_timestamp', 'fr_user', 'fr_timestamp', 'fr_quality', 'fr_tags', 'fr_flags', 'fr_img_name', 'fr_img_sha1', 'fr_img_timestamp')); }
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() ) ); }
function getQueryInfo() { return array('tables' => 'revision', 'fields' => Revision::selectFields(), 'conds' => array('rev_page' => $this->mPageHistory->mTitle->getArticleID()), 'options' => array('USE INDEX' => 'page_timestamp')); }
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); } }
private function buildRevisionList($title, $errMsg = '') { // Builds a form listing the last 50 revisions of $pagename // that allows changing authors // $pagename: Title object // $errMsg: Error message // Returns: HTML. global $wgScript; $dbr = wfGetDb(DB_SLAVE); $res = $dbr->select('revision', Revision::selectFields(), array('rev_page' => $title->getArticleId()), __METHOD__, array('ORDER BY' => 'rev_timestamp DESC', 'LIMIT' => 50)); $revs = array(); while ($r = $dbr->fetchObject($res)) { $revs[] = new Revision($r); } if (empty($revs)) { // That's *very* weird return wfMsg('changeauthor-weirderror'); } $retval = Xml::openElement('form', array('method' => 'POST', 'action' => $wgScript)); $retval .= Xml::hidden('title', $this->selfTitle->getPrefixedDbKey()); $retval .= Xml::hidden('action', 'change'); $retval .= Xml::hidden('targetpage', $title->getPrefixedDbKey()); $retval .= Xml::openElement('fieldset'); $retval .= Xml::element('p', array(), wfMsg('changeauthor-explanation-multi')); $retval .= Xml::inputLabel(wfMsg('changeauthor-comment'), 'comment', 'comment', 50); $retval .= Xml::submitButton(wfMsg('changeauthor-changeauthors-multi')); if ($errMsg != '') { $retval .= Xml::openElement('p') . Xml::openElement('b'); $retval .= Xml::element('font', array('color' => 'red'), $errMsg); $retval .= Xml::closeElement('b') . Xml::closeElement('p'); } $retval .= Xml::element('h2', array(), $title->getPrefixedText()); $retval .= Xml::openElement('ul'); $count = count($revs); foreach ($revs as $i => $rev) { $retval .= $this->buildRevisionLine($rev, $title, $i == 0, $i == $count - 1); } $retval .= Xml::closeElement('ul'); $retval .= Xml::closeElement('fieldset'); $retval .= Xml::closeElement('form'); return $retval; }
/** * @covers Revision::selectFields */ public function testSelectFields() { global $wgContentHandlerUseDB; $fields = Revision::selectFields(); $this->assertTrue(in_array('rev_id', $fields), 'missing rev_id in list of fields'); $this->assertTrue(in_array('rev_page', $fields), 'missing rev_page in list of fields'); $this->assertTrue(in_array('rev_timestamp', $fields), 'missing rev_timestamp in list of fields'); $this->assertTrue(in_array('rev_user', $fields), 'missing rev_user in list of fields'); if ($wgContentHandlerUseDB) { $this->assertTrue(in_array('rev_content_model', $fields), 'missing rev_content_model in list of fields'); $this->assertTrue(in_array('rev_content_format', $fields), 'missing rev_content_format in list of fields'); } }
/** * Get the first revision of the page * * @param int $flags Title::GAID_FOR_UPDATE * @return Revision|null If page doesn't exist */ public function getFirstRevision($flags = 0) { $pageId = $this->getArticleID($flags); if ($pageId) { $db = $flags & self::GAID_FOR_UPDATE ? wfGetDB(DB_MASTER) : wfGetDB(DB_SLAVE); $row = $db->selectRow('revision', Revision::selectFields(), array('rev_page' => $pageId), __METHOD__, array('ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1)); if ($row) { return new Revision($row); } } return null; }
/** * Builds a form listing the last 50 revisions of $title that allows changing authors * @param $title Title object * @param $errMsg String: error message * @return HTML */ private function buildRevisionList($title, $errMsg = '') { global $wgScript; $dbr = wfGetDB(DB_SLAVE); $res = $dbr->select('revision', Revision::selectFields(), array('rev_page' => $title->getArticleId()), __METHOD__, array('ORDER BY' => 'rev_timestamp DESC', 'LIMIT' => 50)); $revs = array(); while ($r = $dbr->fetchObject($res)) { $revs[] = new Revision($r); } if (empty($revs)) { // That's *very* weird return wfMsg('changeauthor-weirderror'); } $retval = Xml::openElement('form', array('method' => 'post', 'action' => $wgScript)); $retval .= Html::Hidden('title', $this->selfTitle->getPrefixedDBkey()); $retval .= Html::Hidden('action', 'change'); $retval .= Html::Hidden('targetpage', $title->getPrefixedDBkey()); $retval .= Xml::openElement('fieldset'); $retval .= Xml::element('p', array(), wfMsg('changeauthor-explanation-multi')); $retval .= Xml::inputLabel(wfMsg('changeauthor-comment'), 'comment', 'comment', 50); $retval .= Xml::submitButton(wfMsgExt('changeauthor-changeauthors-multi', array('parsemag', 'escape'), count($revs))); if ($errMsg != '') { $retval .= Xml::openElement('p') . Xml::openElement('b'); $retval .= Xml::element('font', array('color' => 'red'), $errMsg); $retval .= Xml::closeElement('b') . Xml::closeElement('p'); } $retval .= Xml::element('h2', array(), $title->getPrefixedText()); $retval .= Xml::openElement('ul'); $count = count($revs); foreach ($revs as $i => $rev) { $retval .= $this->buildRevisionLine($rev, $title, $i == 0, $i == $count - 1); } $retval .= Xml::closeElement('ul'); $retval .= Xml::closeElement('fieldset'); $retval .= Xml::closeElement('form'); return $retval; }
/** * Get the Revision object of the oldest revision * @return Revision|null */ public function getOldestRevision() { wfProfileIn( __METHOD__ ); // Try using the slave database first, then try the master $continue = 2; $db = wfGetDB( DB_SLAVE ); $revSelectFields = Revision::selectFields(); $row = null; while ( $continue ) { $row = $db->selectRow( array( 'page', 'revision' ), $revSelectFields, array( 'page_namespace' => $this->mTitle->getNamespace(), 'page_title' => $this->mTitle->getDBkey(), 'rev_page = page_id' ), __METHOD__, array( 'ORDER BY' => 'rev_timestamp ASC' ) ); if ( $row ) { $continue = 0; } else { $db = wfGetDB( DB_MASTER ); $continue--; } } wfProfileOut( __METHOD__ ); return $row ? Revision::newFromRow( $row ) : null; }
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; } } }