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

Release 1.3.0 #73

Merged
merged 19 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
56af143
Encode non-string types as JSON strings
Zamfi99 Jul 10, 2024
64da386
Update tests
Zamfi99 Jul 10, 2024
4ba3a36
Fix block ID state in Relay mock for GraphQL tests
alecgeatches Jul 24, 2024
d9268cc
Add tests for boolean, string, integer, and number type encoding in G…
alecgeatches Jul 24, 2024
a0ce58b
Merge pull request #1 from Automattic/feature/graphql-extend-complex-…
Zamfi99 Jul 25, 2024
184bca0
Merge pull request #66 from Zamfi99/feature/graphql-extend-complex-types
alecgeatches Jul 26, 2024
b555b30
Add asterisk selector test
alecgeatches Jul 30, 2024
003e525
Change content parser to enter <body> tag on start, adjust raw source…
alecgeatches Jul 30, 2024
68982d4
Merge pull request #71 from Automattic/fix/asterisk-attribute-selector
alecgeatches Aug 2, 2024
12a65ed
Add recursive structure to blocksDataV2, split new functionality into…
alecgeatches Aug 3, 2024
08e431e
Fix broken tests for BlocksDataV2
alecgeatches Aug 6, 2024
8183277
Update block hierarchy reconstruction section for blocksDataV2 and ad…
alecgeatches Aug 6, 2024
bdab138
Update existing blocksData examples to use blocksDataV2
alecgeatches Aug 6, 2024
80e6b08
Add deprecated warning to blocksData GraphQL description
alecgeatches Aug 6, 2024
d02658f
Add additional comments to block reconstruction code
alecgeatches Aug 6, 2024
46547df
Fix PHPCS comment doc error
alecgeatches Aug 6, 2024
606aa2c
Merge pull request #72 from Automattic/add/recursive-graphql-format
alecgeatches Aug 7, 2024
f299475
Minor changes to GraphQL "Usage" section text
alecgeatches Aug 7, 2024
906a1d8
Bump plugin version, WP tested version
alecgeatches Aug 7, 2024
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
373 changes: 206 additions & 167 deletions README.md

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions src/graphql/graphql-api.php → src/graphql/graphql-api-v1.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
/**
* GraphQL API to offer an alternative to the REST API.
*/
class GraphQLApi {
class GraphQLApiV1 {
/**
* Initiatilize the graphQL API by hooking into the graphql_register_types action,
* which only fires if WPGraphQL is installed and enabled, and is further controlled
Expand Down Expand Up @@ -140,11 +140,11 @@ public static function register_types() {
/**
* Filter to enable/disable the graphQL API. By default, it is enabled.
*
* @param bool $is_graphql_to_be_enabled Whether the graphQL API should be enabled or not.
* @param bool $is_graphql_enabled Whether the graphQL API should be enabled or not.
*/
$is_graphql_to_be_enabled = apply_filters( 'vip_block_data_api__is_graphql_enabled', true );
$is_graphql_enabled = apply_filters( 'vip_block_data_api__is_graphql_enabled', true );

if ( ! $is_graphql_to_be_enabled ) {
if ( ! $is_graphql_enabled ) {
return;
}

Expand Down Expand Up @@ -246,7 +246,7 @@ public static function register_types() {
'blocksData',
[
'type' => 'BlocksData',
'description' => 'A block representation of post content',
'description' => 'Deprecated, prefer blocksDataV2. A block representation of post content.',
'resolve' => [ __CLASS__, 'get_blocks_data' ],
]
);
Expand Down Expand Up @@ -277,4 +277,4 @@ public static function get_block_attribute_pair( $name, $value ) {
}
}

GraphQLApi::init();
GraphQLApiV1::init();
257 changes: 257 additions & 0 deletions src/graphql/graphql-api-v2.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
<?php
/**
* GraphQL API
*
* @package vip-block-data-api
*/

namespace WPCOMVIP\BlockDataApi;

use GraphQLRelay\Relay;

defined( 'ABSPATH' ) || die();

/**
* GraphQL API to offer an alternative to the REST API.
*/
class GraphQLApiV2 {
/**
* Initiatilize the graphQL API by hooking into the graphql_register_types action,
* which only fires if WPGraphQL is installed and enabled, and is further controlled
* by the vip_block_data_api__is_graphql_enabled filter.
*/
public static function init() {
add_action( 'graphql_register_types', [ __CLASS__, 'register_types' ] );
}

/**
* Extract the blocks data for a post, and return back in the format expected by the GraphQL API.
*
* @param \WPGraphQL\Model\Post $post_model Post model for post.
*
* @return array
*/
public static function get_blocks_data( $post_model ) {
$post_id = $post_model->ID;
$post = get_post( $post_id );

$content_parser = new ContentParser();

$parser_results = $content_parser->parse( $post->post_content, $post_id );

// We need to not return a WP_Error object, and so a regular exception is returned.
if ( is_wp_error( $parser_results ) ) {
Analytics::record_error( $parser_results );

// Return API-safe error with extra data (e.g. stack trace) removed.
return new \Exception( $parser_results->get_error_message() );
}

$parser_results['blocks'] = array_map( function ( $block ) use ( $post_id ) {
return self::transform_block_format( $block, $post_id );
}, $parser_results['blocks'] );

$parser_results['blocks'] = self::flatten_blocks( $parser_results['blocks'] );
return $parser_results;
}

/**
* Transform the block's format to the format expected by the graphQL API.
*
* @param array $block An associative array of parsed block data with keys 'name' and 'attributes'.
* @param array $post_id The associated post ID for the content being transformed. Used to produce unique block IDs.
*
* @return array
*/
public static function transform_block_format( $block, $post_id ) {
// Generate a unique ID for the block.
$block['id'] = Relay::toGlobalId( 'BlockDataV2', sprintf( '%d:%d', $post_id, wp_unique_id() ) );

// Convert the attributes to be in the name-value format that the schema expects.
$block = self::map_attributes( $block );

if ( isset( $block['innerBlocks'] ) && ! empty( $block['innerBlocks'] ) ) {
$block['innerBlocks'] = array_map( function ( $block ) use ( $post_id ) {
return self::transform_block_format( $block, $post_id );
}, $block['innerBlocks'] );
}

return $block;
}

/**
* Convert the attributes to be in the name-value format that the schema expects.
*
* @param array $block An associative array of parsed block data with keys 'name' and 'attributes'.
*
* @return array
*/
public static function map_attributes( $block ) {
// check if type of attributes is stdClass and unset it as that's not supported by graphQL.
if ( isset( $block['attributes'] ) && is_object( $block['attributes'] ) ) {
unset( $block['attributes'] );
} elseif ( isset( $block['attributes'] ) && ! empty( $block['attributes'] ) ) {
$block['attributes'] = array_map(
[ __CLASS__, 'get_block_attribute_pair' ],
array_keys( $block['attributes'] ),
array_values( $block['attributes'] )
);
}

return $block;
}

/**
* Flatten blocks recursively.
*
* @param array $blocks the inner blocks in the block.
* @param string $parent_id Optional. ID of the parent block that $blocks belong to.
*
* @return array
*/
public static function flatten_blocks( $blocks, $parent_id = null ) {
$flattened_blocks = [];

foreach ( $blocks as $block ) {
// Gather innerBlocks from current block
$inner_blocks = $block['innerBlocks'] ?? [];
unset( $block['innerBlocks'] );

// Set parent ID on current block
$block['parentId'] = $parent_id;

// Recurse into inner blocks
$flattened_blocks[] = $block;
$flattened_blocks = array_merge( $flattened_blocks, self::flatten_blocks( $inner_blocks, $block['id'] ) );
}

return $flattened_blocks;
}

/**
* Register types and fields graphql integration.
*
* @return void
*/
public static function register_types() {
/**
* Filter to enable/disable the graphQL API. By default, it is enabled.
*
* @param bool $is_graphql_enabled Whether the graphQL API should be enabled or not.
*/
$is_graphql_enabled = apply_filters( 'vip_block_data_api__is_graphql_enabled', true );

if ( ! $is_graphql_enabled ) {
return;
}

// Register the type corresponding to the attributes of each individual block.
register_graphql_object_type(
'BlockAttributeV2',
[
'description' => 'Block attribute',
'fields' => [
'name' => [
'type' => [ 'non_null' => 'String' ],
'description' => 'Block data attribute name',
],
'value' => [
'type' => [ 'non_null' => 'String' ],
'description' => 'Block data attribute value',
],
'isValueJsonEncoded' => [
'type' => [ 'non_null' => 'Boolean' ],
'description' => 'True if value is a complex JSON-encoded field. This is used to encode attribute types like arrays and objects.',
],
],
],
);

// Register the type corresponding to the individual block, with the above attribute.
register_graphql_type(
'BlockDataV2',
[
'description' => 'Block data (v2)',
'fields' => [
'id' => [
'type' => [ 'non_null' => 'ID' ],
'description' => 'ID of the block',
],
'parentId' => [
'type' => 'ID',
'description' => 'ID of the parent for this inner block, if it is an inner block. Otherwise, it will be null.',
],
'test' => [
'type' => 'String',
'description' => 'Test field',
],
'name' => [
'type' => [ 'non_null' => 'String' ],
'description' => 'Block name',
],
'attributes' => [
'type' => [
'list_of' => 'BlockAttributeV2',
],
'description' => 'Block attributes',
],
],
],
);

// Register the type corresponding to the list of individual blocks, with each item being the above type.
register_graphql_type(
'BlocksDataV2',
[
'description' => 'Data for all the blocks',
'fields' => [
'blocks' => [
'type' => [ 'list_of' => 'BlockDataV2' ],
'description' => 'List of blocks data',
],
'warnings' => [
'type' => [ 'list_of' => 'String' ],
'description' => 'List of warnings related to processing the blocks data',
],
],
],
);

// Register the field on every post type that supports 'editor'.
register_graphql_field(
'NodeWithContentEditor',
'blocksDataV2',
[
'type' => 'BlocksDataV2',
'description' => 'A block representation of post content (v2)',
'resolve' => [ __CLASS__, 'get_blocks_data' ],
]
);
}

/**
* Given a block attribute name and value, return a BlockAttribute array.
*
* @param string $name The name of the block attribute.
* @param mixed $value The value of the block attribute.
*
* @return array
*/
public static function get_block_attribute_pair( $name, $value ) {
// Non-string types (numbers, booleans, arrays, objects, for example) are encoded as JSON strings.
$is_value_json_encoded = false;

if ( ! is_string( $value ) ) {
$value = wp_json_encode( $value );
$is_value_json_encoded = true;
}

return [
'name' => $name,
'value' => strval( $value ),
'isValueJsonEncoded' => $is_value_json_encoded,
];
}
}

GraphQLApiV2::init();
8 changes: 4 additions & 4 deletions src/parser/content-parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ protected function source_block( $block, $registered_blocks, $filter_options ) {
$crawler = new Crawler( sprintf( '<!doctype html><html><body>%s</body></html>', $block['innerHTML'] ) );

// Enter the <body> tag for block parsing.
$crawler = $crawler->filter( 'body' );
$crawler = $crawler->filter( 'body' )->children();

$attribute_value = $this->source_attribute( $crawler, $block_attribute_definition );

Expand Down Expand Up @@ -313,11 +313,11 @@ protected function source_attribute( $crawler, $block_attribute_definition ) {

$attribute_value = $this->source_block_attribute( $crawler, $block_attribute_definition );
} elseif ( 'rich-text' === $attribute_source ) {
$attribute_value = $this->source_block_rich_text( $crawler, $block_attribute_definition );
} elseif ( 'html' === $attribute_source ) {
// Most 'html' sources were converted to 'rich-text' in WordPress 6.5.
// https://github.com/WordPress/gutenberg/pull/43204

$attribute_value = $this->source_block_rich_text( $crawler, $block_attribute_definition );
} elseif ( 'html' === $attribute_source ) {
$attribute_value = $this->source_block_html( $crawler, $block_attribute_definition );
} elseif ( 'text' === $attribute_source ) {
$attribute_value = $this->source_block_text( $crawler, $block_attribute_definition );
Expand Down Expand Up @@ -552,7 +552,7 @@ protected function source_block_raw( $crawler ) {
$attribute_value = null;

if ( $crawler->count() > 0 ) {
$attribute_value = trim( $crawler->html() );
$attribute_value = trim( $crawler->outerHtml() );
}

return $attribute_value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,19 @@

namespace WPCOMVIP\BlockDataApi;

use GraphQLRelay\Relay;

/**
* Tests for the GraphQL API.
*/
class GraphQLAPITest extends RegistryTestCase {
class GraphQLAPIV1Test extends RegistryTestCase {

protected function setUp(): void {
parent::setUp();

// Reset block ID counter before each test
Relay::reset();
}

public function test_is_graphql_enabled_true() {
$this->assertTrue( apply_filters( 'vip_block_data_api__is_graphql_enabled', true ) );
Expand Down Expand Up @@ -175,11 +184,13 @@ public function test_get_blocks_data() {
'post_content' => $html,
] );

$blocks_data = GraphQLApi::get_blocks_data( $post );
$blocks_data = GraphQLApiV1::get_blocks_data( $post );

$this->assertEquals( $expected_blocks, $blocks_data );
}

// get_blocks_data() attribute type tests

public function test_array_data_in_attribute() {
$this->register_global_block_with_attributes( 'test/custom-table', [
'head' => [
Expand Down Expand Up @@ -311,7 +322,7 @@ public function test_array_data_in_attribute() {
'isValueJsonEncoded' => true,
],
],
'id' => '6',
'id' => '1',
],
],
];
Expand All @@ -320,7 +331,7 @@ public function test_array_data_in_attribute() {
'post_content' => $html,
] );

$blocks_data = GraphQLApi::get_blocks_data( $post );
$blocks_data = GraphQLApiV1::get_blocks_data( $post );

$this->assertEquals( $expected_blocks, $blocks_data );
}
Expand Down Expand Up @@ -467,7 +478,7 @@ public function test_flatten_inner_blocks() {
],
];

$flattened_blocks = GraphQLApi::flatten_inner_blocks( $inner_blocks, '1' );
$flattened_blocks = GraphQLApiV1::flatten_inner_blocks( $inner_blocks, '1' );

$this->assertEquals( $expected_blocks, $flattened_blocks );
}
Expand Down
Loading
Loading