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"); }
/** * If this edit will be auto-reviewed on submit * Note: checking wpReviewEdit does not count as auto-reviewed * @param EditPage $editPage * @return bool */ protected function editWillBeAutoreviewed(EditPage $editPage) { $title = $this->article->getTitle(); // convenience if (!$this->article->isReviewable()) { return false; } if ($title->quickUserCan('autoreview')) { if (FlaggedRevs::autoReviewNewPages() && !$this->article->exists()) { return true; // edit will be autoreviewed } if (!isset($editPage->fr_baseFRev)) { $baseRevId = self::getBaseRevId($editPage, $this->getRequest()); $editPage->fr_baseFRev = FlaggedRevision::newFromTitle($title, $baseRevId); } if ($editPage->fr_baseFRev) { return true; // edit will be autoreviewed } } return false; // edit won't be autoreviewed }
/** * Validate and clean up parameters (e.g. from POST request). * @return mixed (true on success, error string on failure) */ protected function doCheckParameters() { $action = $this->getAction(); if ($action === null) { return 'review_param_missing'; // no action specified (approve, reject, de-approve) } elseif (!$this->oldid) { return 'review_no_oldid'; // no revision target } # Get the revision's current flags (if any) $this->oldFrev = FlaggedRevision::newFromTitle($this->page, $this->oldid, FR_MASTER); $this->oldFlags = $this->oldFrev ? $this->oldFrev->getTags() : FlaggedRevision::expandRevisionTags(''); // default # Set initial value for newLastChangeTime (if unchanged on submit) $this->newLastChangeTime = $this->lastChangeTime; # Fill in implicit tag data for binary flag case $iDims = $this->implicitDims(); if ($iDims) { $this->dims = $iDims; // binary flag case } if ($action === 'approve') { # We must at least rate each category as 1, the minimum if (in_array(0, $this->dims, true)) { return 'review_too_low'; } # Special token to discourage fiddling with templates/files... if (!$this->skipValidationKey) { $k = self::validationKey($this->templateParams, $this->imageParams, $this->fileVersion, $this->oldid, $this->sessionKey); if ($this->validatedParams !== $k) { return 'review_bad_key'; } } # Sanity check tags if (!FlaggedRevs::flagsAreValid($this->dims)) { return 'review_bad_tags'; } # Check permissions with tags if (!FlaggedRevs::userCanSetFlags($this->user, $this->dims, $this->oldFlags)) { return 'review_denied'; } } elseif ($action === 'unapprove') { # Check permissions with old tags if (!FlaggedRevs::userCanSetFlags($this->user, $this->oldFlags)) { return 'review_denied'; } } return true; }
/** * Get the stable revision * @return mixed (FlaggedRevision/null) */ public function getStableRev() { if (!FlaggedRevs::inReviewNamespace($this->mTitle)) { return null; // short-circuit } if (!$this->mDataLoaded) { $this->loadPageData(); } # Stable rev deferred even after page data load if ($this->stableRev === null) { $srev = FlaggedRevision::newFromTitle($this->mTitle, $this->stable); $this->stableRev = $srev ? $srev : false; // cache negative hits too } return $this->stableRev ? $this->stableRev : null; // false => null }
/** * Get the HTML output of a revision based on $text. * @param Title $title * @param string $text * @param int $id Source revision Id * @param ParserOptions $pOpts * @return ParserOutput */ public static function parseStableText(Title $title, $text, $id, ParserOptions $pOpts) { global $wgParser; # Notify Parser if includes should be stabilized $resetManager = false; $incManager = FRInclusionManager::singleton(); if ($id && self::inclusionSetting() != FR_INCLUDES_CURRENT) { # Use FRInclusionManager to do the template/file version query # up front unless the versions are already specified there... if (!$incManager->parserOutputIsStabilized()) { $frev = FlaggedRevision::newFromTitle($title, $id); if ($frev) { $incManager->stabilizeParserOutput($frev); $resetManager = true; // need to reset when done } } } # Parse the new body, wikitext -> html $parserOut = $wgParser->parse($text, $title, $pOpts, true, true, $id); # Stable parse done! if ($resetManager) { $incManager->clear(); // reset the FRInclusionManager as needed } return $parserOut; }
/** * Generates a brief review form for a page * @return array (html string, error string or true) */ public function getHtml() { global $wgLang; $revId = $this->rev->getId(); if ($this->rev->isDeleted(Revision::DELETED_TEXT)) { return array('', 'review_bad_oldid'); # The revision must be valid and public } $article = $this->article; // convenience $srev = $article->getStableRev(); # See if the version being displayed is flagged... if ($revId == $article->getStable()) { $frev = $srev; // avoid query } else { $frev = FlaggedRevision::newFromTitle($article->getTitle(), $revId); } $oldFlags = $frev ? $frev->getTags() : FlaggedRevs::quickTags(FR_CHECKED); // basic tags $reviewTime = $frev ? $frev->getTimestamp() : ''; // last review of rev $priorRevId = $this->refRev ? $this->refRev->getId() : 0; # If we are reviewing updates to a page, start off with the stable revision's # flags. Otherwise, we just fill them in with the selected revision's flags. # @TODO: do we want to carry over info for other diffs? if ($srev && $srev->getRevId() == $priorRevId) { // diff-to-stable $flags = $srev->getTags(); # Check if user is allowed to renew the stable version. # If not, then get the flags for the new revision itself. if (!FlaggedRevs::userCanSetFlags($this->user, $oldFlags)) { $flags = $oldFlags; } # Re-review button is need for template/file only review case $reviewIncludes = $srev->getRevId() == $revId && !$article->stableVersionIsSynced(); } else { // views $flags = $oldFlags; $reviewIncludes = false; // re-review button not needed } # Disable form for unprivileged users $disabled = array(); if (!$article->getTitle()->quickUserCan('review') || !FlaggedRevs::userCanSetFlags($this->user, $flags)) { $disabled = array('disabled' => 'disabled'); } # Begin form... $reviewTitle = SpecialPage::getTitleFor('RevisionReview'); $action = $reviewTitle->getLocalUrl('action=submit'); $params = array('method' => 'post', 'action' => $action, 'id' => 'mw-fr-reviewform'); $form = Xml::openElement('form', $params) . "\n"; $form .= Xml::openElement('fieldset', array('class' => 'flaggedrevs_reviewform noprint')) . "\n"; # Add appropriate legend text $legendMsg = $frev ? 'revreview-reflag' : 'revreview-flag'; $form .= Xml::openElement('legend', array('id' => 'mw-fr-reviewformlegend')); $form .= "<strong>" . wfMessage($legendMsg)->escaped() . "</strong>"; $form .= Xml::closeElement('legend') . "\n"; # Show explanatory text $form .= $this->topNotice; # Check if anyone is reviewing this already and # show a conflict warning message as needed... if ($priorRevId) { list($u, $ts) = FRUserActivity::getUserReviewingDiff($priorRevId, $this->rev->getId()); } else { list($u, $ts) = FRUserActivity::getUserReviewingPage($this->rev->getPage()); } $form .= Xml::openElement('p'); // Page under review (and not by this user)... if ($u !== null && $u != $this->user->getName()) { $form .= '<span class="fr-under-review">'; $msg = $priorRevId ? 'revreview-poss-conflict-c' : 'revreview-poss-conflict-p'; $form .= wfMessage($msg, $u, $wgLang->date($ts, true), $wgLang->time($ts, true))->parse(); $form .= "</span>"; // Page not under review or under review by this user... } elseif (!$frev) { // rev not already reviewed $form .= '<span id="mw-fr-reviewing-status" style="display:none;"></span>'; // JS widget } $form .= Xml::closeElement('p') . "\n"; # Start rating controls $css = $disabled ? 'fr-rating-controls-disabled' : 'fr-rating-controls'; $form .= Xml::openElement('p', array('class' => $css, 'id' => 'fr-rating-controls')) . "\n"; # Add main checkboxes/selects $form .= Xml::openElement('span', array('id' => 'mw-fr-ratingselects', 'class' => 'fr-rating-options')) . "\n"; $form .= self::ratingInputs($this->user, $flags, (bool) $disabled, (bool) $frev) . "\n"; $form .= Xml::closeElement('span') . "\n"; # Don't put buttons & comment field on the same line as tag inputs. if (!$disabled && !FlaggedRevs::binaryFlagging()) { // $disabled => no comment/buttons $form .= "<br />"; } # Start comment & buttons $form .= Xml::openElement('span', array('id' => 'mw-fr-confirmreview')) . "\n"; # Hide comment input if needed if (!$disabled) { $form .= Xml::inputLabel(wfMessage('revreview-log')->text(), 'wpReason', 'mw-fr-commentbox', 40, '', array('maxlength' => 255, 'class' => 'fr-comment-box')); } # Add the submit buttons... $rejectId = $this->rejectRefRevId(); // determine if there will be reject button $form .= self::submitButtons($rejectId, $frev, (bool) $disabled, $reviewIncludes); # Show stability log if there is anything interesting... if ($article->isPageLocked()) { $form .= ' ' . FlaggedRevsXML::logToggle('revreview-log-toggle-show'); } # End comment & buttons $form .= Xml::closeElement('span') . "\n"; # ..add the actual stability log body here if ($article->isPageLocked()) { $form .= FlaggedRevsXML::stabilityLogExcerpt($article); } # End rating controls $form .= Xml::closeElement('p') . "\n"; # Show explanatory text $form .= $this->bottomNotice; # Get the file version used for File: pages as needed $fileKey = $this->getFileVersion(); # Get template/file version info as needed list($templateIDs, $imageSHA1Keys) = $this->getIncludeVersions(); # Convert these into flat string params list($templateParams, $imageParams, $fileVersion) = RevisionReviewForm::getIncludeParams($templateIDs, $imageSHA1Keys, $fileKey); # Hidden params $form .= Html::hidden('title', $reviewTitle->getPrefixedText()) . "\n"; $form .= Html::hidden('target', $article->getTitle()->getPrefixedDBKey()) . "\n"; $form .= Html::hidden('refid', $priorRevId, array('id' => 'mw-fr-input-refid')) . "\n"; $form .= Html::hidden('oldid', $revId, array('id' => 'mw-fr-input-oldid')) . "\n"; $form .= Html::hidden('wpEditToken', $this->user->getEditToken()) . "\n"; $form .= Html::hidden('changetime', $reviewTime, array('id' => 'mw-fr-input-changetime')) . "\n"; // id for JS $form .= Html::hidden('userreviewing', (int) ($u === $this->user->getName()), array('id' => 'mw-fr-user-reviewing')) . "\n"; // id for JS # Add review parameters $form .= Html::hidden('templateParams', $templateParams) . "\n"; $form .= Html::hidden('imageParams', $imageParams) . "\n"; $form .= Html::hidden('fileVersion', $fileVersion) . "\n"; # Special token to discourage fiddling... $key = $this->request->getSessionData('wsFlaggedRevsKey'); $checkCode = RevisionReviewForm::validationKey($templateParams, $imageParams, $fileVersion, $revId, $key); $form .= Html::hidden('validatedParams', $checkCode) . "\n"; $form .= Xml::closeElement('fieldset') . "\n"; $form .= Xml::closeElement('form') . "\n"; return array($form, true); }
/** * Mark auto-reviewed edits as patrolled */ public static function autoMarkPatrolled(RecentChange &$rc) { if (empty($rc->mAttribs['rc_this_oldid'])) { return true; } $fa = FlaggableWikiPage::getTitleInstance($rc->getTitle()); $fa->loadPageData('fromdbmaster'); // Is the page reviewable? if ($fa->isReviewable()) { $revId = $rc->mAttribs['rc_this_oldid']; // If the edit we just made was reviewed, then it's the stable rev $frev = FlaggedRevision::newFromTitle($rc->getTitle(), $revId, FR_MASTER); // Reviewed => patrolled if ($frev) { RevisionReviewForm::updateRecentChanges($rc, 'patrol', $frev); $rc->mAttribs['rc_patrolled'] = 1; // make sure irc/email notifs know status } return true; } return true; }
/** * Submit the form parameters for the page config to the DB. * * @return mixed (true on success, error string on failure) */ public function doSubmit() { # Double-check permissions if (!$this->isAllowed()) { return 'stablize_denied'; } # Parse and cleanup the expiry time given... $expiry = $this->getExpiry(); if ($expiry === false) { return 'stabilize_expiry_invalid'; } elseif ($expiry !== Block::infinity() && $expiry < wfTimestampNow()) { return 'stabilize_expiry_old'; } # Update the DB row with the new config... $changed = FRPageConfig::setStabilitySettings($this->page, $this->getNewConfig()); # Log if this actually changed anything... if ($changed) { $article = new FlaggableWikiPage($this->page); if (FlaggedRevs::useOnlyIfProtected()) { # Config may have changed to allow stable versions, so refresh # the tracking table to account for any hidden reviewed versions... $frev = FlaggedRevision::determineStable($this->page, FR_MASTER); if ($frev) { $article->updateStableVersion($frev); } else { $article->clearStableVersion(); } } # Update logs and make a null edit $nullRev = $this->updateLogsAndHistory($article); # Null edit may have been auto-reviewed already $frev = FlaggedRevision::newFromTitle($this->page, $nullRev->getId(), FR_MASTER); $updatesDone = (bool) $frev; // stableVersionUpdates() already called? # Check if this null edit is to be reviewed... if ($this->reviewThis && !$frev) { $flags = null; # Review this revision of the page... $ok = FlaggedRevs::autoReviewEdit($article, $this->user, $nullRev, $flags, true); if ($ok) { FlaggedRevs::markRevisionPatrolled($nullRev); // reviewed -> patrolled $updatesDone = true; // stableVersionUpdates() already called } } # Update page and tracking tables and clear cache. if (!$updatesDone) { FlaggedRevs::stableVersionUpdates($this->page); } } # Apply watchlist checkbox value (may be NULL) $this->updateWatchlist(); # Take this opportunity to purge out expired configurations FRPageConfig::purgeExpiredConfigurations(); return true; }
/** * When an user makes a null-edit we sometimes want to review it... * (a) Null undo or rollback * (b) Null edit with review box checked * Note: called after edit ops are finished */ public static function maybeNullEditReview(Page $article, $user, $text, $s, $m, $a, $b, $flags, $rev, &$status, $baseId) { global $wgRequest; # Revision must *be* null (null edit). We also need the user who made the edit. if (!$user || $rev !== null) { return true; } # Rollback/undo or box checked $reviewEdit = $wgRequest->getCheck('wpReviewEdit'); if (!$baseId && !$reviewEdit) { return true; // short-circuit } $fa = FlaggableWikiPage::getTitleInstance($article->getTitle()); $fa->loadPageData('fromdbmaster'); if (!$fa->isReviewable()) { return true; // page is not reviewable } $title = $article->getTitle(); // convenience # Get the current revision ID $rev = Revision::newFromTitle($title); if (!$rev) { return true; // wtf? } $flags = null; # Is this a rollback/undo that didn't change anything? if ($baseId > 0) { $frev = FlaggedRevision::newFromTitle($title, $baseId); // base rev of null edit $pRev = Revision::newFromId($rev->getParentId()); // current rev parent $revIsNull = $pRev && $pRev->getTextId() == $rev->getTextId(); # Was the edit that we tried to revert to reviewed? # We avoid auto-reviewing null edits to avoid confusion (bug 28476). if ($frev && !$revIsNull) { # Review this revision of the page... $ok = FlaggedRevs::autoReviewEdit($article, $user, $rev, $flags); if ($ok) { FlaggedRevs::markRevisionPatrolled($rev); // reviewed -> patrolled FlaggedRevs::extraHTMLCacheUpdate($title); return true; } } } # Get edit timestamp, it must exist. $editTimestamp = $wgRequest->getVal('wpEdittime'); # Is the page checked off to be reviewed? if ($editTimestamp && $reviewEdit && $title->userCan('review')) { # Check wpEdittime against current revision's time. # If an edit was auto-merged in between, review only up to what # was the current rev when this user started editing the page. if ($rev->getTimestamp() != $editTimestamp) { $dbw = wfGetDB(DB_MASTER); $rev = Revision::loadFromTimestamp($dbw, $title, $editTimestamp); if (!$rev) { return true; // deleted? } } # Review this revision of the page... $ok = FlaggedRevs::autoReviewEdit($article, $user, $rev, $flags, false); if ($ok) { FlaggedRevs::markRevisionPatrolled($rev); // reviewed -> patrolled FlaggedRevs::extraHTMLCacheUpdate($title); } } return true; }