*/
const AS_IMAGE_REDIRECT_LOGGED = 234;
+ /**
+ * Status: can't parse content
+ */
+ const AS_PARSE_ERROR = 240;
+
/**
* HTML id and name for the beginning of the edit form.
*/
var $textbox1 = '', $textbox2 = '', $summary = '', $nosummary = false;
var $edittime = '', $section = '', $sectiontitle = '', $starttime = '';
var $oldid = 0, $editintro = '', $scrolltop = null, $bot = true;
+ var $contentModel = null, $contentFormat = null;
# Placeholders for text injection by hooks (must be HTML)
# extensions should take care to _append_ to the present value
public $editFormTextBottom = '';
public $editFormTextAfterContent = '';
public $previewTextAfterContent = '';
- public $mPreloadText = '';
+ public $mPreloadContent = null;
/* $didSave should be set to true whenever an article was succesfully altered. */
public $didSave = false;
public $suppressIntro = false;
+ /**
+ * Set to true to allow editing of non-text content types.
+ *
+ * @var bool
+ */
+ public $allowNonTextContent = false;
+
/**
* @param $article Article
*/
public function __construct( Article $article ) {
$this->mArticle = $article;
$this->mTitle = $article->getTitle();
+
+ $this->contentModel = $this->mTitle->getContentModel();
+
+ $handler = ContentHandler::getForModelID( $this->contentModel );
+ $this->contentFormat = $handler->getDefaultFormat();
}
/**
$this->isCssSubpage = $this->mTitle->isCssSubpage();
$this->isJsSubpage = $this->mTitle->isJsSubpage();
$this->isWrongCaseCssJsPage = $this->isWrongCaseCssJsPage();
- $this->isNew = !$this->mTitle->exists() || $this->section == 'new';
# Show applicable editing introductions
if ( $this->formtype == 'initial' || $this->firsttime ) {
if ( !$this->mTitle->getArticleID() ) {
wfRunHooks( 'EditFormPreloadText', array( &$this->textbox1, &$this->mTitle ) );
- }
- else {
+ } else {
wfRunHooks( 'EditFormInitialText', array( $this ) );
}
* @since 1.19
* @param $permErrors Array of permissions errors, as returned by
* Title::getUserPermissionsErrors().
+ * @throws PermissionsError
*/
protected function displayPermissionsError( array $permErrors ) {
global $wgRequest, $wgOut;
return;
}
- $content = $this->getContent();
+ $content = $this->getContentObject();
# Use the normal message if there's nothing to display
- if ( $this->firsttime && $content === '' ) {
+ if ( $this->firsttime && ( !$content || $content->isEmpty() ) ) {
$action = $this->mTitle->exists() ? 'edit' :
( $this->mTitle->isTalkPage() ? 'createtalk' : 'createpage' );
throw new PermissionsError( $action, $permErrors );
# If the user made changes, preserve them when showing the markup
# (This happens when a user is blocked during edit, for instance)
if ( !$this->firsttime ) {
- $content = $this->textbox1;
+ $text = $this->textbox1;
$wgOut->addWikiMsg( 'viewyourtext' );
} else {
+ $text = $this->toEditText( $content );
$wgOut->addWikiMsg( 'viewsourcetext' );
}
- $this->showTextbox( $content, 'wpTextbox1', array( 'readonly' ) );
+ $this->showTextbox( $text, 'wpTextbox1', array( 'readonly' ) );
$wgOut->addHTML( Html::rawElement( 'div', array( 'class' => 'templatesUsed' ),
Linker::formatTemplates( $this->getTemplates() ) ) );
* @param $request WebRequest
*/
function importFormData( &$request ) {
- global $wgLang, $wgUser;
+ global $wgContLang, $wgUser;
wfProfileIn( __METHOD__ );
# Section edit can come from either the form or a link
$this->section = $request->getVal( 'wpSection', $request->getVal( 'section' ) );
+ $this->isNew = !$this->mTitle->exists() || $this->section == 'new';
if ( $request->wasPosted() ) {
# These fields need to be checked for encoding.
}
# Truncate for whole multibyte characters
- $this->summary = $wgLang->truncate( $request->getText( 'wpSummary' ), 255 );
+ $this->summary = $wgContLang->truncate( $request->getText( 'wpSummary' ), 255 );
# If the summary consists of a heading, e.g. '==Foobar==', extract the title from the
# header syntax, e.g. 'Foobar'. This is mainly an issue when we are using wpSummary for
# currently doing double duty as both edit summary and section title. Right now this
# is just to allow API edits to work around this limitation, but this should be
# incorporated into the actual edit form when EditPage is rewritten (Bugs 18654, 26312).
- $this->sectiontitle = $wgLang->truncate( $request->getText( 'wpSectionTitle' ), 255 );
+ $this->sectiontitle = $wgContLang->truncate( $request->getText( 'wpSectionTitle' ), 255 );
$this->sectiontitle = preg_replace( '/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->sectiontitle );
$this->edittime = $request->getVal( 'wpEdittime' );
}
}
+ $this->oldid = $request->getInt( 'oldid' );
+
$this->bot = $request->getBool( 'bot', true );
$this->nosummary = $request->getBool( 'nosummary' );
- $this->oldid = $request->getInt( 'oldid' );
+ $content_handler = ContentHandler::getForTitle( $this->mTitle );
+ $this->contentModel = $request->getText( 'model', $content_handler->getModelID() ); #may be overridden by revision
+ $this->contentFormat = $request->getText( 'format', $content_handler->getDefaultFormat() ); #may be overridden by revision
+
+ #TODO: check if the desired model is allowed in this namespace, and if a transition from the page's current model to the new model is allowed
+ #TODO: check if the desired content model supports the given content format!
$this->live = $request->getCheck( 'live' );
$this->editintro = $request->getText( 'editintro',
function initialiseForm() {
global $wgUser;
$this->edittime = $this->mArticle->getTimestamp();
- $this->textbox1 = $this->getContent( false );
+
+ $content = $this->getContentObject( false ); #TODO: track content object?!
+ if ( $content === false ) {
+ return false;
+ }
+ $this->textbox1 = $this->toEditText( $content );
+
// activate checkboxes if user wants them to be always active
# Sort out the "watch" checkbox
if ( $wgUser->getOption( 'watchdefault' ) ) {
/**
* Fetch initial editing page content.
*
- * @param $def_text string
+ * @param $def_text string|bool
* @return mixed string on success, $def_text for invalid sections
* @private
+ * @deprecated since 1.21, get WikiPage::getContent() instead.
*/
- function getContent( $def_text = '' ) {
- global $wgOut, $wgRequest, $wgParser;
+ function getContent( $def_text = false ) {
+ ContentHandler::deprecated( __METHOD__, '1.21' );
+
+ if ( $def_text !== null && $def_text !== false && $def_text !== '' ) {
+ $def_content = $this->toEditContent( $def_text );
+ } else {
+ $def_content = false;
+ }
+
+ $content = $this->getContentObject( $def_content );
+
+ // Note: EditPage should only be used with text based content anyway.
+ return $this->toEditText( $content );
+ }
+
+ /**
+ * @param Content|null $def_content The default value to return
+ *
+ * @return mixed Content on success, $def_content for invalid sections
+ *
+ * @since 1.21
+ */
+ protected function getContentObject( $def_content = null ) {
+ global $wgOut, $wgRequest;
wfProfileIn( __METHOD__ );
- $text = false;
+ $content = false;
// For message page not locally set, use the i18n message.
// For other non-existent articles, use preload text if any.
if ( !$this->mTitle->exists() || $this->section == 'new' ) {
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI && $this->section != 'new' ) {
# If this is a system message, get the default text.
- $text = $this->mTitle->getDefaultMessageText();
+ $msg = $this->mTitle->getDefaultMessageText();
+
+ $content = $this->toEditContent( $msg );
}
- if ( $text === false ) {
+ if ( $content === false ) {
# If requested, preload some text.
$preload = $wgRequest->getVal( 'preload',
// Custom preload text for new sections
$this->section === 'new' ? 'MediaWiki:addsection-preload' : '' );
- $text = $this->getPreloadedText( $preload );
+
+ $content = $this->getPreloadedContent( $preload );
}
// For existing pages, get text based on "undo" or section parameters.
} else {
if ( $this->section != '' ) {
// Get section edit text (returns $def_text for invalid sections)
- $text = $wgParser->getSection( $this->getOriginalContent(), $this->section, $def_text );
+ $orig = $this->getOriginalContent();
+ $content = $orig ? $orig->getSection( $this->section ) : null;
+
+ if ( !$content ) $content = $def_content;
} else {
$undoafter = $wgRequest->getInt( 'undoafter' );
$undo = $wgRequest->getInt( 'undo' );
# Sanity check, make sure it's the right page,
# the revisions exist and they were not deleted.
- # Otherwise, $text will be left as-is.
+ # Otherwise, $content will be left as-is.
if ( !is_null( $undorev ) && !is_null( $oldrev ) &&
$undorev->getPage() == $oldrev->getPage() &&
$undorev->getPage() == $this->mTitle->getArticleID() &&
!$undorev->isDeleted( Revision::DELETED_TEXT ) &&
!$oldrev->isDeleted( Revision::DELETED_TEXT ) ) {
- $text = $this->mArticle->getUndoText( $undorev, $oldrev );
- if ( $text === false ) {
+ $content = $this->mArticle->getUndoContent( $undorev, $oldrev );
+
+ if ( $content === false ) {
# Warn the user that something went wrong
$undoMsg = 'failure';
} else {
wfMessage( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
}
- if ( $text === false ) {
- $text = $this->getOriginalContent();
+ if ( $content === false ) {
+ $content = $this->getOriginalContent();
}
}
}
wfProfileOut( __METHOD__ );
- return $text;
+ return $content;
}
/**
* to the original text of the edit.
*
* This difers from Article::getContent() that when a missing revision is
- * encountered the result will be an empty string and not the
+ * encountered the result will be null and not the
* 'missing-revision' message.
*
* @since 1.19
- * @return string
+ * @return Content|null
*/
private function getOriginalContent() {
if ( $this->section == 'new' ) {
- return $this->getCurrentText();
+ return $this->getCurrentContent();
}
$revision = $this->mArticle->getRevisionFetched();
if ( $revision === null ) {
- return '';
+ if ( !$this->contentModel ) $this->contentModel = $this->getTitle()->getContentModel();
+ $handler = ContentHandler::getForModelID( $this->contentModel );
+
+ return $handler->makeEmptyContent();
}
- return $this->mArticle->getContent();
+ $content = $revision->getContent();
+ return $content;
}
/**
- * Get the actual text of the page. This is basically similar to
- * WikiPage::getRawText() except that when the page doesn't exist an empty
- * string is returned instead of false.
+ * Get the current content of the page. This is basically similar to
+ * WikiPage::getContent( Revision::RAW ) except that when the page doesn't exist an empty
+ * content object is returned instead of null.
*
- * @since 1.19
- * @return string
+ * @since 1.21
+ * @return Content
*/
- private function getCurrentText() {
- $text = $this->mArticle->getRawText();
- if ( $text === false ) {
- return '';
+ protected function getCurrentContent() {
+ $rev = $this->mArticle->getRevision();
+ $content = $rev ? $rev->getContent( Revision::RAW ) : null;
+
+ if ( $content === false || $content === null ) {
+ if ( !$this->contentModel ) $this->contentModel = $this->getTitle()->getContentModel();
+ $handler = ContentHandler::getForModelID( $this->contentModel );
+
+ return $handler->makeEmptyContent();
} else {
- return $text;
+ # nasty side-effect, but needed for consistency
+ $this->contentModel = $rev->getContentModel();
+ $this->contentFormat = $rev->getContentFormat();
+
+ return $content;
}
}
+
/**
* Use this method before edit() to preload some text into the edit box
*
* @param $text string
+ * @deprecated since 1.21, use setPreloadedContent() instead.
*/
public function setPreloadedText( $text ) {
- $this->mPreloadText = $text;
+ ContentHandler::deprecated( __METHOD__, "1.21" );
+
+ $content = $this->toEditContent( $text );
+
+ $this->setPreloadedContent( $content );
+ }
+
+ /**
+ * Use this method before edit() to preload some content into the edit box
+ *
+ * @param $content Content
+ *
+ * @since 1.21
+ */
+ public function setPreloadedContent( Content $content ) {
+ $this->mPreloadContent = $content;
}
/**
* an earlier setPreloadText() or by loading the given page.
*
* @param $preload String: representing the title to preload from.
+ *
* @return String
+ *
+ * @deprecated since 1.21, use getPreloadedContent() instead
*/
protected function getPreloadedText( $preload ) {
- global $wgUser, $wgParser;
+ ContentHandler::deprecated( __METHOD__, "1.21" );
+
+ $content = $this->getPreloadedContent( $preload );
+ $text = $this->toEditText( $content );
- if ( !empty( $this->mPreloadText ) ) {
- return $this->mPreloadText;
+ return $text;
+ }
+
+ /**
+ * Get the contents to be preloaded into the box, either set by
+ * an earlier setPreloadText() or by loading the given page.
+ *
+ * @param $preload String: representing the title to preload from.
+ *
+ * @return Content
+ *
+ * @since 1.21
+ */
+ protected function getPreloadedContent( $preload ) {
+ global $wgUser;
+
+ if ( !empty( $this->mPreloadContent ) ) {
+ return $this->mPreloadContent;
}
+ $handler = ContentHandler::getForTitle( $this->getTitle() );
+
if ( $preload === '' ) {
- return '';
+ return $handler->makeEmptyContent();
}
$title = Title::newFromText( $preload );
# Check for existence to avoid getting MediaWiki:Noarticletext
if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) {
- return '';
+ return $handler->makeEmptyContent();
}
$page = WikiPage::factory( $title );
$title = $page->getRedirectTarget();
# Same as before
if ( $title === null || !$title->exists() || !$title->userCan( 'read', $wgUser ) ) {
- return '';
+ return $handler->makeEmptyContent();
}
$page = WikiPage::factory( $title );
}
$parserOptions = ParserOptions::newFromUser( $wgUser );
- return $wgParser->getPreloadText( $page->getRawText(), $title, $parserOptions );
+ $content = $page->getContent( Revision::RAW );
+
+ if ( !$content ) {
+ return $handler->makeEmptyContent();
+ }
+
+ return $content->preloadTransform( $title, $parserOptions );
}
/**
/**
* Attempt submission
+ * @throws UserBlockedError|ReadOnlyError|ThrottledError|PermissionsError
* @return bool false if output is done, true if the rest of the form should be displayed
*/
function attemptSave() {
case self::AS_HOOK_ERROR:
return false;
+ case self::AS_PARSE_ERROR:
+ $wgOut->addWikiText( '<div class="error">' . $status->getWikiText() . '</div>');
+ return true;
+
case self::AS_SUCCESS_NEW_ARTICLE:
$query = $resultDetails['redirect'] ? 'redirect=no' : '';
$anchor = isset ( $resultDetails['sectionanchor'] ) ? $resultDetails['sectionanchor'] : '';
return $status;
}
+ try {
+ # Construct Content object
+ $textbox_content = $this->toEditContent( $this->textbox1 );
+ } catch (MWContentSerializationException $ex) {
+ $status->fatal( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
+ $status->value = self::AS_PARSE_ERROR;
+ wfProfileOut( __METHOD__ );
+ return $status;
+ }
+
# Check image redirect
if ( $this->mTitle->getNamespace() == NS_FILE &&
- Title::newFromRedirect( $this->textbox1 ) instanceof Title &&
+ $textbox_content->isRedirect() &&
!$wgUser->isAllowed( 'upload' ) ) {
$code = $wgUser->isAnon() ? self::AS_IMAGE_REDIRECT_ANON : self::AS_IMAGE_REDIRECT_LOGGED;
$status->setResult( false, $code );
return $status;
}
- $text = $this->textbox1;
+ $content = $textbox_content;
+
$result['sectionanchor'] = '';
if ( $this->section == 'new' ) {
if ( $this->sectiontitle !== '' ) {
// Insert the section title above the content.
- $text = wfMessage( 'newsectionheaderdefaultlevel' )->rawParams( $this->sectiontitle )
- ->inContentLanguage()->text() . "\n\n" . $text;
+ $content = $content->addSectionHeader( $this->sectiontitle );
// Jump to the new section
$result['sectionanchor'] = $wgParser->guessLegacySectionNameFromWikiText( $this->sectiontitle );
// passed.
if ( $this->summary === '' ) {
$cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
- $this->summary = wfMessage( 'newsectionsummary' )
- ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
+ $this->summary = wfMessage( 'newsectionsummary', $cleanSectionTitle )
+ ->inContentLanguage()->text() ;
}
} elseif ( $this->summary !== '' ) {
// Insert the section title above the content.
- $text = wfMessage( 'newsectionheaderdefaultlevel' )->rawParams( $this->summary )
- ->inContentLanguage()->text() . "\n\n" . $text;
+ $content = $content->addSectionHeader( $this->summary );
// 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 = wfMessage( 'newsectionsummary' )
- ->rawParams( $cleanSummary )->inContentLanguage()->text();
+ $this->summary = wfMessage( 'newsectionsummary', $cleanSummary )
+ ->inContentLanguage()->text();
}
}
$status->value = self::AS_SUCCESS_NEW_ARTICLE;
- } else {
+ } else { # not $new
# Article exists. Check for edit 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 = false;
wfDebug( __METHOD__ . ": conflict suppressed; new section\n" );
}
- } elseif ( $this->section == '' && Revision::userWasLastToEdit( DB_MASTER, $this->mTitle->getArticleID(), $wgUser->getId(), $this->edittime ) ) {
+ } elseif ( $this->section == '' && Revision::userWasLastToEdit( DB_MASTER, $this->mTitle->getArticleID(),
+ $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;
$sectionTitle = $this->summary;
}
+ $content = null;
+
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 );
+ 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" );
- $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $sectionTitle );
+ wfDebug( __METHOD__ . ": getting section '{$this->section}'\n" );
+ $content = $this->mArticle->replaceSectionContent( $this->section, $textbox_content, $sectionTitle );
}
- if ( is_null( $text ) ) {
+
+ if ( is_null( $content ) ) {
wfDebug( __METHOD__ . ": activating conflict; section replace failed.\n" );
$this->isConflict = true;
- $text = $this->textbox1; // do not try to merge here!
+ $content = $textbox_content; // do not try to merge here!
} elseif ( $this->isConflict ) {
+ $contentObj = $content;
# Attempt merge
- if ( $this->mergeChangesInto( $text ) ) {
+ if ( $this->mergeChangesInto( $content ) ) {
// Successful merge! Maybe we should tell the user the good news?
$this->isConflict = false;
+ $content = $this->toEditContent( $content );
wfDebug( __METHOD__ . ": Suppressing edit conflict, successful merge.\n" );
} else {
$this->section = '';
- $this->textbox1 = $text;
+ $this->textbox1 = ContentHandler::getContentText( $contentObj );
wfDebug( __METHOD__ . ": Keeping edit conflict, failed merge.\n" );
}
}
}
// Run post-section-merge edit filter
- if ( !wfRunHooks( 'EditFilterMerged', array( $this, $text, &$this->hookError, $this->summary ) ) ) {
+ $hook_args = array( $this, $content, &$this->hookError, $this->summary );
+
+ if ( !ContentHandler::runLegacyHooks( 'EditFilterMerged', $hook_args )
+ || !wfRunHooks( 'EditFilterMergedContent', $hook_args ) ) {
# Error messages etc. could be handled within the hook...
$status->fatal( 'hookaborted' );
$status->value = self::AS_HOOK_ERROR;
# 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
+ && !$content->equals( $this->getOriginalContent() )
+ && !$content->isRedirect() ) # check if it's not a redirect
{
if ( md5( $this->summary ) == $this->autoSumm ) {
$this->missingSummary = true;
// passed.
if ( $this->summary === '' ) {
$cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
- $this->summary = wfMessage( 'newsectionsummary' )
- ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
+ $this->summary = wfMessage( 'newsectionsummary', $cleanSectionTitle )
+ ->inContentLanguage()->text();
}
} elseif ( $this->summary !== '' ) {
$sectionanchor = $wgParser->guessLegacySectionNameFromWikiText( $this->summary );
# This is a new section, so create a link to the new section
# in the revision summary.
$cleanSummary = $wgParser->stripSectionName( $this->summary );
- $this->summary = wfMessage( 'newsectionsummary' )
- ->rawParams( $cleanSummary )->inContentLanguage()->text();
+ $this->summary = wfMessage( 'newsectionsummary', $cleanSummary )
+ ->inContentLanguage()->text();
}
} elseif ( $this->section != '' ) {
# Try to get a section anchor from the section source, redirect to edited section if header found
// merged the section into full text. Clear the section field
// so that later submission of conflict forms won't try to
// replace that into a duplicated mess.
- $this->textbox1 = $text;
+ $this->textbox1 = $this->toEditText( $content );
$this->section = '';
$status->value = self::AS_SUCCESS_UPDATE;
}
// Check for length errors again now that the section is merged in
- $this->kblength = (int)( strlen( $text ) / 1024 );
+ $this->kblength = (int)( strlen( $this->toEditText( $content ) ) / 1024 );
if ( $this->kblength > $wgMaxArticleSize ) {
$this->tooBig = true;
$status->setResult( false, self::AS_MAX_ARTICLE_SIZE_EXCEEDED );
( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
( $bot ? EDIT_FORCE_BOT : 0 );
- $doEditStatus = $this->mArticle->doEdit( $text, $this->summary, $flags );
+ $doEditStatus = $this->mArticle->doEditContent( $content, $this->summary, $flags,
+ false, null, $this->contentFormat );
if ( $doEditStatus->isOK() ) {
- $result['redirect'] = Title::newFromRedirect( $text ) !== null;
+ $result['redirect'] = $content->isRedirect();
$this->updateWatchlist();
wfProfileOut( __METHOD__ );
return $status;
* @param $editText string
*
* @return bool
+ * @deprecated since 1.21, use mergeChangesIntoContent() instead
*/
- function mergeChangesInto( &$editText ) {
+ function mergeChangesInto( &$editText ){
+ ContentHandler::deprecated( __METHOD__, "1.21" );
+
wfProfileIn( __METHOD__ );
$db = wfGetDB( DB_MASTER );
$currentText = $currentRevision->getText();
$result = '';
+ $editText = $this->toEditText( $editText );
+
if ( wfMerge( $baseText, $editText, $currentText, $result ) ) {
$editText = $result;
wfProfileOut( __METHOD__ );
}
}
+ /**
+ * @private
+ * @todo document
+ *
+ * @param $editContent
+ * @return bool
+ * @since since 1.WD
+ */
+ private function mergeChangesIntoContent( &$editContent ){
+ wfProfileIn( __METHOD__ );
+
+ $db = wfGetDB( DB_MASTER );
+
+ // This is the revision the editor started from
+ $baseRevision = $this->getBaseRevision();
+ $baseContent = $baseRevision ? $baseRevision->getContent() : null;
+
+ if ( is_null( $baseContent ) ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
+ // The current state, we want to merge updates into it
+ $currentRevision = Revision::loadFromTitle( $db, $this->mTitle );
+ $currentContent = $currentRevision ? $currentRevision->getContent() : null;
+
+ if ( is_null( $currentContent ) ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
+ $handler = ContentHandler::getForModelID( $baseContent->getModel() );
+
+ $result = $handler->merge3( $baseContent, $editContent, $currentContent );
+
+ if ( $result ) {
+ $editContent = $result;
+ wfProfileOut( __METHOD__ );
+ return true;
+ } else {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ }
+
/**
* @return Revision
*/
# Give a notice if the user is editing a deleted/moved page...
if ( !$this->mTitle->exists() ) {
LogEventsList::showLogExtract( $wgOut, array( 'delete', 'move' ), $this->mTitle,
- '', array( 'lim' => 10,
- 'conds' => array( "log_action != 'revision'" ),
- 'showIfEmpty' => false,
- 'msgKey' => array( 'recreate-moveddeleted-warn' ) )
+ '',
+ array(
+ 'lim' => 10,
+ 'conds' => array( "log_action != 'revision'" ),
+ 'showIfEmpty' => false,
+ 'msgKey' => array( 'recreate-moveddeleted-warn' )
+ )
);
}
}
// Added using template syntax, to take <noinclude>'s into account.
$wgOut->addWikiTextTitleTidy( '{{:' . $title->getFullText() . '}}', $this->mTitle );
return true;
- } else {
- return false;
}
- } else {
- return false;
}
+ return false;
+ }
+
+ /**
+ * Gets an editable textual representation of $content.
+ * The textual representation can be turned by into a Content object by the
+ * toEditContent() method.
+ *
+ * If $content is null or false or a string, $content is returned unchanged.
+ *
+ * If the given Content object is not of a type that can be edited using the text base EditPage,
+ * an exception will be raised. Set $this->allowNonTextContent to true to allow editing of non-textual
+ * content.
+ *
+ * @param Content|null|false|string $content
+ * @return String the editable text form of the content.
+ *
+ * @throws MWException if $content is not an instance of TextContent and $this->allowNonTextContent is not true.
+ */
+ protected function toEditText( $content ) {
+ if ( $content === null || $content === false ) {
+ return $content;
+ }
+
+ if ( is_string( $content ) ) {
+ return $content;
+ }
+
+ if ( !$this->allowNonTextContent && !( $content instanceof TextContent ) ) {
+ throw new MWException( "This content model can not be edited as text: "
+ . ContentHandler::getLocalizedName( $content->getModel() ) );
+ }
+
+ return $content->serialize( $this->contentFormat );
+ }
+
+ /**
+ * Turns the given text into a Content object by unserializing it.
+ *
+ * If the resulting Content object is not of a type that can be edited using the text base EditPage,
+ * an exception will be raised. Set $this->allowNonTextContent to true to allow editing of non-textual
+ * content.
+ *
+ * @param String|null|bool $text Text to unserialize
+ * @return Content The content object created from $text. If $text was false or null, false resp. null will be
+ * returned instead.
+ *
+ * @throws MWException if unserializing the text results in a Content object that is not an instance of TextContent
+ * and $this->allowNonTextContent is not true.
+ */
+ protected function toEditContent( $text ) {
+ if ( $text === false || $text === null ) {
+ return $text;
+ }
+
+ $content = ContentHandler::makeContent( $text, $this->getTitle(),
+ $this->contentModel, $this->contentFormat );
+
+ if ( !$this->allowNonTextContent && !( $content instanceof TextContent ) ) {
+ throw new MWException( "This content model can not be edited as text: "
+ . ContentHandler::getLocalizedName( $content->getModel() ) );
+ }
+
+ return $content;
}
/**
* Send the edit form and related headers to $wgOut
- * @param $formCallback Callback that takes an OutputPage parameter; will be called
+ * @param $formCallback Callback|null that takes an OutputPage parameter; will be called
* during form output near the top, for captchas and the like.
*/
function showEditForm( $formCallback = null ) {
}
}
+ //@todo: add EditForm plugin interface and use it here!
+ // search for textarea1 and textares2, and allow EditForm to override all uses.
$wgOut->addHTML( Html::openElement( 'form', array( 'id' => self::EDITFORM_ID, 'name' => self::EDITFORM_ID,
'method' => 'post', 'action' => $this->getActionURL( $this->getContextTitle() ),
'enctype' => 'multipart/form-data' ) ) );
$wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) );
+ $wgOut->addHTML( Html::hidden( 'format', $this->contentFormat ) );
+ $wgOut->addHTML( Html::hidden( 'model', $this->contentModel ) );
+
if ( $this->section == 'new' ) {
$this->showSummaryInput( true, $this->summary );
$wgOut->addHTML( $this->getSummaryPreview( true, $this->summary ) );
// resolved between page source edits and custom ui edits using the
// custom edit ui.
$this->textbox2 = $this->textbox1;
- $this->textbox1 = $this->getCurrentText();
+
+ $content = $this->getCurrentContent();
+ $this->textbox1 = $this->toEditText( $content );
$this->showTextbox1();
} else {
Linker::formatHiddenCategories( $this->mArticle->getHiddenCategories() ) ) );
if ( $this->isConflict ) {
- $this->showConflict();
+ try {
+ $this->showConflict();
+ } catch ( MWContentSerializationException $ex ) {
+ // this can't really happen, but be nice if it does.
+ $msg = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
+ $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>');
+ }
}
$wgOut->addHTML( $this->editFormTextBottom . "\n</form>\n" );
if ( $this->section != '' && $this->section != 'new' ) {
if ( !$this->summary && !$this->preview && !$this->diff ) {
- $sectionTitle = self::extractSectionTitle( $this->textbox1 );
+ $sectionTitle = self::extractSectionTitle( $this->textbox1 ); //FIXME: use Content object
if ( $sectionTitle !== false ) {
$this->summary = "/* $sectionTitle */ ";
}
$this->showTextbox( $this->textbox2, 'wpTextbox2', array( 'tabindex' => 6, 'readonly' ) );
}
- protected function showTextbox( $content, $name, $customAttribs = array() ) {
+ protected function showTextbox( $text, $name, $customAttribs = array() ) {
global $wgOut, $wgUser;
- $wikitext = $this->safeUnicodeOutput( $content );
+ $wikitext = $this->safeUnicodeOutput( $text );
if ( strval( $wikitext ) !== '' ) {
// Ensure there's a newline at the end, otherwise adding lines
// is awkward.
$wgOut->addHTML( '</div>' );
if ( $this->formtype == 'diff' ) {
- $this->showDiff();
+ try {
+ $this->showDiff();
+ } catch ( MWContentSerializationException $ex ) {
+ $msg = wfMessage( 'content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
+ $wgOut->addWikiText( '<div class="error">' . $msg->text() . '</div>');
+ }
}
}
* save and then make a comparison.
*/
function showDiff() {
- global $wgUser, $wgContLang, $wgParser, $wgOut;
+ global $wgUser, $wgContLang, $wgOut;
$oldtitlemsg = 'currentrev';
# if message does not exist, show diff against the preloaded default
$oldtext = $this->mTitle->getDefaultMessageText();
if( $oldtext !== false ) {
$oldtitlemsg = 'defaultmessagetext';
+ $oldContent = $this->toEditContent( $oldtext );
+ } else {
+ $oldContent = null;
}
} else {
- $oldtext = $this->mArticle->getRawText();
+ $oldContent = $this->getOriginalContent();
}
- $newtext = $this->mArticle->replaceSection(
- $this->section, $this->textbox1, $this->summary, $this->edittime );
- wfRunHooks( 'EditPageGetDiffText', array( $this, &$newtext ) );
+ $textboxContent = $this->toEditContent( $this->textbox1 );
- $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
- $newtext = $wgParser->preSaveTransform( $newtext, $this->mTitle, $wgUser, $popts );
+ $newContent = $this->mArticle->replaceSectionContent(
+ $this->section, $textboxContent,
+ $this->summary, $this->edittime );
+
+ if ( $newContent ) {
+ ContentHandler::runLegacyHooks( 'EditPageGetDiffText', array( $this, &$newContent ) );
+ wfRunHooks( 'EditPageGetDiffContent', array( $this, &$newContent ) );
+
+ $popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
+ $newContent = $newContent->preSaveTransform( $this->mTitle, $wgUser, $popts );
+ }
- if ( $oldtext !== false || $newtext != '' ) {
+ if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
$oldtitle = wfMessage( $oldtitlemsg )->parse();
$newtitle = wfMessage( 'yourtext' )->parse();
- $de = new DifferenceEngine( $this->mArticle->getContext() );
- $de->setText( $oldtext, $newtext );
+ if ( !$oldContent ) {
+ $oldContent = $newContent->getContentHandler()->makeEmptyContent();
+ }
+
+ if ( !$newContent ) {
+ $newContent = $oldContent->getContentHandler()->makeEmptyContent();
+ }
+
+ $de = $oldContent->getContentHandler()->createDifferenceEngine( $this->mArticle->getContext() );
+ $de->setContent( $oldContent, $newContent );
+
$difftext = $de->getDiff( $oldtitle, $newtitle );
$de->showDiffStyle();
} else {
if ( wfRunHooks( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
$wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
- $de = new DifferenceEngine( $this->mArticle->getContext() );
- $de->setText( $this->textbox2, $this->textbox1 );
+ $content1 = $this->toEditContent( $this->textbox1 );
+ $content2 = $this->toEditContent( $this->textbox2 );
+
+ $handler = ContentHandler::getForModelID( $this->contentModel );
+ $de = $handler->createDifferenceEngine( $this->mArticle->getContext() );
+ $de->setContent( $content2, $content1 );
$de->showDiff(
wfMessage( 'yourtext' )->parse(),
wfMessage( 'storedversion' )->text()
/**
* Get the rendered text for previewing.
+ * @throws MWException
* @return string
*/
function getPreviewText() {
- global $wgOut, $wgUser, $wgParser, $wgRawHtml, $wgLang;
+ global $wgOut, $wgUser, $wgRawHtml, $wgLang;
wfProfileIn( __METHOD__ );
return $parsedNote;
}
- if ( $this->mTriedSave && !$this->mTokenOk ) {
- if ( $this->mTokenOkExceptSuffix ) {
- $note = wfMessage( 'token_suffix_mismatch' )->plain();
- } else {
- $note = wfMessage( 'session_fail_preview' )->plain();
- }
- } elseif ( $this->incompleteForm ) {
- $note = wfMessage( 'edit_form_incomplete' )->plain();
- } else {
- $note = wfMessage( 'previewnote' )->plain() .
- ' [[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' . wfMessage( 'continue-editing' )->text() . ']]';
- }
+ $note = '';
- $parserOptions = $this->mArticle->makeParserOptions( $this->mArticle->getContext() );
+ try {
+ $content = $this->toEditContent( $this->textbox1 );
- $parserOptions->setEditSection( false );
- $parserOptions->setIsPreview( true );
- $parserOptions->setIsSectionPreview( !is_null( $this->section ) && $this->section !== '' );
+ if ( $this->mTriedSave && !$this->mTokenOk ) {
+ if ( $this->mTokenOkExceptSuffix ) {
+ $note = wfMessage( 'token_suffix_mismatch' )->plain() ;
- # don't parse non-wikitext pages, show message about preview
- if ( $this->mTitle->isCssJsSubpage() || !$this->mTitle->isWikitextPage() ) {
- if ( $this->mTitle->isCssJsSubpage() ) {
- $level = 'user';
- } elseif ( $this->mTitle->isCssOrJsPage() ) {
- $level = 'site';
- } else {
- $level = false;
- }
-
- # Used messages to make sure grep find them:
- # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
- $class = 'mw-code';
- if ( $level ) {
- if ( preg_match( "/\\.css$/", $this->mTitle->getText() ) ) {
- $previewtext = "<div id='mw-{$level}csspreview'>\n" . wfMessage( "{$level}csspreview" )->text() . "\n</div>";
- $class .= " mw-css";
- } elseif ( preg_match( "/\\.js$/", $this->mTitle->getText() ) ) {
- $previewtext = "<div id='mw-{$level}jspreview'>\n" . wfMessage( "{$level}jspreview" )->text() . "\n</div>";
- $class .= " mw-js";
} else {
- throw new MWException( 'A CSS/JS (sub)page but which is not css nor js!' );
+ $note = wfMessage( 'session_fail_preview' )->plain() ;
}
- $parserOutput = $wgParser->parse( $previewtext, $this->mTitle, $parserOptions );
- $previewHTML = $parserOutput->getText();
+ } elseif ( $this->incompleteForm ) {
+ $note = wfMessage( 'edit_form_incomplete' )->plain() ;
} else {
- $previewHTML = '';
+ $note = wfMessage( 'previewnote' )->plain() .
+ ' [[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' . wfMessage( 'continue-editing' )->text() . ']]';
}
- $previewHTML .= "<pre class=\"$class\" dir=\"ltr\">\n" . htmlspecialchars( $this->textbox1 ) . "\n</pre>\n";
- } else {
- $toparse = $this->textbox1;
+ $parserOptions = $this->mArticle->makeParserOptions( $this->mArticle->getContext() );
+ $parserOptions->setEditSection( false );
+ $parserOptions->setIsPreview( true );
+ $parserOptions->setIsSectionPreview( !is_null($this->section) && $this->section !== '' );
- # If we're adding a comment, we need to show the
- # summary as the headline
- if ( $this->section == "new" && $this->summary != "" ) {
- $toparse = wfMessage( 'newsectionheaderdefaultlevel', $this->summary )->inContentLanguage()->text() . "\n\n" . $toparse;
- }
+ # don't parse non-wikitext pages, show message about preview
+ if ( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) {
+ if( $this->mTitle->isCssJsSubpage() ) {
+ $level = 'user';
+ } elseif( $this->mTitle->isCssOrJsPage() ) {
+ $level = 'site';
+ } else {
+ $level = false;
+ }
- wfRunHooks( 'EditPageGetPreviewText', array( $this, &$toparse ) );
+ if ( $content->getModel() == CONTENT_MODEL_CSS ) {
+ $format = 'css';
+ } elseif ( $content->getModel() == CONTENT_MODEL_JAVASCRIPT ) {
+ $format = 'js';
+ } else {
+ $format = false;
+ }
- $toparse = $wgParser->preSaveTransform( $toparse, $this->mTitle, $wgUser, $parserOptions );
- $parserOutput = $wgParser->parse( $toparse, $this->mTitle, $parserOptions );
+ # Used messages to make sure grep find them:
+ # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
+ if( $level && $format ) {
+ $note = "<div id='mw-{$level}{$format}preview'>" . wfMessage( "{$level}{$format}preview" )->text() . "</div>";
+ }
+ }
- $rt = Title::newFromRedirectArray( $this->textbox1 );
+ $rt = $content->getRedirectChain();
if ( $rt ) {
$previewHTML = $this->mArticle->viewRedirect( $rt, false );
} else {
- $previewHTML = $parserOutput->getText();
- }
- $this->mParserOutput = $parserOutput;
- $wgOut->addParserOutputNoText( $parserOutput );
+ # If we're adding a comment, we need to show the
+ # summary as the headline
+ if ( $this->section === "new" && $this->summary !== "" ) {
+ $content = $content->addSectionHeader( $this->summary );
+ }
+
+ $hook_args = array( $this, &$content );
+ ContentHandler::runLegacyHooks( 'EditPageGetPreviewText', $hook_args );
+ wfRunHooks( 'EditPageGetPreviewContent', $hook_args );
- if ( count( $parserOutput->getWarnings() ) ) {
- $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
+ $parserOptions->enableLimitReport();
+
+ # For CSS/JS pages, we should have called the ShowRawCssJs hook here.
+ # But it's now deprecated, so never mind
+
+ $content = $content->preSaveTransform( $this->mTitle, $wgUser, $parserOptions );
+ $parserOutput = $content->getParserOutput( $this->getArticle()->getTitle(), null, $parserOptions );
+
+ $previewHTML = $parserOutput->getText();
+ $this->mParserOutput = $parserOutput;
+ $wgOut->addParserOutputNoText( $parserOutput );
+
+ if ( count( $parserOutput->getWarnings() ) ) {
+ $note .= "\n\n" . implode( "\n\n", $parserOutput->getWarnings() );
+ }
}
+ } catch ( MWContentSerializationException $ex ) {
+ $m = wfMessage('content-failed-to-parse', $this->contentModel, $this->contentFormat, $ex->getMessage() );
+ $note .= "\n\n" . $m->parse();
+ $previewHTML = '';
}
if ( $this->isConflict ) {
/**
* Produce the stock "your edit contains spam" page
*
- * @param $match string Text which triggered one or more filters
+ * @param $match string|bool Text which triggered one or more filters
* @deprecated since 1.17 Use method spamPageWithContent() instead
*/
static function spamPage( $match = false ) {