Completed
Push — master ( ff1020...000c95 )
by Rodrigo
23:32 queued 11s
created

WC_Coupon_Data_Store_CPT::get_usage_by_email()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 2
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Class WC_Coupon_Data_Store_CPT file.
4
 *
5
 * @package WooCommerce\DataStore
6
 */
7
8
if ( ! defined( 'ABSPATH' ) ) {
9
	exit;
10
}
11
12
/**
13
 * WC Coupon Data Store: Custom Post Type.
14
 *
15
 * @version  3.0.0
16
 */
17
class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Data_Store_Interface, WC_Object_Data_Store_Interface {
18
19
	/**
20
	 * Internal meta type used to store coupon data.
21
	 *
22
	 * @since 3.0.0
23
	 * @var string
24
	 */
25
	protected $meta_type = 'post';
26
27
	/**
28
	 * Data stored in meta keys, but not considered "meta" for a coupon.
29
	 *
30
	 * @since 3.0.0
31
	 * @var array
32
	 */
33
	protected $internal_meta_keys = array(
34
		'discount_type',
35
		'coupon_amount',
36
		'expiry_date',
37
		'date_expires',
38
		'usage_count',
39
		'individual_use',
40
		'product_ids',
41
		'exclude_product_ids',
42
		'usage_limit',
43
		'usage_limit_per_user',
44
		'limit_usage_to_x_items',
45
		'free_shipping',
46
		'product_categories',
47
		'exclude_product_categories',
48
		'exclude_sale_items',
49
		'minimum_amount',
50
		'maximum_amount',
51
		'customer_email',
52
		'_used_by',
53
		'_edit_lock',
54
		'_edit_last',
55
	);
56
57
	/**
58
	 * Method to create a new coupon in the database.
59
	 *
60
	 * @since 3.0.0
61
	 * @param WC_Coupon $coupon Coupon object.
62
	 */
63 31
	public function create( &$coupon ) {
64 31
		$coupon->set_date_created( current_time( 'timestamp', true ) );
65
66 31
		$coupon_id = wp_insert_post(
67 31
			apply_filters(
68 31
				'woocommerce_new_coupon_data',
69
				array(
70 31
					'post_type'     => 'shop_coupon',
71 31
					'post_status'   => 'publish',
72 31
					'post_author'   => get_current_user_id(),
73 31
					'post_title'    => $coupon->get_code( 'edit' ),
74 31
					'post_content'  => '',
75 31
					'post_excerpt'  => $coupon->get_description( 'edit' ),
76 31
					'post_date'     => gmdate( 'Y-m-d H:i:s', $coupon->get_date_created()->getOffsetTimestamp() ),
77 31
					'post_date_gmt' => gmdate( 'Y-m-d H:i:s', $coupon->get_date_created()->getTimestamp() ),
78
				)
79
			),
80 31
			true
81
		);
82
83 31
		if ( $coupon_id ) {
84 31
			$coupon->set_id( $coupon_id );
85 31
			$this->update_post_meta( $coupon );
86 31
			$coupon->save_meta_data();
87 31
			$coupon->apply_changes();
88 31
			delete_transient( 'rest_api_coupons_type_count' );
89 31
			do_action( 'woocommerce_new_coupon', $coupon_id );
90
		}
91
	}
92
93
	/**
94
	 * Method to read a coupon.
95
	 *
96
	 * @since 3.0.0
97
	 *
98
	 * @param WC_Coupon $coupon Coupon object.
99
	 *
100
	 * @throws Exception If invalid coupon.
101
	 */
102 104
	public function read( &$coupon ) {
103 104
		$coupon->set_defaults();
104
105 104
		$post_object = get_post( $coupon->get_id() );
106
107 104 View Code Duplication
		if ( ! $coupon->get_id() || ! $post_object || 'shop_coupon' !== $post_object->post_type ) {
108
			throw new Exception( __( 'Invalid coupon.', 'woocommerce' ) );
109
		}
110
111 104
		$coupon_id = $coupon->get_id();
112 104
		$coupon->set_props(
113
			array(
114 104
				'code'                        => $post_object->post_title,
115 104
				'description'                 => $post_object->post_excerpt,
116 104
				'date_created'                => 0 < $post_object->post_date_gmt ? wc_string_to_timestamp( $post_object->post_date_gmt ) : null,
117 104
				'date_modified'               => 0 < $post_object->post_modified_gmt ? wc_string_to_timestamp( $post_object->post_modified_gmt ) : null,
118 104
				'date_expires'                => metadata_exists( 'post', $coupon_id, 'date_expires' ) ? get_post_meta( $coupon_id, 'date_expires', true ) : get_post_meta( $coupon_id, 'expiry_date', true ), // @todo: Migrate expiry_date meta to date_expires in upgrade routine.
119 104
				'discount_type'               => get_post_meta( $coupon_id, 'discount_type', true ),
120 104
				'amount'                      => get_post_meta( $coupon_id, 'coupon_amount', true ),
121 104
				'usage_count'                 => get_post_meta( $coupon_id, 'usage_count', true ),
122 104
				'individual_use'              => 'yes' === get_post_meta( $coupon_id, 'individual_use', true ),
123 104
				'product_ids'                 => array_filter( (array) explode( ',', get_post_meta( $coupon_id, 'product_ids', true ) ) ),
124 104
				'excluded_product_ids'        => array_filter( (array) explode( ',', get_post_meta( $coupon_id, 'exclude_product_ids', true ) ) ),
125 104
				'usage_limit'                 => get_post_meta( $coupon_id, 'usage_limit', true ),
126 104
				'usage_limit_per_user'        => get_post_meta( $coupon_id, 'usage_limit_per_user', true ),
127 104
				'limit_usage_to_x_items'      => 0 < get_post_meta( $coupon_id, 'limit_usage_to_x_items', true ) ? get_post_meta( $coupon_id, 'limit_usage_to_x_items', true ) : null,
128 104
				'free_shipping'               => 'yes' === get_post_meta( $coupon_id, 'free_shipping', true ),
129 104
				'product_categories'          => array_filter( (array) get_post_meta( $coupon_id, 'product_categories', true ) ),
130 104
				'excluded_product_categories' => array_filter( (array) get_post_meta( $coupon_id, 'exclude_product_categories', true ) ),
131 104
				'exclude_sale_items'          => 'yes' === get_post_meta( $coupon_id, 'exclude_sale_items', true ),
132 104
				'minimum_amount'              => get_post_meta( $coupon_id, 'minimum_amount', true ),
133 104
				'maximum_amount'              => get_post_meta( $coupon_id, 'maximum_amount', true ),
134 104
				'email_restrictions'          => array_filter( (array) get_post_meta( $coupon_id, 'customer_email', true ) ),
135 104
				'used_by'                     => array_filter( (array) get_post_meta( $coupon_id, '_used_by' ) ),
136
			)
137
		);
138 104
		$coupon->read_meta_data();
139 104
		$coupon->set_object_read( true );
140 104
		do_action( 'woocommerce_coupon_loaded', $coupon );
141
	}
142
143
	/**
144
	 * Updates a coupon in the database.
145
	 *
146
	 * @since 3.0.0
147
	 * @param WC_Coupon $coupon Coupon object.
148
	 */
149 15
	public function update( &$coupon ) {
150 15
		$coupon->save_meta_data();
151 15
		$changes = $coupon->get_changes();
152
153 15
		if ( array_intersect( array( 'code', 'description', 'date_created', 'date_modified' ), array_keys( $changes ) ) ) {
154
			$post_data = array(
155 4
				'post_title'        => $coupon->get_code( 'edit' ),
156 4
				'post_excerpt'      => $coupon->get_description( 'edit' ),
157 4
				'post_date'         => gmdate( 'Y-m-d H:i:s', $coupon->get_date_created( 'edit' )->getOffsetTimestamp() ),
158 4
				'post_date_gmt'     => gmdate( 'Y-m-d H:i:s', $coupon->get_date_created( 'edit' )->getTimestamp() ),
159 4
				'post_modified'     => isset( $changes['date_modified'] ) ? gmdate( 'Y-m-d H:i:s', $coupon->get_date_modified( 'edit' )->getOffsetTimestamp() ) : current_time( 'mysql' ),
160 4
				'post_modified_gmt' => isset( $changes['date_modified'] ) ? gmdate( 'Y-m-d H:i:s', $coupon->get_date_modified( 'edit' )->getTimestamp() ) : current_time( 'mysql', 1 ),
161
			);
162
163
			/**
164
			 * When updating this object, to prevent infinite loops, use $wpdb
165
			 * to update data, since wp_update_post spawns more calls to the
166
			 * save_post action.
167
			 *
168
			 * This ensures hooks are fired by either WP itself (admin screen save),
169
			 * or an update purely from CRUD.
170
			 */
171 4 View Code Duplication
			if ( doing_action( 'save_post' ) ) {
172
				$GLOBALS['wpdb']->update( $GLOBALS['wpdb']->posts, $post_data, array( 'ID' => $coupon->get_id() ) );
173
				clean_post_cache( $coupon->get_id() );
174
			} else {
175 4
				wp_update_post( array_merge( array( 'ID' => $coupon->get_id() ), $post_data ) );
176
			}
177 4
			$coupon->read_meta_data( true ); // Refresh internal meta data, in case things were hooked into `save_post` or another WP hook.
178
		}
179 15
		$this->update_post_meta( $coupon );
180 15
		$coupon->apply_changes();
181 15
		delete_transient( 'rest_api_coupons_type_count' );
182 15
		do_action( 'woocommerce_update_coupon', $coupon->get_id() );
183
	}
184
185
	/**
186
	 * Deletes a coupon from the database.
187
	 *
188
	 * @since 3.0.0
189
	 *
190
	 * @param WC_Coupon $coupon Coupon object.
191
	 * @param array     $args Array of args to pass to the delete method.
192
	 */
193 6
	public function delete( &$coupon, $args = array() ) {
194 6
		$args = wp_parse_args(
195 6
			$args,
196
			array(
197 6
				'force_delete' => false,
198
			)
199
		);
200
201 6
		$id = $coupon->get_id();
202
203 6
		if ( ! $id ) {
204
			return;
205
		}
206
207 6
		if ( $args['force_delete'] ) {
208 6
			wp_delete_post( $id );
209
210 6
			wp_cache_delete( WC_Cache_Helper::get_cache_prefix( 'coupons' ) . 'coupon_id_from_code_' . $coupon->get_code(), 'coupons' );
211
212 6
			$coupon->set_id( 0 );
213 6
			do_action( 'woocommerce_delete_coupon', $id );
214
		} else {
215
			wp_trash_post( $id );
216
			do_action( 'woocommerce_trash_coupon', $id );
217
		}
218
	}
219
220
	/**
221
	 * Helper method that updates all the post meta for a coupon based on it's settings in the WC_Coupon class.
222
	 *
223
	 * @param WC_Coupon $coupon Coupon object.
224
	 * @since 3.0.0
225
	 */
226 43
	private function update_post_meta( &$coupon ) {
227 43
		$updated_props     = array();
228
		$meta_key_to_props = array(
229 43
			'discount_type'              => 'discount_type',
230
			'coupon_amount'              => 'amount',
231
			'individual_use'             => 'individual_use',
232
			'product_ids'                => 'product_ids',
233
			'exclude_product_ids'        => 'excluded_product_ids',
234
			'usage_limit'                => 'usage_limit',
235
			'usage_limit_per_user'       => 'usage_limit_per_user',
236
			'limit_usage_to_x_items'     => 'limit_usage_to_x_items',
237
			'usage_count'                => 'usage_count',
238
			'date_expires'               => 'date_expires',
239
			'free_shipping'              => 'free_shipping',
240
			'product_categories'         => 'product_categories',
241
			'exclude_product_categories' => 'excluded_product_categories',
242
			'exclude_sale_items'         => 'exclude_sale_items',
243
			'minimum_amount'             => 'minimum_amount',
244
			'maximum_amount'             => 'maximum_amount',
245
			'customer_email'             => 'email_restrictions',
246
		);
247
248 43
		$props_to_update = $this->get_props_to_update( $coupon, $meta_key_to_props );
249 43
		foreach ( $props_to_update as $meta_key => $prop ) {
250 43
			$value = $coupon->{"get_$prop"}( 'edit' );
251 43
			$value = is_string( $value ) ? wp_slash( $value ) : $value;
252 43
			switch ( $prop ) {
253
				case 'individual_use':
254
				case 'free_shipping':
255
				case 'exclude_sale_items':
256 34
					$value = wc_bool_to_string( $value );
257 34
					break;
258
				case 'product_ids':
259
				case 'excluded_product_ids':
260 31
					$value = implode( ',', array_filter( array_map( 'intval', $value ) ) );
261 31
					break;
262
				case 'product_categories':
263
				case 'excluded_product_categories':
264 31
					$value = array_filter( array_map( 'intval', $value ) );
265 31
					break;
266
				case 'email_restrictions':
267 31
					$value = array_filter( array_map( 'sanitize_email', $value ) );
268 31
					break;
269
				case 'date_expires':
270 43
					$value = $value ? $value->getTimestamp() : null;
271 43
					break;
272
			}
273
274 43
			$updated = $this->update_or_delete_post_meta( $coupon, $meta_key, $value );
275
276 43
			if ( $updated ) {
277 43
				$this->updated_props[] = $prop;
278
			}
279
		}
280
281 43
		do_action( 'woocommerce_coupon_object_updated_props', $coupon, $updated_props );
282
	}
283
284
	/**
285
	 * Increase usage count for current coupon.
286
	 *
287
	 * @since 3.0.0
288
	 * @param WC_Coupon $coupon Coupon object.
289
	 * @param string    $used_by Either user ID or billing email.
290
	 * @return int New usage count.
291
	 */
292 12
	public function increase_usage_count( &$coupon, $used_by = '' ) {
293 12
		$new_count = $this->update_usage_count_meta( $coupon, 'increase' );
294 12
		if ( $used_by ) {
295 12
			add_post_meta( $coupon->get_id(), '_used_by', strtolower( $used_by ) );
296 12
			$coupon->set_used_by( (array) get_post_meta( $coupon->get_id(), '_used_by' ) );
297
		}
298
299 12
		do_action( 'woocommerce_increase_coupon_usage_count', $coupon, $new_count, $used_by );
300
301 12
		return $new_count;
302
	}
303
304
	/**
305
	 * Decrease usage count for current coupon.
306
	 *
307
	 * @since 3.0.0
308
	 * @param WC_Coupon $coupon Coupon object.
309
	 * @param string    $used_by Either user ID or billing email.
310
	 * @return int New usage count.
311
	 */
312 4
	public function decrease_usage_count( &$coupon, $used_by = '' ) {
313
		global $wpdb;
314 4
		$new_count = $this->update_usage_count_meta( $coupon, 'decrease' );
315 4
		if ( $used_by ) {
316
			/**
317
			 * We're doing this the long way because `delete_post_meta( $id, $key, $value )` deletes.
318
			 * all instances where the key and value match, and we only want to delete one.
319
			 */
320 4
			$meta_id = $wpdb->get_var( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE meta_key = '_used_by' AND meta_value = %s AND post_id = %d LIMIT 1;", $used_by, $coupon->get_id() ) );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
321 4
			if ( $meta_id ) {
322 4
				delete_metadata_by_mid( 'post', $meta_id );
323 4
				$coupon->set_used_by( (array) get_post_meta( $coupon->get_id(), '_used_by' ) );
324
			}
325
		}
326
327 4
		do_action( 'woocommerce_decrease_coupon_usage_count', $coupon, $new_count, $used_by );
328
329 4
		return $new_count;
330
	}
331
332
	/**
333
	 * Increase or decrease the usage count for a coupon by 1.
334
	 *
335
	 * @since 3.0.0
336
	 * @param WC_Coupon $coupon Coupon object.
337
	 * @param string    $operation 'increase' or 'decrease'.
338
	 * @return int New usage count
339
	 */
340 12
	private function update_usage_count_meta( &$coupon, $operation = 'increase' ) {
341
		global $wpdb;
342 12
		$id       = $coupon->get_id();
343 12
		$operator = ( 'increase' === $operation ) ? '+' : '-';
344
345 12
		add_post_meta( $id, 'usage_count', $coupon->get_usage_count( 'edit' ), true );
346 12
		$wpdb->query(
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
347 12
			$wpdb->prepare(
348
				// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
349 12
				"UPDATE $wpdb->postmeta SET meta_value = meta_value {$operator} 1 WHERE meta_key = 'usage_count' AND post_id = %d;",
350
				$id
351
			)
352
		);
353
354
		// Get the latest value direct from the DB, instead of possibly the WP meta cache.
355 12
		return (int) $wpdb->get_var( $wpdb->prepare( "SELECT meta_value FROM $wpdb->postmeta WHERE meta_key = 'usage_count' AND post_id = %d;", $id ) );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
356
	}
357
358
	/**
359
	 * Get the number of uses for a coupon by user ID.
360
	 *
361
	 * @since 3.0.0
362
	 * @param WC_Coupon $coupon Coupon object.
363
	 * @param id        $user_id User ID.
364
	 * @return int
365
	 */
366 1
	public function get_usage_by_user_id( &$coupon, $user_id ) {
367
		global $wpdb;
368 1
		return $wpdb->get_var( $wpdb->prepare( "SELECT COUNT( meta_id ) FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = '_used_by' AND meta_value = %d;", $coupon->get_id(), $user_id ) );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
369
	}
370
371
	/**
372
	 * Get the number of uses for a coupon by email address
373
	 *
374
	 * @since 3.6.4
375
	 * @param WC_Coupon $coupon Coupon object.
376
	 * @param string    $email Email address.
377
	 * @return int
378
	 */
379 1
	public function get_usage_by_email( &$coupon, $email ) {
380
		global $wpdb;
381 1
		return $wpdb->get_var( $wpdb->prepare( "SELECT COUNT( meta_id ) FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = '_used_by' AND meta_value = %s;", $coupon->get_id(), $email ) );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
382
	}
383
384
	/**
385
	 * Return a coupon code for a specific ID.
386
	 *
387
	 * @since 3.0.0
388
	 * @param int $id Coupon ID.
389
	 * @return string Coupon Code
390
	 */
391 1
	public function get_code_by_id( $id ) {
392
		global $wpdb;
393 1
		return $wpdb->get_var(
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
394 1
			$wpdb->prepare(
395 1
				"SELECT post_title
396 1
				FROM $wpdb->posts
397
				WHERE ID = %d
398
				AND post_type = 'shop_coupon'
399
				AND post_status = 'publish'",
400
				$id
401
			)
402
		);
403
	}
404
405
	/**
406
	 * Return an array of IDs for for a specific coupon code.
407
	 * Can return multiple to check for existence.
408
	 *
409
	 * @since 3.0.0
410
	 * @param string $code Coupon code.
411
	 * @return array Array of IDs.
412
	 */
413 105
	public function get_ids_by_code( $code ) {
414
		global $wpdb;
415 105
		return $wpdb->get_col(
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
416 105
			$wpdb->prepare(
417 105
				"SELECT ID FROM $wpdb->posts WHERE post_title = %s AND post_type = 'shop_coupon' AND post_status = 'publish' ORDER BY post_date DESC",
418
				$code
419
			)
420
		);
421
	}
422
}
423