/**
  * Get template and image versions from parsing a revision
  * @param Page $article
  * @param Revision $rev
  * @param User $user
  * @param string $regen use 'regen' to force regeneration
  * @return array( templateIds, fileSHA1Keys )
  * templateIds like ParserOutput->mTemplateIds
  * fileSHA1Keys like ParserOutput->mImageTimeKeys
  */
 public static function getRevIncludes(Page $article, Revision $rev, User $user, $regen = '')
 {
     global $wgMemc;
     wfProfileIn(__METHOD__);
     $key = self::getCacheKey($article->getTitle(), $rev->getId());
     if ($regen === 'regen') {
         $versions = false;
         // skip cache
     } elseif ($rev->isCurrent()) {
         // Check cache entry against page_touched
         $versions = FlaggedRevs::getMemcValue($wgMemc->get($key), $article);
     } else {
         // Old revs won't always be invalidated with template/file changes.
         // Also, we don't care if page_touched changed due to a direct edit.
         $versions = FlaggedRevs::getMemcValue($wgMemc->get($key), $article, 'allowStale');
         if (is_array($versions)) {
             // entry exists
             // Sanity check that the cache is reasonably up to date
             list($templates, $files) = $versions;
             if (self::templatesStale($templates) || self::filesStale($files)) {
                 $versions = false;
                 // no good
             }
         }
     }
     if (!is_array($versions)) {
         // cache miss
         $pOut = false;
         if ($rev->isCurrent()) {
             $parserCache = ParserCache::singleton();
             # Try current version parser cache for this user...
             $pOut = $parserCache->get($article, $article->makeParserOptions($user));
             if ($pOut == false) {
                 # Try current version parser cache for the revision author...
                 $optsUser = $rev->getUser() ? User::newFromId($rev->getUser()) : 'canonical';
                 $pOut = $parserCache->get($article, $article->makeParserOptions($optsUser));
             }
         }
         // ParserOutput::mImageTimeKeys wasn't always there
         if ($pOut == false || !FlaggedRevs::parserOutputIsVersioned($pOut)) {
             $content = $rev->getContent(Revision::RAW);
             if (!$content) {
                 // Just for extra sanity
                 $pOut = new ParserOutput();
             } else {
                 $pOut = $content->getParserOutput($article->getTitle(), $rev->getId(), ParserOptions::newFromUser($user));
             }
         }
         # Get the template/file versions used...
         $versions = array($pOut->getTemplateIds(), $pOut->getFileSearchOptions());
         # Save to cache (check cache expiry for dynamic elements)...
         $data = FlaggedRevs::makeMemcObj($versions);
         $wgMemc->set($key, $data, $pOut->getCacheExpiry());
     }
     wfProfileOut(__METHOD__);
     return $versions;
 }
Esempio n. 2
0
 /**
  * Lazy initialization of article text from DB
  */
 protected function initText()
 {
     if (!isset($this->mText)) {
         if ($this->mRevision != null) {
             $this->mText = SearchEngine::create()->getTextFromContent($this->mTitle, $this->mRevision->getContent());
         } else {
             // TODO: can we fetch raw wikitext for commons images?
             $this->mText = '';
         }
     }
 }
 /**
  * Load the text of the new revision, not the old one
  *
  * @return bool
  */
 public function loadNewText()
 {
     if ($this->mTextLoaded >= 1) {
         return true;
     }
     $this->mTextLoaded = 1;
     if (!$this->loadRevisionData()) {
         return false;
     }
     $this->mNewContent = $this->mNewRev->getContent(Revision::FOR_THIS_USER, $this->getUser());
     return true;
 }
Esempio n. 4
0
 /**
  * Callback function for each revision, preprocessToObj()
  * @param Revision $rev
  */
 public function processRevision($rev)
 {
     $content = $rev->getContent(Revision::RAW);
     if ($content->getModel() !== CONTENT_MODEL_WIKITEXT) {
         return;
     }
     try {
         $this->mPreprocessor->preprocessToObj(strval($content->getNativeData()), 0);
     } catch (Exception $e) {
         $this->error("Caught exception " . $e->getMessage() . " in " . $rev->getTitle()->getPrefixedText());
     }
 }
 public static function runForTitleInternal(Title $title, Revision $revision, $fname)
 {
     global $wgContLang;
     wfProfileIn($fname . '-parse');
     $options = ParserOptions::newFromUserAndLang(new User(), $wgContLang);
     $content = $revision->getContent();
     $parserOutput = $content->getParserOutput($title, $revision->getId(), $options, false);
     wfProfileOut($fname . '-parse');
     wfProfileIn($fname . '-update');
     $updates = $content->getSecondaryDataUpdates($title, null, false, $parserOutput);
     DataUpdate::runUpdates($updates);
     wfProfileOut($fname . '-update');
 }
Esempio n. 6
0
 /**
  * @param $title Title
  * @param $revision Revision
  * @param $fname string
  * @return void
  */
 public static function runForTitleInternal(Title $title, Revision $revision, $fname)
 {
     wfProfileIn($fname);
     $content = $revision->getContent(Revision::RAW);
     if (!$content) {
         // if there is no content, pretend the content is empty
         $content = $revision->getContentHandler()->makeEmptyContent();
     }
     // Revision ID must be passed to the parser output to get revision variables correct
     $parserOutput = $content->getParserOutput($title, $revision->getId(), null, false);
     $updates = $content->getSecondaryDataUpdates($title, null, false, $parserOutput);
     DataUpdate::runUpdates($updates);
     InfoAction::invalidateCache($title);
     wfProfileOut($fname);
 }
 public function doDBUpdates()
 {
     $db = $this->getDB(DB_MASTER);
     if (!$db->tableExists('revision')) {
         $this->error("revision table does not exist", true);
     } else {
         if (!$db->fieldExists('revision', 'rev_sha1', __METHOD__)) {
             $this->output("rev_sha1 column does not exist\n\n", true);
             return false;
         }
     }
     $this->output("Populating rev_len column\n");
     $start = $db->selectField('revision', 'MIN(rev_id)', false, __METHOD__);
     $end = $db->selectField('revision', 'MAX(rev_id)', false, __METHOD__);
     if (!$start || !$end) {
         $this->output("...revision table seems to be empty.\n");
         return true;
     }
     # Do remaining chunks
     $blockStart = intval($start);
     $blockEnd = intval($start) + $this->mBatchSize - 1;
     $count = 0;
     $missing = 0;
     $fields = Revision::selectFields();
     while ($blockStart <= $end) {
         $this->output("...doing rev_id from {$blockStart} to {$blockEnd}\n");
         $res = $db->select('revision', $fields, array("rev_id >= {$blockStart}", "rev_id <= {$blockEnd}", "rev_len IS NULL"), __METHOD__);
         # Go through and update rev_len from these rows.
         foreach ($res as $row) {
             $rev = new Revision($row);
             $content = $rev->getContent();
             if (!$content) {
                 # This should not happen, but sometimes does (bug 20757)
                 $this->output("Content of revision {$row->rev_id} unavailable!\n");
                 $missing++;
             } else {
                 # Update the row...
                 $db->update('revision', array('rev_len' => $content->getSize()), array('rev_id' => $row->rev_id), __METHOD__);
                 $count++;
             }
         }
         $blockStart += $this->mBatchSize;
         $blockEnd += $this->mBatchSize;
         wfWaitForSlaves();
     }
     $this->output("rev_len population complete ... {$count} rows changed ({$missing} missing)\n");
     return true;
 }
Esempio n. 8
0
 /**
  * Do standard deferred updates after page edit.
  * Update links tables, site stats, search index and message cache.
  * Purges pages that include this page if the text was changed here.
  * Every 100th edit, prune the recent changes table.
  *
  * @param $revision Revision object
  * @param $user User object that did the revision
  * @param $options Array of options, following indexes are used:
  * - changed: boolean, whether the revision changed the content (default true)
  * - created: boolean, whether the revision created the page (default false)
  * - oldcountable: boolean or null (default null):
  *   - boolean: whether the page was counted as an article before that
  *     revision, only used in changed is true and created is false
  *   - null: don't change the article count
  */
 public function doEditUpdates(Revision $revision, User $user, array $options = array())
 {
     global $wgEnableParserCache;
     wfProfileIn(__METHOD__);
     $options += array('changed' => true, 'created' => false, 'oldcountable' => null);
     $content = $revision->getContent();
     # Parse the text
     # Be careful not to double-PST: $text is usually already PST-ed once
     if (!$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag('vary-revision')) {
         wfDebug(__METHOD__ . ": No prepared edit or vary-revision is set...\n");
         $editInfo = $this->prepareContentForEdit($content, $revision->getId(), $user);
     } else {
         wfDebug(__METHOD__ . ": No vary-revision, using prepared edit...\n");
         $editInfo = $this->mPreparedEdit;
     }
     # Save it to the parser cache
     if ($wgEnableParserCache) {
         $parserCache = ParserCache::singleton();
         $parserCache->save($editInfo->output, $this, $editInfo->popts);
     }
     # Update the links tables and other secondary data
     $updates = $content->getSecondaryDataUpdates($this->getTitle(), null, true, $editInfo->output);
     DataUpdate::runUpdates($updates);
     wfRunHooks('ArticleEditUpdates', array(&$this, &$editInfo, $options['changed']));
     if (wfRunHooks('ArticleEditUpdatesDeleteFromRecentchanges', array(&$this))) {
         if (0 == mt_rand(0, 99)) {
             // Flush old entries from the `recentchanges` table; we do this on
             // random requests so as to avoid an increase in writes for no good reason
             global $wgRCMaxAge;
             $dbw = wfGetDB(DB_MASTER);
             $cutoff = $dbw->timestamp(time() - $wgRCMaxAge);
             $dbw->delete('recentchanges', array("rc_timestamp < '{$cutoff}'"), __METHOD__);
         }
     }
     if (!$this->mTitle->exists()) {
         wfProfileOut(__METHOD__);
         return;
     }
     $id = $this->getId();
     $title = $this->mTitle->getPrefixedDBkey();
     $shortTitle = $this->mTitle->getDBkey();
     if (!$options['changed']) {
         $good = 0;
         $total = 0;
     } elseif ($options['created']) {
         $good = (int) $this->isCountable($editInfo);
         $total = 1;
     } elseif ($options['oldcountable'] !== null) {
         $good = (int) $this->isCountable($editInfo) - (int) $options['oldcountable'];
         $total = 0;
     } else {
         $good = 0;
         $total = 0;
     }
     DeferredUpdates::addUpdate(new SiteStatsUpdate(0, 1, $good, $total));
     DeferredUpdates::addUpdate(new SearchUpdate($id, $title, $content->getTextForSearchIndex()));
     #@TODO: let the search engine decide what to do with the content object
     # If this is another user's talk page, update newtalk.
     # Don't do this if $options['changed'] = false (null-edits) nor if
     # it's a minor edit and the user doesn't want notifications for those.
     if ($options['changed'] && $this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $user->getTitleKey() && !($revision->isMinor() && $user->isAllowed('nominornewtalk'))) {
         if (wfRunHooks('ArticleEditUpdateNewTalk', array(&$this))) {
             $other = User::newFromName($shortTitle, false);
             if (!$other) {
                 wfDebug(__METHOD__ . ": invalid username\n");
             } elseif (User::isIP($shortTitle)) {
                 // An anonymous user
                 $other->setNewtalk(true, $revision);
             } elseif ($other->isLoggedIn()) {
                 $other->setNewtalk(true, $revision);
             } else {
                 wfDebug(__METHOD__ . ": don't need to notify a nonexistent user\n");
             }
         }
     }
     if ($this->mTitle->getNamespace() == NS_MEDIAWIKI) {
         #XXX: could skip pseudo-messages like js/css here, based on content model.
         $msgtext = $content->getWikitextForTransclusion();
         if ($msgtext === false || $msgtext === null) {
             $msgtext = '';
         }
         MessageCache::singleton()->replace($shortTitle, $msgtext);
     }
     if ($options['created']) {
         self::onArticleCreate($this->mTitle);
     } else {
         self::onArticleEdit($this->mTitle);
     }
     wfProfileOut(__METHOD__);
 }
 public function onSubmit(array $data)
 {
     global $wgContLang;
     if ($data['pagetitle'] === '') {
         // Initial form view of special page, pass
         return false;
     }
     // At this point, it has to be a POST request. This is enforced by HTMLForm,
     // but lets be safe verify that.
     if (!$this->getRequest()->wasPosted()) {
         throw new RuntimeException("Form submission was not POSTed");
     }
     $this->title = Title::newFromText($data['pagetitle']);
     $user = $this->getUser();
     // Check permissions and make sure the user has permission to edit the specific page
     $errors = $this->title->getUserPermissionsErrors('editcontentmodel', $user);
     $errors = wfMergeErrorArrays($errors, $this->title->getUserPermissionsErrors('edit', $user));
     if ($errors) {
         $out = $this->getOutput();
         $wikitext = $out->formatPermissionsErrorMessage($errors);
         // Hack to get our wikitext parsed
         return Status::newFatal(new RawMessage('$1', array($wikitext)));
     }
     $page = WikiPage::factory($this->title);
     if ($this->oldRevision === null) {
         $this->oldRevision = $page->getRevision() ?: false;
     }
     $oldModel = $this->title->getContentModel();
     if ($this->oldRevision) {
         $oldContent = $this->oldRevision->getContent();
         try {
             $newContent = ContentHandler::makeContent($oldContent->getNativeData(), $this->title, $data['model']);
         } catch (MWException $e) {
             return Status::newFatal($this->msg('changecontentmodel-cannot-convert')->params($this->title->getPrefixedText(), ContentHandler::getLocalizedName($data['model'])));
         }
     } else {
         // Page doesn't exist, create an empty content object
         $newContent = ContentHandler::getForModelID($data['model'])->makeEmptyContent();
     }
     $flags = $this->oldRevision ? EDIT_UPDATE : EDIT_NEW;
     if ($user->isAllowed('bot')) {
         $flags |= EDIT_FORCE_BOT;
     }
     $log = new ManualLogEntry('contentmodel', 'change');
     $log->setPerformer($user);
     $log->setTarget($this->title);
     $log->setComment($data['reason']);
     $log->setParameters(array('4::oldmodel' => $oldModel, '5::newmodel' => $data['model']));
     $formatter = LogFormatter::newFromEntry($log);
     $formatter->setContext(RequestContext::newExtraneousContext($this->title));
     $reason = $formatter->getPlainActionText();
     if ($data['reason'] !== '') {
         $reason .= $this->msg('colon-separator')->inContentLanguage()->text() . $data['reason'];
     }
     # Truncate for whole multibyte characters.
     $reason = $wgContLang->truncate($reason, 255);
     $status = $page->doEditContent($newContent, $reason, $flags, $this->oldRevision ? $this->oldRevision->getId() : false, $user);
     if (!$status->isOK()) {
         return $status;
     }
     $logid = $log->insert();
     $log->publish($logid);
     return $status;
 }
Esempio n. 10
0
 /**
  * @covers Revision::getContent
  */
 public function testGetContent_failure()
 {
     $rev = new Revision(['page' => $this->the_page->getId(), 'content_model' => $this->the_page->getContentModel(), 'text_id' => 123456789]);
     $this->assertNull($rev->getContent(), "getContent() should return null if the revision's text blob could not be loaded.");
     // NOTE: check this twice, once for lazy initialization, and once with the cached value.
     $this->assertNull($rev->getContent(), "getContent() should return null if the revision's text blob could not be loaded.");
 }
 /**
  * @param Title $title
  * @param Revision $rev
  * @param string $parseTimestamp TS_MW
  *
  * @return string[] category names
  */
 private function getCategoriesAtRev(Title $title, Revision $rev, $parseTimestamp)
 {
     $content = $rev->getContent();
     $options = $content->getContentHandler()->makeParserOptions('canonical');
     $options->setTimestamp($parseTimestamp);
     // This could possibly use the parser cache if it checked the revision ID,
     // but that's more complicated than it's worth.
     $output = $content->getParserOutput($title, $rev->getId(), $options);
     // array keys will cast numeric category names to ints
     // so we need to cast them back to strings to avoid breaking things!
     return array_map('strval', array_keys($output->getCategories()));
 }
Esempio n. 12
0
 /**
  * Callback function for each revision, parse with both parsers and compare
  * @param Revision $rev
  */
 public function processRevision($rev)
 {
     $title = $rev->getTitle();
     $parser1Name = $this->getOption('parser1');
     $parser2Name = $this->getOption('parser2');
     self::checkParserLocally($parser1Name);
     self::checkParserLocally($parser2Name);
     $parser1 = new $parser1Name();
     $parser2 = new $parser2Name();
     $content = $rev->getContent();
     if ($content->getModel() !== CONTENT_MODEL_WIKITEXT) {
         $this->error("Page {$title->getPrefixedText()} does not contain wikitext " . "but {$content->getModel()}\n");
         return;
     }
     $text = strval($content->getNativeData());
     $output1 = $parser1->parse($text, $title, $this->options);
     $output2 = $parser2->parse($text, $title, $this->options);
     if ($output1->getText() != $output2->getText()) {
         $this->failed++;
         $this->error("Parsing for {$title->getPrefixedText()} differs\n");
         if ($this->saveFailed) {
             file_put_contents($this->saveFailed . '/' . rawurlencode($title->getPrefixedText()) . ".txt", $text);
         }
         if ($this->showDiff) {
             $this->output(wfDiff($this->stripParameters($output1->getText()), $this->stripParameters($output2->getText()), ''));
         }
     } else {
         $this->output($title->getPrefixedText() . "\tOK\n");
         if ($this->showParsedOutput) {
             $this->output($this->stripParameters($output1->getText()));
         }
     }
 }
Esempio n. 13
0
 /**
  * Do standard deferred updates after page edit.
  * Update links tables, site stats, search index and message cache.
  * Purges pages that include this page if the text was changed here.
  * Every 100th edit, prune the recent changes table.
  *
  * @param Revision $revision
  * @param User $user User object that did the revision
  * @param array $options Array of options, following indexes are used:
  * - changed: boolean, whether the revision changed the content (default true)
  * - created: boolean, whether the revision created the page (default false)
  * - moved: boolean, whether the page was moved (default false)
  * - oldcountable: boolean, null, or string 'no-change' (default null):
  *   - boolean: whether the page was counted as an article before that
  *     revision, only used in changed is true and created is false
  *   - null: if created is false, don't update the article count; if created
  *     is true, do update the article count
  *   - 'no-change': don't update the article count, ever
  */
 public function doEditUpdates(Revision $revision, User $user, array $options = array())
 {
     $options += array('changed' => true, 'created' => false, 'moved' => false, 'oldcountable' => null);
     $content = $revision->getContent();
     // Parse the text
     // Be careful not to do pre-save transform twice: $text is usually
     // already pre-save transformed once.
     if (!$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag('vary-revision')) {
         wfDebug(__METHOD__ . ": No prepared edit or vary-revision is set...\n");
         $editInfo = $this->prepareContentForEdit($content, $revision, $user);
     } else {
         wfDebug(__METHOD__ . ": No vary-revision, using prepared edit...\n");
         $editInfo = $this->mPreparedEdit;
     }
     // Save it to the parser cache.
     // Make sure the cache time matches page_touched to avoid double parsing.
     ParserCache::singleton()->save($editInfo->output, $this, $editInfo->popts, $revision->getTimestamp(), $editInfo->revid);
     // Update the links tables and other secondary data
     if ($content) {
         $recursive = $options['changed'];
         // bug 50785
         $updates = $content->getSecondaryDataUpdates($this->getTitle(), null, $recursive, $editInfo->output);
         foreach ($updates as $update) {
             if ($update instanceof LinksUpdate) {
                 $update->setRevision($revision);
                 $update->setTriggeringUser($user);
             }
             DeferredUpdates::addUpdate($update);
         }
     }
     Hooks::run('ArticleEditUpdates', array(&$this, &$editInfo, $options['changed']));
     if (Hooks::run('ArticleEditUpdatesDeleteFromRecentchanges', array(&$this))) {
         // Flush old entries from the `recentchanges` table
         if (mt_rand(0, 9) == 0) {
             JobQueueGroup::singleton()->lazyPush(RecentChangesUpdateJob::newPurgeJob());
         }
     }
     if (!$this->exists()) {
         return;
     }
     $id = $this->getId();
     $title = $this->mTitle->getPrefixedDBkey();
     $shortTitle = $this->mTitle->getDBkey();
     if ($options['oldcountable'] === 'no-change' || !$options['changed'] && !$options['moved']) {
         $good = 0;
     } elseif ($options['created']) {
         $good = (int) $this->isCountable($editInfo);
     } elseif ($options['oldcountable'] !== null) {
         $good = (int) $this->isCountable($editInfo) - (int) $options['oldcountable'];
     } else {
         $good = 0;
     }
     $edits = $options['changed'] ? 1 : 0;
     $total = $options['created'] ? 1 : 0;
     DeferredUpdates::addUpdate(new SiteStatsUpdate(0, $edits, $good, $total));
     DeferredUpdates::addUpdate(new SearchUpdate($id, $title, $content));
     // If this is another user's talk page, update newtalk.
     // Don't do this if $options['changed'] = false (null-edits) nor if
     // it's a minor edit and the user doesn't want notifications for those.
     if ($options['changed'] && $this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $user->getTitleKey() && !($revision->isMinor() && $user->isAllowed('nominornewtalk'))) {
         $recipient = User::newFromName($shortTitle, false);
         if (!$recipient) {
             wfDebug(__METHOD__ . ": invalid username\n");
         } else {
             // Allow extensions to prevent user notification
             // when a new message is added to their talk page
             if (Hooks::run('ArticleEditUpdateNewTalk', array(&$this, $recipient))) {
                 if (User::isIP($shortTitle)) {
                     // An anonymous user
                     $recipient->setNewtalk(true, $revision);
                 } elseif ($recipient->isLoggedIn()) {
                     $recipient->setNewtalk(true, $revision);
                 } else {
                     wfDebug(__METHOD__ . ": don't need to notify a nonexistent user\n");
                 }
             }
         }
     }
     if ($this->mTitle->getNamespace() == NS_MEDIAWIKI) {
         // XXX: could skip pseudo-messages like js/css here, based on content model.
         $msgtext = $content ? $content->getWikitextForTransclusion() : null;
         if ($msgtext === false || $msgtext === null) {
             $msgtext = '';
         }
         MessageCache::singleton()->replace($shortTitle, $msgtext);
     }
     if ($options['created']) {
         self::onArticleCreate($this->mTitle);
     } elseif ($options['changed']) {
         // bug 50785
         self::onArticleEdit($this->mTitle, $revision);
     }
 }
Esempio n. 14
0
 /**
  * Build a diff display between this and the previous either deleted
  * or non-deleted edit.
  *
  * @param Revision $previousRev
  * @param Revision $currentRev
  * @return string HTML
  */
 function showDiff($previousRev, $currentRev)
 {
     $diffContext = clone $this->getContext();
     $diffContext->setTitle($currentRev->getTitle());
     $diffContext->setWikiPage(WikiPage::factory($currentRev->getTitle()));
     $diffEngine = $currentRev->getContentHandler()->createDifferenceEngine($diffContext);
     $diffEngine->showDiffStyle();
     $formattedDiff = $diffEngine->generateContentDiffBody($previousRev->getContent(Revision::FOR_THIS_USER, $this->getUser()), $currentRev->getContent(Revision::FOR_THIS_USER, $this->getUser()));
     $formattedDiff = $diffEngine->addHeader($formattedDiff, $this->diffHeader($previousRev, 'o'), $this->diffHeader($currentRev, 'n'));
     $this->getOutput()->addHTML("<div>{$formattedDiff}</div>\n");
 }
Esempio n. 15
0
 /**
  * Do standard deferred updates after page edit.
  * Update links tables, site stats, search index and message cache.
  * Purges pages that include this page if the text was changed here.
  * Every 100th edit, prune the recent changes table.
  *
  * @param Revision $revision
  * @param User $user User object that did the revision
  * @param array $options Array of options, following indexes are used:
  * - changed: boolean, whether the revision changed the content (default true)
  * - created: boolean, whether the revision created the page (default false)
  * - moved: boolean, whether the page was moved (default false)
  * - restored: boolean, whether the page was undeleted (default false)
  * - oldrevision: Revision object for the pre-update revision (default null)
  * - oldcountable: boolean, null, or string 'no-change' (default null):
  *   - boolean: whether the page was counted as an article before that
  *     revision, only used in changed is true and created is false
  *   - null: if created is false, don't update the article count; if created
  *     is true, do update the article count
  *   - 'no-change': don't update the article count, ever
  */
 public function doEditUpdates(Revision $revision, User $user, array $options = [])
 {
     global $wgRCWatchCategoryMembership, $wgContLang;
     $options += ['changed' => true, 'created' => false, 'moved' => false, 'restored' => false, 'oldrevision' => null, 'oldcountable' => null];
     $content = $revision->getContent();
     $logger = LoggerFactory::getInstance('SaveParse');
     // See if the parser output before $revision was inserted is still valid
     $editInfo = false;
     if (!$this->mPreparedEdit) {
         $logger->debug(__METHOD__ . ": No prepared edit...\n");
     } elseif ($this->mPreparedEdit->output->getFlag('vary-revision')) {
         $logger->info(__METHOD__ . ": Prepared edit has vary-revision...\n");
     } elseif ($this->mPreparedEdit->output->getFlag('vary-revision-id') && $this->mPreparedEdit->output->getSpeculativeRevIdUsed() !== $revision->getId()) {
         $logger->info(__METHOD__ . ": Prepared edit has vary-revision-id with wrong ID...\n");
     } elseif ($this->mPreparedEdit->output->getFlag('vary-user') && !$options['changed']) {
         $logger->info(__METHOD__ . ": Prepared edit has vary-user and is null...\n");
     } else {
         wfDebug(__METHOD__ . ": Using prepared edit...\n");
         $editInfo = $this->mPreparedEdit;
     }
     if (!$editInfo) {
         // Parse the text again if needed. Be careful not to do pre-save transform twice:
         // $text is usually already pre-save transformed once. Avoid using the edit stash
         // as any prepared content from there or in doEditContent() was already rejected.
         $editInfo = $this->prepareContentForEdit($content, $revision, $user, null, false);
     }
     // Save it to the parser cache.
     // Make sure the cache time matches page_touched to avoid double parsing.
     ParserCache::singleton()->save($editInfo->output, $this, $editInfo->popts, $revision->getTimestamp(), $editInfo->revid);
     // Update the links tables and other secondary data
     if ($content) {
         $recursive = $options['changed'];
         // bug 50785
         $updates = $content->getSecondaryDataUpdates($this->getTitle(), null, $recursive, $editInfo->output);
         foreach ($updates as $update) {
             if ($update instanceof LinksUpdate) {
                 $update->setRevision($revision);
                 $update->setTriggeringUser($user);
             }
             DeferredUpdates::addUpdate($update);
         }
         if ($wgRCWatchCategoryMembership && $this->getContentHandler()->supportsCategories() === true && ($options['changed'] || $options['created']) && !$options['restored']) {
             // Note: jobs are pushed after deferred updates, so the job should be able to see
             // the recent change entry (also done via deferred updates) and carry over any
             // bot/deletion/IP flags, ect.
             JobQueueGroup::singleton()->lazyPush(new CategoryMembershipChangeJob($this->getTitle(), ['pageId' => $this->getId(), 'revTimestamp' => $revision->getTimestamp()]));
         }
     }
     Hooks::run('ArticleEditUpdates', [&$this, &$editInfo, $options['changed']]);
     if (Hooks::run('ArticleEditUpdatesDeleteFromRecentchanges', [&$this])) {
         // Flush old entries from the `recentchanges` table
         if (mt_rand(0, 9) == 0) {
             JobQueueGroup::singleton()->lazyPush(RecentChangesUpdateJob::newPurgeJob());
         }
     }
     if (!$this->exists()) {
         return;
     }
     $id = $this->getId();
     $title = $this->mTitle->getPrefixedDBkey();
     $shortTitle = $this->mTitle->getDBkey();
     if ($options['oldcountable'] === 'no-change' || !$options['changed'] && !$options['moved']) {
         $good = 0;
     } elseif ($options['created']) {
         $good = (int) $this->isCountable($editInfo);
     } elseif ($options['oldcountable'] !== null) {
         $good = (int) $this->isCountable($editInfo) - (int) $options['oldcountable'];
     } else {
         $good = 0;
     }
     $edits = $options['changed'] ? 1 : 0;
     $total = $options['created'] ? 1 : 0;
     DeferredUpdates::addUpdate(new SiteStatsUpdate(0, $edits, $good, $total));
     DeferredUpdates::addUpdate(new SearchUpdate($id, $title, $content));
     // If this is another user's talk page, update newtalk.
     // Don't do this if $options['changed'] = false (null-edits) nor if
     // it's a minor edit and the user doesn't want notifications for those.
     if ($options['changed'] && $this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $user->getTitleKey() && !($revision->isMinor() && $user->isAllowed('nominornewtalk'))) {
         $recipient = User::newFromName($shortTitle, false);
         if (!$recipient) {
             wfDebug(__METHOD__ . ": invalid username\n");
         } else {
             // Allow extensions to prevent user notification
             // when a new message is added to their talk page
             if (Hooks::run('ArticleEditUpdateNewTalk', [&$this, $recipient])) {
                 if (User::isIP($shortTitle)) {
                     // An anonymous user
                     $recipient->setNewtalk(true, $revision);
                 } elseif ($recipient->isLoggedIn()) {
                     $recipient->setNewtalk(true, $revision);
                 } else {
                     wfDebug(__METHOD__ . ": don't need to notify a nonexistent user\n");
                 }
             }
         }
     }
     if ($this->mTitle->getNamespace() == NS_MEDIAWIKI) {
         // XXX: could skip pseudo-messages like js/css here, based on content model.
         $msgtext = $content ? $content->getWikitextForTransclusion() : null;
         if ($msgtext === false || $msgtext === null) {
             $msgtext = '';
         }
         MessageCache::singleton()->replace($shortTitle, $msgtext);
         if ($wgContLang->hasVariants()) {
             $wgContLang->updateConversionTable($this->mTitle);
         }
     }
     if ($options['created']) {
         self::onArticleCreate($this->mTitle);
     } elseif ($options['changed']) {
         // bug 50785
         self::onArticleEdit($this->mTitle, $revision);
     }
     ResourceLoaderWikiModule::invalidateModuleCache($this->mTitle, $options['oldrevision'], $revision, wfWikiID());
 }
Esempio n. 16
0
 /**
  * Get the Content object that needs to be saved in order to undo all revisions
  * between $undo and $undoafter. Revisions must belong to the same page,
  * must exist and must not be deleted.
  *
  * @since 1.21
  *
  * @param Revision $current The current text
  * @param Revision $undo The revision to undo
  * @param Revision $undoafter Must be an earlier revision than $undo
  *
  * @return mixed String on success, false on failure
  */
 public function getUndoContent(Revision $current, Revision $undo, Revision $undoafter)
 {
     $cur_content = $current->getContent();
     if (empty($cur_content)) {
         return false;
         // no page
     }
     $undo_content = $undo->getContent();
     $undoafter_content = $undoafter->getContent();
     if (!$undo_content || !$undoafter_content) {
         return false;
         // no content to undo
     }
     $this->checkModelID($cur_content->getModel());
     $this->checkModelID($undo_content->getModel());
     $this->checkModelID($undoafter_content->getModel());
     if ($cur_content->equals($undo_content)) {
         // No use doing a merge if it's just a straight revert.
         return $undoafter_content;
     }
     $undone_content = $this->merge3($undo_content, $undoafter_content, $cur_content);
     return $undone_content;
 }
 /**
  * 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;
 }
Esempio n. 18
0
 /**
  * Callback function for each revision, turn into HTML and save
  * @param Revision $rev
  */
 public function handleRevision($rev)
 {
     $title = $rev->getTitle();
     if (!$title) {
         $this->error("Got bogus revision with null title!");
         return;
     }
     $display = $title->getPrefixedText();
     $this->count++;
     $sanitized = rawurlencode($display);
     $filename = sprintf("%s/%s-%07d-%s.html", $this->outputDirectory, $this->prefix, $this->count, $sanitized);
     $this->output(sprintf("%s\n", $filename, $display));
     $user = new User();
     $options = ParserOptions::newFromUser($user);
     $content = $rev->getContent();
     $output = $content->getParserOutput($title, null, $options);
     file_put_contents($filename, "<!DOCTYPE html>\n" . "<html lang=\"en\" dir=\"ltr\">\n" . "<head>\n" . "<meta charset=\"UTF-8\" />\n" . "<title>" . htmlspecialchars($display) . "</title>\n" . "</head>\n" . "<body>\n" . $output->getText() . "</body>\n" . "</html>");
 }
Esempio n. 19
0
 /**
  * Get the Content object that needs to be saved in order to undo all revisions
  * between $undo and $undoafter. Revisions must belong to the same page,
  * must exist and must not be deleted.
  *
  * @since 1.21
  *
  * @param Revision $current The current text
  * @param Revision $undo The revision to undo
  * @param Revision $undoafter Must be an earlier revision than $undo
  *
  * @return mixed String on success, false on failure
  */
 public function getUndoContent(Revision $current, Revision $undo, Revision $undoafter)
 {
     $cur_content = $current->getContent();
     if (empty($cur_content)) {
         return false;
         // no page
     }
     $undo_content = $undo->getContent();
     $undoafter_content = $undoafter->getContent();
     if (!$undo_content || !$undoafter_content) {
         return false;
         // no content to undo
     }
     try {
         $this->checkModelID($cur_content->getModel());
         $this->checkModelID($undo_content->getModel());
         if ($current->getId() !== $undo->getId()) {
             // If we are undoing the most recent revision,
             // its ok to revert content model changes. However
             // if we are undoing a revision in the middle, then
             // doing that will be confusing.
             $this->checkModelID($undoafter_content->getModel());
         }
     } catch (MWException $e) {
         // If the revisions have different content models
         // just return false
         return false;
     }
     if ($cur_content->equals($undo_content)) {
         // No use doing a merge if it's just a straight revert.
         return $undoafter_content;
     }
     $undone_content = $this->merge3($undo_content, $undoafter_content, $cur_content);
     return $undone_content;
 }
Esempio n. 20
0
 /**
  * Parse the text from a given Revision
  *
  * @param Revision $revision
  */
 function runParser(Revision $revision)
 {
     $content = $revision->getContent();
     $content->getParserOutput($revision->getTitle(), $revision->getId());
     if ($this->clearLinkCache) {
         $this->linkCache->clear();
     }
 }
 /**
  * Extract information from the Revision
  *
  * @param Revision $revision
  * @param object $row Should have a field 'ts_tags' if $this->fld_tags is set
  * @return array
  */
 protected function extractRevisionInfo(Revision $revision, $row)
 {
     $title = $revision->getTitle();
     $user = $this->getUser();
     $vals = array();
     $anyHidden = false;
     if ($this->fld_ids) {
         $vals['revid'] = intval($revision->getId());
         if (!is_null($revision->getParentId())) {
             $vals['parentid'] = intval($revision->getParentId());
         }
     }
     if ($this->fld_flags) {
         $vals['minor'] = $revision->isMinor();
     }
     if ($this->fld_user || $this->fld_userid) {
         if ($revision->isDeleted(Revision::DELETED_USER)) {
             $vals['userhidden'] = true;
             $anyHidden = true;
         }
         if ($revision->userCan(Revision::DELETED_USER, $user)) {
             if ($this->fld_user) {
                 $vals['user'] = $revision->getUserText(Revision::RAW);
             }
             $userid = $revision->getUser(Revision::RAW);
             if (!$userid) {
                 $vals['anon'] = true;
             }
             if ($this->fld_userid) {
                 $vals['userid'] = $userid;
             }
         }
     }
     if ($this->fld_timestamp) {
         $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $revision->getTimestamp());
     }
     if ($this->fld_size) {
         if (!is_null($revision->getSize())) {
             $vals['size'] = intval($revision->getSize());
         } else {
             $vals['size'] = 0;
         }
     }
     if ($this->fld_sha1) {
         if ($revision->isDeleted(Revision::DELETED_TEXT)) {
             $vals['sha1hidden'] = true;
             $anyHidden = true;
         }
         if ($revision->userCan(Revision::DELETED_TEXT, $user)) {
             if ($revision->getSha1() != '') {
                 $vals['sha1'] = wfBaseConvert($revision->getSha1(), 36, 16, 40);
             } else {
                 $vals['sha1'] = '';
             }
         }
     }
     if ($this->fld_contentmodel) {
         $vals['contentmodel'] = $revision->getContentModel();
     }
     if ($this->fld_comment || $this->fld_parsedcomment) {
         if ($revision->isDeleted(Revision::DELETED_COMMENT)) {
             $vals['commenthidden'] = true;
             $anyHidden = true;
         }
         if ($revision->userCan(Revision::DELETED_COMMENT, $user)) {
             $comment = $revision->getComment(Revision::RAW);
             if ($this->fld_comment) {
                 $vals['comment'] = $comment;
             }
             if ($this->fld_parsedcomment) {
                 $vals['parsedcomment'] = Linker::formatComment($comment, $title);
             }
         }
     }
     if ($this->fld_tags) {
         if ($row->ts_tags) {
             $tags = explode(',', $row->ts_tags);
             ApiResult::setIndexedTagName($tags, 'tag');
             $vals['tags'] = $tags;
         } else {
             $vals['tags'] = array();
         }
     }
     $content = null;
     global $wgParser;
     if ($this->fetchContent) {
         $content = $revision->getContent(Revision::FOR_THIS_USER, $this->getUser());
         // Expand templates after getting section content because
         // template-added sections don't count and Parser::preprocess()
         // will have less input
         if ($content && $this->section !== false) {
             $content = $content->getSection($this->section, false);
             if (!$content) {
                 $this->dieUsage("There is no section {$this->section} in r" . $revision->getId(), 'nosuchsection');
             }
         }
         if ($revision->isDeleted(Revision::DELETED_TEXT)) {
             $vals['texthidden'] = true;
             $anyHidden = true;
         } elseif (!$content) {
             $vals['textmissing'] = true;
         }
     }
     if ($this->fld_content && $content) {
         $text = null;
         if ($this->generateXML) {
             if ($content->getModel() === CONTENT_MODEL_WIKITEXT) {
                 $t = $content->getNativeData();
                 # note: don't set $text
                 $wgParser->startExternalParse($title, ParserOptions::newFromContext($this->getContext()), Parser::OT_PREPROCESS);
                 $dom = $wgParser->preprocessToDom($t);
                 if (is_callable(array($dom, 'saveXML'))) {
                     $xml = $dom->saveXML();
                 } else {
                     $xml = $dom->__toString();
                 }
                 $vals['parsetree'] = $xml;
             } else {
                 $vals['badcontentformatforparsetree'] = true;
                 $this->setWarning("Conversion to XML is supported for wikitext only, " . $title->getPrefixedDBkey() . " uses content model " . $content->getModel());
             }
         }
         if ($this->expandTemplates && !$this->parseContent) {
             #XXX: implement template expansion for all content types in ContentHandler?
             if ($content->getModel() === CONTENT_MODEL_WIKITEXT) {
                 $text = $content->getNativeData();
                 $text = $wgParser->preprocess($text, $title, ParserOptions::newFromContext($this->getContext()));
             } else {
                 $this->setWarning("Template expansion is supported for wikitext only, " . $title->getPrefixedDBkey() . " uses content model " . $content->getModel());
                 $vals['badcontentformat'] = true;
                 $text = false;
             }
         }
         if ($this->parseContent) {
             $po = $content->getParserOutput($title, $revision->getId(), ParserOptions::newFromContext($this->getContext()));
             $text = $po->getText();
         }
         if ($text === null) {
             $format = $this->contentFormat ? $this->contentFormat : $content->getDefaultFormat();
             $model = $content->getModel();
             if (!$content->isSupportedFormat($format)) {
                 $name = $title->getPrefixedDBkey();
                 $this->setWarning("The requested format {$this->contentFormat} is not " . "supported for content model {$model} used by {$name}");
                 $vals['badcontentformat'] = true;
                 $text = false;
             } else {
                 $text = $content->serialize($format);
                 // always include format and model.
                 // Format is needed to deserialize, model is needed to interpret.
                 $vals['contentformat'] = $format;
                 $vals['contentmodel'] = $model;
             }
         }
         if ($text !== false) {
             ApiResult::setContentValue($vals, 'content', $text);
         }
     }
     if ($content && (!is_null($this->diffto) || !is_null($this->difftotext))) {
         static $n = 0;
         // Number of uncached diffs we've had
         if ($n < $this->getConfig()->get('APIMaxUncachedDiffs')) {
             $vals['diff'] = array();
             $context = new DerivativeContext($this->getContext());
             $context->setTitle($title);
             $handler = $revision->getContentHandler();
             if (!is_null($this->difftotext)) {
                 $model = $title->getContentModel();
                 if ($this->contentFormat && !ContentHandler::getForModelID($model)->isSupportedFormat($this->contentFormat)) {
                     $name = $title->getPrefixedDBkey();
                     $this->setWarning("The requested format {$this->contentFormat} is not " . "supported for content model {$model} used by {$name}");
                     $vals['diff']['badcontentformat'] = true;
                     $engine = null;
                 } else {
                     $difftocontent = ContentHandler::makeContent($this->difftotext, $title, $model, $this->contentFormat);
                     $engine = $handler->createDifferenceEngine($context);
                     $engine->setContent($content, $difftocontent);
                 }
             } else {
                 $engine = $handler->createDifferenceEngine($context, $revision->getID(), $this->diffto);
                 $vals['diff']['from'] = $engine->getOldid();
                 $vals['diff']['to'] = $engine->getNewid();
             }
             if ($engine) {
                 $difftext = $engine->getDiffBody();
                 ApiResult::setContentValue($vals['diff'], 'body', $difftext);
                 if (!$engine->wasCacheHit()) {
                     $n++;
                 }
             }
         } else {
             $vals['diff']['notcached'] = true;
         }
     }
     if ($anyHidden && $revision->isDeleted(Revision::DELETED_RESTRICTED)) {
         $vals['suppressed'] = true;
     }
     return $vals;
 }
 /**
  * Lazy initialization of article text from DB
  */
 protected function initText()
 {
     if (!isset($this->mText)) {
         if ($this->mRevision != null) {
             //TODO: if we could plug in some code that knows about special content models *and* about
             //      special features of the search engine, the search could benefit.
             $content = $this->mRevision->getContent();
             $this->mText = $content ? $content->getTextForSearchIndex() : '';
         } else {
             // TODO: can we fetch raw wikitext for commons images?
             $this->mText = '';
         }
     }
 }
 /**
  * Look up some text of a revision from its revision id
  *
  * Note that this is really *some* text, we do not make *any* guarantee
  * that this text will be even close to what the user actually sees, or
  * that the form is fit for any intended purpose.
  *
  * Note also that if the revision for any reason is not an Revision
  * the function returns with an empty string.
  *
  * @param Revision $revision a valid revision
  * @param $audience Integer: one of:
  *      Revision::FOR_PUBLIC       to be displayed to all users
  *      Revision::FOR_THIS_USER    to be displayed to the given user
  *      Revision::RAW              get the text regardless of permissions
  * @return string|null the content of the revision as some kind of string,
  * 		or an empty string if it can not be found
  */
 static function revisionToString($revision, $audience = Revision::FOR_THIS_USER)
 {
     if (!$revision instanceof Revision) {
         return '';
     }
     if (defined('MW_SUPPORTS_CONTENTHANDLER')) {
         $content = $revision->getContent($audience);
         if ($content === null) {
             return '';
         }
         $result = self::contentToString($content);
     } else {
         // For MediaWiki without contenthandler support (< 1.21)
         $result = $revision->getText();
     }
     return $result;
 }
 /**
  * Parse the text from a given Revision
  *
  * @param Revision $revision
  */
 function runParser(Revision $revision)
 {
     $content = $revision->getContent();
     $content->getParserOutput($revision->getTitle(), $revision->getId());
 }
 /**
  * @param Revision $revision
  * @return string
  */
 protected function feedItemDesc($revision)
 {
     if ($revision) {
         $msg = wfMessage('colon-separator')->inContentLanguage()->text();
         $content = $revision->getContent();
         if ($content instanceof TextContent) {
             // only textual content has a "source view".
             $html = nl2br(htmlspecialchars($content->getNativeData()));
         } else {
             //XXX: we could get an HTML representation of the content via getParserOutput, but that may
             //     contain JS magic and generally may not be suitable for inclusion in a feed.
             //     Perhaps Content should have a getDescriptiveHtml method and/or a getSourceText method.
             //Compare also FeedUtils::formatDiffRow.
             $html = '';
         }
         return '<p>' . htmlspecialchars($revision->getUserText()) . $msg . htmlspecialchars(FeedItem::stripComment($revision->getComment())) . "</p>\n<hr />\n<div>" . $html . "</div>";
     }
     return '';
 }
 /**
  * Add a wiki page
  *
  * @return void
  */
 function add()
 {
     if (!WikiPage::canAdd(logged_user(), active_project())) {
         flash_error(lang('no access permissions'));
         $this->redirectTo('wiki');
     }
     //if
     //Here we will edit a wiki page
     $preview = false;
     $data = array_var($_POST, 'wiki', false);
     if (false !== $data) {
         $preview = array_key_exists('preview', $data);
     }
     if (!$preview && $data) {
         //Make a new wiki page
         $page = new WikiPage();
         //Set the Id for this project
         $page->setProjectId(active_project()->getId());
         $page->setProjectIndex(logged_user()->isMemberOfOwnerCompany() ? $data['project_index'] : 0);
         $page->setPublish(logged_user()->isMemberOfOwnerCompany() ? $data['publish'] : 0);
         $page->setParentId($data['parent_id']);
         $page->setProjectSidebar(logged_user()->isMemberOfOwnerCompany() ? $data['project_sidebar'] : 0);
         //Make a new revision of this page
         $revision = $page->makeRevision();
         // Check to see if we want to lock this page
         if (isset($data['locked'])) {
             if ($data['locked'] == 1 && $page->canLock(logged_user())) {
                 // If we want to lock this page and the user has permissions to lock it, and the page is not already locked
                 $page->setLocked(true);
                 $page->setLockedById(logged_user()->getId());
                 $page->setLockedOn(DateTimeValueLib::now());
             }
             // if
         }
         // if
         //Set attributes from form
         $revision->setFromAttributes($data);
         //Set user ID and project ID
         $revision->setCreatedbyId(logged_user()->getId());
         try {
             //Start the db transaction
             DB::beginWork();
             //Save the page
             $page->save();
             //Make a log entry
             ApplicationLogs::createLog($page, active_project(), ApplicationLogs::ACTION_ADD);
             if (plugin_active('tags')) {
                 //Add page tags
                 $page->setTagsFromCSV($data['tags']);
             }
             //Commit changed
             DB::commit();
             //Tell the user they made a page
             flash_success(lang('success add wiki page'));
             //Redirect
             $this->redirectToUrl($page->getViewUrl());
         } catch (Exception $e) {
             DB::rollback();
             tpl_assign('error', $e);
         }
         //try
     }
     // if
     if (!isset($page) || !instance_of($page, 'WikiPage')) {
         $page = new WikiPage();
         $page->setProjectId(active_project()->getId());
     }
     // if
     $revision = new Revision();
     if (!$data) {
         // there was no input POSTed
         $data['content'] = $revision->getContent();
     }
     $data['preview_content'] = do_textile($data['content']);
     //Assign revision object
     tpl_assign('data', $data);
     tpl_assign('page', $page);
     tpl_assign('revision', $revision);
     tpl_assign('tags', '');
     $this->setTemplate('edit');
     $this->setSidebar(get_template_path('textile_help_sidebar'));
 }
Esempio n. 27
0
 /**
  * Populates the search index with content from all pages
  */
 protected function populateSearchIndex()
 {
     $res = $this->db->select('page', 'MAX(page_id) AS count');
     $s = $this->db->fetchObject($res);
     $count = $s->count;
     $this->output("Rebuilding index fields for {$count} pages...\n");
     $n = 0;
     $fields = array_merge(Revision::selectPageFields(), Revision::selectFields(), Revision::selectTextFields());
     while ($n < $count) {
         if ($n) {
             $this->output($n . "\n");
         }
         $end = $n + self::RTI_CHUNK_SIZE - 1;
         $res = $this->db->select(['page', 'revision', 'text'], $fields, ["page_id BETWEEN {$n} AND {$end}", 'page_latest = rev_id', 'rev_text_id = old_id'], __METHOD__);
         foreach ($res as $s) {
             try {
                 $title = Title::makeTitle($s->page_namespace, $s->page_title);
                 $rev = new Revision($s);
                 $content = $rev->getContent();
                 $u = new SearchUpdate($s->page_id, $title, $content);
                 $u->doUpdate();
             } catch (MWContentSerializationException $ex) {
                 $this->output("Failed to deserialize content of revision {$s->rev_id} of page " . "`" . $title->getPrefixedDBkey() . "`!\n");
             }
         }
         $n += self::RTI_CHUNK_SIZE;
     }
 }
Esempio n. 28
0
 /**
  * Tests whether $rev->getContent() returns a clone when needed.
  *
  * @group Database
  */
 function testGetContentClone()
 {
     $content = new RevisionTestModifyableContent("foo");
     $rev = new Revision(array('id' => 42, 'page' => 23, 'title' => Title::newFromText("testGetContentClone_dummy"), 'content' => $content, 'length' => $content->getSize(), 'comment' => "testing", 'minor_edit' => false));
     $content = $rev->getContent(Revision::RAW);
     $content->setText("bar");
     $content2 = $rev->getContent(Revision::RAW);
     $this->assertNotSame($content, $content2, "expected a clone");
     // content is mutable, expect clone
     $this->assertEquals("foo", $content2->getText());
     // clone should contain the original text
     $content2->setText("bla bla");
     $this->assertEquals("bar", $content->getText());
     // clones should be independent
 }
Esempio n. 29
0
	/**
	 * Do standard deferred updates after page edit.
	 * Update links tables, site stats, search index and message cache.
	 * Purges pages that include this page if the text was changed here.
	 * Every 100th edit, prune the recent changes table.
	 *
	 * @param $revision Revision object
	 * @param $user User object that did the revision
	 * @param array $options of options, following indexes are used:
	 * - changed: boolean, whether the revision changed the content (default true)
	 * - created: boolean, whether the revision created the page (default false)
	 * - oldcountable: boolean or null (default null):
	 *   - boolean: whether the page was counted as an article before that
	 *     revision, only used in changed is true and created is false
	 *   - null: don't change the article count
	 */
	public function doEditUpdates( Revision $revision, User $user, array $options = array() ) {
		global $wgEnableParserCache;

		wfProfileIn( __METHOD__ );

		$options += array( 'changed' => true, 'created' => false, 'oldcountable' => null );
		$content = $revision->getContent();

		// Parse the text
		// Be careful not to do pre-save transform twice: $text is usually
		// already pre-save transformed once.
		if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
			wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
			$editInfo = $this->prepareContentForEdit( $content, $revision->getId(), $user );
		} else {
			wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
			$editInfo = $this->mPreparedEdit;
		}

		// Save it to the parser cache
		if ( $wgEnableParserCache ) {
			$parserCache = ParserCache::singleton();
			$parserCache->save( $editInfo->output, $this, $editInfo->popts );
		}

		// Update the links tables and other secondary data
		if ( $content ) {
			$recursive = $options['changed']; // bug 50785
			$updates = $content->getSecondaryDataUpdates(
				$this->getTitle(), null, $recursive, $editInfo->output );
			DataUpdate::runUpdates( $updates );
		}

		wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );

		if ( wfRunHooks( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) {
			if ( 0 == mt_rand( 0, 99 ) ) {
				// Flush old entries from the `recentchanges` table; we do this on
				// random requests so as to avoid an increase in writes for no good reason
				RecentChange::purgeExpiredChanges();
			}
		}

		if ( !$this->exists() ) {
			wfProfileOut( __METHOD__ );
			return;
		}

		$id = $this->getId();
		$title = $this->mTitle->getPrefixedDBkey();
		$shortTitle = $this->mTitle->getDBkey();

		if ( !$options['changed'] ) {
			$good = 0;
			$total = 0;
		} elseif ( $options['created'] ) {
			$good = (int)$this->isCountable( $editInfo );
			$total = 1;
		} elseif ( $options['oldcountable'] !== null ) {
			$good = (int)$this->isCountable( $editInfo ) - (int)$options['oldcountable'];
			$total = 0;
		} else {
			$good = 0;
			$total = 0;
		}

		DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, $good, $total ) );
		DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $content ) );

		// If this is another user's talk page, update newtalk.
		// Don't do this if $options['changed'] = false (null-edits) nor if
		// it's a minor edit and the user doesn't want notifications for those.
		if ( $options['changed']
			&& $this->mTitle->getNamespace() == NS_USER_TALK
			&& $shortTitle != $user->getTitleKey()
			&& !( $revision->isMinor() && $user->isAllowed( 'nominornewtalk' ) )
		) {
			$recipient = User::newFromName( $shortTitle, false );
			if ( !$recipient ) {
				wfDebug( __METHOD__ . ": invalid username\n" );
			} else {
				// Allow extensions to prevent user notification when a new message is added to their talk page
				if ( wfRunHooks( 'ArticleEditUpdateNewTalk', array( &$this, $recipient ) ) ) {
					if ( User::isIP( $shortTitle ) ) {
						// An anonymous user
						$recipient->setNewtalk( true, $revision );
					} elseif ( $recipient->isLoggedIn() ) {
						$recipient->setNewtalk( true, $revision );
					} else {
						wfDebug( __METHOD__ . ": don't need to notify a nonexistent user\n" );
					}
				}
			}
		}

		if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
			// XXX: could skip pseudo-messages like js/css here, based on content model.
			$msgtext = $content ? $content->getWikitextForTransclusion() : null;
			if ( $msgtext === false || $msgtext === null ) {
				$msgtext = '';
			}

			MessageCache::singleton()->replace( $shortTitle, $msgtext );
		}

		if ( $options['created'] ) {
			self::onArticleCreate( $this->mTitle );
		} else {
			self::onArticleEdit( $this->mTitle );
		}

		wfProfileOut( __METHOD__ );
	}
Esempio n. 30
0
 /**
  * Get text content object
  * Does *NOT* follow redirects.
  * TODO: when is this null?
  *
  * @note code that wants to retrieve page content from the database should use WikiPage::getContent().
  *
  * @return Content|null
  *
  * @since 1.21
  */
 protected function fetchContentObject()
 {
     if ($this->mContentLoaded) {
         return $this->mContentObject;
     }
     wfProfileIn(__METHOD__);
     $this->mContentLoaded = true;
     $this->mContent = null;
     $oldid = $this->getOldID();
     # Pre-fill content with error message so that if something
     # fails we'll have something telling us what we intended.
     //XXX: this isn't page content but a UI message. horrible.
     $this->mContentObject = new MessageContent('missing-revision', array($oldid), array());
     if ($oldid) {
         # $this->mRevision might already be fetched by getOldIDFromRequest()
         if (!$this->mRevision) {
             $this->mRevision = Revision::newFromId($oldid);
             if (!$this->mRevision) {
                 wfDebug(__METHOD__ . " failed to retrieve specified revision, id {$oldid}\n");
                 wfProfileOut(__METHOD__);
                 return false;
             }
         }
     } else {
         if (!$this->mPage->getLatest()) {
             wfDebug(__METHOD__ . " failed to find page data for title " . $this->getTitle()->getPrefixedText() . "\n");
             wfProfileOut(__METHOD__);
             return false;
         }
         $this->mRevision = $this->mPage->getRevision();
         if (!$this->mRevision) {
             wfDebug(__METHOD__ . " failed to retrieve current page, rev_id " . $this->mPage->getLatest() . "\n");
             wfProfileOut(__METHOD__);
             return false;
         }
     }
     // @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks.
     // We should instead work with the Revision object when we need it...
     $this->mContentObject = $this->mRevision->getContent(Revision::FOR_THIS_USER, $this->getContext()->getUser());
     // Loads if user is allowed
     $this->mRevIdFetched = $this->mRevision->getId();
     wfRunHooks('ArticleAfterFetchContentObject', array(&$this, &$this->mContentObject));
     wfProfileOut(__METHOD__);
     return $this->mContentObject;
 }