From: daniel Date: Tue, 28 Aug 2012 11:42:46 +0000 (+0000) Subject: Merge "Add Content::matchMagicWord" into Wikidata X-Git-Tag: 1.31.0-rc.0~22097^2^2~50 X-Git-Url: https://git.cyclocoop.org/%242?a=commitdiff_plain;h=da1f395cfb7b4e5ad6963305ef35904754162917;hp=ac8150cd6ff073f93991564992a0e44641bc957e;p=lhc%2Fweb%2Fwiklou.git Merge "Add Content::matchMagicWord" into Wikidata --- diff --git a/includes/Article.php b/includes/Article.php index dc6294f2b7..f791edffd0 100644 --- a/includes/Article.php +++ b/includes/Article.php @@ -391,7 +391,7 @@ class Article extends Page { $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__ ); @@ -679,10 +679,16 @@ class Article extends Page { 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 { @@ -693,7 +699,8 @@ class Article extends Page { # 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; } @@ -830,7 +837,7 @@ class Article extends Page { } // 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() ); } @@ -1704,8 +1711,10 @@ class Article extends Page { * @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 ); } diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 9c18cedba1..6b3b27aa9e 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -293,6 +293,7 @@ $wgAutoloadLocalClasses = array( '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', diff --git a/includes/ContentHandler.php b/includes/ContentHandler.php index e460eeef21..1f20682189 100644 --- a/includes/ContentHandler.php +++ b/includes/ContentHandler.php @@ -331,6 +331,12 @@ abstract class ContentHandler { else return wfMsg( $key ); } + public static function getContentModels() { + global $wgContentHandlers; + + return array_keys( $wgContentHandlers ); + } + public static function getAllContentFormats() { global $wgContentHandlers; @@ -825,6 +831,62 @@ abstract class ContentHandler { 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; + } } /** diff --git a/includes/EditPage.php b/includes/EditPage.php index 3e38fb8ca9..09e5f15350 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -1457,8 +1457,10 @@ class EditPage { } // 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; @@ -2547,18 +2549,7 @@ HTML $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 ); @@ -2862,17 +2853,9 @@ HTML $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(); diff --git a/includes/Revision.php b/includes/Revision.php index 30ffbea35a..0008b4e918 100644 --- a/includes/Revision.php +++ b/includes/Revision.php @@ -882,9 +882,10 @@ class Revision implements IDBAccessObject { * Revision::RAW get the text regardless of permissions * @param $user User object to check for, only if FOR_THIS_USER is passed * to the $audience parameter - * @return String + * * @deprecated in 1.WD, use getContent() instead * @todo: replace usage in core + * @return String */ public function getText( $audience = self::FOR_PUBLIC, User $user = null ) { wfDeprecated( __METHOD__, '1.WD' ); @@ -904,9 +905,8 @@ class Revision implements IDBAccessObject { * Revision::RAW get the text regardless of permissions * @param $user User object to check for, only if FOR_THIS_USER is passed * to the $audience parameter - * @return Content - * * @since 1.WD + * @return Content */ public function getContent( $audience = self::FOR_PUBLIC, User $user = null ) { if( $audience == self::FOR_PUBLIC && $this->isDeleted( self::DELETED_TEXT ) ) { @@ -946,14 +946,19 @@ class Revision implements IDBAccessObject { /** * Fetch original serialized data without regard for view restrictions * - * @return String - * * @since 1.WD + * @return String */ public function getSerializedData() { return $this->mText; } + /** + * Gets the content object for the revision + * + * @since 1.WD + * @return Content + */ protected function getContentInternal() { if( is_null( $this->mContent ) ) { // Revision is immutable. Load on demand: @@ -970,7 +975,7 @@ class Revision implements IDBAccessObject { $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 } /** diff --git a/includes/WikiPage.php b/includes/WikiPage.php index a4afda7ef4..0318f2d8d9 100644 --- a/includes/WikiPage.php +++ b/includes/WikiPage.php @@ -1623,24 +1623,13 @@ class WikiPage extends Page implements IDBAccessObject { $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() ) { @@ -1869,11 +1858,11 @@ class WikiPage extends Page implements IDBAccessObject { # 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 @@ -1884,11 +1873,11 @@ class WikiPage extends Page implements IDBAccessObject { // 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' ); diff --git a/includes/api/ApiEditPage.php b/includes/api/ApiEditPage.php index 7574e6ce81..a611c9f146 100644 --- a/includes/api/ApiEditPage.php +++ b/includes/api/ApiEditPage.php @@ -54,7 +54,11 @@ class ApiEditPage extends ApiBase { $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 @@ -139,7 +143,12 @@ class ApiEditPage extends ApiBase { $text = ''; } - $content = ContentHandler::makeContent( $text, $this->getTitle() ); + try { + $content = ContentHandler::makeContent( $text, $this->getTitle() ); + } catch ( MWContentSerializationException $ex ) { + $this->dieUsage( $ex->getMessage(), 'parseerror' ); + return; + } } } @@ -195,12 +204,12 @@ class ApiEditPage extends ApiBase { $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, @@ -322,6 +331,9 @@ class ApiEditPage extends ApiBase { 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' ); @@ -425,6 +437,7 @@ class ApiEditPage extends ApiBase { 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' ), @@ -512,6 +525,12 @@ class ApiEditPage extends ApiBase { ApiBase::PARAM_TYPE => 'boolean', ApiBase::PARAM_DFLT => false, ), + 'contentformat' => array( + ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(), + ), + 'contentmodel' => array( + ApiBase::PARAM_TYPE => ContentHandler::getContentModels(), + ) ); } @@ -550,6 +569,8 @@ class ApiEditPage extends ApiBase { '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', ); } diff --git a/includes/api/ApiParse.php b/includes/api/ApiParse.php index 3500795232..702825a4da 100644 --- a/includes/api/ApiParse.php +++ b/includes/api/ApiParse.php @@ -26,7 +26,15 @@ * @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 ); @@ -44,6 +52,9 @@ class ApiParse extends ApiBase { $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' ); } @@ -95,17 +106,17 @@ class ApiParse extends ApiBase { // 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'] ) { @@ -153,44 +164,54 @@ class ApiParse extends ApiBase { $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(); @@ -280,10 +301,10 @@ class ApiParse extends ApiBase { 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'] ) ) { @@ -291,8 +312,12 @@ class ApiParse extends ApiBase { } 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 { @@ -324,23 +349,21 @@ class ApiParse extends ApiBase { } /** - * @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 @@ -349,21 +372,23 @@ class ApiParse extends ApiBase { $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 ) { @@ -556,6 +581,12 @@ class ApiParse extends ApiBase { 'section' => null, 'disablepp' => false, 'generatexml' => false, + 'contentformat' => array( + ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(), + ), + 'contentmodel' => array( + ApiBase::PARAM_TYPE => ContentHandler::getContentModels(), + ) ); } @@ -601,6 +632,8 @@ class ApiParse extends ApiBase { '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', ); } @@ -621,6 +654,8 @@ class ApiParse extends ApiBase { 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.' ), ) ); } diff --git a/includes/diff/DifferenceEngine.php b/includes/diff/DifferenceEngine.php index f76d9f0cab..2030f1ed16 100644 --- a/includes/diff/DifferenceEngine.php +++ b/includes/diff/DifferenceEngine.php @@ -223,7 +223,11 @@ class DifferenceEngine extends ContextSource { # 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. @@ -510,13 +514,12 @@ class DifferenceEngine extends ContextSource { $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(); @@ -524,8 +527,7 @@ class DifferenceEngine extends ContextSource { } } 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 { @@ -711,13 +713,19 @@ class DifferenceEngine extends ContextSource { * @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 ); } @@ -998,9 +1006,9 @@ class DifferenceEngine extends ContextSource { /** * 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() ); diff --git a/includes/installer/DatabaseUpdater.php b/includes/installer/DatabaseUpdater.php index 6483c4e8ac..d344704b6c 100644 --- a/includes/installer/DatabaseUpdater.php +++ b/includes/installer/DatabaseUpdater.php @@ -561,15 +561,27 @@ abstract class DatabaseUpdater { } /** + * If the specified table exists, drop it, or execute the + * patch if one is provided. + * + * Public @since 1.20 + * * @param $table string - * @param $patch string + * @param $patch string|false * @param $fullpath bool */ - protected function dropTable( $table, $patch, $fullpath = false ) { + public function dropTable( $table, $patch = false, $fullpath = false ) { if ( $this->db->tableExists( $table, __METHOD__ ) ) { - $this->output( "Dropping table $table... " ); - $this->applyPatch( $patch, $fullpath ); - $this->output( "done.\n" ); + $msg = "Dropping table $table"; + + if ( $patch === false ) { + $this->output( "$msg ..." ); + $this->db->dropTable( $table, __METHOD__ ); + $this->output( "done.\n" ); + } + else { + $this->applyPatch( $patch, $fullpath, $msg ); + } } else { $this->output( "...$table doesn't exist.\n" ); } diff --git a/tests/phpunit/includes/ContentHandlerTest.php b/tests/phpunit/includes/ContentHandlerTest.php index afeddf88df..01bba65726 100644 --- a/tests/phpunit/includes/ContentHandlerTest.php +++ b/tests/phpunit/includes/ContentHandlerTest.php @@ -236,6 +236,26 @@ class ContentHandlerTest extends MediaWikiTestCase { 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 { @@ -389,4 +409,3 @@ class DummyContentForTesting extends AbstractContent { return new ParserOutput( $this->getNativeData() ); } } - diff --git a/tests/phpunit/includes/RevisionTest.php b/tests/phpunit/includes/RevisionTest.php index f9a400cdd3..5a983555c0 100644 --- a/tests/phpunit/includes/RevisionTest.php +++ b/tests/phpunit/includes/RevisionTest.php @@ -31,6 +31,7 @@ class RevisionTest extends MediaWikiTestCase { $wgNamespaceContentModels[ 12312 ] = "testing"; $wgContentHandlers[ "testing" ] = 'DummyContentHandlerForTesting'; + $wgContentHandlers[ "RevisionTestModifyableContent" ] = 'RevisionTestModifyableContentHandler'; MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache $wgContLang->resetNamespaces(); # reset namespace cache @@ -346,6 +347,87 @@ class RevisionTest extends MediaWikiTestCase { $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( '' ); + } +}