$content = $this->fetchContentObject();
$this->mContent = ContentHandler::getContentText( $content ); #@todo: get rid of mContent everywhere!
- wfRunHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) ); #BC cruft, deprecated!
+ ContentHandler::runLegacyHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
wfProfileOut( __METHOD__ );
wfDebug( __METHOD__ . ": showing CSS/JS source\n" );
$this->showCssOrJsPage();
$outputDone = true;
- } elseif( !wfRunHooks( 'ArticleContentViewCustom', array( $this->fetchContentObject(), $this->getTitle(), $outputPage ) ) ) {
+ } elseif( !wfRunHooks( 'ArticleContentViewCustom',
+ array( $this->fetchContentObject(), $this->getTitle(),
+ $outputPage ) ) ) {
+
# Allow extensions do their own custom view for certain pages
$outputDone = true;
- } elseif( Hooks::isRegistered( 'ArticleViewCustom' ) && !wfRunHooks( 'ArticleViewCustom', array( $this->fetchContent(), $this->getTitle(), $outputPage ) ) ) { #FIXME: fetchContent() is deprecated!
+ } elseif( !ContentHandler::runLegacyHooks( 'ArticleViewCustom',
+ array( $this->fetchContentObject(), $this->getTitle(),
+ $outputPage ) ) ) {
+
# Allow extensions do their own custom view for certain pages
$outputDone = true;
} else {
# Viewing a redirect page (e.g. with parameter redirect=no)
$outputPage->addHTML( $this->viewRedirect( $rt ) );
# Parse just to get categories, displaytitle, etc.
- $this->mParserOutput = $content->getParserOutput( $this->getTitle(), $oldid, $parserOptions, false );
+ $this->mParserOutput = $content->getParserOutput( $this->getTitle(), $oldid,
+ $parserOptions, false );
$outputPage->addParserOutputNoText( $this->mParserOutput );
$outputDone = true;
}
}
// Give hooks a chance to customise the output
- if ( !Hooks::isRegistered('ShowRawCssJs') || wfRunHooks( 'ShowRawCssJs', array( $this->fetchContent(), $this->getTitle(), $wgOut ) ) ) { #FIXME: fetchContent() is deprecated
+ if ( ContentHandler::runLegacyHooks( 'ShowRawCssJs', array( $this->fetchContentObject(), $this->getTitle(), $wgOut ) ) ) {
$po = $this->mContentObject->getParserOutput( $this->getTitle() );
$wgOut->addHTML( $po->getText() );
}
* @return ParserOutput or false if the given revsion ID is not found
*/
public function getParserOutput( $oldid = null, User $user = null ) {
+ //XXX: bypasses mParserOptions and thus setParserOptions()
+
$user = is_null( $user ) ? $this->getContext()->getUser() : $user;
- $parserOptions = $this->mPage->makeParserOptions( $user ); //XXX: bypasses mParserOptions and thus setParserOptions()
+ $parserOptions = $this->mPage->makeParserOptions( $user );
return $this->mPage->getParserOutput( $parserOptions, $oldid );
}
'AbstractContent' => 'includes/Content.php',
'ContentHandler' => 'includes/ContentHandler.php',
'CssContent' => 'includes/Content.php',
+ 'TextContentHandler' => 'includes/ContentHandler.php',
'CssContentHandler' => 'includes/ContentHandler.php',
'JavaScriptContent' => 'includes/Content.php',
'JavaScriptContentHandler' => 'includes/ContentHandler.php',
public function getDeletionUpdates( Title $title,
ParserOutput $parserOutput = null );
+ /**
+ * Returns true if this Content object matches the given magic word.
+ *
+ * @param MagicWord $word the magic word to match
+ *
+ * @return bool whether this Content object matches the given magic word.
+ */
+ public function matchMagicWord( MagicWord $word );
+
# TODO: handle ImagePage and CategoryPage
# TODO: make sure we cover lucene search / wikisearch.
# TODO: make sure ReplaceTemplates still works
}
/**
- * Returns a list of updates to perform when this content is deleted.
- * The necessary updates may be taken from the Content object, or depend on
- * the current state of the database.
+ * @see Content::getDeletionUpdates()
*
* @since WD.1
*
new LinksDeletionUpdate( $title ),
);
}
+
+ /**
+ * @see Content::matchMagicWord()
+ *
+ * This default implementation always returns false. Subclasses may override this to supply matching logic.
+ *
+ * @param MagicWord $word
+ *
+ * @return bool
+ */
+ public function matchMagicWord( MagicWord $word ) {
+ return false;
+ }
}
/**
# TODO: make Highlighter interface, use highlighter here, if available
return htmlspecialchars( $this->getNativeData() );
}
-
}
/**
);
}
-
+ /**
+ * @see Content::matchMagicWord()
+ *
+ * This implementation calls $word->match() on the this TextContent object's text.
+ *
+ * @param MagicWord $word
+ *
+ * @return bool whether this Content object matches the given magic word.
+ */
+ public function matchMagicWord( MagicWord $word ) {
+ return $word->match( $this->getNativeData() );
+ }
}
/**
else return wfMsg( $key );
}
+ public static function getContentModels() {
+ global $wgContentHandlers;
+
+ return array_keys( $wgContentHandlers );
+ }
+
public static function getAllContentFormats() {
global $wgContentHandlers;
public function supportsSections() {
return false;
}
+
+ /**
+ * Call a legacy hook that uses text instead of Content objects.
+ * Will log a warning when a matching hook function is registered.
+ * If the textual representation of the content is changed by the
+ * hook function, a new Content object is constructed from the new
+ * text.
+ *
+ * @param $event String: event name
+ * @param $args Array: parameters passed to hook functions
+ *
+ * @return Boolean True if no handler aborted the hook
+ */
+ public static function runLegacyHooks( $event, $args = array() ) {
+ if ( !Hooks::isRegistered( $event ) ) {
+ return true; // nothing to do here
+ }
+
+ wfWarn( "Using obsolete hook $event" );
+
+ // convert Content objects to text
+ $contentObjects = array();
+ $contentTexts = array();
+
+ foreach ( $args as $k => $v ) {
+ if ( $v instanceof Content ) {
+ /* @var Content $v */
+
+ $contentObjects[$k] = $v;
+
+ $v = $v->serialize();
+ $contentTexts[ $k ] = $v;
+ $args[ $k ] = $v;
+ }
+ }
+
+ // call the hook functions
+ $ok = wfRunHooks( $event, $args );
+
+ // see if the hook changed the text
+ foreach ( $contentTexts as $k => $orig ) {
+ /* @var Content $content */
+
+ $modified = $args[ $k ];
+ $content = $contentObjects[$k];
+
+ if ( $modified !== $orig ) {
+ // text was changed, create updated Content object
+ $content = $content->getContentHandler()->unserializeContent( $modified );
+ }
+
+ $args[ $k ] = $content;
+ }
+
+ return $ok;
+ }
}
/**
}
// Run post-section-merge edit filter
- if ( !wfRunHooks( 'EditFilterMerged', array( $this, $content->serialize( $this->content_format ), &$this->hookError, $this->summary ) )
- || !wfRunHooks( 'EditFilterMergedContent', array( $this, $content, &$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;
$this->section, $textboxContent,
$this->summary, $this->edittime );
- # hanlde legacy text-based hook
- $newtext_orig = $newContent->serialize( $this->content_format );
- $newtext = $newtext_orig; #clone
- wfRunHooks( 'EditPageGetDiffText', array( $this, &$newtext ) );
-
- if ( $newtext != $newtext_orig ) {
- #if the hook changed the text, create a new Content object accordingly.
- #XXX: handle parse errors ?
- $newContent = ContentHandler::makeContent( $newtext, $this->getTitle(),
- $newContent->getModel() );
- }
-
+ ContentHandler::runLegacyHooks( 'EditPageGetDiffText', array( $this, &$newContent ) );
wfRunHooks( 'EditPageGetDiffContent', array( $this, &$newContent ) );
$popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
$content = $content->addSectionHeader( $this->summary );
}
- $toparse_orig = $content->serialize( $this->content_format );
- $toparse = $toparse_orig;
- wfRunHooks( 'EditPageGetPreviewText', array( $this, &$toparse ) );
-
- if ( $toparse !== $toparse_orig ) {
- #hook changed the text, create new Content object
- $content = ContentHandler::makeContent( $toparse, $this->getTitle(),
- $this->content_model, $this->content_format );
- }
-
- wfRunHooks( 'EditPageGetPreviewContent', array( $this, &$content ) );
+ $hook_args = array( $this, &$content );
+ ContentHandler::runLegacyHooks( 'EditPageGetPreviewText', $hook_args );
+ wfRunHooks( 'EditPageGetPreviewContent', $hook_args );
$parserOptions->enableLimitReport();
class LinkFilter {
/**
- * Check whether $text contains a link to $filterEntry
+ * Check whether $content contains a link to $filterEntry
*
- * @param $text String: text to check
+ * @param $content Content: content to check
* @param $filterEntry String: domainparts, see makeRegex() for more details
* @return Integer: 0 if no match or 1 if there's at least one match
*/
- static function matchEntry( $text, $filterEntry ) {
+ static function matchEntry( Content $content, $filterEntry ) {
+ if ( !( $content instanceof TextContent ) ) {
+ //TODO: handle other types of content too.
+ // Maybe create ContentHandler::matchFilter( LinkFilter ).
+ // Think about a common base class for LinkFilter and MagicWord.
+ return 0;
+ }
+
+ $text = $content->getNativeData();
+
$regex = LinkFilter::makeRegex( $filterEntry );
return preg_match( $regex, $text );
}
$this->mContent = is_null( $this->mText ) ? null : $handler->unserializeContent( $this->mText, $format );
}
- return $this->mContent;
+ return $this->mContent->copy(); // NOTE: copy() will return $this for immutable content objects
}
/**
$flags = $this->checkFlags( $flags );
- # call legacy hook
- $hook_ok = wfRunHooks( 'ArticleContentSave', array( &$this, &$user, &$content, &$summary,
- $flags & EDIT_MINOR, null, null, &$flags, &$status ) );
+ # handle hook
+ $hook_args = array( &$this, &$user, &$content, &$summary,
+ $flags & EDIT_MINOR, null, null, &$flags, &$status );
- if ( $hook_ok && Hooks::isRegistered( 'ArticleSave' ) ) { # avoid serialization overhead if the hook isn't present
- $content_text = $content->serialize();
- $txt = $content_text; # clone
+ if ( !wfRunHooks( 'ArticleContentSave', $hook_args )
+ || !ContentHandler::runLegacyHooks( 'ArticleSave', $hook_args ) ) {
- $hook_ok = wfRunHooks( 'ArticleSave', array( &$this, &$user, &$txt, &$summary,
- $flags & EDIT_MINOR, null, null, &$flags, &$status ) ); #TODO: survey extensions using this hook
-
- if ( $txt !== $content_text ) {
- # if the text changed, unserialize the new version to create an updated Content object.
- $content = $content->getContentHandler()->unserializeContent( $txt );
- }
- }
-
- if ( !$hook_ok ) {
wfDebug( __METHOD__ . ": ArticleSave or ArticleSaveContent hook aborted save!\n" );
if ( $status->isOK() ) {
# Update links, etc.
$this->doEditUpdates( $revision, $user, array( 'created' => true ) );
- wfRunHooks( 'ArticleInsertComplete', array( &$this, &$user, $serialized, $summary,
- $flags & EDIT_MINOR, null, null, &$flags, $revision ) );
+ $hook_args = array( &$this, &$user, $content, $summary,
+ $flags & EDIT_MINOR, null, null, &$flags, $revision );
- wfRunHooks( 'ArticleContentInsertComplete', array( &$this, &$user, $content, $summary,
- $flags & EDIT_MINOR, null, null, &$flags, $revision ) );
+ ContentHandler::runLegacyHooks( 'ArticleInsertComplete', $hook_args );
+ wfRunHooks( 'ArticleContentInsertComplete', $hook_args );
}
# Do updates right now unless deferral was requested
// Return the new revision (or null) to the caller
$status->value['revision'] = $revision;
- wfRunHooks( 'ArticleSaveComplete', array( &$this, &$user, $serialized, $summary,
- $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) );
+ $hook_args = array( &$this, &$user, $content, $summary,
+ $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId );
- wfRunHooks( 'ArticleContentSaveComplete', array( &$this, &$user, $content, $summary,
- $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) );
+ ContentHandler::runLegacyHooks( 'ArticleSaveComplete', $hook_args );
+ wfRunHooks( 'ArticleContentSaveComplete', $hook_args );
# Promote user to any groups they meet the criteria for
$user->addAutopromoteOnceGroups( 'onEdit' );
$this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) );
}
- $contentHandler = $pageObj->getContentHandler();
+ if ( !isset( $params['contentmodel'] ) || $params['contentmodel'] == '' ) {
+ $contentHandler = $pageObj->getContentHandler();
+ } else {
+ $contentHandler = ContentHandler::getForModelID( $params['contentmodel'] );
+ }
// @todo ask handler whether direct editing is supported at all! make allowFlatEdit() method or some such
$text = '';
}
- $content = ContentHandler::makeContent( $text, $this->getTitle() );
+ try {
+ $content = ContentHandler::makeContent( $text, $this->getTitle() );
+ } catch ( MWContentSerializationException $ex ) {
+ $this->dieUsage( $ex->getMessage(), 'parseerror' );
+ return;
+ }
}
}
$this->dieUsageMsg( array( 'revwrongpage', $undoafterRev->getID(), $titleObj->getPrefixedText() ) );
}
- $newContent = $contentHandler->getUndoContent( $undoRev, $undoafterRev );
+ $newContent = $contentHandler->getUndoContent( $pageObj->getRevision(), $undoRev, $undoafterRev );
+
if ( !$newContent ) {
$this->dieUsageMsg( 'undo-failure' );
}
- $params['contentformat'] = $contentHandler->getDefaultFormat();
$params['text'] = $newContent->serialize( $params['contentformat'] );
// If no summary was given and we only undid one rev,
case EditPage::AS_HOOK_ERROR_EXPECTED:
$this->dieUsageMsg( 'hookaborted' );
+ case EditPage::AS_PARSE_ERROR:
+ $this->dieUsage( $status->getMessage(), 'parseerror' );
+
case EditPage::AS_IMAGE_REDIRECT_ANON:
$this->dieUsageMsg( 'noimageredirect-anon' );
array( 'undo-failure' ),
array( 'hashcheckfailed' ),
array( 'hookaborted' ),
+ array( 'code' => 'parseerror', 'info' => 'Failed to parse the given text.' ),
array( 'noimageredirect-anon' ),
array( 'noimageredirect-logged' ),
array( 'spamdetected', 'spam' ),
ApiBase::PARAM_TYPE => 'boolean',
ApiBase::PARAM_DFLT => false,
),
+ 'contentformat' => array(
+ ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
+ ),
+ 'contentmodel' => array(
+ ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
+ )
);
}
'undo' => "Undo this revision. Overrides {$p}text, {$p}prependtext and {$p}appendtext",
'undoafter' => 'Undo all revisions from undo to this one. If not set, just undo one revision',
'redirect' => 'Automatically resolve redirects',
+ 'contentformat' => 'Content serialization format used for the input text',
+ 'contentmodel' => 'Content model of the new content',
);
}
* @ingroup API
*/
class ApiParse extends ApiBase {
- private $section, $text, $pstText = null;
+
+ /** @var String $section */
+ private $section = null;
+
+ /** @var Content $content */
+ private $content = null;
+
+ /** @var Content $pstContent */
+ private $pstContent = null;
public function __construct( $main, $action ) {
parent::__construct( $main, $action );
$pageid = $params['pageid'];
$oldid = $params['oldid'];
+ $model = $params['contentmodel'];
+ $format = $params['contentformat'];
+
if ( !is_null( $page ) && ( !is_null( $text ) || $title != 'API' ) ) {
$this->dieUsage( 'The page parameter cannot be used together with the text and title parameters', 'params' );
}
// If for some reason the "oldid" is actually the current revision, it may be cached
if ( $titleObj->getLatestRevID() === intval( $oldid ) ) {
// May get from/save to parser cache
- $p_result = $this->getParsedSectionOrText( $titleObj, $popts, $pageid,
- isset( $prop['wikitext'] ) ) ;
+ $pageObj = WikiPage::factory( $titleObj );
+ $p_result = $this->getParsedContent( $pageObj, $popts, $pageid, isset( $prop['wikitext'] ) ) ;
} else { // This is an old revision, so get the text differently
- $this->text = $rev->getText( Revision::FOR_THIS_USER, $this->getUser() );
+ $this->content = $rev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
if ( $this->section !== false ) {
- $this->text = $this->getSectionText( $this->text, 'r' . $rev->getId() );
+ $this->content = $this->getSectionContent( $this->content, 'r' . $rev->getId() );
}
// Should we save old revision parses to the parser cache?
- $p_result = $wgParser->parse( $this->text, $titleObj, $popts );
+ $p_result = $this->content->getParserOutput( $titleObj, $popts );
}
} else { // Not $oldid, but $pageid or $page
if ( $params['redirects'] ) {
$oldid = $titleObj->getLatestRevID();
}
+ $pageObj = WikiPage::factory( $titleObj );
+
// Potentially cached
- $p_result = $this->getParsedSectionOrText( $titleObj, $popts, $pageid,
- isset( $prop['wikitext'] ) ) ;
+ $p_result = $this->getParsedContent( $pageObj, $popts, $pageid, isset( $prop['wikitext'] ) ) ;
}
} else { // Not $oldid, $pageid, $page. Hence based on $text
-
- if ( is_null( $text ) ) {
- $this->dieUsage( 'The text parameter should be passed with the title parameter. Should you be using the "page" parameter instead?', 'params' );
- }
- $this->text = $text;
$titleObj = Title::newFromText( $title );
if ( !$titleObj ) {
$this->dieUsageMsg( array( 'invalidtitle', $title ) );
}
$wgTitle = $titleObj;
+ if ( is_null( $text ) ) {
+ $this->dieUsage( 'The text parameter should be passed with the title parameter. Should you be using the "page" parameter instead?', 'params' );
+ }
+
+ try {
+ $this->content = ContentHandler::makeContent( $text, $titleObj, $model, $format );
+ } catch ( MWContentSerializationException $ex ) {
+ $this->dieUsage( $ex->getMessage(), 'parseerror' );
+ }
+
if ( $this->section !== false ) {
- $this->text = $this->getSectionText( $this->text, $titleObj->getText() );
+ $this->content = $this->getSectionContent( $this->content, $titleObj->getText() );
}
if ( $params['pst'] || $params['onlypst'] ) {
- $this->pstText = $wgParser->preSaveTransform( $this->text, $titleObj, $this->getUser(), $popts );
+ $this->pstContent = $this->content->preSaveTransform( $titleObj, $this->getUser(), $popts );
}
if ( $params['onlypst'] ) {
// Build a result and bail out
$result_array = array();
$result_array['text'] = array();
- $result->setContent( $result_array['text'], $this->pstText );
+ $result->setContent( $result_array['text'], $this->pstContent->serialize( $format ) );
if ( isset( $prop['wikitext'] ) ) {
$result_array['wikitext'] = array();
- $result->setContent( $result_array['wikitext'], $this->text );
+ $result->setContent( $result_array['wikitext'], $this->content->serialize( $format ) );
}
$result->addValue( null, $this->getModuleName(), $result_array );
return;
}
+
// Not cached (save or load)
- #FIXME: use Content object!
- $p_result = $wgParser->parse( $params['pst'] ? $this->pstText : $this->text, $titleObj, $popts );
+ if ( $params['pst'] ) {
+ $p_result = $this->pstContent->getParserOutput( $titleObj, $popts );
+ } else {
+ $p_result = $this->content->getParserOutput( $titleObj, $popts );
+ }
}
$result_array = array();
if ( isset( $prop['wikitext'] ) ) {
$result_array['wikitext'] = array();
- $result->setContent( $result_array['wikitext'], $this->text );
- if ( !is_null( $this->pstText ) ) {
+ $result->setContent( $result_array['wikitext'], $this->content->serialize( $format ) );
+ if ( !is_null( $this->pstContent ) ) {
$result_array['psttext'] = array();
- $result->setContent( $result_array['psttext'], $this->pstText );
+ $result->setContent( $result_array['psttext'], $this->pstContent->serialize( $format ) );
}
}
if ( isset( $prop['properties'] ) ) {
}
if ( $params['generatexml'] ) {
+ if ( $this->content->getModel() != CONTENT_MODEL_WIKITEXT ) {
+ $this->dieUsage( "generatexml is only supported for wikitext content", "notwikitext" );
+ }
+
$wgParser->startExternalParse( $titleObj, $popts, OT_PREPROCESS );
- $dom = $wgParser->preprocessToDom( $this->text );
+ $dom = $wgParser->preprocessToDom( $this->content->getNativeData() );
if ( is_callable( array( $dom, 'saveXML' ) ) ) {
$xml = $dom->saveXML();
} else {
}
/**
- * @param $titleObj Title
+ * @param $page WikiPage
* @param $popts ParserOptions
* @param $pageId Int
* @param $getWikitext Bool
* @return ParserOutput
*/
- private function getParsedSectionOrText( $titleObj, $popts, $pageId = null, $getWikitext = false ) {
- global $wgParser;
-
- $page = WikiPage::factory( $titleObj );
+ private function getParsedContent( WikiPage $page, $popts, $pageId = null, $getWikitext = false ) {
+ $this->content = $page->getContent( Revision::RAW ); //XXX: really raw?
- if ( $this->section !== false ) { #FIXME: get section Content, get parser output, ...
- $this->text = $this->getSectionText( $page->getRawText(), !is_null( $pageId )
- ? 'page id ' . $pageId : $titleObj->getText() ); #FIXME: get section...
+ if ( $this->section !== false ) {
+ $this->content = $this->getSectionContent( $this->content, !is_null( $pageId )
+ ? 'page id ' . $pageId : $page->getTitle()->getText() );
// Not cached (save or load)
- return $wgParser->parse( $this->text, $titleObj, $popts );
+ return $this->content->getParserOutput( $page->getTitle(), $popts );
} else {
// Try the parser cache first
// getParserOutput will save to Parser cache if able
$this->dieUsage( "There is no revision ID {$page->getLatest()}", 'missingrev' );
}
if ( $getWikitext ) {
- $this->content = $page->getContent( Revision::RAW ); #FIXME: use $this->content everywhere
- $this->text = ContentHandler::getContentText( $this->content ); #FIXME: serialize, get format from params; or use object structure in result?
+ $this->content = $page->getContent( Revision::RAW );
}
return $pout;
}
}
- private function getSectionText( $text, $what ) { #FIXME: replace with Content::getSection
- global $wgParser;
+ private function getSectionContent( Content $content, $what ) {
// Not cached (save or load)
- $text = $wgParser->getSection( $text, $this->section, false );
- if ( $text === false ) {
+ $section = $content->getSection( $this->section );
+ if ( $section === false ) {
$this->dieUsage( "There is no section {$this->section} in " . $what, 'nosuchsection' );
}
- return $text;
+ if ( $section === null ) {
+ $this->dieUsage( "Sections are not supported by " . $what, 'nosuchsection' );
+ $section = false;
+ }
+ return $section;
}
private function formatLangLinks( $links ) {
'section' => null,
'disablepp' => false,
'generatexml' => false,
+ 'contentformat' => array(
+ ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
+ ),
+ 'contentmodel' => array(
+ ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
+ )
);
}
'section' => 'Only retrieve the content of this section number',
'disablepp' => 'Disable the PP Report from the parser output',
'generatexml' => 'Generate XML parse tree',
+ 'contentformat' => 'Content serialization format used for the input text',
+ 'contentmodel' => 'Content model of the new content',
);
}
array( 'code' => 'nosuchsection', 'info' => 'There is no section sectionnumber in page' ),
array( 'nosuchpageid' ),
array( 'invalidtitle', 'title' ),
+ array( 'code' => 'parseerror', 'info' => 'Failed to parse the given text.' ),
+ array( 'code' => 'notwikitext', 'info' => 'The requested operation is only supported on wikitext content.' ),
) );
}
* @private
* @ingroup DifferenceEngine
*/
-class _DiffOp { #FIXME: no longer private!
+class _DiffOp {
var $type;
var $orig;
var $closing;
* @private
* @ingroup DifferenceEngine
*/
-class _DiffOp_Copy extends _DiffOp { #FIXME: no longer private!
+class _DiffOp_Copy extends _DiffOp {
var $type = 'copy';
function __construct( $orig, $closing = false ) {
* @private
* @ingroup DifferenceEngine
*/
-class _DiffOp_Delete extends _DiffOp { #FIXME: no longer private!
+class _DiffOp_Delete extends _DiffOp {
var $type = 'delete';
function __construct( $lines ) {
* @private
* @ingroup DifferenceEngine
*/
-class _DiffOp_Add extends _DiffOp { #FIXME: no longer private!
+class _DiffOp_Add extends _DiffOp {
var $type = 'add';
function __construct( $lines ) {
* @private
* @ingroup DifferenceEngine
*/
-class _DiffOp_Change extends _DiffOp { #FIXME: no longer private!
+class _DiffOp_Change extends _DiffOp {
var $type = 'change';
function __construct( $orig, $closing ) {
* @private
* @ingroup DifferenceEngine
*/
-class _DiffEngine { #FIXME: no longer private!
+class _DiffEngine {
const MAX_XREF_LENGTH = 10000;
* @private
* @ingroup DifferenceEngine
*/
-class Diff extends DiffResult {
+class Diff {
var $edits;
/**
* @param $from_lines array An array of strings.
* (Typically these are lines from a file.)
* @param $to_lines array An array of strings.
- * @param $eng _DiffEngine|null The diff engine to use.
*/
- function __construct( $from_lines, $to_lines, $eng = null ) {
- if ( !$eng ) {
- $eng = new _DiffEngine();
- }
-
- $edits = $eng->diff( $from_lines, $to_lines );
-
- parent::__construct( $edits );
-
- //$this->_check( $from_lines, $to_lines );
- }
-}
-
-/**
- * Class representing the result of 'diffin' two sequences of strings.
- * @todo document
- * @private
- * @ingroup DifferenceEngine
- */
-class DiffResult {
-
- /**
- * Constructor.
- *
- * @param $edits array An array of Edit.
- */
- function __construct( $edits ) {
- $this->edits = $edits;
+ function __construct( $from_lines, $to_lines ) {
+ $eng = new _DiffEngine;
+ $this->edits = $eng->diff( $from_lines, $to_lines );
+ // $this->_check($from_lines, $to_lines);
}
/**
/**
* Get the closing set of lines.
*
+ * This reconstructs the $to_lines parameter passed to the
+ * constructor.
+ *
* @return array The sequence of strings.
*/
function closing() {
# If external diffs are enabled both globally and for the user,
# we'll use the application/x-external-editor interface to call
# an external diff tool like kompare, kdiff3, etc.
- if ( ExternalEdit::useExternalEngine( $this->getContext(), 'diff' ) ) { #FIXME: how to handle this for non-text content?
+ if ( ExternalEdit::useExternalEngine( $this->getContext(), 'diff' ) ) {
+ //TODO: come up with a good solution for non-text content here.
+ // at least, the content format needs to be passed to the client somehow.
+ // Currently, action=raw will just fail for non-text content.
+
$urls = array(
'File' => array( 'Extension' => 'wiki', 'URL' =>
# This should be mOldPage, but it may not be set, see below.
$out->setRevisionTimestamp( $this->mNewRev->getTimestamp() );
$out->setArticleFlag( true );
- #NOTE: only needed for B/C: custom rendering of JS/CSS via hook
+ // NOTE: only needed for B/C: custom rendering of JS/CSS via hook
if ( $this->mNewPage->isCssJsSubpage() || $this->mNewPage->isCssOrJsPage() ) {
// Stolen from Article::view --AG 2007-10-11
// Give hooks a chance to customise the output
// @TODO: standardize this crap into one function
- if ( !Hook::isRegistered( 'ShowRawCssJs' )
- || wfRunHooks( 'ShowRawCssJs', array( ContentHandler::getContentText( $this->mNewContent ), $this->mNewPage, $out ) ) ) {
+ if ( ContentHandler::runLegacyHooks( 'ShowRawCssJs', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
// NOTE: deprecated hook, B/C only
// use the content object's own rendering
$po = $this->mContentObject->getParserOutput();
}
} elseif( !wfRunHooks( 'ArticleContentViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
// Handled by extension
- } elseif( Hooks::isRegistered( 'ArticleViewCustom' )
- && !wfRunHooks( 'ArticleViewCustom', array( ContentHandler::getContentText( $this->mNewContent ), $this->mNewPage, $out ) ) ) {
+ } elseif( !ContentHandler::runLegacyHooks( 'ArticleViewCustom', array( $this->mNewContent, $this->mNewPage, $out ) ) ) {
// NOTE: deprecated hook, B/C only
// Handled by extension
} else {
* @since 1.WD
*/
function generateContentDiffBody( Content $old, Content $new ) {
- #XXX: generate a warning if $old or $new are not instances of TextContent?
- #XXX: fail if $old and $new don't have the same content model? or what?
+ if ( !( $old instanceof TextContent ) ) {
+ throw new MWException( "Diff not implemented for " . get_class( $old ) . "; "
+ . "override generateContentDiffBody to fix this." );
+ }
+
+ if ( !( $new instanceof TextContent ) ) {
+ throw new MWException( "Diff not implemented for " . get_class( $new ) . "; "
+ . "override generateContentDiffBody to fix this." );
+ }
$otext = $old->serialize();
$ntext = $new->serialize();
- #XXX: text should be "already segmented". what does that mean?
return $this->generateTextDiffBody( $otext, $ntext );
}
/**
* Use specified text instead of loading from the database
- * @deprecated since 1.WD
+ * @deprecated since 1.WD, use setContent() instead.
*/
- function setText( $oldText, $newText ) { #FIXME: no longer use this, use setContent()!
+ function setText( $oldText, $newText ) {
wfDeprecated( __METHOD__, "1.WD" );
$oldContent = ContentHandler::makeContent( $oldText, $this->getTitle() );
}
# Check for a suppression tag (used e.g. in periodically archived discussions)
- $text = ContentHandler::getContentText( $content );
$mw = MagicWord::get( 'staticredirect' );
- if ( $mw->match( $text ) ) { #FIXME: add support for this to ContentHandler/Content
+ if ( $content->matchMagicWord( $mw ) ) {
wfDebug( __METHOD__.": skipping: suppressed with __STATICREDIRECT__\n" );
return true;
}
$newTitle = Title::makeTitle( $newTitle->getNamespace(), $newTitle->getDBkey(),
$currentDest->getFragment() );
+ $text = ContentHandler::getContentText( $content ); #FIXME: get rid of this!
+
# Fix the text
# Remember that redirect pages can have categories, templates, etc.,
# so the regex has to be fairly general
$rev = Revision::newFromTitle( $title );
$currentRevId = $rev->getId();
- //FIXME: LinkFilter needs to handle Content objects! Or rather, ContentHandler needs to provide the appropriate LinkFilter.
- while ( $rev && ( $rev->isDeleted( Revision::DELETED_TEXT ) || LinkFilter::matchEntry( $rev->getText() , $domain ) ) ) {
+ while ( $rev && ( $rev->isDeleted( Revision::DELETED_TEXT )
+ || LinkFilter::matchEntry( $rev->getContent( Revision::RAW ), $domain ) ) ) {
$rev = $rev->getPrevious();
}
$page = WikiPage::factory( $title );
if ( $rev ) {
// Revert to this revision
+ $content = $rev->getContent( Revision::RAW );
+
$this->output( "reverting\n" );
- $page->doEdit( $rev->getText(), wfMessage( 'spam_reverting', $domain )->inContentLanguage()->text(),
+ $page->doEditContent( $content, wfMessage( 'spam_reverting', $domain )->inContentLanguage()->text(),
EDIT_UPDATE, $rev->getId() );
} elseif ( $this->hasOption( 'delete' ) ) {
// Didn't find a non-spammy revision, blank the page
$page->doDeleteArticle( wfMessage( 'spam_deleting', $domain )->inContentLanguage()->text() );
} else {
// Didn't find a non-spammy revision, blank the page
+ $handler = ContentHandler::getForTitle( $title );
+ $content = $handler->makeEmptyContent();
+
$this->output( "blanking\n" );
- $page->doEdit( '', wfMessage( 'spam_blanking', $domain )->inContentLanguage()->text() );
+ $page->doEditContent( $content, wfMessage( 'spam_blanking', $domain )->inContentLanguage()->text() );
}
$dbw->commit( __METHOD__ );
}
public function testSupportsSections() {
$this->markTestIncomplete( "not yet implemented" );
}
+
+ public function testRunLegacyHooks() {
+ Hooks::register( 'testRunLegacyHooks', __CLASS__ . '::dummyHookHandler' );
+
+ $content = new WikitextContent( 'test text' );
+ $ok = ContentHandler::runLegacyHooks( 'testRunLegacyHooks', array( 'foo', &$content, 'bar' ) );
+
+ $this->assertTrue( $ok, "runLegacyHooks should have returned true" );
+ $this->assertEquals( "TEST TEXT", $content->getNativeData() );
+ }
+
+ public static function dummyHookHandler( $foo, &$text, $bar ) {
+ if ( $text === null || $text === false ) {
+ return false;
+ }
+
+ $text = strtoupper( $text );
+
+ return true;
+ }
}
class DummyContentHandlerForTesting extends ContentHandler {
return new ParserOutput( $this->getNativeData() );
}
}
-
);
}
+ public function testMatchMagicWord( ) {
+ $mw = MagicWord::get( "staticredirect" );
+
+ $content = $this->newContent( "#REDIRECT [[FOO]]\n__STATICREDIRECT__" );
+ $this->assertFalse( $content->matchMagicWord( $mw ), "should not have matched magic word, since it's not wikitext" );
+ }
+
# =================================================================================================================
public function testGetModel() {
$wgNamespaceContentModels[ 12312 ] = "testing";
$wgContentHandlers[ "testing" ] = 'DummyContentHandlerForTesting';
+ $wgContentHandlers[ "RevisionTestModifyableContent" ] = 'RevisionTestModifyableContentHandler';
MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache
$wgContLang->resetNamespaces(); # reset namespace cache
$this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() );
}
+ /**
+ * 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
+ }
+
+
+ /**
+ * Tests whether $rev->getContent() returns the same object repeatedly if appropriate.
+ *
+ * @group Database
+ */
+ function testGetContentUncloned() {
+ $rev = $this->newTestRevision( "hello", "testGetContentUncloned_dummy", CONTENT_MODEL_WIKITEXT );
+ $content = $rev->getContent( Revision::RAW );
+ $content2 = $rev->getContent( Revision::RAW );
+
+ // for immutable content like wikitext, this should be the same object
+ $this->assertSame( $content, $content2 );
+ }
+
+}
+
+class RevisionTestModifyableContent extends TextContent {
+ public function __construct( $text ) {
+ parent::__construct( $text, "RevisionTestModifyableContent" );
+ }
+
+ public function copy( ) {
+ return new RevisionTestModifyableContent( $this->mText );
+ }
+
+ public function getText() {
+ return $this->mText;
+ }
+
+ public function setText( $text ) {
+ $this->mText = $text;
+ }
+
}
+class RevisionTestModifyableContentHandler extends TextContentHandler {
+
+ public function __construct( ) {
+ parent::__construct( "RevisionTestModifyableContent", array( CONTENT_FORMAT_TEXT ) );
+ }
+ public function unserializeContent( $text, $format = null ) {
+ $this->checkFormat( $format );
+
+ return new RevisionTestModifyableContent( $text );
+ }
+
+ public function makeEmptyContent() {
+ return new RevisionTestModifyableContent( '' );
+ }
+}
$this->assertEquals( "hello world.", $content->getWikitextForTransclusion() );
}
+ public function testMatchMagicWord( ) {
+ $mw = MagicWord::get( "staticredirect" );
+
+ $content = $this->newContent( "#REDIRECT [[FOO]]\n__STATICREDIRECT__" );
+ $this->assertTrue( $content->matchMagicWord( $mw ), "should have matched magic word" );
+
+ $content = $this->newContent( "#REDIRECT [[FOO]]" );
+ $this->assertFalse( $content->matchMagicWord( $mw ), "should not have matched magic word" );
+ }
+
# =================================================================================================================
public function testGetModel() {