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 GraphQL API #51

Merged
merged 45 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
b1ee4fa
Add WP-GraphQL to Block Data API, import it on plugin startup and mak…
ingeniumed Oct 31, 2023
5d9a6a6
Add the skeleton layout of the graphQL endpoint
ingeniumed Oct 31, 2023
a386950
Add the parent_id and id to each block
ingeniumed Oct 31, 2023
bf73c1c
Transform the block attributes, and add an extra param to the sourced…
ingeniumed Nov 1, 2023
83bfd74
Require the new graphQL api file
ingeniumed Nov 1, 2023
7e42577
Set the minimum version of the plugin to be 8.0
ingeniumed Nov 1, 2023
cf0286d
Remove the graphQL plugin from the plugin itself.
ingeniumed Nov 8, 2023
61bee9d
Remove the lib folder from the PHPCS
ingeniumed Nov 13, 2023
4c00b0d
Attempting to flatten the nested blocks
ingeniumed Nov 13, 2023
d810a0f
Flattened the innerblocks, but still have a duplicate showing up
ingeniumed Nov 13, 2023
71dd8ae
Fixed the bug with duplicates in the child list
ingeniumed Nov 14, 2023
d98425a
Downgrade php version to 7.4
ingeniumed Nov 14, 2023
d5ab09e
Add some php docs and fix linting problems
ingeniumed Nov 14, 2023
778509d
Tweak todo comments
ingeniumed Nov 14, 2023
75e992f
Remove the new filter and instead just add a new arg to the existing one
ingeniumed Nov 16, 2023
04237ed
Move the id/parentId logic to the graphQL logic instead of the conten…
ingeniumed Nov 16, 2023
145ea17
Fix linting errors
ingeniumed Nov 16, 2023
1df6165
Fix unknown error
ingeniumed Nov 28, 2023
830894b
Fix unknown error
ingeniumed Nov 28, 2023
e06b15a
Fix unknown error
ingeniumed Nov 28, 2023
7e5ae7e
Fix unknown error
ingeniumed Nov 28, 2023
33fc4dd
Fix unknown error
ingeniumed Nov 28, 2023
1dda9a7
add tests and clean up the code
ingeniumed Nov 30, 2023
df10512
Fix the linting problems
ingeniumed Nov 30, 2023
5ad55f1
Add more comments
ingeniumed Nov 30, 2023
a07df2b
Add docs for graphql and add analytics
ingeniumed Dec 1, 2023
984f519
tweak the table
ingeniumed Dec 1, 2023
43560a8
Update README.md
ingeniumed Dec 4, 2023
9f03a0c
Add the utility function
ingeniumed Dec 4, 2023
0e63fe4
Add the utility function
ingeniumed Dec 4, 2023
47f2efb
Re-write the graphQL generation separate from a filter
ingeniumed Dec 7, 2023
17000db
fix failures in tests
ingeniumed Dec 7, 2023
3c6cdd0
fix failures in tests
ingeniumed Dec 7, 2023
5bb6030
fix failures in tests
ingeniumed Dec 7, 2023
e4acbf8
fix failures in tests
ingeniumed Dec 7, 2023
bd255c2
Add a mock for a graphQL specific function
ingeniumed Dec 7, 2023
a25b630
fix linting
ingeniumed Dec 7, 2023
272e92b
Add a flatten option for the innerblocks
ingeniumed Dec 8, 2023
fac6ebf
Address README feedback, standardize code spacing, update TOC
alecgeatches Dec 11, 2023
d578fe4
Add minor spacing and documentation changes
alecgeatches Dec 11, 2023
8cdc1e2
Explicitly strval block attribute values
alecgeatches Dec 12, 2023
90c77de
Add note about mocked integer ID values in README
alecgeatches Dec 12, 2023
a64af6d
add a mention of the flatten paramter
ingeniumed Dec 13, 2023
6e2c367
Incorporate post ID into GraphQL relay IDs to prevent cache collisions
alecgeatches Dec 13, 2023
f89ed3c
Fix linting errors, make Relay mock return more deterministic numbers
alecgeatches Dec 13, 2023
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
363 changes: 328 additions & 35 deletions README.md
smithjw1 marked this conversation as resolved.
Show resolved Hide resolved

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
"automattic/vipwpcs": "^3.0",
"yoast/phpunit-polyfills": "^2.0",
"dms/phpunit-arraysubset-asserts": "^0.5.0"

},
"config": {
"allow-plugins": {
Expand Down
2 changes: 1 addition & 1 deletion phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
<rule ref="WordPress-Extra"/>
<!-- For help in understanding these custom sniff properties:
https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties -->
<config name="minimum_supported_wp_version" value="5.8"/>
<config name="minimum_supported_wp_version" value="5.9"/>

<rule ref="WordPress-VIP-Go">
<!-- These disallow anonymous functions as action callbacks -->
Expand Down
245 changes: 245 additions & 0 deletions src/graphql/graphql-api.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
<?php
/**
* GraphQL API
*
* @package vip-block-data-api
*/

namespace WPCOMVIP\BlockDataApi;

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

/**
* GraphQL API to offer an alternative to the REST API.
*/
class GraphQLApi {
/**
* Initiatilize the graphQL API, if its allowed
Copy link
Member

Choose a reason for hiding this comment

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

This comment could be better (either here or below), to explain that it hooks 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have made this change as well

*
* @access private
Copy link
Member

Choose a reason for hiding this comment

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

not private

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Taken it out

*/
public static function init() {
/**
* 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.
*/
$is_graphql_to_be_enabled = apply_filters( 'vip_block_data_api__is_graphql_enabled', true );
smithjw1 marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

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

This init method is being called on load (at the end of this file, not hooked into WordPress init or another lifecycle hook). Therefore, plugin / theme code may not be able to add a filter before this is executed. I would move this inside the register_types method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, I have moved this into the register_types now.


if ( ! $is_graphql_to_be_enabled ) {
return;
}

add_filter( 'vip_block_data_api__sourced_block_result', [ __CLASS__, 'transform_block_format' ], 10, 5 );
Copy link
Member

Choose a reason for hiding this comment

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

Do you want to add this filter if WPGraphQL isn't installed or active?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have taken it out now entirely.


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 ) {
Copy link
Member

Choose a reason for hiding this comment

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

Is there a PHP version policy for this plugin? Can you use type declarations?

Suggested change
public static function get_blocks_data( $post_model ) {
public static function get_blocks_data( \WPGraphQL\Model\Post $post_model ) {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We have set it at 7.4, but will be upping it to 8.0. At that point we were gonna go in and set the type declarations. That is on the cards, so I haven't done that here.

That said, I do have to figure out when I do how to pass in post model of type \WPGraphQL\Model\Post when we dont bundle in WP-GraphQL with the plugins. The tests fail due to it, I realized.

I'll leave this one as is for this PR

$post_id = $post_model->ID;
$post = get_post( $post_id );
$filter_options = [ 'graphQL' => true ];
alecgeatches marked this conversation as resolved.
Show resolved Hide resolved

$content_parser = new ContentParser();

$parser_results = $content_parser->parse( $post->post_content, $post_id, $filter_options );
Copy link
Member

Choose a reason for hiding this comment

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

can access on $model->fields

Suggested change
$parser_results = $content_parser->parse( $post->post_content, $post_id, $filter_options );
$parser_results = $content_parser->parse( $post_model->fields->contentRaw, $post_model->fields->ID, $filter_options );

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had tried to do it that way, but no matter what I did the fields were null. That's why I did it this way. The plugin internally calls get_post already so the info is quickly returned.

Is there anyone special I need to do? I had called it the way u suggested 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I realized I summed up the same comment that's in the blocks.php within the decoupled bundle itself. That's really why it's been done this way as that same reasoning applies.

	// WPGraphQL's Post model restricts access to the raw post_content (contentRaw)
	// based on "edit_posts" cap. Since we want to serve blocks even to logged-out
	// users -- and because we are parsing this content before returning it --
	// we'll bypass the model and access the raw content directly.


// 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() );
chriszarate marked this conversation as resolved.
Show resolved Hide resolved
}

return $parser_results;
}

/**
* Transform the block's format to the format expected by the graphQL API.
*
* @param array $sourced_block An associative array of parsed block data with keys 'name' and 'attribute'.
* @param string $block_name Name of the parsed block, e.g. 'core/paragraph'.
* @param int $post_id Post ID associated with the parsed block.
* @param array $block Result of parse_blocks() for this block. Contains 'blockName', 'attrs', 'innerHTML', and 'innerBlocks' keys.
* @param array $filter_options Options to filter using, if any.
*
* @return array
*/
public static function transform_block_format( $sourced_block, $block_name, $post_id, $block, $filter_options ) { // phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand why this is called via a filter from ContentParser. Why not just call it directly in get_blocks_data?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's 2 reasons why it was done that way:

  • I wanted to keep the GraphQL logic separate from the parsing logic
  • I also wanted to ensure that, the transforming happened at the same time as when the parser worked so we didn't loop over each block twice

So this way, while its odd, gives us the best of both worlds. With that said, I do think it can be done in a better way even if that means we go over the entire block list twice.

Talking it over with @alecgeatches, I'll change it up to:

  • Get the parser results from ContentParser
  • Go over the results, and transform it into the graphQL format
  • This would make any potential changes to the structure, like flattening or not flattening innerBlocks easier as well as anything else we think of.
  • No more filter oddeties to be done

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Switched it over so the transformation is done after the parser comes back, so more filter weirdness

if ( isset( $filter_options['graphQL'] ) && $filter_options['graphQL'] ) {

// Add the ID to the block, if it is not already there.
if ( ! isset( $sourced_block['id'] ) && isset( $filter_options['id'] ) ) {
$sourced_block['id'] = $filter_options['id'];
}

// Flatten the inner blocks, if any.
if ( isset( $sourced_block['innerBlocks'] ) ) {
$sourced_block['innerBlocks'] = self::flatten_inner_blocks( $sourced_block['innerBlocks'], $filter_options );
}

// Convert the attributes to be in the name-value format that the schema expects.
if ( isset( $sourced_block['attributes'] ) && ! empty( $sourced_block['attributes'] ) && ! isset( $sourced_block['attributes'][0]['name'] ) ) {
$sourced_block['attributes'] = array_map(
function ( $name, $value ) {
return [
'name' => $name,
'value' => $value,
];
},
array_keys( $sourced_block['attributes'] ),
array_values( $sourced_block['attributes'] )
);
}
}

return $sourced_block;
}

/**
* Flatten the inner blocks, no matter how many levels of nesting is there.
*
* @param array $inner_blocks the inner blocks in the block.
* @param array $filter_options Options to filter using, if any.
* @param array $flattened_blocks the flattened blocks that's built up as we go through the inner blocks.
*
* @return array
*/
public static function flatten_inner_blocks( $inner_blocks, $filter_options, $flattened_blocks = [] ) {
foreach ( $inner_blocks as $inner_block ) {
// This block doesnt have any inner blocks, so just add it to the flattened blocks. Ensure the parentId is set.
if ( ! isset( $inner_block['innerBlocks'] ) ) {
$inner_block['parentId'] = $inner_block['parentId'] ?? $filter_options['parentId'];
array_push( $flattened_blocks, $inner_block );
// This block is a root block, so go through the inner blocks recursively.
} elseif ( ! isset( $inner_block['parentId'] ) ) {
$inner_blocks_copy = $inner_block['innerBlocks'];
unset( $inner_block['innerBlocks'] );
$inner_block['parentId'] = $inner_block['parentId'] ?? $filter_options['parentId'];
array_push( $flattened_blocks, $inner_block );
$flattened_blocks = self::flatten_inner_blocks( $inner_blocks_copy, $filter_options, $flattened_blocks );
}
}

return $flattened_blocks;
}

/**
* Register types and fields graphql integration.
*
* @return void
*/
public static function register_types() {
// Register the type corresponding to the attributes of each individual block.
register_graphql_object_type(
'BlockDataAttribute',
Copy link
Member

Choose a reason for hiding this comment

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

Naming is hard, but isn't this a BlockAttribute? BlockData is the collection?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yip, changed it

[
'description' => 'Block data attribute',
'fields' => [
'name' => [
'type' => 'String',
Copy link
Member

Choose a reason for hiding this comment

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

It's good practice to designate fields as non-nullable whenever possible. That way the consumer can have confidence that a field will always have a value and can skip inspection.

Suggested change
'type' => 'String',
'type' => [ 'non_null' => 'String' ],

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, I have done this now

'description' => 'Block data attribute name',
],
'value' => [
'type' => 'String',
'description' => 'Block data attribute value',
],
],
],
);

// Register the type corresponding to the individual inner block, with the above attribute.
register_graphql_type(
'InnerBlockData',
[
'description' => 'Block data',
'fields' => [
'id' => [
'type' => 'String',
Copy link
Member

Choose a reason for hiding this comment

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

IDs in WPGraphQL are always non-nullable and are typically of type ID meaning a Relay ID. Example. Create a Relay ID with Relay::toGlobalId example.

Suggested change
'type' => 'String',
'type' => [ 'non_null' => 'ID' ],

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, I have done this now for the ID and the parentId.

'description' => 'ID of the block',
],
'parentId' => [
'type' => 'String',
Copy link
Member

Choose a reason for hiding this comment

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

review all these fields for non-nullability

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, I have done this now for all the fields

'description' => 'ID of the parent for this inner block, if it is an inner block. This will match the ID of the block',
],
'name' => [
'type' => 'String',
'description' => 'Block name',
],
'attributes' => [
'type' => [
'list_of' => 'BlockDataAttribute',
],
'description' => 'Block data attributes',
],
],
],
);

// Register the type corresponding to the individual block, with the above attribute.
register_graphql_type(
'BlockData',
chriszarate marked this conversation as resolved.
Show resolved Hide resolved
[
'description' => 'Block data',
ingeniumed marked this conversation as resolved.
Show resolved Hide resolved
'fields' => [
'id' => [
'type' => 'String',
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
'type' => 'String',
'type' => [ 'non_null' => ID ],

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, I have done this now

'description' => 'ID of the block',
],
'name' => [
'type' => 'String',
'description' => 'Block name',
],
'attributes' => [
'type' => [
'list_of' => 'BlockDataAttribute',
],
'description' => 'Block data attributes',
],
'innerBlocks' => [
'type' => [ 'list_of' => 'InnerBlockData' ],
'description' => 'Flattened list of inner blocks of this block',
Copy link
Member

Choose a reason for hiding this comment

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

I get why these are flattened, but is the "reparenting" going to be annoying / error-prone? Is the nesting issue severe enough to warrant this approach?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The core problem we wanted to solve was that, you should be able to get back the complicated block hierarchy in a post without having any knowledge of the depth involved. This gets us past that, and to ensure that the re-parenting isn't annoying we added a little utility function in the README that could be used. Admittedly that function could be simplified further, but I kept it simple enough to follow. By ensuring that each block gets an id and if applicable, a parent id the likelihood of an error should be minimal (one would hope, but obviously things can go wrong).

The other approach would be to flatten the innerBlocks alongside all the blocks but that proved to be even more complicated (in terms of code and the outcome) and wouldn't necessarily match the structure of a parent-child in a post with Gutenberg blocks.

So with that in mind, this was the ultimate solution that was picked to allow graphQL to be added on top of the block data api without the innerBlocks being a problem.

Copy link
Member

Choose a reason for hiding this comment

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

Ok. I totally get the why, just wondering how big a deal this actually is in practice:

you should be able to get back the complicated block hierarchy in a post without having any knowledge of the depth involved

If you use fragments in your query, adding another layer isn't a huge deal. And since you are reconstructing the deep tree on the client side, your block parsing code isn't less complex.

Yes, if you add another level beyond what you are querying for, it simply won't be in the response. But adding a hasInnerBlocks or innerBlocksCount property could allow the queryer to determine if they are "missing out" on blocks.

Another drawback of this approach is that it makes human inspection of the response data much less friendly, possibly impossible. Human debugging is useful and common, especially with the built-in GraphiQL client.

Again, totally understand where this is coming from, just want to make sure the devex has been fully considered.

Copy link
Member

Choose a reason for hiding this comment

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

Just an idea: If you use the same type for both inner and outer blocks, you could add a GraphQL field directive that allows the queryer to choose whether to flatten or not. If would look like this:

query FlattenBlocks {
   post(id: "abc123=") {
      blockData(flatten: true) {
         # ...
      }
   }
}

query NestedBlocks {
   post(id: "abc123=") {
      blockData(flatten: false) { # or omit the directive
         # ...
      }
   }
}

Copy link
Contributor

Choose a reason for hiding this comment

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

In the research specifically mentioned flattening the hierarchy and using parent/child relationships. No one balked at it.

I'm not trying to suggest that flattening is the right choice. I think optionality is good, as are Chris' points.

My take is we have options that customers will accept, and we can choose from among them, which is a great place to be.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks to the re-write I did of the way the parser's result is transformed into the right graphQL schema, I was able to add in this flatten option. So by default it's set to true because we want the inner blocks to be flattened. Omitting it sets it to true by default. If you set it to false, it will not flatten the inner blocks, and send back the original hierarchy.

So best of both options are available, but we give back the flattened hierarchy by default as thats the best option we want to be used.

Copy link
Member

@chriszarate chriszarate Dec 8, 2023

Choose a reason for hiding this comment

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

Nice! FYI it is not mentioned in the README

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, added it in under usage!

],
],
],
);

// Register the type corresponding to the list of individual blocks, with each item being the above type.
register_graphql_type(
'BlocksData',
[
'description' => 'Data for all the blocks',
'fields' => [
'blocks' => [
'type' => [ 'list_of' => 'BlockData' ],
'description' => 'List of blocks data',
],
'warnings' => [
Copy link
Member

Choose a reason for hiding this comment

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

Warning / error data typically doesn't ship in the data payload, but instead goes to graphql_debug() where it can be conditionally displayed in the errors payload. Better control for the site maintainer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm still working on this, ran into some problems that are of my own making.

'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',
Copy link
Member

Choose a reason for hiding this comment

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

I know you are following the pattern of the decoupled bundle here, but let's discuss the pros and cons of adding the field to this union.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had looked up this page as well as this one to see what it could support. From what I gathered this would be what we support - pages and posts that would be made using the content editor. We have a check with the contentParser that already checks to see if the provided post has blocks within it or not, so that should account for the classic editor.

That was my thinking, alongside this being used in the decoupled bundle already. Was there something else or a downside that I might have missed in using this?

Copy link
Member

Choose a reason for hiding this comment

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

Nevermind, I was conflating this with querying for contentNodes, which is a bit more problematic. I think this is fine.

this would be what we support - pages and posts that would be made using the content editor.

The NodeWithContentEditor interface picks up any post type that supports the content editor regardless of its purpose or visibility. It's worth noting that the default value for register_post_type#supports includes 'editor' (and also many developers and plugin authors don't pay close attention to post type args), so in reality the field will probably get added to a bunch of random plugin-created post types where it might not make sense. But it's fine since the user is unlikely to expose them in GraphQL or query for them. Just FYI.

'BlocksData',
Copy link
Member

Choose a reason for hiding this comment

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

field name should be camel case

Suggested change
'BlocksData',
'blocksData',

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed this as well

[
'type' => 'BlocksData',
'description' => 'A block representation of post content',
'resolve' => [ __CLASS__, 'get_blocks_data' ],
]
);
}
}

GraphQLApi::init();
5 changes: 3 additions & 2 deletions src/parser/block-additions/core-image.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class CoreImage {
* @access private
*/
public static function init() {
add_filter( 'vip_block_data_api__sourced_block_result', [ __CLASS__, 'add_image_metadata' ], 5, 4 );
add_filter( 'vip_block_data_api__sourced_block_result', [ __CLASS__, 'add_image_metadata' ], 5, 5 );
Copy link
Member

Choose a reason for hiding this comment

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

seems like you could just keep the arity at 4

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Made this change

}

/**
Expand All @@ -29,12 +29,13 @@ public static function init() {
* @param string $block_name Name of the block.
* @param int|null $post_id Id of the post.
* @param array $block Block being parsed.
* @param array $filter_options Options for the filter, if any.
*
* @access private
*
* @return array Updated sourced block with new metadata information
*/
public static function add_image_metadata( $sourced_block, $block_name, $post_id, $block ) { // phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
public static function add_image_metadata( $sourced_block, $block_name, $post_id, $block, $filter_options ) { // phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
Copy link
Member

Choose a reason for hiding this comment

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

unused?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yip, cleaned this up

if ( 'core/image' !== $block_name ) {
return $sourced_block;
}
Expand Down
17 changes: 12 additions & 5 deletions src/parser/content-parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

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

use ArrayObject;
use Throwable;
use WP_Error;
use WP_Block_Type;
Expand Down Expand Up @@ -105,6 +106,8 @@ public function should_block_be_included( $block, $block_name, $filter_options )
* @return array|WP_Error
*/
public function parse( $post_content, $post_id = null, $filter_options = [] ) {
Analytics::record_usage();

if ( isset( $filter_options['exclude'] ) && isset( $filter_options['include'] ) ) {
return new WP_Error( 'vip-block-data-api-invalid-params', 'Cannot provide blocks to exclude and include at the same time', [ 'status' => 400 ] );
}
Expand Down Expand Up @@ -235,8 +238,11 @@ protected function source_block( $block, $registered_blocks, $filter_options ) {
'attributes' => $block_attributes,
];

$filter_options['id'] = wp_unique_id();

if ( isset( $block['innerBlocks'] ) ) {
$inner_blocks = array_map( function ( $block ) use ( $registered_blocks, $filter_options ) {
$filter_options['parentId'] = $filter_options['id'];
$inner_blocks = array_map( function ( $block ) use ( $registered_blocks, $filter_options ) {
return $this->source_block( $block, $registered_blocks, $filter_options );
}, $block['innerBlocks'] );

Expand All @@ -255,17 +261,18 @@ protected function source_block( $block, $registered_blocks, $filter_options ) {

/**
* Filters a block when parsing is complete.
*
*
* @param array $sourced_block An associative array of parsed block data with keys 'name' and 'attribute'.
* @param string $block_name Name of the parsed block, e.g. 'core/paragraph'.
* @param int $post_id Post ID associated with the parsed block.
* @param array $block Result of parse_blocks() for this block. Contains 'blockName', 'attrs', 'innerHTML', and 'innerBlocks' keys.
* @param array $filter_options Options to filter using, if any.
*/
$sourced_block = apply_filters( 'vip_block_data_api__sourced_block_result', $sourced_block, $block_name, $this->post_id, $block );
$sourced_block = apply_filters( 'vip_block_data_api__sourced_block_result', $sourced_block, $block_name, $this->post_id, $block, $filter_options );

// If attributes are empty, explicitly use an object to avoid encoding an empty array in JSON.
// If attributes are empty, explicitly use an ArrayObject to encode an empty json object in JSON.
if ( empty( $sourced_block['attributes'] ) ) {
$sourced_block['attributes'] = (object) [];
$sourced_block['attributes'] = new ArrayObject();
}

return $sourced_block;
Expand Down
2 changes: 0 additions & 2 deletions src/rest/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,6 @@ public static function get_block_content( $params ) {
$post_id = $params['id'];
$post = get_post( $post_id );

Analytics::record_usage();
chriszarate marked this conversation as resolved.
Show resolved Hide resolved

$parse_time_start = microtime( true );

$content_parser = new ContentParser();
Expand Down
Loading
Loading