diff --git a/lib/init.php b/lib/init.php index 88dcba4525f6e2..56c413d693b59e 100644 --- a/lib/init.php +++ b/lib/init.php @@ -57,3 +57,121 @@ function gutenberg_menu() { ); } add_action( 'admin_menu', 'gutenberg_menu', 9 ); + +if ( ! function_exists( 'html_contains_block' ) ) { + /** + * 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, + * 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 + */ + 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. + * + * @see WP_Block_Parser. + */ + $pattern = sprintf( + '~).)*+)?}\s+)?/?-->~s', + preg_quote( str_replace( 'core/', '', $block_name ), '~' ) + ); + + 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; + } + + $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 false; + } +} +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 ) { + + $wp_template_part_posts = get_posts( + array( + 'post_type' => 'wp_template_part', + 'posts_per_page' => -1, + ) + ); + + $wp_template_part_posts_with_navigation = array(); + foreach ( $wp_template_part_posts as $wp_template_part_post ) { + $found_navigation = html_contains_block( + $wp_template_part_post->post_content, + 'navigation', + 'ref', + $wp_navigation_id + ); + if ( $found_navigation ) { + $wp_template_part_posts_with_navigation[] = $wp_template_part_post->ID; + } + } + return $wp_template_part_posts_with_navigation; + } +} + +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' ); 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 960e0363f2e588..a747fc79c55eff 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 } ) => ( + + + + ) + ) } + + + ) } ); }