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-patterns.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-patterns.php
213 lines
1 <?php
2 /**
3 * Block pattern MCP abilities.
4 *
5 * Block patterns are ready-made arrangements of blocks — hero sections, feature
6 * grids, testimonials, calls-to-action, pricing tables, headers, footers — that
7 * ship with the active theme and with WordPress core. They are the fastest route
8 * from a blank page to a professional-looking layout, but the pattern registry
9 * is invisible to the generic content tools.
10 *
11 * list-patterns surfaces the catalogue (optionally filtered by category or
12 * keyword) without the heavy block markup; get-pattern returns one pattern's
13 * full markup so it can be dropped into a page/post via create-content or
14 * gutenberg-apply-operations, or into a template via update-template.
15 *
16 * Exposed abilities:
17 * atarim/list-patterns Registered patterns (name/title/categories), optionally filtered.
18 * atarim/get-pattern One pattern's full block markup, ready to insert.
19 *
20 * @package atarim-visual-collaboration
21 */
22
23 if ( ! defined('ABSPATH') ) {
24 exit;
25 }
26
27 class AVCF_Abilities_Patterns extends AVCF_Abilities_Base {
28
29 public function register() {
30 $this->register_list();
31 $this->register_get();
32 }
33
34 private function register_list() {
35 wp_register_ability( 'atarim/list-patterns', [
36 'label' => 'List Block Patterns',
37 'description' => 'Returns registered block patterns — the pre-built section layouts (heroes, feature grids, testimonials, CTAs, headers, footers, etc.) from the active theme and WordPress core. Each item gives name (the id for get-pattern), title, description, categories, keywords, block_types (contexts where the pattern is offered, e.g. core/template-part/header), and viewport_width. Block markup is omitted here to keep the list lean — fetch it with get-pattern. Filter with category (a slug from get-site-editor-overview\'s pattern_categories, e.g. "call-to-action", "testimonials", "header") and/or search (matched against title, description and keywords). The response also lists all available categories.',
38 'category' => 'atarim',
39 'input_schema' => [
40 'type' => 'object',
41 'properties' => [
42 'category' => [ 'type' => 'string', 'description' => 'Filter to patterns in this category slug.' ],
43 'search' => [ 'type' => 'string', 'description' => 'Free-text filter over title, description and keywords.' ],
44 'limit' => [ 'type' => 'integer', 'minimum' => 1, 'maximum' => 200, 'default' => 100 ],
45 ],
46 'additionalProperties' => false,
47 ],
48 'output_schema' => [
49 'type' => 'object',
50 'properties' => [
51 'success' => [ 'type' => 'boolean' ],
52 'total' => [ 'type' => 'integer' ],
53 'returned' => [ 'type' => 'integer' ],
54 'categories' => [ 'type' => 'array', 'items' => [ 'type' => 'string' ] ],
55 'patterns' => [ 'type' => 'array' ],
56 'message' => [ 'type' => 'string' ],
57 ],
58 'required' => [ 'success', 'message' ],
59 ],
60 'execute_callback' => function( $input = [] ) {
61 if ( ! class_exists( 'WP_Block_Patterns_Registry' ) ) {
62 return [ 'success' => false, 'message' => 'Block patterns are not supported on this WordPress version.' ];
63 }
64
65 $all = WP_Block_Patterns_Registry::get_instance()->get_all_registered();
66 $category = isset( $input['category'] ) ? sanitize_title( (string) $input['category'] ) : '';
67 $search = isset( $input['search'] ) ? mb_strtolower( trim( (string) $input['search'] ) ) : '';
68 $limit = isset( $input['limit'] ) ? max( 1, min( 200, (int) $input['limit'] ) ) : 100;
69
70 $matched = [];
71 foreach ( $all as $pattern ) {
72 if ( $category !== '' && ! in_array( $category, array_map( 'sanitize_title', (array) ( $pattern['categories'] ?? [] ) ), true ) ) {
73 continue;
74 }
75 if ( $search !== '' && ! $this->avcf_pattern_matches_search( $pattern, $search ) ) {
76 continue;
77 }
78 $matched[] = $this->avcf_pattern_summary( $pattern );
79 }
80
81 $total = count( $matched );
82 $returned = array_slice( $matched, 0, $limit );
83
84 return [
85 'success' => true,
86 'total' => $total,
87 'returned' => count( $returned ),
88 'categories' => $this->avcf_all_pattern_categories(),
89 'patterns' => $returned,
90 'message' => $total > count( $returned )
91 ? sprintf( '%d pattern(s) matched; returning first %d. Narrow with category/search.', $total, count( $returned ) )
92 : sprintf( '%d pattern(s) matched.', $total ),
93 ];
94 },
95 'permission_callback' => function() {
96 return current_user_can( 'edit_posts' );
97 },
98 'meta' => [
99 'mcp' => [ 'public' => true, 'type' => 'tool' ],
100 'annotations' => [ 'readonly' => true, 'destructive' => false, 'idempotent' => true ],
101 ],
102 ] );
103 }
104
105 private function register_get() {
106 wp_register_ability( 'atarim/get-pattern', [
107 'label' => 'Get Block Pattern',
108 'description' => 'Returns one registered block pattern in full by its name (from list-patterns, e.g. "twentytwentyfive/hero"). content is ready-to-use Gutenberg block markup: pass it as the content of create-content (content_format:"blocks"), splice it into a page with gutenberg-apply-operations (insert op with markup), or drop it into a header/footer via update-template. Also returns the pattern\'s title, description, categories and viewport_width.',
109 'category' => 'atarim',
110 'input_schema' => [
111 'type' => 'object',
112 'properties' => [
113 'name' => [ 'type' => 'string', 'minLength' => 1, 'description' => 'Pattern name/id from list-patterns.' ],
114 ],
115 'required' => [ 'name' ],
116 'additionalProperties' => false,
117 ],
118 'output_schema' => [
119 'type' => 'object',
120 'properties' => [
121 'success' => [ 'type' => 'boolean' ],
122 'pattern' => [ 'type' => 'object' ],
123 'message' => [ 'type' => 'string' ],
124 ],
125 'required' => [ 'success', 'message' ],
126 ],
127 'execute_callback' => function( $input = [] ) {
128 if ( ! class_exists( 'WP_Block_Patterns_Registry' ) ) {
129 return [ 'success' => false, 'message' => 'Block patterns are not supported on this WordPress version.' ];
130 }
131 $name = isset( $input['name'] ) ? (string) $input['name'] : '';
132 if ( $name === '' ) {
133 return [ 'success' => false, 'message' => 'name is required.' ];
134 }
135
136 $registry = WP_Block_Patterns_Registry::get_instance();
137 if ( ! $registry->is_registered( $name ) ) {
138 return [ 'success' => false, 'message' => sprintf( 'No registered pattern named "%s".', $name ) ];
139 }
140
141 $pattern = $registry->get_registered( $name );
142 $summary = $this->avcf_pattern_summary( $pattern );
143 $summary['content'] = isset( $pattern['content'] ) ? (string) $pattern['content'] : '';
144
145 return [
146 'success' => true,
147 'pattern' => $summary,
148 'message' => sprintf( 'Pattern "%s" returned (%d bytes of block markup).', $name, strlen( $summary['content'] ) ),
149 ];
150 },
151 'permission_callback' => function() {
152 return current_user_can( 'edit_posts' );
153 },
154 'meta' => [
155 'mcp' => [ 'public' => true, 'type' => 'tool' ],
156 'annotations' => [ 'readonly' => true, 'destructive' => false, 'idempotent' => true ],
157 ],
158 ] );
159 }
160
161 /**
162 * Build a lean summary of a pattern (no block markup).
163 *
164 * @param array $pattern
165 * @return array
166 */
167 private function avcf_pattern_summary( $pattern ) {
168 return [
169 'name' => isset( $pattern['name'] ) ? (string) $pattern['name'] : '',
170 'title' => isset( $pattern['title'] ) ? (string) $pattern['title'] : '',
171 'description' => isset( $pattern['description'] ) ? (string) $pattern['description'] : '',
172 'categories' => array_values( array_map( 'strval', (array) ( $pattern['categories'] ?? [] ) ) ),
173 'keywords' => array_values( array_map( 'strval', (array) ( $pattern['keywords'] ?? [] ) ) ),
174 'block_types' => array_values( array_map( 'strval', (array) ( $pattern['blockTypes'] ?? [] ) ) ),
175 'viewport_width' => isset( $pattern['viewportWidth'] ) ? (int) $pattern['viewportWidth'] : 0,
176 ];
177 }
178
179 /**
180 * Whether a pattern matches a lowercased free-text search over its title,
181 * description and keywords.
182 *
183 * @param array $pattern
184 * @param string $search
185 * @return bool
186 */
187 private function avcf_pattern_matches_search( $pattern, $search ) {
188 $haystack = mb_strtolower(
189 (string) ( $pattern['title'] ?? '' ) . ' ' .
190 (string) ( $pattern['description'] ?? '' ) . ' ' .
191 implode( ' ', (array) ( $pattern['keywords'] ?? [] ) )
192 );
193 return mb_strpos( $haystack, $search ) !== false;
194 }
195
196 /**
197 * All registered pattern category slugs.
198 *
199 * @return array<int,string>
200 */
201 private function avcf_all_pattern_categories() {
202 if ( ! class_exists( 'WP_Block_Pattern_Categories_Registry' ) ) {
203 return [];
204 }
205 $cats = [];
206 foreach ( WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered() as $cat ) {
207 $cats[] = (string) $cat['name'];
208 }
209 sort( $cats );
210 return $cats;
211 }
212 }
213