Esempio n. 1
0
 /**
  * Loads everything except the text
  * This isn't necessary for all uses, so it's only done if needed.
  */
 protected function loadLastEdit()
 {
     if ($this->mLastRevision !== null) {
         return;
         // already loaded
     }
     $latest = $this->getLatest();
     if (!$latest) {
         return;
         // page doesn't exist or is missing page_latest info
     }
     if ($this->mDataLoadedFrom == self::READ_LOCKING) {
         // Bug 37225: if session S1 loads the page row FOR UPDATE, the result always
         // includes the latest changes committed. This is true even within REPEATABLE-READ
         // transactions, where S1 normally only sees changes committed before the first S1
         // SELECT. Thus we need S1 to also gets the revision row FOR UPDATE; otherwise, it
         // may not find it since a page row UPDATE and revision row INSERT by S2 may have
         // happened after the first S1 SELECT.
         // http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read
         $flags = Revision::READ_LOCKING;
         $revision = Revision::newFromPageId($this->getId(), $latest, $flags);
     } elseif ($this->mDataLoadedFrom == self::READ_LATEST) {
         // Bug T93976: if page_latest was loaded from the master, fetch the
         // revision from there as well, as it may not exist yet on a replica DB.
         // Also, this keeps the queries in the same REPEATABLE-READ snapshot.
         $flags = Revision::READ_LATEST;
         $revision = Revision::newFromPageId($this->getId(), $latest, $flags);
     } else {
         $dbr = wfGetDB(DB_REPLICA);
         $revision = Revision::newKnownCurrent($dbr, $this->getId(), $latest);
     }
     if ($revision) {
         // sanity
         $this->setLastEdit($revision);
     }
 }
Esempio n. 2
0
	/**
	 * Loads everything except the text
	 * This isn't necessary for all uses, so it's only done if needed.
	 */
	protected function loadLastEdit() {
		if ( $this->mLastRevision !== null ) {
			return; // already loaded
		}

		$latest = $this->getLatest();
		if ( !$latest ) {
			return; // page doesn't exist or is missing page_latest info
		}

		// Bug 37225: if session S1 loads the page row FOR UPDATE, the result always includes the
		// latest changes committed. This is true even within REPEATABLE-READ transactions, where
		// S1 normally only sees changes committed before the first S1 SELECT. Thus we need S1 to
		// also gets the revision row FOR UPDATE; otherwise, it may not find it since a page row
		// UPDATE and revision row INSERT by S2 may have happened after the first S1 SELECT.
		// http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read.
		$flags = ( $this->mDataLoadedFrom == self::READ_LOCKING ) ? Revision::READ_LOCKING : 0;
		$revision = Revision::newFromPageId( $this->getId(), $latest, $flags );
		if ( $revision ) { // sanity
			$this->setLastEdit( $revision );
		}
	}
Esempio n. 3
0
 public function execute()
 {
     global $wgMemc;
     $user = $this->getUser();
     $params = $this->extractRequestParams();
     $page = $this->getTitleOrPageId($params);
     $title = $page->getTitle();
     if (!ContentHandler::getForModelID($params['contentmodel'])->isSupportedFormat($params['contentformat'])) {
         $this->dieUsage("Unsupported content model/format", 'badmodelformat');
     }
     // Trim and fix newlines so the key SHA1's match (see RequestContext::getText())
     $text = rtrim(str_replace("\r\n", "\n", $params['text']));
     $textContent = ContentHandler::makeContent($text, $title, $params['contentmodel'], $params['contentformat']);
     $page = WikiPage::factory($title);
     if ($page->exists()) {
         // Page exists: get the merged content with the proposed change
         $baseRev = Revision::newFromPageId($page->getId(), $params['baserevid']);
         if (!$baseRev) {
             $this->dieUsage("No revision ID {$params['baserevid']}", 'missingrev');
         }
         $currentRev = $page->getRevision();
         if (!$currentRev) {
             $this->dieUsage("No current revision of page ID {$page->getId()}", 'missingrev');
         }
         // Merge in the new version of the section to get the proposed version
         $editContent = $page->replaceSectionAtRev($params['section'], $textContent, $params['sectiontitle'], $baseRev->getId());
         if (!$editContent) {
             $this->dieUsage("Could not merge updated section.", 'replacefailed');
         }
         if ($currentRev->getId() == $baseRev->getId()) {
             // Base revision was still the latest; nothing to merge
             $content = $editContent;
         } else {
             // Merge the edit into the current version
             $baseContent = $baseRev->getContent();
             $currentContent = $currentRev->getContent();
             if (!$baseContent || !$currentContent) {
                 $this->dieUsage("Missing content for page ID {$page->getId()}", 'missingrev');
             }
             $handler = ContentHandler::getForModelID($baseContent->getModel());
             $content = $handler->merge3($baseContent, $editContent, $currentContent);
         }
     } else {
         // New pages: use the user-provided content model
         $content = $textContent;
     }
     if (!$content) {
         // merge3() failed
         $this->getResult()->addValue(null, $this->getModuleName(), array('status' => 'editconflict'));
         return;
     }
     // The user will abort the AJAX request by pressing "save", so ignore that
     ignore_user_abort(true);
     // Get a key based on the source text, format, and user preferences
     $key = self::getStashKey($title, $content, $user);
     // De-duplicate requests on the same key
     if ($user->pingLimiter('stashedit')) {
         $status = 'ratelimited';
     } elseif ($wgMemc->lock($key, 0, 30)) {
         /** @noinspection PhpUnusedLocalVariableInspection */
         $unlocker = new ScopedCallback(function () use($key) {
             global $wgMemc;
             $wgMemc->unlock($key);
         });
         $status = self::parseAndStash($page, $content, $user);
     } else {
         $status = 'busy';
     }
     $this->getResult()->addValue(null, $this->getModuleName(), array('status' => $status));
 }
Esempio n. 4
0
 /**
  * Determine if we can merge a page.
  * We check if an inaccessible revision would become the latest and
  * deny the merge if so -- it's theoretically possible to update the
  * latest revision, but opens a can of worms -- search engine updates,
  * recentchanges review, etc.
  *
  * @param integer $id The page_id
  * @param Title $newTitle The new title
  * @param string $logStatus This is set to the log status message on failure
  * @return bool
  */
 private function canMerge($id, Title $newTitle, &$logStatus)
 {
     $latestDest = Revision::newFromTitle($newTitle, 0, Revision::READ_LATEST);
     $latestSource = Revision::newFromPageId($id, 0, Revision::READ_LATEST);
     if ($latestSource->getTimestamp() > $latestDest->getTimestamp()) {
         $logStatus = 'cannot merge since source is later';
         return false;
     } else {
         return true;
     }
 }
Esempio n. 5
0
 /**
  * Loads everything except the text
  * This isn't necessary for all uses, so it's only done if needed.
  */
 protected function loadLastEdit()
 {
     if ($this->mLastRevision !== null) {
         return;
         // already loaded
     }
     $latest = $this->getLatest();
     if (!$latest) {
         return;
         // page doesn't exist or is missing page_latest info
     }
     $revision = Revision::newFromPageId($this->getId(), $latest);
     if ($revision) {
         // sanity
         $this->setLastEdit($revision);
     }
 }
Esempio n. 6
0
 public function execute()
 {
     $user = $this->getUser();
     $params = $this->extractRequestParams();
     if ($user->isBot()) {
         // sanity
         $this->dieUsage('This interface is not supported for bots', 'botsnotsupported');
     }
     $cache = ObjectCache::getLocalClusterInstance();
     $page = $this->getTitleOrPageId($params);
     $title = $page->getTitle();
     if (!ContentHandler::getForModelID($params['contentmodel'])->isSupportedFormat($params['contentformat'])) {
         $this->dieUsage('Unsupported content model/format', 'badmodelformat');
     }
     $text = null;
     $textHash = null;
     if (strlen($params['stashedtexthash'])) {
         // Load from cache since the client indicates the text is the same as last stash
         $textHash = $params['stashedtexthash'];
         $textKey = $cache->makeKey('stashedit', 'text', $textHash);
         $text = $cache->get($textKey);
         if (!is_string($text)) {
             $this->dieUsage('No stashed text found with the given hash', 'missingtext');
         }
     } elseif ($params['text'] !== null) {
         // Trim and fix newlines so the key SHA1's match (see WebRequest::getText())
         $text = rtrim(str_replace("\r\n", "\n", $params['text']));
         $textHash = sha1($text);
     } else {
         $this->dieUsage('The text or stashedtexthash parameter must be given', 'missingtextparam');
     }
     $textContent = ContentHandler::makeContent($text, $title, $params['contentmodel'], $params['contentformat']);
     $page = WikiPage::factory($title);
     if ($page->exists()) {
         // Page exists: get the merged content with the proposed change
         $baseRev = Revision::newFromPageId($page->getId(), $params['baserevid']);
         if (!$baseRev) {
             $this->dieUsage("No revision ID {$params['baserevid']}", 'missingrev');
         }
         $currentRev = $page->getRevision();
         if (!$currentRev) {
             $this->dieUsage("No current revision of page ID {$page->getId()}", 'missingrev');
         }
         // Merge in the new version of the section to get the proposed version
         $editContent = $page->replaceSectionAtRev($params['section'], $textContent, $params['sectiontitle'], $baseRev->getId());
         if (!$editContent) {
             $this->dieUsage('Could not merge updated section.', 'replacefailed');
         }
         if ($currentRev->getId() == $baseRev->getId()) {
             // Base revision was still the latest; nothing to merge
             $content = $editContent;
         } else {
             // Merge the edit into the current version
             $baseContent = $baseRev->getContent();
             $currentContent = $currentRev->getContent();
             if (!$baseContent || !$currentContent) {
                 $this->dieUsage("Missing content for page ID {$page->getId()}", 'missingrev');
             }
             $handler = ContentHandler::getForModelID($baseContent->getModel());
             $content = $handler->merge3($baseContent, $editContent, $currentContent);
         }
     } else {
         // New pages: use the user-provided content model
         $content = $textContent;
     }
     if (!$content) {
         // merge3() failed
         $this->getResult()->addValue(null, $this->getModuleName(), ['status' => 'editconflict']);
         return;
     }
     // The user will abort the AJAX request by pressing "save", so ignore that
     ignore_user_abort(true);
     if ($user->pingLimiter('stashedit')) {
         $status = 'ratelimited';
     } else {
         $status = self::parseAndStash($page, $content, $user, $params['summary']);
         $textKey = $cache->makeKey('stashedit', 'text', $textHash);
         $cache->set($textKey, $text, self::MAX_CACHE_TTL);
     }
     $stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
     $stats->increment("editstash.cache_stores.{$status}");
     $this->getResult()->addValue(null, $this->getModuleName(), ['status' => $status, 'texthash' => $textHash]);
 }
Esempio n. 7
0
 public function execute()
 {
     $user = $this->getUser();
     $params = $this->extractRequestParams();
     if ($user->isBot()) {
         // sanity
         $this->dieUsage('This interface is not supported for bots', 'botsnotsupported');
     }
     $page = $this->getTitleOrPageId($params);
     $title = $page->getTitle();
     if (!ContentHandler::getForModelID($params['contentmodel'])->isSupportedFormat($params['contentformat'])) {
         $this->dieUsage('Unsupported content model/format', 'badmodelformat');
     }
     // Trim and fix newlines so the key SHA1's match (see RequestContext::getText())
     $text = rtrim(str_replace("\r\n", "\n", $params['text']));
     $textContent = ContentHandler::makeContent($text, $title, $params['contentmodel'], $params['contentformat']);
     $page = WikiPage::factory($title);
     if ($page->exists()) {
         // Page exists: get the merged content with the proposed change
         $baseRev = Revision::newFromPageId($page->getId(), $params['baserevid']);
         if (!$baseRev) {
             $this->dieUsage("No revision ID {$params['baserevid']}", 'missingrev');
         }
         $currentRev = $page->getRevision();
         if (!$currentRev) {
             $this->dieUsage("No current revision of page ID {$page->getId()}", 'missingrev');
         }
         // Merge in the new version of the section to get the proposed version
         $editContent = $page->replaceSectionAtRev($params['section'], $textContent, $params['sectiontitle'], $baseRev->getId());
         if (!$editContent) {
             $this->dieUsage('Could not merge updated section.', 'replacefailed');
         }
         if ($currentRev->getId() == $baseRev->getId()) {
             // Base revision was still the latest; nothing to merge
             $content = $editContent;
         } else {
             // Merge the edit into the current version
             $baseContent = $baseRev->getContent();
             $currentContent = $currentRev->getContent();
             if (!$baseContent || !$currentContent) {
                 $this->dieUsage("Missing content for page ID {$page->getId()}", 'missingrev');
             }
             $handler = ContentHandler::getForModelID($baseContent->getModel());
             $content = $handler->merge3($baseContent, $editContent, $currentContent);
         }
     } else {
         // New pages: use the user-provided content model
         $content = $textContent;
     }
     if (!$content) {
         // merge3() failed
         $this->getResult()->addValue(null, $this->getModuleName(), ['status' => 'editconflict']);
         return;
     }
     // The user will abort the AJAX request by pressing "save", so ignore that
     ignore_user_abort(true);
     // Use the master DB for fast blocking locks
     $dbw = wfGetDB(DB_MASTER);
     // Get a key based on the source text, format, and user preferences
     $key = self::getStashKey($title, $content, $user);
     // De-duplicate requests on the same key
     if ($user->pingLimiter('stashedit')) {
         $status = 'ratelimited';
     } elseif ($dbw->lock($key, __METHOD__, 1)) {
         $status = self::parseAndStash($page, $content, $user);
         $dbw->unlock($key, __METHOD__);
     } else {
         $status = 'busy';
     }
     $this->getStats()->increment("editstash.cache_stores.{$status}");
     $this->getResult()->addValue(null, $this->getModuleName(), ['status' => $status]);
 }