From ce0f13f7ce930088901200ab9c17402f6c4d3372 Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Wed, 1 Nov 2023 20:44:50 +0200 Subject: [PATCH 01/16] adds a new field in wp_navigation rest response that shows all template parts that use the current wp_navigation --- lib/init.php | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/lib/init.php b/lib/init.php index 88dcba4525f6e..a1a9c6408d14f 100644 --- a/lib/init.php +++ b/lib/init.php @@ -57,3 +57,45 @@ function gutenberg_menu() { ); } add_action( 'admin_menu', 'gutenberg_menu', 9 ); + +function recursively_find_block_by_attribute( $blocks, $block_name, $attribute_name, $attribute_value, $found_blocks = array() ) { + foreach ( $blocks as $block ) { + if ( $block['blockName'] === 'core/navigation' &&isset($block['attrs'][ $attribute_name ]) && $block['attrs'][ $attribute_name ] === $attribute_value ) { + $found_blocks[] = $block; + } + if ( $block['innerBlocks'] ) { + $found_blocks = array_merge( $found_blocks, recursively_find_block_by_attribute( $block['innerBlocks'], $block_name, $attribute_name, $attribute_value, $found_blocks) ); + } + } + return $found_blocks; +} + + +function get_template_parts_that_use_menu( $wp_navigation_id ) { + // get all wp_template_part posts + $wp_template_part_posts = get_posts( array( + 'post_type' => 'wp_template_part', + 'posts_per_page' => -1, + ) ); + // loop through them and find the ones that have a navigation block + // with the ref attribute set to $wp_navigation_id + $wp_template_part_posts_with_navigation = array(); + foreach ( $wp_template_part_posts as $wp_template_part_post ) { + $wp_template_part_blocks = parse_blocks( $wp_template_part_post->post_content ); + $found_avigation = count( recursively_find_block_by_attribute( $wp_template_part_blocks, 'core/navigation', 'ref', $wp_navigation_id) ) > 0; + if( $found_avigation ) { + $wp_template_part_posts_with_navigation[] = $wp_template_part_post->ID; + } + } + return $wp_template_part_posts_with_navigation; +} + +// register a rest field for posts that returns the template parts that use the menu +function register_template_parts_that_use_menu_field() { + register_rest_field( 'wp_navigation', 'template_parts_that_use_menu', array( + 'get_callback' => function( $post ) { + return get_template_parts_that_use_menu( $post['id'] ); + }, + ) ); +} +add_action( 'rest_api_init', 'register_template_parts_that_use_menu_field' ); From e0453838b8d56e56bc29c2311d9c26de997fdd98 Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Wed, 1 Nov 2023 21:06:01 +0200 Subject: [PATCH 02/16] limit the new field to edit context only --- lib/init.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/init.php b/lib/init.php index a1a9c6408d14f..741858fc39c94 100644 --- a/lib/init.php +++ b/lib/init.php @@ -96,6 +96,10 @@ function register_template_parts_that_use_menu_field() { 'get_callback' => function( $post ) { return get_template_parts_that_use_menu( $post['id'] ); }, + 'schema' => array( + 'type' => 'array', + 'context' => array( 'edit' ), + ), ) ); } add_action( 'rest_api_init', 'register_template_parts_that_use_menu_field' ); From 203c46ceddb076c52600eb8f14a2e66d7f2c0a2a Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Wed, 1 Nov 2023 22:30:50 +0200 Subject: [PATCH 03/16] show in parts where menu is used in UI --- .../single-navigation-menu.js | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/single-navigation-menu.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/single-navigation-menu.js index 960e0363f2e58..a747fc79c55ef 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/single-navigation-menu.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/single-navigation-menu.js @@ -3,14 +3,57 @@ */ import { __ } from '@wordpress/i18n'; import { decodeEntities } from '@wordpress/html-entities'; +import { + __experimentalItemGroup as ItemGroup, + __experimentalTruncate as Truncate, +} from '@wordpress/components'; +import { header, footer, layout } from '@wordpress/icons'; +import { useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies */ +import { + SidebarNavigationScreenDetailsPanel, + SidebarNavigationScreenDetailsPanelRow, +} from '../sidebar-navigation-screen-details-panel'; +import SidebarNavigationItem from '../sidebar-navigation-item'; import { SidebarNavigationScreenWrapper } from '../sidebar-navigation-screen-navigation-menus'; import ScreenNavigationMoreMenu from './more-menu'; import NavigationMenuEditor from './navigation-menu-editor'; import buildNavigationLabel from '../sidebar-navigation-screen-navigation-menus/build-navigation-label'; import EditButton from './edit-button'; +import { useLink } from '../routes/link'; +import { TEMPLATE_PART_POST_TYPE } from '../../utils/constants'; + +function TemplateAreaButton( { postId, icon, title } ) { + const icons = { + header, + footer, + }; + const linkInfo = useLink( { + postType: TEMPLATE_PART_POST_TYPE, + postId, + } ); + + return ( + + + { decodeEntities( title ) } + + + ); +} export default function SingleNavigationMenu( { navigationMenu, @@ -20,6 +63,22 @@ export default function SingleNavigationMenu( { } ) { const menuTitle = navigationMenu?.title?.rendered; + const templatePartsIds = navigationMenu?.template_parts_that_use_menu ?? []; + + const templateParts = useSelect( + ( select ) => + select( coreStore ).getEntityRecords( + 'postType', + TEMPLATE_PART_POST_TYPE + ), + [] + ); + + const templatePartsData = + templateParts?.filter( ( templatePart ) => + templatePartsIds.includes( templatePart.wp_id ) + ) ?? []; + return ( + + { templatePartsData.length > 0 && ( + + + { templatePartsData.map( + ( { wp_id: wpId, theme, slug, title } ) => ( + + + + ) + ) } + + + ) } ); } From 97edfa9921335b3c6ee38d17c7cd5cad6801736d Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Wed, 1 Nov 2023 22:48:26 +0200 Subject: [PATCH 04/16] lint PHP --- lib/init.php | 142 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 93 insertions(+), 49 deletions(-) diff --git a/lib/init.php b/lib/init.php index 741858fc39c94..73da81bd1e10f 100644 --- a/lib/init.php +++ b/lib/init.php @@ -1,4 +1,5 @@ 'wp_template_part', - 'posts_per_page' => -1, - ) ); - // loop through them and find the ones that have a navigation block - // with the ref attribute set to $wp_navigation_id - $wp_template_part_posts_with_navigation = array(); - foreach ( $wp_template_part_posts as $wp_template_part_post ) { - $wp_template_part_blocks = parse_blocks( $wp_template_part_post->post_content ); - $found_avigation = count( recursively_find_block_by_attribute( $wp_template_part_blocks, 'core/navigation', 'ref', $wp_navigation_id) ) > 0; - if( $found_avigation ) { - $wp_template_part_posts_with_navigation[] = $wp_template_part_post->ID; - } +/** + * Get all template parts that use a menu. + * + * @param mixed $wp_navigation_id + * + * @return array template parts that use the menu + */ +if (!function_exists('get_template_parts_that_use_menu')) { + function get_template_parts_that_use_menu($wp_navigation_id) + { + // get all wp_template_part posts + $wp_template_part_posts = get_posts(array( + 'post_type' => 'wp_template_part', + 'posts_per_page' => -1, + )); + // loop through them and find the ones that have a navigation block + // with the ref attribute set to $wp_navigation_id + $wp_template_part_posts_with_navigation = array(); + foreach ($wp_template_part_posts as $wp_template_part_post) { + $wp_template_part_blocks = parse_blocks($wp_template_part_post->post_content); + $found_avigation = count( + recursively_find_block_by_attribute( + $wp_template_part_blocks, + 'core/navigation', + 'ref', + $wp_navigation_id + ) + ) > 0; + if ($found_avigation) { + $wp_template_part_posts_with_navigation[] = $wp_template_part_post->ID; + } + } + return $wp_template_part_posts_with_navigation; } - return $wp_template_part_posts_with_navigation; -} +}; // register a rest field for posts that returns the template parts that use the menu -function register_template_parts_that_use_menu_field() { - register_rest_field( 'wp_navigation', 'template_parts_that_use_menu', array( - 'get_callback' => function( $post ) { - return get_template_parts_that_use_menu( $post['id'] ); +function register_template_parts_that_use_menu_field() +{ + register_rest_field('wp_navigation', 'template_parts_that_use_menu', array( + 'get_callback' => function ($post) { + return get_template_parts_that_use_menu($post['id']); }, - 'schema' => array( - 'type' => 'array', - 'context' => array( 'edit' ), - ), - ) ); + 'schema' => array( + 'type' => 'array', + 'context' => array('edit'), + ), + )); } -add_action( 'rest_api_init', 'register_template_parts_that_use_menu_field' ); +add_action('rest_api_init', 'register_template_parts_that_use_menu_field'); From b379469db6a4392dc78ccfd9503c07aafbd2b934 Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Wed, 1 Nov 2023 23:29:43 +0200 Subject: [PATCH 05/16] remove hardcoded block name --- lib/init.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/init.php b/lib/init.php index 73da81bd1e10f..3480695abaed8 100644 --- a/lib/init.php +++ b/lib/init.php @@ -75,7 +75,7 @@ function recursively_find_block_by_attribute($blocks, $block_name, $attribute_na { foreach ($blocks as $block) { if ( - $block['blockName'] === 'core/navigation' && + $block['blockName'] === $block_name && isset($block['attrs'][$attribute_name]) && $attribute_value === $block['attrs'][$attribute_name] ) { From f2370b309a159063e65dfc409c221ddd99aa786a Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Thu, 2 Nov 2023 00:02:13 +0200 Subject: [PATCH 06/16] lint again --- lib/init.php | 169 +++++++++++++++++++++++++++------------------------ 1 file changed, 88 insertions(+), 81 deletions(-) diff --git a/lib/init.php b/lib/init.php index 3480695abaed8..b1da4fbdae1b7 100644 --- a/lib/init.php +++ b/lib/init.php @@ -1,5 +1,4 @@ 'wp_template_part', - 'posts_per_page' => -1, - )); - // loop through them and find the ones that have a navigation block - // with the ref attribute set to $wp_navigation_id +if ( !function_exists( 'get_template_parts_that_use_menu' ) ) { + /** + * Get all template parts that use a menu. + * + * @param int $wp_navigation_id The menu id. + * + * @return array template parts that use the menu + */ + function get_template_parts_that_use_menu( $wp_navigation_id ) { + // get all wp_template_part posts. + $wp_template_part_posts = get_posts( + array( + 'post_type' => 'wp_template_part', + 'posts_per_page' => -1, + ) + ); + // loop through them and find the ones that have a navigation block, + // with the ref attribute set to $wp_navigation_id. $wp_template_part_posts_with_navigation = array(); - foreach ($wp_template_part_posts as $wp_template_part_post) { - $wp_template_part_blocks = parse_blocks($wp_template_part_post->post_content); + foreach ( $wp_template_part_posts as $wp_template_part_post ) { + $wp_template_part_blocks = parse_blocks( $wp_template_part_post->post_content ); $found_avigation = count( recursively_find_block_by_attribute( $wp_template_part_blocks, @@ -125,25 +125,32 @@ function get_template_parts_that_use_menu($wp_navigation_id) $wp_navigation_id ) ) > 0; - if ($found_avigation) { + if ( $found_avigation ) { $wp_template_part_posts_with_navigation[] = $wp_template_part_post->ID; } } return $wp_template_part_posts_with_navigation; } -}; +} -// register a rest field for posts that returns the template parts that use the menu -function register_template_parts_that_use_menu_field() -{ - register_rest_field('wp_navigation', 'template_parts_that_use_menu', array( - 'get_callback' => function ($post) { - return get_template_parts_that_use_menu($post['id']); - }, - 'schema' => array( - 'type' => 'array', - 'context' => array('edit'), - ), - )); +if ( ! function_exists( 'register_template_parts_that_use_menu_field' ) ) { + /** + * Register a rest field for posts that returns the template parts that use the menu. + */ + function register_template_parts_that_use_menu_field() { + register_rest_field( + 'wp_navigation', + 'template_parts_that_use_menu', + array( + 'get_callback' => function ( $post ) { + return get_template_parts_that_use_menu( $post['id'] ); + }, + 'schema' => array( + 'type' => 'array', + 'context' => array( 'edit' ), + ), + ) + ); + } } -add_action('rest_api_init', 'register_template_parts_that_use_menu_field'); +add_action( 'rest_api_init', 'register_template_parts_that_use_menu_field' ); From 005e8cb36127c12cedc144ed2a6bb6da3e6c226d Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Thu, 2 Nov 2023 10:35:44 +0200 Subject: [PATCH 07/16] more lint --- lib/init.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/init.php b/lib/init.php index b1da4fbdae1b7..0ab4ec293be76 100644 --- a/lib/init.php +++ b/lib/init.php @@ -96,7 +96,7 @@ function recursively_find_block_by_attribute( $blocks, $block_name, $attribute_n } } -if ( !function_exists( 'get_template_parts_that_use_menu' ) ) { +if ( ! function_exists( 'get_template_parts_that_use_menu' ) ) { /** * Get all template parts that use a menu. * @@ -145,7 +145,7 @@ function register_template_parts_that_use_menu_field() { 'get_callback' => function ( $post ) { return get_template_parts_that_use_menu( $post['id'] ); }, - 'schema' => array( + 'schema' => array( 'type' => 'array', 'context' => array( 'edit' ), ), From 52208e009fba417292e3b5895f6c57fd5f821750 Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Mon, 6 Nov 2023 08:31:23 +0200 Subject: [PATCH 08/16] more linting --- lib/init.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/init.php b/lib/init.php index 0ab4ec293be76..016e25bd26379 100644 --- a/lib/init.php +++ b/lib/init.php @@ -117,7 +117,7 @@ function get_template_parts_that_use_menu( $wp_navigation_id ) { $wp_template_part_posts_with_navigation = array(); foreach ( $wp_template_part_posts as $wp_template_part_post ) { $wp_template_part_blocks = parse_blocks( $wp_template_part_post->post_content ); - $found_avigation = count( + $found_avigation = count( recursively_find_block_by_attribute( $wp_template_part_blocks, 'core/navigation', From 4d9ffacaffba61c56092fba53cfaeb2851c87090 Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Fri, 24 Nov 2023 19:02:29 +0200 Subject: [PATCH 09/16] test block search controller --- .../class-block-search-controller.php | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 lib/compat/wordpress-6.5/class-block-search-controller.php diff --git a/lib/compat/wordpress-6.5/class-block-search-controller.php b/lib/compat/wordpress-6.5/class-block-search-controller.php new file mode 100644 index 0000000000000..012df06aa71ed --- /dev/null +++ b/lib/compat/wordpress-6.5/class-block-search-controller.php @@ -0,0 +1,171 @@ +namespace = 'wp/v2'; + $this->rest_base = 'block_search'; + } + + /** + * Registers the routes for the objects of the controller. + */ + public function register_routes() { + register_rest_route( + $this->namespace, + '/' . $this->rest_base, + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( + $this, + 'find_posts_by_block_attribute', + ), + 'args' => array( + 'post_type' => array( + 'required' => true, + ), + 'block_name' => array( + 'required' => true, + ), + 'attribute_name' => array( + 'required' => true, + ), + 'attribute_value' => array( + 'required' => true, + ), + 'posts_per_page' => array( + 'required' => false, + ), + ), + 'permission_callback' => array( $this, 'permissions_check' ), + ), + ) + ); + } + + /** + * Permission callback to check if the user can use this endpoint. + */ + public function permissions_check() { + // Check user permissions. + return true; + } + + /** + * The callback for the find_posts_by_block route. + * + * @param WP_REST_Request $request The request object. + */ + public function find_posts_by_block_attribute( $request ) { + $post_type = $request['post_type']; + $block_name = $request['block_name']; + $attribute_name = $request['attribute_name']; + $attribute_value = $request['attribute_value']; + + $posts_per_page = apply_filters( + 'block_search_posts_per_page', + $request['posts_per_page'] ?? get_option( 'posts_per_page' ), + $request + ); + + $paged = 1; // Start on the first page. + $posts_with_block = array(); + + do { + $posts = get_posts( + array( + 'post_type' => $post_type, + 'posts_per_page' => $posts_per_page, + 'paged' => $paged, + 'post_status' => 'publish', + ) + ); + + if ( empty( $posts ) ) { + break; + } + + foreach ( $posts as $post ) { + if ( count( $posts_with_block ) >= $posts_per_page ) { + break 2; + } + + $blocks = parse_blocks( $post->post_content ); + $found_blocks = $this->recursively_find_block_by_attribute( + $blocks, + $block_name, + $attribute_name, + $attribute_value + ); + + if ( ! empty( $found_blocks ) ) { + $posts_with_block[] = $post->ID; + } + } + + ++$paged; + + } while ( count( $posts_with_block ) < $posts_per_page ); + + return new WP_REST_Response( $posts_with_block, 200 ); + } + + + /** + * Helper method to recursively find blocks by an attribute. + * + * @param array $blocks The blocks to search. + * @param string $block_name The block name to search for. + * @param string $attribute_name The attribute name to search for. + * @param string $attribute_value The attribute value to search for. + * @param array $found_blocks The found blocks. + * + * @return array The found blocks. + */ + private function recursively_find_block_by_attribute( $blocks, $block_name, $attribute_name, $attribute_value, $found_blocks = array() ) { + foreach ( $blocks as $block ) { + if ( + $block['blockName'] === $block_name && + isset( $block['attrs'][ $attribute_name ] ) && + $block['attrs'][ $attribute_name ] === $attribute_value + ) { + $found_blocks[] = $block; + } + if ( ! empty( $block['innerBlocks'] ) ) { + $found_blocks = array_merge( + $found_blocks, + $this->recursively_find_block_by_attribute( + $block['innerBlocks'], + $block_name, + $attribute_name, + $attribute_value, + $found_blocks + ) + ); + } + } + return $found_blocks; + } + } +} + +add_action( + 'rest_api_init', + function () { + $controller = new Block_Search_Controller(); + $controller->register_routes(); + } +); From 6974af20dfe8cd438dbcb9ed7363532ab62356b1 Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Mon, 15 Jan 2024 17:05:33 +0200 Subject: [PATCH 10/16] remove the block search controller until we figure out a better way --- .../class-block-search-controller.php | 171 ------------------ 1 file changed, 171 deletions(-) delete mode 100644 lib/compat/wordpress-6.5/class-block-search-controller.php diff --git a/lib/compat/wordpress-6.5/class-block-search-controller.php b/lib/compat/wordpress-6.5/class-block-search-controller.php deleted file mode 100644 index 012df06aa71ed..0000000000000 --- a/lib/compat/wordpress-6.5/class-block-search-controller.php +++ /dev/null @@ -1,171 +0,0 @@ -namespace = 'wp/v2'; - $this->rest_base = 'block_search'; - } - - /** - * Registers the routes for the objects of the controller. - */ - public function register_routes() { - register_rest_route( - $this->namespace, - '/' . $this->rest_base, - array( - array( - 'methods' => WP_REST_Server::READABLE, - 'callback' => array( - $this, - 'find_posts_by_block_attribute', - ), - 'args' => array( - 'post_type' => array( - 'required' => true, - ), - 'block_name' => array( - 'required' => true, - ), - 'attribute_name' => array( - 'required' => true, - ), - 'attribute_value' => array( - 'required' => true, - ), - 'posts_per_page' => array( - 'required' => false, - ), - ), - 'permission_callback' => array( $this, 'permissions_check' ), - ), - ) - ); - } - - /** - * Permission callback to check if the user can use this endpoint. - */ - public function permissions_check() { - // Check user permissions. - return true; - } - - /** - * The callback for the find_posts_by_block route. - * - * @param WP_REST_Request $request The request object. - */ - public function find_posts_by_block_attribute( $request ) { - $post_type = $request['post_type']; - $block_name = $request['block_name']; - $attribute_name = $request['attribute_name']; - $attribute_value = $request['attribute_value']; - - $posts_per_page = apply_filters( - 'block_search_posts_per_page', - $request['posts_per_page'] ?? get_option( 'posts_per_page' ), - $request - ); - - $paged = 1; // Start on the first page. - $posts_with_block = array(); - - do { - $posts = get_posts( - array( - 'post_type' => $post_type, - 'posts_per_page' => $posts_per_page, - 'paged' => $paged, - 'post_status' => 'publish', - ) - ); - - if ( empty( $posts ) ) { - break; - } - - foreach ( $posts as $post ) { - if ( count( $posts_with_block ) >= $posts_per_page ) { - break 2; - } - - $blocks = parse_blocks( $post->post_content ); - $found_blocks = $this->recursively_find_block_by_attribute( - $blocks, - $block_name, - $attribute_name, - $attribute_value - ); - - if ( ! empty( $found_blocks ) ) { - $posts_with_block[] = $post->ID; - } - } - - ++$paged; - - } while ( count( $posts_with_block ) < $posts_per_page ); - - return new WP_REST_Response( $posts_with_block, 200 ); - } - - - /** - * Helper method to recursively find blocks by an attribute. - * - * @param array $blocks The blocks to search. - * @param string $block_name The block name to search for. - * @param string $attribute_name The attribute name to search for. - * @param string $attribute_value The attribute value to search for. - * @param array $found_blocks The found blocks. - * - * @return array The found blocks. - */ - private function recursively_find_block_by_attribute( $blocks, $block_name, $attribute_name, $attribute_value, $found_blocks = array() ) { - foreach ( $blocks as $block ) { - if ( - $block['blockName'] === $block_name && - isset( $block['attrs'][ $attribute_name ] ) && - $block['attrs'][ $attribute_name ] === $attribute_value - ) { - $found_blocks[] = $block; - } - if ( ! empty( $block['innerBlocks'] ) ) { - $found_blocks = array_merge( - $found_blocks, - $this->recursively_find_block_by_attribute( - $block['innerBlocks'], - $block_name, - $attribute_name, - $attribute_value, - $found_blocks - ) - ); - } - } - return $found_blocks; - } - } -} - -add_action( - 'rest_api_init', - function () { - $controller = new Block_Search_Controller(); - $controller->register_routes(); - } -); From bf8dcdbcd72d392789bcf91f20af020eee783631 Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Wed, 17 Jan 2024 11:52:47 +0200 Subject: [PATCH 11/16] seek through the document as a string instead of parsing document blocks --- lib/init.php | 100 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 39 deletions(-) diff --git a/lib/init.php b/lib/init.php index 016e25bd26379..997c8745b5c4b 100644 --- a/lib/init.php +++ b/lib/init.php @@ -58,44 +58,69 @@ function gutenberg_menu() { } add_action( 'admin_menu', 'gutenberg_menu', 9 ); -if ( ! function_exists( 'recursively_find_block_by_attribute' ) ) { +if ( ! function_exists( 'html_contains_block' ) ) { /** * Recursively find a block by attribute. * - * @param array $blocks The blocks to search in. - * @param string $block_name The block type to search for. - * @param mixed $attribute_name The attribute name to search for. - * @param mixed $attribute_value The attribute value to search for. - * @param array $found_blocks The found blocks. + * @param string $html The html to search in. + * @param string $block_name The block type to search for. + * @param string $attribute_name The attribute name to search for. + * @param string $attribute_value The attribute value to search for. * - * @return array Found blocks + * @return bool True if block is found, false otherwise */ - function recursively_find_block_by_attribute( $blocks, $block_name, $attribute_name, $attribute_value, $found_blocks = array() ) { - foreach ( $blocks as $block ) { - if ( - $block['blockName'] === $block_name && - isset( $block['attrs'][ $attribute_name ] ) && - $attribute_value === $block['attrs'][ $attribute_name ] - ) { - $found_blocks[] = $block; - } - if ( $block['innerBlocks'] ) { - $found_blocks = array_merge( - $found_blocks, - recursively_find_block_by_attribute( - $block['innerBlocks'], - $block_name, - $attribute_name, - $attribute_value, - $found_blocks - ) - ); - } + function html_contains_block( $html, $block_name, $attribute_name = null, $attribute_value = null ) { + $at = 0; + + /** + * This is the same regex as the one used in the block parser. + * It is better to use this solution to look for a block's existence + * in a document compared to having to parsing the blocks in the + * document, avoiding all the performance drawbacks of achieving + * a full representation of block content just to check if one block + * is there. + */ + $pattern = sprintf( + '~).)*+)?}\s+)?/?-->~s', + // @TODO: we could ensure that there's a namespace on the block name. + preg_quote( $block_name, '~' ) + ); + + while ( false !== preg_match( $pattern, $html, $matches, PREG_OFFSET_CAPTURE, $at ) ) { + + // bail if no matches + if ( empty ($matches) ) { + return false; + } + + $at = $matches[0][1] + strlen( $matches[0][0] ); + + if ( ! isset( $attribute_name ) ) { + return true; + } + + // Don't parse JSON if it's not possible for the attribute to exist. + if ( ! str_contains( $matches['attrs'][0], "\"{$attribute_name}\":" ) ) { + continue; + } + + $attrs = json_decode( $matches['attrs'][0], /* as-associative */ true ); + if ( ! isset( $attrs[ $attribute_name ] ) ) { + continue; + } + + if ( ! isset( $attribute_value ) ) { + return true; + } + + if ( $attribute_value === $attrs[ $attribute_name ] ) { + return true; } - return $found_blocks; } -} + return false; +} +} if ( ! function_exists( 'get_template_parts_that_use_menu' ) ) { /** * Get all template parts that use a menu. @@ -116,15 +141,12 @@ function get_template_parts_that_use_menu( $wp_navigation_id ) { // with the ref attribute set to $wp_navigation_id. $wp_template_part_posts_with_navigation = array(); foreach ( $wp_template_part_posts as $wp_template_part_post ) { - $wp_template_part_blocks = parse_blocks( $wp_template_part_post->post_content ); - $found_avigation = count( - recursively_find_block_by_attribute( - $wp_template_part_blocks, - 'core/navigation', - 'ref', - $wp_navigation_id - ) - ) > 0; + $found_avigation = html_contains_block( + $wp_template_part_post->post_content, + 'navigation', + 'ref', + $wp_navigation_id + ); if ( $found_avigation ) { $wp_template_part_posts_with_navigation[] = $wp_template_part_post->ID; } From 32188bbe56cd48fd29e28ad4bf08b1d53c2d05f3 Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Wed, 17 Jan 2024 13:19:33 +0200 Subject: [PATCH 12/16] lint --- lib/init.php | 78 ++++++++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/lib/init.php b/lib/init.php index 997c8745b5c4b..0c01a1feb954b 100644 --- a/lib/init.php +++ b/lib/init.php @@ -70,56 +70,56 @@ function gutenberg_menu() { * @return bool True if block is found, false otherwise */ function html_contains_block( $html, $block_name, $attribute_name = null, $attribute_value = null ) { - $at = 0; + $at = 0; + + /** + * This is the same regex as the one used in the block parser. + * It is better to use this solution to look for a block's existence + * in a document compared to having to parsing the blocks in the + * document, avoiding all the performance drawbacks of achieving + * a full representation of block content just to check if one block + * is there. + */ + $pattern = sprintf( + '~).)*+)?}\s+)?/?-->~s', + // @TODO: we could ensure that there's a namespace on the block name. + preg_quote( $block_name, '~' ) + ); - /** - * This is the same regex as the one used in the block parser. - * It is better to use this solution to look for a block's existence - * in a document compared to having to parsing the blocks in the - * document, avoiding all the performance drawbacks of achieving - * a full representation of block content just to check if one block - * is there. - */ - $pattern = sprintf( - '~).)*+)?}\s+)?/?-->~s', - // @TODO: we could ensure that there's a namespace on the block name. - preg_quote( $block_name, '~' ) - ); + while ( false !== preg_match( $pattern, $html, $matches, PREG_OFFSET_CAPTURE, $at ) ) { - while ( false !== preg_match( $pattern, $html, $matches, PREG_OFFSET_CAPTURE, $at ) ) { + // bail if no matches + if ( empty ($matches) ) { + return false; + } - // bail if no matches - if ( empty ($matches) ) { - return false; - } + $at = $matches[0][1] + strlen( $matches[0][0] ); - $at = $matches[0][1] + strlen( $matches[0][0] ); + if ( ! isset( $attribute_name ) ) { + return true; + } - if ( ! isset( $attribute_name ) ) { - return true; - } + // Don't parse JSON if it's not possible for the attribute to exist. + if ( ! str_contains( $matches['attrs'][0], "\"{$attribute_name}\":" ) ) { + continue; + } - // Don't parse JSON if it's not possible for the attribute to exist. - if ( ! str_contains( $matches['attrs'][0], "\"{$attribute_name}\":" ) ) { - continue; - } + $attrs = json_decode( $matches['attrs'][0], /* as-associative */ true ); + if ( ! isset( $attrs[ $attribute_name ] ) ) { + continue; + } - $attrs = json_decode( $matches['attrs'][0], /* as-associative */ true ); - if ( ! isset( $attrs[ $attribute_name ] ) ) { - continue; - } + if ( ! isset( $attribute_value ) ) { + return true; + } - if ( ! isset( $attribute_value ) ) { - return true; + if ( $attribute_value === $attrs[ $attribute_name ] ) { + return true; + } } - if ( $attribute_value === $attrs[ $attribute_name ] ) { - return true; - } + return false; } - - return false; -} } if ( ! function_exists( 'get_template_parts_that_use_menu' ) ) { /** From 03ad7ac4292f7a0d11e4079ff1c04d4875c40ec8 Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Wed, 17 Jan 2024 20:21:53 +0200 Subject: [PATCH 13/16] improved comments, fixed the seek loop, removed the attribute existence check --- lib/init.php | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/lib/init.php b/lib/init.php index 0c01a1feb954b..78a275db234c5 100644 --- a/lib/init.php +++ b/lib/init.php @@ -60,14 +60,21 @@ function gutenberg_menu() { if ( ! function_exists( 'html_contains_block' ) ) { /** - * Recursively find a block by attribute. + * Returns whether the given HTML contains a block + * of the given type and, if provided, + * a given attribute and attribute value. * * @param string $html The html to search in. - * @param string $block_name The block type to search for. - * @param string $attribute_name The attribute name to search for. - * @param string $attribute_value The attribute value to search for. + * @param string $block_name Find this block type, + * with an optional "core/" namespace, + * e.g. "paragraph", "core/paragraph", + * "my_plugin/my_block". + * @param string $attribute_name If provided, the block must also + * contain this attribute. + * @param string $attribute_value If provided, the given attribute's + * value must also match this. * - * @return bool True if block is found, false otherwise + * @return bool True if block is found, false otherwise */ function html_contains_block( $html, $block_name, $attribute_name = null, $attribute_value = null ) { $at = 0; @@ -79,33 +86,23 @@ function html_contains_block( $html, $block_name, $attribute_name = null, $attri * document, avoiding all the performance drawbacks of achieving * a full representation of block content just to check if one block * is there. + * + * @see WP_Block_Parser. */ $pattern = sprintf( '~).)*+)?}\s+)?/?-->~s', - // @TODO: we could ensure that there's a namespace on the block name. preg_quote( $block_name, '~' ) ); - while ( false !== preg_match( $pattern, $html, $matches, PREG_OFFSET_CAPTURE, $at ) ) { - - // bail if no matches - if ( empty ($matches) ) { - return false; - } - + while ( 1 === preg_match( $pattern, $html, $matches, PREG_OFFSET_CAPTURE, $at ) ) { $at = $matches[0][1] + strlen( $matches[0][0] ); if ( ! isset( $attribute_name ) ) { return true; } - // Don't parse JSON if it's not possible for the attribute to exist. - if ( ! str_contains( $matches['attrs'][0], "\"{$attribute_name}\":" ) ) { - continue; - } - $attrs = json_decode( $matches['attrs'][0], /* as-associative */ true ); - if ( ! isset( $attrs[ $attribute_name ] ) ) { + if ( ! array_key_exists( $attribute_name, $attrs ) ) { continue; } From 952d133428d496e38a7be7cb2bd476a2c56af6b3 Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Thu, 18 Jan 2024 15:10:33 +0200 Subject: [PATCH 14/16] fix typo --- lib/init.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/init.php b/lib/init.php index 78a275db234c5..430b9ae502c1a 100644 --- a/lib/init.php +++ b/lib/init.php @@ -138,13 +138,13 @@ function get_template_parts_that_use_menu( $wp_navigation_id ) { // with the ref attribute set to $wp_navigation_id. $wp_template_part_posts_with_navigation = array(); foreach ( $wp_template_part_posts as $wp_template_part_post ) { - $found_avigation = html_contains_block( + $found_navigation = html_contains_block( $wp_template_part_post->post_content, 'navigation', 'ref', $wp_navigation_id ); - if ( $found_avigation ) { + if ( $found_navigation ) { $wp_template_part_posts_with_navigation[] = $wp_template_part_post->ID; } } From 766aba903044d33d72776d861a3647a7d9203402 Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Thu, 18 Jan 2024 15:11:25 +0200 Subject: [PATCH 15/16] remove useless comments --- lib/init.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/init.php b/lib/init.php index 430b9ae502c1a..1b7a61a1dd4c3 100644 --- a/lib/init.php +++ b/lib/init.php @@ -127,15 +127,14 @@ function html_contains_block( $html, $block_name, $attribute_name = null, $attri * @return array template parts that use the menu */ function get_template_parts_that_use_menu( $wp_navigation_id ) { - // get all wp_template_part posts. + $wp_template_part_posts = get_posts( array( 'post_type' => 'wp_template_part', 'posts_per_page' => -1, ) ); - // loop through them and find the ones that have a navigation block, - // with the ref attribute set to $wp_navigation_id. + $wp_template_part_posts_with_navigation = array(); foreach ( $wp_template_part_posts as $wp_template_part_post ) { $found_navigation = html_contains_block( From c9ddbfa0e6d64fafa661b66128d748b582315412 Mon Sep 17 00:00:00 2001 From: Andrei Draganescu Date: Mon, 22 Jan 2024 09:34:37 +0200 Subject: [PATCH 16/16] better comments, include namespace in block name --- lib/init.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/init.php b/lib/init.php index 1b7a61a1dd4c3..56c413d693b59 100644 --- a/lib/init.php +++ b/lib/init.php @@ -63,6 +63,9 @@ function gutenberg_menu() { * Returns whether the given HTML contains a block * of the given type and, if provided, * a given attribute and attribute value. + * + * Note that it's not possible to search for an attribute + * whose value is `null`. * * @param string $html The html to search in. * @param string $block_name Find this block type, @@ -91,7 +94,7 @@ function html_contains_block( $html, $block_name, $attribute_name = null, $attri */ $pattern = sprintf( '~).)*+)?}\s+)?/?-->~s', - preg_quote( $block_name, '~' ) + preg_quote( str_replace( 'core/', '', $block_name ), '~' ) ); while ( 1 === preg_match( $pattern, $html, $matches, PREG_OFFSET_CAPTURE, $at ) ) { @@ -102,7 +105,7 @@ function html_contains_block( $html, $block_name, $attribute_name = null, $attri } $attrs = json_decode( $matches['attrs'][0], /* as-associative */ true ); - if ( ! array_key_exists( $attribute_name, $attrs ) ) { + if ( ! isset( $attrs[ $attribute_name ] ) ) { continue; }