jenkins-bot has submitted this change and it was merged. Change subject: When a CollaborationListContent or CollaborationHubContent page does not meet the schema, instead of throwing an exception it returns the offending JSON. ......................................................................
When a CollaborationListContent or CollaborationHubContent page does not meet the schema, instead of throwing an exception it returns the offending JSON. This introduces a new top-level property in the object called "displaymode" which is currently optional but will be required soon. The options are "normal" (the default), "error" (used for outputting the error) and "members" (CollaborationListContent only; not currently used but will replace the ismemberlist bool). Bug: T148085 Change-Id: I4f76f8f43405cf81eb84108a848a76e0255a76b7 --- M extension.json M i18n/en.json M i18n/qqq.json M includes/content/CollaborationHubContent.php M includes/content/CollaborationHubContentHandler.php M includes/content/CollaborationHubContentSchema.php M includes/content/CollaborationListContent.php M includes/content/CollaborationListContentHandler.php M includes/content/CollaborationListContentSchema.php M tests/phpunit/CollaborationHubContentHandlerTest.php 10 files changed, 159 insertions(+), 116 deletions(-) Approvals: Isarra: Looks good to me, approved jenkins-bot: Verified diff --git a/extension.json b/extension.json index 1946eee..335ff7e 100644 --- a/extension.json +++ b/extension.json @@ -3,7 +3,7 @@ "version": "0.1", "author": [ "Kim Schoonover", "Brian Wolff", "James Hare" ], "url": "https://www.mediawiki.org/wiki/Extension:CollaborationKit", - "descriptionmsg": "colaborationkit-desc", + "descriptionmsg": "collaborationkit-desc", "type": "other", "license-name": "GPL-2.0+", "MessagesDirs": { diff --git a/i18n/en.json b/i18n/en.json index 22e18d9..806b387 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -2,7 +2,7 @@ "@metadata": { "authors": [ "Isarra", "Harej", "bawolff" ] }, - "colaborationkit-desc": "Next-generation WikiProjects and collaborative workspaces for wikis", + "collaborationkit-desc": "Next-generation WikiProjects and collaborative workspaces for wikis", "createcollaborationhub": "Create new Collaboration Hub project", "createhubfeature": "Create a new Collaboration Hub feature", "collaborationkit-createhub-title": "Page title", @@ -35,6 +35,7 @@ "collaborationkit-createhubfeature-freetext": "Generic wiki page", "collaborationkit-createhubfeature-articlelist": "List of pages", "collaborationkit-list-isempty": "This list has no items in it.", + "collaborationkit-list-invalid": "This content does not meet the requirements of the CollaborationListContent schema. This may happen as a result of a software update. The content is reproduced below.", "collaborationkit-list-notlist": "[[$1]] is not a CollaborationKit list page", "collaborationkit-list-taglist": "'''{{PLURAL:$2|Tagged:|Tags:}}''' $1 ", "collaborationkit-list-toomanytags": "You are not allowed to specify more than {{PLURAL:$1|one tag|$1 tags}}", @@ -77,6 +78,7 @@ "collaborationkit-hub-toc-label": "Project contents", "collaborationkit-hub-addpage": "Add feature", "collaborationkit-hub-manage": "Manage project", + "collaborationkit-hub-invalid": "This content does not meet the requirements of the CollaborationHubContent schema. This may happen as a result of a software update. The content is reproduced below.", "collaborationkit-subpage-toc-label": "Part of a project:", "collaborationkit-red1": "Dark red", "collaborationkit-red2": "Red", diff --git a/i18n/qqq.json b/i18n/qqq.json index a83df17..5fb5b83 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -2,7 +2,7 @@ "@metadata": { "authors": [ "Isarra", "bawolff" ] }, - "colaborationkit-desc": "{{desc|name=CollaborationKit|url=https://www.mediawiki.org/wiki/Extension:CollaborationKit}}", + "collaborationkit-desc": "{{desc|name=CollaborationKit|url=https://www.mediawiki.org/wiki/Extension:CollaborationKit}}", "createcollaborationhub": "Header for Special:CreateCollaborationHub", "createhubfeature": "Header for Special:CreateHubFeature", "collaborationkit-createhub-title": "Label for target page title input on Special:CreateCollaborationHub", @@ -35,6 +35,7 @@ "collaborationkit-createhubfeature-invalidcontenttype": "Error message on Special:CreateHubFeature in the unlikely event that an invalid content type is specified for a feature", "collaborationkit-createhubfeature-editsummary": "Edit summary used by Special:CreateHubFeature when creating a new feature", "collaborationkit-list-isempty": "Shown on lists that are empty immediately after the description", + "collaborationkit-list-invalid": "Message shown on lists that do not comply with the JSON Schema; shown before displaying the offending content", "collaborationkit-list-notlist": "Error when <nowiki>{{#transcludelist:page}}</nowiki> is given a page that is not a CollaborationKit list page. $1 Name of page given.", "collaborationkit-list-taglist": "Box for showing tags a specific item has on a list. $1 = comma separated list of tags. $2 = number of tags", "collaborationkit-list-toomanytags": "Error if <nowiki>{{#transcludelist:...}}</nowiki> is given more than the allowed number of tag arguments. $1 - number of tags allowed. $2 - number of tags given.", @@ -77,6 +78,7 @@ "collaborationkit-hub-toc-label": "Label for the toc on a Collaboration Hub mainpage", "collaborationkit-hub-addpage": "Label for button/link to add a new subpage/feature to a Collaboration Hub", "collaborationkit-hub-manage": "Label for extra button/link to edit Collaboration Hub", + "collaborationkit-hub-invalid": "Message shown on hubs that do not comply with the JSON Schema; shown before displaying the offending content", "collaborationkit-subpage-toc-label": "Label for the toc on a Collaboration Hub subpage", "collaborationkit-red1": "Color label", "collaborationkit-red2": "Color label", diff --git a/includes/content/CollaborationHubContent.php b/includes/content/CollaborationHubContent.php index 91fa4a5..5197af0 100644 --- a/includes/content/CollaborationHubContent.php +++ b/includes/content/CollaborationHubContent.php @@ -42,6 +42,9 @@ /** @var string */ protected $themeColour; + /** @var $displaymode String How to display contents */ + protected $displaymode; + /** @var bool Whether contents have been populated */ protected $decoded = false; @@ -87,18 +90,19 @@ */ public function isValid() { $hubSchema = include __DIR__ . '/CollaborationHubContentSchema.php'; - $this->decode(); - if ( $this->decoded ) { - $jsonParse = $this->getData(); - if ( $jsonParse->isGood() ) { - // Forcing the object to become an array - $jsonAsArray = json_decode( json_encode( $jsonParse->getValue() ), true ); - try { - EventLogging::schemaValidate( $jsonAsArray, $hubSchema ); - return true; - } catch ( JsonSchemaException $e ) { - return false; - } + $jsonParse = $this->getData(); + if ( $jsonParse->isGood() ) { + // TODO: The schema should be checking for required fields but for some reason that doesn't work + if ( !isset( $jsonParse->value->content ) ) { + return false; + } + // Forcing the object to become an array + $jsonAsArray = json_decode( json_encode( $jsonParse->getValue() ), true ); + try { + EventLogging::schemaValidate( $jsonAsArray, $hubSchema ); + return true; + } catch ( JsonSchemaException $e ) { + return false; } return false; } @@ -115,31 +119,36 @@ $jsonParse = $this->getData(); $data = $jsonParse->isGood() ? $jsonParse->getValue() : null; if ( $data ) { - $this->displayName = isset( $data->display_name ) ? $data->display_name : ''; - $this->introduction = isset( $data->introduction ) ? $data->introduction : ''; - $this->footer = isset( $data->footer ) ? $data->footer : ''; - $this->image = isset( $data->image ) ? $data->image : 'none'; - - // Set colour to default if empty or missing - if ( !isset( $data->colour ) || $data->colour == '' ) { - $this->themeColour = 'blue5'; + if ( !$this->isValid() ) { + $this->displaymode = 'error'; + $this->errortext = FormatJson::encode( $data, true, FormatJson::ALL_OK ); } else { - $this->themeColour = $data->colour; - } + $this->displayName = isset( $data->display_name ) ? $data->display_name : ''; + $this->introduction = isset( $data->introduction ) ? $data->introduction : ''; + $this->footer = isset( $data->footer ) ? $data->footer : ''; + $this->image = isset( $data->image ) ? $data->image : 'none'; - if ( isset( $data->content ) && is_array( $data->content ) ) { - $this->content = []; - foreach ( $data->content as $itemObject ) { - if ( !is_object( $itemObject ) ) { // Malformed item - $this->content = null; - break; + // Set colour to default if empty or missing + if ( !isset( $data->colour ) || $data->colour == '' ) { + $this->themeColour = 'blue5'; + } else { + $this->themeColour = $data->colour; + } + + if ( isset( $data->content ) && is_array( $data->content ) ) { + $this->content = []; + foreach ( $data->content as $itemObject ) { + if ( !is_object( $itemObject ) ) { // Malformed item + $this->content = null; + break; + } + $item = []; + $item['title'] = isset( $itemObject->title ) ? $itemObject->title : null; + $item['image'] = isset( $itemObject->image ) ? $itemObject->image : null; + $item['displayTitle'] = isset( $itemObject->display_title ) ? $itemObject->display_title : null; + + $this->content[] = $item; } - $item = []; - $item['title'] = isset( $itemObject->title ) ? $itemObject->title : null; - $item['image'] = isset( $itemObject->image ) ? $itemObject->image : null; - $item['displayTitle'] = isset( $itemObject->display_title ) ? $itemObject->display_title : null; - - $this->content[] = $item; } } } @@ -211,68 +220,76 @@ // Dummy parse intro and footer to get categories and whatnot $output = $wgParser->parse( $this->getIntroduction() . $this->getFooter(), $title, $options, true, true, $revId ); $html = ''; - // set up hub with theme stuff - $html .= Html::openElement( - 'div', - [ 'class' => $this->getHubClasses() ] - ); - // get page image - $html .= Html::rawElement( - 'div', - [ 'class' => 'wp-header-image' ], - $this->getParsedImage( $this->getImage(), 200 ) - ); - // get members list - $html .= Html::rawElement( - 'div', - [ 'class' => 'wp-members' ], - $this->getMembersBlock( $title, $options ) - ); - // get parsed intro - $html .= Html::rawElement( - 'div', - [ 'class' => 'wp-intro' ], - $this->getParsedIntroduction( $title, $options ) - ); - // get announcements - $html .= Html::rawElement( - 'div', - [ 'class' => 'wp-announcements' ], - $this->getParsedAnnouncements( $title, $options ) - ); - // get table of contents - $html .= Html::rawElement( - 'div', - [ 'class' => 'wp-toc' ], - $this->getTableOfContents( $title, $options ) - ); - // get transcluded content - $html .= Html::rawElement( - 'div', - [ 'class' => 'wp-content' ], - $this->getParsedContent( $title, $options, $output ) - ); - // get footer - $html .= Html::rawElement( - 'div', - [ 'class' => 'wp-footer' ], - $this->getParsedFooter( $title, $options ) - ); - $html .= Html::rawElement( - 'div', - [ 'class' => 'wp-footeractions' ], - $this->getSecondFooter( $title ) - ); - $html .= Html::closeElement( 'div' ); - $output->setText( $html ); + // If error, then bypass all this and just show the offending JSON - // Add some style stuff - $output->addModuleStyles( 'ext.CollaborationKit.hub.styles' ); - $output->addModules( 'ext.CollaborationKit.icons' ); - $output->addModules( 'ext.CollaborationKit.blots' ); - $output->addModules( 'ext.CollaborationKit.list.styles' ); - $output->setEnableOOUI( true ); + if ( $this->displaymode == 'error' ) { + $html = '<div class=errorbox>' . wfMessage( 'collaborationkit-hub-invalid' ) . '</div>\n<pre>' . $this->errortext . '</pre>'; + $output->setText( $html ); + } else { + // set up hub with theme stuff + $html .= Html::openElement( + 'div', + [ 'class' => $this->getHubClasses() ] + ); + // get page image + $html .= Html::rawElement( + 'div', + [ 'class' => 'wp-header-image' ], + $this->getParsedImage( $this->getImage(), 200 ) + ); + // get members list + $html .= Html::rawElement( + 'div', + [ 'class' => 'wp-members' ], + $this->getMembersBlock( $title, $options ) + ); + // get parsed intro + $html .= Html::rawElement( + 'div', + [ 'class' => 'wp-intro' ], + $this->getParsedIntroduction( $title, $options ) + ); + // get announcements + $html .= Html::rawElement( + 'div', + [ 'class' => 'wp-announcements' ], + $this->getParsedAnnouncements( $title, $options ) + ); + // get table of contents + $html .= Html::rawElement( + 'div', + [ 'class' => 'wp-toc' ], + $this->getTableOfContents( $title, $options ) + ); + // get transcluded content + $html .= Html::rawElement( + 'div', + [ 'class' => 'wp-content' ], + $this->getParsedContent( $title, $options, $output ) + ); + // get footer + $html .= Html::rawElement( + 'div', + [ 'class' => 'wp-footer' ], + $this->getParsedFooter( $title, $options ) + ); + $html .= Html::rawElement( + 'div', + [ 'class' => 'wp-footeractions' ], + $this->getSecondFooter( $title ) + ); + $html .= Html::closeElement( 'div' ); + + $output->setText( $html ); + + // Add some style stuff + $output->addModuleStyles( 'ext.CollaborationKit.hub.styles' ); + $output->addModules( 'ext.CollaborationKit.icons' ); + $output->addModules( 'ext.CollaborationKit.blots' ); + $output->addModules( 'ext.CollaborationKit.list.styles' ); + $output->setEnableOOUI( true ); + } } /** diff --git a/includes/content/CollaborationHubContentHandler.php b/includes/content/CollaborationHubContentHandler.php index cdf2b14..118c380 100644 --- a/includes/content/CollaborationHubContentHandler.php +++ b/includes/content/CollaborationHubContentHandler.php @@ -31,9 +31,7 @@ public function unserializeContent( $text, $format = null ) { $this->checkFormat( $format ); $content = new CollaborationHubContent( $text ); - if ( !$content->isValid() ) { - throw new MWContentSerializationException( 'The collaborationhub content is invalid.' ); - } + // Deliberately not validating at this step; validation is done later. return $content; } diff --git a/includes/content/CollaborationHubContentSchema.php b/includes/content/CollaborationHubContentSchema.php index ee959c4..8f8d1de 100644 --- a/includes/content/CollaborationHubContentSchema.php +++ b/includes/content/CollaborationHubContentSchema.php @@ -2,6 +2,12 @@ return [ 'type' => 'object', 'properties' => [ + 'displaymode' => [ + 'enum' => [ + 0 => 'normal', + 1 => 'error' + ] + ], 'introduction' => [ 'type' => 'string', ], diff --git a/includes/content/CollaborationListContent.php b/includes/content/CollaborationListContent.php index b45422b..438db6c 100644 --- a/includes/content/CollaborationListContent.php +++ b/includes/content/CollaborationListContent.php @@ -22,6 +22,8 @@ protected $options; /** @var $items Array List of items */ protected $items; + /** @var $displaymode String The variety of list */ + protected $displaymode; function __construct( $text, $type = 'CollaborationListContent' ) { parent::__construct( $text, $type ); @@ -41,6 +43,10 @@ return false; } $data = $status->value; + // TODO: The schema should be checking for required fields but for some reason that doesn't work + if ( !isset( $data->description ) || !isset( $data->items ) || !isset( $data->options ) ) { + return false; + } $jsonAsArray = json_decode( json_encode( $data ), true ); try { EventLogging::schemaValidate( $jsonAsArray, $listSchema ); @@ -109,13 +115,16 @@ if ( $this->decoded ) { return; } - if ( !$this->isValid() ) { - throw new Exception( "Can't decode invalid content" ); - } $data = $this->getData()->value; - $this->description = $data->description; - $this->options = $data->options; - $this->items = $data->items; + if ( !$this->isValid() ) { + $this->displaymode = 'error'; + $this->errortext = FormatJson::encode( $data, true, FormatJson::ALL_OK ); + } else { + $this->displaymode = 'normal'; // For now, while this field is still optional + $this->description = $data->description; + $this->options = $data->options; + $this->items = $data->items; + } $this->decoded = true; } @@ -182,6 +191,14 @@ $maxItems = $options['maxItems']; $includeDesc = $options['includeDesc']; + // If this is an error-type list (i.e. a schema-violating blob), + // just return the plain JSON. + + if ( $this->displaymode == 'error' ) { + $errorWikitext = '<div class=errorbox>' . wfMessage( 'collaborationkit-list-invalid' ) . '</div>\n<pre>' . $this->errortext . '</pre>'; + return $errorWikitext; + } + // Hack to force style loading even when we don't have a Parser reference. $text = "<collaborationkitloadliststyles/>\n"; diff --git a/includes/content/CollaborationListContentHandler.php b/includes/content/CollaborationListContentHandler.php index b0a98b0..674638e 100644 --- a/includes/content/CollaborationListContentHandler.php +++ b/includes/content/CollaborationListContentHandler.php @@ -45,9 +45,7 @@ $text = FormatJson::encode( $data ); } $content = new CollaborationListContent( $text ); - if ( !$content->isValid() ) { - throw new MWContentSerializationException( 'The collaborationlist content is invalid.' ); - } + // Deliberately not validating at this step; validation is done later. return $content; } diff --git a/includes/content/CollaborationListContentSchema.php b/includes/content/CollaborationListContentSchema.php index 0766013..6dfa7ce 100644 --- a/includes/content/CollaborationListContentSchema.php +++ b/includes/content/CollaborationListContentSchema.php @@ -3,6 +3,16 @@ 'type' => 'object', 'required' => [ 'description', 'items', 'options' ], 'properties' => [ + 'displaymode' => [ + 'enum' => [ + 0 => 'normal', + 1 => 'members', + 2 => 'error' + ] + ], + 'errortext' => [ + 'type' => 'string' + ], 'description' => [ 'type' => 'string' ], diff --git a/tests/phpunit/CollaborationHubContentHandlerTest.php b/tests/phpunit/CollaborationHubContentHandlerTest.php index 7be872c..cd59255 100644 --- a/tests/phpunit/CollaborationHubContentHandlerTest.php +++ b/tests/phpunit/CollaborationHubContentHandlerTest.php @@ -11,13 +11,6 @@ $this->handler = TestingAccessWrapper::newFromObject( $handler ); } - /** - * @expectedException MWContentSerializationException - */ - public function testUnserializeContent() { - $this->handler->unserializeContent( 'There once was a horse named bob.' ); - } - public function testMakeEmptyContent() { $empty = $this->handler->makeEmptyContent(); $this->assertTrue( $empty->isValid() ); -- To view, visit https://gerrit.wikimedia.org/r/316196 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: I4f76f8f43405cf81eb84108a848a76e0255a76b7 Gerrit-PatchSet: 8 Gerrit-Project: mediawiki/extensions/CollaborationKit Gerrit-Branch: master Gerrit-Owner: Harej <jamesmh...@gmail.com> Gerrit-Reviewer: Brian Wolff <bawolff...@gmail.com> Gerrit-Reviewer: Harej <jamesmh...@gmail.com> Gerrit-Reviewer: Isarra <zhoris...@gmail.com> Gerrit-Reviewer: Siebrand <siebr...@kitano.nl> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits