/** * @since 2.2 */ public function getEditInfo(Title $title) { $this->page = new \WikiPage($title); if (class_exists('WikitextContent')) { $content = $this->page->getRevision()->getContent(); $format = $content->getContentHandler()->getDefaultFormat(); return $this->page->prepareContentForEdit($content, null, null, $format); } return $this->page->prepareTextForEdit($this->page->getRevision()->getRawText(), null, null); }
/** * @param WikiPage $page * @param Content $content * @param User $user * @return integer ApiStashEdit::ERROR_* constant * @since 1.25 */ public static function parseAndStash(WikiPage $page, Content $content, User $user) { global $wgMemc; $format = $content->getDefaultFormat(); $editInfo = $page->prepareContentForEdit($content, null, $user, $format, false); if ($editInfo && $editInfo->output) { $key = self::getStashKey($page->getTitle(), $content, $user); list($stashInfo, $ttl) = self::buildStashValue($editInfo->pstContent, $editInfo->output, $editInfo->timestamp); if ($stashInfo) { $ok = $wgMemc->set($key, $stashInfo, $ttl); if ($ok) { wfDebugLog('StashEdit', "Cached parser output for key '{$key}'."); return self::ERROR_NONE; } else { wfDebugLog('StashEdit', "Failed to cache parser output for key '{$key}'."); return self::ERROR_CACHE; } } else { wfDebugLog('StashEdit', "Uncacheable parser output for key '{$key}'."); return self::ERROR_UNCACHEABLE; } } return self::ERROR_PARSE; }
/** * @param WikiPage $page * @param Content $content * @param User $user * @return integer ApiStashEdit::ERROR_* constant * @since 1.25 */ public static function parseAndStash(WikiPage $page, Content $content, User $user) { $cache = ObjectCache::getLocalClusterInstance(); $logger = LoggerFactory::getInstance('StashEdit'); $format = $content->getDefaultFormat(); $editInfo = $page->prepareContentForEdit($content, null, $user, $format, false); if ($editInfo && $editInfo->output) { $key = self::getStashKey($page->getTitle(), $content, $user); list($stashInfo, $ttl) = self::buildStashValue($editInfo->pstContent, $editInfo->output, $editInfo->timestamp); if ($stashInfo) { $ok = $cache->set($key, $stashInfo, $ttl); if ($ok) { $logger->debug("Cached parser output for key '{$key}'."); return self::ERROR_NONE; } else { $logger->error("Failed to cache parser output for key '{$key}'."); return self::ERROR_CACHE; } } else { $logger->info("Uncacheable parser output for key '{$key}'."); return self::ERROR_UNCACHEABLE; } } return self::ERROR_PARSE; }
/** * 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; }
/** * @param WikiPage $page * @param $content Content|string * @param $section string * @param $isContent bool If true, $content is a Content object * @param $oldtext string The content of the revision prior to $content. When * null this will be loaded from the database. * @return bool true if the captcha should run */ function shouldCheck(WikiPage $page, $content, $section, $isContent = false, $oldtext = null) { $title = $page->getTitle(); $this->trigger = ''; if ($oldtext === null) { global $wgRequest; $loadOldtextFlags = $wgRequest->wasPosted() ? Revision::READ_LATEST : Revision::READ_NORMAL; } if ($isContent) { if ($content->getModel() == CONTENT_MODEL_WIKITEXT) { $newtext = $content->getNativeData(); } else { $newtext = null; } $isEmpty = $content->isEmpty(); } else { $newtext = $content; $isEmpty = $content === ''; } global $wgUser; if ($wgUser->isAllowed('skipcaptcha')) { wfDebug("ConfirmEdit: user group allows skipping captcha\n"); return false; } if ($this->isIPWhitelisted()) { return false; } global $wgEmailAuthentication, $ceAllowConfirmedEmail; if ($wgEmailAuthentication && $ceAllowConfirmedEmail && $wgUser->isEmailConfirmed()) { wfDebug("ConfirmEdit: user has confirmed mail, skipping captcha\n"); return false; } if ($this->captchaTriggers($title, 'edit')) { // Check on all edits global $wgUser; $this->trigger = sprintf("edit trigger by '%s' at [[%s]]", $wgUser->getName(), $title->getPrefixedText()); $this->action = 'edit'; wfDebug("ConfirmEdit: checking all edits...\n"); return true; } if ($this->captchaTriggers($title, 'create') && !$title->exists()) { // Check if creating a page global $wgUser; $this->trigger = sprintf("Create trigger by '%s' at [[%s]]", $wgUser->getName(), $title->getPrefixedText()); $this->action = 'create'; wfDebug("ConfirmEdit: checking on page creation...\n"); return true; } if (!$isEmpty && $this->captchaTriggers($title, 'addurl')) { // Only check edits that add URLs if ($isContent) { // Get links from the database $oldLinks = $this->getLinksFromTracker($title); // Share a parse operation with Article::doEdit() $editInfo = $page->prepareContentForEdit($content); if ($editInfo->output) { $newLinks = array_keys($editInfo->output->getExternalLinks()); } else { $newLinks = array(); } } else { // Get link changes in the slowest way known to man $oldtext = isset($oldtext) ? $oldtext : $this->loadText($title, $section, $loadOldtextFlags); $oldLinks = $this->findLinks($title, $oldtext); $newLinks = $this->findLinks($title, $newtext); } $unknownLinks = array_filter($newLinks, array(&$this, 'filterLink')); $addedLinks = array_diff($unknownLinks, $oldLinks); $numLinks = count($addedLinks); if ($numLinks > 0) { global $wgUser; $this->trigger = sprintf("%dx url trigger by '%s' at [[%s]]: %s", $numLinks, $wgUser->getName(), $title->getPrefixedText(), implode(", ", $addedLinks)); $this->action = 'addurl'; return true; } } global $wgCaptchaRegexes; if ($newtext !== null && $wgCaptchaRegexes) { // Custom regex checks. Reuse $oldtext if set above. $oldtext = isset($oldtext) ? $oldtext : $this->loadText($title, $section, $loadOldtextFlags); foreach ($wgCaptchaRegexes as $regex) { $newMatches = array(); if (preg_match_all($regex, $newtext, $newMatches)) { $oldMatches = array(); preg_match_all($regex, $oldtext, $oldMatches); $addedMatches = array_diff($newMatches[0], $oldMatches[0]); $numHits = count($addedMatches); if ($numHits > 0) { global $wgUser; $this->trigger = sprintf("%dx %s at [[%s]]: %s", $numHits, $regex, $wgUser->getName(), $title->getPrefixedText(), implode(", ", $addedMatches)); $this->action = 'edit'; return true; } } } } return false; }
/** * @param WikiPage $page * @param $content Content|string * @param $section string * @param IContextSource $context * @param $oldtext string The content of the revision prior to $content. When * null this will be loaded from the database. * @return bool true if the captcha should run */ function shouldCheck(WikiPage $page, $content, $section, $context, $oldtext = null) { global $ceAllowConfirmedEmail; if (!$context instanceof IContextSource) { $context = RequestContext::getMain(); } $request = $context->getRequest(); $user = $context->getUser(); // captcha check exceptions, which will return always false if ($user->isAllowed('skipcaptcha')) { wfDebug("ConfirmEdit: user group allows skipping captcha\n"); return false; } elseif ($this->isIPWhitelisted()) { wfDebug("ConfirmEdit: user IP is whitelisted"); return false; } elseif ($ceAllowConfirmedEmail && $user->isEmailConfirmed()) { wfDebug("ConfirmEdit: user has confirmed mail, skipping captcha\n"); return false; } $title = $page->getTitle(); $this->trigger = ''; if ($content instanceof Content) { if ($content->getModel() == CONTENT_MODEL_WIKITEXT) { $newtext = $content->getNativeData(); } else { $newtext = null; } $isEmpty = $content->isEmpty(); } else { $newtext = $content; $isEmpty = $content === ''; } if ($this->captchaTriggers($title, 'edit')) { // Check on all edits $this->trigger = sprintf("edit trigger by '%s' at [[%s]]", $user->getName(), $title->getPrefixedText()); $this->action = 'edit'; wfDebug("ConfirmEdit: checking all edits...\n"); return true; } if ($this->captchaTriggers($title, 'create') && !$title->exists()) { // Check if creating a page $this->trigger = sprintf("Create trigger by '%s' at [[%s]]", $user->getName(), $title->getPrefixedText()); $this->action = 'create'; wfDebug("ConfirmEdit: checking on page creation...\n"); return true; } // The following checks are expensive and should be done only, if we can assume, that the edit will be saved if (!$request->wasPosted()) { wfDebug("ConfirmEdit: request not posted, assuming that no content will be saved -> no CAPTCHA check"); return false; } if (!$isEmpty && $this->captchaTriggers($title, 'addurl')) { // Only check edits that add URLs if ($content instanceof Content) { // Get links from the database $oldLinks = $this->getLinksFromTracker($title); // Share a parse operation with Article::doEdit() $editInfo = $page->prepareContentForEdit($content); if ($editInfo->output) { $newLinks = array_keys($editInfo->output->getExternalLinks()); } else { $newLinks = array(); } } else { // Get link changes in the slowest way known to man $oldtext = isset($oldtext) ? $oldtext : $this->loadText($title, $section); $oldLinks = $this->findLinks($title, $oldtext); $newLinks = $this->findLinks($title, $newtext); } $unknownLinks = array_filter($newLinks, array(&$this, 'filterLink')); $addedLinks = array_diff($unknownLinks, $oldLinks); $numLinks = count($addedLinks); if ($numLinks > 0) { $this->trigger = sprintf("%dx url trigger by '%s' at [[%s]]: %s", $numLinks, $user->getName(), $title->getPrefixedText(), implode(", ", $addedLinks)); $this->action = 'addurl'; return true; } } global $wgCaptchaRegexes; if ($newtext !== null && $wgCaptchaRegexes) { if (!is_array($wgCaptchaRegexes)) { throw new UnexpectedValueException('$wgCaptchaRegexes is required to be an array, ' . gettype($wgCaptchaRegexes) . ' given.'); } // Custom regex checks. Reuse $oldtext if set above. $oldtext = isset($oldtext) ? $oldtext : $this->loadText($title, $section); foreach ($wgCaptchaRegexes as $regex) { $newMatches = array(); if (preg_match_all($regex, $newtext, $newMatches)) { $oldMatches = array(); preg_match_all($regex, $oldtext, $oldMatches); $addedMatches = array_diff($newMatches[0], $oldMatches[0]); $numHits = count($addedMatches); if ($numHits > 0) { $this->trigger = sprintf("%dx %s at [[%s]]: %s", $numHits, $regex, $user->getName(), $title->getPrefixedText(), implode(", ", $addedMatches)); $this->action = 'edit'; return true; } } } } return false; }
/** * @param WikiPage $page * @param Content $content Edit content * @param User $user * @param string $summary Edit summary * @return integer ApiStashEdit::ERROR_* constant * @since 1.25 */ public static function parseAndStash(WikiPage $page, Content $content, User $user, $summary) { $cache = ObjectCache::getLocalClusterInstance(); $logger = LoggerFactory::getInstance('StashEdit'); $title = $page->getTitle(); $key = self::getStashKey($title, self::getContentHash($content), $user); // Use the master DB for fast blocking locks $dbw = wfGetDB(DB_MASTER); if (!$dbw->lock($key, __METHOD__, 1)) { // De-duplicate requests on the same key return self::ERROR_BUSY; } /** @noinspection PhpUnusedLocalVariableInspection */ $unlocker = new ScopedCallback(function () use($dbw, $key) { $dbw->unlock($key, __METHOD__); }); $cutoffTime = time() - self::PRESUME_FRESH_TTL_SEC; // Reuse any freshly build matching edit stash cache $editInfo = $cache->get($key); if ($editInfo && wfTimestamp(TS_UNIX, $editInfo->timestamp) >= $cutoffTime) { $alreadyCached = true; } else { $format = $content->getDefaultFormat(); $editInfo = $page->prepareContentForEdit($content, null, $user, $format, false); $alreadyCached = false; } if ($editInfo && $editInfo->output) { // Let extensions add ParserOutput metadata or warm other caches Hooks::run('ParserOutputStashForEdit', [$page, $content, $editInfo->output, $summary, $user]); if ($alreadyCached) { $logger->debug("Already cached parser output for key '{$key}' ('{$title}')."); return self::ERROR_NONE; } list($stashInfo, $ttl, $code) = self::buildStashValue($editInfo->pstContent, $editInfo->output, $editInfo->timestamp, $user); if ($stashInfo) { $ok = $cache->set($key, $stashInfo, $ttl); if ($ok) { $logger->debug("Cached parser output for key '{$key}' ('{$title}')."); return self::ERROR_NONE; } else { $logger->error("Failed to cache parser output for key '{$key}' ('{$title}')."); return self::ERROR_CACHE; } } else { $logger->info("Uncacheable parser output for key '{$key}' ('{$title}') [{$code}]."); return self::ERROR_UNCACHEABLE; } } return self::ERROR_PARSE; }