• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wp-graphql / wp-graphql-woocommerce / 23354837106

20 Mar 2026 05:31PM UTC coverage: 83.637% (+0.4%) from 83.244%
23354837106

push

github

web-flow
feat: Product and product attribute mutations (#851)

* devops: Product and product attribute mutation tests generated

* feat: Product Mutations implemented and tested

* chore: Linter and PHPStan compliance met

* fix: resolve test failures in product and variation mutation tests

- Removed duplicate requires for payment-method classes (rebase artifact)
- Wrapped bare `attributes` queries in `... on SimpleProduct` inline
  fragments (attributes field is on ProductWithAttributes interface,
  not the base Product type)
- Fixed attribute label expectations to match factory output (lowercase)
- Fixed relay ID prefix from 'product'/'product_variation' to 'post'
  to match WPGraphQL's actual encoding
- Cache WP_Post before force-deletion and re-cache after so the Product
  model's type resolver can still determine the GraphQL type in the
  response
- Added wp_cache_flush() before asserting product deletion to avoid
  false positives from the re-cached post

* chore: fix PHPCS lint errors in mutation files

* fix: resolve CI test failures for attribute and variation mutations

- Fixed ProductVariation type registration in schema filters so the
  variation mutation output field resolves correctly
- Renamed attribute slug from 'pattern' to 'fabric' in
  ProductAttributeMutationsTest to avoid collision with the pattern
  attribute created by ProductAttributeQueriesTest
- Fixed PHPStan errors for redundant is_wp_error checks
- Updated IntrospectionQueryTest

1251 of 1437 new or added lines in 23 files covered. (87.06%)

13995 of 16733 relevant lines covered (83.64%)

91.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.95
/includes/mutation/class-product-attribute-term-create.php
1
<?php
2
/**
3
 * Mutation - createProductAttributeTerm
4
 *
5
 * Registers mutation for creating a product attribute term.
6
 *
7
 * @package WPGraphQL\WooCommerce\Mutation
8
 * @since TBD
9
 */
10

11
namespace WPGraphQL\WooCommerce\Mutation;
12

13
use GraphQL\Error\UserError;
14
use GraphQL\Type\Definition\ResolveInfo;
15
use WPGraphQL\AppContext;
16

17
/**
18
 * Class Product_Attribute_Term_Create
19
 */
20
class Product_Attribute_Term_Create {
21
        /**
22
         * Registers mutation
23
         *
24
         * @return void
25
         */
26
        public static function register_mutation() {
27
                register_graphql_mutation(
182✔
28
                        'createProductAttributeTerm',
182✔
29
                        [
182✔
30
                                'inputFields'         => self::get_input_fields(),
182✔
31
                                'outputFields'        => self::get_output_fields(),
182✔
32
                                'mutateAndGetPayload' => [ self::class, 'mutate_and_get_payload' ],
182✔
33
                        ]
182✔
34
                );
182✔
35
        }
36

37
        /**
38
         * Defines the mutation input field configuration
39
         *
40
         * @return array
41
         */
42
        public static function get_input_fields() {
43
                return [
182✔
44
                        'attributeId' => [
182✔
45
                                'type'        => [ 'non_null' => 'Int' ],
182✔
46
                                'description' => __( 'The ID of the attribute to which the term belongs.', 'wp-graphql-woocommerce' ),
182✔
47
                        ],
182✔
48
                        'name'        => [
182✔
49
                                'type'        => [ 'non_null' => 'String' ],
182✔
50
                                'description' => __( 'The name of the term.', 'wp-graphql-woocommerce' ),
182✔
51
                        ],
182✔
52
                        'slug'        => [
182✔
53
                                'type'        => 'String',
182✔
54
                                'description' => __( 'The slug of the term.', 'wp-graphql-woocommerce' ),
182✔
55
                        ],
182✔
56
                        'description' => [
182✔
57
                                'type'        => 'String',
182✔
58
                                'description' => __( 'The description of the term.', 'wp-graphql-woocommerce' ),
182✔
59
                        ],
182✔
60
                        'menuOrder'   => [
182✔
61
                                'type'        => 'Int',
182✔
62
                                'description' => __( 'The order of the term in the menu.', 'wp-graphql-woocommerce' ),
182✔
63
                        ],
182✔
64
                ];
182✔
65
        }
66

67
        /**
68
         * Defines the mutation output field configuration
69
         *
70
         * @return array
71
         */
72
        public static function get_output_fields() {
73
                return [
182✔
74
                        'term' => [
182✔
75
                                'type'    => 'ProductAttributeTermObject',
182✔
76
                                'resolve' => static function ( $payload ) {
182✔
77
                                        return (object) $payload['term'];
1✔
78
                                },
182✔
79
                        ],
182✔
80
                ];
182✔
81
        }
82

83
        /**
84
         * Defines the mutation data modification closure.
85
         *
86
         * @param array                                $input    Mutation input.
87
         * @param \WPGraphQL\AppContext                $context  AppContext instance.
88
         * @param \GraphQL\Type\Definition\ResolveInfo $info     ResolveInfo instance. Can be
89
         * use to get info about the current node in the GraphQL tree.
90
         *
91
         * @throws \GraphQL\Error\UserError Invalid ID provided | Lack of capabilities.
92
         *
93
         * @return array
94
         */
95
        public static function mutate_and_get_payload( $input, AppContext $context, ResolveInfo $info ) {
96
                if ( ! $input['attributeId'] ) {
2✔
NEW
97
                        throw new UserError( __( 'An attributeId is required to create a new product attribute term.', 'wp-graphql-woocommerce' ) );
×
98
                }
99

100
                $context  = 'createProductAttributeTerm' === $info->fieldName ? 'create' : 'edit';
2✔
101
                $taxonomy = wc_attribute_taxonomy_name_by_id( $input['attributeId'] );
2✔
102
                if ( empty( $taxonomy ) ) {
2✔
NEW
103
                        throw new UserError( __( 'Invalid attributeId.', 'wp-graphql-woocommerce' ) );
×
104
                }
105

106
                if ( ! wc_rest_check_product_term_permissions( $taxonomy, $context ) ) {
2✔
107
                        throw new UserError( __( 'Sorry, you are not allowed to create product attribute terms.', 'wp-graphql-woocommerce' ) );
2✔
108
                }
109

110
                $id   = isset( $input['id'] ) ? $input['id'] : null;
2✔
111
                $args = [];
2✔
112

113
                if ( ! empty( $input['description'] ) ) {
2✔
114
                        $args['description'] = $input['description'];
2✔
115
                }
116

117
                if ( ! empty( $input['slug'] ) ) {
2✔
118
                        $args['slug'] = $input['slug'];
2✔
119
                }
120

121
                if ( $id && ! empty( $input['name'] ) ) {
2✔
122
                        $args['name'] = $input['name'];
1✔
123
                }
124

125
                $term = null;
2✔
126
                if ( $id ) {
2✔
127
                        $term = get_term( $id, $taxonomy );
1✔
128
                }
129

130
                if ( is_wp_error( $term ) ) {
2✔
NEW
131
                        throw new UserError( $term->get_error_message() );
×
132
                } elseif ( $term && ! wc_rest_check_product_term_permissions( $taxonomy, $context, $term->term_id ) ) {
2✔
NEW
133
                        throw new UserError( __( 'Sorry, you are not allowed to update this product attribute term.', 'wp-graphql-woocommerce' ) );
×
134
                }
135

136
                if ( $id ) {
2✔
137
                        $term = wp_update_term( $id, $taxonomy, $args );
1✔
138
                } elseif ( ! empty( $input['name'] ) ) {
1✔
139
                        $name = $input['name'];
1✔
140
                        $term = wp_insert_term( $name, $taxonomy, $args );
1✔
141
                } else {
NEW
142
                        $updating = 'updateProductAttributeTerm' === $info->fieldName;
×
NEW
143
                        throw new UserError(
×
NEW
144
                                $updating
×
NEW
145
                                        ? __( 'A name is required to create a new product attribute term.', 'wp-graphql-woocommerce' )
×
NEW
146
                                        : __( 'A valid term "id" and changeable parameter are required to update a product attribute term.', 'wp-graphql-woocommerce' )
×
NEW
147
                        );
×
148
                }
149

150
                if ( is_wp_error( $term ) ) {
2✔
NEW
151
                        throw new UserError( $term->get_error_message() );
×
152
                }
153

154
                /**
155
                 * Newly created product attribute term.
156
                 *
157
                 * @var \WP_Term|\WP_Error|null $term
158
                 */
159
                $term = get_term( $term['term_id'], $taxonomy );
2✔
160
                if ( ! $term ) {
2✔
NEW
161
                        throw new UserError( __( 'Failed to retrieve term for modification. Please check input.', 'wp-graphql-woocommerce' ) );
×
162
                } elseif ( is_wp_error( $term ) ) {
2✔
NEW
163
                        throw new UserError( $term->get_error_message() );
×
164
                }
165

166
                if ( isset( $input['menuOrder'] ) ) {
2✔
167
                        $success = update_term_meta( $term->term_id, 'order_' . $taxonomy, $input['menuOrder'] );
2✔
168
                        if ( is_wp_error( $success ) ) {
2✔
NEW
169
                                throw new UserError( $success->get_error_message() );
×
170
                        }
171
                }
172

173
                $menu_order = get_term_meta( $term->term_id, 'order_' . $taxonomy, true );
2✔
174
                $data       = [
2✔
175
                        'id'          => $term->term_id,
2✔
176
                        'name'        => $term->name,
2✔
177
                        'slug'        => $term->slug,
2✔
178
                        'description' => $term->description,
2✔
179
                        'menu_order'  => (int) $menu_order,
2✔
180
                        'count'       => (int) $term->count,
2✔
181
                ];
2✔
182

183
                return [ 'term' => $data ];
2✔
184
        }
185
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc