From: Yuri Astrakhan Date: Mon, 15 Sep 2014 08:17:29 +0000 (-0400) Subject: CSS/JSON/JavaScript ContentHandler refactoring X-Git-Tag: 1.31.0-rc.0~14019 X-Git-Url: https://git.cyclocoop.org/%7B%24www_url%7Dadmin/compta/exercices/journal.php?a=commitdiff_plain;h=3a28ee5acb7d8ccfa5d80766f2bd764d14902f24;p=lhc%2Fweb%2Fwiklou.git CSS/JSON/JavaScript ContentHandler refactoring * All content handlers that deal with code/data tend to have English as their page language & pageview language, so moved common code to the abstract CodeContentHandler class. * Renamed JSONContent & JSONContentHandler into JsonContent* Change-Id: I46819a0572ef5becc211d0d82471ff7102edaa3c --- diff --git a/RELEASE-NOTES-1.24 b/RELEASE-NOTES-1.24 index 33fe0d0091..c5766d0c07 100644 --- a/RELEASE-NOTES-1.24 +++ b/RELEASE-NOTES-1.24 @@ -174,7 +174,7 @@ production. * (bug 15484) Users will now be redirected to the login page when they need to log in, rather than being shown a page asking them to log in and having to click another link to actually get to the login page. -* A JSONContent and JSONContentHandler were added for extensions to extend. +* A JsonContent and JsonContentHandler were added for extensions to extend. * (bug 35045) Redirects to sections will now update the URL in browser's address bar using the HTML5 History API. When [[Dog]] redirects to [[Animals#Dog]], the user will now see "Animals#Dog" in their browser instead of "Dog#Dog". diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 5242ec0f65..7c5de2ac6f 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -375,20 +375,21 @@ $wgAutoloadLocalClasses = array( # includes/content 'AbstractContent' => 'includes/content/AbstractContent.php', - 'ContentHandler' => 'includes/content/ContentHandler.php', + 'CodeContentHandler' => 'includes/content/CodeContentHandler.php', 'Content' => 'includes/content/Content.php', - 'CssContentHandler' => 'includes/content/CssContentHandler.php', + 'ContentHandler' => 'includes/content/ContentHandler.php', 'CssContent' => 'includes/content/CssContent.php', - 'JavaScriptContentHandler' => 'includes/content/JavaScriptContentHandler.php', + 'CssContentHandler' => 'includes/content/CssContentHandler.php', 'JavaScriptContent' => 'includes/content/JavaScriptContent.php', - 'JSONContentHandler' => 'includes/content/JSONContentHandler.php', - 'JSONContent' => 'includes/content/JSONContent.php', + 'JavaScriptContentHandler' => 'includes/content/JavaScriptContentHandler.php', + 'JsonContent' => 'includes/content/JsonContent.php', + 'JsonContentHandler' => 'includes/content/JsonContentHandler.php', 'MessageContent' => 'includes/content/MessageContent.php', 'MWContentSerializationException' => 'includes/content/ContentHandler.php', - 'TextContentHandler' => 'includes/content/TextContentHandler.php', 'TextContent' => 'includes/content/TextContent.php', - 'WikitextContentHandler' => 'includes/content/WikitextContentHandler.php', + 'TextContentHandler' => 'includes/content/TextContentHandler.php', 'WikitextContent' => 'includes/content/WikitextContent.php', + 'WikitextContentHandler' => 'includes/content/WikitextContentHandler.php', # includes/context 'ContextSource' => 'includes/context/ContextSource.php', diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 73179cf1a1..8b29733706 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -859,7 +859,7 @@ $wgContentHandlers = array( // dumb version, no syntax highlighting CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler', // simple implementation, for use by extensions, etc. - CONTENT_MODEL_JSON => 'JSONContentHandler', + CONTENT_MODEL_JSON => 'JsonContentHandler', // dumb version, no syntax highlighting CONTENT_MODEL_CSS => 'CssContentHandler', // plain text, for use by extensions, etc. diff --git a/includes/content/CodeContentHandler.php b/includes/content/CodeContentHandler.php new file mode 100644 index 0000000000..447a2a732a --- /dev/null +++ b/includes/content/CodeContentHandler.php @@ -0,0 +1,65 @@ + - * @author Kunal Mehta - */ - -/** - * Represents the content of a JSON content. - * @since 1.24 - */ -class JSONContent extends TextContent { - - public function __construct( $text, $modelId = CONTENT_MODEL_JSON ) { - parent::__construct( $text, $modelId ); - } - - /** - * Decodes the JSON into a PHP associative array. - * @return array - */ - public function getJsonData() { - return FormatJson::decode( $this->getNativeData(), true ); - } - - /** - * @return bool Whether content is valid JSON. - */ - public function isValid() { - return $this->getJsonData() !== null; - } - - /** - * Pretty-print JSON - * - * @return bool|null|string - */ - public function beautifyJSON() { - $decoded = FormatJson::decode( $this->getNativeData(), true ); - if ( !is_array( $decoded ) ) { - return null; - } - return FormatJson::encode( $decoded, true ); - - } - - /** - * Beautifies JSON prior to save. - * @param Title $title Title - * @param User $user User - * @param ParserOptions $popts - * @return JSONContent - */ - public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) { - return new static( $this->beautifyJSON() ); - } - - /** - * Set the HTML and add the appropriate styles - * - * - * @param Title $title - * @param int $revId - * @param ParserOptions $options - * @param bool $generateHtml - * @param ParserOutput $output - */ - protected function fillParserOutput( Title $title, $revId, - ParserOptions $options, $generateHtml, ParserOutput &$output - ) { - if ( $generateHtml ) { - $output->setText( $this->objectTable( $this->getJsonData() ) ); - $output->addModuleStyles( 'mediawiki.content.json' ); - } else { - $output->setText( '' ); - } - } - /** - * Constructs an HTML representation of a JSON object. - * @param array $mapping - * @return string HTML - */ - protected function objectTable( $mapping ) { - $rows = array(); - - foreach ( $mapping as $key => $val ) { - $rows[] = $this->objectRow( $key, $val ); - } - return Xml::tags( 'table', array( 'class' => 'mw-json' ), - Xml::tags( 'tbody', array(), join( "\n", $rows ) ) - ); - } - - /** - * Constructs HTML representation of a single key-value pair. - * @param string $key - * @param mixed $val - * @return string HTML. - */ - protected function objectRow( $key, $val ) { - $th = Xml::elementClean( 'th', array(), $key ); - if ( is_array( $val ) ) { - $td = Xml::tags( 'td', array(), self::objectTable( $val ) ); - } else { - if ( is_string( $val ) ) { - $val = '"' . $val . '"'; - } else { - $val = FormatJson::encode( $val ); - } - - $td = Xml::elementClean( 'td', array( 'class' => 'value' ), $val ); - } - - return Xml::tags( 'tr', array(), $th . $td ); - } - -} diff --git a/includes/content/JSONContentHandler.php b/includes/content/JSONContentHandler.php deleted file mode 100644 index b0b7aaea48..0000000000 --- a/includes/content/JSONContentHandler.php +++ /dev/null @@ -1,54 +0,0 @@ - - * @author Kunal Mehta - */ - -/** - * @since 1.24 - */ -class JSONContentHandler extends TextContentHandler { - - public function __construct( $modelId = CONTENT_MODEL_JSON ) { - parent::__construct( $modelId, array( CONTENT_FORMAT_JSON ) ); - } - - /** - * @return string - */ - protected function getContentClass() { - return 'JSONContent'; - } - - /** - * Returns the english language, because JSON is english, and should be handled as such. - * - * @param Title $title - * @param Content|null $content - * - * @return Language Return of wfGetLangObj( 'en' ) - * - * @see ContentHandler::getPageLanguage() - */ - public function getPageLanguage( Title $title, Content $content = null ) { - return wfGetLangObj( 'en' ); - } - - /** - * Returns the english language, because JSON is english, and should be handled as such. - * - * @param Title $title - * @param Content|null $content - * - * @return Language Return of wfGetLangObj( 'en' ) - * - * @see ContentHandler::getPageLanguage() - */ - public function getPageViewLanguage( Title $title, Content $content = null ) { - return wfGetLangObj( 'en' ); - } -} diff --git a/includes/content/JavaScriptContentHandler.php b/includes/content/JavaScriptContentHandler.php index 8d62e2a3ad..457b83d78a 100644 --- a/includes/content/JavaScriptContentHandler.php +++ b/includes/content/JavaScriptContentHandler.php @@ -27,7 +27,7 @@ * @ingroup Content * @todo make ScriptContentHandler base class, do highlighting stuff there? */ -class JavaScriptContentHandler extends TextContentHandler { +class JavaScriptContentHandler extends CodeContentHandler { /** * @param string $modelId @@ -39,33 +39,4 @@ class JavaScriptContentHandler extends TextContentHandler { protected function getContentClass() { return 'JavaScriptContent'; } - - /** - * Returns the english language, because JS is english, and should be handled as such. - * - * @param Title $title - * @param Content $content - * - * @return Language Return of wfGetLangObj( 'en' ) - * - * @see ContentHandler::getPageLanguage() - */ - public function getPageLanguage( Title $title, Content $content = null ) { - return wfGetLangObj( 'en' ); - } - - /** - * Returns the english language, because JS is english, and should be handled as such. - * - * @param Title $title - * @param Content $content - * - * @return Language Return of wfGetLangObj( 'en' ) - * - * @see ContentHandler::getPageViewLanguage() - */ - public function getPageViewLanguage( Title $title, Content $content = null ) { - return wfGetLangObj( 'en' ); - } - } diff --git a/includes/content/JsonContent.php b/includes/content/JsonContent.php new file mode 100644 index 0000000000..b36827c507 --- /dev/null +++ b/includes/content/JsonContent.php @@ -0,0 +1,120 @@ + + * @author Kunal Mehta + */ + +/** + * Represents the content of a JSON content. + * @since 1.24 + */ +class JsonContent extends TextContent { + + public function __construct( $text, $modelId = CONTENT_MODEL_JSON ) { + parent::__construct( $text, $modelId ); + } + + /** + * Decodes the JSON into a PHP associative array. + * @return array + */ + public function getJsonData() { + return FormatJson::decode( $this->getNativeData(), true ); + } + + /** + * @return bool Whether content is valid JSON. + */ + public function isValid() { + return $this->getJsonData() !== null; + } + + /** + * Pretty-print JSON + * + * @return bool|null|string + */ + public function beautifyJSON() { + $decoded = FormatJson::decode( $this->getNativeData(), true ); + if ( !is_array( $decoded ) ) { + return null; + } + return FormatJson::encode( $decoded, true ); + + } + + /** + * Beautifies JSON prior to save. + * @param Title $title Title + * @param User $user User + * @param ParserOptions $popts + * @return JsonContent + */ + public function preSaveTransform( Title $title, User $user, ParserOptions $popts ) { + return new static( $this->beautifyJSON() ); + } + + /** + * Set the HTML and add the appropriate styles + * + * + * @param Title $title + * @param int $revId + * @param ParserOptions $options + * @param bool $generateHtml + * @param ParserOutput $output + */ + protected function fillParserOutput( Title $title, $revId, + ParserOptions $options, $generateHtml, ParserOutput &$output + ) { + if ( $generateHtml ) { + $output->setText( $this->objectTable( $this->getJsonData() ) ); + $output->addModuleStyles( 'mediawiki.content.json' ); + } else { + $output->setText( '' ); + } + } + /** + * Constructs an HTML representation of a JSON object. + * @param array $mapping + * @return string HTML + */ + protected function objectTable( $mapping ) { + $rows = array(); + + foreach ( $mapping as $key => $val ) { + $rows[] = $this->objectRow( $key, $val ); + } + return Xml::tags( 'table', array( 'class' => 'mw-json' ), + Xml::tags( 'tbody', array(), join( "\n", $rows ) ) + ); + } + + /** + * Constructs HTML representation of a single key-value pair. + * @param string $key + * @param mixed $val + * @return string HTML. + */ + protected function objectRow( $key, $val ) { + $th = Xml::elementClean( 'th', array(), $key ); + if ( is_array( $val ) ) { + $td = Xml::tags( 'td', array(), self::objectTable( $val ) ); + } else { + if ( is_string( $val ) ) { + $val = '"' . $val . '"'; + } else { + $val = FormatJson::encode( $val ); + } + + $td = Xml::elementClean( 'td', array( 'class' => 'value' ), $val ); + } + + return Xml::tags( 'tr', array(), $th . $td ); + } + +} diff --git a/includes/content/JsonContentHandler.php b/includes/content/JsonContentHandler.php new file mode 100644 index 0000000000..392ce37beb --- /dev/null +++ b/includes/content/JsonContentHandler.php @@ -0,0 +1,26 @@ + + * @author Kunal Mehta + */ + +/** + * @since 1.24 + */ +class JsonContentHandler extends CodeContentHandler { + + public function __construct( $modelId = CONTENT_MODEL_JSON ) { + parent::__construct( $modelId, array( CONTENT_FORMAT_JSON ) ); + } + + /** + * @return string + */ + protected function getContentClass() { + return 'JsonContent'; + } +} diff --git a/includes/content/MessageContent.php b/includes/content/MessageContent.php index 2240669dfa..5b84657f99 100644 --- a/includes/content/MessageContent.php +++ b/includes/content/MessageContent.php @@ -29,7 +29,7 @@ * Wrapper allowing us to handle a system message as a Content object. * Note that this is generally *not* used to represent content from the * MediaWiki namespace, and that there is no MessageContentHandler. - * MessageContent is just intended as glue for wrapping a message programatically. + * MessageContent is just intended as glue for wrapping a message programmatically. * * @ingroup Content */ diff --git a/tests/phpunit/includes/content/JSONContentTest.php b/tests/phpunit/includes/content/JSONContentTest.php deleted file mode 100644 index acfdc0e59f..0000000000 --- a/tests/phpunit/includes/content/JSONContentTest.php +++ /dev/null @@ -1,115 +0,0 @@ -assertEquals( $isValid, $obj->isValid() ); - $this->assertEquals( $expected, $obj->getJsonData() ); - } - - public function provideValidConstruction() { - return array( - array( 'foo', CONTENT_MODEL_JSON, false, null ), - array( FormatJson::encode( array() ), CONTENT_MODEL_JSON, true, array() ), - array( FormatJson::encode( array( 'foo' ) ), CONTENT_MODEL_JSON, true, array( 'foo' ) ), - ); - } - - /** - * @dataProvider provideDataToEncode - */ - public function testBeautifyUsesFormatJson( $data ) { - $obj = new JSONContent( FormatJson::encode( $data ) ); - $this->assertEquals( FormatJson::encode( $data, true ), $obj->beautifyJSON() ); - } - - public function provideDataToEncode() { - return array( - array( array() ), - array( array( 'foo' ) ), - array( array( 'foo', 'bar' ) ), - array( array( 'baz' => 'foo', 'bar' ) ), - array( array( 'baz' => 1000, 'bar' ) ), - ); - } - - /** - * @dataProvider provideDataToEncode - */ - public function testPreSaveTransform( $data ) { - $obj = new JSONContent( FormatJson::encode( $data ) ); - $newObj = $obj->preSaveTransform( $this->getMockTitle(), $this->getMockUser(), $this->getMockParserOptions() ); - $this->assertTrue( $newObj->equals( new JSONContent( FormatJson::encode( $data, true ) ) ) ); - } - - private function getMockTitle() { - return $this->getMockBuilder( 'Title' ) - ->disableOriginalConstructor() - ->getMock(); - } - - private function getMockUser() { - return $this->getMockBuilder( 'User' ) - ->disableOriginalConstructor() - ->getMock(); - } - private function getMockParserOptions() { - return $this->getMockBuilder( 'ParserOptions' ) - ->disableOriginalConstructor() - ->getMock(); - } - - /** - * @dataProvider provideDataAndParserText - */ - public function testFillParserOutput( $data, $expected ) { - $obj = new JSONContent( FormatJson::encode( $data ) ); - $parserOutput = $obj->getParserOutput( $this->getMockTitle(), null, null, true ); - $this->assertInstanceOf( 'ParserOutput', $parserOutput ); -// var_dump( $parserOutput->getText(), "\n" ); - $this->assertEquals( $expected, $parserOutput->getText() ); - } - - public function provideDataAndParserText() { - return array( - array( - array(), - '
' - ), - array( - array( 'foo' ), - '
0"foo"
' - ), - array( - array( 'foo', 'bar' ), - '' . - "\n" . - '
0"foo"
1"bar"
' - ), - array( - array( 'baz' => 'foo', 'bar' ), - '' . - "\n" . - '
baz"foo"
0"bar"
' - ), - array( - array( 'baz' => 1000, 'bar' ), - '' . - "\n" . - '
baz1000
0"bar"
' - ), - array( - array( ''), - '
0"<script>alert("evil!")</script>"
', - ), - ); - } -} diff --git a/tests/phpunit/includes/content/JsonContentTest.php b/tests/phpunit/includes/content/JsonContentTest.php new file mode 100644 index 0000000000..6c77d1aaf0 --- /dev/null +++ b/tests/phpunit/includes/content/JsonContentTest.php @@ -0,0 +1,115 @@ +assertEquals( $isValid, $obj->isValid() ); + $this->assertEquals( $expected, $obj->getJsonData() ); + } + + public function provideValidConstruction() { + return array( + array( 'foo', CONTENT_MODEL_JSON, false, null ), + array( FormatJson::encode( array() ), CONTENT_MODEL_JSON, true, array() ), + array( FormatJson::encode( array( 'foo' ) ), CONTENT_MODEL_JSON, true, array( 'foo' ) ), + ); + } + + /** + * @dataProvider provideDataToEncode + */ + public function testBeautifyUsesFormatJson( $data ) { + $obj = new JsonContent( FormatJson::encode( $data ) ); + $this->assertEquals( FormatJson::encode( $data, true ), $obj->beautifyJSON() ); + } + + public function provideDataToEncode() { + return array( + array( array() ), + array( array( 'foo' ) ), + array( array( 'foo', 'bar' ) ), + array( array( 'baz' => 'foo', 'bar' ) ), + array( array( 'baz' => 1000, 'bar' ) ), + ); + } + + /** + * @dataProvider provideDataToEncode + */ + public function testPreSaveTransform( $data ) { + $obj = new JsonContent( FormatJson::encode( $data ) ); + $newObj = $obj->preSaveTransform( $this->getMockTitle(), $this->getMockUser(), $this->getMockParserOptions() ); + $this->assertTrue( $newObj->equals( new JsonContent( FormatJson::encode( $data, true ) ) ) ); + } + + private function getMockTitle() { + return $this->getMockBuilder( 'Title' ) + ->disableOriginalConstructor() + ->getMock(); + } + + private function getMockUser() { + return $this->getMockBuilder( 'User' ) + ->disableOriginalConstructor() + ->getMock(); + } + private function getMockParserOptions() { + return $this->getMockBuilder( 'ParserOptions' ) + ->disableOriginalConstructor() + ->getMock(); + } + + /** + * @dataProvider provideDataAndParserText + */ + public function testFillParserOutput( $data, $expected ) { + $obj = new JsonContent( FormatJson::encode( $data ) ); + $parserOutput = $obj->getParserOutput( $this->getMockTitle(), null, null, true ); + $this->assertInstanceOf( 'ParserOutput', $parserOutput ); +// var_dump( $parserOutput->getText(), "\n" ); + $this->assertEquals( $expected, $parserOutput->getText() ); + } + + public function provideDataAndParserText() { + return array( + array( + array(), + '
' + ), + array( + array( 'foo' ), + '
0"foo"
' + ), + array( + array( 'foo', 'bar' ), + '' . + "\n" . + '
0"foo"
1"bar"
' + ), + array( + array( 'baz' => 'foo', 'bar' ), + '' . + "\n" . + '
baz"foo"
0"bar"
' + ), + array( + array( 'baz' => 1000, 'bar' ), + '' . + "\n" . + '
baz1000
0"bar"
' + ), + array( + array( ''), + '
0"<script>alert("evil!")</script>"
', + ), + ); + } +}