protected function autoreview_current(User $user) { $this->output("Auto-reviewing all current page versions...\n"); if (!$user->getID()) { $this->output("Invalid user specified.\n"); return; } elseif (!$user->isAllowed('review')) { $this->output("User specified (id: {$user->getID()}) does not have \"review\" rights.\n"); return; } $db = wfGetDB(DB_MASTER); $this->output("Reviewer username: "******"\n"); $start = $db->selectField('page', 'MIN(page_id)', false, __METHOD__); $end = $db->selectField('page', 'MAX(page_id)', false, __METHOD__); if (is_null($start) || is_null($end)) { $this->output("...page table seems to be empty.\n"); return; } # Do remaining chunk $end += $this->mBatchSize - 1; $blockStart = $start; $blockEnd = $start + $this->mBatchSize - 1; $count = 0; $changed = 0; $flags = FlaggedRevs::quickTags(FR_CHECKED); // Assume basic level while ($blockEnd <= $end) { $this->output("...doing page_id from {$blockStart} to {$blockEnd}\n"); $res = $db->select(array('page', 'revision'), '*', array("page_id BETWEEN {$blockStart} AND {$blockEnd}", 'page_namespace' => FlaggedRevs::getReviewNamespaces(), 'rev_id = page_latest'), __METHOD__); # Go through and autoreview the current version of every page... foreach ($res as $row) { $title = Title::newFromRow($row); $rev = Revision::newFromRow($row); # Is it already reviewed? $frev = FlaggedRevision::newFromTitle($title, $row->page_latest, FR_MASTER); # Rev should exist, but to be safe... if (!$frev && $rev) { $article = new Article($title); $db->begin(); FlaggedRevs::autoReviewEdit($article, $user, $rev, $flags, true); FlaggedRevs::HTMLCacheUpdates($article->getTitle()); $db->commit(); $changed++; } $count++; } $db->freeResult($res); $blockStart += $this->mBatchSize - 1; $blockEnd += $this->mBatchSize - 1; // XXX: Don't let deferred jobs array get absurdly large (bug 24375) DeferredUpdates::doUpdates('commit'); wfWaitForSlaves(5); } $this->output("Auto-reviewing of all pages complete ..." . "{$count} rows [{$changed} changed]\n"); }
protected function feedItem($row) { $title = Title::makeTitle(intval($row->page_namespace), $row->page_title); if ($title && $title->userCan('read', $this->getUser())) { $date = $row->rev_timestamp; $comments = $title->getTalkPage()->getFullURL(); $revision = Revision::newFromRow($row); return new FeedItem($title->getPrefixedText(), $this->feedItemDesc($revision), $title->getFullURL(), $date, $this->feedItemAuthor($revision), $comments); } return null; }
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; }
/** * 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; }
/** * @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'); } }
protected function feedItem($row) { // This hook is the api contributions equivalent to the // ContributionsLineEnding hook. Hook implementers may cancel // the hook to signal the user is not allowed to read this item. $feedItem = null; $hookResult = Hooks::run('ApiFeedContributions::feedItem', array($row, $this->getContext(), &$feedItem)); // Hook returned a valid feed item if ($feedItem instanceof FeedItem) { return $feedItem; // Hook was canceled and did not return a valid feed item } elseif (!$hookResult) { return null; } // Hook completed and did not return a valid feed item $title = Title::makeTitle(intval($row->page_namespace), $row->page_title); if ($title && $title->userCan('read', $this->getUser())) { $date = $row->rev_timestamp; $comments = $title->getTalkPage()->getFullURL(); $revision = Revision::newFromRow($row); return new FeedItem($title->getPrefixedText(), $this->feedItemDesc($revision), $title->getFullURL(array('diff' => $revision->getId())), $date, $this->feedItemAuthor($revision), $comments); } return null; }
/** * Get the Revision object of the oldest revision * @return Revision|null */ public function getOldestRevision() { // Try using the replica DB first, then try the master $continue = 2; $db = wfGetDB(DB_REPLICA); $revSelectFields = Revision::selectFields(); $row = null; while ($continue) { $row = $db->selectRow(['page', 'revision'], $revSelectFields, ['page_namespace' => $this->mTitle->getNamespace(), 'page_title' => $this->mTitle->getDBkey(), 'rev_page = page_id'], __METHOD__, ['ORDER BY' => 'rev_timestamp ASC']); if ($row) { $continue = 0; } else { $db = wfGetDB(DB_MASTER); $continue--; } } return $row ? Revision::newFromRow($row) : null; }
/** * @covers Revision::newFromRow */ public function testNewFromRow() { $orig = $this->makeRevision(); $dbr = wfGetDB(DB_SLAVE); $res = $dbr->select('revision', '*', ['rev_id' => $orig->getId()]); $this->assertTrue(is_object($res), 'query failed'); $row = $res->fetchObject(); $res->free(); $rev = Revision::newFromRow($row); $this->assertRevEquals($orig, $rev); }
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; }
protected function loadWikiMessages() { $dbr = wfGetDB(DB_SLAVE, array()); echo "Loading page list...\n"; $res = $dbr->select('page', '*', array('page_namespace' => NS_MEDIAWIKI), 'evaluate_messaging.php'); $rows = array(); $byRev = array(); foreach ($res as $row) { $rows[] = $row; $byRev[$row->page_latest] = false; } $res->free(); echo "Loading revisions...\n"; $res = $dbr->select('revision', '*', array('rev_id' => array_keys($byRev)), 'evaluate_messaging.php'); foreach ($res as $row) { $byRev[$row->rev_id] = $row; } $res->free(); echo "Loading contents...\n"; $i = 0; $messages = array(); foreach ($rows as $row) { list($key, $langcode) = MessageCache::singleton()->figureMessage($row->page_title); $revision = Revision::newFromRow($byRev[$row->page_latest]); $content = $revision->getRawText(); $messages[$langcode][lcfirst($key)] = $content; if (++$i % 100 == 0) { echo "."; flush(); } } echo "\n"; $this->wikiMessages = $messages; $this->saveToFile(self::WIKI_MESSAGES_CACHE, $messages); }
/** * Fetch an article from this or another local MediaWiki database. * This is probably *very* fragile, and shouldn't be used perhaps. * * @param string $wiki * @param string $article * @return string */ function getArticleText($wiki, $article) { wfDebugLog('SpamBlacklist', "Fetching {$this->getBlacklistType()} blacklist from '{$article}' on '{$wiki}'...\n"); $title = Title::newFromText($article); // Load all the relevant tables from the correct DB. // This assumes that old_text is the actual text or // that the external store system is at least unified. $row = wfGetDB(DB_SLAVE, array(), $wiki)->selectRow(array('page', 'revision', 'text'), array_merge(Revision::selectFields(), Revision::selectPageFields(), Revision::selectTextFields()), array('page_namespace' => $title->getNamespace(), 'page_title' => $title->getDBkey(), 'rev_id=page_latest', 'old_id=rev_text_id'), __METHOD__); return $row ? Revision::newFromRow($row)->getText() : false; }