Passed
Pull Request — master (#257)
by
unknown
04:32
created

WPInv_Discount::sanitize_discount_data()   C

Complexity

Conditions 12
Paths 38

Size

Total Lines 81
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 47
c 1
b 0
f 0
nc 38
nop 1
dl 0
loc 81
rs 6.9666

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
 * Contains Discount calculation class
4
 *
5
 * @since   1.0.14
6
 */
7
8
defined( 'ABSPATH' ) || exit;
9
10
/**
11
 * Discount class.
12
 * 
13
 * @since 1.0.14
14
 * @property string $code
15
 * @property string $description
16
 * @property string $type
17
 * @property string $type_name
18
 * @property string $expiration
19
 * @property string $start
20
 * @property string $status
21
 * @property string $date_modified
22
 * @property string $date_created
23
 * @property array $items
24
 * @property array $excluded_items
25
 * @property int $uses
26
 * @property int $max_uses
27
 * @property bool $is_recurring
28
 * @property bool $is_single_use
29
 * @property float $min_total
30
 * @property float $max_total
31
 * @property float $amount
32
 *
33
 */
34
class WPInv_Discount {
35
	
36
	/**
37
	 * Discount ID.
38
	 *
39
	 * @since 1.0.14
40
	 * @var integer|null
41
	 */
42
	public $ID = null;
43
44
	/**
45
	 * Old discount status.
46
	 *
47
	 * @since 1.0.14
48
	 * @var string
49
	 */
50
	public $old_status = 'draft';
51
	
52
	/**
53
	 * Data array, with defaults.
54
	 *
55
	 * @since 1.0.14
56
	 * @var array
57
	 */
58
	protected $data = array();
59
60
	/**
61
	 * Discount constructor.
62
	 *
63
	 * @param int|array|string|WPInv_Discount $discount discount data, object, ID or code.
64
	 * @since 1.0.14
65
	 */
66
	public function __construct( $discount = array() ) {
67
        
68
        // If the discount is an instance of this class...
69
		if ( $discount instanceof WPInv_Discount ) {
70
			$this->init( $discount->data );
71
			return;
72
        }
73
        
74
        // If the discount is an array of discount details...
75
        if ( is_array( $discount ) ) {
76
			$this->init( $discount );
77
			return;
78
		}
79
		
80
		// Try fetching the discount by its post id.
81
		$data = false;
82
		
83
		if ( ! empty( $discount ) && is_numeric( $discount ) ) {
84
			$discount = absint( $discount );
85
			$data = self::get_data_by( 'id', $discount );
86
		}
87
88
		if ( is_array( $data ) ) {
89
			$this->init( $data );
90
			return;
91
		}
92
		
93
		// Try fetching the discount by its discount code.
94
		if ( ! empty( $discount ) && is_string( $discount ) ) {
95
			$data = self::get_data_by( 'discount_code', $discount );
96
		}
97
98
		if ( is_array( $data ) ) {
99
			$this->init( $data );
100
			return;
101
		} 
102
		
103
		// If we are here then the discount does not exist.
104
		$this->init( array() );
105
	}
106
	
107
	/**
108
	 * Sets up object properties
109
	 *
110
	 * @since 1.0.14
111
	 * @param array $data An array containing the discount's data
112
	 */
113
	public function init( $data ) {
114
		$data       	  = self::sanitize_discount_data( $data );
115
		$this->data 	  = $data;
116
		$this->old_status = $data['status'];
117
		$this->ID   	  = $data['ID'];
118
	}
119
	
120
	/**
121
	 * Fetch a discount from the db/cache
122
	 *
123
	 *
124
	 * @static
125
	 *
126
	 *
127
	 * @param string $field The field to query against: 'ID', 'discount_code'
128
	 * @param string|int $value The field value
129
	 * @since 1.0.14
130
	 * @return array|bool array of discount details on success. False otherwise.
131
	 */
132
	public static function get_data_by( $field, $value ) {
133
134
		// 'ID' is an alias of 'id'.
135
		if ( 'ID' === $field ) {
136
			$field = 'id';
137
		}
138
139
		if ( 'id' == $field ) {
140
			// Make sure the value is numeric to avoid casting objects, for example,
141
			// to int 1.
142
			if ( ! is_numeric( $value ) )
143
				return false;
144
			$value = intval( $value );
145
			if ( $value < 1 )
146
				return false;
147
		} else {
148
			$value = trim( $value );
149
		}
150
151
		if ( !$value || ! is_string( $field ) )
152
			return false;
153
154
		// prepare query args
155
		switch ( strtolower( $field ) ) {
156
			case 'id':
157
				$discount_id = $value;
158
				$args		 = array( 'include' => array( $value ) );
159
				break;
160
			case 'discount_code':
161
			case 'code':
162
				$discount_id = wp_cache_get( $value, 'WPInv_Discount_Codes' );
163
				$args		 = array( 'meta_key' => '_wpi_discount_code', 'meta_value' => $value );
164
				break;
165
			case 'name':
166
				$discount_id = 0;
167
				$args		 = array( 'name' => $value );
168
				break;
169
			default:
170
				return false;
171
		}
172
173
		// Check if there is a cached value.
174
		if ( ! empty( $discount_id ) && $discount = wp_cache_get( (int) $discount_id, 'WPInv_Discounts' ) ) {
175
			return $discount;
176
		}
177
178
		$args = array_merge(
179
			$args,
180
			array(
181
				'post_type'      => 'wpi_discount',
182
				'posts_per_page' => 1,
183
				'post_status'    => array( 'publish', 'pending', 'draft', 'expired' )
184
			)
185
		);
186
187
		$discount = get_posts( $args );
0 ignored issues
show
Security Variable Injection introduced by
$args can contain request data and is used in variable name context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. Read from $_POST, and wpinv_store_discount() is called
    in includes/admin/admin-meta-boxes.php on line 379
  2. Enters via parameter $data
    in includes/wpinv-discount-functions.php on line 252
  3. array('code' => IssetNode ? sanitize_text_field($data['code']) : $existing_data['code'], 'type' => IssetNode ? sanitize_text_field($data['type']) : $existing_data['type'], 'amount' => IssetNode ? wpinv_sanitize_amount($data['amount']) : $existing_data['amount'], 'start' => IssetNode ? sanitize_text_field($data['start']) : $existing_data['start'], 'expiration' => IssetNode ? sanitize_text_field($data['expiration']) : $existing_data['expiration'], 'min_total' => IssetNode ? wpinv_sanitize_amount($data['min_total']) : $existing_data['min_total'], 'max_total' => IssetNode ? wpinv_sanitize_amount($data['max_total']) : $existing_data['max_total'], 'max_uses' => IssetNode ? absint($data['max_uses']) : $existing_data['max_uses'], 'items' => IssetNode ? $data['items'] : $existing_data['items'], 'excluded_items' => IssetNode ? $data['excluded_items'] : $existing_data['excluded_items'], 'is_recurring' => IssetNode ? (bool)$data['recurring'] : $existing_data['is_recurring'], 'is_single_use' => IssetNode ? (bool)$data['single_use'] : false, 'uses' => IssetNode ? (int)$data['uses'] : $existing_data['uses']) is assigned to $meta
    in includes/wpinv-discount-functions.php on line 260
  4. Data is passed through array_merge(), and array_merge($existing_data, $meta) is assigned to $data
    in includes/wpinv-discount-functions.php on line 277
  5. wpinv_get_discount_obj() is called
    in includes/wpinv-discount-functions.php on line 278
  6. Enters via parameter $discount
    in includes/wpinv-discount-functions.php on line 215
  7. WPInv_Discount::__construct() is called
    in includes/wpinv-discount-functions.php on line 216
  8. Enters via parameter $discount
    in includes/class-wpinv-discount.php on line 66
  9. WPInv_Discount::get_data_by() is called
    in includes/class-wpinv-discount.php on line 95
  10. Enters via parameter $value
    in includes/class-wpinv-discount.php on line 132
  11. Data is passed through trim(), and trim($value) is assigned to $value
    in includes/class-wpinv-discount.php on line 148
  12. array('include' => array($value)) is assigned to $args
    in includes/class-wpinv-discount.php on line 158
  13. Data is passed through array_merge(), and array_merge($args, array('post_type' => 'wpi_discount', 'posts_per_page' => 1, 'post_status' => array('publish', 'pending', 'draft', 'expired'))) is assigned to $args
    in includes/class-wpinv-discount.php on line 178

Used in variable context

  1. get_posts() is called
    in includes/class-wpinv-discount.php on line 187
  2. Enters via parameter $args
    in wordpress/wp-includes/post.php on line 2009
  3. wp_parse_args() is called
    in wordpress/wp-includes/post.php on line 2023
  4. Enters via parameter $args
    in wordpress/wp-includes/functions.php on line 4294
  5. wp_parse_str() is called
    in wordpress/wp-includes/functions.php on line 4300
  6. Enters via parameter $string
    in wordpress/wp-includes/formatting.php on line 4865
  7. parse_str() is called
    in wordpress/wp-includes/formatting.php on line 4866

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
188
				
189
		if( empty( $discount ) ) {
190
			return false;
191
		}
192
193
		$discount = $discount[0];
194
		
195
		// Prepare the return data.
196
		$return = array(
197
            'ID'                          => $discount->ID,
198
            'code'                        => get_post_meta( $discount->ID, '_wpi_discount_code', true ),
199
            'amount'                      => get_post_meta( $discount->ID, '_wpi_discount_amount', true ),
200
            'date_created'                => $discount->post_date,
201
			'date_modified'               => $discount->post_modified,
202
			'status'               		  => $discount->post_status,
203
			'start'                  	  => get_post_meta( $discount->ID, '_wpi_discount_start', true ),
204
            'expiration'                  => get_post_meta( $discount->ID, '_wpi_discount_expiration', true ),
205
            'type'               		  => get_post_meta( $discount->ID, '_wpi_discount_type', true ),
206
            'description'                 => $discount->post_excerpt,
207
            'uses'                 		  => get_post_meta( $discount->ID, '_wpi_discount_uses', true ),
208
            'is_single_use'               => get_post_meta( $discount->ID, '_wpi_discount_is_single_use', true ),
209
            'items'              	      => get_post_meta( $discount->ID, '_wpi_discount_items', true ),
210
            'excluded_items'              => get_post_meta( $discount->ID, '_wpi_discount_excluded_items', true ),
211
            'max_uses'                    => get_post_meta( $discount->ID, '_wpi_discount_max_uses', true ),
212
            'is_recurring'                => get_post_meta( $discount->ID, '_wpi_discount_is_recurring', true ),
213
            'min_total'                   => get_post_meta( $discount->ID, '_wpi_discount_min_total', true ),
214
            'max_total'                   => get_post_meta( $discount->ID, '_wpi_discount_max_total', true ),
215
        );
216
		
217
		$return = self::sanitize_discount_data( $return );
218
		$return = apply_filters( 'wpinv_discount_properties', $return );
219
220
		// Update the cache with our data
221
		wp_cache_add( $discount->ID, $return, 'WPInv_Discounts' );
222
		wp_cache_add( $return['code'], $discount->ID, 'WPInv_Discount_Codes' );
223
224
		return $return;
225
	}
226
	
227
	/**
228
	 * Sanitizes discount data
229
	 *
230
	 * @static
231
	 * @since 1.0.14
232
	 * @access public
233
	 *
234
	 * @return array the sanitized data
235
	 */
236
	public static function sanitize_discount_data( $data ) {
237
		
238
		$allowed_discount_types = array_keys( wpinv_get_discount_types() );
239
		
240
		$return = array(
241
            'ID'                          => null,
242
            'code'                        => '',
243
            'amount'                      => 0,
244
            'date_created'                => current_time('mysql'),
245
            'date_modified'               => current_time('mysql'),
246
			'expiration'                  => null,
247
			'start'                  	  => current_time('mysql'),
248
			'status'                  	  => 'draft',
249
            'type'               		  => 'percent',
250
            'description'                 => '',
251
            'uses'                        => 0,
252
            'is_single_use'               => false,
253
            'items'              		  => array(),
254
            'excluded_items'              => array(),
255
            'max_uses'                    => 0,
256
            'is_recurring'                => false,
257
            'min_total'                   => '',
258
			'max_total'              	  => '',
259
		);
260
		
261
				
262
		// Arrays only please.
263
		if (! is_array( $data ) ) {
264
            return $return;
265
        }
266
267
		// If an id is provided, ensure it is a valid discount.
268
        if (! empty( $data['ID'] ) && is_numeric( $data['ID'] ) && 'wpi_discount' !== get_post_type( $data['ID'] ) ) {
269
            return $return;
270
		}
271
272
        $return = array_merge( $return, $data );
273
274
        // Sanitize some keys.
275
        $return['amount']         = wpinv_sanitize_amount( $return['amount'] );
276
		$return['is_single_use']  = (bool) $return['is_single_use'];
277
		$return['is_recurring']   = (bool) $return['is_recurring'];
278
		$return['uses']	          = (int) $return['uses'];
279
		$return['max_uses']	      = (int) $return['max_uses'];
280
		$return['min_total'] 	  = wpinv_sanitize_amount( $return['min_total'] );
281
        $return['max_total'] 	  = wpinv_sanitize_amount( $return['max_total'] );
282
283
		// Trim all values.
284
		$return = wpinv_clean( $return );
285
		
286
		// Ensure the discount type is supported.
287
        if ( ! in_array( $return['type'], $allowed_discount_types, true ) ) {
288
            $return['type'] = 'percent';
289
		}
290
		$return['type_name'] = wpinv_get_discount_type_name( $return['type'] );
291
		
292
		// Do not offer more than a 100% discount.
293
		if ( $return['type'] == 'percent' && (float) $return['amount'] > 100 ) {
294
			$return['amount'] = 100;
295
		}
296
297
		// Format dates.
298
		foreach( wpinv_parse_list( 'date_created date_modified expiration start') as $prop ) {
299
			if( ! empty( $return[$prop] ) ) {
300
				$return[$prop]      = date_i18n( 'Y-m-d H:i:s', strtotime( $return[$prop] ) );
301
			}
302
		}
303
304
		// Formart items.
305
		foreach( wpinv_parse_list( 'excluded_items items') as $prop ) {
306
307
			if( ! empty( $return[$prop] ) ) {
308
				// Ensure that the property is an array of non-empty integers.
309
				$return[$prop]      = array_filter( array_map( 'intval', wpinv_parse_list( $return[$prop] ) ) );
310
			} else {
311
				$return[$prop]      = array();
312
			}
313
314
		}
315
		
316
		return apply_filters( 'sanitize_discount_data', $return, $data );
317
	}
318
	
319
	/**
320
	 * Magic method for checking the existence of a certain custom field.
321
	 *
322
	 * @since 1.0.14
323
	 * @access public
324
	 *
325
	 * @return bool Whether the given discount field is set.
326
	 */
327
	public function __isset( $key ){
328
		return isset( $this->data[$key] );
329
	}
330
	
331
	/**
332
	 * Magic method for accessing discount properties.
333
	 *
334
	 * @since 1.0.14
335
	 * @access public
336
	 *
337
	 * @param string $key Discount data to retrieve
338
	 * @return mixed Value of the given discount property (if set).
339
	 */
340
	public function __get( $key ) {
341
		
342
		if ( $key == 'id' ) {
343
			$key = 'ID';
344
		}
345
		
346
		if( method_exists( $this, "get_$key") ) {
347
			$value 	= call_user_func( array( $this, "get_$key" ) );
348
		} else if( isset( $this->data[$key] ) ) {
349
			$value 	= $this->data[$key];
350
		} else {
351
			$value = null;
352
		}
353
		
354
		return apply_filters( "wpinv_get_discount_{$key}", $value, $this->ID, $this, $this->data['code'], $this->data );
355
356
	}
357
	
358
	/**
359
	 * Magic method for setting discount fields.
360
	 *
361
	 * This method does not update custom fields in the database.
362
	 *
363
	 * @since 1.0.14
364
	 * @access public
365
	 *
366
	 */
367
	public function __set( $key, $value ) {
368
		
369
		if ( 'id' == strtolower( $key ) ) {
370
			
371
			$this->ID = $value;
372
			$this->data['ID'] = $value;
373
			return;
374
			
375
		}
376
		
377
		$value = apply_filters( "wpinv_set_discount_{$key}", $value, $this->ID, $this, $this->code, $this->data );
378
		$this->data[$key] = $value;
379
		
380
	}
381
	
382
	/**
383
	 * Saves (or updates) a discount to the database
384
	 *
385
	 * @since 1.0.14
386
	 * @access public
387
	 * @return bool
388
	 *
389
	 */
390
	public function save(){
391
		
392
		$data = self::sanitize_discount_data( $this->data );
393
394
		// Should we create a new post?
395
		if(! $data[ 'ID' ] ) {
396
397
			$id = wp_insert_post( array(
398
				'post_status'           => $data['status'],
399
				'post_type'             => 'wpi_discount',
400
				'post_excerpt'          => $data['description'],
401
			) );
402
403
			if( empty( $id ) ) {
404
				return false;
405
			}
406
407
			$data[ 'ID' ] = (int) $id;
408
			$this->ID = $data[ 'ID' ];
409
			$this->data['ID'] = $data[ 'ID' ];
410
411
		} else {
412
			$this->update_status( $data['post_status'] );
413
		}
414
415
		$meta = apply_filters( 'wpinv_update_discount', $data, $this->ID, $this );
416
417
		do_action( 'wpinv_pre_update_discount', $meta, $this->ID, $this );
418
419
		foreach( wpinv_parse_list( 'ID date_created date_modified status description type_name' ) as $prop ) {
420
			unset( $meta[$prop] );
421
		}
422
423
		if( empty( $meta['uses'] ) ) {
424
			unset( $meta['uses'] );
425
		}
426
427
		// Save the metadata
428
		foreach( $meta as $key => $value ) {
429
			update_post_meta( $this->ID, "_wpi_discount_$key", $value );
430
		}
431
		
432
		// Empty the cache for this discount.
433
		wp_cache_delete( $this->ID, 'WPInv_Discounts' );
434
		wp_cache_delete( $data['code'], 'WPInv_Discount_Codes' );
435
436
		do_action( 'wpinv_post_update_discount', $meta, $this->ID, $this );
437
438
		$data = self::get_data_by( 'id', $this->ID );
439
		if( is_array( $data ) ) {
440
			$this->init( $data );
441
		} else {
442
			$this->init( array() );
443
		}
444
445
		return true;		
446
	}
447
448
	/**
449
	 * Saves (or updates) a discount to the database
450
	 *
451
	 * @since 1.0.14
452
	 * @access public
453
	 * @return bool
454
	 *
455
	 */
456
	public function update_status( $status = 'publish' ){
457
458
459
		if( $this->exists() && $this->old_status != $status ) {
460
461
			do_action( 'wpinv_pre_update_discount_status', $this->ID, $this->old_status, $status );
462
        	$updated = wp_update_post( array( 'ID' => $this->ID, 'post_status' => $status ) );
463
			do_action( 'wpinv_post_update_discount_status', $this->ID, $this->old_status, $status );
464
465
			wp_cache_delete( $this->ID, 'WPInv_Discounts' );
466
			wp_cache_delete( $this->code, 'WPInv_Discount_Codes' );
467
468
			return $updated !== 0;
469
			
470
		}
471
472
		return false;		
473
	}
474
	
475
	
476
	/**
477
	 * Checks whether a discount exists in the database or not
478
	 * 
479
	 * @since 1.0.14
480
	 */
481
	public function exists(){
482
		return ! empty( $this->ID );
483
	}
484
	
485
	// Boolean methods
486
	
487
	/**
488
	 * Checks the discount type.
489
	 * 
490
	 * 
491
	 * @param  string $type the discount type to check against
492
	 * @since 1.0.14
493
	 * @return bool
494
	 */
495
	public function is_type( $type ) {
496
		return $this->type == $type;
497
	}
498
	
499
	/**
500
	 * Checks whether the discount is published or not
501
	 * 
502
	 * @since 1.0.14
503
	 * @return bool
504
	 */
505
	public function is_active() {
506
		return $this->status == 'publish';
507
	}
508
	
509
	/**
510
	 * Checks whether the discount is has exided the usage limit or not
511
	 * 
512
	 * @since 1.0.14
513
	 * @return bool
514
	 */
515
	public function has_exceeded_limit() {
516
		if( empty( $this->max_uses ) || empty( $this->uses ) ) { 
517
			return false ;
518
		}
519
		
520
		$exceeded =  $this->uses >= $this->max_uses;
521
		return apply_filters( 'wpinv_is_discount_maxed_out', $exceeded, $this->ID, $this, $this->code );
522
	}
523
	
524
	/**
525
	 * Checks if the discount is expired
526
	 * 
527
	 * @since 1.0.14
528
	 * @return bool
529
	 */
530
	public function is_expired() {
531
		$expired = empty ( $this->expiration ) ? false : current_time( 'timestamp' ) > strtotime( $this->expiration );
532
		return apply_filters( 'wpinv_is_discount_expired', $expired, $this->ID, $this, $this->code );
533
	}
534
535
	/**
536
	 * Checks the discount start date.
537
	 * 
538
	 * @since 1.0.14
539
	 * @return bool
540
	 */
541
	public function has_started() {
542
		$started = empty ( $this->start ) ? true : current_time( 'timestamp' ) > strtotime( $this->start );
543
		return apply_filters( 'wpinv_is_discount_started', $started, $this->ID, $this, $this->code );		
544
	}
545
	
546
	/**
547
	 * Check if a discount is valid for a given item id.
548
	 *
549
	 * @param  int|array  $item_ids
550
	 * @since 1.0.14
551
	 * @return boolean
552
	 */
553
	public function is_valid_for_items( $item_ids ) {
554
		 
555
		$item_ids = wpinv_parse_list( $item_ids );
556
		$included = array_intersect( $item_ids, $this->items );
557
		$excluded = array_intersect( $item_ids, $this->excluded_items );
558
559
		if( ! empty( $this->excluded_items ) && ! empty( $excluded ) ) {
560
			return false;
561
		}
562
563
		if( ! empty( $this->items ) && empty( $included ) ) {
564
			return false;
565
		}
566
		return true;
567
	}
568
	
569
	/**
570
	 * Check if a discount is valid for the given amount
571
	 *
572
	 * @param  float  $amount The amount to check against
573
	 * @since 1.0.14
574
	 * @return boolean
575
	 */
576
	public function is_valid_for_amount( $amount ) {
577
578
		$amount = floatval( $amount );
579
580
		// check if it meets the minimum amount valid.
581
		if( $this->min_total > 0 && $amount < $this->min_total ) {
582
			return false;
583
		}
584
585
		// check if it meets the maximum amount valid.
586
		if( $this->max_total > 0 && $amount > $this->max_total ) {
587
			return false;
588
		}
589
590
		return true;
591
	}
592
593
	/**
594
	 * Checks if the minimum amount is met
595
	 *
596
	 * @param  float  $amount The amount to check against
597
	 * @since 1.0.14
598
	 * @return boolean
599
	 */
600
	public function is_minimum_amount_met( $amount ) {
601
		$amount = floatval( $amount );
602
		$min_met= ! ( $this->min_total > 0 && $amount < $this->min_total );
603
		return apply_filters( 'wpinv_is_discount_min_met', $min_met, $this->ID, $this, $this->code, $amount );
604
	}
605
606
	/**
607
	 * Checks if the maximum amount is met
608
	 *
609
	 * @param  float  $amount The amount to check against
610
	 * @since 1.0.14
611
	 * @return boolean
612
	 */
613
	public function is_maximum_amount_met( $amount ) {
614
		$amount = floatval( $amount );
615
		$max_met= ! ( $this->max_total > 0 && $amount > $this->max_total );
616
		return apply_filters( 'wpinv_is_discount_max_met', $max_met, $this->ID, $this, $this->code, $amount );
617
	}
618
619
	/**
620
	 * Check if a discount is valid for the given user
621
	 *
622
	 * @param  int|string  $user
623
	 * @since 1.0.14
624
	 * @return boolean
625
	 */
626
	public function is_valid_for_user( $user ) {
627
		global $wpi_checkout_id;
628
629
		if( empty( $user ) || empty( $this->is_single_use ) ) {
630
			return true;
631
		}
632
633
		$user_id = 0;
634
        if ( is_int( $user ) ) {
635
            $user_id = absint( $user );
636
        } else if ( is_email( $user ) && $user_data = get_user_by( 'email', $user ) ) {
637
            $user_id = $user_data->ID;
638
        } else if ( $user_data = get_user_by( 'login', $user ) ) {
639
            $user_id = $user_data->ID;
640
        } else if ( absint( $user ) > 0 ) {
641
            $user_id = absint( $user );
642
		}
643
644
		if( empty( $user_id ) ) {
645
			return true;
646
		}
647
		
648
		// Get all payments with matching user id
649
        $payments = wpinv_get_invoices( array( 'user' => $user_id, 'limit' => false ) ); 
650
		$code     = strtolower( $this->code );
651
652
		foreach ( $payments as $payment ) {
653
654
			// Don't count discount used for current invoice checkout.
655
			if ( !empty( $wpi_checkout_id ) && $wpi_checkout_id == $payment->ID ) {
656
				continue;
657
			}
658
			
659
			if ( $payment->has_status( array( 'wpi-cancelled', 'wpi-failed' ) ) ) {
660
				continue;
661
			}
662
663
			$discounts = $payment->get_discounts( true );
664
			if ( empty( $discounts ) ) {
665
				continue;
666
			}
667
668
			$discounts = array_map( 'strtolower', wpinv_parse_list( $discounts ) );
669
			if ( ! empty( $discounts ) && in_array( $code, $discounts ) ) {
670
				return false;
671
			}
672
		}
673
674
		return true;
675
	}
676
677
	/**
678
	 * Deletes the discount from the database
679
	 *
680
	 * @since 1.0.14
681
	 * @return boolean
682
	 */
683
	public function remove() {
684
685
		if( empty( $this->ID ) ) {
686
			return true;
687
		}
688
689
		do_action( 'wpinv_pre_delete_discount', $this->ID );
690
		wp_cache_delete( $this->ID, 'WPInv_Discounts' );
691
    	wp_delete_post( $this->ID, true );
692
		wp_cache_delete( $this->code, 'WPInv_Discount_Codes' );
693
    	do_action( 'wpinv_post_delete_discount', $this->ID );
694
695
		$this->ID = null;
696
		$this->data['id'] = null;
697
		return true;
698
	}
699
700
	/**
701
	 * Increases a discount's usage.
702
	 *
703
	 * @since 1.0.14
704
	 * @param int $by The number of usages to increas by.
705
	 * @return int
706
	 */
707
	public function increase_usage( $by = 1 ) {
708
709
		$this->uses = $this->uses + $by;
710
711
		if( $this->uses  < 0 ) {
712
			$this->uses = 0;
713
		}
714
715
		$this->save();
716
717
		if( $by > 0 ) {
718
			do_action( 'wpinv_discount_increase_use_count', $this->uses, $this->ID, $this->code, $by );
719
		} else {
720
			do_action( 'wpinv_discount_decrease_use_count', $this->uses, $this->ID, $this->code, absint( $by ) );
721
		}
722
		
723
		return $this->uses;
724
	}
725
726
	/**
727
	 * Retrieves discount data
728
	 *
729
	 * @since 1.0.14
730
	 * @return array
731
	 */
732
	public function get_data() {
733
		$return = array();
734
		foreach( array_keys( $this->data ) as $key ) {
735
			$return[ $key ] = $this->$key;
736
		}
737
		return $return;
738
	}
739
740
	/**
741
	 * Retrieves discount data as json
742
	 *
743
	 * @since 1.0.14
744
	 * @return string|false
745
	 */
746
	public function get_data_as_json() {
747
		return wp_json_encode( $this->get_data() );
748
	}
749
750
	/**
751
	 * Checks if a discount can only be used once per user.
752
	 *
753
	 * @since 1.0.14
754
	 * @return bool
755
	 */
756
	public function get_is_single_use() {
757
		return (bool) apply_filters( 'wpinv_is_discount_single_use', $this->data['is_single_use'], $this->ID, $this, $this->code );
758
	}
759
760
	/**
761
	 * Checks if a discount is recurring.
762
	 *
763
	 * @since 1.0.14
764
	 * @return bool
765
	 */
766
	public function get_is_recurring() {
767
		return (bool) apply_filters( 'wpinv_is_discount_recurring', $this->data['is_recurring'], $this->ID, $this->code, $this );
768
	}
769
770
	/**
771
	 * Returns a discount's included items.
772
	 *
773
	 * @since 1.0.14
774
	 * @return array
775
	 */
776
	public function get_items() {
777
		return wpinv_parse_list( apply_filters( 'wpinv_get_discount_item_reqs', $this->data['items'], $this->ID, $this, $this->code ) );
778
	}
779
780
	/**
781
	 * Returns a discount's discounted amount.
782
	 *
783
	 * @since 1.0.14
784
	 * @return float
785
	 */
786
	public function get_discounted_amount( $amount ) {
787
788
		if ( $this->type == 'flat' ) {
789
            $amount = $amount - $this->amount;
790
		} else {
791
            $amount = $amount - ( $amount * ( $this->amount / 100 ) );
792
		}
793
794
		if ( $amount < 0 ) {
795
			$amount = 0;
796
		}
797
798
		return apply_filters( 'wpinv_discounted_amount', $amount, $this->ID, $this, $this->code, $this->amount );
799
	}
800
	
801
}
802