/**
  * Checks if the stable version is synced with the current revision
  * Note: slower than getPendingRevCount()
  * @return bool
  */
 public function stableVersionIsSynced()
 {
     global $wgMemc, $wgParserCacheExpireTime;
     $srev = $this->getStableRev();
     if (!$srev) {
         return true;
     }
     # Stable text revision must be the same as the current
     if ($this->revsArePending()) {
         return false;
         # Stable file revision must be the same as the current
     } elseif ($this->mTitle->getNamespace() == NS_FILE) {
         $file = $this->getFile();
         // current upload version
         if ($file && $file->getTimestamp() > $srev->getFileTimestamp()) {
             return false;
         }
     }
     # If using the current version of includes, there is nothing else to check.
     if (FlaggedRevs::inclusionSetting() == FR_INCLUDES_CURRENT) {
         return true;
         // short-circuit
     }
     # Try the cache...
     $key = wfMemcKey('flaggedrevs', 'includesSynced', $this->getId());
     $value = FlaggedRevs::getMemcValue($wgMemc->get($key), $this);
     if ($value === "true") {
         return true;
     } elseif ($value === "false") {
         return false;
     }
     # Since the stable and current revisions have the same text and only outputs,
     # the only other things to check for are template and file differences in the output.
     # (a) Check if the current output has a newer template/file used
     # (b) Check if the stable version has a file/template that was deleted
     $synced = !$srev->findPendingTemplateChanges() && !$srev->findPendingFileChanges('noForeign');
     # Save to cache. This will be updated whenever the page is touched.
     $data = FlaggedRevs::makeMemcObj($synced ? "true" : "false");
     $wgMemc->set($key, $data, $wgParserCacheExpireTime);
     return $synced;
 }
 /**
  * (a) Stabilize inclusions in Parser output
  * (b) Load all of the "review time" versions of template/files from $frev
  * (c) Load their stable version counterparts (avoids DB hits)
  * Note: Used when calling FlaggedRevs::parseStableText().
  * @param FlaggedRevision $frev
  * @return void
  */
 public function stabilizeParserOutput(FlaggedRevision $frev)
 {
     $tStbVersions = $fStbVersions = array();
     // stable versions
     $tRevVersions = $frev->getTemplateVersions();
     $fRevVersions = $frev->getFileVersions();
     # We can preload *most* of the stable version IDs the parser will need...
     if (FlaggedRevs::inclusionSetting() == FR_INCLUDES_STABLE) {
         $tStbVersions = $frev->getStableTemplateVersions();
         $fStbVersions = $frev->getStableFileVersions();
     }
     $this->setReviewedVersions($tRevVersions, $fRevVersions);
     $this->setStableVersionCache($tStbVersions, $fStbVersions);
 }
 /**
  * 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
         }
     }
 }
 /**
  * Automatically review an revision and add a log entry in the review log.
  *
  * This is called during edit operations after the new revision is added
  * and the page tables updated, but before LinksUpdate is called.
  *
  * $auto is here for revisions checked off to be reviewed. Auto-review
  * triggers on edit, but we don't want those to count as just automatic.
  * This also makes it so the user's name shows up in the page history.
  *
  * If $flags is given, then they will be the review tags. If not, the one
  * from the stable version will be used or minimal tags if that's not possible.
  * If no appropriate tags can be found, then the review will abort.
  */
 public static function autoReviewEdit(WikiPage $article, $user, Revision $rev, array $flags = null, $auto = true)
 {
     wfProfileIn(__METHOD__);
     $title = $article->getTitle();
     // convenience
     # Get current stable version ID (for logging)
     $oldSv = FlaggedRevision::newFromStable($title, FR_MASTER);
     $oldSvId = $oldSv ? $oldSv->getRevId() : 0;
     # Set the auto-review tags from the prior stable version.
     # Normally, this should already be done and given here...
     if (!is_array($flags)) {
         if ($oldSv) {
             # Use the last stable version if $flags not given
             if ($user->isAllowed('bot')) {
                 $flags = $oldSv->getTags();
                 // no change for bot edits
             } else {
                 # Account for perms/tags...
                 $flags = self::getAutoReviewTags($user, $oldSv->getTags());
             }
         } else {
             // new page?
             $flags = self::quickTags(FR_CHECKED);
             // use minimal level
         }
         if (!is_array($flags)) {
             wfProfileOut(__METHOD__);
             return false;
             // can't auto-review this revision
         }
     }
     # Get review property flags
     $propFlags = $auto ? array('auto') : array();
     # Note: this needs to match the prepareContentForEdit() call WikiPage::doEditContent.
     # This is for consistency and also to avoid triggering a second parse otherwise.
     $editInfo = $article->prepareContentForEdit($rev->getContent(), null, $user, $rev->getContentFormat());
     $poutput = $editInfo->output;
     // revision HTML output
     # Get the "review time" versions of templates and files.
     # This tries to make sure each template/file version either came from the stable
     # version of that template/file or was a "review time" version used in the stable
     # version of this page. If a pending version of a template/file is currently vandalism,
     # we try to avoid storing its ID as the "review time" version so it won't show up when
     # someone views the page. If not possible, this stores the current template/file.
     if (FlaggedRevs::inclusionSetting() === FR_INCLUDES_CURRENT) {
         $tVersions = $poutput->getTemplateIds();
         $fVersions = $poutput->getFileSearchOptions();
     } else {
         $tVersions = $oldSv ? $oldSv->getTemplateVersions() : array();
         $fVersions = $oldSv ? $oldSv->getFileVersions() : array();
         foreach ($poutput->getTemplateIds() as $ns => $pages) {
             foreach ($pages as $dbKey => $revId) {
                 if (!isset($tVersions[$ns][$dbKey])) {
                     $srev = FlaggedRevision::newFromStable(Title::makeTitle($ns, $dbKey));
                     if ($srev) {
                         // use stable
                         $tVersions[$ns][$dbKey] = $srev->getRevId();
                     } else {
                         // use current
                         $tVersions[$ns][$dbKey] = $revId;
                     }
                 }
             }
         }
         foreach ($poutput->getFileSearchOptions() as $dbKey => $info) {
             if (!isset($fVersions[$dbKey])) {
                 $srev = FlaggedRevision::newFromStable(Title::makeTitle(NS_FILE, $dbKey));
                 if ($srev && $srev->getFileTimestamp()) {
                     // use stable
                     $fVersions[$dbKey]['time'] = $srev->getFileTimestamp();
                     $fVersions[$dbKey]['sha1'] = $srev->getFileSha1();
                 } else {
                     // use current
                     $fVersions[$dbKey]['time'] = $info['time'];
                     $fVersions[$dbKey]['sha1'] = $info['sha1'];
                 }
             }
         }
     }
     # If this is an image page, get the corresponding file version info...
     $fileData = array('name' => null, 'timestamp' => null, 'sha1' => null);
     if ($title->getNamespace() == NS_FILE) {
         # We must use WikiFilePage process cache on upload or get bitten by slave lag
         $file = $article instanceof WikiFilePage || $article instanceof ImagePage ? $article->getFile() : wfFindFile($title, array('bypassCache' => true));
         // skip cache; bug 31056
         if (is_object($file) && $file->exists()) {
             $fileData['name'] = $title->getDBkey();
             $fileData['timestamp'] = $file->getTimestamp();
             $fileData['sha1'] = $file->getSha1();
         }
     }
     # Our review entry
     $flaggedRevision = new FlaggedRevision(array('rev' => $rev, 'user_id' => $user->getId(), 'timestamp' => $rev->getTimestamp(), 'quality' => FlaggedRevs::getQualityTier($flags, 0), 'tags' => FlaggedRevision::flattenRevisionTags($flags), 'img_name' => $fileData['name'], 'img_timestamp' => $fileData['timestamp'], 'img_sha1' => $fileData['sha1'], 'templateVersions' => $tVersions, 'fileVersions' => $fVersions, 'flags' => implode(',', $propFlags)));
     $flaggedRevision->insert();
     # Update the article review log
     FlaggedRevsLog::updateReviewLog($title, $flags, array(), '', $rev->getId(), $oldSvId, true, $auto);
     # Update page and tracking tables and clear cache
     FlaggedRevs::stableVersionUpdates($article);
     wfProfileOut(__METHOD__);
     return true;
 }
 /**
  * Fetch pending file changes for this reviewed page
  * version against a list of current versions of files.
  * See findPendingFileChanges() for details.
  *
  * @param array $newFiles
  * @param bool|string $noForeign Using 'noForeign' skips foreign file updates (bug 15748)
  * @return array of (title, MW file timestamp in reviewed version, has stable rev) tuples
  */
 public function findFileChanges(array $newFiles, $noForeign = false)
 {
     if (FlaggedRevs::inclusionSetting() == FR_INCLUDES_CURRENT) {
         return array();
         // short-circuit
     }
     $fileChanges = array();
     $rFiles = $this->getFileVersions();
     $sFiles = $this->getStableFileVersions();
     foreach ($newFiles as $dbKey => $sha1Time) {
         $reviewedTS = isset($rFiles[$dbKey]['time']) ? $rFiles[$dbKey]['time'] : null;
         $stableTS = isset($sFiles[$dbKey]['time']) ? $sFiles[$dbKey]['time'] : null;
         # Get file timestamp used in this FlaggedRevision when parsed
         $usedTS = self::fileTimestampUsed($stableTS, $reviewedTS);
         # Check for edits/creations/deletions...
         $title = Title::makeTitleSafe(NS_FILE, $dbKey);
         if (self::fileChanged($title, $usedTS, $noForeign)) {
             $fileChanges[] = array($title, $usedTS, (bool) $stableTS);
         }
     }
     return $fileChanges;
 }
 /**
  * Update the page tables with a new stable version.
  * @param Title $title
  * @param FlaggedRevision|null $sv, the new stable version (optional)
  * @param FlaggedRevision|null $oldSv, the old stable version (optional)
  * @param Object editInfo Article edit info about the current revision (optional)
  * @return bool stable version text/file changed and FR_INCLUDES_STABLE
  */
 public static function stableVersionUpdates(Title $title, $sv = null, $oldSv = null, $editInfo = null)
 {
     $changed = false;
     if ($oldSv === null) {
         // optional
         $oldSv = FlaggedRevision::newFromStable($title, FR_MASTER);
     }
     if ($sv === null) {
         // optional
         $sv = FlaggedRevision::determineStable($title, FR_MASTER);
     }
     $article = new FlaggableWikiPage($title);
     if (!$sv) {
         # Empty flaggedrevs data for this page if there is no stable version
         $article->clearStableVersion();
         # Check if pages using this need to be refreshed...
         if (FlaggedRevs::inclusionSetting() == FR_INCLUDES_STABLE) {
             $changed = (bool) $oldSv;
         }
     } else {
         # Update flagged page related fields
         $article->updateStableVersion($sv, $editInfo ? $editInfo->revid : null);
         # Check if pages using this need to be invalidated/purged...
         if (FlaggedRevs::inclusionSetting() == FR_INCLUDES_STABLE) {
             $changed = !$oldSv || $sv->getRevId() != $oldSv->getRevId() || $sv->getFileTimestamp() != $oldSv->getFileTimestamp() || $sv->getFileSha1() != $oldSv->getFileSha1();
         }
         # Update template/file version cache...
         if ($editInfo && $sv->getRevId() != $editInfo->revid) {
             FRInclusionCache::setRevIncludes($title, $editInfo->revid, $editInfo->output);
         }
     }
     # Lazily rebuild dependancies on next parse (we invalidate below)
     FlaggedRevs::clearStableOnlyDeps($title->getArticleID());
     # Clear page cache
     $title->invalidateCache();
     self::purgeSquid($title);
     return $changed;
 }
 /**
  * Tag output function must be called by caller
  * Parser cache control deferred to caller
  * @param \FlaggedRevision|\stable $srev stable version
  * @param string $tag review box/bar info
  * @param string $prot protection notice
  * @return ParserOutput
  */
 protected function showStableVersion(FlaggedRevision $srev, &$tag, $prot)
 {
     $reqUser = $this->getUser();
     $this->load();
     $flags = $srev->getTags();
     $time = $this->getLanguage()->date($srev->getTimestamp(), true);
     # Set display revision ID
     $this->out->setRevisionId($srev->getRevId());
     # Get quality level
     $quality = FlaggedRevs::isQuality($flags);
     $synced = $this->article->stableVersionIsSynced();
     # Construct some tagging
     if (!$this->out->isPrintable() && !($this->article->lowProfileUI() && $synced)) {
         $revsSince = $this->article->getPendingRevCount();
         // Simple icon-based UI
         if ($this->useSimpleUI()) {
             $icon = '';
             # For protection based configs, show lock only if it's not redundant.
             if ($this->showRatingIcon()) {
                 $icon = FlaggedRevsXML::stableStatusIcon($quality);
             }
             if (!$reqUser->getId()) {
                 $msgHTML = '';
                 // Anons just see simple icons
             } else {
                 $msg = $quality ? 'revreview-quick-quality' : 'revreview-quick-basic';
                 # Uses messages 'revreview-quick-quality-same', 'revreview-quick-basic-same'
                 $msg = $synced ? "{$msg}-same" : $msg;
                 $msgHTML = $this->msg($msg, $srev->getRevId())->numParams($revsSince)->parse();
             }
             $msgHTML = $prot . $icon . $msgHTML;
             $tag = FlaggedRevsXML::prettyRatingBox($srev, $msgHTML, $revsSince, 'stable', $synced);
             // Standard UI
         } else {
             $icon = FlaggedRevsXML::stableStatusIcon($quality);
             $msg = $quality ? 'revreview-quality' : 'revreview-basic';
             if ($synced) {
                 # uses messages 'revreview-quality-same', 'revreview-basic-same'
                 $msg .= '-same';
             } elseif ($revsSince == 0) {
                 # uses messages 'revreview-quality-i', 'revreview-basic-i'
                 $msg .= '-i';
             }
             $tag = $prot . $icon;
             $tag .= $this->msg($msg, $srev->getRevId(), $time)->numParams($revsSince)->parse();
             if (!empty($flags)) {
                 $tag .= FlaggedRevsXML::ratingToggle();
                 $tag .= "<div id='mw-fr-revisiondetails'>" . FlaggedRevsXML::addTagRatings($flags) . '</div>';
             }
         }
     }
     # Get parsed stable version and output HTML
     $pOpts = $this->article->makeParserOptions($reqUser);
     if (!$this->article->getTitle()->quickUserCan('edit', $reqUser)) {
         $pOpts->setEditSection(false);
     }
     $parserCache = FRParserCacheStable::singleton();
     $parserOut = $parserCache->get($this->article, $pOpts);
     # Do not use the parser cache if it lacks mImageTimeKeys and there is a
     # chance that a review form will be added to this page (which requires the versions).
     $canReview = $this->article->getTitle()->userCan('review');
     if ($parserOut && (!$canReview || FlaggedRevs::parserOutputIsVersioned($parserOut))) {
         # Cache hit. Note that redirects are not cached.
         $this->out->addParserOutput($parserOut);
     } else {
         # Get the new stable parser output...
         if (FlaggedRevs::inclusionSetting() == FR_INCLUDES_CURRENT && $synced) {
             # We can try the current version cache, since they are the same revision
             $parserOut = ParserCache::singleton()->get($this->article, $pOpts);
         } else {
             $parserOut = false;
         }
         if (!$parserOut) {
             $parserOut = FlaggedRevs::parseStableRevision($srev, $pOpts);
         }
         if (!$parserOut) {
             // serious error
             $this->out->addWikiMsg('missing-article', $this->article->getTitle()->getPrefixedText(), $this->msg('missingarticle-rev', $srev->getRevId())->text());
             return null;
         }
         # Update the stable version cache
         $parserCache->save($parserOut, $this->article, $pOpts);
         # Add the stable output to the page view
         $this->out->addParserOutput($parserOut);
         # Update the stable version dependancies
         FlaggedRevs::updateStableOnlyDeps($this->article, $parserOut);
     }
     # Update page sync status for tracking purposes.
     # NOTE: avoids master hits and doesn't have to be perfect for what it does
     if ($this->article->syncedInTracking() != $synced) {
         if (wfGetLB()->safeGetLag(wfGetDB(DB_SLAVE)) <= 5) {
             // avoid write-delay cycles
             $this->article->updateSyncStatus($synced);
         }
     }
     return $parserOut;
 }
 /**
  * Select the desired images based on the selected stable version time/SHA-1
  */
 public static function parserFetchStableFile($parser, Title $title, &$options, &$query)
 {
     if (!$parser instanceof Parser) {
         return true;
         // nothing to do
     }
     $incManager = FRInclusionManager::singleton();
     if (!$incManager->parserOutputIsStabilized()) {
         return true;
         // trigger for stable version parsing only
     }
     # Normalize NS_MEDIA to NS_FILE
     if ($title->getNamespace() == NS_MEDIA) {
         $title = Title::makeTitle(NS_FILE, $title->getDBkey());
         $title->resetArticleId($title->getArticleID());
         // avoid extra queries
     }
     # Check if this file is only on a foreign repo
     $file = wfFindFile($title);
     if ($file && !$file->isLocal()) {
         return true;
         // just use the current version (bug 41832)
     }
     $time = $sha1 = false;
     // unspecified (defaults to current version)
     # Check for the version of this file used when reviewed...
     list($maybeTS, $maybeSha1) = $incManager->getReviewedFileVersion($title);
     if ($maybeTS !== null) {
         $time = $maybeTS;
         // use if specified (even '0')
         $sha1 = $maybeSha1;
     }
     # Check for stable version of file if this feature is enabled...
     if (FlaggedRevs::inclusionSetting() == FR_INCLUDES_STABLE) {
         list($maybeTS, $maybeSha1) = $incManager->getStableFileVersion($title);
         # Take the newest of these two...
         if ($maybeTS && $maybeTS > $time) {
             $time = $maybeTS;
             $sha1 = $maybeSha1;
         }
     }
     # Tell Parser what file version to use
     if ($time === '0') {
         $options['broken'] = true;
     } elseif ($time !== false) {
         $options['time'] = $time;
         $options['sha1'] = $sha1;
         # Stabilize the file link
         if ($query != '') {
             $query .= '&';
         }
         $query .= "filetimestamp=" . urlencode(wfTimestamp(TS_MW, $time));
     }
     return true;
 }