Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add parsing and validation for block.json files #2

Merged
merged 8 commits into from
Jun 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions block-json/block.json
Original file line number Diff line number Diff line change
@@ -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",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up with a more nuanced way of defining assets, for example:
https://github.com/WordPress/gutenberg/blob/master/docs/rfc/block-registration.md#editor-script

{ "script": "file:./build/script.js" }

Full description here:
https://github.com/WordPress/gutenberg/blob/master/docs/rfc/block-registration.md#wpdefinedasset

Long story short, developers can provide a relative path as before or a script/style handle. For the Block Directory, you could say that file is the only option.

"script": "build/main.js",
"editorStyle": "build/editor.css",
"style": "build/style.css"
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are going to be added two more fields:

  • context (name might change because context is reserved in REST API)
  • providesContext

Tracking ticket: https://core.trac.wordpress.org/ticket/49927
Documentation update: WordPress/gutenberg#22686

174 changes: 174 additions & 0 deletions block-json/class-parser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
<?php

namespace WordPressdotorg\Plugin_Directory\Block_JSON;

use WP_Error;

defined( 'WPINC' ) || die();

/**
* Class Parser
*
* @package WordPressdotorg\Plugin_Directory\Block_JSON
*/
class Parser {
/**
* A location resource must be pointing to a file named this.
*
* @const string
*/
const REQUIRED_FILENAME = 'block.json';

/**
* Parse the JSON content of a given resource into a PHP object.
*
* @param array $resource An associative array with one item where the key specifies the type of resource and the
* value represents the resource as a string. Valid types are `url`, `file`, and `content`.
* In this case, the value of `content` is expected to be a raw string of JSON, while the
* other two are expected to be the location of a file containing the JSON.
*
* @return object|WP_Error An object if the parse was successful, otherwise a WP_Error.
*/
public static function parse( array $resource ) {
reset( $resource );
$type = key( $resource );
$handle = current( $resource );

switch ( $type ) {
case 'url':
$content = self::extract_content_from_url( $handle );
break;
case 'file':
$content = self::extract_content_from_file( $handle );
break;
case 'content':
$content = $handle;
break;
default:
$content = new WP_Error(
'no_valid_resource',
__( 'No valid resource type was given.', 'wporg-plugins' )
);
break;
}

if ( is_wp_error( $content ) ) {
return $content;
}

return self::parse_content( $content );
}

/**
* Get the contents of a block.json file via an HTTP request to a URL.
*
* @param string $url
*
* @return string|WP_Error
*/
protected static function extract_content_from_url( $url ) {
$url = esc_url_raw( $url );

$filename_length = strlen( self::REQUIRED_FILENAME );
if ( strtolower( substr( $url, - $filename_length ) ) !== self::REQUIRED_FILENAME ) {
return new WP_Error(
'resource_url_invalid',
sprintf(
/* translators: %s: file name */
__( 'URL must end in %s!', 'wporg-plugins' ),
'<code>' . self::REQUIRED_FILENAME . '</code>'
)
);
}

$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' ),
'<code>' . self::REQUIRED_FILENAME . '</code>'
)
);
}

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',
sprintf(
__( 'JSON Parser: %s', 'wporg-plugins' ),
esc_html( $error )
),
array(
'error_code' => json_last_error(),
)
);
}

return $parsed;
}
}
Loading