diff --git a/.phpcs.xml.dist b/.phpcs.xml.dist
index 4cdec649..0840286b 100644
--- a/.phpcs.xml.dist
+++ b/.phpcs.xml.dist
@@ -40,7 +40,7 @@
Tests for WordPress version compatibility.
https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties
-->
-
+
diff --git a/includes/BlockSupports/AbstractBlockSupport.php b/includes/BlockSupports/AbstractBlockSupport.php
new file mode 100644
index 00000000..a56ff9a9
--- /dev/null
+++ b/includes/BlockSupports/AbstractBlockSupport.php
@@ -0,0 +1,36 @@
+ __( 'Attributes for a block with Align support', 'wp-graphql-content-blocks' ),
+ 'eagerlyLoadType' => true,
+ 'interfaces' => [ 'EditorBlock' ],
+ 'fields' => [
+ 'align' => [
+ 'type' => 'String',
+ 'description' => __( 'The align attribute for the block.', 'wp-graphql-content-blocks' ),
+ ],
+ ],
+ ]
+ );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public static function has_block_support( \WP_Block_Type $block_type ): bool {
+ return block_has_support( $block_type, [ 'align' ], false );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public static function get_attributes_interfaces( \WP_Block_Type $block_type ): array {
+ if ( ! self::has_block_support( $block_type ) ) {
+ return [];
+ }
+
+ return [ 'BlockWithAlignSupportAttributes' ];
+ }
+}
diff --git a/includes/BlockSupports/Anchor.php b/includes/BlockSupports/Anchor.php
new file mode 100644
index 00000000..24402b83
--- /dev/null
+++ b/includes/BlockSupports/Anchor.php
@@ -0,0 +1,64 @@
+ __( 'Attributes for a Block with Anchor support.', 'wp-graphql-content-blocks' ),
+ 'eagerlyLoadType' => true,
+ 'interfaces' => [ 'EditorBlock' ],
+ 'fields' => [
+ 'anchor' => [
+ 'type' => 'String',
+ 'description' => __( 'The anchor attribute for the block.', 'wp-graphql-content-blocks' ),
+ 'resolve' => static function ( $block ) {
+ $rendered_block = wp_unslash( $block->renderedHtml );
+
+ if ( empty( $rendered_block ) ) {
+ return null;
+ }
+
+ return DOMHelpers::parse_first_node_attribute( $rendered_block, 'id' );
+ },
+ ],
+ ],
+ ]
+ );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public static function has_block_support( \WP_Block_Type $block_type ): bool {
+ return block_has_support( $block_type, [ 'anchor' ], false );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public static function get_attributes_interfaces( \WP_Block_Type $block_type ): array {
+ if ( ! self::has_block_support( $block_type ) ) {
+ return [];
+ }
+
+ return [ 'BlockWithAnchorSupportAttributes' ];
+ }
+}
diff --git a/includes/BlockSupports/Color.php b/includes/BlockSupports/Color.php
new file mode 100644
index 00000000..6590abec
--- /dev/null
+++ b/includes/BlockSupports/Color.php
@@ -0,0 +1,61 @@
+ __( 'Attributes for a Block with Color support.', 'wp-graphql-content-blocks' ),
+ 'eagerlyLoadType' => true,
+ 'interfaces' => [ 'EditorBlock' ],
+ 'fields' => [
+ 'backgroundColor' => [
+ 'type' => 'String',
+ 'description' => __( 'The backgroundColor attribute for the block.', 'wp-graphql-content-blocks' ),
+ ],
+ 'textColor' => [
+ 'type' => 'String',
+ 'description' => __( 'The textColor attribute for the block.', 'wp-graphql-content-blocks' ),
+ ],
+ 'gradient' => [
+ 'type' => 'String',
+ 'description' => __( 'The gradientColor attribute for the block.', 'wp-graphql-content-blocks' ),
+ ],
+ ],
+ ]
+ );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public static function has_block_support( \WP_Block_Type $block_type ): bool {
+ return block_has_support( $block_type, [ 'color' ], false );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public static function get_attributes_interfaces( \WP_Block_Type $block_type ): array {
+ if ( ! self::has_block_support( $block_type ) ) {
+ return [];
+ }
+
+ return [ 'BlockWithColorSupportAttributes' ];
+ }
+}
diff --git a/includes/BlockSupports/CustomClassName.php b/includes/BlockSupports/CustomClassName.php
new file mode 100644
index 00000000..cb4e0449
--- /dev/null
+++ b/includes/BlockSupports/CustomClassName.php
@@ -0,0 +1,53 @@
+ __( 'Attributes for a block with customClassName support', 'wp-graphql-content-blocks' ),
+ 'eagerlyLoadType' => true,
+ 'interfaces' => [ 'EditorBlock' ],
+ 'fields' => [
+ 'className' => [
+ 'type' => 'String',
+ 'description' => __( 'The custom CSS class name attribute for the block.', 'wp-graphql-content-blocks' ),
+ ],
+ ],
+ ]
+ );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public static function has_block_support( \WP_Block_Type $block_type ): bool {
+ return block_has_support( $block_type, [ 'customClassName' ], false );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public static function get_attributes_interfaces( \WP_Block_Type $block_type ): array {
+ if ( ! self::has_block_support( $block_type ) ) {
+ return [];
+ }
+
+ return [ 'BlockWithCustomClassNameSupportAttributes' ];
+ }
+}
diff --git a/includes/BlockSupports/Shadow.php b/includes/BlockSupports/Shadow.php
new file mode 100644
index 00000000..d4e959e0
--- /dev/null
+++ b/includes/BlockSupports/Shadow.php
@@ -0,0 +1,53 @@
+ __( 'Attributes for a Block with Shadow support.', 'wp-graphql-content-blocks' ),
+ 'eagerlyLoadType' => true,
+ 'interfaces' => [ 'EditorBlock' ],
+ 'fields' => [
+ 'shadow' => [
+ 'type' => 'String',
+ 'description' => __( 'The shadow attribute for the block.', 'wp-graphql-content-blocks' ),
+ ],
+ ],
+ ]
+ );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public static function has_block_support( \WP_Block_Type $block_type ): bool {
+ return block_has_support( $block_type, [ 'shadow' ], false );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public static function get_attributes_interfaces( \WP_Block_Type $block_type ): array {
+ if ( ! self::has_block_support( $block_type ) ) {
+ return [];
+ }
+
+ return [ 'BlockWithShadowSupportAttributes' ];
+ }
+}
diff --git a/includes/BlockSupports/Typography.php b/includes/BlockSupports/Typography.php
new file mode 100644
index 00000000..e1fddf80
--- /dev/null
+++ b/includes/BlockSupports/Typography.php
@@ -0,0 +1,57 @@
+ __( 'Attributes for a Block with Typography support.', 'wp-graphql-content-blocks' ),
+ 'eagerlyLoadType' => true,
+ 'interfaces' => [ 'EditorBlock' ],
+ 'fields' => [
+ 'fontSize' => [
+ 'type' => 'String',
+ 'description' => __( 'The fontSize attribute for the block.', 'wp-graphql-content-blocks' ),
+ ],
+ 'fontFamily' => [
+ 'type' => 'String',
+ 'description' => __( 'The fontFamily attribute for the block.', 'wp-graphql-content-blocks' ),
+ ],
+ ],
+ ]
+ );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public static function has_block_support( \WP_Block_Type $block_type ): bool {
+ return block_has_support( $block_type, [ 'typography' ], false );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public static function get_attributes_interfaces( \WP_Block_Type $block_type ): array {
+ if ( ! self::has_block_support( $block_type ) ) {
+ return [];
+ }
+
+ return [ 'BlockWithTypographySupportAttributes' ];
+ }
+}
diff --git a/includes/Blocks/Block.php b/includes/Blocks/Block.php
index 94258282..96aaa9d9 100644
--- a/includes/Blocks/Block.php
+++ b/includes/Blocks/Block.php
@@ -8,6 +8,7 @@
namespace WPGraphQL\ContentBlocks\Blocks;
use WPGraphQL\ContentBlocks\Data\BlockAttributeResolver;
+use WPGraphQL\ContentBlocks\Model\Block as BlockModel;
use WPGraphQL\ContentBlocks\Registry\Registry;
use WPGraphQL\ContentBlocks\Type\Scalar\Scalar;
use WPGraphQL\ContentBlocks\Utilities\WPGraphQLHelpers;
@@ -108,6 +109,7 @@ private function register_block_attributes_as_fields(): void {
'fields' => $block_attribute_fields,
]
);
+
register_graphql_field(
$this->type_name,
'attributes',
@@ -119,6 +121,7 @@ private function register_block_attributes_as_fields(): void {
$this->type_name
),
'resolve' => static function ( $block ) {
+ // Use the model to resolve the block attributes.
return $block;
},
]
@@ -135,55 +138,41 @@ private function register_block_attributes_as_fields(): void {
* @return mixed
*/
private function get_attribute_type( $name, $attribute, $prefix ) {
- $type = null;
-
if ( isset( $attribute['type'] ) ) {
switch ( $attribute['type'] ) {
case 'rich-text':
case 'string':
- $type = 'String';
- break;
+ return 'String';
case 'boolean':
- $type = 'Boolean';
- break;
+ return 'Boolean';
case 'number':
- $type = 'Float';
- break;
+ return 'Float';
case 'integer':
- $type = 'Int';
- break;
+ return 'Int';
case 'array':
if ( isset( $attribute['query'] ) ) {
- $type = [ 'list_of' => $this->get_query_type( $name, $attribute['query'], $prefix ) ];
- } elseif ( isset( $attribute['items'] ) ) {
+ return [ 'list_of' => $this->get_query_type( $name, $attribute['query'], $prefix ) ];
+ }
+
+ if ( isset( $attribute['items'] ) ) {
$of_type = $this->get_attribute_type( $name, $attribute['items'], $prefix );
if ( null !== $of_type ) {
- $type = [ 'list_of' => $of_type ];
- } else {
- $type = Scalar::get_block_attributes_array_type_name();
+ return [ 'list_of' => $of_type ];
}
- } else {
- $type = Scalar::get_block_attributes_array_type_name();
+
+ return Scalar::get_block_attributes_array_type_name();
}
- break;
+
+ return Scalar::get_block_attributes_array_type_name();
case 'object':
- $type = Scalar::get_block_attributes_object_type_name();
- break;
+ return Scalar::get_block_attributes_object_type_name();
}
} elseif ( isset( $attribute['source'] ) ) {
- $type = 'String';
+ return 'String';
}
- if ( null !== $type ) {
- $default_value = $attribute['default'] ?? null;
-
- if ( isset( $default_value ) ) {
- $type = [ 'non_null' => $type ];
- }
- }
-
- return $type;
+ return null;
}
/**
@@ -219,8 +208,10 @@ private function get_block_attribute_fields( ?array $block_attributes, string $p
$config = [
$attribute_name => $attribute_config,
];
- $result = $this->resolve_block_attributes_recursive( $block['attrs'], wp_unslash( render_block( $block ) ), $config );
+ $result = $this->resolve_block_attributes_recursive( $block, $config );
+
+ // Normalize the value.
return $result[ $attribute_name ];
},
];
@@ -325,7 +316,9 @@ private function normalize_attribute_value( $value, $type ) {
}
/**
- * Register the Type for the block. This happens after all other object types are already registered.
+ * Register the Type for the block.
+ *
+ * This happens after all other object types are already registered.
*/
private function register_type(): void {
register_graphql_object_type(
@@ -338,9 +331,6 @@ private function register_type(): void {
'name' => [
'type' => 'String',
'description' => __( 'The name of the block', 'wp-graphql-content-blocks' ),
- 'resolve' => static function ( $block ) {
- return isset( $block['blockName'] ) ? (string) $block['blockName'] : null;
- },
],
],
]
@@ -368,18 +358,17 @@ private function get_block_attributes_interfaces(): array {
/**
* Resolved the value of the block attributes based on the specified config
*
- * @param array $attribute_values The block current attributes value.
- * @param string $html The block rendered html.
- * @param array $attribute_configs The block current attribute configuration, keyed to the attribute name.
+ * @param \WPGraphQL\ContentBlocks\Model\Block $block The block model instance.
+ * @param array $attribute_configs The block current attribute configuration, keyed to the attribute name.
*/
- private function resolve_block_attributes_recursive( $attribute_values, string $html, array $attribute_configs ): array {
+ private function resolve_block_attributes_recursive( BlockModel $block, array $attribute_configs ): array {
$result = [];
// Clean up the html.
- $html = trim( $html );
+ $html = isset( $block->renderedHtml ) ? trim( $block->renderedHtml ) : '';
foreach ( $attribute_configs as $key => $config ) {
- $attribute_value = $attribute_values[ $key ] ?? null;
+ $attribute_value = $block->parsedAttributes[ $key ] ?? null;
$result[ $key ] = BlockAttributeResolver::resolve_block_attribute( $config, $html, $attribute_value );
}
diff --git a/includes/Blocks/CoreImage.php b/includes/Blocks/CoreImage.php
index caf6c81a..532299c6 100644
--- a/includes/Blocks/CoreImage.php
+++ b/includes/Blocks/CoreImage.php
@@ -55,8 +55,9 @@ public function __construct( WP_Block_Type $block, Registry $block_registry ) {
$this->type_name
),
'resolve' => static function ( $block ) {
- $attrs = $block['attrs'];
+ $attrs = $block->parsedAttributes ?? [];
$id = $attrs['id'] ?? null;
+
if ( $id ) {
$media_details = wp_get_attachment_metadata( $id );
if ( ! empty( $media_details ) ) {
diff --git a/includes/Data/ContentBlocksResolver.php b/includes/Data/ContentBlocksResolver.php
index dcf6c43c..caf0b10b 100644
--- a/includes/Data/ContentBlocksResolver.php
+++ b/includes/Data/ContentBlocksResolver.php
@@ -7,6 +7,7 @@
namespace WPGraphQL\ContentBlocks\Data;
+use WPGraphQL\ContentBlocks\Model\Block;
use WPGraphQL\Model\Post;
/**
@@ -20,7 +21,7 @@ final class ContentBlocksResolver {
* @param array $args GraphQL query args to pass to the connection resolver.
* @param string[] $allowed_block_names The list of allowed block names to filter.
*
- * @return array The resolved parsed blocks.
+ * @return \WPGraphQL\ContentBlocks\Model\Block[]
*/
public static function resolve_content_blocks( $node, $args, $allowed_block_names = [] ): array {
/**
@@ -39,7 +40,6 @@ public static function resolve_content_blocks( $node, $args, $allowed_block_name
$content = null;
if ( $node instanceof Post ) {
-
// @todo: this is restricted intentionally.
// $content = $node->contentRaw;
@@ -88,7 +88,30 @@ public static function resolve_content_blocks( $node, $args, $allowed_block_name
*/
$parsed_blocks = apply_filters( 'wpgraphql_content_blocks_resolve_blocks', $parsed_blocks, $node, $args, $allowed_block_names );
- return is_array( $parsed_blocks ) ? $parsed_blocks : [];
+ // Map the blocks to the Block model
+ $models = is_array( $parsed_blocks ) ? array_map(
+ static function ( $parsed_block ) {
+ $wp_block = new \WP_Block( $parsed_block );
+
+ if ( ! $wp_block->block_type ) {
+ graphql_debug(
+ sprintf(
+ // translators: %s is the block name.
+ __( 'Block type not found for block: %s', 'wp-graphql-content-blocks' ),
+ $wp_block->name ?: 'Unknown'
+ ),
+ );
+
+ return null;
+ }
+
+ return new Block( $wp_block );
+ },
+ $parsed_blocks
+ ) : [];
+
+ // Filter out unknown blocks.
+ return array_values( array_filter( $models ) );
}
/**
@@ -118,7 +141,7 @@ private static function handle_do_blocks( array $blocks ): array {
foreach ( $blocks as $block ) {
$block_data = self::handle_do_block( $block );
- if ( $block_data ) {
+ if ( ! empty( $block_data ) ) {
$parsed[] = $block_data;
}
}
@@ -147,12 +170,19 @@ private static function handle_do_block( array $block ): ?array {
// Assign a unique clientId to the block.
$block['clientId'] = uniqid();
- // @todo apply more hydrations.
+ // Some block need to be hydrated.
$block = self::populate_template_part_inner_blocks( $block );
+ $block = self::populate_post_content_inner_blocks( $block );
$block = self::populate_reusable_blocks( $block );
-
$block = self::populate_pattern_inner_blocks( $block );
+ /**
+ * Filters the block data after it has been processed.
+ *
+ * @param array $block The block data.
+ */
+ $block = apply_filters( 'wpgraphql_content_blocks_handle_do_block', $block );
+
// Prepare innerBlocks.
if ( ! empty( $block['innerBlocks'] ) ) {
$block['innerBlocks'] = self::handle_do_blocks( $block['innerBlocks'] );
@@ -213,6 +243,35 @@ private static function populate_template_part_inner_blocks( array $block ): arr
return $block;
}
+ /**
+ * Populates the innerBlocks of a core/post-content block with the blocks from the post content.
+ *
+ * @param array $block The block to populate.
+ *
+ * @return array The populated block.
+ */
+ private static function populate_post_content_inner_blocks( array $block ): array {
+ if ( 'core/post-content' !== $block['blockName'] ) {
+ return $block;
+ }
+
+ $post = get_post();
+
+ if ( ! $post ) {
+ return $block;
+ }
+
+ $parsed_blocks = ! empty( $post->post_content ) ? self::parse_blocks( $post->post_content ) : null;
+
+ if ( empty( $parsed_blocks ) ) {
+ return $block;
+ }
+
+ $block['innerBlocks'] = $parsed_blocks;
+
+ return $block;
+ }
+
/**
* Populates reusable blocks with the blocks from the reusable ref ID.
*
@@ -263,6 +322,7 @@ private static function populate_pattern_inner_blocks( array $block ): array {
}
$block['innerBlocks'] = $resolved_patterns;
+
return $block;
}
diff --git a/includes/Field/BlockSupports/Anchor.php b/includes/Field/BlockSupports/Anchor.php
index 4ca1ba02..bab2af5b 100644
--- a/includes/Field/BlockSupports/Anchor.php
+++ b/includes/Field/BlockSupports/Anchor.php
@@ -2,6 +2,8 @@
/**
* Registers the BlockSupports Anchor Field
*
+ * @deprecated @next-version Use `BlockWithAnchorSupportAttributes` instead.
+ *
* @package WPGraphQL\ContentBlocks\Field\BlockSupports
*/
@@ -21,16 +23,20 @@ public static function register(): void {
register_graphql_interface_type(
'BlockWithSupportsAnchor',
[
- 'description' => __( 'Block that supports Anchor field', 'wp-graphql-content-blocks' ),
- 'fields' => [
+ 'description' => __( 'Block that supports Anchor field', 'wp-graphql-content-blocks' ),
+ 'deprecationReason' => __( 'Use `BlockWithAnchorSupportAttributes` instead.', 'wp-graphql-content-blocks' ),
+ 'fields' => [
'anchor' => [
- 'type' => 'string',
- 'description' => __( 'The anchor field for the block.', 'wp-graphql-content-blocks' ),
- 'resolve' => static function ( $block ) {
- $rendered_block = wp_unslash( render_block( $block ) );
+ 'type' => 'string',
+ 'description' => __( 'The anchor field for the block.', 'wp-graphql-content-blocks' ),
+ 'deprecationReason' => __( 'Use `BlockWithAnchorSupportAttributes` instead.', 'wp-graphql-content-blocks' ),
+ 'resolve' => static function ( $block ) {
+ $rendered_block = wp_unslash( $block->renderedHtml );
+
if ( empty( $rendered_block ) ) {
return null;
}
+
return DOMHelpers::parse_first_node_attribute( $rendered_block, 'id' );
},
],
diff --git a/includes/Model/Block.php b/includes/Model/Block.php
new file mode 100644
index 00000000..cbcce93b
--- /dev/null
+++ b/includes/Model/Block.php
@@ -0,0 +1,114 @@
+ $parsedAttributes
+ * @property ?string $type
+ * @property \WP_Block $wpBlock
+ */
+class Block extends Model {
+ /**
+ * The underlying \WP_Block instance for the block data.
+ *
+ * @var \WP_Block
+ */
+ protected $data;
+
+ /**
+ * The rendered block html.
+ *
+ * @var ?string
+ */
+ protected $rendered_block;
+
+ /**
+ * {@inheritDoc}
+ *
+ * @param \WP_Block $block The block data to be modeled.
+ */
+ public function __construct( \WP_Block $block ) {
+ $this->data = $block;
+
+ parent::__construct();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function is_private() {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ protected function init() {
+ if ( empty( $this->fields ) ) {
+ $this->fields = [
+ 'clientId' => fn (): ?string => $this->data->parsed_block['clientId'] ?? uniqid(),
+ 'parentClientId' => fn (): ?string => $this->data->parsed_block['parentClientId'] ?? null,
+ 'name' => fn (): ?string => $this->data->name ?: null,
+ 'blockEditorCategoryName' => fn () => isset( $this->data->block_type->category ) ? $this->data->block_type->category : null,
+ 'isDynamic' => fn (): bool => is_callable( $this->data->block_type->render_callback ),
+ 'apiVersion' => fn (): ?int => $this->data->block_type->api_version ?: null,
+ 'cssClassNames' => fn (): ?array => isset( $this->data->attributes['className'] ) ? explode( ' ', $this->data->attributes['className'] ) : null,
+ 'renderedHtml' => fn (): ?string => $this->get_rendered_block(),
+ 'innerBlocks' => function (): array {
+ $block_list = $this->data->inner_blocks ?: [];
+
+ $models_to_return = [];
+
+ foreach ( $block_list as $block ) {
+ $models_to_return[] = new self( $block );
+ }
+
+ return $models_to_return;
+ },
+ 'parsedAttributes' => fn (): array => $this->data->attributes,
+ 'type' => function (): ?string {
+ $block_name = $this->name ?? null;
+
+ return isset( $block_name ) ? WPGraphQLHelpers::format_type_name( $block_name ) : null;
+ },
+ 'wpBlock' => function (): \WP_Block {
+ return $this->data;
+ },
+ ];
+ }
+ }
+
+ /**
+ * Renders the block html - only once.
+ *
+ * The `render_block()` function causes side effects (such as globally-incrementing the counter used for layout styles), so we only want to call it once.
+ */
+ protected function get_rendered_block(): ?string {
+ if ( ! isset( $this->rendered_block ) ) {
+ $this->rendered_block = ! empty( $this->data->parsed_block ) ? render_block( $this->data->parsed_block ) : '';
+ }
+
+ return $this->rendered_block;
+ }
+}
diff --git a/includes/Registry/Registry.php b/includes/Registry/Registry.php
index dacc69e7..8fe8f51c 100644
--- a/includes/Registry/Registry.php
+++ b/includes/Registry/Registry.php
@@ -7,8 +7,9 @@
namespace WPGraphQL\ContentBlocks\Registry;
+use WPGraphQL\ContentBlocks\BlockSupports;
use WPGraphQL\ContentBlocks\Blocks\Block;
-use WPGraphQL\ContentBlocks\Field\BlockSupports\Anchor;
+use WPGraphQL\ContentBlocks\Field\BlockSupports\Anchor as DeprecatedAnchorField;
use WPGraphQL\ContentBlocks\Type\InterfaceType\EditorBlockInterface;
use WPGraphQL\ContentBlocks\Type\InterfaceType\PostTypeBlockInterface;
use WPGraphQL\ContentBlocks\Type\Scalar\Scalar;
@@ -65,9 +66,9 @@ public function __construct( TypeRegistry $type_registry, $block_type_registry )
* Registry init procedure.
*/
public function init(): void {
- $this->register_interface_types();
$this->register_scalar_types();
$this->register_support_block_types();
+ $this->register_interface_types();
$this->register_block_types();
}
@@ -179,7 +180,7 @@ public function get_block_additional_interfaces( string $block_name ): array {
$block_interfaces = [];
// NOTE: Using add_filter here creates a performance penalty.
- $block_interfaces = Anchor::get_block_interfaces( $block_interfaces, $block_spec );
+ $block_interfaces = DeprecatedAnchorField::get_block_interfaces( $block_interfaces, $block_spec );
return $block_interfaces;
}
@@ -199,8 +200,22 @@ public function get_block_attributes_interfaces( string $block_name ): array {
}
$block_interfaces = [];
+
+ $block_support_classes = $this->get_block_supports_classes();
+
+ foreach ( $block_support_classes as $instance ) {
+ $interfaces_to_add = $instance::get_attributes_interfaces( $block_spec );
+
+ if ( empty( $interfaces_to_add ) ) {
+ continue;
+ }
+
+ $block_interfaces = array_merge( $block_interfaces, $interfaces_to_add );
+ }
+
// NOTE: Using add_filter here creates a performance penalty.
- $block_interfaces = Anchor::get_block_attributes_interfaces( $block_interfaces, $block_spec );
+ $block_interfaces = DeprecatedAnchorField::get_block_attributes_interfaces( $block_interfaces, $block_spec );
+
return $block_interfaces;
}
@@ -312,6 +327,27 @@ protected function register_block_type( WP_Block_Type $block ): void {
* @return void
*/
protected function register_support_block_types() {
- Anchor::register();
+ DeprecatedAnchorField::register();
+
+ $block_support_classes = $this->get_block_supports_classes();
+ foreach ( $block_support_classes as $block_support_class ) {
+ $block_support_class::register();
+ }
+ }
+
+ /**
+ * Get the block supports classes, keyed by the GraphQL type name they register.
+ *
+ * @return class-string<\WPGraphQL\ContentBlocks\BlockSupports\AbstractBlockSupport>[]
+ */
+ private function get_block_supports_classes(): array {
+ return [
+ BlockSupports\Align::class,
+ BlockSupports\Anchor::class,
+ BlockSupports\CustomClassName::class,
+ BlockSupports\Color::class,
+ BlockSupports\Shadow::class,
+ BlockSupports\Typography::class,
+ ];
}
}
diff --git a/includes/Type/InterfaceType/EditorBlockInterface.php b/includes/Type/InterfaceType/EditorBlockInterface.php
index 5aa8e760..907ebe3c 100644
--- a/includes/Type/InterfaceType/EditorBlockInterface.php
+++ b/includes/Type/InterfaceType/EditorBlockInterface.php
@@ -8,7 +8,6 @@
namespace WPGraphQL\ContentBlocks\Type\InterfaceType;
use WPGraphQL\ContentBlocks\Data\ContentBlocksResolver;
-use WPGraphQL\ContentBlocks\Utilities\WPGraphQLHelpers;
use WP_Block_Type_Registry;
/**
@@ -75,16 +74,10 @@ public static function register_type(): void {
'clientId' => [
'type' => 'String',
'description' => __( 'The id of the Block', 'wp-graphql-content-blocks' ),
- 'resolve' => static function ( $block ) {
- return isset( $block['clientId'] ) ? $block['clientId'] : uniqid();
- },
],
'parentClientId' => [
'type' => 'String',
'description' => __( 'The parent id of the Block', 'wp-graphql-content-blocks' ),
- 'resolve' => static function ( $block ) {
- return isset( $block['parentClientId'] ) ? $block['parentClientId'] : null;
- },
],
'name' => [
'type' => 'String',
@@ -93,22 +86,16 @@ public static function register_type(): void {
'blockEditorCategoryName' => [
'type' => 'String',
'description' => __( 'The name of the category the Block belongs to', 'wp-graphql-content-blocks' ),
- 'resolve' => static function ( $block ) {
- return isset( self::get_block( $block )->category ) ? self::get_block( $block )->category : null;
- },
],
'isDynamic' => [
'type' => [ 'non_null' => 'Boolean' ],
'description' => __( 'Whether the block is Dynamic (server rendered)', 'wp-graphql-content-blocks' ),
- 'resolve' => static function ( $block ) {
- return isset( self::get_block( $block )->render_callback );
- },
],
'apiVersion' => [
'type' => 'Integer',
'description' => __( 'The API version of the Gutenberg Block', 'wp-graphql-content-blocks' ),
'resolve' => static function ( $block ) {
- return isset( self::get_block( $block )->api_version ) && absint( self::get_block( $block )->api_version ) ? absint( self::get_block( $block )->api_version ) : 2;
+ return $block->apiVersion ?? 2;
},
],
'innerBlocks' => [
@@ -116,38 +103,25 @@ public static function register_type(): void {
'list_of' => 'EditorBlock',
],
'description' => __( 'The inner blocks of the Block', 'wp-graphql-content-blocks' ),
- 'resolve' => static function ( $block ) {
- return isset( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ? $block['innerBlocks'] : [];
- },
],
'cssClassNames' => [
'type' => [ 'list_of' => 'String' ],
'description' => __( 'CSS Classnames to apply to the block', 'wp-graphql-content-blocks' ),
- 'resolve' => static function ( $block ) {
- if ( isset( $block['attrs']['className'] ) ) {
- return explode( ' ', $block['attrs']['className'] );
- }
-
- return null;
- },
],
'renderedHtml' => [
'type' => 'String',
'description' => __( 'The rendered HTML for the block', 'wp-graphql-content-blocks' ),
- 'resolve' => static function ( $block ) {
- return render_block( $block );
- },
],
'type' => [
'type' => 'String',
'description' => __( 'The (GraphQL) type of the block', 'wp-graphql-content-blocks' ),
'resolve' => static function ( $block ) {
- return WPGraphQLHelpers::get_type_name_for_block( $block['blockName'] ?? null );
+ return $block->type;
},
],
],
'resolveType' => static function ( $block ) {
- return WPGraphQLHelpers::get_type_name_for_block( $block['blockName'] ?? null );
+ return $block->type;
},
]
);
diff --git a/includes/Type/InterfaceType/PostTypeBlockInterface.php b/includes/Type/InterfaceType/PostTypeBlockInterface.php
index f65e4d88..e77e4cbf 100644
--- a/includes/Type/InterfaceType/PostTypeBlockInterface.php
+++ b/includes/Type/InterfaceType/PostTypeBlockInterface.php
@@ -8,7 +8,6 @@
namespace WPGraphQL\ContentBlocks\Type\InterfaceType;
use WPGraphQL\ContentBlocks\Data\ContentBlocksResolver;
-use WPGraphQL\ContentBlocks\Utilities\WPGraphQLHelpers;
/**
* Class PostTypeBlockInterface
@@ -36,7 +35,7 @@ public static function register_type( string $post_type, array $block_names = []
],
],
'resolveType' => static function ( $block ) {
- return WPGraphQLHelpers::get_type_name_for_block( $block['blockName'] ?? null );
+ return $block->type;
},
]
);
diff --git a/includes/Utilities/DOMHelpers.php b/includes/Utilities/DOMHelpers.php
index 7f5cec79..82381fcb 100644
--- a/includes/Utilities/DOMHelpers.php
+++ b/includes/Utilities/DOMHelpers.php
@@ -19,11 +19,10 @@ final class DOMHelpers {
* @param string $html The HTML string to parse.
* @param string $selector The selector to use.
* @param string $attribute The attribute to extract.
- * @param mixed $default_value The default value to return if the selector is not found.
*
* @return ?string extracted attribute
*/
- public static function parse_attribute( string $html, string $selector, string $attribute, $default_value = null ): ?string {
+ public static function parse_attribute( string $html, string $selector, string $attribute ): ?string {
// Bail early if there's no html to parse.
if ( empty( trim( $html ) ) ) {
return null;
@@ -38,8 +37,7 @@ public static function parse_attribute( string $html, string $selector, string $
$selector = '*';
}
- $nodes = $doc->find( $selector );
- $default_value = isset( $default_value ) ? $default_value : null;
+ $nodes = $doc->find( $selector );
foreach ( $nodes as $node ) {
if ( $node->hasAttribute( $attribute ) ) {
@@ -47,7 +45,7 @@ public static function parse_attribute( string $html, string $selector, string $
}
}
- return $default_value;
+ return null;
}
/**
@@ -240,7 +238,7 @@ public static function find_nodes( string $html, ?string $selector = null ) {
public static function parseAttribute( $html, $selector, $attribute, $default_value = null ): ?string {
_deprecated_function( __METHOD__, '4.2.0', self::class . '::parse_attribute' );
- return self::parse_attribute( $html, $selector, $attribute, $default_value );
+ return self::parse_attribute( $html, $selector, $attribute ) ?: $default_value;
}
/**
diff --git a/tests/unit/ContentBlocksResolverTest.php b/tests/unit/ContentBlocksResolverTest.php
index dea2faae..0ba53885 100644
--- a/tests/unit/ContentBlocksResolverTest.php
+++ b/tests/unit/ContentBlocksResolverTest.php
@@ -103,7 +103,7 @@ public function test_resolve_content_blocks_resolves_reusable_blocks() {
// There should return only the non-empty blocks
$this->assertEquals( 3, count( $actual ) );
- $this->assertEquals( 'core/columns', $actual[0]['blockName'] );
+ $this->assertEquals( 'core/columns', $actual[0]->name );
}
public function test_resolve_content_blocks_filters_empty_blocks() {
@@ -111,32 +111,32 @@ public function test_resolve_content_blocks_filters_empty_blocks() {
$actual = $this->instance->resolve_content_blocks( $post_model, [ 'flat' => true ] );
// There should return only the non-empty blocks
$this->assertEquals( 6, count( $actual ) );
- $this->assertEquals( 'core/columns', $actual[0]['blockName'] );
+ $this->assertEquals( 'core/columns', $actual[0]->name );
}
public function test_resolve_content_blocks_resolves_classic_blocks() {
$post_model = new Post( get_post( $this->post_id ) );
$actual = $this->instance->resolve_content_blocks( $post_model, [ 'flat' => true ] );
- $this->assertEquals( 'core/freeform', $actual[5]['blockName'] );
+ $this->assertEquals( 'core/freeform', $actual[5]->name );
}
public function test_resolve_content_blocks_filters_blocks_not_from_allow_list() {
$post_model = new Post( get_post( $this->post_id ) );
$allowed = [ 'core/column', 'core/paragraph' ];
- $parsed_blocks = $this->instance->resolve_content_blocks( $post_model, [ 'flat' => true ], $allowed );
+ $actual = $this->instance->resolve_content_blocks( $post_model, [ 'flat' => true ], $allowed );
$actual_block_names = array_values(
array_unique(
array_map(
- static function ( $parsed_block ) {
- return $parsed_block['blockName'];
+ static function ( $block ) {
+ return $block->name;
},
- $parsed_blocks,
+ $actual,
)
)
);
// There should return only blocks from the allow list
- $this->assertEquals( 4, count( $parsed_blocks ) );
+ $this->assertEquals( 4, count( $actual ) );
$this->assertEquals( $allowed, $actual_block_names );
}
@@ -193,7 +193,13 @@ public function test_filters_wpgraphql_content_blocks_resolve_blocks() {
add_filter(
'wpgraphql_content_blocks_resolve_blocks',
static function ( $blocks, $node, $args, $allowed_block_names ) {
- return [ [ 'blockName' => 'core/test-filter' ] ];
+ return [
+ [ 'blockName' => 'core/test-filter' ], // This will be filtered out.
+ [
+ 'blockName' => 'core/paragraph',
+ 'attrs' => [ 'content' => 'Test content' ],
+ ]
+ ];
},
10,
4
@@ -205,7 +211,7 @@ static function ( $blocks, $node, $args, $allowed_block_names ) {
// The block should be resolved from the post node.
$this->assertCount( 1, $resolved_blocks );
- $this->assertEquals( 'core/test-filter', $resolved_blocks[0]['blockName'] );
+ $this->assertEquals( 'core/paragraph', $resolved_blocks[0]->name );
// Cleanup.
remove_all_filters( 'wpgraphql_content_blocks_resolve_blocks' );
@@ -254,27 +260,27 @@ public function test_inner_blocks() {
$resolved_blocks = $this->instance->resolve_content_blocks( $post, [ 'flat' => false ] );
$this->assertCount( 1, $resolved_blocks, 'There should be only one top-level block (columns).' );
- $this->assertEquals( 'core/columns', $resolved_blocks[0]['blockName'] );
- $this->assertNotEmpty( $resolved_blocks[0]['clientId'], 'The clientId should be set.' );
- $this->assertArrayNotHasKey( 'parentClientId', $resolved_blocks[0], 'The parentClientId should be empty.' );
+ $this->assertEquals( 'core/columns', $resolved_blocks[0]->name, 'The top-level block should be columns.' );
+ $this->assertNotEmpty( $resolved_blocks[0]->clientId, 'The clientId should be set.' );
+ $this->assertEmpty( $resolved_blocks[0]->parentClientId, 'The parentClientId should be empty.' );
- $this->assertCount( 2, $resolved_blocks[0]['innerBlocks'], 'There should be two inner blocks (columns).' );
+ $this->assertCount( 2, $resolved_blocks[0]->innerBlocks, 'There should be two inner blocks (columns).' );
// Check the inner blocks.
- $expected_parent_client_id = $resolved_blocks[0]['clientId'];
+ $expected_parent_client_id = $resolved_blocks[0]->clientId;
- foreach ( $resolved_blocks[0]['innerBlocks'] as $inner_block ) {
- $this->assertEquals( 'core/column', $inner_block['blockName'] );
- $this->assertCount( 2, $inner_block['innerBlocks'], 'There should be two inner blocks (column).' );
- $this->assertNotEmpty( $inner_block['clientId'], 'The clientId should be set.' );
- $this->assertArrayNotHasKey( 'parentClientId', $resolved_blocks[0], 'The parentClientId should only be set when flattening.' ); // @todo This is incorrect, the parentClientId should be set for nested blocks.
+ foreach ( $resolved_blocks[0]->innerBlocks as $inner_block ) {
+ $this->assertEquals( 'core/column', $inner_block->name, 'The inner block should be a column.' );
+ $this->assertCount( 2, $inner_block->innerBlocks, 'There should be two inner blocks (column).' );
+ $this->assertNotEmpty( $inner_block->clientId, 'The clientId should be set.' );
+ $this->assertEmpty( $resolved_blocks[0]->parentClientId, 'The parentClientId should only be set when flattening.' ); // @todo This is incorrect, the parentClientId should be set for nested blocks.
// Check the inner inner blocks.
- $expected_parent_client_id = $inner_block['clientId'];
+ $expected_parent_client_id = $inner_block->clientId;
- foreach ( $inner_block['innerBlocks'] as $inner_inner_block ) {
- $this->assertNotEmpty( $inner_inner_block['clientId'], 'The clientId should be set.' );
- $this->assertArrayNotHasKey( 'parentClientId', $resolved_blocks[0], 'The parentClientId should only be set when flattening.' ); // @todo This is incorrect, the parentClientId should be set for nested blocks.
+ foreach ( $inner_block->innerBlocks as $inner_inner_block ) {
+ $this->assertNotEmpty( $inner_inner_block->clientId, 'The clientId should be set.' );
+ $this->assertEmpty( $resolved_blocks[0]->parentClientId, 'The parentClientId should only be set when flattening.' ); // @todo This is incorrect, the parentClientId should be set for nested blocks.
}
}
@@ -287,40 +293,40 @@ public function test_inner_blocks() {
$this->assertCount( 7, $resolved_blocks, 'There should be five blocks when flattened.' );
// Check the top-level block (columns).
- $this->assertNotEmpty( $resolved_blocks[0]['clientId'], 'The clientId should be set.' );
- $this->assertEqualBlocks( $expected_blocks[0], $resolved_blocks[0], 'The top-level block should match.' );
+ $this->assertNotEmpty( $resolved_blocks[0]->clientId, 'The clientId should be set.' );
+ $this->assertEqualBlockAttributes( $expected_blocks[0]->wpBlock->attributes, $resolved_blocks[0]->wpBlock->attributes, 'The top-level block should match.' );
// Check first inner block (column).
- $expected_parent_client_id = $resolved_blocks[0]['clientId'];
- $this->assertNotEmpty( $resolved_blocks[1]['clientId'], 'The clientId should be set.' );
- $this->assertEquals( $expected_parent_client_id, $resolved_blocks[1]['parentClientId'], 'The parentClientId should match.' );
- $this->assertEqualBlocks( $expected_blocks[0]['innerBlocks'][0], $resolved_blocks[1], 'The first inner block should match.' );
+ $expected_parent_client_id = $resolved_blocks[0]->clientId;
+ $this->assertNotEmpty( $resolved_blocks[1]->clientId, 'The clientId should be set.' );
+ $this->assertEquals( $expected_parent_client_id, $resolved_blocks[1]->parentClientId, 'The parentClientId should match.' );
+ $this->assertEqualBlockAttributes( $expected_blocks[0]->innerBlocks[0]->wpBlock->attributes, $resolved_blocks[1]->wpBlock->attributes, 'The first inner block should match.' );
// Check first inner block children.
- $expected_parent_client_id = $resolved_blocks[1]['clientId'];
- $this->assertNotEmpty( $resolved_blocks[2]['clientId'], 'The clientId should be set.' );
- $this->assertEquals( $expected_parent_client_id, $resolved_blocks[2]['parentClientId'], 'The parentClientId should match.' );
- $this->assertEqualBlocks( $expected_blocks[0]['innerBlocks'][0]['innerBlocks'][0], $resolved_blocks[2], 'The first inner inner block should match.' );
+ $expected_parent_client_id = $resolved_blocks[1]->clientId;
+ $this->assertNotEmpty( $resolved_blocks[2]->clientId, 'The clientId should be set.' );
+ $this->assertEquals( $expected_parent_client_id, $resolved_blocks[2]->parentClientId, 'The parentClientId should match.' );
+ $this->assertEqualBlockAttributes( $expected_blocks[0]->innerBlocks[0]->innerBlocks[0]->wpBlock->attributes, $resolved_blocks[2]->wpBlock->attributes, 'The first inner inner block should match.' );
- $this->assertNotEmpty( $resolved_blocks[3]['clientId'], 'The clientId should be set.' );
- $this->assertEquals( $expected_parent_client_id, $resolved_blocks[3]['parentClientId'], 'The parentClientId should match.' );
- $this->assertEqualBlocks( $expected_blocks[0]['innerBlocks'][0]['innerBlocks'][1], $resolved_blocks[3], 'The second inner inner block should match.' );
+ $this->assertNotEmpty( $resolved_blocks[3]->clientId, 'The clientId should be set.' );
+ $this->assertEquals( $expected_parent_client_id, $resolved_blocks[3]->parentClientId, 'The parentClientId should match.' );
+ $this->assertEqualBlockAttributes( $expected_blocks[0]->innerBlocks[0]->innerBlocks[1]->wpBlock->attributes, $resolved_blocks[3]->wpBlock->attributes, 'The second inner inner block should match.' );
// Check second inner block (column).
- $expected_parent_client_id = $resolved_blocks[0]['clientId'];
- $this->assertNotEmpty( $resolved_blocks[4]['clientId'], 'The clientId should be set.' );
- $this->assertEquals( $expected_parent_client_id, $resolved_blocks[4]['parentClientId'], 'The parentClientId should match.' );
- $this->assertEqualBlocks( $expected_blocks[0]['innerBlocks'][1], $resolved_blocks[4], 'The first inner block should match.' );
+ $expected_parent_client_id = $resolved_blocks[0]->clientId;
+ $this->assertNotEmpty( $resolved_blocks[4]->clientId, 'The clientId should be set.' );
+ $this->assertEquals( $expected_parent_client_id, $resolved_blocks[4]->parentClientId, 'The parentClientId should match.' );
+ $this->assertEqualBlockAttributes( $expected_blocks[0]->innerBlocks[1]->wpBlock->attributes, $resolved_blocks[4]->wpBlock->attributes, 'The first inner block should match.' );
// Check second inner block children.
- $expected_parent_client_id = $resolved_blocks[4]['clientId'];
- $this->assertNotEmpty( $resolved_blocks[5]['clientId'], 'The clientId should be set.' );
- $this->assertEquals( $expected_parent_client_id, $resolved_blocks[5]['parentClientId'], 'The parentClientId should match.' );
- $this->assertEqualBlocks( $expected_blocks[0]['innerBlocks'][1]['innerBlocks'][0], $resolved_blocks[5], 'The first inner inner block should match.' );
-
- $this->assertNotEmpty( $resolved_blocks[6]['clientId'], 'The clientId should be set.' );
- $this->assertEquals( $expected_parent_client_id, $resolved_blocks[6]['parentClientId'], 'The parentClientId should match.' );
- $this->assertEqualBlocks( $expected_blocks[0]['innerBlocks'][1]['innerBlocks'][1], $resolved_blocks[6], 'The second inner inner block should match.' );
+ $expected_parent_client_id = $resolved_blocks[4]->clientId;
+ $this->assertNotEmpty( $resolved_blocks[5]->clientId, 'The clientId should be set.' );
+ $this->assertEquals( $expected_parent_client_id, $resolved_blocks[5]->parentClientId, 'The parentClientId should match.' );
+ $this->assertEqualBlockAttributes( $expected_blocks[0]->innerBlocks[1]->innerBlocks[0]->wpBlock->attributes, $resolved_blocks[5]->wpBlock->attributes, 'The first inner inner block should match.' );
+
+ $this->assertNotEmpty( $resolved_blocks[6]->clientId, 'The clientId should be set.' );
+ $this->assertEquals( $expected_parent_client_id, $resolved_blocks[6]->parentClientId, 'The parentClientId should match.' );
+ $this->assertEqualBlockAttributes( $expected_blocks[0]->innerBlocks[1]->innerBlocks[1]->wpBlock->attributes, $resolved_blocks[6]->wpBlock->attributes, 'The second inner inner block should match.' );
}
/**
@@ -330,7 +336,7 @@ public function test_inner_blocks() {
* @param array $actual The actual block.
* @param string $message The message to display if the assertion fails.
*/
- protected function assertEqualBlocks( $expected, $actual, $message = '' ) {
+ protected function assertEqualBlockAttributes( $expected, $actual, $message = '' ) {
// Remove clientId and parentClientId from comparison.
unset( $expected['clientId'] );
unset( $expected['parentClientId'] );
diff --git a/tests/unit/DOMHelpersTest.php b/tests/unit/DOMHelpersTest.php
index ba2abd4d..5a2cc325 100644
--- a/tests/unit/DOMHelpersTest.php
+++ b/tests/unit/DOMHelpersTest.php
@@ -21,7 +21,6 @@ public function testParseAttribute(): void {
// $html
$this->assertNull( DOMHelpers::parse_attribute( '', $no_existent_selector, $data_attribute ) );
$this->assertNull( DOMHelpers::parse_attribute( $html, $no_existent_selector, $data_attribute ) );
- $this->assertEquals( 'Bar', DOMHelpers::parse_attribute( $html, $no_existent_selector, $data_attribute, 'Bar' ) );
$this->assertEquals( 'foo-data', DOMHelpers::parse_attribute( $html, $id_selector, $data_attribute ) );
$this->assertEquals( 'foo-class', DOMHelpers::parse_attribute( $html, $id_selector, $class_attribute ) );
$this->assertEquals( 'foo-id', DOMHelpers::parse_attribute( $html, $id_selector, $id_attribute ) );
@@ -36,7 +35,6 @@ public function testParseAttribute(): void {
$this->assertEquals( 'center', DOMHelpers::parse_attribute( $html2, '*', 'data-align' ) );
$this->assertEquals( 'right', DOMHelpers::parse_attribute( $html2, '.has-text-align-right', 'data-align' ) );
$this->assertNull( DOMHelpers::parse_attribute( $html2, '.non-existent-class', 'data-align' ) );
- $this->assertEquals( 'default', DOMHelpers::parse_attribute( $html2, '.non-existent-class', 'data-align', 'default' ) );
// $htm3
$this->assertEquals( 'left', DOMHelpers::parse_attribute( $html3, 'span', 'data-align' ) );