wfProfileOut( __METHOD__ . '-checks' );
- // Use SELECT FOR UPDATE here to avoid transaction collision in
- // WikiPage::updateRevisionOn() and ending in the self::AS_END case.
- $this->mArticle->loadPageData( 'forupdate' );
+ # Load the page data from the master. If anything changes in the meantime,
+ # we detect it by using page_latest like a token in a 1 try compare-and-swap.
+ $this->mArticle->loadPageData( 'fromdbmaster' );
$new = !$this->mArticle->exists();
- if ( $new ) {
- // Late check for create permission, just in case *PARANOIA*
- if ( !$this->mTitle->userCan( 'create' ) ) {
- $status->fatal( 'nocreatetext' );
- $status->value = self::AS_NO_CREATE_PERMISSION;
- wfDebug( __METHOD__ . ": no create permission\n" );
- wfProfileOut( __METHOD__ );
- return $status;
- }
+ try {
+ if ( $new ) {
+ // Late check for create permission, just in case *PARANOIA*
+ if ( !$this->mTitle->userCan( 'create' ) ) {
+ $status->fatal( 'nocreatetext' );
+ $status->value = self::AS_NO_CREATE_PERMISSION;
+ wfDebug( __METHOD__ . ": no create permission\n" );
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
- # Don't save a new article if it's blank.
- if ( $this->textbox1 == '' ) {
- $status->setResult( false, self::AS_BLANK_ARTICLE );
- wfProfileOut( __METHOD__ );
- return $status;
- }
+ # Don't save a new article if it's blank.
+ if ( $this->textbox1 == '' ) {
+ $status->setResult( false, self::AS_BLANK_ARTICLE );
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
- // Run post-section-merge edit filter
- if ( !wfRunHooks( 'EditFilterMerged', array( $this, $this->textbox1, &$this->hookError, $this->summary ) ) ) {
- # Error messages etc. could be handled within the hook...
- $status->fatal( 'hookaborted' );
- $status->value = self::AS_HOOK_ERROR;
- wfProfileOut( __METHOD__ );
- return $status;
- } elseif ( $this->hookError != '' ) {
- # ...or the hook could be expecting us to produce an error
- $status->fatal( 'hookaborted' );
- $status->value = self::AS_HOOK_ERROR_EXPECTED;
- wfProfileOut( __METHOD__ );
- return $status;
- }
+ // Run post-section-merge edit filter
+ if ( !wfRunHooks( 'EditFilterMerged', array( $this, $this->textbox1, &$this->hookError, $this->summary ) ) ) {
+ # Error messages etc. could be handled within the hook...
+ $status->fatal( 'hookaborted' );
+ $status->value = self::AS_HOOK_ERROR;
+ wfProfileOut( __METHOD__ );
+ return $status;
+ } elseif ( $this->hookError != '' ) {
+ # ...or the hook could be expecting us to produce an error
+ $status->fatal( 'hookaborted' );
+ $status->value = self::AS_HOOK_ERROR_EXPECTED;
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
- $text = $this->textbox1;
- $result['sectionanchor'] = '';
- if ( $this->section == 'new' ) {
- if ( $this->sectiontitle !== '' ) {
- // Insert the section title above the content.
- $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->sectiontitle ) . "\n\n" . $text;
-
- // Jump to the new section
- $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
-
- // If no edit summary was specified, create one automatically from the section
- // title and have it link to the new section. Otherwise, respect the summary as
- // passed.
- if ( $this->summary === '' ) {
- $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
- $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSectionTitle );
- }
- } elseif ( $this->summary !== '' ) {
- // Insert the section title above the content.
- $text = wfMsgForContent( 'newsectionheaderdefaultlevel', $this->summary ) . "\n\n" . $text;
+ $content = ContentHandler::makeContent( $this->textbox1, $this->getTitle(), $this->content_model, $this->content_format );
- // Jump to the new section
- $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
+ $result['sectionanchor'] = '';
+ if ( $this->section == 'new' ) {
+ if ( $this->sectiontitle !== '' ) {
+ // Insert the section title above the content.
+ $content = $content->addSectionHeader( $this->sectiontitle );
+
+ // Jump to the new section
+ $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
+
+ // If no edit summary was specified, create one automatically from the section
+ // title and have it link to the new section. Otherwise, respect the summary as
+ // passed.
+ if ( $this->summary === '' ) {
+ $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
+ $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSectionTitle );
+ }
+ } elseif ( $this->summary !== '' ) {
+ // Insert the section title above the content.
+ $content = $content->addSectionHeader( $this->sectiontitle );
- // Create a link to the new section from the edit summary.
- $cleanSummary = $wgParser->stripSectionName( $this->summary );
- $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSummary );
+ // Jump to the new section
+ $result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
+
+ // Create a link to the new section from the edit summary.
+ $cleanSummary = $wgParser->stripSectionName( $this->summary );
+ $this->summary = wfMsgForContent( 'newsectionsummary', $cleanSummary );
+ }
}
- }
- $status->value = self::AS_SUCCESS_NEW_ARTICLE;
+ $status->value = self::AS_SUCCESS_NEW_ARTICLE;
- } else {
+ } else { # not $new
- # Article exists. Check for edit conflict.
- $timestamp = $this->mArticle->getTimestamp();
- wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
+ # Article exists. Check for edit conflict.
- if ( $timestamp != $this->edittime ) {
- $this->isConflict = true;
- if ( $this->section == 'new' ) {
- if ( $this->mArticle->getUserText() == $wgUser->getName() &&
- $this->mArticle->getComment() == $this->summary ) {
- // Probably a duplicate submission of a new comment.
- // This can happen when squid resends a request after
- // a timeout but the first one actually went through.
- wfDebug( __METHOD__ . ": duplicate new section submission; trigger edit conflict!\n" );
- } else {
- // New comment; suppress conflict.
+ $this->mArticle->clear(); # Force reload of dates, etc.
+ $timestamp = $this->mArticle->getTimestamp();
+
+ wfDebug( "timestamp: {$timestamp}, edittime: {$this->edittime}\n" );
+
+ if ( $timestamp != $this->edittime ) {
+ $this->isConflict = true;
+ if ( $this->section == 'new' ) {
+ if ( $this->mArticle->getUserText() == $wgUser->getName() &&
+ $this->mArticle->getComment() == $this->summary ) {
+ // Probably a duplicate submission of a new comment.
+ // This can happen when squid resends a request after
+ // a timeout but the first one actually went through.
+ wfDebug( __METHOD__ . ": duplicate new section submission; trigger edit conflict!\n" );
+ } else {
+ // New comment; suppress conflict.
+ $this->isConflict = false;
+ wfDebug( __METHOD__ . ": conflict suppressed; new section\n" );
+ }
+ } elseif ( $this->section == '' && $this->userWasLastToEdit( $wgUser->getId(), $this->edittime ) ) {
+ # Suppress edit conflict with self, except for section edits where merging is required.
+ wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
$this->isConflict = false;
- wfDebug( __METHOD__ . ": conflict suppressed; new section\n" );
}
- } elseif ( $this->section == '' && $this->userWasLastToEdit( $wgUser->getId(), $this->edittime ) ) {
- # Suppress edit conflict with self, except for section edits where merging is required.
- wfDebug( __METHOD__ . ": Suppressing edit conflict, same user.\n" );
- $this->isConflict = false;
}
- }
-
- // If sectiontitle is set, use it, otherwise use the summary as the section title (for
- // backwards compatibility with old forms/bots).
- if ( $this->sectiontitle !== '' ) {
- $sectionTitle = $this->sectiontitle;
- } else {
- $sectionTitle = $this->summary;
- }
- if ( $this->isConflict ) {
- wfDebug( __METHOD__ . ": conflict! getting section '$this->section' for time '$this->edittime' (article time '{$timestamp}')\n" );
- $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle, $this->edittime );
- } else {
- wfDebug( __METHOD__ . ": getting section '$this->section'\n" );
- $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle );
- }
- if ( is_null( $text ) ) {
- wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
- $this->isConflict = true;
- $text = $this->textbox1; // do not try to merge here!
- } elseif ( $this->isConflict ) {
- # Attempt merge
- if ( $this->mergeChangesInto( $text ) ) {
- // Successful merge! Maybe we should tell the user the good news?
- $this->isConflict = false;
- wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
+ // If sectiontitle is set, use it, otherwise use the summary as the section title (for
+ // backwards compatibility with old forms/bots).
+ if ( $this->sectiontitle !== '' ) {
+ $sectionTitle = $this->sectiontitle;
} else {
- $this->section = '';
- $this->textbox1 = $text;
- wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
+ $sectionTitle = $this->summary;
}
- }
- if ( $this->isConflict ) {
- $status->setResult( false, self::AS_CONFLICT_DETECTED );
- wfProfileOut( __METHOD__ );
- return $status;
- }
+ $textbox_content = ContentHandler::makeContent( $this->textbox1, $this->getTitle(), $this->content_model, $this->content_format );
+ $content = null;
- // Run post-section-merge edit filter
- if ( !wfRunHooks( 'EditFilterMerged', array( $this, $text, &$this->hookError, $this->summary ) ) ) {
- # Error messages etc. could be handled within the hook...
- $status->fatal( 'hookaborted' );
- $status->value = self::AS_HOOK_ERROR;
- wfProfileOut( __METHOD__ );
- return $status;
- } elseif ( $this->hookError != '' ) {
- # ...or the hook could be expecting us to produce an error
- $status->fatal( 'hookaborted' );
- $status->value = self::AS_HOOK_ERROR_EXPECTED;
- wfProfileOut( __METHOD__ );
- return $status;
- }
+ if ( $this->isConflict ) {
+ wfDebug( __METHOD__ . ": conflict! getting section '$this->section' for time '$this->edittime' (article time '{$timestamp}')\n" );
+ $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle, $this->edittime );
+ } else {
+ wfDebug( __METHOD__ . ": getting section '$this->section'\n" );
+ $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle );
+ }
- # Handle the user preference to force summaries here, but not for null edits
- if ( $this->section != 'new' && !$this->allowBlankSummary
- && $this->getOriginalContent() != $text
- && !Title::newFromRedirect( $text ) ) # check if it's not a redirect
- {
- if ( md5( $this->summary ) == $this->autoSumm ) {
- $this->missingSummary = true;
- $status->fatal( 'missingsummary' );
- $status->value = self::AS_SUMMARY_NEEDED;
- wfProfileOut( __METHOD__ );
- return $status;
+ if ( is_null( $content ) ) {
+ wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
+ $this->isConflict = true;
+ $content = $textbox_content; // do not try to merge here!
+ } elseif ( $this->isConflict ) {
+ # Attempt merge
+ if ( $this->mergeChangesIntoContent( $textbox_content ) ) {
+ // Successful merge! Maybe we should tell the user the good news?
+ $this->isConflict = false;
+ $content = $textbox_content;
+ wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
+ } else {
+ $this->section = '';
+ #$this->textbox1 = $text; #redundant, nothing to do here?
+ wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
+ }
}
- }
- # And a similar thing for new sections
- if ( $this->section == 'new' && !$this->allowBlankSummary ) {
- if ( trim( $this->summary ) == '' ) {
- $this->missingSummary = true;
- $status->fatal( 'missingsummary' ); // or 'missingcommentheader' if $section == 'new'. Blegh
- $status->value = self::AS_SUMMARY_NEEDED;
+ if ( $this->isConflict ) {
+ $status->setResult( false, self::AS_CONFLICT_DETECTED );
wfProfileOut( __METHOD__ );
return $status;
}
* @private
* @todo document
*
- * @parma $editText string
+ * @param $editText string
*
* @return bool
+ * @deprecated since 1.WD, use mergeChangesIntoContent() instead
+ */
+ function mergeChangesInto( &$editText ){
+ wfDebug( __METHOD__, "1.WD" );
+
+ $editContent = ContentHandler::makeContent( $editText, $this->getTitle(), $this->content_model, $this->content_format );
+
+ $ok = $this->mergeChangesIntoContent( $editContent );
+
+ if ( $ok ) {
+ $editText = $editContent->serialize( $this->content_format ); #XXX: really serialize?!
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @private
+ * @todo document
+ *
+ * @parma $editText string
+ *
+ * @return bool
+ * @since since 1.WD
*/
- function mergeChangesInto( &$editText ) {
+ private function mergeChangesIntoContent( &$editContent ){
wfProfileIn( __METHOD__ );
$db = wfGetDB( DB_MASTER );
wfProfileIn( __METHOD__ );
+ $this->checkContentModel();
+
$data = $this->mText;
- $flags = Revision::compressRevisionText( $data );
+ $flags = self::compressRevisionText( $data );
# Write to external storage if required
if( $wgDefaultExternalStore ) {
$rev_id = isset( $this->mId )
? $this->mId
: $dbw->nextSequenceValue( 'revision_rev_id_seq' );
-
- $dbw->insert( 'revision',
- array(
- 'rev_id' => $rev_id,
- 'rev_page' => $this->mPage,
- 'rev_text_id' => $this->mTextId,
- 'rev_comment' => $this->mComment,
- 'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
- 'rev_user' => $this->mUser,
- 'rev_user_text' => $this->mUserText,
- 'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ),
- 'rev_deleted' => $this->mDeleted,
- 'rev_len' => $this->mSize,
- 'rev_parent_id' => is_null( $this->mParentId )
- ? $this->getPreviousRevisionId( $dbw )
- : $this->mParentId,
- 'rev_sha1' => is_null( $this->mSha1 )
- ? self::base36Sha1( $this->mText )
- : $this->mSha1
- ), __METHOD__
+ $row = array(
+ 'rev_id' => $rev_id,
+ 'rev_page' => $this->mPage,
+ 'rev_text_id' => $this->mTextId,
+ 'rev_comment' => $this->mComment,
+ 'rev_minor_edit' => $this->mMinorEdit ? 1 : 0,
+ 'rev_user' => $this->mUser,
+ 'rev_user_text' => $this->mUserText,
+ 'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ),
+ 'rev_deleted' => $this->mDeleted,
+ 'rev_len' => $this->mSize,
+ 'rev_parent_id' => is_null( $this->mParentId )
+ ? $this->getPreviousRevisionId( $dbw )
+ : $this->mParentId,
+ 'rev_sha1' => is_null( $this->mSha1 )
+ ? Revision::base36Sha1( $this->mText )
+ : $this->mSha1,
);
+ if ( $wgContentHandlerUseDB ) {
+ //NOTE: Store null for the default model and format, to save space.
+ //XXX: Makes the DB sensitive to changed defaults. Make this behaviour optional? Only in miser mode?
+
+ $model = $this->getContentModel();
+ $format = $this->getContentFormat();
+
+ $defaultModel = ContentHandler::getDefaultModelFor( $this->getTitle() );
+ $defaultFormat = ContentHandler::getForModelID( $defaultModel )->getDefaultFormat();
+
+ $row[ 'rev_content_model' ] = ( $model === $defaultModel ) ? null : $model;
+ $row[ 'rev_content_format' ] = ( $format === $defaultFormat ) ? null : $format;
+ }
+
+ $dbw->insert( 'revision', $row, __METHOD__ );
+
$this->mId = !is_null( $rev_id ) ? $rev_id : $dbw->insertId();
wfRunHooks( 'RevisionInsertComplete', array( &$this, $data, $flags ) );