PluginProbe ʕ •ᴥ•ʔ
Atarim – AI Agency for WordPress: Edit Pages, Fix Code, Update Plugins, SEO & Client Feedback / 5.1
Atarim – AI Agency for WordPress: Edit Pages, Fix Code, Update Plugins, SEO & Client Feedback v5.1
5.1.3 5.1.2 5.1.1 5.1 5.0 trunk 3.10 3.11 3.12 3.13 3.14 3.15 3.16 3.17 3.18 3.19 3.2.0 3.2.1 3.22 3.22.1 3.22.2 3.22.3 3.22.4 3.22.5 3.22.6 3.3.0 3.3.1 3.3.2 3.3.2.1 3.3.2.2 3.3.3 3.30 3.31 3.32 3.4 3.4.1 3.4.3 3.4.4 3.5 3.5.1 3.6 3.6.1 3.7 3.8 3.9 3.9.1 3.9.2 3.9.3 3.9.4 3.9.6 3.9.6.1 4.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.0.9 4.1.0 4.1.1 4.1.2 4.1.3 4.2 4.2.1 4.2.2 4.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.4
atarim-visual-collaboration / doit / abilities / class-avcf-abilities-block-navigation.php
atarim-visual-collaboration / doit / abilities Last commit date
class-avcf-abilities-base.php 3 weeks ago class-avcf-abilities-block-navigation.php 3 weeks ago class-avcf-abilities-cache.php 3 weeks ago class-avcf-abilities-content.php 3 weeks ago class-avcf-abilities-core.php 3 weeks ago class-avcf-abilities-global-styles.php 3 weeks ago class-avcf-abilities-gutenberg.php 3 weeks ago class-avcf-abilities-media.php 3 weeks ago class-avcf-abilities-metadata.php 3 weeks ago class-avcf-abilities-navigation.php 3 weeks ago class-avcf-abilities-patterns.php 3 weeks ago class-avcf-abilities-plugins.php 3 weeks ago class-avcf-abilities-settings.php 3 weeks ago class-avcf-abilities-taxonomies.php 3 weeks ago class-avcf-abilities-templates.php 3 weeks ago class-avcf-abilities-theme-files.php 3 weeks ago class-avcf-abilities-themes.php 3 weeks ago class-avcf-abilities-users.php 3 weeks ago
class-avcf-abilities-block-navigation.php
246 lines
1 <?php
2 /**
3 * Block-theme navigation (wp_navigation) MCP abilities.
4 *
5 * Block themes do not use classic menus for primary navigation — they use the
6 * Navigation block, whose menus are stored as wp_navigation posts (block markup
7 * of wp:navigation-link / wp:navigation-submenu / wp:page-list). The classic
8 * menu abilities in the navigation category deliberately leave these out of
9 * scope; this category covers them, closing the gap for the common modern case
10 * where a block theme's header nav is a wp_navigation post.
11 *
12 * The generic content tools cannot reach wp_navigation because it is not a
13 * public post type. These abilities list, read and replace the block markup of
14 * a navigation menu; the markup is standard Gutenberg markup, so it can also be
15 * edited structurally with the gutenberg-* abilities once its post id is known.
16 *
17 * Exposed abilities:
18 * atarim/list-navigation All wp_navigation menus.
19 * atarim/get-navigation One menu's block markup + block summary.
20 * atarim/update-navigation Create or replace a navigation menu's block markup.
21 *
22 * @package atarim-visual-collaboration
23 */
24
25 if ( ! defined('ABSPATH') ) {
26 exit;
27 }
28
29 class AVCF_Abilities_Block_Navigation extends AVCF_Abilities_Base {
30
31 public function register() {
32 $this->register_list();
33 $this->register_get();
34 $this->register_update();
35 }
36
37 private function register_list() {
38 wp_register_ability( 'atarim/list-navigation', [
39 'label' => 'List Navigation Menus',
40 'description' => 'Returns the block-theme navigation menus (wp_navigation posts) used by the Navigation block in block themes — the header/footer menus edited in the Site Editor. Each item gives id, title, slug and status. Use get-navigation to read one\'s block markup and update-navigation to change it. (For CLASSIC themes that register nav_menu locations, use list-menus instead.)',
41 'category' => 'atarim',
42 'input_schema' => [
43 'type' => 'object',
44 'properties' => [],
45 'additionalProperties' => false,
46 ],
47 'output_schema' => [
48 'type' => 'object',
49 'properties' => [
50 'success' => [ 'type' => 'boolean' ],
51 'total' => [ 'type' => 'integer' ],
52 'menus' => [ 'type' => 'array' ],
53 'message' => [ 'type' => 'string' ],
54 ],
55 'required' => [ 'success', 'message' ],
56 ],
57 'execute_callback' => function( $input = [] ) {
58 $posts = get_posts( [
59 'post_type' => 'wp_navigation',
60 'post_status' => [ 'publish', 'draft' ],
61 'numberposts' => -1,
62 'orderby' => 'title',
63 'order' => 'ASC',
64 'suppress_filters' => false,
65 ] );
66
67 $menus = [];
68 foreach ( $posts as $post ) {
69 $menus[] = [
70 'id' => (int) $post->ID,
71 'title' => (string) $post->post_title,
72 'slug' => (string) $post->post_name,
73 'status' => (string) $post->post_status,
74 ];
75 }
76
77 return [
78 'success' => true,
79 'total' => count( $menus ),
80 'menus' => $menus,
81 'message' => sprintf( '%d block navigation menu(s).', count( $menus ) ),
82 ];
83 },
84 'permission_callback' => function() {
85 return current_user_can( 'edit_theme_options' );
86 },
87 'meta' => [
88 'mcp' => [ 'public' => true, 'type' => 'tool' ],
89 'annotations' => [ 'readonly' => true, 'destructive' => false, 'idempotent' => true ],
90 ],
91 ] );
92 }
93
94 private function register_get() {
95 wp_register_ability( 'atarim/get-navigation', [
96 'label' => 'Get Navigation Menu',
97 'description' => 'Returns one block navigation menu (wp_navigation post) by id, including its raw Gutenberg block markup (content) and a top-level block summary. The markup is composed of blocks like wp:navigation-link (a single link, attrs label/url/kind/id), wp:navigation-submenu (a link with children) and wp:page-list (auto-lists pages). Read this before update-navigation.',
98 'category' => 'atarim',
99 'input_schema' => [
100 'type' => 'object',
101 'properties' => [
102 'id' => [ 'type' => 'integer', 'minimum' => 1, 'description' => 'wp_navigation post id from list-navigation.' ],
103 ],
104 'required' => [ 'id' ],
105 'additionalProperties' => false,
106 ],
107 'output_schema' => [
108 'type' => 'object',
109 'properties' => [
110 'success' => [ 'type' => 'boolean' ],
111 'menu' => [ 'type' => 'object' ],
112 'message' => [ 'type' => 'string' ],
113 ],
114 'required' => [ 'success', 'message' ],
115 ],
116 'execute_callback' => function( $input = [] ) {
117 $id = isset( $input['id'] ) ? (int) $input['id'] : 0;
118 $post = $id > 0 ? get_post( $id ) : null;
119 if ( ! $post || $post->post_type !== 'wp_navigation' ) {
120 return [ 'success' => false, 'message' => sprintf( 'No navigation menu with id %d.', $id ) ];
121 }
122
123 $content = (string) $post->post_content;
124 $summary = [];
125 if ( function_exists( 'parse_blocks' ) ) {
126 foreach ( parse_blocks( $content ) as $block ) {
127 if ( ! empty( $block['blockName'] ) ) {
128 $summary[] = (string) $block['blockName'];
129 }
130 }
131 }
132
133 return [
134 'success' => true,
135 'menu' => [
136 'id' => (int) $post->ID,
137 'title' => (string) $post->post_title,
138 'slug' => (string) $post->post_name,
139 'status' => (string) $post->post_status,
140 'content' => $content,
141 'block_summary' => $summary,
142 ],
143 'message' => sprintf( 'Navigation "%s" has %d top-level block(s).', $post->post_title, count( $summary ) ),
144 ];
145 },
146 'permission_callback' => function() {
147 return current_user_can( 'edit_theme_options' );
148 },
149 'meta' => [
150 'mcp' => [ 'public' => true, 'type' => 'tool' ],
151 'annotations' => [ 'readonly' => true, 'destructive' => false, 'idempotent' => true ],
152 ],
153 ] );
154 }
155
156 private function register_update() {
157 wp_register_ability( 'atarim/update-navigation', [
158 'label' => 'Update Navigation Menu',
159 'description' => 'Creates or replaces a block navigation menu (wp_navigation post). Pass id to REPLACE an existing menu\'s block markup, or omit id and pass title to create a new one. content is the full Gutenberg navigation block markup — a sequence of wp:navigation-link / wp:navigation-submenu / wp:page-list blocks, e.g. \'<!-- wp:navigation-link {"label":"Home","url":"/","kind":"custom"} /-->\'. content REPLACES the previous markup. To change which menu the header shows, edit the header template part (update-template) so its wp:navigation block references the intended ref id. Malformed markup can break the menu — validate structure first.',
160 'category' => 'atarim',
161 'input_schema' => [
162 'type' => 'object',
163 'properties' => [
164 'id' => [ 'type' => 'integer', 'minimum' => 1, 'description' => 'Existing menu id to replace. Omit to create.' ],
165 'title' => [ 'type' => 'string', 'description' => 'Menu title. Required when creating.' ],
166 'content' => [ 'type' => 'string', 'description' => 'Full replacement navigation block markup.' ],
167 'status' => [ 'type' => 'string', 'enum' => [ 'publish', 'draft' ], 'default' => 'publish' ],
168 ],
169 'required' => [ 'content' ],
170 'additionalProperties' => false,
171 ],
172 'output_schema' => [
173 'type' => 'object',
174 'properties' => [
175 'success' => [ 'type' => 'boolean' ],
176 'id' => [ 'type' => 'integer' ],
177 'created' => [ 'type' => 'boolean' ],
178 'message' => [ 'type' => 'string' ],
179 ],
180 'required' => [ 'success', 'message' ],
181 ],
182 'execute_callback' => function( $input = [] ) {
183 if ( ! isset( $input['content'] ) || ! is_string( $input['content'] ) || $input['content'] === '' ) {
184 return [ 'success' => false, 'message' => 'content is required.' ];
185 }
186 $content = (string) $input['content'];
187 $status = isset( $input['status'] ) && $input['status'] === 'draft' ? 'draft' : 'publish';
188 $id = isset( $input['id'] ) ? (int) $input['id'] : 0;
189
190 if ( $id > 0 ) {
191 $existing = get_post( $id );
192 if ( ! $existing || $existing->post_type !== 'wp_navigation' ) {
193 return [ 'success' => false, 'message' => sprintf( 'No navigation menu with id %d.', $id ) ];
194 }
195 $postarr = [ 'ID' => $id, 'post_content' => $content, 'post_status' => $status ];
196 if ( isset( $input['title'] ) && $input['title'] !== '' ) {
197 $postarr['post_title'] = sanitize_text_field( (string) $input['title'] );
198 }
199 $result = wp_update_post( $postarr, true );
200 return $this->avcf_nav_result( $result, $id, false );
201 }
202
203 $title = isset( $input['title'] ) ? sanitize_text_field( (string) $input['title'] ) : '';
204 if ( $title === '' ) {
205 return [ 'success' => false, 'message' => 'title is required when creating a navigation menu.' ];
206 }
207 $result = wp_insert_post( [
208 'post_type' => 'wp_navigation',
209 'post_title' => $title,
210 'post_content' => $content,
211 'post_status' => $status,
212 ], true );
213 return $this->avcf_nav_result( $result, 0, true );
214 },
215 'permission_callback' => function() {
216 return current_user_can( 'edit_theme_options' );
217 },
218 'meta' => [
219 'mcp' => [ 'public' => true, 'type' => 'tool' ],
220 'annotations' => [ 'readonly' => false, 'destructive' => true, 'idempotent' => false ],
221 ],
222 ] );
223 }
224
225 /**
226 * Shape a create/update result into the ability response.
227 *
228 * @param int|WP_Error $result
229 * @param int $id Existing id (0 when creating).
230 * @param bool $created
231 * @return array
232 */
233 private function avcf_nav_result( $result, $id, $created ) {
234 if ( is_wp_error( $result ) ) {
235 return [ 'success' => false, 'message' => 'Save failed: ' . $result->get_error_message() ];
236 }
237 $final_id = $created ? (int) $result : $id;
238 return [
239 'success' => true,
240 'id' => $final_id,
241 'created' => $created,
242 'message' => sprintf( 'Navigation menu %d %s.', $final_id, $created ? 'created' : 'updated' ),
243 ];
244 }
245 }
246