Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 928
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
WooProducts
0.00% covered (danger)
0.00%
0 / 928
0.00% covered (danger)
0.00%
0 / 8
2756
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 register_product_abilities
0.00% covered (danger)
0.00%
0 / 374
0.00% covered (danger)
0.00%
0 / 1
240
 register_category_abilities
0.00% covered (danger)
0.00%
0 / 188
0.00% covered (danger)
0.00%
0 / 1
90
 register_tag_abilities
0.00% covered (danger)
0.00%
0 / 156
0.00% covered (danger)
0.00%
0 / 1
30
 register_brand_abilities
0.00% covered (danger)
0.00%
0 / 156
0.00% covered (danger)
0.00%
0 / 1
30
 add_product_taxonomies
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
90
 get_product_taxonomy_ids
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
42
 get_taxonomy
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2declare( strict_types=1 );
3
4namespace BLU\Abilities;
5
6/**
7 * WooProducts abilities for WooCommerce products.
8 */
9class WooProducts {
10
11    /**
12     * Constructor - registers WooCommerce product abilities if WooCommerce is active.
13     */
14    public function __construct() {
15        if ( ! class_exists( 'WooCommerce' ) ) {
16            return;
17        }
18
19        $this->register_product_abilities();
20        $this->register_category_abilities();
21        $this->register_tag_abilities();
22        $this->register_brand_abilities();
23    }
24
25    /**
26     * Register product abilities.
27     */
28    private function register_product_abilities(): void {
29        // Search products
30        blu_register_ability(
31            'blu/wc-products-search',
32            array(
33                'label'               => 'Search WooCommerce Products',
34                'description'         => 'Search and filter WooCommerce products with pagination',
35                'category'            => 'blu-mcp',
36                'input_schema'        => array(
37                    'type'       => 'object',
38                    'properties' => array(
39                        'search'   => array(
40                            'type'        => 'string',
41                            'description' => 'Search term',
42                        ),
43                        'page'     => array(
44                            'type'        => 'integer',
45                            'description' => 'Page number',
46                        ),
47                        'per_page' => array(
48                            'type'        => 'integer',
49                            'description' => 'Products per page',
50                        ),
51                    ),
52                ),
53                'execute_callback'    => function ( $input = null ) {
54                    $request = new \WP_REST_Request( 'GET', '/wc/v3/products' );
55                    if ( $input ) {
56                        $request->set_query_params( $input );
57                    }
58                    $response = rest_do_request( $request );
59
60                    return blu_standardize_rest_response( $response );
61                },
62                'permission_callback' => fn() => current_user_can( 'edit_products' ),
63                'meta'                => array(
64                    'annotations' => array(
65                        'readonly'    => true,
66                        'destructive' => false,
67                        'idempotent'  => true,
68                    ),
69                ),
70            )
71        );
72
73        // Get product
74        blu_register_ability(
75            'blu/wc-get-product',
76            array(
77                'label'               => 'Get WooCommerce Product',
78                'description'         => 'Get a WooCommerce product by ID',
79                'category'            => 'blu-mcp',
80                'input_schema'        => array(
81                    'type'       => 'object',
82                    'properties' => array(
83                        'id' => array(
84                            'type'        => 'integer',
85                            'description' => 'Product ID',
86                        ),
87                    ),
88                    'required'   => array( 'id' ),
89                ),
90                'execute_callback'    => function ( $input ) {
91                    $request  = new \WP_REST_Request( 'GET', '/wc/v3/products/' . $input['id'] );
92                    $response = rest_do_request( $request );
93
94                    return blu_standardize_rest_response( $response );
95                },
96                'permission_callback' => fn() => current_user_can( 'edit_products' ),
97                'meta'                => array(
98                    'annotations' => array(
99                        'readonly'    => true,
100                        'destructive' => false,
101                        'idempotent'  => true,
102                    ),
103                ),
104            )
105        );
106
107        // Add product
108        blu_register_ability(
109            'blu/wc-add-product',
110            array(
111                'label'               => 'Add WooCommerce Product',
112                'description'         => 'Create a WooCommerce product, or start the guided add-product flow. If ready is false or omitted, no product is created—the response returns assistant-only steps (A/B options, suggestions).',
113                'category'            => 'blu-mcp',
114                'input_schema'        => array(
115                    'type'        => 'object',
116                    'properties'  => array(
117                        'name'                 => array(
118                            'type'        => 'string',
119                            'description' => 'Product name',
120                        ),
121                        'type'                 => array(
122                            'type'        => 'string',
123                            'description' => 'Product type',
124                        ),
125                        'description'          => array(
126                            'type'        => 'string',
127                            'description' => 'Product description',
128                        ),
129                        'short_description'    => array(
130                            'type'        => 'string',
131                            'description' => 'Product short description',
132                        ),
133                        'regular_price'        => array(
134                            'type'        => 'string',
135                            'description' => 'Product price',
136                        ),
137                        'sale_price'           => array(
138                            'type'        => 'string',
139                            'description' => 'Product sale price',
140                        ),
141                        'categories'           => array(
142                            'type'        => 'array',
143                            'description' => 'List of categories',
144                            'items'       => array(
145                                'type'       => 'object',
146                                'properties' => array(
147                                    'id' => array(
148                                        'description' => 'Category ID.',
149                                        'type'        => 'integer',
150                                    ),
151                                ),
152                            ),
153                        ),
154                        'tags'                 => array(
155                            'type'        => 'array',
156                            'description' => 'List of tags',
157                            'items'       => array(
158                                'type'       => 'object',
159                                'properties' => array(
160                                    'id' => array(
161                                        'description' => 'Tag ID.',
162                                        'type'        => 'integer',
163                                    ),
164                                ),
165                            ),
166                        ),
167                        'brands'               => array(
168                            'type'        => 'array',
169                            'description' => 'List of brands',
170                            'items'       => array(
171                                'type'       => 'object',
172                                'properties' => array(
173                                    'id' => array(
174                                        'description' => 'Brand ID.',
175                                        'type'        => 'integer',
176                                    ),
177                                ),
178                            ),
179                        ),
180                        'variation_attributes' => array(
181                            'type'        => 'array',
182                            'description' => 'List of variation attributes',
183                            'items'       => array(
184                                'type'       => 'object',
185                                'properties' => array(
186                                    'name'  => array(
187                                        'description' => 'Attribute name',
188                                        'type'        => 'string',
189                                    ),
190                                    'terms' => array(
191                                        'type'        => 'array',
192                                        'items'       => array( 'type' => 'string' ),
193                                        'description' => 'Attribute name',
194                                    ),
195                                ),
196                            ),
197                        ),
198                        'status'               => array(
199                            'description' => 'Product status (post status).',
200                            'type'        => 'string',
201                            'default'     => 'draft',
202                            'enum'        => array_merge(
203                                array_keys( get_post_statuses() ),
204                                array(
205                                    'future',
206                                    'auto-draft',
207                                    'trash',
208                                )
209                            ),
210                        ),
211                    ),
212                    'required'    => array( 'name' ),
213                ),
214                'execute_callback'    => function ( $input ) {
215
216                    $request = new \WP_REST_Request( 'POST', '/wc/v3/products' );
217
218                    $variation_attributes = $input['variation_attributes'] ?? array();
219                    if ( $variation_attributes ) {
220                        $input['type'] = 'variable';
221                    }
222
223                    if ( isset( $input['variation_attributes'] ) ) {
224                        unset( $input['variation_attributes'] );
225                    }
226
227                    $request->set_body_params( $input );
228                    $response = rest_do_request( $request );
229
230                    if ( ! $response->is_error() && (bool) $variation_attributes ) {
231                        $data       = $response->get_data();
232                        $product_id = absint( $data['id'] ?? 0 );
233
234                        if ( $product_id ) {
235                            $product            = wc_get_product( $product_id );
236                            $position           = 0;
237                            $product_attributes = array();
238
239                            foreach ( $variation_attributes as $attribute ) {
240                                $attribute_id   = 0;
241                                $attribute_name = wc_clean( esc_html( $attribute['name'] ) );
242
243                                $terms = wc_get_text_attributes( implode( WC_DELIMITER, $attribute['terms'] ) );
244
245                                $product_attribute = new \WC_Product_Attribute();
246                                $product_attribute->set_id( $attribute_id );
247                                $product_attribute->set_name( $attribute_name );
248                                $product_attribute->set_options( $terms );
249                                $product_attribute->set_position( $position );
250                                $product_attribute->set_visible( true );
251                                $product_attribute->set_variation( true );
252
253                                $product_attributes[] = $product_attribute;
254
255                                $position++;
256                            }
257
258                            $product->set_attributes( $product_attributes );
259                            $product->save();
260
261                            /**
262                             * The variable product
263                             *
264                             * @var $variation \WC_Product_Variation
265                             */
266                            $variation = wc_get_product_object( 'variation' );
267                            if ( isset( $input['regular_price'] ) ) {
268                                $variation->set_regular_price( $input['regular_price'] );
269                            }
270                            $variation->set_parent_id( $product->get_id() );
271                            $variation->save();
272
273                            $request  = new \WP_REST_Request( 'GET', '/wc/v3/products/' . $product_id );
274                            $response = rest_do_request( $request );
275                        }
276                    }
277
278                    return blu_standardize_rest_response( $response );
279                },
280                'permission_callback' => fn() => current_user_can( 'edit_products' ),
281                'meta'                => array(
282                    'annotations' => array(
283                        'readonly'    => false,
284                        'destructive' => false,
285                        'idempotent'  => false,
286                    ),
287                ),
288            ),
289        );
290
291        // Update product
292        blu_register_ability(
293            'blu/wc-update-product',
294            array(
295                'label'               => 'Update WooCommerce Product',
296                'description'         => 'Update a WooCommerce product by ID',
297                'category'            => 'blu-mcp',
298                'input_schema'        => array(
299                    'type'       => 'object',
300                    'properties' => array(
301                        'id'                => array(
302                            'type'        => 'integer',
303                            'description' => 'Product ID',
304                        ),
305                        'name'              => array(
306                            'type'        => 'string',
307                            'description' => 'Product name',
308                        ),
309                        'description'       => array(
310                            'type'        => 'string',
311                            'description' => 'Product description',
312                        ),
313                        'short_description' => array(
314                            'type'        => 'string',
315                            'description' => 'Product short description',
316                        ),
317                        'regular_price'     => array(
318                            'type'        => 'string',
319                            'description' => 'Product price',
320                        ),
321                        'sale_price'        => array(
322                            'type'        => 'string',
323                            'description' => 'Product sale price',
324                        ),
325                        'categories'        => array(
326                            'type'        => 'array',
327                            'description' => 'List of categories',
328                            'items'       => array(
329                                'type'       => 'object',
330                                'properties' => array(
331                                    'id' => array(
332                                        'description' => 'Category ID.',
333                                        'type'        => 'integer',
334                                    ),
335                                ),
336                            ),
337                        ),
338                        'tags'              => array(
339                            'type'        => 'array',
340                            'description' => 'List of tags',
341                            'items'       => array(
342                                'type'       => 'object',
343                                'properties' => array(
344                                    'id' => array(
345                                        'description' => 'Tag ID.',
346                                        'type'        => 'integer',
347                                    ),
348                                ),
349                            ),
350                        ),
351                        'brands'            => array(
352                            'type'        => 'array',
353                            'description' => 'List of brands',
354                            'items'       => array(
355                                'type'       => 'object',
356                                'properties' => array(
357                                    'id' => array(
358                                        'description' => 'Brand ID.',
359                                        'type'        => 'integer',
360                                    ),
361                                ),
362                            ),
363                        ),
364                        'status'            => array(
365                            'description' => 'Product status (post status).',
366                            'type'        => 'string',
367                            'default'     => 'draft',
368                            'enum'        => array_merge(
369                                array_keys( get_post_statuses() ),
370                                array(
371                                    'future',
372                                    'auto-draft',
373                                    'trash',
374                                )
375                            ),
376                        ),
377                    ),
378                    'required'   => array( 'id' ),
379                ),
380                'execute_callback'    => function ( $input ) {
381                    $id = $input['id'];
382                    unset( $input['id'] );
383
384                    if ( isset( $input['categories'] ) && count( $input['categories'] ) > 0 ) {
385                        $stored_category     = $this->get_product_taxonomy_ids( $id );
386                        $input['categories'] = array_merge( $input['categories'], $stored_category );
387                    }
388
389                    if ( isset( $input['tags'] ) && count( $input['tags'] ) > 0 ) {
390                        $stored_tag    = $this->get_product_taxonomy_ids( $id, 'tags' );
391                        $input['tags'] = array_merge( $input['tags'], $stored_tag );
392                    }
393
394                    if ( isset( $input['brands'] ) && count( $input['brands'] ) > 0 ) {
395                        $stored_brand    = $this->get_product_taxonomy_ids( $id, 'brands' );
396                        $input['brands'] = array_merge( $input['brands'], $stored_brand );
397                    }
398
399                    $request = new \WP_REST_Request( 'PUT', '/wc/v3/products/' . $id );
400                    $request->set_body_params( $input );
401                    $response = rest_do_request( $request );
402
403                    return blu_standardize_rest_response( $response );
404                },
405                'permission_callback' => fn() => current_user_can( 'edit_products' ),
406                'meta'                => array(
407                    'annotations' => array(
408                        'readonly'    => false,
409                        'destructive' => false,
410                        'idempotent'  => true,
411                    ),
412                ),
413            )
414        );
415
416        // Delete product
417        blu_register_ability(
418            'blu/wc-delete-product',
419            array(
420                'label'               => 'Delete WooCommerce Product',
421                'description'         => 'Delete a WooCommerce product by ID',
422                'category'            => 'blu-mcp',
423                'input_schema'        => array(
424                    'type'       => 'object',
425                    'properties' => array(
426                        'id' => array(
427                            'type'        => 'integer',
428                            'description' => 'Product ID',
429                        ),
430                    ),
431                    'required'   => array( 'id' ),
432                ),
433                'execute_callback'    => function ( $input ) {
434                    $request = new \WP_REST_Request( 'DELETE', '/wc/v3/products/' . $input['id'] );
435                    $request->set_param( 'force', true );
436                    $response = rest_do_request( $request );
437
438                    return blu_standardize_rest_response( $response );
439                },
440                'permission_callback' => fn() => current_user_can( 'delete_products' ),
441                'meta'                => array(
442                    'annotations' => array(
443                        'readonly'    => false,
444                        'destructive' => true,
445                        'idempotent'  => true,
446                    ),
447                ),
448            )
449        );
450    }
451
452    /**
453     * Register product category abilities.
454     */
455    private function register_category_abilities(): void {
456        // List categories
457
458        blu_register_ability(
459            'blu/wc-list-product-categories',
460            array(
461                'label'               => 'List WooCommerce Product Categories',
462                'description'         => 'List all WooCommerce product categories',
463                'category'            => 'blu-mcp',
464                'input_schema'        => array(
465                    'type'       => 'object',
466                    'properties' => array(
467                        'patterns' => array(
468                            'type'        => 'array',
469                            'description' => 'List of relevant categories and regex based on product name',
470                            'items'       => array( 'type' => 'string' ),
471                            'maxItems'    => 5,
472                        ),
473                    ),
474                ),
475                'execute_callback'    => function ( $input ) {
476                    $page       = 1;
477                    $categories = array();
478                    $request    = new \WP_REST_Request( 'GET', '/wc/v3/products/categories' );
479                    do {
480                        $request->set_query_params( array( 'page' => $page ) );
481                        $response = rest_do_request( $request );
482                        if ( is_wp_error( $response ) ) {
483                            return blu_standardize_rest_response( $response );
484                        }
485                        $data  = $response->get_data();
486                        $total = count( $data );
487                        foreach ( $data as $category ) {
488                            $categories[] = array(
489                                'id'     => $category['id'],
490                                'name'   => $category['name'],
491                                'parent' => $category['parent'],
492                            );
493                        }
494                        $page++;
495                    } while ( $total > 0 );
496
497                    $patterns = $input['patterns'] ?? array();
498
499                    $is_valid = blu_is_valid_input_array( $patterns, 'patterns', 0, 5 );
500                    if ( is_wp_error( $is_valid ) ) {
501                        return blu_standardize_rest_response( $is_valid );
502                    }
503
504                    blu_filter_terms_by_patterns( $patterns, $categories );
505
506                    return blu_prepare_ability_response( 200, $categories );
507                },
508                'permission_callback' => fn() => current_user_can( 'edit_products' ),
509                'meta'                => array(
510                    'annotations' => array(
511                        'readonly'    => true,
512                        'destructive' => false,
513                        'idempotent'  => true,
514                    ),
515                ),
516            )
517        );
518
519        // Add category
520        blu_register_ability(
521            'blu/wc-add-product-category',
522            array(
523                'label'               => 'Add WooCommerce Product Category',
524                'description'         => 'Add one or more new WooCommerce product categories',
525                'category'            => 'blu-mcp',
526                'input_schema'        => array(
527                    'type'       => 'object',
528                    'properties' => array(
529                        'categories'    => array(
530                            'type'        => 'array',
531                            'description' => 'List of product categories name',
532                            'items'       => array( 'type' => 'string' ),
533                            'minItems'    => 1,
534                        ),
535                        'hierarchical'  => array(
536                            'type'        => 'boolean',
537                            'description' => 'Add the category hierarchically or not.',
538                            'default'     => false,
539                        ),
540                        'is_google_tax' => array(
541                            'type'        => 'boolean',
542                            'description' => 'Define is a google taxonomy or not',
543                            'default'     => false,
544                        ),
545                    ),
546                    'required'   => array( 'categories' ),
547                ),
548                'execute_callback'    => function ( $input ) {
549
550                    $all_categories = $input['categories'] ?? array();
551                    $is_google_tax  = $input['is_google_tax'] ?? false;
552                    $hierarchical   = $input['hierarchical'] ?? false;
553
554                    $is_valid = blu_is_valid_input_array( $all_categories, 'categories', 1 );
555
556                    if ( is_wp_error( $is_valid ) ) {
557                        return blu_standardize_rest_response( $is_valid );
558                    }
559                    if ( $is_google_tax ) {
560                        $created  = array();
561                        $existing = array();
562                        foreach ( $all_categories as $category_path ) {
563                            $categories = explode( '>', $category_path );
564
565                            $resp = $this->add_product_taxonomies( $categories, 'categories', true );
566                            if ( ! in_array( $resp['statusCode'], array( 200, 201 ) ) ) {
567                                return $resp;
568                            }
569
570                            $created  = array_merge( $created, $resp['message']['created'] );
571                            $existing = array_merge( $existing, $resp['message']['existing'] );
572                        }
573
574                        return array(
575                            'statusCode' => count( $created ) > 0 ? 201 : 200,
576                            'status'     => 'success',
577                            'message'    => array(
578                                'created'  => $created,
579                                'existing' => $existing,
580                                'total'    => count( $created ) + count( $existing ),
581                            ),
582                        );
583                    } else {
584                        return $this->add_product_taxonomies( $all_categories, 'categories', $hierarchical );
585                    }
586                },
587                'permission_callback' => fn() => current_user_can( 'manage_product_terms' ),
588                'meta'                => array(
589                    'annotations' => array(
590                        'readonly'    => false,
591                        'destructive' => false,
592                        'idempotent'  => false,
593                    ),
594                ),
595            )
596        );
597
598        // Update category
599        blu_register_ability(
600            'blu/wc-update-product-category',
601            array(
602                'label'               => 'Update WooCommerce Product Category',
603                'description'         => 'Update a WooCommerce product category',
604                'category'            => 'blu-mcp',
605                'input_schema'        => array(
606                    'type'       => 'object',
607                    'properties' => array(
608                        'id'   => array(
609                            'type'        => 'integer',
610                            'description' => 'Category ID',
611                        ),
612                        'name' => array(
613                            'type'        => 'string',
614                            'description' => 'Category name',
615                        ),
616                    ),
617                    'required'   => array( 'id' ),
618                ),
619                'execute_callback'    => function ( $input ) {
620                    $id = $input['id'];
621                    unset( $input['id'] );
622                    $request = new \WP_REST_Request( 'PUT', '/wc/v3/products/categories/' . $id );
623                    $request->set_body_params( $input );
624                    $response = rest_do_request( $request );
625
626                    return blu_standardize_rest_response( $response );
627                },
628                'permission_callback' => fn() => current_user_can( 'manage_product_terms' ),
629                'meta'                => array(
630                    'annotations' => array(
631                        'readonly'    => false,
632                        'destructive' => false,
633                        'idempotent'  => true,
634                    ),
635                ),
636            )
637        );
638
639        // Delete category
640        blu_register_ability(
641            'blu/wc-delete-product-category',
642            array(
643                'label'               => 'Delete WooCommerce Product Category',
644                'description'         => 'Delete a WooCommerce product category',
645                'category'            => 'blu-mcp',
646                'input_schema'        => array(
647                    'type'       => 'object',
648                    'properties' => array(
649                        'id' => array(
650                            'type'        => 'integer',
651                            'description' => 'Category ID',
652                        ),
653                    ),
654                    'required'   => array( 'id' ),
655                ),
656                'execute_callback'    => function ( $input ) {
657                    $request = new \WP_REST_Request( 'DELETE', '/wc/v3/products/categories/' . $input['id'] );
658                    $request->set_param( 'force', true );
659                    $response = rest_do_request( $request );
660
661                    return blu_standardize_rest_response( $response );
662                },
663                'permission_callback' => fn() => current_user_can( 'delete_product_terms' ),
664                'meta'                => array(
665                    'annotations' => array(
666                        'readonly'    => false,
667                        'destructive' => true,
668                        'idempotent'  => true,
669                    ),
670                ),
671            )
672        );
673    }
674
675    /**
676     * Register product tag abilities.
677     */
678    private function register_tag_abilities(): void {
679        // List tags
680        blu_register_ability(
681            'blu/wc-list-product-tags',
682            array(
683                'label'               => 'List WooCommerce Product Tags',
684                'description'         => 'List all WooCommerce product tags',
685                'category'            => 'blu-mcp',
686                'input_schema'        => array(
687                    'type'       => 'object',
688                    'properties' => array(
689                        'patterns' => array(
690                            'type'        => 'array',
691                            'description' => 'List of relevant tags based on product name, product description and product category',
692                            'items'       => array( 'type' => 'string' ),
693                            'maxItems'    => 5,
694                        ),
695                    ),
696                ),
697                'execute_callback'    => function ( $input ) {
698                    $page    = 1;
699                    $tags    = array();
700                    $request = new \WP_REST_Request( 'GET', '/wc/v3/products/tags' );
701                    do {
702                        $request->set_query_params( array( 'page' => $page ) );
703                        $response = rest_do_request( $request );
704                        if ( is_wp_error( $response ) ) {
705                            return blu_standardize_rest_response( $response );
706                        }
707                        $data  = $response->get_data();
708                        $total = count( $data );
709                        foreach ( $data as $tag ) {
710                            $tags[] = array(
711                                'id'   => $tag['id'],
712                                'name' => $tag['name'],
713                            );
714                        }
715                        $page++;
716                    } while ( $total > 0 );
717
718                    $patterns = $input['patterns'] ?? array();
719
720                    $is_valid = blu_is_valid_input_array( $patterns, 'patterns', 0, 5 );
721                    if ( is_wp_error( $is_valid ) ) {
722                        return blu_standardize_rest_response( $is_valid );
723                    }
724
725                    blu_filter_terms_by_patterns( $patterns, $tags );
726
727                    return blu_prepare_ability_response( 200, $tags );
728                },
729
730                'permission_callback' => fn() => current_user_can( 'edit_products' ),
731                'meta'                => array(
732                    'annotations' => array(
733                        'readonly'    => true,
734                        'destructive' => false,
735                        'idempotent'  => true,
736                    ),
737                ),
738            )
739        );
740
741        // Add tag
742        blu_register_ability(
743            'blu/wc-add-product-tag',
744            array(
745                'label'               => 'Add WooCommerce Product Tag',
746                'description'         => 'Add one or more new WooCommerce product tag',
747                'category'            => 'blu-mcp',
748                'input_schema'        => array(
749                    'type'       => 'object',
750                    'properties' => array(
751                        'tags' => array(
752                            'type'        => 'array',
753                            'description' => 'The list of product tag name',
754                            'items'       => array( 'type' => 'string' ),
755                            'minItems'    => 1,
756                        ),
757                    ),
758                    'required'   => array( 'tags' ),
759                ),
760                'execute_callback'    => function ( $input ) {
761                    $all_tag  = $input['tags'] ?? array();
762                    $is_valid = blu_is_valid_input_array( $all_tag, 'tags', 1 );
763
764                    if ( is_wp_error( $is_valid ) ) {
765                        return blu_standardize_rest_response( $is_valid );
766                    }
767
768                    return $this->add_product_taxonomies( $all_tag, 'tags' );
769                },
770                'permission_callback' => fn() => current_user_can( 'manage_product_terms' ),
771                'meta'                => array(
772                    'annotations' => array(
773                        'readonly'    => false,
774                        'destructive' => false,
775                        'idempotent'  => false,
776                    ),
777                ),
778            )
779        );
780
781        // Update tag
782        blu_register_ability(
783            'blu/wc-update-product-tag',
784            array(
785                'label'               => 'Update WooCommerce Product Tag',
786                'description'         => 'Update a WooCommerce product tag',
787                'category'            => 'blu-mcp',
788                'input_schema'        => array(
789                    'type'       => 'object',
790                    'properties' => array(
791                        'id'   => array(
792                            'type'        => 'integer',
793                            'description' => 'Tag ID',
794                        ),
795                        'name' => array(
796                            'type'        => 'string',
797                            'description' => 'Tag name',
798                        ),
799                    ),
800                    'required'   => array( 'id' ),
801                ),
802                'execute_callback'    => function ( $input ) {
803                    $id = $input['id'];
804                    unset( $input['id'] );
805                    $request = new \WP_REST_Request( 'PUT', '/wc/v3/products/tags/' . $id );
806                    $request->set_body_params( $input );
807                    $response = rest_do_request( $request );
808
809                    return blu_standardize_rest_response( $response );
810                },
811                'permission_callback' => fn() => current_user_can( 'manage_product_terms' ),
812                'meta'                => array(
813                    'annotations' => array(
814                        'readonly'    => false,
815                        'destructive' => false,
816                        'idempotent'  => true,
817                    ),
818                ),
819            )
820        );
821
822        // Delete tag
823        blu_register_ability(
824            'blu/wc-delete-product-tag',
825            array(
826                'label'               => 'Delete WooCommerce Product Tag',
827                'description'         => 'Delete a WooCommerce product tag',
828                'category'            => 'blu-mcp',
829                'input_schema'        => array(
830                    'type'       => 'object',
831                    'properties' => array(
832                        'id' => array(
833                            'type'        => 'integer',
834                            'description' => 'Tag ID',
835                        ),
836                    ),
837                    'required'   => array( 'id' ),
838                ),
839                'execute_callback'    => function ( $input ) {
840                    $request = new \WP_REST_Request( 'DELETE', '/wc/v3/products/tags/' . $input['id'] );
841                    $request->set_param( 'force', true );
842                    $response = rest_do_request( $request );
843
844                    return blu_standardize_rest_response( $response );
845                },
846                'permission_callback' => fn() => current_user_can( 'delete_product_terms' ),
847                'meta'                => array(
848                    'annotations' => array(
849                        'readonly'    => false,
850                        'destructive' => true,
851                        'idempotent'  => true,
852                    ),
853                ),
854            )
855        );
856    }
857
858    /**
859     * Register product brand abilities.
860     */
861    private function register_brand_abilities(): void {
862        // List brands
863        blu_register_ability(
864            'blu/wc-list-product-brands',
865            array(
866                'label'               => 'List WooCommerce Product Brands',
867                'description'         => 'List all WooCommerce product brands',
868                'category'            => 'blu-mcp',
869                'input_schema'        => array(
870                    'type'       => 'object',
871                    'properties' => array(
872                        'patterns' => array(
873                            'type'        => 'array',
874                            'description' => 'List of relevant brands based on product name, product description and product category',
875                            'items'       => array( 'type' => 'string' ),
876                            'maxItems'    => 5,
877                        ),
878                    ),
879                ),
880                'execute_callback'    => function ( $input ) {
881                    $request = new \WP_REST_Request( 'GET', '/wc/v3/products/brands' );
882                    $brands  = array();
883                    $page    = 1;
884                    do {
885                        $request->set_query_params( array( 'page' => $page ) );
886                        $response = rest_do_request( $request );
887                        if ( is_wp_error( $response ) ) {
888                            return blu_standardize_rest_response( $response );
889                        }
890                        $data  = $response->get_data();
891                        $total = count( $data );
892                        foreach ( $data as $brand ) {
893                            $brands[] = array(
894                                'id'   => $brand['id'],
895                                'name' => $brand['name'],
896                            );
897                        }
898                        $page++;
899                    } while ( $total > 0 );
900
901                    $patterns = $input['patterns'] ?? array();
902
903                    $is_valid = blu_is_valid_input_array( $patterns, 'patterns', 0, 5 );
904                    if ( is_wp_error( $is_valid ) ) {
905                        return blu_standardize_rest_response( $is_valid );
906                    }
907
908                    blu_filter_terms_by_patterns( $patterns, $brands );
909
910                    return blu_prepare_ability_response( 200, $brands );
911                },
912                'permission_callback' => fn() => current_user_can( 'edit_products' ),
913                'meta'                => array(
914                    'annotations' => array(
915                        'readonly'    => true,
916                        'destructive' => false,
917                        'idempotent'  => true,
918                    ),
919                ),
920            )
921        );
922
923        // Add brand
924        blu_register_ability(
925            'blu/wc-add-product-brand',
926            array(
927                'label'               => 'Add WooCommerce Product Brand',
928                'description'         => 'Add one or more new WooCommerce product brand',
929                'category'            => 'blu-mcp',
930                'input_schema'        => array(
931                    'type'       => 'object',
932                    'properties' => array(
933                        'brands' => array(
934                            'type'        => 'array',
935                            'description' => 'The list of Brand name',
936                            'items'       => array( 'type' => 'string' ),
937                            'minItems'    => 1,
938                        ),
939                    ),
940                    'required'   => array( 'brands' ),
941                ),
942                'execute_callback'    => function ( $input ) {
943                    $all_brand = $input['brands'] ?? array();
944
945                    $is_valid = blu_is_valid_input_array( $all_brand, 'brands', 1 );
946
947                    if ( is_wp_error( $is_valid ) ) {
948                        return blu_standardize_rest_response( $is_valid );
949                    }
950
951                    return $this->add_product_taxonomies( $all_brand, 'brands' );
952                },
953                'permission_callback' => fn() => current_user_can( 'manage_product_terms' ),
954                'meta'                => array(
955                    'annotations' => array(
956                        'readonly'    => false,
957                        'destructive' => false,
958                        'idempotent'  => false,
959                    ),
960                ),
961            )
962        );
963
964        // Update brand
965        blu_register_ability(
966            'blu/wc-update-product-brand',
967            array(
968                'label'               => 'Update WooCommerce Product Brand',
969                'description'         => 'Update a WooCommerce product brand',
970                'category'            => 'blu-mcp',
971                'input_schema'        => array(
972                    'type'       => 'object',
973                    'properties' => array(
974                        'id'   => array(
975                            'type'        => 'integer',
976                            'description' => 'Brand ID',
977                        ),
978                        'name' => array(
979                            'type'        => 'string',
980                            'description' => 'Brand name',
981                        ),
982                    ),
983                    'required'   => array( 'id' ),
984                ),
985                'execute_callback'    => function ( $input ) {
986                    $id = $input['id'];
987                    unset( $input['id'] );
988                    $request = new \WP_REST_Request( 'PUT', '/wc/v3/products/brands/' . $id );
989                    $request->set_body_params( $input );
990                    $response = rest_do_request( $request );
991
992                    return blu_standardize_rest_response( $response );
993                },
994                'permission_callback' => fn() => current_user_can( 'manage_product_terms' ),
995                'meta'                => array(
996                    'annotations' => array(
997                        'readonly'    => false,
998                        'destructive' => false,
999                        'idempotent'  => true,
1000                    ),
1001                ),
1002            )
1003        );
1004
1005        // Delete brand
1006        blu_register_ability(
1007            'blu/wc-delete-product-brand',
1008            array(
1009                'label'               => 'Delete WooCommerce Product Brand',
1010                'description'         => 'Delete a WooCommerce product brand',
1011                'category'            => 'blu-mcp',
1012                'input_schema'        => array(
1013                    'type'       => 'object',
1014                    'properties' => array(
1015                        'id' => array(
1016                            'type'        => 'integer',
1017                            'description' => 'Brand ID',
1018                        ),
1019                    ),
1020                    'required'   => array( 'id' ),
1021                ),
1022                'execute_callback'    => function ( $input ) {
1023                    $request = new \WP_REST_Request( 'DELETE', '/wc/v3/products/brands/' . $input['id'] );
1024                    $request->set_param( 'force', true );
1025                    $response = rest_do_request( $request );
1026
1027                    return blu_standardize_rest_response( $response );
1028                },
1029                'permission_callback' => fn() => current_user_can( 'delete_product_terms' ),
1030                'meta'                => array(
1031                    'annotations' => array(
1032                        'readonly'    => false,
1033                        'destructive' => true,
1034                        'idempotent'  => true,
1035                    ),
1036                ),
1037            )
1038        );
1039    }
1040
1041
1042
1043    // Utilities.
1044
1045    /**
1046     * Add the product taxonomy with REST API
1047     *
1048     * @param array   $taxonomies The taxonomy to add.
1049     * @param string  $type The REST API type : categories|tags|brands.
1050     * @param boolean $hierarchical If add the item with hierarchical structure.
1051     *
1052     * @return array
1053     */
1054    private function add_product_taxonomies( $taxonomies, $type = 'categories', $hierarchical = false ) {
1055        $hierarchical = 'categories' === $type ? $hierarchical : false;
1056        $parent       = 0;
1057        $request      = new \WP_REST_Request( 'POST', '/wc/v3/products/' . $type );
1058        $created      = array();
1059        $existing     = array();
1060        foreach ( $taxonomies as $taxonomy ) {
1061            $args = array(
1062                'name' => trim( $taxonomy ),
1063            );
1064            if ( $hierarchical ) {
1065                $args['parent'] = $parent;
1066            }
1067            $request->set_body_params( $args );
1068            $response = rest_do_request( $request );
1069            $response = blu_standardize_rest_response( $response );
1070            if ( 400 == $response ['statusCode'] && 'term_exists' === $response['message']['code'] ) {
1071                $parent        = $response['message']['data']['resource_id'];
1072                $term_response = $this->get_taxonomy( $parent, $type );
1073                if ( 200 == $term_response['statusCode'] ) {
1074                    $existing[] = $term_response['message'];
1075                } else {
1076                    return $term_response;
1077                }
1078            } elseif ( 201 == $response ['statusCode'] ) {
1079                $parent    = $response['message']['id'];
1080                $created[] = $response['message'];
1081            } else {
1082                return $response;
1083            }
1084        }
1085
1086        $total = count( $existing ) + count( $created );
1087
1088        return array(
1089            'statusCode' => count( $created ) > 0 ? 201 : 200,
1090            'status'     => 'success',
1091            'message'    => array(
1092                'total'    => $total,
1093                'created'  => $created,
1094                'existing' => $existing,
1095            ),
1096        );
1097    }
1098
1099    /**
1100     * Get the taxonomy set to product
1101     *
1102     * @param int    $product_id The product id.
1103     * @param string $taxonomy The taxonomy to return.
1104     *
1105     * @return array|array[]
1106     */
1107    private function get_product_taxonomy_ids( $product_id, $taxonomy = 'categories' ) {
1108        $request  = new \WP_REST_Request( 'GET', '/wc/v3/products/' . $product_id );
1109        $response = rest_do_request( $request );
1110        $ids      = array();
1111        if ( is_wp_error( $response ) ) {
1112            return $ids;
1113        } else {
1114            $data = $response->get_data();
1115            if ( isset( $data[ $taxonomy ] ) && count( $data[ $taxonomy ] ) > 0 ) {
1116
1117                foreach ( $data[ $taxonomy ] as $tax ) {
1118                    if ( isset( $tax['id'] ) ) {
1119                        $ids[] = array( 'id' => $tax['id'] );
1120                    }
1121                }
1122            }
1123        }
1124
1125        return $ids;
1126    }
1127
1128    /**
1129     * Get the term by id
1130     *
1131     * @param int    $term_id The term id.
1132     * @param string $taxonomy The taxonomy type.
1133     *
1134     * @return array
1135     */
1136    private function get_taxonomy( $term_id, $taxonomy = 'categories' ) {
1137        $request = new \WP_REST_Request( 'GET', '/wc/v3/products/' . $taxonomy . '/' . $term_id );
1138
1139        $response = rest_do_request( $request );
1140
1141        return blu_standardize_rest_response( $response );
1142    }
1143}