Completed
Push — master ( 15aa29...17da96 )
by Claudio
18:39 queued 11s
created

WC_Webhook::is_valid_resource()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
cc 7
nc 5
nop 1
dl 0
loc 23
ccs 0
cts 11
cp 0
crap 56
rs 8.6186
c 0
b 0
f 0
1
<?php
2
/**
3
 * Webhook
4
 *
5
 * This class handles storing and retrieving webhook data from the associated.
6
 *
7
 * Webhooks are enqueued to their associated actions, delivered, and logged.
8
 *
9
 * @version  3.2.0
10
 * @package  WooCommerce/Webhooks
11
 * @since    2.2.0
12
 */
13
14
defined( 'ABSPATH' ) || exit;
15
16 1
require_once 'legacy/class-wc-legacy-webhook.php';
17
18
/**
19
 * Webhook class.
20
 */
21
class WC_Webhook extends WC_Legacy_Webhook {
22
23
	/**
24
	 * Stores webhook data.
25
	 *
26
	 * @var array
27
	 */
28
	protected $data = array(
29
		'date_created'     => null,
30
		'date_modified'    => null,
31
		'status'           => 'disabled',
32
		'delivery_url'     => '',
33
		'secret'           => '',
34
		'name'             => '',
35
		'topic'            => '',
36
		'hooks'            => '',
37
		'resource'         => '',
38
		'event'            => '',
39
		'failure_count'    => 0,
40
		'user_id'          => 0,
41
		'api_version'      => 3,
42
		'pending_delivery' => false,
43
	);
44
45
	/**
46
	 * Load webhook data based on how WC_Webhook is called.
47
	 *
48
	 * @param WC_Webhook|int $data Webhook ID or data.
49
	 * @throws Exception If webhook cannot be read/found and $data is set.
50
	 */
51 26
	public function __construct( $data = 0 ) {
52 26
		parent::__construct( $data );
53
54 26 View Code Duplication
		if ( $data instanceof WC_Webhook ) {
55
			$this->set_id( absint( $data->get_id() ) );
56 26
		} elseif ( is_numeric( $data ) ) {
57 26
			$this->set_id( $data );
58
		}
59
60 26
		$this->data_store = WC_Data_Store::load( 'webhook' );
61
62
		// If we have an ID, load the webhook from the DB.
63 26 View Code Duplication
		if ( $this->get_id() ) {
64
			try {
65 8
				$this->data_store->read( $this );
66
			} catch ( Exception $e ) {
67
				$this->set_id( 0 );
68 8
				$this->set_object_read( true );
69
			}
70
		} else {
71 26
			$this->set_object_read( true );
72
		}
73
	}
74
75
	/**
76
	 * Enqueue the hooks associated with the webhook.
77
	 *
78
	 * @since 2.2.0
79
	 */
80 8
	public function enqueue() {
81 8
		$hooks = $this->get_hooks();
82 8
		$url   = $this->get_delivery_url();
83
84 8
		if ( is_array( $hooks ) && ! empty( $url ) ) {
85 8
			foreach ( $hooks as $hook ) {
86 8
				add_action( $hook, array( $this, 'process' ) );
87
			}
88
		}
89
	}
90
91
	/**
92
	 * Process the webhook for delivery by verifying that it should be delivered.
93
	 * and scheduling the delivery (in the background by default, or immediately).
94
	 *
95
	 * @since  2.2.0
96
	 * @param  mixed $arg The first argument provided from the associated hooks.
97
	 * @return mixed $arg Returns the argument in case the webhook was hooked into a filter.
98
	 */
99
	public function process( $arg ) {
100
101
		// Verify that webhook should be processed for delivery.
102
		if ( ! $this->should_deliver( $arg ) ) {
103
			return;
104
		}
105
106
		/**
107
		 * Process webhook delivery.
108
		 *
109
		 * @since 3.3.0
110
		 * @hooked wc_webhook_process_delivery - 10
111
		 */
112
		do_action( 'woocommerce_webhook_process_delivery', $this, $arg );
113
114
		return $arg;
115
	}
116
117
	/**
118
	 * Helper to check if the webhook should be delivered, as some hooks.
119
	 * (like `wp_trash_post`) will fire for every post type, not just ours.
120
	 *
121
	 * @since  2.2.0
122
	 * @param  mixed $arg First hook argument.
123
	 * @return bool       True if webhook should be delivered, false otherwise.
124
	 */
125
	private function should_deliver( $arg ) {
126
		$should_deliver = $this->is_active() && $this->is_valid_topic() && $this->is_valid_action( $arg ) && $this->is_valid_resource( $arg );
127
128
		/**
129
		 * Let other plugins intercept deliver for some messages queue like rabbit/zeromq.
130
		 *
131
		 * @param bool       $should_deliver True if the webhook should be sent, or false to not send it.
132
		 * @param WC_Webhook $this The current webhook class.
133
		 * @param mixed      $arg First hook argument.
134
		 */
135
		return apply_filters( 'woocommerce_webhook_should_deliver', $should_deliver, $this, $arg );
136
	}
137
138
	/**
139
	 * Returns if webhook is active.
140
	 *
141
	 * @since  3.6.0
142
	 * @return bool  True if validation passes.
143
	 */
144
	private function is_active() {
145
		return 'active' === $this->get_status();
146
	}
147
148
	/**
149
	 * Returns if topic is valid.
150
	 *
151
	 * @since  3.6.0
152
	 * @return bool  True if validation passes.
153
	 */
154
	private function is_valid_topic() {
155
		return wc_is_webhook_valid_topic( $this->get_topic() );
156
	}
157
158
	/**
159
	 * Validates the criteria for certain actions.
160
	 *
161
	 * @since  3.6.0
162
	 * @param  mixed $arg First hook argument.
163
	 * @return bool       True if validation passes.
164
	 */
165
	private function is_valid_action( $arg ) {
166
		$current_action = current_action();
167
		$return         = true;
168
169
		switch ( $current_action ) {
170
			case 'delete_post':
171
			case 'wp_trash_post':
172
			case 'untrashed_post':
173
				$return = $this->is_valid_post_action( $arg );
174
				break;
175
			case 'delete_user':
176
				$return = $this->is_valid_user_action( $arg );
177
				break;
178
		}
179
180
		if ( 0 === strpos( $current_action, 'woocommerce_process_shop' ) || 0 === strpos( $current_action, 'woocommerce_process_product' ) ) {
181
			$return = $this->is_valid_processing_action( $arg );
182
		}
183
184
		return $return;
185
	}
186
187
	/**
188
	 * Validates post actions.
189
	 *
190
	 * @since  3.6.0
191
	 * @param  mixed $arg First hook argument.
192
	 * @return bool       True if validation passes.
193
	 */
194
	private function is_valid_post_action( $arg ) {
195
		// Only deliver deleted/restored event for coupons, orders, and products.
196
		if ( isset( $GLOBALS['post_type'] ) && ! in_array( $GLOBALS['post_type'], array( 'shop_coupon', 'shop_order', 'product' ), true ) ) {
197
			return false;
198
		}
199
200
		// Check if is delivering for the correct resource.
201
		if ( isset( $GLOBALS['post_type'] ) && str_replace( 'shop_', '', $GLOBALS['post_type'] ) !== $this->get_resource() ) {
202
			return false;
203
		}
204
		return true;
205
	}
206
207
	/**
208
	 * Validates user actions.
209
	 *
210
	 * @since  3.6.0
211
	 * @param  mixed $arg First hook argument.
212
	 * @return bool       True if validation passes.
213
	 */
214
	private function is_valid_user_action( $arg ) {
215
		$user = get_userdata( absint( $arg ) );
216
217
		// Only deliver deleted customer event for users with customer role.
218
		if ( ! $user || ! in_array( 'customer', (array) $user->roles, true ) ) {
219
			return false;
220
		}
221
222
		return true;
223
	}
224
225
	/**
226
	 * Validates WC processing actions.
227
	 *
228
	 * @since  3.6.0
229
	 * @param  mixed $arg First hook argument.
230
	 * @return bool       True if validation passes.
231
	 */
232
	private function is_valid_processing_action( $arg ) {
233
		// The `woocommerce_process_shop_*` and `woocommerce_process_product_*` hooks
234
		// fire for create and update of products and orders, so check the post
235
		// creation date to determine the actual event.
236
		$resource = get_post( absint( $arg ) );
237
238
		// Drafts don't have post_date_gmt so calculate it here.
239
		$gmt_date = get_gmt_from_date( $resource->post_date );
240
241
		// A resource is considered created when the hook is executed within 10 seconds of the post creation date.
242
		$resource_created = ( ( time() - 10 ) <= strtotime( $gmt_date ) );
243
244
		if ( 'created' === $this->get_event() && ! $resource_created ) {
245
			return false;
246
		} elseif ( 'updated' === $this->get_event() && $resource_created ) {
247
			return false;
248
		}
249
		return true;
250
	}
251
252
	/**
253
	 * Checks the resource for this webhook is valid e.g. valid post status.
254
	 *
255
	 * @since  3.6.0
256
	 * @param  mixed $arg First hook argument.
257
	 * @return bool       True if validation passes.
258
	 */
259
	private function is_valid_resource( $arg ) {
260
		$resource = $this->get_resource();
261
262
		if ( in_array( $resource, array( 'order', 'product', 'coupon' ), true ) ) {
263
			$status = get_post_status( absint( $arg ) );
264
265
			// Ignore auto drafts for all resources.
266
			if ( in_array( $status, array( 'auto-draft', 'new' ), true ) ) {
267
				return false;
268
			}
269
270
			// Ignore standard drafts for orders.
271
			if ( 'order' === $resource && 'draft' === $status ) {
272
				return false;
273
			}
274
275
			// Check registered order types for order types args.
276
			if ( 'order' === $resource && ! in_array( get_post_type( absint( $arg ) ), wc_get_order_types( 'order-webhooks' ), true ) ) {
277
				return false;
278
			}
279
		}
280
		return true;
281
	}
282
283
	/**
284
	 * Deliver the webhook payload using wp_safe_remote_request().
285
	 *
286
	 * @since 2.2.0
287
	 * @param mixed $arg First hook argument.
288
	 */
289
	public function deliver( $arg ) {
290
		$start_time = microtime( true );
291
		$payload    = $this->build_payload( $arg );
292
293
		// Setup request args.
294
		$http_args = array(
295
			'method'      => 'POST',
296
			'timeout'     => MINUTE_IN_SECONDS,
297
			'redirection' => 0,
298
			'httpversion' => '1.0',
299
			'blocking'    => true,
300
			'user-agent'  => sprintf( 'WooCommerce/%s Hookshot (WordPress/%s)', WC_VERSION, $GLOBALS['wp_version'] ),
301
			'body'        => trim( wp_json_encode( $payload ) ),
302
			'headers'     => array(
303
				'Content-Type' => 'application/json',
304
			),
305
			'cookies'     => array(),
306
		);
307
308
		$http_args = apply_filters( 'woocommerce_webhook_http_args', $http_args, $arg, $this->get_id() );
309
310
		// Add custom headers.
311
		$delivery_id                                      = $this->get_new_delivery_id();
312
		$http_args['headers']['X-WC-Webhook-Source']      = home_url( '/' ); // Since 2.6.0.
313
		$http_args['headers']['X-WC-Webhook-Topic']       = $this->get_topic();
314
		$http_args['headers']['X-WC-Webhook-Resource']    = $this->get_resource();
315
		$http_args['headers']['X-WC-Webhook-Event']       = $this->get_event();
316
		$http_args['headers']['X-WC-Webhook-Signature']   = $this->generate_signature( $http_args['body'] );
317
		$http_args['headers']['X-WC-Webhook-ID']          = $this->get_id();
318
		$http_args['headers']['X-WC-Webhook-Delivery-ID'] = $delivery_id;
319
320
		// Webhook away!
321
		$response = wp_safe_remote_request( $this->get_delivery_url(), $http_args );
322
323
		$duration = round( microtime( true ) - $start_time, 5 );
324
325
		$this->log_delivery( $delivery_id, $http_args, $response, $duration );
326
327
		do_action( 'woocommerce_webhook_delivery', $http_args, $response, $duration, $arg, $this->get_id() );
328
	}
329
330
	/**
331
	 * Get Legacy API payload.
332
	 *
333
	 * @since  3.0.0
334
	 * @param  string $resource    Resource type.
335
	 * @param  int    $resource_id Resource ID.
336
	 * @param  string $event       Event type.
337
	 * @return array
338
	 */
339
	private function get_legacy_api_payload( $resource, $resource_id, $event ) {
340
		// Include & load API classes.
341
		WC()->api->includes();
342
		WC()->api->register_resources( new WC_API_Server( '/' ) );
343
344
		switch ( $resource ) {
345
			case 'coupon':
346
				$payload = WC()->api->WC_API_Coupons->get_coupon( $resource_id );
347
				break;
348
349
			case 'customer':
350
				$payload = WC()->api->WC_API_Customers->get_customer( $resource_id );
351
				break;
352
353
			case 'order':
354
				$payload = WC()->api->WC_API_Orders->get_order( $resource_id, null, apply_filters( 'woocommerce_webhook_order_payload_filters', array() ) );
355
				break;
356
357
			case 'product':
358
				// Bulk and quick edit action hooks return a product object instead of an ID.
359
				if ( 'updated' === $event && is_a( $resource_id, 'WC_Product' ) ) {
360
					$resource_id = $resource_id->get_id();
0 ignored issues
show
Bug introduced by
The method get_id cannot be called on $resource_id (of type integer).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
361
				}
362
				$payload = WC()->api->WC_API_Products->get_product( $resource_id );
363
				break;
364
365
			// Custom topics include the first hook argument.
366
			case 'action':
367
				$payload = array(
368
					'action' => current( $this->get_hooks() ),
369
					'arg'    => $resource_id,
370
				);
371
				break;
372
373
			default:
374
				$payload = array();
375
				break;
376
		}
377
378
		return $payload;
379
	}
380
381
	/**
382
	 * Get WP API integration payload.
383
	 *
384
	 * @since  3.0.0
385
	 * @param  string $resource    Resource type.
386
	 * @param  int    $resource_id Resource ID.
387
	 * @param  string $event       Event type.
388
	 * @return array
389
	 */
390
	private function get_wp_api_payload( $resource, $resource_id, $event ) {
391
		$rest_api_versions = wc_get_webhook_rest_api_versions();
392
		$version_suffix    = end( $rest_api_versions ) !== $this->get_api_version() ? strtoupper( str_replace( 'wp_api', '', $this->get_api_version() ) ) : '';
393
394
		// Load REST API endpoints to generate payload.
395
		if ( ! did_action( 'rest_api_init' ) ) {
396
			WC()->api->rest_api_includes();
397
		}
398
399
		switch ( $resource ) {
400
			case 'coupon':
401
			case 'customer':
402
			case 'order':
403
			case 'product':
404
				$class      = 'WC_REST_' . ucfirst( $resource ) . 's' . $version_suffix . '_Controller';
405
				$request    = new WP_REST_Request( 'GET' );
406
				$controller = new $class();
407
408
				// Bulk and quick edit action hooks return a product object instead of an ID.
409
				if ( 'product' === $resource && 'updated' === $event && is_a( $resource_id, 'WC_Product' ) ) {
410
					$resource_id = $resource_id->get_id();
0 ignored issues
show
Bug introduced by
The method get_id cannot be called on $resource_id (of type integer).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
411
				}
412
413
				$request->set_param( 'id', $resource_id );
414
				$result  = $controller->get_item( $request );
415
				$payload = isset( $result->data ) ? $result->data : array();
416
				break;
417
418
			// Custom topics include the first hook argument.
419
			case 'action':
420
				$payload = array(
421
					'action' => current( $this->get_hooks() ),
422
					'arg'    => $resource_id,
423
				);
424
				break;
425
426
			default:
427
				$payload = array();
428
				break;
429
		}
430
431
		return $payload;
432
	}
433
434
	/**
435
	 * Build the payload data for the webhook.
436
	 *
437
	 * @since  2.2.0
438
	 * @param  mixed $resource_id First hook argument, typically the resource ID.
439
	 * @return mixed              Payload data.
440
	 */
441
	public function build_payload( $resource_id ) {
442
		// Build the payload with the same user context as the user who created
443
		// the webhook -- this avoids permission errors as background processing
444
		// runs with no user context.
445
		$current_user = get_current_user_id();
446
		wp_set_current_user( $this->get_user_id() );
447
448
		$resource = $this->get_resource();
449
		$event    = $this->get_event();
450
451
		// If a resource has been deleted, just include the ID.
452
		if ( 'deleted' === $event ) {
453
			$payload = array(
454
				'id' => $resource_id,
455
			);
456
		} else {
457
			if ( in_array( $this->get_api_version(), wc_get_webhook_rest_api_versions(), true ) ) {
458
				$payload = $this->get_wp_api_payload( $resource, $resource_id, $event );
459
			} else {
460
				$payload = $this->get_legacy_api_payload( $resource, $resource_id, $event );
461
			}
462
		}
463
464
		// Restore the current user.
465
		wp_set_current_user( $current_user );
466
467
		return apply_filters( 'woocommerce_webhook_payload', $payload, $resource, $resource_id, $this->get_id() );
468
	}
469
470
	/**
471
	 * Generate a base64-encoded HMAC-SHA256 signature of the payload body so the.
472
	 * recipient can verify the authenticity of the webhook. Note that the signature.
473
	 * is calculated after the body has already been encoded (JSON by default).
474
	 *
475
	 * @since  2.2.0
476
	 * @param  string $payload Payload data to hash.
477
	 * @return string
478
	 */
479 1
	public function generate_signature( $payload ) {
480 1
		$hash_algo = apply_filters( 'woocommerce_webhook_hash_algorithm', 'sha256', $payload, $this->get_id() );
481
482 1
		return base64_encode( hash_hmac( $hash_algo, $payload, wp_specialchars_decode( $this->get_secret(), ENT_QUOTES ), true ) );
483
	}
484
485
	/**
486
	 * Generate a new unique hash as a delivery id based on current time and wehbook id.
487
	 * Return the hash for inclusion in the webhook request.
488
	 *
489
	 * @since  2.2.0
490
	 * @return string
491
	 */
492
	public function get_new_delivery_id() {
493
		// Since we no longer use comments to store delivery logs, we generate a unique hash instead based on current time and webhook ID.
494
		return wp_hash( $this->get_id() . strtotime( 'now' ) );
495
	}
496
497
	/**
498
	 * Log the delivery request/response.
499
	 *
500
	 * @since 2.2.0
501
	 * @param string         $delivery_id Previously created hash.
502
	 * @param array          $request     Request data.
503
	 * @param array|WP_Error $response    Response data.
504
	 * @param float          $duration    Request duration.
505
	 */
506
	public function log_delivery( $delivery_id, $request, $response, $duration ) {
507
		$logger  = wc_get_logger();
508
		$message = array(
509
			'Webhook Delivery' => array(
510
				'Delivery ID' => $delivery_id,
511
				'Date'        => date_i18n( __( 'M j, Y @ G:i', 'woocommerce' ), strtotime( 'now' ), true ),
512
				'URL'         => $this->get_delivery_url(),
513
				'Duration'    => $duration,
514
				'Request'     => array(
515
					'Method'  => $request['method'],
516
					'Headers' => array_merge(
517
						array(
518
							'User-Agent' => $request['user-agent'],
519
						),
520
						$request['headers']
521
					),
522
				),
523
				'Body'        => wp_slash( $request['body'] ),
524
			),
525
		);
526
527
		// Parse response.
528
		if ( is_wp_error( $response ) ) {
529
			$response_code    = $response->get_error_code();
530
			$response_message = $response->get_error_message();
531
			$response_headers = array();
532
			$response_body    = '';
533
		} else {
534
			$response_code    = wp_remote_retrieve_response_code( $response );
535
			$response_message = wp_remote_retrieve_response_message( $response );
536
			$response_headers = wp_remote_retrieve_headers( $response );
537
			$response_body    = wp_remote_retrieve_body( $response );
538
		}
539
540
		$message['Webhook Delivery']['Response'] = array(
541
			'Code'    => $response_code,
542
			'Message' => $response_message,
543
			'Headers' => $response_headers,
544
			'Body'    => $response_body,
545
		);
546
547
		if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) {
548
			$message['Webhook Delivery']['Body']             = 'Webhook body is not logged unless WP_DEBUG mode is turned on. This is to avoid the storing of personal data in the logs.';
549
			$message['Webhook Delivery']['Response']['Body'] = 'Webhook body is not logged unless WP_DEBUG mode is turned on. This is to avoid the storing of personal data in the logs.';
550
		}
551
552
		$logger->info(
553
			wc_print_r( $message, true ),
0 ignored issues
show
Bug introduced by
It seems like wc_print_r($message, true) targeting wc_print_r() can also be of type boolean; however, WC_Logger::info() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
554
			array(
555
				'source' => 'webhooks-delivery',
556
			)
557
		);
558
559
		// Track failures.
560
		// Check for a success, which is a 2xx, 301 or 302 Response Code.
561
		if ( intval( $response_code ) >= 200 && intval( $response_code ) < 303 ) {
562
			$this->set_failure_count( 0 );
563
			$this->save();
564
		} else {
565
			$this->failed_delivery();
566
		}
567
	}
568
569
	/**
570
	 * Track consecutive delivery failures and automatically disable the webhook.
571
	 * if more than 5 consecutive failures occur. A failure is defined as a.
572
	 * non-2xx response.
573
	 *
574
	 * @since 2.2.0
575
	 */
576
	private function failed_delivery() {
577
		$failures = $this->get_failure_count();
578
579
		if ( $failures > apply_filters( 'woocommerce_max_webhook_delivery_failures', 5 ) ) {
580
			$this->set_status( 'disabled' );
581
582
			do_action( 'woocommerce_webhook_disabled_due_delivery_failures', $this->get_id() );
583
		} else {
584
			$this->set_failure_count( ++$failures );
585
		}
586
587
		$this->save();
588
	}
589
590
	/**
591
	 * Get the delivery logs for this webhook.
592
	 *
593
	 * @since  3.3.0
594
	 * @return string
595
	 */
596
	public function get_delivery_logs() {
597
		return esc_url( add_query_arg( 'log_file', wc_get_log_file_name( 'webhooks-delivery' ), admin_url( 'admin.php?page=wc-status&tab=logs' ) ) );
598
	}
599
600
	/**
601
	 * Get the delivery log specified by the ID. The delivery log includes:
602
	 *
603
	 * + duration
604
	 * + summary
605
	 * + request method/url
606
	 * + request headers/body
607
	 * + response code/message/headers/body
608
	 *
609
	 * @since 2.2
610
	 * @deprecated 3.3.0
611
	 * @param int $delivery_id Delivery ID.
612
	 * @return void
613
	 */
614
	public function get_delivery_log( $delivery_id ) {
615
		wc_deprecated_function( 'WC_Webhook::get_delivery_log', '3.3' );
616
	}
617
618
	/**
619
	 * Send a test ping to the delivery URL, sent when the webhook is first created.
620
	 *
621
	 * @since  2.2.0
622
	 * @return bool|WP_Error
623
	 */
624
	public function deliver_ping() {
625
		$args = array(
626
			'user-agent' => sprintf( 'WooCommerce/%s Hookshot (WordPress/%s)', WC_VERSION, $GLOBALS['wp_version'] ),
627
			'body'       => 'webhook_id=' . $this->get_id(),
628
		);
629
630
		$test          = wp_safe_remote_post( $this->get_delivery_url(), $args );
631
		$response_code = wp_remote_retrieve_response_code( $test );
632
633
		if ( is_wp_error( $test ) ) {
634
			/* translators: error message */
635
			return new WP_Error( 'error', sprintf( __( 'Error: Delivery URL cannot be reached: %s', 'woocommerce' ), $test->get_error_message() ) );
636
		}
637
638
		if ( 200 !== $response_code ) {
639
			/* translators: error message */
640
			return new WP_Error( 'error', sprintf( __( 'Error: Delivery URL returned response code: %s', 'woocommerce' ), absint( $response_code ) ) );
641
		}
642
643
		$this->set_pending_delivery( false );
644
		$this->save();
645
646
		return true;
647
	}
648
649
	/*
650
	|--------------------------------------------------------------------------
651
	| Getters
652
	|--------------------------------------------------------------------------
653
	*/
654
655
	/**
656
	 * Get the friendly name for the webhook.
657
	 *
658
	 * @since  2.2.0
659
	 * @param  string $context What the value is for.
660
	 *                         Valid values are 'view' and 'edit'.
661
	 * @return string
662
	 */
663 10
	public function get_name( $context = 'view' ) {
664 10
		return apply_filters( 'woocommerce_webhook_name', $this->get_prop( 'name', $context ), $this->get_id() );
665
	}
666
667
	/**
668
	 * Get the webhook status.
669
	 *
670
	 * - 'active' - delivers payload.
671
	 * - 'paused' - does not deliver payload, paused by admin.
672
	 * - 'disabled' - does not delivery payload, paused automatically due to consecutive failures.
673
	 *
674
	 * @since  2.2.0
675
	 * @param  string $context What the value is for.
676
	 *                         Valid values are 'view' and 'edit'.
677
	 * @return string status
678
	 */
679 11
	public function get_status( $context = 'view' ) {
680 11
		return apply_filters( 'woocommerce_webhook_status', $this->get_prop( 'status', $context ), $this->get_id() );
681
	}
682
683
	/**
684
	 * Get webhopk created date.
685
	 *
686
	 * @since  3.2.0
687
	 * @param  string $context  What the value is for.
688
	 *                          Valid values are 'view' and 'edit'.
689
	 * @return WC_DateTime|null Object if the date is set or null if there is no date.
690
	 */
691 1
	public function get_date_created( $context = 'view' ) {
692 1
		return $this->get_prop( 'date_created', $context );
693
	}
694
695
	/**
696
	 * Get webhopk modified date.
697
	 *
698
	 * @since  3.2.0
699
	 * @param  string $context  What the value is for.
700
	 *                          Valid values are 'view' and 'edit'.
701
	 * @return WC_DateTime|null Object if the date is set or null if there is no date.
702
	 */
703 1
	public function get_date_modified( $context = 'view' ) {
704 1
		return $this->get_prop( 'date_modified', $context );
705
	}
706
707
	/**
708
	 * Get the secret used for generating the HMAC-SHA256 signature.
709
	 *
710
	 * @since  2.2.0
711
	 * @param  string $context What the value is for.
712
	 *                         Valid values are 'view' and 'edit'.
713
	 * @return string
714
	 */
715 11
	public function get_secret( $context = 'view' ) {
716 11
		return apply_filters( 'woocommerce_webhook_secret', $this->get_prop( 'secret', $context ), $this->get_id() );
717
	}
718
719
	/**
720
	 * Get the webhook topic, e.g. `order.created`.
721
	 *
722
	 * @since  2.2.0
723
	 * @param  string $context What the value is for.
724
	 *                         Valid values are 'view' and 'edit'.
725
	 * @return string
726
	 */
727 13
	public function get_topic( $context = 'view' ) {
728 13
		return apply_filters( 'woocommerce_webhook_topic', $this->get_prop( 'topic', $context ), $this->get_id() );
729
	}
730
731
	/**
732
	 * Get the delivery URL.
733
	 *
734
	 * @since  2.2.0
735
	 * @param  string $context What the value is for.
736
	 *                         Valid values are 'view' and 'edit'.
737
	 * @return string
738
	 */
739 10
	public function get_delivery_url( $context = 'view' ) {
740 10
		return apply_filters( 'woocommerce_webhook_delivery_url', $this->get_prop( 'delivery_url', $context ), $this->get_id() );
741
	}
742
743
	/**
744
	 * Get the user ID for this webhook.
745
	 *
746
	 * @since  2.2.0
747
	 * @param  string $context What the value is for.
748
	 *                         Valid values are 'view' and 'edit'.
749
	 * @return int
750
	 */
751 10
	public function get_user_id( $context = 'view' ) {
752 10
		return $this->get_prop( 'user_id', $context );
753
	}
754
755
	/**
756
	 * API version.
757
	 *
758
	 * @since  3.0.0
759
	 * @param  string $context What the value is for.
760
	 *                         Valid values are 'view' and 'edit'.
761
	 * @return string
762
	 */
763 10
	public function get_api_version( $context = 'view' ) {
764 10
		$version = $this->get_prop( 'api_version', $context );
765
766 10
		return 0 < $version ? 'wp_api_v' . $version : 'legacy_v3';
767
	}
768
769
	/**
770
	 * Get the failure count.
771
	 *
772
	 * @since  2.2.0
773
	 * @param  string $context What the value is for.
774
	 *                         Valid values are 'view' and 'edit'.
775
	 * @return int
776
	 */
777 10
	public function get_failure_count( $context = 'view' ) {
778 10
		return $this->get_prop( 'failure_count', $context );
779
	}
780
781
	/**
782
	 * Get pending delivery.
783
	 *
784
	 * @since  3.2.0
785
	 * @param  string $context What the value is for.
786
	 *                         Valid values are 'view' and 'edit'.
787
	 * @return bool
788
	 */
789 10
	public function get_pending_delivery( $context = 'view' ) {
790 10
		return $this->get_prop( 'pending_delivery', $context );
791
	}
792
793
	/*
794
	|--------------------------------------------------------------------------
795
	| Setters
796
	|--------------------------------------------------------------------------
797
	 */
798
799
	/**
800
	 * Set webhook name.
801
	 *
802
	 * @since 3.2.0
803
	 * @param string $name Webhook name.
804
	 */
805 9
	public function set_name( $name ) {
806 9
		$this->set_prop( 'name', $name );
807
	}
808
809
	/**
810
	 * Set webhook created date.
811
	 *
812
	 * @since 3.2.0
813
	 * @param string|integer|null $date UTC timestamp, or ISO 8601 DateTime.
814
	 *                                  If the DateTime string has no timezone or offset,
815
	 *                                  WordPress site timezone will be assumed.
816
	 *                                  Null if their is no date.
817
	 */
818 10
	public function set_date_created( $date = null ) {
819 10
		$this->set_date_prop( 'date_created', $date );
820
	}
821
822
	/**
823
	 * Set webhook modified date.
824
	 *
825
	 * @since 3.2.0
826
	 * @param string|integer|null $date UTC timestamp, or ISO 8601 DateTime.
827
	 *                                  If the DateTime string has no timezone or offset,
828
	 *                                  WordPress site timezone will be assumed.
829
	 *                                  Null if their is no date.
830
	 */
831 1
	public function set_date_modified( $date = null ) {
832 1
		$this->set_date_prop( 'date_modified', $date );
833
	}
834
835
	/**
836
	 * Set status.
837
	 *
838
	 * @since 3.2.0
839
	 * @param string $status Status.
840
	 */
841 10
	public function set_status( $status ) {
842 10
		if ( ! array_key_exists( $status, wc_get_webhook_statuses() ) ) {
843
			$status = 'disabled';
844
		}
845
846 10
		$this->set_prop( 'status', $status );
847
	}
848
849
	/**
850
	 * Set the secret used for generating the HMAC-SHA256 signature.
851
	 *
852
	 * @since 2.2.0
853
	 * @param string $secret Secret.
854
	 */
855 9
	public function set_secret( $secret ) {
856 9
		$this->set_prop( 'secret', $secret );
857
	}
858
859
	/**
860
	 * Set the webhook topic and associated hooks.
861
	 * The topic resource & event are also saved separately.
862
	 *
863
	 * @since 2.2.0
864
	 * @param string $topic Webhook topic.
865
	 */
866 12
	public function set_topic( $topic ) {
867 12
		$topic = wc_clean( $topic );
868
869 12
		if ( ! wc_is_webhook_valid_topic( $topic ) ) {
0 ignored issues
show
Bug introduced by
It seems like $topic defined by wc_clean($topic) on line 867 can also be of type array; however, wc_is_webhook_valid_topic() does only seem to accept string, 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...
870
			$topic = '';
871
		}
872
873 12
		$this->set_prop( 'topic', $topic );
874
	}
875
876
	/**
877
	 * Set the delivery URL.
878
	 *
879
	 * @since 2.2.0
880
	 * @param string $url Delivery URL.
881
	 */
882 9
	public function set_delivery_url( $url ) {
883 9
		$this->set_prop( 'delivery_url', esc_url_raw( $url, array( 'http', 'https' ) ) );
884
	}
885
886
	/**
887
	 * Set user ID.
888
	 *
889
	 * @since 3.2.0
890
	 * @param int $user_id User ID.
891
	 */
892 9
	public function set_user_id( $user_id ) {
893 9
		$this->set_prop( 'user_id', (int) $user_id );
894
	}
895
896
	/**
897
	 * Set API version.
898
	 *
899
	 * @since 3.0.0
900
	 * @param int|string $version REST API version.
901
	 */
902 9
	public function set_api_version( $version ) {
903 9
		if ( ! is_numeric( $version ) ) {
904 1
			$version = $this->data_store->get_api_version_number( $version );
905
		}
906
907 9
		$this->set_prop( 'api_version', (int) $version );
908
	}
909
910
	/**
911
	 * Set pending delivery.
912
	 *
913
	 * @since 3.2.0
914
	 * @param bool $pending_delivery Set true if is pending for delivery.
915
	 */
916 10
	public function set_pending_delivery( $pending_delivery ) {
917 10
		$this->set_prop( 'pending_delivery', (bool) $pending_delivery );
918
	}
919
920
	/**
921
	 * Set failure count.
922
	 *
923
	 * @since 3.2.0
924
	 * @param bool $failure_count Total of failures.
925
	 */
926 9
	public function set_failure_count( $failure_count ) {
927 9
		$this->set_prop( 'failure_count', intval( $failure_count ) );
928
	}
929
930
	/*
931
	|--------------------------------------------------------------------------
932
	| Non-CRUD Getters
933
	|--------------------------------------------------------------------------
934
	*/
935
936
	/**
937
	 * Get the associated hook names for a topic.
938
	 *
939
	 * @since  2.2.0
940
	 * @param  string $topic Topic name.
941
	 * @return array
942
	 */
943 1
	private function get_topic_hooks( $topic ) {
944
		$topic_hooks = array(
945
			'coupon.created'   => array(
946 1
				'woocommerce_process_shop_coupon_meta',
947
				'woocommerce_new_coupon',
948
			),
949
			'coupon.updated'   => array(
950
				'woocommerce_process_shop_coupon_meta',
951
				'woocommerce_update_coupon',
952
			),
953
			'coupon.deleted'   => array(
954
				'wp_trash_post',
955
			),
956
			'coupon.restored'  => array(
957
				'untrashed_post',
958
			),
959
			'customer.created' => array(
960
				'user_register',
961
				'woocommerce_created_customer',
962
				'woocommerce_new_customer',
963
			),
964
			'customer.updated' => array(
965
				'profile_update',
966
				'woocommerce_update_customer',
967
			),
968
			'customer.deleted' => array(
969
				'delete_user',
970
			),
971
			'order.created'    => array(
972
				'woocommerce_new_order',
973
			),
974
			'order.updated'    => array(
975
				'woocommerce_update_order',
976
				'woocommerce_order_refunded',
977
			),
978
			'order.deleted'    => array(
979
				'wp_trash_post',
980
			),
981
			'order.restored'   => array(
982
				'untrashed_post',
983
			),
984
			'product.created'  => array(
985
				'woocommerce_process_product_meta',
986
				'woocommerce_new_product',
987
				'woocommerce_new_product_variation',
988
			),
989
			'product.updated'  => array(
990
				'woocommerce_process_product_meta',
991
				'woocommerce_update_product',
992
				'woocommerce_update_product_variation',
993
			),
994
			'product.deleted'  => array(
995
				'wp_trash_post',
996
			),
997
			'product.restored' => array(
998
				'untrashed_post',
999
			),
1000
		);
1001
1002 1
		$topic_hooks = apply_filters( 'woocommerce_webhook_topic_hooks', $topic_hooks, $this );
1003
1004 1
		return isset( $topic_hooks[ $topic ] ) ? $topic_hooks[ $topic ] : array();
1005
	}
1006
1007
	/**
1008
	 * Get the hook names for the webhook.
1009
	 *
1010
	 * @since  2.2.0
1011
	 * @return array
1012
	 */
1013 9
	public function get_hooks() {
1014 9
		if ( 'action' === $this->get_resource() ) {
1015 8
			$hooks = array( $this->get_event() );
1016
		} else {
1017 1
			$hooks = $this->get_topic_hooks( $this->get_topic() );
1018
		}
1019
1020 9
		return apply_filters( 'woocommerce_webhook_hooks', $hooks, $this->get_id() );
1021
	}
1022
1023
	/**
1024
	 * Get the resource for the webhook, e.g. `order`.
1025
	 *
1026
	 * @since  2.2.0
1027
	 * @return string
1028
	 */
1029 10
	public function get_resource() {
1030 10
		$topic = explode( '.', $this->get_topic() );
1031
1032 10
		return apply_filters( 'woocommerce_webhook_resource', $topic[0], $this->get_id() );
1033
	}
1034
1035
	/**
1036
	 * Get the event for the webhook, e.g. `created`.
1037
	 *
1038
	 * @since  2.2.0
1039
	 * @return string
1040
	 */
1041 9
	public function get_event() {
1042 9
		$topic = explode( '.', $this->get_topic() );
1043
1044 9
		return apply_filters( 'woocommerce_webhook_event', isset( $topic[1] ) ? $topic[1] : '', $this->get_id() );
1045
	}
1046
1047
	/**
1048
	 * Get the webhook i18n status.
1049
	 *
1050
	 * @return string
1051
	 */
1052 1
	public function get_i18n_status() {
1053 1
		$status   = $this->get_status();
1054 1
		$statuses = wc_get_webhook_statuses();
1055
1056 1
		return isset( $statuses[ $status ] ) ? $statuses[ $status ] : $status;
1057
	}
1058
}
1059