Completed
Push — master ( 8709d3...0bb353 )
by Claudio
07:25
created

WC_REST_Product_Attributes_Controller   C

Complexity

Total Complexity 58

Size/Duplication

Total Lines 623
Duplicated Lines 19.26 %

Coupling/Cohesion

Components 2
Dependencies 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 120
loc 623
rs 6.0651
wmc 58
lcom 2
cbo 1

19 Methods

Rating   Name   Duplication   Size   Complexity  
A get_items_permissions_check() 7 7 2
A create_item_permissions_check() 7 7 2
A get_item_permissions_check() 11 11 3
A update_item_permissions_check() 11 11 3
A delete_item_permissions_check() 11 11 3
A register_routes() 60 60 1
A batch_items_permissions_check() 7 7 2
A get_items() 0 11 2
B create_item() 0 63 7
A get_item() 0 13 2
C update_item() 3 72 8
B delete_item() 3 56 7
B prepare_item_for_response() 0 29 2
A prepare_links() 0 13 1
A get_item_schema() 0 53 1
A get_collection_params() 0 6 1
A get_taxonomy() 0 13 3
A get_attribute() 0 15 3
B validate_attribute_slug() 0 11 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WC_REST_Product_Attributes_Controller often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WC_REST_Product_Attributes_Controller, and based on these observations, apply Extract Interface, too.

1
<?php
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 23 and the first side effect is on line 14.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/**
3
 * REST API Product Attributes controller
4
 *
5
 * Handles requests to the products/attributes endpoint.
6
 *
7
 * @author   WooThemes
8
 * @category API
9
 * @package  WooCommerce/API
10
 * @since    2.6.0
11
 */
12
13
if ( ! defined( 'ABSPATH' ) ) {
14
	exit;
15
}
16
17
/**
18
 * REST API Product Attributes controller class.
19
 *
20
 * @package WooCommerce/API
21
 * @extends WC_REST_Controller
22
 */
23
class WC_REST_Product_Attributes_Controller extends WC_REST_Controller {
24
25
	/**
26
	 * Endpoint namespace.
27
	 *
28
	 * @var string
29
	 */
30
	protected $namespace = 'wc/v1';
31
32
	/**
33
	 * Route base.
34
	 *
35
	 * @var string
36
	 */
37
	protected $rest_base = 'products/attributes';
38
39
	/**
40
	 * Attribute name.
41
	 *
42
	 * @var string
43
	 */
44
	protected $attribute = '';
45
46
	/**
47
	 * Register the routes for product attributes.
48
	 */
49 View Code Duplication
	public function register_routes() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
50
		register_rest_route( $this->namespace, '/' . $this->rest_base, array(
51
			array(
52
				'methods'             => WP_REST_Server::READABLE,
53
				'callback'            => array( $this, 'get_items' ),
54
				'permission_callback' => array( $this, 'get_items_permissions_check' ),
55
				'args'                => $this->get_collection_params(),
56
			),
57
			array(
58
				'methods'             => WP_REST_Server::CREATABLE,
59
				'callback'            => array( $this, 'create_item' ),
60
				'permission_callback' => array( $this, 'create_item_permissions_check' ),
61
				'args'                => array_merge( $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), array(
62
					'name' => array(
63
						'required' => true,
64
					),
65
				) ),
66
			),
67
			'schema' => array( $this, 'get_public_item_schema' ),
68
		));
69
70
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
71
			array(
72
				'methods'             => WP_REST_Server::READABLE,
73
				'callback'            => array( $this, 'get_item' ),
74
				'permission_callback' => array( $this, 'get_item_permissions_check' ),
75
				'args'                => array(
76
					'context'         => $this->get_context_param( array( 'default' => 'view' ) ),
77
				),
78
			),
79
			array(
80
				'methods'             => WP_REST_Server::EDITABLE,
81
				'callback'            => array( $this, 'update_item' ),
82
				'permission_callback' => array( $this, 'update_item_permissions_check' ),
83
				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
84
			),
85
			array(
86
				'methods'             => WP_REST_Server::DELETABLE,
87
				'callback'            => array( $this, 'delete_item' ),
88
				'permission_callback' => array( $this, 'delete_item_permissions_check' ),
89
				'args'                => array(
90
					'force' => array(
91
						'default'     => false,
92
						'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ),
93
					),
94
				),
95
			),
96
			'schema' => array( $this, 'get_public_item_schema' ),
97
		) );
98
99
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', array(
100
			array(
101
				'methods'             => WP_REST_Server::EDITABLE,
102
				'callback'            => array( $this, 'batch_items' ),
103
				'permission_callback' => array( $this, 'batch_items_permissions_check' ),
104
				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
105
			),
106
			'schema' => array( $this, 'get_public_batch_schema' ),
107
		) );
108
	}
109
110
	/**
111
	 * Check if a given request has access to read the attributes.
112
	 *
113
	 * @param  WP_REST_Request $request Full details about the request.
114
	 * @return WP_Error|boolean
115
	 */
116 View Code Duplication
	public function get_items_permissions_check( $request ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
117
		if ( ! wc_rest_check_manager_permissions( 'attributes', 'read' ) ) {
118
			return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
119
		}
120
121
		return true;
122
	}
123
124
	/**
125
	 * Check if a given request has access to create a attribute.
126
	 *
127
	 * @param  WP_REST_Request $request Full details about the request.
128
	 * @return WP_Error|boolean
129
	 */
130 View Code Duplication
	public function create_item_permissions_check( $request ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
131
		if ( ! wc_rest_check_manager_permissions( 'attributes', 'create' ) ) {
132
			return new WP_Error( 'woocommerce_rest_cannot_create', __( 'Sorry, you cannot create new resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
133
		}
134
135
		return true;
136
	}
137
138
	/**
139
	 * Check if a given request has access to read a attribute.
140
	 *
141
	 * @param  WP_REST_Request $request Full details about the request.
142
	 * @return WP_Error|boolean
143
	 */
144 View Code Duplication
	public function get_item_permissions_check( $request ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
145
		if ( ! $this->get_taxonomy( $request ) ) {
146
			return new WP_Error( "woocommerce_rest_taxonomy_invalid", __( "Resource doesn't exist.", 'woocommerce' ), array( 'status' => 404 ) );
147
		}
148
149
		if ( ! wc_rest_check_manager_permissions( 'attributes', 'read' ) ) {
150
			return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view this resource', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
151
		}
152
153
		return true;
154
	}
155
156
	/**
157
	 * Check if a given request has access to update a attribute.
158
	 *
159
	 * @param  WP_REST_Request $request Full details about the request.
160
	 * @return WP_Error|boolean
161
	 */
162 View Code Duplication
	public function update_item_permissions_check( $request ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
163
		if ( ! $this->get_taxonomy( $request ) ) {
164
			return new WP_Error( "woocommerce_rest_taxonomy_invalid", __( "Resource doesn't exist.", 'woocommerce' ), array( 'status' => 404 ) );
165
		}
166
167
		if ( ! wc_rest_check_manager_permissions( 'attributes', 'edit' ) ) {
168
			return new WP_Error( 'woocommerce_rest_cannot_update', __( 'Sorry, you cannot update resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
169
		}
170
171
		return true;
172
	}
173
174
	/**
175
	 * Check if a given request has access to delete a attribute.
176
	 *
177
	 * @param  WP_REST_Request $request Full details about the request.
178
	 * @return WP_Error|boolean
179
	 */
180 View Code Duplication
	public function delete_item_permissions_check( $request ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
181
		if ( ! $this->get_taxonomy( $request ) ) {
182
			return new WP_Error( "woocommerce_rest_taxonomy_invalid", __( "Resource doesn't exist.", 'woocommerce' ), array( 'status' => 404 ) );
183
		}
184
185
		if ( ! wc_rest_check_manager_permissions( 'attributes', 'delete' ) ) {
186
			return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'Sorry, you cannot delete resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
187
		}
188
189
		return true;
190
	}
191
192
	/**
193
	 * Check if a given request has access batch create, update and delete items.
194
	 *
195
	 * @param  WP_REST_Request $request Full details about the request.
196
	 * @return boolean
197
	 */
198 View Code Duplication
	public function batch_items_permissions_check( $request ) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
199
		if ( ! wc_rest_check_manager_permissions( 'attributes', 'batch' ) ) {
200
			return new WP_Error( 'woocommerce_rest_cannot_batch', __( 'Sorry, you are not allowed to manipule this resource.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
201
		}
202
203
		return true;
204
	}
205
206
	/**
207
	 * Get all attributes.
208
	 *
209
	 * @param WP_REST_Request $request
210
	 * @return array
211
	 */
212
	public function get_items( $request ) {
213
		$attributes = wc_get_attribute_taxonomies();
214
		$data       = array();
215
		foreach ( $attributes as $attribute_obj ) {
216
			$attribute = $this->prepare_item_for_response( $attribute_obj, $request );
217
			$attribute = $this->prepare_response_for_collection( $attribute );
218
			$data[] = $attribute;
219
		}
220
221
		return rest_ensure_response( $data );
222
	}
223
224
	/**
225
	 * Create a single attribute.
226
	 *
227
	 * @param WP_REST_Request $request Full details about the request.
228
	 * @return WP_REST_Request|WP_Error
229
	 */
230
	public function create_item( $request ) {
231
		global $wpdb;
232
233
		$args = array(
234
			'attribute_label'   => $request['name'],
235
			'attribute_name'    => $request['slug'],
236
			'attribute_type'    => ! empty( $request['type'] ) ? $request['type'] : 'select',
237
			'attribute_orderby' => ! empty( $request['order_by'] ) ? $request['order_by'] : 'menu_order',
238
			'attribute_public'  => true === $request['has_archives'],
239
		);
240
241
		// Set the attribute slug.
242
		if ( empty( $args['attribute_name'] ) ) {
243
			$args['attribute_name'] = wc_sanitize_taxonomy_name( stripslashes( $args['attribute_label'] ) );
244
		} else {
245
			$args['attribute_name'] = preg_replace( '/^pa\_/', '', wc_sanitize_taxonomy_name( stripslashes( $args['attribute_name'] ) ) );
246
		}
247
248
		$valid_slug = $this->validate_attribute_slug( $args['attribute_name'], true );
249
		if ( is_wp_error( $valid_slug ) ) {
250
			return $valid_slug;
251
		}
252
253
		$insert = $wpdb->insert(
254
			$wpdb->prefix . 'woocommerce_attribute_taxonomies',
255
			$args,
256
			array( '%s', '%s', '%s', '%s', '%d' )
257
		);
258
259
		// Checks for errors.
260
		if ( is_wp_error( $insert ) ) {
261
			return new WP_Error( 'woocommerce_rest_cannot_create', $insert->get_error_message(), array( 'status' => 400 ) );
262
		}
263
264
		$attribute = $this->get_attribute( $wpdb->insert_id );
265
266
		if ( is_wp_error( $attribute ) ) {
267
			return $attribute;
268
		}
269
270
		$this->update_additional_fields_for_object( $attribute, $request );
0 ignored issues
show
Bug introduced by
It seems like $attribute defined by $this->get_attribute($wpdb->insert_id) on line 264 can also be of type object<stdClass>; however, WP_REST_Controller::upda...nal_fields_for_object() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
271
272
		/**
273
		 * Fires after a single product attribute is created or updated via the REST API.
274
		 *
275
		 * @param stdObject       $attribute Inserted attribute object.
276
		 * @param WP_REST_Request $request   Request object.
277
		 * @param boolean         $creating  True when creating attribute, false when updating.
278
		 */
279
		do_action( 'woocommerce_rest_insert_product_attribute', $attribute, $request, true );
280
281
		$request->set_param( 'context', 'edit' );
282
		$response = $this->prepare_item_for_response( $attribute, $request );
0 ignored issues
show
Bug introduced by
It seems like $attribute defined by $this->get_attribute($wpdb->insert_id) on line 264 can also be of type object<stdClass>; however, WC_REST_Product_Attribut...are_item_for_response() does only seem to accept object<obj>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
283
		$response = rest_ensure_response( $response );
284
		$response->set_status( 201 );
285
		$response->header( 'Location', rest_url( '/' . $this->namespace . '/' . $this->rest_base . '/' . $attribute->attribute_id ) );
286
287
		// Clear transients.
288
		flush_rewrite_rules();
289
		delete_transient( 'wc_attribute_taxonomies' );
290
291
		return $response;
292
	}
293
294
	/**
295
	 * Get a single attribute.
296
	 *
297
	 * @param WP_REST_Request $request Full details about the request.
298
	 * @return WP_REST_Request|WP_Error
299
	 */
300
	public function get_item( $request ) {
301
		global $wpdb;
302
303
		$attribute = $this->get_attribute( $request['id'] );
304
305
		if ( is_wp_error( $attribute ) ) {
306
			return $attribute;
307
		}
308
309
		$response = $this->prepare_item_for_response( $attribute, $request );
0 ignored issues
show
Bug introduced by
It seems like $attribute defined by $this->get_attribute($request['id']) on line 303 can also be of type object<stdClass>; however, WC_REST_Product_Attribut...are_item_for_response() does only seem to accept object<obj>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
310
311
		return rest_ensure_response( $response );
312
	}
313
314
	/**
315
	 * Update a single term from a taxonomy.
316
	 *
317
	 * @param WP_REST_Request $request Full details about the request.
318
	 * @return WP_REST_Request|WP_Error
319
	 */
320
	public function update_item( $request ) {
321
		global $wpdb;
322
323
		$id     = (int) $request['id'];
324
		$format = array( '%s', '%s', '%s', '%s', '%d' );
325
		$args   = array(
326
			'attribute_label'   => $request['name'],
327
			'attribute_name'    => $request['slug'],
328
			'attribute_type'    => $request['type'],
329
			'attribute_orderby' => $request['order_by'],
330
			'attribute_public'  => $request['has_archives'],
331
		);
332
333
		$i = 0;
334
		foreach ( $args as $key => $value ) {
335
			if ( empty( $value ) && ! is_bool( $value ) ) {
336
				unset( $args[ $key ] );
337
				unset( $format[ $i ] );
338
			}
339
340
			$i++;
341
		}
342
343
		// Set the attribute slug.
344
		if ( ! empty( $args['attribute_name'] ) ) {
345
			$args['attribute_name'] = preg_replace( '/^pa\_/', '', wc_sanitize_taxonomy_name( stripslashes( $args['attribute_name'] ) ) );
346
347
			$valid_slug = $this->validate_attribute_slug( $args['attribute_name'], true );
348
			if ( is_wp_error( $valid_slug ) ) {
349
				return $valid_slug;
350
			}
351
		}
352
353
		$update = $wpdb->update(
354
			$wpdb->prefix . 'woocommerce_attribute_taxonomies',
355
			$args,
356
			array( 'attribute_id' => $id ),
357
			$format,
358
			array( '%d' )
359
		);
360
361
		// Checks for errors.
362 View Code Duplication
		if ( false === $update ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
363
			return new WP_Error( 'woocommerce_rest_cannot_edit', __( 'Could not edit the attribute', 'woocommerce' ), array( 'status' => 400 ) );
364
		}
365
366
		$attribute = $this->get_attribute( $id );
367
368
		if ( is_wp_error( $attribute ) ) {
369
			return $attribute;
370
		}
371
372
		$this->update_additional_fields_for_object( $attribute, $request );
0 ignored issues
show
Bug introduced by
It seems like $attribute defined by $this->get_attribute($id) on line 366 can also be of type object<stdClass>; however, WP_REST_Controller::upda...nal_fields_for_object() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
373
374
		/**
375
		 * Fires after a single product attribute is created or updated via the REST API.
376
		 *
377
		 * @param stdObject       $attribute Inserted attribute object.
378
		 * @param WP_REST_Request $request   Request object.
379
		 * @param boolean         $creating  True when creating attribute, false when updating.
380
		 */
381
		do_action( 'woocommerce_rest_insert_product_attribute', $attribute, $request, false );
382
383
		$request->set_param( 'context', 'edit' );
384
		$response = $this->prepare_item_for_response( $attribute, $request );
0 ignored issues
show
Bug introduced by
It seems like $attribute defined by $this->get_attribute($id) on line 366 can also be of type object<stdClass>; however, WC_REST_Product_Attribut...are_item_for_response() does only seem to accept object<obj>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
385
386
		// Clear transients.
387
		flush_rewrite_rules();
388
		delete_transient( 'wc_attribute_taxonomies' );
389
390
		return rest_ensure_response( $response );
391
	}
392
393
	/**
394
	 * Delete a single attribute.
395
	 *
396
	 * @param WP_REST_Request $request Full details about the request.
397
	 * @return WP_REST_Response|WP_Error
398
	 */
399
	public function delete_item( $request ) {
400
		global $wpdb;
401
402
		$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
403
404
		// We don't support trashing for this type, error out.
405
		if ( ! $force ) {
406
			return new WP_Error( 'woocommerce_rest_trash_not_supported', __( 'Resource does not support trashing.', 'woocommerce' ), array( 'status' => 501 ) );
407
		}
408
409
		$attribute = $this->get_attribute( $request['id'] );
410
411
		if ( is_wp_error( $attribute ) ) {
412
			return $attribute;
413
		}
414
415
		$request->set_param( 'context', 'edit' );
416
		$response = $this->prepare_item_for_response( $attribute, $request );
0 ignored issues
show
Bug introduced by
It seems like $attribute defined by $this->get_attribute($request['id']) on line 409 can also be of type object<stdClass>; however, WC_REST_Product_Attribut...are_item_for_response() does only seem to accept object<obj>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
417
418
		$deleted = $wpdb->delete(
419
			$wpdb->prefix . 'woocommerce_attribute_taxonomies',
420
			array( 'attribute_id' => $attribute->attribute_id ),
421
			array( '%d' )
422
		);
423
424 View Code Duplication
		if ( false === $deleted ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
425
			return new WP_Error( 'woocommerce_rest_cannot_delete', __( 'The resource cannot be deleted.', 'woocommerce' ), array( 'status' => 500 ) );
426
		}
427
428
		$taxonomy = wc_attribute_taxonomy_name( $attribute->attribute_name );
429
430
		if ( taxonomy_exists( $taxonomy ) ) {
431
			$terms = get_terms( $taxonomy, 'orderby=name&hide_empty=0' );
432
			foreach ( $terms as $term ) {
433
				wp_delete_term( $term->term_id, $taxonomy );
434
			}
435
		}
436
437
		/**
438
		 * Fires after a single attribute is deleted via the REST API.
439
		 *
440
		 * @param stdObject        $attribute     The deleted attribute.
441
		 * @param WP_REST_Response $response The response data.
442
		 * @param WP_REST_Request  $request  The request sent to the API.
443
		 */
444
		do_action( 'woocommerce_rest_delete_product_attribute', $attribute, $response, $request );
445
446
		// Fires woocommerce_attribute_deleted hook.
447
		do_action( 'woocommerce_attribute_deleted', $attribute->attribute_id, $attribute->attribute_name, $taxonomy );
448
449
		// Clear transients.
450
		flush_rewrite_rules();
451
		delete_transient( 'wc_attribute_taxonomies' );
452
453
		return $response;
454
	}
455
456
	/**
457
	 * Prepare a single product attribute output for response.
458
	 *
459
	 * @param obj $item Term object.
460
	 * @param WP_REST_Request $request
461
	 * @return WP_REST_Response $response
462
	 */
463
	public function prepare_item_for_response( $item, $request ) {
464
		$data = array(
465
			'id'           => (int) $item->attribute_id,
466
			'name'         => $item->attribute_label,
467
			'slug'         => wc_attribute_taxonomy_name( $item->attribute_name ),
468
			'type'         => $item->attribute_type,
469
			'order_by'     => $item->attribute_orderby,
470
			'has_archives' => (bool) $item->attribute_public,
471
		);
472
473
		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
474
		$data    = $this->add_additional_fields_to_object( $data, $request );
475
		$data    = $this->filter_response_by_context( $data, $context );
476
477
		$response = rest_ensure_response( $data );
478
479
		$response->add_links( $this->prepare_links( $item ) );
480
481
		/**
482
		 * Filter a attribute item returned from the API.
483
		 *
484
		 * Allows modification of the product attribute data right before it is returned.
485
		 *
486
		 * @param WP_REST_Response  $response  The response object.
487
		 * @param object            $item      The original attribute object.
488
		 * @param WP_REST_Request   $request   Request used to generate the response.
489
		 */
490
		return apply_filters( 'woocommerce_rest_prepare_product_attribute', $response, $item, $request );
491
	}
492
493
	/**
494
	 * Prepare links for the request.
495
	 *
496
	 * @param object $attribute Attribute object.
497
	 * @param WP_REST_Request $request Full details about the request.
0 ignored issues
show
Bug introduced by
There is no parameter named $request. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
498
	 * @return array Links for the given attribute.
499
	 */
500
	protected function prepare_links( $attribute ) {
501
		$base  = '/' . $this->namespace . '/' . $this->rest_base;
502
		$links = array(
503
			'self' => array(
504
				'href' => rest_url( trailingslashit( $base ) . $attribute->attribute_id ),
505
			),
506
			'collection' => array(
507
				'href' => rest_url( $base ),
508
			),
509
		);
510
511
		return $links;
512
	}
513
514
	/**
515
	 * Get the Attribute's schema, conforming to JSON Schema.
516
	 *
517
	 * @return array
518
	 */
519
	public function get_item_schema() {
520
		$schema = array(
521
			'$schema'              => 'http://json-schema.org/draft-04/schema#',
522
			'title'                => 'product_attribute',
523
			'type'                 => 'object',
524
			'properties'           => array(
525
				'id' => array(
526
					'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
527
					'type'        => 'integer',
528
					'context'     => array( 'view', 'edit' ),
529
					'readonly'    => true,
530
				),
531
				'name' => array(
532
					'description' => __( 'Attribute name.', 'woocommerce' ),
533
					'type'        => 'string',
534
					'context'     => array( 'view', 'edit' ),
535
					'arg_options' => array(
536
						'sanitize_callback' => 'sanitize_text_field',
537
					),
538
				),
539
				'slug' => array(
540
					'description' => __( 'An alphanumeric identifier for the resource unique to its type.', 'woocommerce' ),
541
					'type'        => 'string',
542
					'context'     => array( 'view', 'edit' ),
543
					'arg_options' => array(
544
						'sanitize_callback' => 'sanitize_title',
545
					),
546
				),
547
				'type' => array(
548
					'description' => __( 'Type of attribute.', 'woocommerce' ),
549
					'type'        => 'string',
550
					'default'     => 'select',
551
					'enum'        => array_keys( wc_get_attribute_types() ),
552
					'context'     => array( 'view', 'edit' ),
553
				),
554
				'order_by' => array(
555
					'description' => __( 'Default sort order.', 'woocommerce' ),
556
					'type'        => 'string',
557
					'default'     => 'menu_order',
558
					'enum'        => array( 'menu_order', 'name', 'name_num', 'id' ),
559
					'context'     => array( 'view', 'edit' ),
560
				),
561
				'has_archives' => array(
562
					'description' => __( 'Enable/Disable attribute archives.', 'woocommerce' ),
563
					'type'        => 'boolean',
564
					'default'     => false,
565
					'context'     => array( 'view', 'edit' ),
566
				),
567
			),
568
		);
569
570
		return $this->add_additional_fields_schema( $schema );
571
	}
572
573
	/**
574
	 * Get the query params for collections
575
	 *
576
	 * @return array
577
	 */
578
	public function get_collection_params() {
579
		$params = array();
580
		$params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
581
582
		return $params;
583
	}
584
585
	/**
586
	 * Get attribute name.
587
	 *
588
	 * @param WP_REST_Request $request Full details about the request.
589
	 * @return int|WP_Error
590
	 */
591
	protected function get_taxonomy( $request ) {
592
		if ( '' !== $this->attribute ) {
593
			return $this->attribute;
594
		}
595
596
		if ( $request['id'] ) {
597
			$name = wc_attribute_taxonomy_name_by_id( (int) $request['id'] );
598
599
			$this->attribute = $name;
600
		}
601
602
		return $this->attribute;
603
	}
604
605
	/**
606
	 * Get attribute data.
607
	 *
608
	 * @param int $id Attribute ID.
609
	 * @return stdClass|WP_Error
610
	 */
611
	protected function get_attribute( $id ) {
612
		global $wpdb;
613
614
		$attribute = $wpdb->get_row( $wpdb->prepare( "
615
			SELECT *
616
			FROM {$wpdb->prefix}woocommerce_attribute_taxonomies
617
			WHERE attribute_id = %d
618
		 ", $id ) );
619
620
		if ( is_wp_error( $attribute ) || is_null( $attribute ) ) {
621
			return new WP_Error( 'woocommerce_rest_attribute_invalid', __( "Resource doesn't exist.", 'woocommerce' ), array( 'status' => 404 ) );
622
		}
623
624
		return $attribute;
625
	}
626
627
	/**
628
	 * Validate attribute slug.
629
	 *
630
	 * @param string $slug
631
	 * @param bool $new_data
632
	 * @return bool|WP_Error
633
	 */
634
	protected function validate_attribute_slug( $slug, $new_data = true ) {
635
		if ( strlen( $slug ) >= 28 ) {
636
			return new WP_Error( 'woocommerce_rest_invalid_product_attribute_slug_too_long', sprintf( __( 'Slug "%s" is too long (28 characters max).', 'woocommerce' ), $slug ), array( 'status' => 400 ) );
637
		} else if ( wc_check_if_attribute_name_is_reserved( $slug ) ) {
638
			return new WP_Error( 'woocommerce_rest_invalid_product_attribute_slug_reserved_name', sprintf( __( 'Slug "%s" is not allowed because it is a reserved term.', 'woocommerce' ), $slug ), array( 'status' => 400 ) );
639
		} else if ( $new_data && taxonomy_exists( wc_attribute_taxonomy_name( $slug ) ) ) {
640
			return new WP_Error( 'woocommerce_rest_invalid_product_attribute_slug_already_exists', sprintf( __( 'Slug "%s" is already in use.', 'woocommerce' ), $slug ), array( 'status' => 400 ) );
641
		}
642
643
		return true;
644
	}
645
}
646