/**
  * Add unreviewed pages links
  */
 public function addToCategoryView()
 {
     $reqUser = $this->getUser();
     $this->load();
     if (!$reqUser->isAllowed('review')) {
         return true;
     }
     if (!FlaggedRevs::useOnlyIfProtected()) {
         # Add links to lists of unreviewed pages and pending changes in this category
         $category = $this->article->getTitle()->getText();
         $this->out->appendSubtitle(Html::rawElement('span', array('class' => 'plainlinks', 'id' => 'mw-fr-category-oldreviewed'), wfMsgExt('flaggedrevs-categoryview', 'parseinline', urlencode($category))));
     }
     return true;
 }
 /**
  * Purge expired restrictions from the flaggedpage_config table.
  * The stable version of pages may change and invalidation may be required.
  */
 public static function purgeExpiredConfigurations()
 {
     if (wfReadOnly()) {
         return;
     }
     $dbw = wfGetDB(DB_MASTER);
     # Find pages with expired configs...
     $config = self::getDefaultVisibilitySettings();
     // config is to be reset
     $encCutoff = $dbw->addQuotes($dbw->timestamp());
     $ret = $dbw->select(array('flaggedpage_config', 'page'), array('fpc_page_id', 'page_namespace', 'page_title'), array('page_id = fpc_page_id', 'fpc_expiry < ' . $encCutoff), __METHOD__);
     # Figured out to do with each page...
     $pagesClearConfig = array();
     $pagesClearTracking = $titlesClearTracking = array();
     foreach ($ret as $row) {
         # If FlaggedRevs got "turned off" (in protection config)
         # for this page, then clear it from the tracking tables...
         if (FlaggedRevs::useOnlyIfProtected() && !$config['override']) {
             $pagesClearTracking[] = $row->fpc_page_id;
             // no stable version
             $titlesClearTracking[] = Title::newFromRow($row);
             // no stable version
         }
         $pagesClearConfig[] = $row->fpc_page_id;
         // page with expired config
     }
     # Clear the expired config for these pages...
     if (count($pagesClearConfig)) {
         $dbw->delete('flaggedpage_config', array('fpc_page_id' => $pagesClearConfig, 'fpc_expiry < ' . $encCutoff), __METHOD__);
     }
     # Clear the tracking rows and update page_touched for the
     # pages in $pagesClearConfig that do now have a stable version...
     if (count($pagesClearTracking)) {
         FlaggedRevs::clearTrackingRows($pagesClearTracking);
         $dbw->update('page', array('page_touched' => $dbw->timestamp()), array('page_id' => $pagesClearTracking), __METHOD__);
     }
     # Also, clear their squid caches and purge other pages that use this page.
     # NOTE: all of these updates are deferred via $wgDeferredUpdateList.
     foreach ($titlesClearTracking as $title) {
         FlaggedRevs::purgeSquid($title);
         if (FlaggedRevs::inclusionSetting() == FR_INCLUDES_STABLE) {
             FlaggedRevs::HTMLCacheUpdates($title);
             // purge pages that use this page
         }
     }
 }
 /**
  * Is this article reviewable?
  * @return bool
  */
 public function isReviewable()
 {
     if (!FlaggedRevs::inReviewNamespace($this->mTitle)) {
         return false;
     }
     # Check if flagging is disabled for this page via config
     if (FlaggedRevs::useOnlyIfProtected()) {
         $config = $this->getStabilitySettings();
         // page configuration
         return (bool) $config['override'];
         // stable is default or flagging disabled
     }
     return true;
 }
 /**
  * Get a FlaggedRevision of the stable version of a title.
  * Skips tracking tables to figure out new stable version.
  * @param Title $title, page title
  * @param int $flags (FR_MASTER, FR_FOR_UPDATE)
  * @param array $config, optional page config (use to skip queries)
  * @param string $precedence (latest,quality,pristine)
  * @return FlaggedRevision|null (null on failure)
  */
 public static function determineStable(Title $title, $flags = 0, $config = array(), $precedence = 'latest')
 {
     if (!FlaggedRevs::inReviewNamespace($title)) {
         return null;
         // short-circuit
     }
     $options = array();
     # User master/slave as appropriate...
     if ($flags & FR_FOR_UPDATE || $flags & FR_MASTER) {
         $db = wfGetDB(DB_MASTER);
         if ($flags & FR_FOR_UPDATE) {
             $options[] = 'FOR UPDATE';
         }
         $pageId = $title->getArticleID(Title::GAID_FOR_UPDATE);
     } else {
         $db = wfGetDB(DB_SLAVE);
         $pageId = $title->getArticleID();
     }
     if (!$pageId) {
         return null;
         // short-circuit query
     }
     # Get visiblity settings to see if page is reviewable...
     if (FlaggedRevs::useOnlyIfProtected()) {
         if (empty($config)) {
             $config = FRPageConfig::getStabilitySettings($title, $flags);
         }
         if (!$config['override']) {
             return null;
             // page is not reviewable; no stable version
         }
     }
     $baseConds = array('fr_page_id' => $pageId, 'rev_id = fr_rev_id', 'rev_page = fr_page_id', $db->bitAnd('rev_deleted', Revision::DELETED_TEXT) . ' = 0');
     $options['ORDER BY'] = 'fr_rev_timestamp DESC';
     $row = null;
     if ($precedence !== 'latest') {
         # Look for the latest pristine revision...
         if (FlaggedRevs::pristineVersions()) {
             $prow = $db->selectRow(array('flaggedrevs', 'revision'), self::selectFields(), array_merge($baseConds, array('fr_quality' => FR_PRISTINE)), __METHOD__, $options);
             # Looks like a plausible revision
             $row = $prow ? $prow : $row;
         }
         if ($row && $precedence === 'pristine') {
             // we have what we want already
             # Look for the latest quality revision...
         } elseif (FlaggedRevs::qualityVersions()) {
             // If we found a pristine rev above, this one must be newer...
             $newerClause = $row ? array('fr_rev_timestamp > ' . $db->addQuotes($row->fr_rev_timestamp)) : array();
             $qrow = $db->selectRow(array('flaggedrevs', 'revision'), self::selectFields(), array_merge($baseConds, array('fr_quality' => FR_QUALITY), $newerClause), __METHOD__, $options);
             $row = $qrow ? $qrow : $row;
         }
     }
     # Do we have one? If not, try the latest reviewed revision...
     if (!$row) {
         $row = $db->selectRow(array('flaggedrevs', 'revision'), self::selectFields(), $baseConds, __METHOD__, $options);
         if (!$row) {
             return null;
         }
     }
     $frev = new self($row);
     $frev->mTitle = $title;
     return $frev;
 }
 /**
  * Get log params (associate array) from a stability config
  * @param array $config
  * @return array (associative)
  */
 public static function stabilityLogParams(array $config)
 {
     $params = $config;
     if (!FlaggedRevs::useOnlyIfProtected()) {
         $params['precedence'] = 1;
         // b/c hack for presenting log params...
     }
     return $params;
 }
 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
 }
 /**
  * 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;
 }
 /**
  * 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;
 }
 public static function addToChangeListLine(&$list, &$articlelink, &$s, RecentChange &$rc)
 {
     global $wgUser;
     $title = $rc->getTitle();
     // convenience
     if (!FlaggedRevs::inReviewNamespace($title) || empty($rc->mAttribs['rc_this_oldid']) || !array_key_exists('fp_stable', $rc->mAttribs)) {
         return true;
         // confirm that page is in reviewable namespace
     }
     $rlink = $css = '';
     // page is not reviewed
     if ($rc->mAttribs['fp_stable'] == null) {
         // Is this a config were pages start off reviewable?
         // Hide notice from non-reviewers due to vandalism concerns (bug 24002).
         if (!FlaggedRevs::useOnlyIfProtected() && $wgUser->isAllowed('review')) {
             $rlink = wfMsgHtml('revreview-unreviewedpage');
             $css = 'flaggedrevs-unreviewed';
         }
         // page is reviewed and has pending edits (use timestamps; bug 15515)
     } elseif (isset($rc->mAttribs['fp_pending_since']) && $rc->mAttribs['rc_timestamp'] >= $rc->mAttribs['fp_pending_since']) {
         $rlink = $list->skin->link($title, wfMsgHtml('revreview-reviewlink'), array('title' => wfMsg('revreview-reviewlink-title')), array('oldid' => $rc->mAttribs['fp_stable'], 'diff' => 'cur') + FlaggedRevs::diffOnlyCGI());
         $css = 'flaggedrevs-pending';
     }
     if ($rlink != '') {
         $articlelink .= " <span class=\"mw-fr-reviewlink {$css}\">[{$rlink}]</span>";
     }
     return true;
 }