From 0786756649b9afd92756fb954e5ef41a89b84f31 Mon Sep 17 00:00:00 2001 From: Sarah Norris Date: Wed, 30 Jul 2025 12:19:47 +0100 Subject: [PATCH 1/3] Add server side rendering for Terms Query and Term Template blocks --- src/wp-includes/blocks/term-template.php | 180 +++++++++++++++++++++++ src/wp-includes/blocks/terms-query.php | 48 ++++++ 2 files changed, 228 insertions(+) create mode 100644 src/wp-includes/blocks/term-template.php create mode 100644 src/wp-includes/blocks/terms-query.php diff --git a/src/wp-includes/blocks/term-template.php b/src/wp-includes/blocks/term-template.php new file mode 100644 index 0000000000000..5d3ae5c79c361 --- /dev/null +++ b/src/wp-includes/blocks/term-template.php @@ -0,0 +1,180 @@ +context ) || ! isset( $attributes ) ) { + return ''; + } + + $query_block_context = $block->context; + + if ( empty( $query_block_context['termQuery'] ) ) { + return ''; + } + + $query = $query_block_context['termQuery']; + + $query_args = array( + 'per_page' => $query['perPage'] ?? 100, + 'page' => $query['pages'] ?? 1, + 'taxonomy' => $query['taxonomy'] ?? 'category', + 'order' => $query['order'] ?? 'asc', + 'orderby' => $query['orderBy'] ?? 'name', + 'hide_empty' => $query['hideEmpty'] ?? true, + 'include' => $query['include'] ?? array(), + 'exclude' => $query['exclude'] ?? array(), + ); + + $terms_query = new WP_Term_Query( $query_args ); + $terms = $terms_query->get_terms(); + + if ( ! $terms || is_wp_error( $terms ) ) { + return ''; + } + + // Handle parent. + if ( isset( $query['parent'] ) ) { + $terms = array_filter( + $terms, + function ( $term ) use ( $query ) { + return $term->parent === $query['parent']; + } + ); + } + + // Handle hierarchical list. + $is_hierarchical = ! empty( $query['hierarchical'] ); + + if ( ! $is_hierarchical && isset( $query['parent'] ) ) { + $query_args['parent'] = $query['parent']; + } + + if ( $is_hierarchical ) { + $content = render_block_core_term_template_hierarchical( $terms, $block ); + } else { + $content = render_block_core_term_template_flat( $terms, $block ); + } + + return sprintf( + '', + $content + ); +} + +/** + * Renders terms in a flat list structure. + * + * @since 6.9.0 + * + * @param array $terms Array of WP_Term objects. + * @param WP_Block $block Block instance. + * + * @return string HTML content for flat terms list. + */ +function render_block_core_term_template_flat( $terms, $block ) { + $content = ''; + foreach ( $terms as $term ) { + $content .= render_block_core_term_template_single( $term, $block ); + } + return $content; +} + +/** + * Renders terms in a hierarchical structure. + * + * @since 6.9.0 + * + * @param array $terms Array of WP_Term objects. + * @param WP_Block $block Block instance. + * @param int $parent_id Parent term ID (0 for top-level). + * + * @return string HTML content for hierarchical terms list. + */ +function render_block_core_term_template_hierarchical( $terms, $block, $parent_id = 0 ) { + $content = ''; + + // Filter terms for current parent. + $child_terms = array_filter( + $terms, + function ( $term ) use ( $parent_id ) { + return $term->parent === $parent_id; + } + ); + + foreach ( $child_terms as $term ) { + $term_content = render_block_core_term_template_single( $term, $block ); + + $children_content = render_block_core_term_template_hierarchical( $terms, $block, $term->term_id ); + + if ( ! empty( $children_content ) ) { + $term_content = str_replace( '', '', $term_content ); + } + + $content .= $term_content; + } + + return $content; +} + +/** + * Renders a single term with its inner blocks. + * + * @since 6.9.0 + * + * @param WP_Term $term Term object. + * @param WP_Block $block Block instance. + * + * @return string HTML content for a single term. + */ +function render_block_core_term_template_single( $term, $block ) { + $inner_blocks = $block->inner_blocks; + $block_content = ''; + + if ( ! empty( $inner_blocks ) ) { + $term_id = $term->term_id; + $taxonomy = $term->taxonomy; + + foreach ( $inner_blocks as $inner_block ) { + $inner_block->context['termId'] = $term_id; + $inner_block->context['taxonomy'] = $taxonomy; + + $block_content .= $inner_block->render( array( 'dynamic' => true ) ); + } + } + + $term_classes = implode( ' ', array( 'wp-block-term', 'term-' . $term->term_id ) ); + + $term_name = esc_html( $term->name ); + + return '
  • ' . $term_name . $block_content . '
  • '; +} + +/** + * Registers the `core/term-template` block on the server. + * + * @since 6.9.0 + */ +function register_block_core_term_template() { + register_block_type_from_metadata( + __DIR__ . '/term-template', + array( + 'render_callback' => 'render_block_core_term_template', + ) + ); +} +add_action( 'init', 'register_block_core_term_template' ); diff --git a/src/wp-includes/blocks/terms-query.php b/src/wp-includes/blocks/terms-query.php new file mode 100644 index 0000000000000..c8175222873a8 --- /dev/null +++ b/src/wp-includes/blocks/terms-query.php @@ -0,0 +1,48 @@ + $classnames ) ); + + return sprintf( + '
    %2$s
    ', + $wrapper_attributes, + $content + ); +} + + +/** + * Registers the `core/terms-query` block on the server. + * + * @since 6.9.0 + */ +function register_block_core_terms_query() { + register_block_type_from_metadata( + __DIR__ . '/terms-query', + array( + 'render_callback' => 'render_block_core_terms_query', + ) + ); +} +add_action( 'init', 'register_block_core_terms_query' ); From 8393b48a6da6aaea604e5f8e447b894e414c4cee Mon Sep 17 00:00:00 2001 From: Sarah Norris Date: Wed, 17 Sep 2025 12:18:18 +0100 Subject: [PATCH 2/3] Remove block files --- src/wp-includes/blocks/term-template.php | 180 ----------------------- src/wp-includes/blocks/terms-query.php | 48 ------ 2 files changed, 228 deletions(-) delete mode 100644 src/wp-includes/blocks/term-template.php delete mode 100644 src/wp-includes/blocks/terms-query.php diff --git a/src/wp-includes/blocks/term-template.php b/src/wp-includes/blocks/term-template.php deleted file mode 100644 index 5d3ae5c79c361..0000000000000 --- a/src/wp-includes/blocks/term-template.php +++ /dev/null @@ -1,180 +0,0 @@ -context ) || ! isset( $attributes ) ) { - return ''; - } - - $query_block_context = $block->context; - - if ( empty( $query_block_context['termQuery'] ) ) { - return ''; - } - - $query = $query_block_context['termQuery']; - - $query_args = array( - 'per_page' => $query['perPage'] ?? 100, - 'page' => $query['pages'] ?? 1, - 'taxonomy' => $query['taxonomy'] ?? 'category', - 'order' => $query['order'] ?? 'asc', - 'orderby' => $query['orderBy'] ?? 'name', - 'hide_empty' => $query['hideEmpty'] ?? true, - 'include' => $query['include'] ?? array(), - 'exclude' => $query['exclude'] ?? array(), - ); - - $terms_query = new WP_Term_Query( $query_args ); - $terms = $terms_query->get_terms(); - - if ( ! $terms || is_wp_error( $terms ) ) { - return ''; - } - - // Handle parent. - if ( isset( $query['parent'] ) ) { - $terms = array_filter( - $terms, - function ( $term ) use ( $query ) { - return $term->parent === $query['parent']; - } - ); - } - - // Handle hierarchical list. - $is_hierarchical = ! empty( $query['hierarchical'] ); - - if ( ! $is_hierarchical && isset( $query['parent'] ) ) { - $query_args['parent'] = $query['parent']; - } - - if ( $is_hierarchical ) { - $content = render_block_core_term_template_hierarchical( $terms, $block ); - } else { - $content = render_block_core_term_template_flat( $terms, $block ); - } - - return sprintf( - '', - $content - ); -} - -/** - * Renders terms in a flat list structure. - * - * @since 6.9.0 - * - * @param array $terms Array of WP_Term objects. - * @param WP_Block $block Block instance. - * - * @return string HTML content for flat terms list. - */ -function render_block_core_term_template_flat( $terms, $block ) { - $content = ''; - foreach ( $terms as $term ) { - $content .= render_block_core_term_template_single( $term, $block ); - } - return $content; -} - -/** - * Renders terms in a hierarchical structure. - * - * @since 6.9.0 - * - * @param array $terms Array of WP_Term objects. - * @param WP_Block $block Block instance. - * @param int $parent_id Parent term ID (0 for top-level). - * - * @return string HTML content for hierarchical terms list. - */ -function render_block_core_term_template_hierarchical( $terms, $block, $parent_id = 0 ) { - $content = ''; - - // Filter terms for current parent. - $child_terms = array_filter( - $terms, - function ( $term ) use ( $parent_id ) { - return $term->parent === $parent_id; - } - ); - - foreach ( $child_terms as $term ) { - $term_content = render_block_core_term_template_single( $term, $block ); - - $children_content = render_block_core_term_template_hierarchical( $terms, $block, $term->term_id ); - - if ( ! empty( $children_content ) ) { - $term_content = str_replace( '', '', $term_content ); - } - - $content .= $term_content; - } - - return $content; -} - -/** - * Renders a single term with its inner blocks. - * - * @since 6.9.0 - * - * @param WP_Term $term Term object. - * @param WP_Block $block Block instance. - * - * @return string HTML content for a single term. - */ -function render_block_core_term_template_single( $term, $block ) { - $inner_blocks = $block->inner_blocks; - $block_content = ''; - - if ( ! empty( $inner_blocks ) ) { - $term_id = $term->term_id; - $taxonomy = $term->taxonomy; - - foreach ( $inner_blocks as $inner_block ) { - $inner_block->context['termId'] = $term_id; - $inner_block->context['taxonomy'] = $taxonomy; - - $block_content .= $inner_block->render( array( 'dynamic' => true ) ); - } - } - - $term_classes = implode( ' ', array( 'wp-block-term', 'term-' . $term->term_id ) ); - - $term_name = esc_html( $term->name ); - - return '
  • ' . $term_name . $block_content . '
  • '; -} - -/** - * Registers the `core/term-template` block on the server. - * - * @since 6.9.0 - */ -function register_block_core_term_template() { - register_block_type_from_metadata( - __DIR__ . '/term-template', - array( - 'render_callback' => 'render_block_core_term_template', - ) - ); -} -add_action( 'init', 'register_block_core_term_template' ); diff --git a/src/wp-includes/blocks/terms-query.php b/src/wp-includes/blocks/terms-query.php deleted file mode 100644 index c8175222873a8..0000000000000 --- a/src/wp-includes/blocks/terms-query.php +++ /dev/null @@ -1,48 +0,0 @@ - $classnames ) ); - - return sprintf( - '
    %2$s
    ', - $wrapper_attributes, - $content - ); -} - - -/** - * Registers the `core/terms-query` block on the server. - * - * @since 6.9.0 - */ -function register_block_core_terms_query() { - register_block_type_from_metadata( - __DIR__ . '/terms-query', - array( - 'render_callback' => 'render_block_core_terms_query', - ) - ); -} -add_action( 'init', 'register_block_core_terms_query' ); From a105ca99cc4b961cc289358a523083cab00f9b78 Mon Sep 17 00:00:00 2001 From: Sarah Norris Date: Wed, 17 Sep 2025 12:22:27 +0100 Subject: [PATCH 3/3] Add term data source --- src/wp-includes/block-bindings/term-data.php | 96 ++++++++++++++++++++ src/wp-settings.php | 1 + 2 files changed, 97 insertions(+) create mode 100644 src/wp-includes/block-bindings/term-data.php diff --git a/src/wp-includes/block-bindings/term-data.php b/src/wp-includes/block-bindings/term-data.php new file mode 100644 index 0000000000000..94f1002d72e30 --- /dev/null +++ b/src/wp-includes/block-bindings/term-data.php @@ -0,0 +1,96 @@ + "name" ). + * @param WP_Block $block_instance The block instance. + * @return mixed The value computed for the source. + */ +function gutenberg_block_bindings_term_data_get_value( array $source_args, $block_instance ) { + if ( empty( $source_args['key'] ) ) { + return null; + } + + if ( empty( $block_instance->context['termId'] ) || empty( $block_instance->context['taxonomy'] ) ) { + return null; + } + + $term_id = $block_instance->context['termId']; + $taxonomy = $block_instance->context['taxonomy']; + + // Get the term data. + $term = get_term( $term_id, $taxonomy ); + if ( is_wp_error( $term ) || ! $term ) { + return null; + } + + // Check if taxonomy exists and is publicly queryable. + $taxonomy_object = get_taxonomy( $taxonomy ); + if ( ! $taxonomy_object || ! $taxonomy_object->publicly_queryable ) { + if ( ! current_user_can( 'read' ) ) { + return null; + } + } + + switch ( $source_args['key'] ) { + case 'id': + return esc_html( (string) $term_id ); + + case 'name': + return esc_html( $term->name ); + + case 'link': + return esc_url( get_term_link( $term ) ); + + case 'slug': + return esc_html( $term->slug ); + + case 'description': + return wp_kses_post( $term->description ); + + case 'parent': + return esc_html( (string) $term->parent ); + + case 'count': + return esc_html( (string) '(' . $term->count . ')' ); + + default: + return null; + } +} + +/** + * Registers Term Data source in the block bindings registry. + * + * @since 6.9.0 + * @access private + */ +function gutenberg_register_block_bindings_term_data_source() { + if ( get_block_bindings_source( 'core/term-data' ) ) { + // The source is already registered. + return; + } + + register_block_bindings_source( + 'core/term-data', + array( + 'label' => _x( 'Term Data', 'block bindings source' ), + 'get_value_callback' => 'gutenberg_block_bindings_term_data_get_value', + 'uses_context' => array( 'termId', 'taxonomy' ), + ) + ); +} + +add_action( 'init', 'gutenberg_register_block_bindings_term_data_source' ); diff --git a/src/wp-settings.php b/src/wp-settings.php index 60ffc307c5f6e..90503179466c2 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -365,6 +365,7 @@ require ABSPATH . WPINC . '/block-bindings.php'; require ABSPATH . WPINC . '/block-bindings/pattern-overrides.php'; require ABSPATH . WPINC . '/block-bindings/post-meta.php'; +require ABSPATH . WPINC . '/block-bindings/term-data.php'; require ABSPATH . WPINC . '/blocks.php'; require ABSPATH . WPINC . '/blocks/index.php'; require ABSPATH . WPINC . '/block-editor.php';