WC_REST_Webhooks_Controller::update_item()   C
last analyzed

Complexity

Conditions 13
Paths 68

Size

Total Lines 74
Code Lines 37

Duplication

Lines 22
Ratio 29.73 %

Importance

Changes 0
Metric Value
cc 13
dl 22
loc 74
rs 5.3888
c 0
b 0
f 0
eloc 37
nc 68
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * REST API Webhooks controller
4
 *
5
 * Handles requests to the /webhooks 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 Webhooks controller class.
19
 *
20
 * @package WooCommerce/API
21
 * @extends WC_REST_Posts_Controller
22
 */
23
class WC_REST_Webhooks_Controller extends WC_REST_Posts_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 = 'webhooks';
38
39
	/**
40
	 * Post type.
41
	 *
42
	 * @var string
43
	 */
44
	protected $post_type = 'shop_webhook';
45
46
	/**
47
	 * Initialize Webhooks actions.
48
	 */
49
	public function __construct() {
50
		add_filter( "woocommerce_rest_{$this->post_type}_query", array( $this, 'query_args' ), 10, 2 );
51
	}
52
53
	/**
54
	 * Register the routes for webhooks.
55
	 */
56
	public function register_routes() {
57
		register_rest_route( $this->namespace, '/' . $this->rest_base, array(
58
			array(
59
				'methods'             => WP_REST_Server::READABLE,
60
				'callback'            => array( $this, 'get_items' ),
61
				'permission_callback' => array( $this, 'get_items_permissions_check' ),
62
				'args'                => $this->get_collection_params(),
63
			),
64
			array(
65
				'methods'             => WP_REST_Server::CREATABLE,
66
				'callback'            => array( $this, 'create_item' ),
67
				'permission_callback' => array( $this, 'create_item_permissions_check' ),
68
				'args'                => array_merge( $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), array(
69
					'topic' => array(
70
						'required' => true,
71
					),
72
					'delivery_url' => array(
73
						'required' => true,
74
					),
75
				) ),
76
			),
77
			'schema' => array( $this, 'get_public_item_schema' ),
78
		) );
79
80
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
81
			array(
82
				'methods'             => WP_REST_Server::READABLE,
83
				'callback'            => array( $this, 'get_item' ),
84
				'permission_callback' => array( $this, 'get_item_permissions_check' ),
85
				'args'                => array(
86
					'context' => $this->get_context_param( array( 'default' => 'view' ) ),
87
				),
88
			),
89
			array(
90
				'methods'             => WP_REST_Server::EDITABLE,
91
				'callback'            => array( $this, 'update_item' ),
92
				'permission_callback' => array( $this, 'update_item_permissions_check' ),
93
				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
94
			),
95
			array(
96
				'methods'             => WP_REST_Server::DELETABLE,
97
				'callback'            => array( $this, 'delete_item' ),
98
				'permission_callback' => array( $this, 'delete_item_permissions_check' ),
99
				'args'                => array(
100
					'force' => array(
101
						'default'     => false,
102
						'description' => __( 'Required to be true, as resource does not support trashing.', 'woocommerce' ),
103
					),
104
				),
105
			),
106
			'schema' => array( $this, 'get_public_item_schema' ),
107
		) );
108
109
		register_rest_route( $this->namespace, '/' . $this->rest_base . '/batch', array(
110
			array(
111
				'methods'             => WP_REST_Server::EDITABLE,
112
				'callback'            => array( $this, 'batch_items' ),
113
				'permission_callback' => array( $this, 'batch_items_permissions_check' ),
114
				'args'                => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
115
			),
116
			'schema' => array( $this, 'get_public_batch_schema' ),
117
		) );
118
	}
119
120
	/**
121
	 * Create a single webhook.
122
	 *
123
	 * @param WP_REST_Request $request Full details about the request.
124
	 * @return WP_Error|WP_REST_Response
125
	 */
126
	public function create_item( $request ) {
127
		if ( ! empty( $request['id'] ) ) {
128
			return new WP_Error( "woocommerce_rest_{$this->post_type}_exists", sprintf( __( 'Cannot create existing %s.', 'woocommerce' ), $this->post_type ), array( 'status' => 400 ) );
129
		}
130
131
		// Validate topic.
132
		if ( empty( $request['topic'] ) || ! wc_is_webhook_valid_topic( strtolower( $request['topic'] ) ) ) {
133
			return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_topic", __( 'Webhook topic is required and must be valid.', 'woocommerce' ), array( 'status' => 400 ) );
134
		}
135
136
		// Validate delivery URL.
137
		if ( empty( $request['delivery_url'] ) || ! wc_is_valid_url( $request['delivery_url'] ) ) {
138
			return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_delivery_url", __( 'Webhook delivery URL must be a valid URL starting with http:// or https://.', 'woocommerce' ), array( 'status' => 400 ) );
139
		}
140
141
		$post = $this->prepare_item_for_database( $request );
142
		if ( is_wp_error( $post ) ) {
143
			return $post;
144
		}
145
146
		$post->post_type = $this->post_type;
147
		$post_id = wp_insert_post( $post, true );
148
149 View Code Duplication
		if ( is_wp_error( $post_id ) ) {
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...
150
151
			if ( in_array( $post_id->get_error_code(), array( 'db_insert_error' ) ) ) {
152
				$post_id->add_data( array( 'status' => 500 ) );
153
			} else {
154
				$post_id->add_data( array( 'status' => 400 ) );
155
			}
156
			return $post_id;
157
		}
158
		$post->ID = $post_id;
159
160
		$webhook = new WC_Webhook( $post_id );
161
162
		// Set topic.
163
		$webhook->set_topic( $request['topic'] );
164
165
		// Set delivery URL.
166
		$webhook->set_delivery_url( $request['delivery_url'] );
167
168
		// Set secret.
169
		$webhook->set_secret( ! empty( $request['secret'] ) ? $request['secret'] : wc_webhook_generate_secret() );
170
171
		// Set status.
172
		if ( ! empty( $request['status'] ) ) {
173
			$webhook->update_status( $request['status'] );
174
		}
175
176
		$post = get_post( $post_id );
177
		$this->update_additional_fields_for_object( $post, $request );
178
179
		/**
180
		 * Fires after a single item is created or updated via the REST API.
181
		 *
182
		 * @param WP_Post         $post      Inserted object.
183
		 * @param WP_REST_Request $request   Request object.
184
		 * @param boolean         $creating  True when creating item, false when updating.
185
		 */
186
		do_action( "woocommerce_rest_insert_{$this->post_type}", $post, $request, true );
187
188
		$request->set_param( 'context', 'edit' );
189
		$response = $this->prepare_item_for_response( $post, $request );
190
		$response = rest_ensure_response( $response );
191
		$response->set_status( 201 );
192
		$response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $post_id ) ) );
193
194
		// Send ping.
195
		$webhook->deliver_ping();
196
197
		// Clear cache.
198
		delete_transient( 'woocommerce_webhook_ids' );
199
200
		return $response;
201
	}
202
203
	/**
204
	 * Update a single webhook.
205
	 *
206
	 * @param WP_REST_Request $request Full details about the request.
207
	 * @return WP_Error|WP_REST_Response
208
	 */
209
	public function update_item( $request ) {
210
		$id   = (int) $request['id'];
211
		$post = get_post( $id );
212
213
		if ( empty( $id ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) {
214
			return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'ID is invalid.', 'woocommerce' ), array( 'status' => 400 ) );
215
		}
216
217
		$webhook = new WC_Webhook( $id );
218
219
		// Update topic.
220 View Code Duplication
		if ( ! empty( $request['topic'] ) ) {
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...
221
			if ( wc_is_webhook_valid_topic( strtolower( $request['topic'] ) ) ) {
222
				$webhook->set_topic( $request['topic'] );
223
			} else {
224
				return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_topic", __( 'Webhook topic must be valid.', 'woocommerce' ), array( 'status' => 400 ) );
225
			}
226
		}
227
228
		// Update delivery URL.
229 View Code Duplication
		if ( ! empty( $request['delivery_url'] ) ) {
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...
230
			if ( wc_is_valid_url( $request['delivery_url'] ) ) {
231
				$webhook->set_delivery_url( $request['delivery_url'] );
232
			} else {
233
				return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_delivery_url", __( 'Webhook delivery URL must be a valid URL starting with http:// or https://.', 'woocommerce' ), array( 'status' => 400 ) );
234
			}
235
		}
236
237
		// Update secret.
238
		if ( ! empty( $request['secret'] ) ) {
239
			$webhook->set_secret( $request['secret'] );
240
		}
241
242
		// Update status.
243
		if ( ! empty( $request['status'] ) ) {
244
			$webhook->update_status( $request['status'] );
245
		}
246
247
		$post = $this->prepare_item_for_database( $request );
248
		if ( is_wp_error( $post ) ) {
249
			return $post;
250
		}
251
252
		// Convert the post object to an array, otherwise wp_update_post will expect non-escaped input.
253
		$post_id = wp_update_post( (array) $post, true );
254 View Code Duplication
		if ( is_wp_error( $post_id ) ) {
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...
255
			if ( in_array( $post_id->get_error_code(), array( 'db_update_error' ) ) ) {
256
				$post_id->add_data( array( 'status' => 500 ) );
257
			} else {
258
				$post_id->add_data( array( 'status' => 400 ) );
259
			}
260
			return $post_id;
261
		}
262
263
		$post = get_post( $post_id );
264
		$this->update_additional_fields_for_object( $post, $request );
265
266
		/**
267
		 * Fires after a single item is created or updated via the REST API.
268
		 *
269
		 * @param WP_Post         $post      Inserted object.
270
		 * @param WP_REST_Request $request   Request object.
271
		 * @param boolean         $creating  True when creating item, false when updating.
272
		 */
273
		do_action( "woocommerce_rest_insert_{$this->post_type}", $post, $request, false );
274
275
		$request->set_param( 'context', 'edit' );
276
		$response = $this->prepare_item_for_response( $post, $request );
277
278
		// Clear cache.
279
		delete_transient( 'woocommerce_webhook_ids' );
280
281
		return rest_ensure_response( $response );
282
	}
283
284
	/**
285
	 * Delete a single webhook.
286
	 *
287
	 * @param WP_REST_Request $request Full details about the request.
288
	 * @return WP_REST_Response|WP_Error
289
	 */
290
	public function delete_item( $request ) {
291
		$id    = (int) $request['id'];
292
		$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
293
294
		// We don't support trashing for this type, error out.
295
		if ( ! $force ) {
296
			return new WP_Error( 'woocommerce_rest_trash_not_supported', __( 'Webhooks do not support trashing.', 'woocommerce' ), array( 'status' => 501 ) );
297
		}
298
299
		$post = get_post( $id );
300
301
		if ( empty( $id ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) {
302
			return new WP_Error( "woocommerce_rest_{$this->post_type}_invalid_id", __( 'Invalid post id.', 'woocommerce' ), array( 'status' => 404 ) );
303
		}
304
305
		$request->set_param( 'context', 'edit' );
306
		$response = $this->prepare_item_for_response( $post, $request );
307
308
		$result = wp_delete_post( $id, true );
309
310
		if ( ! $result ) {
311
			return new WP_Error( 'woocommerce_rest_cannot_delete', sprintf( __( 'The %s cannot be deleted.', 'woocommerce' ), $this->post_type ), array( 'status' => 500 ) );
312
		}
313
314
		/**
315
		 * Fires after a single item is deleted or trashed via the REST API.
316
		 *
317
		 * @param object           $post     The deleted or trashed item.
318
		 * @param WP_REST_Response $response The response data.
319
		 * @param WP_REST_Request  $request  The request sent to the API.
320
		 */
321
		do_action( "woocommerce_rest_delete_{$this->post_type}", $post, $response, $request );
322
323
		// Clear cache.
324
		delete_transient( 'woocommerce_webhook_ids' );
325
326
		return $response;
327
	}
328
329
	/**
330
	 * Prepare a single webhook for create or update.
331
	 *
332
	 * @param WP_REST_Request $request Request object.
333
	 * @return WP_Error|stdClass $data Post object.
334
	 */
335
	protected function prepare_item_for_database( $request ) {
336
		global $wpdb;
337
338
		$data = new stdClass;
339
340
		// Post ID.
341
		if ( isset( $request['id'] ) ) {
342
			$data->ID = absint( $request['id'] );
343
		}
344
345
		// Validate required POST fields.
346
		if ( 'POST' === $request->get_method() && empty( $data->ID ) ) {
347
			$data->post_title = ! empty( $request['name'] ) ? $request['name'] : sprintf( __( 'Webhook created on %s', 'woocommerce' ), strftime( _x( '%b %d, %Y @ %I:%M %p', 'Webhook created on date parsed by strftime', 'woocommerce' ) ) );
348
349
			// Post author.
350
			$data->post_author = get_current_user_id();
351
352
			// Post password.
353
			$password = strlen( uniqid( 'webhook_' ) );
354
			$data->post_password = $password > 20 ? substr( $password, 0, 20 ) : $password;
355
356
			// Post status.
357
			$data->post_status = 'publish';
358
		} else {
359
360
			// Allow edit post title.
361
			if ( ! empty( $request['name'] ) ) {
362
				$data->post_title = $request['name'];
363
			}
364
		}
365
366
		// Comment status.
367
		$data->comment_status = 'closed';
368
369
		// Ping status.
370
		$data->ping_status = 'closed';
371
372
		/**
373
		 * Filter the query_vars used in `get_items` for the constructed query.
374
		 *
375
		 * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being
376
		 * prepared for insertion.
377
		 *
378
		 * @param stdClass        $data An object representing a single item prepared
379
		 *                                       for inserting or updating the database.
380
		 * @param WP_REST_Request $request       Request object.
381
		 */
382
		return apply_filters( "woocommerce_rest_pre_insert_{$this->post_type}", $data, $request );
383
	}
384
385
	/**
386
	 * Prepare a single webhook output for response.
387
	 *
388
	 * @param WP_REST_Request $request Request object.
389
	 * @return WP_REST_Response $response Response data.
390
	 */
391
	public function prepare_item_for_response( $post, $request ) {
392
		$id      = (int) $post->ID;
393
		$webhook = new WC_Webhook( $id );
394
		$data    = array(
395
			'id'            => $webhook->id,
396
			'name'          => $webhook->get_name(),
397
			'status'        => $webhook->get_status(),
398
			'topic'         => $webhook->get_topic(),
399
			'resource'      => $webhook->get_resource(),
400
			'event'         => $webhook->get_event(),
401
			'hooks'         => $webhook->get_hooks(),
402
			'delivery_url'  => $webhook->get_delivery_url(),
403
			'date_created'  => wc_rest_prepare_date_response( $webhook->get_post_data()->post_date_gmt ),
404
			'date_modified' => wc_rest_prepare_date_response( $webhook->get_post_data()->post_modified_gmt ),
405
		);
406
407
		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
408
		$data    = $this->add_additional_fields_to_object( $data, $request );
409
		$data    = $this->filter_response_by_context( $data, $context );
410
411
		// Wrap the data in a response object.
412
		$response = rest_ensure_response( $data );
413
414
		$response->add_links( $this->prepare_links( $post ) );
415
416
		/**
417
		 * Filter webhook object returned from the REST API.
418
		 *
419
		 * @param WP_REST_Response $response The response object.
420
		 * @param WC_Webhook       $webhook  Webhook object used to create response.
421
		 * @param WP_REST_Request  $request  Request object.
422
		 */
423
		return apply_filters( "woocommerce_rest_prepare_{$this->post_type}", $response, $webhook, $request );
424
	}
425
426
	/**
427
	 * Query args.
428
	 *
429
	 * @param array $args
430
	 * @param WP_REST_Request $request
431
	 * @return array
432
	 */
433
	public function query_args( $args, $request ) {
434
		// Set post_status.
435
		switch ( $request['status'] ) {
436
			case 'active' :
437
				$args['post_status'] = 'publish';
438
				break;
439
			case 'paused' :
440
				$args['post_status'] = 'draft';
441
				break;
442
			case 'disabled' :
443
				$args['post_status'] = 'pending';
444
				break;
445
			default :
446
				$args['post_status'] = 'any';
447
				break;
448
		}
449
450
		return $args;
451
	}
452
453
	/**
454
	 * Get the Webhook's schema, conforming to JSON Schema.
455
	 *
456
	 * @return array
457
	 */
458
	public function get_item_schema() {
459
		$schema = array(
460
			'$schema'    => 'http://json-schema.org/draft-04/schema#',
461
			'title'      => 'webhook',
462
			'type'       => 'object',
463
			'properties' => array(
464
				'id' => array(
465
					'description' => __( 'Unique identifier for the resource.', 'woocommerce' ),
466
					'type'        => 'integer',
467
					'context'     => array( 'view', 'edit' ),
468
					'readonly'    => true,
469
				),
470
				'name' => array(
471
					'description' => __( 'A friendly name for the webhook.', 'woocommerce' ),
472
					'type'        => 'string',
473
					'context'     => array( 'view', 'edit' ),
474
				),
475
				'status' => array(
476
					'description' => __( 'Webhook status.', 'woocommerce' ),
477
					'type'        => 'string',
478
					'default'     => 'active',
479
					'enum'        => array( 'active', 'paused', 'disabled' ),
480
					'context'     => array( 'view', 'edit' ),
481
					'arg_options' => array(
482
						'sanitize_callback' => 'wc_is_webhook_valid_topic',
483
					),
484
				),
485
				'topic' => array(
486
					'description' => __( 'Webhook topic.', 'woocommerce' ),
487
					'type'        => 'string',
488
					'context'     => array( 'view', 'edit' ),
489
				),
490
				'resource' => array(
491
					'description' => __( 'Webhook resource.', 'woocommerce' ),
492
					'type'        => 'string',
493
					'context'     => array( 'view', 'edit' ),
494
					'readonly'    => true,
495
				),
496
				'event' => array(
497
					'description' => __( 'Webhook event.', 'woocommerce' ),
498
					'type'        => 'string',
499
					'context'     => array( 'view', 'edit' ),
500
					'readonly'    => true,
501
				),
502
				'hooks' => array(
503
					'description' => __( 'WooCommerce action names associated with the webhook.', 'woocommerce' ),
504
					'type'        => 'array',
505
					'context'     => array( 'view', 'edit' ),
506
					'readonly'    => true,
507
				),
508
				'delivery_url' => array(
509
					'description' => __( 'The URL where the webhook payload is delivered.', 'woocommerce' ),
510
					'type'        => 'string',
511
					'format'      => 'uri',
512
					'context'     => array( 'view', 'edit' ),
513
					'readonly'    => true,
514
				),
515
				'secret' => array(
516
					'description' => __( "Secret key used to generate a hash of the delivered webhook and provided in the request headers. This will default is a MD5 hash from the current user's ID|username if not provided.", 'woocommerce' ),
517
					'type'        => 'string',
518
					'format'      => 'uri',
519
					'context'     => array( 'edit' ),
520
				),
521
				'date_created' => array(
522
					'description' => __( "The date the webhook was created, in the site's timezone.", 'woocommerce' ),
523
					'type'        => 'date-time',
524
					'context'     => array( 'view', 'edit' ),
525
					'readonly'    => true,
526
				),
527
				'date_modified' => array(
528
					'description' => __( "The date the webhook was last modified, in the site's timezone.", 'woocommerce' ),
529
					'type'        => 'date-time',
530
					'context'     => array( 'view', 'edit' ),
531
					'readonly'    => true,
532
				),
533
			),
534
		);
535
536
		return $this->add_additional_fields_schema( $schema );
537
	}
538
539
	/**
540
	 * Get the query params for collections of attachments.
541
	 *
542
	 * @return array
543
	 */
544
	public function get_collection_params() {
545
		$params = parent::get_collection_params();
546
547
		$params['status'] = array(
548
			'default'           => 'all',
549
			'description'       => __( 'Limit result set to webhooks assigned a specific status.', 'woocommerce' ),
550
			'type'              => 'string',
551
			'enum'              => array( 'all', 'active', 'paused', 'disabled' ),
552
			'sanitize_callback' => 'sanitize_key',
553
			'validate_callback' => 'rest_validate_request_arg',
554
		);
555
556
		return $params;
557
	}
558
}
559