public function testUpdateUserParams()
 {
     $p = FRUserCounters::getUserParams(-1);
     # Assumes (main) IN content namespace
     $title = Title::makeTitleSafe(0, 'helloworld');
     $article = new Article($title);
     $copyP = $p;
     FRUserCounters::updateUserParams($copyP, $article, "Manual edit comment");
     $this->assertEquals($p['editComments'] + 1, $copyP['editComments'], "Manual summary");
     $copyP = $p;
     FRUserCounters::updateUserParams($copyP, $article, "/* section */");
     $this->assertEquals($p['editComments'], $copyP['editComments'], "Auto summary");
     $copyP = $p;
     FRUserCounters::updateUserParams($copyP, $article, "edit summary");
     $this->assertEquals($p['totalContentEdits'] + 1, $copyP['totalContentEdits'], "Content edit count on content edit");
     $expected = $p['uniqueContentPages'];
     $expected[] = 0;
     $this->assertEquals($expected, $copyP['uniqueContentPages'], "Unique content pages on content edit");
     # Assumes (user) NOT IN content namespace
     $title = Title::makeTitleSafe(NS_USER, 'helloworld');
     $article = new Article($title);
     $copyP = $p;
     FRUserCounters::updateUserParams($copyP, $article, "Manual edit comment");
     $this->assertEquals($p['editComments'] + 1, $copyP['editComments'], "Manual summary");
     $copyP = $p;
     FRUserCounters::updateUserParams($copyP, $article, "/* section */");
     $this->assertEquals($p['editComments'], $copyP['editComments'], "Auto summary");
     $title = Title::makeTitleSafe(NS_USER, 'helloworld');
     $article = new Article($title);
     $copyP = $p;
     FRUserCounters::updateUserParams($copyP, $article, "edit summary");
     $this->assertEquals($p['totalContentEdits'], $copyP['totalContentEdits'], "Content edit count on non-content edit");
     $this->assertEquals($p['uniqueContentPages'], $copyP['uniqueContentPages'], "Unique content pages on non-content edit");
 }
 public function execute()
 {
     global $wgContentNamespaces, $wgFlaggedRevsAutopromote;
     $this->output("Populating and updating flaggedrevs_promote table\n");
     $dbr = wfGetDB(DB_SLAVE);
     $dbw = wfGetDB(DB_MASTER);
     $start = $dbr->selectField('user', 'MIN(user_id)', false, __METHOD__);
     $end = $dbr->selectField('user', 'MAX(user_id)', false, __METHOD__);
     if (is_null($start) || is_null($end)) {
         $this->output("...user table seems to be empty.\n");
         return;
     }
     $count = 0;
     $changed = 0;
     for ($blockStart = $start; $blockStart <= $end; $blockStart += $this->mBatchSize) {
         $blockEnd = min($end, $blockStart + $this->mBatchSize - 1);
         $this->output("...doing user_id from {$blockStart} to {$blockEnd}\n");
         $cond = "user_id BETWEEN {$blockStart} AND {$blockEnd}\n";
         $res = $dbr->select('user', '*', $cond, __METHOD__);
         # Go through and clean up missing items, as well as correct fr_quality...
         foreach ($res as $row) {
             $dbw->begin();
             $user = User::newFromRow($row);
             $p = FRUserCounters::getUserParams($user->getId(), FR_FOR_UPDATE);
             $oldp = $p;
             # Get edit comments used
             $sres = $dbr->select('revision', '1', array('rev_user' => $user->getID(), "rev_comment NOT LIKE '/*%*/'"), __METHOD__, array('LIMIT' => max($wgFlaggedRevsAutopromote['editComments'], 500)));
             $p['editComments'] = $dbr->numRows($sres);
             # Get content page edits
             $sres = $dbr->select(array('revision', 'page'), '1', array('rev_user' => $user->getID(), 'page_id = rev_page', 'page_namespace' => $wgContentNamespaces), __METHOD__, array('LIMIT' => max($wgFlaggedRevsAutopromote['totalContentEdits'], 500)));
             $p['totalContentEdits'] = $dbr->numRows($sres);
             # Get unique content pages edited
             $sres = $dbr->select(array('revision', 'page'), 'DISTINCT(rev_page)', array('rev_user' => $user->getID(), 'page_id = rev_page', 'page_namespace' => $wgContentNamespaces), __METHOD__, array('LIMIT' => max($wgFlaggedRevsAutopromote['uniqueContentPages'], 50)));
             $p['uniqueContentPages'] = array();
             foreach ($sres as $innerRow) {
                 $p['uniqueContentPages'][] = (int) $innerRow->rev_page;
             }
             # Save the new params...
             if ($oldp != $p) {
                 FRUserCounters::saveUserParams($user->getId(), $p);
                 $changed++;
             }
             $count++;
             $dbw->commit();
         }
         wfWaitForSlaves(5);
     }
     $this->output("flaggedrevs_promote table update complete ..." . " {$count} rows [{$changed} changed or added]\n");
 }
 /**
  * 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 'review_denied';
     }
     # We can only approve actual revisions...
     if ($this->getAction() === 'approve') {
         $rev = Revision::newFromTitle($this->page, $this->oldid);
         # Check for archived/deleted revisions...
         if (!$rev || $rev->getVisibility()) {
             return 'review_bad_oldid';
         }
         # Check for review conflicts...
         if ($this->lastChangeTime !== null) {
             // API uses null
             $lastChange = $this->oldFrev ? $this->oldFrev->getTimestamp() : '';
             if ($lastChange !== $this->lastChangeTime) {
                 return 'review_conflict_oldid';
             }
         }
         $status = $this->approveRevision($rev, $this->oldFrev);
         # We can only unapprove approved revisions...
     } elseif ($this->getAction() === 'unapprove') {
         # Check for review conflicts...
         if ($this->lastChangeTime !== null) {
             // API uses null
             $lastChange = $this->oldFrev ? $this->oldFrev->getTimestamp() : '';
             if ($lastChange !== $this->lastChangeTime) {
                 return 'review_conflict_oldid';
             }
         }
         # Check if we can find this flagged rev...
         if (!$this->oldFrev) {
             return 'review_not_flagged';
         }
         $status = $this->unapproveRevision($this->oldFrev);
     } elseif ($this->getAction() === 'reject') {
         $newRev = Revision::newFromTitle($this->page, $this->oldid);
         $oldRev = Revision::newFromTitle($this->page, $this->refid);
         # Do not mess with archived/deleted revisions
         if (!$oldRev || $oldRev->isDeleted(Revision::DELETED_TEXT)) {
             return 'review_bad_oldid';
         } elseif (!$newRev || $newRev->isDeleted(Revision::DELETED_TEXT)) {
             return 'review_bad_oldid';
         }
         # Check that the revs are in order
         if ($oldRev->getTimestamp() > $newRev->getTimestamp()) {
             return 'review_cannot_undo';
         }
         # Make sure we are only rejecting pending changes
         $srev = FlaggedRevision::newFromStable($this->page, FR_MASTER);
         if ($srev && $oldRev->getTimestamp() < $srev->getRevTimestamp()) {
             return 'review_cannot_reject';
             // not really a use case
         }
         $article = new WikiPage($this->page);
         # Get text with changes after $oldRev up to and including $newRev removed
         $new_text = $article->getUndoText($newRev, $oldRev);
         if ($new_text === false) {
             return 'review_cannot_undo';
         }
         $baseRevId = $newRev->isCurrent() ? $oldRev->getId() : 0;
         # Actually make the edit...
         $editStatus = $article->doEdit($new_text, $this->getComment(), 0, $baseRevId, $this->user);
         $status = $editStatus->isOK() ? true : 'review_cannot_undo';
         if ($editStatus->isOK() && class_exists('EchoEvent') && $editStatus->value['revision']) {
             $affectedRevisions = array();
             // revid -> userid
             $revisions = wfGetDB(DB_SLAVE)->select('revision', array('rev_id', 'rev_user'), array('rev_id <= ' . $newRev->getId(), 'rev_timestamp <= ' . $newRev->getTimestamp(), 'rev_id > ' . $oldRev->getId(), 'rev_timestamp > ' . $oldRev->getTimestamp(), 'rev_page' => $article->getId()), __METHOD__);
             foreach ($revisions as $row) {
                 $affectedRevisions[$row->rev_id] = $row->rev_user;
             }
             EchoEvent::create(array('type' => 'reverted', 'title' => $this->page, 'extra' => array('revid' => $editStatus->value['revision']->getId(), 'reverted-users-ids' => array_values($affectedRevisions), 'reverted-revision-ids' => array_keys($affectedRevisions), 'method' => 'flaggedrevs-reject'), 'agent' => $this->user));
         }
         # If this undid one edit by another logged-in user, update user tallies
         if ($status === true && $newRev->getParentId() == $oldRev->getId() && $newRev->getRawUser()) {
             if ($newRev->getRawUser() != $this->user->getId()) {
                 // no self-reverts
                 FRUserCounters::incCount($newRev->getRawUser(), 'revertedEdits');
             }
         }
     }
     # Watch page if set to do so
     if ($status === true) {
         if ($this->user->getOption('flaggedrevswatch') && !$this->page->userIsWatching()) {
             $this->user->addWatch($this->page);
         }
     }
     return $status;
 }
 /**
  * Increments a count for a user
  * @param int $uid User id
  * @param string $param Count name
  * @return string
  */
 public static function incCount($uid, $param)
 {
     $p = self::getUserParams($uid, FR_FOR_UPDATE);
     if (!isset($p[$param])) {
         $p[$param] = 0;
     }
     $p[$param]++;
     FRUserCounters::saveUserParams($uid, $p);
 }
 /**
  * Check an autopromote condition that is defined by FlaggedRevs
  *
  * Note: some unobtrusive caching is used to avoid DB hits.
  */
 public static function checkAutoPromoteCond($cond, array $params, User $user, &$result)
 {
     global $wgMemc;
     switch ($cond) {
         case APCOND_FR_EDITSUMMARYCOUNT:
             $p = FRUserCounters::getParams($user);
             $result = $p && $p['editComments'] >= $params[0];
             break;
         case APCOND_FR_NEVERBOCKED:
             if ($user->isBlocked()) {
                 $result = false;
                 // failed
             } else {
                 $key = wfMemcKey('flaggedrevs', 'autopromote-notblocked', $user->getId());
                 $val = $wgMemc->get($key);
                 if ($val === 'false') {
                     $result = false;
                     // failed
                 } else {
                     # Hit the DB if the result is not cached or if we need
                     # to check if the user was blocked since the last check...
                     $now_unix = time();
                     $last_checked = is_int($val) ? $val : 0;
                     // TS_UNIX
                     $result = !self::wasPreviouslyBlocked($user, $last_checked);
                     $wgMemc->set($key, $result ? $now_unix : 'false', 7 * 86400);
                 }
             }
             break;
         case APCOND_FR_UNIQUEPAGECOUNT:
             $p = FRUserCounters::getParams($user);
             $result = $p && $p['uniqueContentPages'] >= $params[0];
             break;
         case APCOND_FR_EDITSPACING:
             $key = wfMemcKey('flaggedrevs', 'autopromote-editspacing', $user->getId(), $params[0], $params[1]);
             $val = $wgMemc->get($key);
             if ($val === 'true') {
                 $result = true;
                 // passed
             } elseif ($val === 'false') {
                 $result = false;
                 // failed
             } else {
                 # Hit the DB only if the result is not cached...
                 $pass = self::editSpacingCheck($user, $params[0], $params[1]);
                 # Make a key to store the results
                 if ($pass === true) {
                     $wgMemc->set($key, 'true', 14 * 86400);
                 } else {
                     $wgMemc->set($key, 'false', $pass);
                 }
                 $result = $pass === true;
             }
             break;
         case APCOND_FR_EDITCOUNT:
             # $maxNew is the *most* edits that can be too recent
             $maxNew = $user->getEditCount() - $params[0];
             if ($maxNew < 0) {
                 $result = false;
                 // doesn't meet count even *with* recent edits
             } elseif ($params[1] <= 0) {
                 $result = true;
                 // passed; we aren't excluding any recent edits
             } else {
                 # Check all recent edits...
                 $n = self::recentEditCount($user->getId(), $params[1], $maxNew);
                 $result = $n <= $maxNew;
             }
             break;
         case APCOND_FR_CONTENTEDITCOUNT:
             $p = FRUserCounters::getParams($user);
             if (!$p) {
                 $result = false;
             } else {
                 # $maxNew is the *most* edits that can be too recent
                 $maxNew = $p['totalContentEdits'] - $params[0];
                 if ($maxNew < 0) {
                     $result = false;
                     // doesn't meet count even *with* recent edits
                 } elseif ($params[1] <= 0) {
                     $result = true;
                     // passed; we aren't excluding any recent edits
                 } else {
                     # Check all recent content edits...
                     $n = self::recentContentEditCount($user->getId(), $params[1], $maxNew);
                     $result = $n <= $maxNew;
                 }
             }
             break;
         case APCOND_FR_CHECKEDEDITCOUNT:
             $key = wfMemcKey('flaggedrevs', 'autopromote-reviewededits', $user->getId(), $params[0], $params[1]);
             $val = $wgMemc->get($key);
             if ($val === 'true') {
                 $result = true;
                 // passed
             } elseif ($val === 'false') {
                 $result = false;
                 // failed
             } else {
                 # Hit the DB only if the result is not cached...
                 $result = self::reviewedEditsCheck($user, $params[0], $params[1]);
                 if ($result) {
                     $wgMemc->set($key, 'true', 7 * 86400);
                 } else {
                     $wgMemc->set($key, 'false', 3600);
                     // briefly cache
                 }
             }
             break;
         case APCOND_FR_USERPAGEBYTES:
             $result = !$params[0] || $user->getUserPage()->getLength() >= $params[0];
             break;
         case APCOND_FR_MAXREVERTEDEDITRATIO:
             $p = FRUserCounters::getParams($user);
             $result = $p && $params[0] * $user->getEditCount() >= $p['revertedEdits'];
             break;
         case APCOND_FR_NEVERDEMOTED:
             // b/c
             $p = FRUserCounters::getParams($user);
             $result = $p && empty($p['demoted']);
             break;
     }
     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 'review_denied';
     }
     # We can only approve actual revisions...
     if ($this->getAction() === 'approve') {
         $rev = Revision::newFromTitle($this->page, $this->oldid);
         # Check for archived/deleted revisions...
         if (!$rev || $rev->getVisibility()) {
             return 'review_bad_oldid';
         }
         # Check for review conflicts...
         if ($this->lastChangeTime !== null) {
             // API uses null
             $lastChange = $this->oldFrev ? $this->oldFrev->getTimestamp() : '';
             if ($lastChange !== $this->lastChangeTime) {
                 return 'review_conflict_oldid';
             }
         }
         $status = $this->approveRevision($rev, $this->oldFrev);
         # We can only unapprove approved revisions...
     } elseif ($this->getAction() === 'unapprove') {
         # Check for review conflicts...
         if ($this->lastChangeTime !== null) {
             // API uses null
             $lastChange = $this->oldFrev ? $this->oldFrev->getTimestamp() : '';
             if ($lastChange !== $this->lastChangeTime) {
                 return 'review_conflict_oldid';
             }
         }
         # Check if we can find this flagged rev...
         if (!$this->oldFrev) {
             return 'review_not_flagged';
         }
         $status = $this->unapproveRevision($this->oldFrev);
     } elseif ($this->getAction() === 'reject') {
         $newRev = Revision::newFromTitle($this->page, $this->oldid);
         $oldRev = Revision::newFromTitle($this->page, $this->refid);
         # Do not mess with archived/deleted revisions
         if (!$oldRev || $oldRev->isDeleted(Revision::DELETED_TEXT)) {
             return 'review_bad_oldid';
         } elseif (!$newRev || $newRev->isDeleted(Revision::DELETED_TEXT)) {
             return 'review_bad_oldid';
         }
         # Check that the revs are in order
         if ($oldRev->getTimestamp() > $newRev->getTimestamp()) {
             return 'review_cannot_undo';
         }
         # Make sure we are only rejecting pending changes
         $srev = FlaggedRevision::newFromStable($this->page, FR_MASTER);
         if ($srev && $oldRev->getTimestamp() < $srev->getRevTimestamp()) {
             return 'review_cannot_reject';
             // not really a use case
         }
         $article = new WikiPage($this->page);
         # Get text with changes after $oldRev up to and including $newRev removed
         $new_text = $article->getUndoText($newRev, $oldRev);
         if ($new_text === false) {
             return 'review_cannot_undo';
         }
         $baseRevId = $newRev->isCurrent() ? $oldRev->getId() : 0;
         # Actually make the edit...
         $editStatus = $article->doEdit($new_text, $this->getComment(), 0, $baseRevId, $this->user);
         $status = $editStatus->isOK() ? true : 'review_cannot_undo';
         # If this undid one edit by another logged-in user, update user tallies
         if ($status === true && $newRev->getParentId() == $oldRev->getId() && $newRev->getRawUser()) {
             if ($newRev->getRawUser() != $this->user->getId()) {
                 // no self-reverts
                 FRUserCounters::incCount($newRev->getRawUser(), 'revertedEdits');
             }
         }
     }
     # Watch page if set to do so
     if ($status === true) {
         if ($this->user->getOption('flaggedrevswatch') && !$this->page->userIsWatching()) {
             $this->user->addWatch($this->page);
         }
     }
     return $status;
 }