From 12b90adf60f9eb285b1a25fedcece33cb66c28d1 Mon Sep 17 00:00:00 2001 From: Corey McKrill <916023+coreymckrill@users.noreply.github.com> Date: Thu, 11 Jun 2020 16:02:16 -0700 Subject: [PATCH 1/8] Add block-json directory and files --- block-json/block.json | 34 +++ block-json/class-parser.php | 171 ++++++++++++ block-json/class-validator.php | 497 +++++++++++++++++++++++++++++++++ 3 files changed, 702 insertions(+) create mode 100644 block-json/block.json create mode 100644 block-json/class-parser.php create mode 100644 block-json/class-validator.php diff --git a/block-json/block.json b/block-json/block.json new file mode 100644 index 0000000..3f3444e --- /dev/null +++ b/block-json/block.json @@ -0,0 +1,34 @@ +{ + "name": "my-plugin/notice", + "title": "Notice", + "category": "text", + "parent": [ "core/group" ], + "icon": "star", + "description": "Shows warning, error or success notices ...", + "keywords": [ "alert", "message" ], + "textdomain": "my-plugin", + "attributes": { + "message": { + "type": "string", + "source": "html", + "selector": ".message" + } + }, + "supports": { + "align": true, + "lightBlockWrapper": true + }, + "styles": [ + { "name": "default", "label": "Default", "isDefault": true }, + { "name": "other", "label": "Other" } + ], + "example": { + "attributes": { + "message": "This is a notice!" + } + }, + "editorScript": "build/editor.js", + "script": "build/main.js", + "editorStyle": "build/editor.css", + "style": "build/style.css" +} diff --git a/block-json/class-parser.php b/block-json/class-parser.php new file mode 100644 index 0000000..4dd778e --- /dev/null +++ b/block-json/class-parser.php @@ -0,0 +1,171 @@ +' . self::REQUIRED_FILENAME . '' + ) + ); + } + + $response = wp_safe_remote_get( $url ); + $response_code = wp_remote_retrieve_response_code( $response ); + + if ( is_wp_error( $response ) ) { + return $response; + } elseif ( 200 !== $response_code ) { + return new WP_Error( + 'resource_url_unexpected_response', + __( 'URL returned an unexpected status code.', 'wporg-plugins' ), + array( + 'status' => $response_code, + ) + ); + } + + return wp_remote_retrieve_body( $response ); + } + + /** + * Get the contents of a block.json file via a path in the filesystem. + * + * @param string $file + * + * @return string|WP_Error + */ + protected static function extract_content_from_file( $file ) { + $filename_length = strlen( self::REQUIRED_FILENAME ); + if ( strtolower( substr( $file, - $filename_length ) ) !== self::REQUIRED_FILENAME ) { + return new WP_Error( + 'resource_file_invalid', + sprintf( + /* translators: %s: file name */ + __( 'File must be named %s!', 'wporg-plugins' ), + '' . self::REQUIRED_FILENAME . '' + ) + ); + } + + if ( ! is_readable( $file ) ) { + return new WP_Error( + 'resource_file_unreadable', + __( 'The file could not be read.', 'wporg-plugins' ), + array( + 'file' => $file, + ) + ); + } + + $content = file_get_contents( $file ); + + if ( false === $content ) { + return new WP_Error( + 'resource_file_failed_retrieval', + __( 'Could not get the contents of the file.', 'wporg-plugins' ), + array( + 'file' => $file, + ) + ); + } + + return $content; + } + + /** + * Parse a JSON string into an object, and handle parsing errors. + * + * @param string $content + * + * @return object|WP_Error + */ + protected static function parse_content( $content ) { + $parsed = json_decode( $content ); + $error = json_last_error_msg(); + + // TODO Once we are on PHP 7.3 we can use the JSON_THROW_ON_ERROR option and catch an exception here. + if ( 'No error' !== $error ) { + return new WP_Error( + 'json_parse_error', + esc_html( $error ), + array( + 'error_code' => json_last_error(), + ) + ); + } + + return $parsed; + } +} diff --git a/block-json/class-validator.php b/block-json/class-validator.php new file mode 100644 index 0000000..f60f5a4 --- /dev/null +++ b/block-json/class-validator.php @@ -0,0 +1,497 @@ +messages = new WP_Error(); + } + + /** + * The schema for the block.json file. + * + * This attempts to follow the schema for the schema. + * See https://json-schema.org/understanding-json-schema/reference/index.html + * + * @return array + */ + public static function schema() { + return array( + 'type' => 'object', + 'properties' => array( + 'attributes' => array( + 'type' => 'object', + 'additionalProperties' => array( + 'type' => 'object', + 'properties' => array( + 'attribute' => array( + 'type' => 'string', + ), + 'meta' => array( + 'type' => 'string', + ), + 'multiline' => array( + 'type' => 'string', + ), + 'query' => array( + 'type' => 'object', + ), + 'selector' => array( + 'type' => 'string', + ), + 'source' => array( + 'type' => 'string', + 'enum' => array( 'attribute', 'text', 'html', 'query' ), + ), + 'type' => array( + 'type' => 'string', + 'enum' => array( 'null', 'boolean', 'object', 'array', 'number', 'string', 'integer' ), + ), + ), + 'required' => array( 'type' ), + ), + ), + 'category' => array( + 'type' => 'string', + ), + 'description' => array( + 'type' => 'string', + ), + 'editorScript' => array( + 'type' => 'string', + ), + 'editorStyle' => array( + 'type' => 'string', + ), + 'example' => array( + 'type' => 'object', + 'additionalProperties' => array( + 'type' => 'object', + ), + ), + 'icon' => array( + 'type' => 'string', + ), + 'keywords' => array( + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + 'name' => array( + 'type' => 'string', + ), + 'parent' => array( + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), + ), + 'script' => array( + 'type' => 'string', + ), + 'style' => array( + 'type' => 'string', + ), + 'styles' => array( + 'type' => 'array', + 'items' => array( + 'type' => 'object', + 'properties' => array( + 'isDefault' => array( + 'type' => 'boolean', + ), + 'label' => array( + 'type' => 'string', + ), + 'name' => array( + 'type' => 'string', + ), + ), + ), + ), + 'supports' => array( + 'type' => 'object', + 'properties' => array( + 'align' => array( + 'type' => array( 'boolean', 'array' ), + 'items' => array( + 'type' => 'string', + 'enum' => array( 'left', 'center', 'right', 'wide', 'full' ), + ), + ), + 'alignWide' => array( + 'type' => 'boolean', + ), + 'anchor' => array( + 'type' => 'boolean', + ), + 'className' => array( + 'type' => 'boolean', + ), + 'customClassName' => array( + 'type' => 'boolean', + ), + 'html' => array( + 'type' => 'boolean', + ), + 'inserter' => array( + 'type' => 'boolean', + ), + 'multiple' => array( + 'type' => 'boolean', + ), + 'reusable' => array( + 'type' => 'boolean', + ), + ), + ), + 'textdomain' => array( + 'type' => 'string', + ), + 'title' => array( + 'type' => 'string', + ), + ), + 'required' => array( 'name', 'title', 'category' ), + 'additionalProperties' => false, + ); + } + + /** + * Validate a PHP object representation of a block.json file. + * + * @param object $block_json + * + * @return bool|WP_Error + */ + public function validate( $block_json ) { + $schema = self::schema(); + + $this->validate_object( $block_json, 'block.json', $schema ); + $this->check_conditional_properties( $block_json ); + + if ( $this->messages->has_errors() ) { + return $this->messages; + } + + return true; + } + + /** + * Check for properties that are conditionally required. + * + * @param object $block_json + * + * @return void + */ + protected function check_conditional_properties( $block_json ) { + if ( ! is_object( $block_json ) ) { + return; + } + + if ( ! isset( $block_json->script ) && ! isset( $block_json->editorScript ) ) { + $this->messages->add( + 'error', + sprintf( + __( 'At least one of the following properties must be present: %s', 'wporg-plugins' ), + // translators: used between list items, there is a space after the comma. + 'script' . __( ', ', 'wporg-plugins' ) . 'editorScript' + ) + ); + $this->append_error_data( 'block.json:script', 'error' ); + $this->append_error_data( 'block.json:editorScript', 'error' ); + } + + if ( ! isset( $block_json->style ) && ! isset( $block_json->editorStyle ) ) { + $this->messages->add( + 'error', + sprintf( + __( 'At least one of the following properties must be present: %s', 'wporg-plugins' ), + // translators: used between list items, there is a space after the comma. + 'style' . __( ', ', 'wporg-plugins' ) . 'editorStyle' + ) + ); + $this->append_error_data( 'block.json:style', 'error' ); + $this->append_error_data( 'block.json:editorStyle', 'error' ); + } + } + + /** + * Validate an object and its properties. + * + * @param object $object The value to validate as an object. + * @param string $prop The name of the property, used in error reporting. + * @param array $schema The schema for the property, used for validation. + * + * @return bool + */ + protected function validate_object( $object, $prop, $schema ) { + if ( ! is_object( $object ) ) { + $this->messages->add( + 'error', + sprintf( + __( 'The %s property must contain an object value.', 'wporg-plugins' ), + '' . $prop . '' + ) + ); + $this->append_error_data( $prop, 'error' ); + + return false; + } + + $results = array(); + + if ( isset( $schema['required'] ) ) { + foreach ( $schema['required'] as $required_prop ) { + if ( ! property_exists( $object, $required_prop ) ) { + $this->messages->add( + 'error', + sprintf( + __( 'The %1$s property is required in the %2$s object.', 'wporg-plugins' ), + '' . $required_prop . '', + '' . $prop . '' + ) + ); + $this->append_error_data( "$prop:$required_prop", 'error' ); + $results[] = false; + } + } + } + + if ( isset( $schema['properties'] ) ) { + foreach ( $schema['properties'] as $subprop => $subschema ) { + if ( ! isset( $object->$subprop ) ) { + continue; + } + + if ( isset( $subschema['type'] ) ) { + $results[] = $this->route_validation_for_type( + $subschema['type'], + $object->$subprop, + "$prop:$subprop", + $subschema + ); + } + } + } + + if ( isset( $schema['additionalProperties'] ) ) { + if ( false === $schema['additionalProperties'] ) { + foreach ( get_object_vars( $object ) as $var ) { + if ( ! isset( $schema[ $var ] ) ) { + $this->messages->add( + 'warning', + sprintf( + __( '%1$s is not a valid property in the %2$s object.', 'wporg-plugins' ), + '' . $var . '', + '' . $prop . '' + ) + ); + $this->append_error_data( "$prop:$var", 'warning' ); + continue; + } + } + } elseif ( isset( $schema['additionalProperties']['type'] ) ) { + foreach ( $object as $subprop => $subvalue ) { + $results[] = $this->route_validation_for_type( + $schema['additionalProperties']['type'], + $subvalue, + "$prop:$subprop", + $schema['additionalProperties'] + ); + } + } + } + + return ! in_array( false, $results, true ); + } + + /** + * Validate an array and its items. + * + * @param array $array The value to validate as an array. + * @param string $prop The name of the property, used in error reporting. + * @param array $schema The schema for the property, used for validation. + * + * @return bool + */ + protected function validate_array( $array, $prop, $schema ) { + if ( ! is_array( $array ) ) { + $this->messages->add( + 'error', + sprintf( + __( 'The %s property must contain an array value.', 'wporg-plugins' ), + '' . $prop . '' + ) + ); + $this->append_error_data( $prop, 'error' ); + + return false; + } + + if ( isset( $schema['items']['type'] ) ) { + $results = array(); + + foreach ( $array as $item ) { + $results[] = $this->route_validation_for_type( + $schema['items']['type'], + $item, + $prop . '[]', + $schema['items'] + ); + } + + return ! in_array( false, $results, true ); + } + + return true; + } + + /** + * Validate a string. + * + * @param string $string The value to validate as a string. + * @param string $prop The name of the property, used in error reporting. + * @param array $schema The schema for the property, used for validation. + * + * @return bool + */ + protected function validate_string( $string, $prop, $schema ) { + if ( ! is_string( $string ) ) { + $this->messages->add( + 'error', + sprintf( + __( 'The %s property must contain a string value.', 'wporg-plugins' ), + '' . $prop . '' + ) + ); + $this->append_error_data( $prop, 'error' ); + + return false; + } + + if ( isset( $schema['enum'] ) ) { + if ( ! in_array( $string, $schema['enum'], true ) ) { + $this->messages->add( + 'warning', + sprintf( + __( '"%1$s" is not a valid value for the %2$s property.', 'wporg-plugins' ), + esc_html( $string ), + '' . $prop . '' + ) + ); + $this->append_error_data( $prop, 'warning' ); + } + } + + return true; + } + + /** + * Validate a boolean. + * + * @param bool $boolean The value to validate as a boolean. + * @param string $prop The name of the property, used in error reporting. + * + * @return bool + */ + protected function validate_boolean( $boolean, $prop ) { + if ( ! is_bool( $boolean ) ) { + $this->messages->add( + 'error', + sprintf( + __( 'The %s property must contain a boolean value.', 'wporg-plugins' ), + '' . $prop . '' + ) + ); + $this->append_error_data( $prop, 'error' ); + + return false; + } + + return true; + } + + /** + * Send a property value to the correct validator depending on which type(s) it can be. + * + * @param string|array $valid_types + * @param mixed $value + * @param string $prop + * @param array $schema + * + * @return bool + */ + protected function route_validation_for_type( $valid_types, $value, $prop, $schema ) { + // There is a single valid type. + if ( is_string( $valid_types ) ) { + $method = "validate_$valid_types"; + return $method( $value, $prop, $schema ); + } + + // There are multiple valid types in an array. + foreach ( $valid_types as $type ) { + switch ( $type ) { + case 'boolean': + $check = 'is_bool'; + break; + default: + $check = "is_$type"; + break; + } + + if ( $check( $value ) ) { + $method = "validate_$type"; + return $method( $value, $prop, $schema ); + } + } + + // Made it this far, it's none of the valid types. + $this->messages->add( + 'error', + sprintf( + __( 'The %1$s property must contain a value that is one of these types: %2$s', 'wporg-plugins' ), + '' . $prop . '', + // translators: used between list items, there is a space after the comma. + '' . implode( '' . __( ', ', 'wporg-plugins' ) . '', $valid_types ) . '' + ) + ); + $this->append_error_data( $prop, 'error' ); + + return false; + } + + /** + * Add more data to an error code. + * + * The `add_data` method in WP_Error replaces data with each subsequent call with the same error code. + * + * @param mixed $new_data The data to append. + * @param string $error_code The error code to assign the data to. + * + * @return void + */ + protected function append_error_data( $new_data, $error_code ) { + $data = $this->messages->get_error_data( $error_code ) ?: array(); + $data[] = $new_data; + $this->messages->add_data( $data, $error_code ); + } +} From ed5c3f2d59c463daf4f0a1801aaa4f5858bdda58 Mon Sep 17 00:00:00 2001 From: Corey McKrill <916023+coreymckrill@users.noreply.github.com> Date: Thu, 11 Jun 2020 17:26:06 -0700 Subject: [PATCH 2/8] Add context to parser error messages --- block-json/class-parser.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/block-json/class-parser.php b/block-json/class-parser.php index 4dd778e..9a5f45e 100644 --- a/block-json/class-parser.php +++ b/block-json/class-parser.php @@ -159,7 +159,10 @@ protected static function parse_content( $content ) { if ( 'No error' !== $error ) { return new WP_Error( 'json_parse_error', - esc_html( $error ), + sprintf( + __( 'JSON Parser: %s', 'wporg-plugins' ), + esc_html( $error ) + ), array( 'error_code' => json_last_error(), ) From dfad00a627ca53dcd13bf758402a8494c586e793 Mon Sep 17 00:00:00 2001 From: Corey McKrill <916023+coreymckrill@users.noreply.github.com> Date: Thu, 11 Jun 2020 17:26:50 -0700 Subject: [PATCH 3/8] Tweaks to validator --- block-json/class-validator.php | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/block-json/class-validator.php b/block-json/class-validator.php index f60f5a4..9b5ba63 100644 --- a/block-json/class-validator.php +++ b/block-json/class-validator.php @@ -71,6 +71,9 @@ public static function schema() { 'category' => array( 'type' => 'string', ), + 'comment' => array( + 'type' => 'string', + ), 'description' => array( 'type' => 'string', ), @@ -178,11 +181,16 @@ public static function schema() { /** * Validate a PHP object representation of a block.json file. * - * @param object $block_json + * @param object|WP_Error $block_json * * @return bool|WP_Error */ public function validate( $block_json ) { + // A WP_Error instance is technically an object, but shouldn't be validated. + if ( is_wp_error( $block_json ) ) { + return $block_json; + } + $schema = self::schema(); $this->validate_object( $block_json, 'block.json', $schema ); @@ -295,17 +303,17 @@ protected function validate_object( $object, $prop, $schema ) { if ( isset( $schema['additionalProperties'] ) ) { if ( false === $schema['additionalProperties'] ) { - foreach ( get_object_vars( $object ) as $var ) { - if ( ! isset( $schema[ $var ] ) ) { + foreach ( array_keys( get_object_vars( $object ) ) as $key ) { + if ( ! isset( $schema['properties'][ $key ] ) ) { $this->messages->add( 'warning', sprintf( __( '%1$s is not a valid property in the %2$s object.', 'wporg-plugins' ), - '' . $var . '', + '' . $key . '', '' . $prop . '' ) ); - $this->append_error_data( "$prop:$var", 'warning' ); + $this->append_error_data( "$prop:$key", 'warning' ); continue; } } @@ -444,7 +452,7 @@ protected function route_validation_for_type( $valid_types, $value, $prop, $sche // There is a single valid type. if ( is_string( $valid_types ) ) { $method = "validate_$valid_types"; - return $method( $value, $prop, $schema ); + return $this->$method( $value, $prop, $schema ); } // There are multiple valid types in an array. @@ -460,7 +468,7 @@ protected function route_validation_for_type( $valid_types, $value, $prop, $sche if ( $check( $value ) ) { $method = "validate_$type"; - return $method( $value, $prop, $schema ); + return $this->$method( $value, $prop, $schema ); } } From f89618b7f7002b00a6bd1b1d543aee0deeb5ff9b Mon Sep 17 00:00:00 2001 From: Corey McKrill <916023+coreymckrill@users.noreply.github.com> Date: Thu, 11 Jun 2020 17:43:35 -0700 Subject: [PATCH 4/8] Add missing result --- block-json/class-validator.php | 1 + 1 file changed, 1 insertion(+) diff --git a/block-json/class-validator.php b/block-json/class-validator.php index 9b5ba63..4b7cd2d 100644 --- a/block-json/class-validator.php +++ b/block-json/class-validator.php @@ -314,6 +314,7 @@ protected function validate_object( $object, $prop, $schema ) { ) ); $this->append_error_data( "$prop:$key", 'warning' ); + $results[] = false; continue; } } From 61a0b273b30fb4ad384b82fe44d2a18b043a41d8 Mon Sep 17 00:00:00 2001 From: Corey McKrill <916023+coreymckrill@users.noreply.github.com> Date: Fri, 12 Jun 2020 12:29:34 -0700 Subject: [PATCH 5/8] Add pattern matching to string validator --- block-json/class-validator.php | 54 +++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/block-json/class-validator.php b/block-json/class-validator.php index 4b7cd2d..e4f12ae 100644 --- a/block-json/class-validator.php +++ b/block-json/class-validator.php @@ -78,10 +78,12 @@ public static function schema() { 'type' => 'string', ), 'editorScript' => array( - 'type' => 'string', + 'type' => 'string', + 'pattern' => '\.js$', ), 'editorStyle' => array( - 'type' => 'string', + 'type' => 'string', + 'pattern' => '\.css$', ), 'example' => array( 'type' => 'object', @@ -108,10 +110,12 @@ public static function schema() { ), ), 'script' => array( - 'type' => 'string', + 'type' => 'string', + 'pattern' => '\.js$', ), 'style' => array( - 'type' => 'string', + 'type' => 'string', + 'pattern' => '\.css$', ), 'styles' => array( 'type' => 'array', @@ -411,6 +415,26 @@ protected function validate_string( $string, $prop, $schema ) { } } + if ( isset( $schema['pattern'] ) ) { + if ( ! preg_match( '#' . $schema['pattern'] . '#', $string ) ) { + $pattern_description = $this->get_human_readable_pattern_description( $schema['pattern'] ); + if ( $pattern_description ) { + $message = sprintf( + $pattern_description, + '' . $prop . '' + ); + } else { + $message = sprintf( + __( 'The value of %s does not match the required pattern.', 'wporg-plugins' ), + '' . $prop . '' + ); + } + + $this->messages->add( 'warning', $message ); + $this->append_error_data( $prop, 'warning' ); + } + } + return true; } @@ -503,4 +527,26 @@ protected function append_error_data( $new_data, $error_code ) { $data[] = $new_data; $this->messages->add_data( $data, $error_code ); } + + /** + * Get a description of a regex pattern that can be understood by humans. + * + * @param string $pattern A regex pattern. + * + * @return string + */ + protected function get_human_readable_pattern_description( $pattern ) { + $description = ''; + + switch ( $pattern ) { + case '\.css$': + $description = __( 'The value of %s must end in ".css".', 'wporg-plugins' ); + break; + case '\.js$': + $description = __( 'The value of %s must end in ".js".', 'wporg-plugins' ); + break; + } + + return $description; + } } From 9d7074cd93cf412e6ded4f06777789a58718835b Mon Sep 17 00:00:00 2001 From: Corey McKrill <916023+coreymckrill@users.noreply.github.com> Date: Fri, 12 Jun 2020 14:35:58 -0700 Subject: [PATCH 6/8] Include index when reporting array item errors --- block-json/class-validator.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/block-json/class-validator.php b/block-json/class-validator.php index e4f12ae..7f86f93 100644 --- a/block-json/class-validator.php +++ b/block-json/class-validator.php @@ -362,14 +362,16 @@ protected function validate_array( $array, $prop, $schema ) { if ( isset( $schema['items']['type'] ) ) { $results = array(); + $index = 0; foreach ( $array as $item ) { $results[] = $this->route_validation_for_type( $schema['items']['type'], $item, - $prop . '[]', + $prop . "[$index]", $schema['items'] ); + $index ++; } return ! in_array( false, $results, true ); From ebaa1b8df730415d18136eb1a47a9541b7f601d1 Mon Sep 17 00:00:00 2001 From: Corey McKrill <916023+coreymckrill@users.noreply.github.com> Date: Fri, 12 Jun 2020 15:45:35 -0700 Subject: [PATCH 7/8] Use block parser/validator in the plugin importer --- cli/class-import.php | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/cli/class-import.php b/cli/class-import.php index 810eae9..b7b34fa 100644 --- a/cli/class-import.php +++ b/cli/class-import.php @@ -4,6 +4,7 @@ use Exception; use WordPressdotorg\Plugin_Directory\Jobs\API_Update_Updater; use WordPressdotorg\Plugin_Directory\Jobs\Tide_Sync; +use WordPressdotorg\Plugin_Directory\Block_JSON; use WordPressdotorg\Plugin_Directory\Plugin_Directory; use WordPressdotorg\Plugin_Directory\Readme\Parser; use WordPressdotorg\Plugin_Directory\Template; @@ -658,10 +659,26 @@ static function find_blocks_in_file( $filename ) { } } if ( 'block.json' === basename( $filename ) ) { - // A block.json file has everything we want. - $blockinfo = json_decode( file_get_contents( $filename ) ); - if ( isset( $blockinfo->name ) ) { - $blocks[] = $blockinfo; + // A block.json file should have everything we want. + $validator = new Block_JSON\Validator(); + $block = Block_JSON\Parser::parse( array( 'file' => $filename ) ); + $result = $validator->validate( $block ); + if ( ! is_wp_error( $block ) && is_wp_error( $result ) ) { + // Only certain properties must be valid for our purposes here. + $required_valid_props = array( + 'block.json', + 'block.json:editorScript', + 'block.json:editorStyle', + 'block.json:name', + 'block.json:script', + 'block.json:style', + ); + $invalid_props = array_intersect( $required_valid_props, $result->get_error_data( 'error' ) ); + if ( empty( $invalid_props ) ) { + $blocks[] = $block; + } + } elseif ( true === $result ) { + $blocks[] = $block; } } From 8e9bbe36ee524dd25508dbb9a065db9f55e29545 Mon Sep 17 00:00:00 2001 From: tellyworth Date: Wed, 17 Jun 2020 20:50:37 +1000 Subject: [PATCH 8/8] Category will become optional. --- block-json/class-validator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block-json/class-validator.php b/block-json/class-validator.php index 7f86f93..e9a8b88 100644 --- a/block-json/class-validator.php +++ b/block-json/class-validator.php @@ -177,7 +177,7 @@ public static function schema() { 'type' => 'string', ), ), - 'required' => array( 'name', 'title', 'category' ), + 'required' => array( 'name', 'title' ), 'additionalProperties' => false, ); }