/**
  * Get a selector of reviewable namespaces
  * @param int $selected, namespace selected
  * @param $all Mixed: Value of an item denoting all namespaces, or null to omit
  * @return string
  */
 public static function getNamespaceMenu($selected = null, $all = null)
 {
     global $wgContLang;
     $namespaces = FlaggedRevs::getReviewNamespaces();
     $s = "<label for='namespace'>" . wfMsgHtml('namespace') . "</label>";
     if ($selected !== '') {
         if (is_null($selected)) {
             # No namespace selected; let exact match work without hitting Main
             $selected = '';
         } else {
             # Let input be numeric strings without breaking the empty match.
             $selected = intval($selected);
         }
     }
     $s .= "\n<select id='namespace' name='namespace' class='namespaceselector'>\n";
     $arr = $wgContLang->getFormattedNamespaces();
     if (!is_null($all)) {
         $arr = array($all => wfMsg('namespacesall')) + $arr;
         // should be first
     }
     foreach ($arr as $index => $name) {
         # Content pages only (except 'all')
         if ($index !== $all && !in_array($index, $namespaces)) {
             continue;
         }
         $name = $index !== 0 ? $name : wfMsg('blanknamespace');
         if ($index === $selected) {
             $s .= "\t" . Xml::element("option", array("value" => $index, "selected" => "selected"), $name) . "\n";
         } else {
             $s .= "\t" . Xml::element("option", array("value" => $index), $name) . "\n";
         }
     }
     $s .= "</select>\n";
     return $s;
 }
 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");
 }
예제 #3
0
 public function getAllowedParams()
 {
     $namespaces = FlaggedRevs::getReviewNamespaces();
     return array('start' => array(ApiBase::PARAM_TYPE => 'integer'), 'end' => array(ApiBase::PARAM_TYPE => 'integer'), 'dir' => array(ApiBase::PARAM_DFLT => 'newer', ApiBase::PARAM_TYPE => array('newer', 'older')), 'namespace' => array(ApiBase::PARAM_DFLT => !$namespaces ? NS_MAIN : $namespaces[0], ApiBase::PARAM_TYPE => 'namespace', ApiBase::PARAM_ISMULTI => true), 'filterredir' => array(ApiBase::PARAM_DFLT => 'all', ApiBase::PARAM_TYPE => array('redirects', 'nonredirects', 'all')), 'filterlevel' => array(ApiBase::PARAM_DFLT => null, ApiBase::PARAM_TYPE => 'integer', ApiBase::PARAM_MIN => 0, ApiBase::PARAM_MAX => 2), 'limit' => array(ApiBase::PARAM_DFLT => 10, ApiBase::PARAM_TYPE => 'limit', ApiBase::PARAM_MIN => 1, ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1, ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2));
 }
 public function getAllowedParams()
 {
     $namespaces = FlaggedRevs::getReviewNamespaces();
     // Replace '' with more readable 'none' in autoreview restiction levels
     $autoreviewLevels = FlaggedRevs::getRestrictionLevels();
     $autoreviewLevels[] = 'none';
     return array('start' => array(ApiBase::PARAM_TYPE => 'integer'), 'end' => array(ApiBase::PARAM_TYPE => 'integer'), 'dir' => array(ApiBase::PARAM_DFLT => 'newer', ApiBase::PARAM_TYPE => array('newer', 'older')), 'namespace' => array(ApiBase::PARAM_DFLT => null, ApiBase::PARAM_TYPE => 'namespace', ApiBase::PARAM_ISMULTI => true), 'default' => array(ApiBase::PARAM_DFLT => null, ApiBase::PARAM_TYPE => array('latest', 'stable')), 'autoreview' => array(ApiBase::PARAM_DFLT => null, ApiBase::PARAM_TYPE => $autoreviewLevels), 'limit' => array(ApiBase::PARAM_DFLT => 10, ApiBase::PARAM_TYPE => 'limit', ApiBase::PARAM_MIN => 1, ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1, ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2));
 }
 /**
  * @param int $namespace (null for "all")
  * @param string $autoreview ('' for "all", 'none' for no restriction)
  */
 function __construct($form, $conds = array(), $namespace, $autoreview, $indef)
 {
     $this->mForm = $form;
     $this->mConds = $conds;
     $this->indef = $indef;
     // Must be content pages...
     $validNS = FlaggedRevs::getReviewNamespaces();
     if (is_integer($namespace)) {
         if (!in_array($namespace, $validNS)) {
             $namespace = $validNS;
             // fallback to "all"
         }
     } else {
         $namespace = $validNS;
         // "all"
     }
     $this->namespace = $namespace;
     if ($autoreview === 'none') {
         $autoreview = '';
         // 'none' => ''
     } elseif ($autoreview === '') {
         $autoreview = null;
         // '' => null
     }
     $this->autoreview = $autoreview;
     parent::__construct();
 }
 function __construct($form, $conds = array(), $type = 0, $namespace = 0, $hideRedirs = 1)
 {
     $this->mForm = $form;
     $this->mConds = $conds;
     $this->type = $type;
     # Must be a content page...
     if (!is_null($namespace)) {
         $namespace = intval($namespace);
     }
     $vnamespaces = FlaggedRevs::getReviewNamespaces();
     if (is_null($namespace) || !in_array($namespace, $vnamespaces)) {
         $namespace = !$vnamespaces ? -1 : $vnamespaces[0];
     }
     $this->namespace = $namespace;
     $this->hideRedirs = $hideRedirs;
     parent::__construct();
 }
 function __construct($form, $live, $namespace, $redirs = false, $category = null, $level = 0)
 {
     $this->mForm = $form;
     $this->live = (bool) $live;
     # Must be a content page...
     if (!is_null($namespace)) {
         $namespace = (int) $namespace;
     }
     $vnamespaces = FlaggedRevs::getReviewNamespaces();
     # Must be a single NS for perfomance reasons
     if (is_null($namespace) || !in_array($namespace, $vnamespaces)) {
         $namespace = !$vnamespaces ? -1 : $vnamespaces[0];
     }
     $this->namespace = $namespace;
     $this->category = $category ? str_replace(' ', '_', $category) : null;
     $this->level = intval($level);
     $this->showredirs = (bool) $redirs;
     parent::__construct();
     // Don't get too expensive
     $this->mLimitsShown = array(20, 50);
     $this->setLimit($this->mLimit);
     // apply max limit
 }
 function __construct($form, $namespace, $level = -1, $category = '', $size = null, $watched = false, $stable = false)
 {
     $this->mForm = $form;
     # Must be a content page...
     $vnamespaces = FlaggedRevs::getReviewNamespaces();
     if (is_null($namespace)) {
         $namespace = $vnamespaces;
     } else {
         $namespace = intval($namespace);
     }
     # Sanity check
     if (!in_array($namespace, $vnamespaces)) {
         $namespace = $vnamespaces;
     }
     $this->namespace = $namespace;
     # Sanity check level: 0 = checked; 1 = quality; 2 = pristine
     $this->level = $level >= 0 && $level <= 2 ? $level : -1;
     $this->category = $category ? str_replace(' ', '_', $category) : null;
     $this->size = $size !== null ? intval($size) : null;
     $this->watched = (bool) $watched;
     $this->stable = $stable && !FlaggedRevs::isStableShownByDefault() && !FlaggedRevs::useOnlyIfProtected();
     parent::__construct();
     # Don't get too expensive
     $this->mLimitsShown = array(20, 50, 100);
     $this->setLimit($this->mLimit);
     // apply max limit
 }
 function __construct($form, $level = -1, $category = '', $tag = '')
 {
     $this->mForm = $form;
     # Must be a content page...
     $this->namespace = FlaggedRevs::getReviewNamespaces();
     # Sanity check level: 0 = checked; 1 = quality; 2 = pristine
     $this->level = $level >= 0 && $level <= 2 ? $level : -1;
     $this->tag = $tag;
     $this->category = $category ? str_replace(' ', '_', $category) : null;
     parent::__construct();
     // Don't get to expensive
     $this->mLimitsShown = array(20, 50, 100);
     $this->setLimit($this->mLimit);
     // apply max limit
 }
 public static function stableDumpQuery(array &$tables, array &$opts, array &$join)
 {
     $namespaces = FlaggedRevs::getReviewNamespaces();
     if ($namespaces) {
         $tables[] = 'flaggedpages';
         $opts['ORDER BY'] = 'fp_page_id ASC';
         $opts['USE INDEX'] = array('flaggedpages' => 'PRIMARY');
         $join['page'] = array('INNER JOIN', array('page_id = fp_page_id', 'page_namespace' => $namespaces));
         $join['revision'] = array('INNER JOIN', 'rev_page = fp_page_id AND rev_id = fp_stable');
     }
     return false;
     // final
 }
 /**
  * Get edit review time statistics (as recent as possible)
  * @param $dbcache Database cache object
  * @param $users string "anons" or "users"
  * @return Array associative
  */
 private static function getEditReviewTimes($dbCache, $users = 'anons')
 {
     $result = array('average' => 0, 'median' => 0, 'percTable' => array(), 'sampleSize' => 0, 'sampleStartTS' => null, 'sampleEndTS' => null);
     if (FlaggedRevs::useOnlyIfProtected()) {
         return $result;
         // disabled
     }
     $aveRT = $medianRT = 0;
     $rPerTable = array();
     // review wait percentiles
     # Only go so far back...otherwise we will get garbage values due to
     # the fact that FlaggedRevs wasn't enabled until after a while.
     $dbr = wfGetDB(DB_SLAVE);
     $installedUnix = (int) $dbr->selectField('logging', 'UNIX_TIMESTAMP( MIN(log_timestamp) )', array('log_type' => 'review'));
     if (!$installedUnix) {
         $installedUnix = wfTimestamp(TS_UNIX);
         // now
     }
     $encInstalled = $dbr->addQuotes($dbr->timestamp($installedUnix));
     # Skip the most recent recent revs as they are likely to just
     # be WHERE condition misses. This also gives us more data to use.
     # Lastly, we want to avoid bias that would make the time too low
     # since new revisions could not have "took a long time to sight".
     $worstLagTS = $dbr->timestamp();
     // now
     $last = '0';
     while (true) {
         // should almost always be ~1 pass
         # Get the page with the worst pending lag...
         $row = $dbr->selectRow(array('flaggedpage_pending', 'flaggedrevs'), array('fpp_page_id', 'fpp_rev_id', 'fpp_pending_since', 'fr_timestamp'), array('fpp_quality' => 0, 'fpp_pending_since > ' . $encInstalled, 'fr_page_id = fpp_page_id AND fr_rev_id = fpp_rev_id', 'fpp_pending_since > ' . $dbr->addQuotes($last)), __METHOD__, array('ORDER BY' => 'fpp_pending_since ASC', 'USE INDEX' => array('flaggedpage_pending' => 'fpp_quality_pending')));
         if (!$row) {
             break;
         }
         # Find the newest revision at the time the page was reviewed,
         # this is the one that *should* have been reviewed.
         $idealRev = (int) $dbr->selectField('revision', 'rev_id', array('rev_page' => $row->fpp_page_id, 'rev_timestamp < ' . $dbr->addQuotes($row->fr_timestamp)), __METHOD__, array('ORDER BY' => 'rev_timestamp DESC', 'LIMIT' => 1));
         if ($row->fpp_rev_id >= $idealRev) {
             $worstLagTS = $row->fpp_pending_since;
             break;
             // sane $worstLagTS found
             # Fudge factor to prevent deliberate reviewing of non-current revisions
             # from squeezing the range. Shouldn't effect anything otherwise.
         } else {
             $last = $row->fpp_pending_since;
             // next iteration
         }
     }
     # User condition (anons/users)
     if ($users === 'anons') {
         $userCondition = 'rev_user = 0';
     } elseif ($users === 'users') {
         $userCondition = 'rev_user = 1';
     } else {
         throw new MWException('Invalid $users param given.');
     }
     # Avoid having to censor data
     # Note: if no edits pending, $worstLagTS is the cur time just before we checked
     # for the worst lag. Thus, new edits *right* after the check are properly excluded.
     $maxTSUnix = wfTimestamp(TS_UNIX, $worstLagTS) - 1;
     // all edits later reviewed
     $encMaxTS = $dbr->addQuotes($dbr->timestamp($maxTSUnix));
     # Use a one week time range
     $days = 7;
     $minTSUnix = $maxTSUnix - $days * 86400;
     $encMinTS = $dbr->addQuotes($dbr->timestamp($minTSUnix));
     # Approximate the number rows to scan
     $rows = $dbr->estimateRowCount('revision', '1', array($userCondition, "rev_timestamp BETWEEN {$encMinTS} AND {$encMaxTS}"));
     # If the range doesn't have many rows (like on small wikis), use 30 days
     if ($rows < 500) {
         $days = 30;
         $minTSUnix = $maxTSUnix - $days * 86400;
         $encMinTS = $dbr->addQuotes($dbr->timestamp($minTSUnix));
         # Approximate rows to scan
         $rows = $dbr->estimateRowCount('revision', '1', array($userCondition, "rev_timestamp BETWEEN {$encMinTS} AND {$encMaxTS}"));
         # If the range doesn't have many rows (like on really tiny wikis), use 90 days
         if ($rows < 500) {
             $days = 90;
             $minTSUnix = $maxTSUnix - $days * 86400;
         }
     }
     $sampleSize = 1500;
     // sample size
     # Sanity check the starting timestamp
     $minTSUnix = max($minTSUnix, $installedUnix);
     $encMinTS = $dbr->addQuotes($dbr->timestamp($minTSUnix));
     # Get timestamp boundaries
     $timeCondition = "rev_timestamp BETWEEN {$encMinTS} AND {$encMaxTS}";
     # Get mod for edit spread
     $ecKey = wfMemcKey('flaggedrevs', 'anonEditCount', $days);
     $edits = (int) $dbCache->get($ecKey);
     if (!$edits) {
         $edits = (int) $dbr->selectField(array('page', 'revision'), 'COUNT(*)', array($userCondition, $timeCondition, 'page_id = rev_page', 'page_namespace' => FlaggedRevs::getReviewNamespaces()));
         $dbCache->set($ecKey, $edits, 14 * 24 * 3600);
         // cache for 2 weeks
     }
     $mod = max(floor($edits / $sampleSize), 1);
     # $mod >= 1
     # For edits that started off pending, how long do they take to get reviewed?
     # Edits started off pending if made when a flagged rev of the page already existed.
     # Get the *first* reviewed rev *after* each edit and get the time difference.
     $res = $dbr->select(array('revision', 'p' => 'flaggedrevs', 'n' => 'flaggedrevs'), array('MIN(rev_timestamp) AS rt', 'MIN(n.fr_timestamp) AS nft', 'MAX(p.fr_rev_id)'), array($userCondition, $timeCondition, "(rev_id % {$mod}) = 0"), __METHOD__, array('GROUP BY' => array('rev_timestamp', 'rev_id'), 'USE INDEX' => array('revision' => 'user_timestamp'), 'STRAIGHT_JOIN'), array('p' => array('INNER JOIN', array('p.fr_page_id = rev_page', 'p.fr_rev_id < rev_id', 'p.fr_timestamp < rev_timestamp')), 'n' => array('INNER JOIN', array('n.fr_page_id = rev_page', 'n.fr_rev_id >= rev_id', 'n.fr_timestamp >= rev_timestamp'))));
     $secondsR = 0;
     // total wait seconds for edits later reviewed
     $secondsP = 0;
     // total wait seconds for edits still pending
     $aveRT = $medianRT = 0;
     $times = array();
     if ($dbr->numRows($res)) {
         # Get the elapsed times revs were pending (flagged time - edit time)
         foreach ($res as $row) {
             $time = wfTimestamp(TS_UNIX, $row->nft) - wfTimestamp(TS_UNIX, $row->rt);
             $time = max($time, 0);
             // sanity
             $secondsR += $time;
             $times[] = $time;
         }
         $sampleSize = count($times);
         $aveRT = ($secondsR + $secondsP) / $sampleSize;
         // sample mean
         sort($times);
         // order smallest -> largest
         // Sample median
         $rank = round(count($times) / 2 + 0.5) - 1;
         $medianRT = $times[$rank];
         // Make percentile tabulation data
         $doPercentiles = array(35, 45, 55, 65, 75, 85, 90, 95);
         foreach ($doPercentiles as $percentile) {
             $rank = round($percentile * count($times) / 100 + 0.5) - 1;
             $rPerTable[$percentile] = $times[$rank];
         }
         $result['average'] = $aveRT;
         $result['median'] = $medianRT;
         $result['percTable'] = $rPerTable;
         $result['sampleSize'] = count($times);
         $result['sampleStartTS'] = $minTSUnix;
         $result['sampleEndTS'] = $maxTSUnix;
     }
     return $result;
 }
 public function execute($par)
 {
     global $wgContLang, $wgFlaggedRevsStats, $wgFlaggedRevsProtection;
     $out = $this->getOutput();
     $lang = $this->getLanguage();
     $this->setHeaders();
     $this->db = wfGetDB(DB_SLAVE);
     $this->maybeUpdate();
     $ec = $this->getEditorCount();
     $rc = $this->getReviewerCount();
     $mt = $this->getMeanReviewWaitAnon();
     $mdt = $this->getMedianReviewWaitAnon();
     $pt = $this->getMeanPendingWait();
     $pData = $this->getReviewPercentilesAnon();
     $timestamp = $this->getLastUpdate();
     $out->addWikiMsg('validationstatistics-users', $lang->formatnum($ec), $lang->formatnum($rc));
     # Most of the output depends on background queries
     if (!$this->readyForQuery()) {
         return false;
     }
     # Is there a review time table available?
     if (count($pData)) {
         $headerRows = $dataRows = '';
         foreach ($pData as $percentile => $perValue) {
             $headerRows .= "<th>P<sub>" . intval($percentile) . "</sub></th>";
             $dataRows .= '<td>' . $lang->formatTimePeriod($perValue, 'avoidminutes') . '</td>';
         }
         $css = 'wikitable flaggedrevs_stats_table';
         $reviewChart = "<table class='{$css}' style='white-space: nowrap;'>\n";
         $reviewChart .= "<tr align='center'>{$headerRows}</tr>\n";
         $reviewChart .= "<tr align='center'>{$dataRows}</tr>\n";
         $reviewChart .= "</table>\n";
     } else {
         $reviewChart = '';
     }
     if ($timestamp != '-') {
         # Show "last updated"...
         $out->addWikiMsg('validationstatistics-lastupdate', $lang->date($timestamp, true), $lang->time($timestamp, true));
     }
     $out->addHtml('<hr/>');
     # Show pending time stats...
     $out->addWikiMsg('validationstatistics-pndtime', $lang->formatTimePeriod($pt, 'avoidminutes'));
     # Show review time stats...
     if (!FlaggedRevs::useOnlyIfProtected()) {
         $out->addWikiMsg('validationstatistics-revtime', $lang->formatTimePeriod($mt, 'avoidminutes'), $lang->formatTimePeriod($mdt, 'avoidminutes'), $reviewChart);
     }
     # Show per-namespace stats table...
     $out->addWikiMsg('validationstatistics-table');
     $out->addHTML(Xml::openElement('table', array('class' => 'wikitable flaggedrevs_stats_table')));
     $out->addHTML("<tr>\n");
     // Headings (for a positive grep result):
     // validationstatistics-ns, validationstatistics-total, validationstatistics-stable,
     // validationstatistics-latest, validationstatistics-synced, validationstatistics-old,
     // validationstatistics-unreviewed
     $msgs = array('ns', 'total', 'stable', 'latest', 'synced', 'old');
     // our headings
     if (!$wgFlaggedRevsProtection) {
         $msgs[] = 'unreviewed';
     }
     foreach ($msgs as $msg) {
         $out->addHTML('<th>' . $this->msg("validationstatistics-{$msg}")->parse() . '</th>');
     }
     $out->addHTML("</tr>\n");
     $namespaces = FlaggedRevs::getReviewNamespaces();
     foreach ($namespaces as $namespace) {
         $total = $this->getTotalPages($namespace);
         $reviewed = $this->getReviewedPages($namespace);
         $synced = $this->getSyncedPages($namespace);
         if ($total === '-' || $reviewed === '-' || $synced === '-') {
             continue;
             // NS added to config recently?
         }
         $NsText = $wgContLang->getFormattedNsText($namespace);
         $NsText = $NsText ? $NsText : $this->msg('blanknamespace')->escaped();
         $percRev = intval($total) == 0 ? '-' : $this->msg('parentheses', $this->msg('percent')->numParams(sprintf('%4.2f', 100 * intval($reviewed) / intval($total)))->escaped())->text();
         $percLatest = intval($total) == 0 ? '-' : $this->msg('parentheses', $this->msg('percent')->numParams(sprintf('%4.2f', 100 * intval($synced) / intval($total)))->escaped())->text();
         $percSynced = intval($reviewed) == 0 ? '-' : $this->msg('percent')->numParams(sprintf('%4.2f', 100 * intval($synced) / intval($reviewed)))->escaped();
         $outdated = intval($reviewed) - intval($synced);
         $outdated = $lang->formatnum(max(0, $outdated));
         // lag between queries
         $unreviewed = intval($total) - intval($reviewed);
         $unreviewed = $lang->formatnum(max(0, $unreviewed));
         // lag between queries
         $out->addHTML("<tr align='center'>\n\t\t\t\t\t<td>" . htmlspecialchars($NsText) . "</td>\n\t\t\t\t\t<td>" . htmlspecialchars($lang->formatnum($total)) . "</td>\n\t\t\t\t\t<td>" . htmlspecialchars($lang->formatnum($reviewed) . $wgContLang->getDirMark()) . " <i>{$percRev}</i>\n\t\t\t\t\t</td>\n\t\t\t\t\t<td>" . htmlspecialchars($lang->formatnum($synced) . $wgContLang->getDirMark()) . " <i>{$percLatest}</i>\n\t\t\t\t\t</td>\n\t\t\t\t\t<td>" . $percSynced . "</td>\n\t\t\t\t\t<td>" . Linker::linkKnown(SpecialPage::getTitleFor('PendingChanges'), htmlspecialchars($outdated), array(), array('namespace' => $namespace)) . "</td>");
         if (!$wgFlaggedRevsProtection) {
             $out->addHTML("\n\t\t\t\t\t<td>" . Linker::linkKnown(SpecialPage::getTitleFor('UnreviewedPages'), htmlspecialchars($unreviewed), array(), array('namespace' => $namespace)) . "</td>");
         }
         $out->addHTML("\n\t\t\t\t</tr>");
     }
     $out->addHTML(Xml::closeElement('table'));
     # Is there a top X user list? If so, then show it...
     $data = $this->getTopReviewers();
     if (is_array($data) && count($data)) {
         $out->addWikiMsg('validationstatistics-utable', $lang->formatNum($wgFlaggedRevsStats['topReviewersCount']), $lang->formatNum($wgFlaggedRevsStats['topReviewersHours']));
         $css = 'wikitable flaggedrevs_stats_table';
         $reviewChart = "<table class='{$css}' style='white-space: nowrap;'>\n";
         $reviewChart .= '<tr><th>' . $this->msg('validationstatistics-user')->escaped() . '</th><th>' . $this->msg('validationstatistics-reviews')->escaped() . '</th></tr>';
         foreach ($data as $userId => $reviews) {
             $reviewChart .= '<tr><td>' . htmlspecialchars(User::whois($userId)) . '</td><td>' . $lang->formatNum($reviews) . '</td></tr>';
         }
         $reviewChart .= "</table>\n";
         $out->addHTML($reviewChart);
     }
     return true;
 }
 protected static function maybeAddBacklogNotice(OutputPage &$out)
 {
     global $wgUser;
     if (!$wgUser->isAllowed('review')) {
         return true;
         // not relevant to user
     }
     $namespaces = FlaggedRevs::getReviewNamespaces();
     $watchlist = SpecialPage::getTitleFor('Watchlist');
     # Add notice to watchlist about pending changes...
     if ($out->getTitle()->equals($watchlist) && $namespaces) {
         $dbr = wfGetDB(DB_SLAVE, 'watchlist');
         // consistency with watchlist
         $watchedOutdated = (bool) $dbr->selectField(array('watchlist', 'page', 'flaggedpages'), '1', array('wl_user' => $wgUser->getId(), 'wl_namespace' => $namespaces, 'wl_namespace = page_namespace', 'wl_title = page_title', 'fp_page_id = page_id', 'fp_pending_since IS NOT NULL'), __METHOD__);
         # Give a notice if pages on the users's wachlist have pending edits
         if ($watchedOutdated) {
             $css = 'plainlinks fr-watchlist-pending-notice';
             $out->prependHTML("<div id='mw-fr-watchlist-pending-notice' class='{$css}'>" . wfMsgExt('flaggedrevs-watched-pending', 'parseinline') . "</div>");
         }
     }
     return true;
 }