Skip to content

Commit 3412a9d

Browse files
authored
[N/A] Implement Navigation Slug Reference for Navigation Block (#4)
1 parent 604890a commit 3412a9d

File tree

7 files changed

+334
-4
lines changed

7 files changed

+334
-4
lines changed

build/index.asset.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<?php return array('dependencies' => array('react', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-dom-ready', 'wp-edit-post', 'wp-edit-site', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => '7222d0a86dcb84c05c6b');
1+
<?php return array('dependencies' => array('react', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-dom-ready', 'wp-edit-post', 'wp-edit-site', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => '9d11076637a1ef99f3e2');

build/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/classes/Core.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,20 @@ class Core {
3333
*/
3434
public ?BreakpointVisibility $bp_visibility = null;
3535

36+
/**
37+
* Navigation Slug Handler
38+
*
39+
* @var ?NavigationSlugHandler
40+
*/
41+
public ?NavigationSlugHandler $navigation_slug_handler = null;
42+
3643
/**
3744
* Constructor
3845
*/
3946
public function __construct() {
40-
$this->block_icons = new BlockIcons();
41-
$this->bp_visibility = new BreakpointVisibility();
47+
$this->block_icons = new BlockIcons();
48+
$this->bp_visibility = new BreakpointVisibility();
49+
$this->navigation_slug_handler = new NavigationSlugHandler();
4250
}
4351

4452
/**
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
<?php
2+
/**
3+
* Navigation Slug Handler
4+
*
5+
* Handles slug-based navigation menu references for the core/navigation block.
6+
* Allows referencing navigation menus by slug instead of hardcoded IDs.
7+
*
8+
* @package Viget\BlocksToolkit
9+
*/
10+
11+
namespace Viget\BlocksToolkit;
12+
13+
use WP_Post;
14+
15+
/**
16+
* Class NavigationSlugHandler
17+
*/
18+
class NavigationSlugHandler {
19+
20+
/**
21+
* Cache for navigation slug lookups to avoid repeated queries.
22+
*
23+
* @var array
24+
*/
25+
private static $slug_cache = [];
26+
27+
/**
28+
* Initialize the handler.
29+
*/
30+
public function __construct() {
31+
$this->init_hooks();
32+
}
33+
34+
/**
35+
* Initialize WordPress hooks.
36+
*/
37+
private function init_hooks() {
38+
// Hook into block rendering to resolve slug references.
39+
add_filter(
40+
'render_block_data',
41+
[
42+
$this,
43+
'resolve_navigation_slug'
44+
]
45+
);
46+
47+
// Hook into navigation post updates to keep references in sync
48+
add_action(
49+
'post_updated',
50+
[
51+
$this,
52+
'handle_navigation_update'
53+
],
54+
10,
55+
3
56+
);
57+
}
58+
59+
/**
60+
* Resolve navigation slug references to IDs during block rendering.
61+
*
62+
* @param array $parsed_block The parsed block data.
63+
*
64+
* @return array Modified block data.
65+
*/
66+
public function resolve_navigation_slug( $parsed_block ) {
67+
// Only process core/navigation blocks
68+
if ( empty( $parsed_block['blockName'] ) || 'core/navigation' !== $parsed_block['blockName'] ) {
69+
return $parsed_block;
70+
}
71+
72+
// Check if refSlug attribute exists
73+
if ( empty( $parsed_block['attrs']['refSlug'] ) ) {
74+
return $parsed_block;
75+
}
76+
77+
$slug = $parsed_block['attrs']['refSlug'];
78+
$navigation_post = $this->get_navigation_by_slug( $slug );
79+
80+
if ( $navigation_post ) {
81+
// Update both ref and refSlug with current values to keep them in sync
82+
$parsed_block['attrs']['ref'] = $navigation_post->ID;
83+
$parsed_block['attrs']['refSlug'] = $navigation_post->post_name;
84+
} else {
85+
// Slug not found - remove refSlug to prevent stale data, keep ref as fallback
86+
unset( $parsed_block['attrs']['refSlug'] );
87+
}
88+
89+
return $parsed_block;
90+
}
91+
92+
/**
93+
* Get navigation post by slug with caching.
94+
*
95+
* @param string $slug The navigation post slug.
96+
*
97+
* @return WP_Post|null The navigation post or null if not found.
98+
*/
99+
private function get_navigation_by_slug( string $slug ): ?WP_Post {
100+
// Check cache first
101+
if ( isset( self::$slug_cache[ $slug ] ) ) {
102+
return self::$slug_cache[ $slug ];
103+
}
104+
105+
$navigation = get_posts(
106+
[
107+
'post_type' => 'wp_navigation',
108+
'name' => $slug,
109+
'post_status' => 'publish',
110+
'posts_per_page' => 1,
111+
'no_found_rows' => true,
112+
]
113+
);
114+
115+
$result = ! empty( $navigation ) ? $navigation[0] : null;
116+
117+
// Cache the result
118+
self::$slug_cache[ $slug ] = $result;
119+
120+
return $result;
121+
}
122+
123+
/**
124+
* Handle navigation post updates to keep references in sync.
125+
*
126+
* @param int $post_id Post ID.
127+
* @param WP_Post $post_after Post object after the update.
128+
* @param WP_Post $post_before Post object before the update.
129+
*/
130+
public function handle_navigation_update( $post_id, $post_after, $post_before ) {
131+
// Only process wp_navigation posts
132+
if ( 'wp_navigation' !== $post_after->post_type ) {
133+
return;
134+
}
135+
136+
// Check if the slug (post_name) changed
137+
if ( $post_after->post_name !== $post_before->post_name ) {
138+
$this->update_navigation_references( $post_before->post_name, $post_after->post_name, $post_id );
139+
}
140+
141+
// Clear cache for this navigation
142+
unset( self::$slug_cache[ $post_after->post_name ] );
143+
if ( $post_after->post_name !== $post_before->post_name ) {
144+
unset( self::$slug_cache[ $post_before->post_name ] );
145+
}
146+
}
147+
148+
/**
149+
* Update navigation references in template parts and patterns.
150+
*
151+
* @param string $old_slug The old navigation slug.
152+
* @param string $new_slug The new navigation slug.
153+
* @param int $post_id The navigation post ID.
154+
*/
155+
private function update_navigation_references( $old_slug, $new_slug, $post_id ) {
156+
// Find template parts and patterns that reference the old slug
157+
$template_parts = get_posts(
158+
[
159+
'post_type' => 'wp_template_part',
160+
'post_status' => 'publish',
161+
'posts_per_page' => -1,
162+
'meta_query' => [
163+
[
164+
'key' => '_wp_template_part_area',
165+
'compare' => 'EXISTS',
166+
],
167+
],
168+
]
169+
);
170+
171+
$patterns = get_posts(
172+
[
173+
'post_type' => 'wp_block',
174+
'post_status' => 'publish',
175+
'posts_per_page' => -1,
176+
]
177+
);
178+
179+
$posts_to_update = array_merge( $template_parts, $patterns );
180+
181+
foreach ( $posts_to_update as $post_to_update ) {
182+
$content = $post_to_update->post_content;
183+
184+
// Look for navigation blocks with the old slug
185+
$pattern = '/<!-- wp:navigation\s+({[^}]*"refSlug":"' . preg_quote( $old_slug, '/' ) . '"[^}]*})\s*\/-->/';
186+
187+
if ( ! preg_match( $pattern, $content, $matches ) ) {
188+
continue;
189+
}
190+
191+
// Parse the attributes
192+
$attributes = json_decode( $matches[1], true );
193+
194+
if ( ! $attributes || ! isset( $attributes['refSlug'] ) ) {
195+
continue;
196+
}
197+
198+
// Update both refSlug and ref
199+
$attributes['refSlug'] = $new_slug;
200+
$attributes['ref'] = $post_id;
201+
202+
// Replace the old block with updated attributes
203+
$new_block = '<!-- wp:navigation ' . wp_json_encode( $attributes ) . ' /-->';
204+
$new_content = preg_replace( $pattern, $new_block, $content );
205+
206+
// Update the post
207+
wp_update_post(
208+
[
209+
'ID' => $post_to_update->ID,
210+
'post_content' => $new_content,
211+
]
212+
);
213+
}
214+
}
215+
216+
/**
217+
* Clear the slug cache.
218+
*/
219+
public static function clear_cache() {
220+
self::$slug_cache = [];
221+
}
222+
}

src/features/navigation-slug.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/**
2+
* Navigation Slug Reference Feature
3+
*
4+
* Adds refSlug attribute to core/navigation blocks for slug-based menu references.
5+
* Works with the existing WordPress menu selector instead of creating a separate dropdown.
6+
*
7+
* @package Viget\BlocksToolkit
8+
*/
9+
10+
/**
11+
* WordPress dependencies
12+
*/
13+
import { __ } from '@wordpress/i18n';
14+
import { addFilter } from '@wordpress/hooks';
15+
import { useEffect } from '@wordpress/element';
16+
import { useSelect } from '@wordpress/data';
17+
18+
/**
19+
* Add the refSlug attribute to core/navigation blocks.
20+
*
21+
* @param {Object} settings Block settings.
22+
* @return {Object} Modified block settings.
23+
*/
24+
function addRefSlugAttribute(settings) {
25+
if ('core/navigation' !== settings.name) {
26+
return settings;
27+
}
28+
29+
const refSlugAttribute = {
30+
refSlug: {
31+
type: 'string',
32+
},
33+
};
34+
35+
return {
36+
...settings,
37+
attributes: {
38+
...settings.attributes,
39+
...refSlugAttribute,
40+
},
41+
};
42+
}
43+
44+
addFilter(
45+
'blocks.registerBlockType',
46+
'viget-blocks-toolkit/navigation-ref-slug-attribute',
47+
addRefSlugAttribute,
48+
);
49+
50+
/**
51+
* Sync refSlug with ref when menu is selected through existing WordPress interface.
52+
*
53+
* @param {Object} BlockEdit Block edit component.
54+
* @return {Object} Enhanced block edit component.
55+
*/
56+
function syncRefSlugWithRef(BlockEdit) {
57+
return (props) => {
58+
if (props.name !== 'core/navigation') {
59+
return <BlockEdit {...props} />;
60+
}
61+
62+
const { attributes, setAttributes } = props;
63+
const { refSlug, ref } = attributes;
64+
65+
// Get available navigation menus
66+
const navigationMenus = useSelect((select) => {
67+
return select('core').getEntityRecords('postType', 'wp_navigation', {
68+
status: 'publish',
69+
per_page: -1,
70+
});
71+
}, []);
72+
73+
// Sync refSlug when ref changes (user selects menu through existing interface)
74+
useEffect(() => {
75+
if (ref && navigationMenus) {
76+
const selectedMenu = navigationMenus.find((menu) => menu.id === ref);
77+
if (selectedMenu && selectedMenu.slug !== refSlug) {
78+
// Update refSlug to match the selected menu's slug
79+
setAttributes({
80+
refSlug: selectedMenu.slug,
81+
});
82+
}
83+
}
84+
}, [ref, navigationMenus, refSlug, setAttributes]);
85+
86+
return <BlockEdit {...props} />;
87+
};
88+
}
89+
90+
addFilter(
91+
'editor.BlockEdit',
92+
'viget-blocks-toolkit/navigation-ref-slug-sync',
93+
syncRefSlugWithRef,
94+
);

src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,6 @@ import './features/media-position';
1616

1717
// Breakpoint Visibility Support
1818
import './features/breakpoint-visibility';
19+
20+
// Navigation Slug Reference Support
21+
import './features/navigation-slug';

viget-blocks-toolkit.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,8 @@
4949
// Breakpoint Visibility support.
5050
require_once 'src/classes/BreakpointVisibility.php';
5151

52+
// Navigation Slug Handler.
53+
require_once 'src/classes/NavigationSlugHandler.php';
54+
5255
// Initialize the plugin.
5356
vgtbt();

0 commit comments

Comments
 (0)