Completed
Push — master ( a85a3b...a9e055 )
by Justin
11:13
created

wpsc_coupons::get_total_quantity()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 9
rs 9.6666
cc 2
eloc 5
nc 2
nop 1
1
<?php
2
/**
3
 * The Coupons Class
4
 *
5
 * Holds the main coupon class amd other important coupon functions
6
 *
7
 * @package wp-e-commerce
8
 */
9
10
/**
11
* uses coupons function, no parameters
12
* @return boolean if true, all items in the cart do use shipping
13
*/
14
function wpsc_uses_coupons() {
15
	global $wpsc_coupons;
16
17
	if ( empty( $wpsc_coupons ) ) {
18
		$wpsc_coupons = new wpsc_coupons();
19
	}
20
21
	if ( is_object( $wpsc_coupons ) ) {
22
		return $wpsc_coupons->uses_coupons();
23
	}
24
25
	return false;
26
}
27
28
function wpsc_coupons_error(){
29
	global $wpsc_coupons;
30
31
	if ( isset( $wpsc_coupons->errormsg ) && $wpsc_coupons->errormsg == true ) {
0 ignored issues
show
Coding Style introduced by
The if-else statement can be simplified to return isset($wpsc_coupo...pons->errormsg == true;.
Loading history...
32
		return true;
33
	} else {
34
		return false;
35
	}
36
}
37
/**
38
 * Coupons class.
39
 *
40
 * @todo  Cleanup early in 4.0 / PHP5
41
 * @package wp-e-commerce
42
 * @since 3.7
43
 */
44
class wpsc_coupons {
0 ignored issues
show
Coding Style introduced by
Class name "wpsc_coupons" is not in camel caps format
Loading history...
45
46
	public $coupon;
47
48
	public $code;
49
	public $value;
50
	public $is_percentage;
51
	public $conditions;
52
	public $start_date;
53
	public $active;
54
	public $every_product ;
55
	public $end_date;
56
	public $use_once;
57
	public $is_used;
58
59
	public $discount;
60
61
	//for error message
62
	public $errormsg;
63
64
	/**
65
	 * Coupons constructor
66
	 *
67
	 * Instantiate a coupons object with optional variable $code;
68
	 *
69
	 * @param string code (optional) the coupon code you would like to use.
70
	 * @return bool True if coupon code exists, False otherwise.
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
71
	 */
72
	public function __construct( $code = '' ) {
73
	    global $wpdb;
74
75
		if ( empty( $code ) ) {
76
			return false;
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
77
		}
78
79
		$this->code = $code;
80
81
		$coupon_data = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM `".WPSC_TABLE_COUPON_CODES."` WHERE coupon_code = %s LIMIT 1", $code ) , ARRAY_A );
82
83
		if ( empty( $coupon_data ) ) {
84
			$this->errormsg = true;
85
			wpsc_delete_customer_meta( 'coupon' );
86
			return false;
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
87
		} else {
88
89
			$this->coupon = new WPSC_Coupon( $coupon_data['id'] );
90
91
			// Store these values for back-compatibiilty pre 4.0?
92
			$this->value         = $this->coupon->get( 'value' );
93
			$this->is_percentage = $this->coupon->get( 'is-percentage' );
94
			$this->conditions    = $this->coupon->get( 'condition' );
95
			$this->is_used       = $this->coupon->get( 'is-used' );
96
			$this->active        = $this->coupon->get( 'active' );
97
			$this->use_once      = $this->coupon->get( 'use-once' );
98
			$this->start_date    = $this->coupon->get( 'start' );
99
			$this->end_date      = $this->coupon->get( 'expiry' );
100
			$this->every_product = $this->coupon->get( 'every_product' );
101
			$this->errormsg      = false;
102
103
			return $this->validate_coupon();
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
104
105
		}
106
107
	}
108
109
	private function has_coupon() {
110
111
		return isset( $this->coupon );
112
113
	}
114
115
	/**
116
	 * Coupons validator
117
	 *
118
	 * Checks if the current coupon is valid to use (Expiry date, Active, Used).
119
	 *
120
	 * @return bool True if coupon is not expired, used and still active, False otherwise.
121
	 */
122
	public function validate_coupon() {
123
124
		$valid = $this->has_coupon() ? $this->coupon->is_valid() : false;
125
126
		return apply_filters( 'wpsc_coupons_validate_coupon', $valid, $this );
127
128
	}
129
130
	/**
131
	 * Check whether the coupon has conditions
132
	 *
133
	 * @since  3.8.9
134
	 * @return boolean True if there are conditions
135
	 */
136
	public function has_conditions() {
137
138
		return $this->has_coupon() ? $this->coupon->has_conditions() : false;
139
140
	}
141
142
	/**
143
	 * Check if item's name matches condition
144
	 *
145
	 * @since  3.8.9
146
	 * @access private
147
	 * @param  array  $condition Condition arguments
148
	 * @param  object $cart_item Cart item
149
	 * @return boolean
150
	 */
151
	public function _callback_condition_item_name( $condition, $cart_item ) {
152
		$product_data = get_post( $cart_item->product_id );
153
154
		switch( $condition['logic'] ) {
155
156
			case 'equal': // Checks if the product name is exactly the same as the condition value
157
				return $product_data->post_title == $condition['value'];
158
159
			case 'greater': // Checks if the product name is not the same as the condition value
160
				return $product_data->post_title > $condition['value'];
161
162
			case 'less': // Checks if the product name is not the same as the condition value
163
				return $product_data->post_title < $condition['value'];
164
165
			case 'contains': // Checks if the product name contains the condition value
166
				return preg_match( "/(.*)" . preg_quote( $condition['value'], '/' ) . "(.*)/", $product_data->post_title );
0 ignored issues
show
Bug Best Practice introduced by
The return type of return preg_match('/(.*)...duct_data->post_title); (integer) is incompatible with the return type documented by wpsc_coupons::_callback_condition_item_name of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
167
168
			case 'category': // Checks if the product category is the condition value
169
				$id = $product_data->ID;
170
				if ( $product_data->post_parent )
171
					$id = $product_data->post_parent;
172
173
				$category_condition = $condition['value'];
174
				if ( false !== strpos( $category_condition, ',' ) ) {
175
					$category_condition = explode( ',', $condition['value'] );
176
					$category_condition = array_map( 'trim', $category_condition );
177
				}
178
				return has_term( $category_condition, 'wpsc_product_category', $id );
179
180
			case 'not_contain': // Checks if the product name contains the condition value
181
				return ! preg_match( "/(.*)" . preg_quote( $condition['value'], '/' ) . "(.*)/", $product_data->post_title );
182
183
			case 'begins': // Checks if the product name begins with condition value
184
				return preg_match( "/^" . preg_quote( $condition['value'], '/' ) . "/", $product_data->post_title );
0 ignored issues
show
Bug Best Practice introduced by
The return type of return preg_match('/^' ....duct_data->post_title); (integer) is incompatible with the return type documented by wpsc_coupons::_callback_condition_item_name of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
185
186
			case 'ends': // Checks if the product name ends with condition value
187
				return preg_match( "/" . preg_quote( $condition['value'], '/' ) . "$/", $product_data->post_title );
0 ignored issues
show
Bug Best Practice introduced by
The return type of return preg_match('/' . ...duct_data->post_title); (integer) is incompatible with the return type documented by wpsc_coupons::_callback_condition_item_name of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
188
189
		}
190
191
		return false;
192
	}
193
194
	/**
195
	 * Check whether item quantity matches condition
196
	 *
197
	 * @since  3.8.9
198
	 * @access private
199
	 * @param  array  $condition Condition arguments
200
	 * @param  object $cart_item Cart item
201
	 * @return boolean
202
	 */
203
	public function _callback_condition_item_quantity( $condition, $cart_item ) {
204
		$value = (int) $condition['value'];
205
206
		switch( $condition['logic'] ) {
207
			case 'equal': //Checks if the quantity of a product in the cart equals condition value
208
				return $cart_item->quantity == $value;
209
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
210
211
			case 'greater'://Checks if the quantity of a product is greater than the condition value
212
				return $cart_item->quantity > $value;
213
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
214
215
			case 'less'://Checks if the quantity of a product is less than the condition value
216
				return $cart_item->quantity < $value;
217
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
218
219
			case 'contains'://Checks if the product name contains the condition value
220
				return preg_match( "/(.*)" . $value . "(.*)/", $cart_item->quantity );
221
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
222
223
			case 'not_contain'://Checks if the product name contains the condition value
224
				return ! preg_match( "/(.*)" . $value . "(.*)/",$cart_item->quantity );
225
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
226
227
			case 'begins'://Checks if the product name begins with condition value
228
				return preg_match( "/^" . $value ."/", $cart_item->quantity );
229
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
230
231
			case 'ends'://Checks if the product name ends with condition value
232
				return preg_match( "/" . $value . "$/",$cart_item->quantity );
233
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
234
  		}
235
236
		return false;
237
	}
238
239
	/**
240
	 * Check whether total quantity matches condition
241
	 *
242
	 * @since  3.8.9
243
	 * @access private
244
	 * @param  array  $condition Condition arguments
245
	 * @param  object $cart_item Cart item
246
	 * @return boolean
247
	 */
248
	public function _callback_condition_total_quantity( $condition, $cart_item ) {
0 ignored issues
show
Unused Code introduced by
The parameter $cart_item is not used and could be removed.

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

Loading history...
249
250
		$total_quantity = wpsc_cart_item_count();
251
		$value          = (int) $condition['value'];
252
253
		switch( $condition['logic'] ) {
254
			case 'equal'://Checks if the quantity of products in the cart equals condition value
255
				return $total_quantity == $value;
256
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
257
258
			case 'greater'://Checks if the quantity in the cart is greater than the condition value
259
				return $total_quantity > $value;
260
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
261
262
			case 'less'://Checks if the quantity in the cart is less than the condition value
263
				return $total_quantity < $value;
264
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
265
		}
266
267
		return false;
268
	}
269
270
	/**
271
	 * Checks whether subtotal matches condition
272
	 *
273
	 * @since  3.8.9
274
	 * @access private
275
	 * @param  array  $condition Condition arguments
276
	 * @param  object $cart_item Cart item
277
	 * @return
278
	 */
279
	public function _callback_condition_subtotal_amount( $condition, $cart_item ) {
0 ignored issues
show
Unused Code introduced by
The parameter $cart_item is not used and could be removed.

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

Loading history...
280
		global $wpsc_cart;
281
		$subtotal = $wpsc_cart->calculate_subtotal();
282
		$value = (float) $condition['value'];
283
284
		switch( $condition['logic'] ) {
285
			case 'equal'://Checks if the subtotal of products in the cart equals condition value
286
				return $subtotal == $value;
287
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
288
289
			case 'greater'://Checks if the subtotal of the cart is greater than the condition value
290
				return $subtotal > $value;
291
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
292
293
			case 'less'://Checks if the subtotal of the cart is less than the condition value
294
				return $subtotal < $value;
295
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
296
		}
297
298
		return false;
299
	}
300
301
	/**
302
	 * Filter out cart items that don't match coupon conditions
303
	 *
304
	 * @since  3.8.9
305
	 * @access private
306
	 * @param  object $cart_item Cart item
307
	 * @return bool
308
	 */
309
	public function _filter_cart_item_conditions( $cart_item ) {
310
		global $wpsc_cart;
311
312
		$compare_logic = false;
313
314
		$conditions = $this->has_coupon() ? $this->coupon->get( 'condition' ) : array();
315
316
		foreach ( $conditions as $condition ) {
317
318
			$callback = '_callback_condition_' . $condition['property'];
319
320
			if ( is_callable( array( $this, $callback ) ) ) {
321
322
				$result = $this->$callback( $condition, $cart_item );
323
324
			} else {
325
326
				/* This allows for a function outside of this class to override a custom condition. */
327
				if ( function_exists( $callback ) ) {
328
					$result = $callback( $condition, $cart_item );
329
				} else {
330
					/* This allows for a plugin to create a condition callback for the condition. Perk: doesn't have to follow $callback nomenclature. */
331
					$result = apply_filters( 'wpsc_coupon_conditions_default_callback', false, $callback, $condition, $cart_item );
332
				}
0 ignored issues
show
introduced by
Blank line found after control structure
Loading history...
333
334
			}
335
336
			if ( ! $result ) {
337
				switch ( $condition['operator'] ) {
338
					case 'or':
339
						$compare_logic = $compare_logic || apply_filters( 'wpsc_coupon_compare_logic', false, $condition, $cart_item );
340
					break;
341
					case 'and':
342
						$compare_logic = $compare_logic && apply_filters( 'wpsc_coupon_compare_logic', false, $condition, $cart_item );
343
					break;
344
					default:
345
						$compare_logic = apply_filters( 'wpsc_coupon_compare_logic', false, $condition, $cart_item );
346
				}
347
			} else {
348
				switch ( $condition['operator'] ) {
349
					case 'or':
350
						$compare_logic = $compare_logic || $result;
351
					break;
352
					case 'and':
353
						$compare_logic = $compare_logic && $result;
354
					break;
355
					default:
356
						$compare_logic = $result;
357
				}
358
			}
359
		}
360
361
		return $compare_logic;
362
	}
363
364
	/**
365
	 * Get cart items that match coupon conditions
366
	 *
367
	 * @since  3.8.9
368
	 * @access private
369
	 * @return array Array containing eligible cart items
370
	 */
371
	public function get_eligible_items() {
372
		global $wpsc_cart;
373
374
		$conditions = $this->has_coupon() ? $this->coupon->get( 'condition' ) : array();
375
376
		// cache product objects if we have a "item name" condition
377
		if ( in_array( 'item_name', $conditions ) ) {
378
			$ids = $wpsc_cart->get_items( array( 'fields' => 'product_id' ) );
379
			get_posts( array(
380
				'post_type'   => 'wpsc-product',
381
				'numberposts' => -1,
0 ignored issues
show
introduced by
Disabling pagination is prohibited in VIP context, do not set numberposts to -1 ever.
Loading history...
382
				'post__in'    => array( $ids ),
383
			) );
384
		}
385
386
		// sort the items by total price so that we can use this in $this->calculate_discount_conditions()
387
		$orderby = apply_filters( 'wpsc_coupon_select_item_orderby', 'unit_price' );
388
		$order   = apply_filters( 'wpsc_coupon_select_item_order'  , 'ASC'        );
389
		$cart_items = $wpsc_cart->get_items( array( 'orderby' => $orderby, 'order' => $order ) );
390
391
		$cart_items = array_filter( $cart_items, array( $this, '_filter_cart_item_conditions' ) );
392
393
		return $cart_items;
394
	}
395
396
	/**
397
	 * Calculate the subtotal of the items passed in as argument
398
	 *
399
	 * @since  3.8.9
400
	 * @access private
401
	 * @param  array $items Array of items
402
	 * @return float        Subtotal
403
	 */
404
	private function calculate_subtotal( $items ) {
405
		$total = 0;
406
407
		foreach ( $items as $item ) {
408
			$total += $item->total_price;
409
		}
410
411
		return $total;
412
	}
413
414
	/**
415
	 * Get the total quantity of the items passed in as argument
416
	 *
417
	 * @since  3.8.9
418
	 * @access private
419
	 * @param  array $items Array of items
420
	 * @return float        Subtotal
421
	 */
422
	private function get_total_quantity( $items ) {
423
		$total = 0;
424
425
		foreach ( $items as $item ) {
426
			$total += $item->quantity;
427
		}
428
429
		return $total;
430
	}
431
432
	/**
433
	 * Calculate the discount amount, taking coupon conditions into consideration
434
	 *
435
	 * @since  3.8.9
436
	 * @access private
437
	 * @return float Discount amount
438
	 */
439
	private function calculate_discount_conditions() {
440
		global $wpsc_cart;
441
442
		if ( ! $this->has_coupon() ) {
443
			return 0;
444
		}
445
446
		// findout whether the cart meet the conditions
447
		$items = $this->get_eligible_items();
448
449
		if ( empty( $items ) )
450
			return 0;
451
452
		// if this is free shipping, return the total shipping regardless of whether "Apply on all
453
		// products" is checked or not
454
		if ( $this->coupon->is_free_shipping() ) {
455
			return $this->calculate_free_shipping();
456
		}
457
458
		// if  "Apply on all products" is checked, discount amount should be based on the total values
459
		// of eligible cart items
460
		if ( $this->coupon->applies_to_all_items() ) {
461
			if ( $this->coupon->is_percentage() ) {
462
				$subtotal = $this->calculate_subtotal( $items );
463
				$discount = $this->coupon->get_percentage_discount( $subtotal );
464
  			} else {
465
				$quantity = $this->get_total_quantity( $items );
466
				$discount = $this->coupon->get_fixed_discount( $quantity );
467
			}
468
			return $discount;
469
		}
470
471
		// if "Apply on all products" is not checked and the coupon is percentage, the discount
472
		// amount should be based on the eligible cart item with lowest unit price
473
		if ( $this->coupon->is_percentage() ) {
474
475
			$field = apply_filters( 'wpsc_coupon_select_item_field', 'unit_price' );
476
			$item = array_shift( $items );
477
478
			return $this->coupon->get_percentage_discount( $item->$field );
479
480
		}
481
482
		// if "Apply on all products" is not checked and the coupon is a fixed value
483
		// return the discount value
484
		return $this->coupon->get( 'value' );
485
	}
486
487
	/**
488
	 * Calculate discount amount without taking conditions into consideration
489
	 *
490
	 * @since  3.8.9
491
	 * @access private
492
	 * @return float Discount amount
493
	 */
494
	private function calculate_discount_without_conditions() {
495
		global $wpsc_cart;
496
497
		if ( ! $this->has_coupon() ) {
498
			return 0;
499
		}
500
501
		// if this is free shipping, return the total shipping regardless of whether "Apply on all
502
		// products" is checked or not
503
		if ( $this->coupon->is_free_shipping() ) {
504
			return $this->calculate_free_shipping();
505
		}
506
507
		// if  "Apply on all products" is checked, discount amount should be based on the overall
508
		// cart
509
		if ( $this->coupon->applies_to_all_items() ) {
510
			if ( $this->coupon->is_percentage() ) {
511
				$subtotal = $wpsc_cart->calculate_subtotal();
512
				$discount = $this->coupon->get_percentage_discount( $subtotal );
513
			} else {
514
				$discount = $this->coupon->get_fixed_discount( wpsc_cart_item_count() );
515
  			}
516
			return $discount;
517
		}
518
519
		// if "Apply on all products" is not checked and the coupon is percentage, the discount
520
		// amount should be based on the cart item with lowest unit_price
521
		if ( $this->coupon->is_percentage() ) {
522
			$orderby = apply_filters( 'wpsc_coupon_select_item_orderby', 'unit_price' );
523
			$order   = apply_filters( 'wpsc_coupon_select_item_order'  , 'ASC'        );
524
			$field   = apply_filters( 'wpsc_coupon_select_item_field'  , 'unit_price' );
525
			$cart_items = $wpsc_cart->get_items( array( 'fields' => $field, 'orderby' => $orderby, 'order' => $order ) );
526
			if ( empty( $cart_items ) )
527
				return 0;
528
529
			$item = array_shift( $cart_items );
530
531
			return $this->coupon->get_percentage_discount( $item );
532
533
		}
534
535
		// if "Apply on all products" is not checked and the coupon is a fixed value
536
		// return the discount value
537
		return $this->coupon->get( 'value' );
538
	}
539
540
	/**
541
	 * Check whether this coupon is a "Free shipping" coupon
542
	 *
543
	 * @since  3.8.9
544
	 * @return boolean
545
	 */
546
	public function is_free_shipping() {
547
548
		return $this->has_coupon() ? $this->coupon->is_free_shipping() : false;
549
550
	}
551
552
	/**
553
	 * Check whether this coupon is a "percentage" coupon
554
	 *
555
	 * @since  3.8.9
556
	 * @return boolean
557
	 */
558
	public function is_percentage() {
559
560
		return $this->has_coupon() ? $this->coupon->is_percentage() : false;
561
562
	}
563
564
	/**
565
	 * Check whether this coupon is a fixed amount coupon
566
	 *
567
	 * @since  3.8.9
568
	 * @return boolean
569
	 */
570
	public function is_fixed_amount() {
571
572
		return $this->has_coupon() ? $this->coupon->is_fixed_amount() : false;
573
574
	}
575
576
	/**
577
	 * Check whether this coupon can be applied to all items
578
	 *
579
	 * @since  3.8.9
580
	 * @return boolean
581
	 */
582
	public function applies_to_all_items() {
583
584
		return $this->has_coupon() ? $this->coupon->applies_to_all_items() : false;
585
586
	}
587
588
	/**
589
	 * Calculate the free shipping discount amount
590
	 * @return float
591
	 */
592
	private function calculate_free_shipping() {
593
		global $wpsc_cart;
594
		return $wpsc_cart->calculate_total_shipping();
595
	}
596
597
598
	/**
599
	 * Calculate the discount amount
600
	 *
601
	 * @since  3.8.9
602
	 * @return float
603
	 */
604
	public function calculate_discount() {
605
		global $wpsc_cart;
606
607
		$wpsc_cart->clear_cache();
608
609
		if ( $this->has_conditions() ) {
610
			return $this->calculate_discount_conditions();
611
		} else {
612
			return $this->calculate_discount_without_conditions();
613
		}
614
615
  	}
616
617
	/**
618
	 * Comparing logic with the product information
619
	 *
620
	 * Checks if the product matches the logic
621
	 *
622
	 * @todo  Is this ever even used?
623
	 *
624
	 * @return bool True if all conditions are matched, False otherwise.
625
	 */
626
	function compare_logic( $condition, $product ) {
627
		global $wpdb;
628
629
		if ( 'item_name' == $condition['property'] ) {
630
			$product_data = $wpdb->get_results( "SELECT * FROM " . $wpdb->posts . " WHERE id='{$product->product_id}'" );
631
			$product_data = $product_data[0];
632
633
			switch( $condition['logic'] ) {
634
				case 'equal': //Checks if the product name is exactly the same as the condition value
635
					if ( $product_data->post_title == $condition['value'] )
636
						return true;
637
				break;
638
639
				case 'greater'://Checks if the product name is not the same as the condition value
640
					if ( $product_data->post_title > $condition['value'] )
641
						return true;
642
				break;
643
644
				case 'less'://Checks if the product name is not the same as the condition value
645
					if ( $product_data->post_title < $condition['value'] )
646
						return true;
647
				break;
648
649
				case 'contains'://Checks if the product name contains the condition value
650
					preg_match( "/(.*)" . preg_quote( $condition['value'], '/' ) . "(.*)/", $product_data->post_title, $match );
651
652
					if ( ! empty( $match ) )
653
						return true;
654
				break;
655
656
				case 'category'://Checks if the product category is the condition value
657
					if ( $product_data->post_parent ) {
658
						$categories = wpsc_get_product_terms( $product_data->post_parent, 'wpsc_product_category' );
659
					} else {
660
						$categories = wpsc_get_product_terms( $product_data->ID, 'wpsc_product_category' );
661
					}
662
					foreach ( $categories as $cat ) {
663
						if ( strtolower( $cat->name ) == strtolower( $condition['value'] ) )
664
							return true;
665
					}
666
				break;
667
668
				case 'not_contain'://Checks if the product name contains the condition value
669
					preg_match( "/(.*)" . preg_quote( $condition['value'], '/' ) . "(.*)/", $product_data->post_title, $match );
670
671
					if ( empty( $match ) )
672
						return true;
673
				break;
674
675
				case 'begins'://Checks if the product name begins with condition value
676
					preg_match( "/^" . preg_quote( $condition['value'], '/' ) . "/", $product_data->post_title, $match );
677
					if ( ! empty( $match ) )
678
						return true;
679
				break;
680
681
				case 'ends'://Checks if the product name ends with condition value
682
					preg_match( "/" . preg_quote( $condition['value'], '/' ) . "$/", $product_data->post_title, $match );
683
					if ( ! empty( $match ) )
684
						return true;
685
				break;
686
687
				default:
688
				return false;
689
			}
690
		} else if ( 'item_quantity' == $condition['property'] ) {
691
692
			switch( $condition['logic'] ) {
693
				case 'equal'://Checks if the quantity of a product in the cart equals condition value
694
					if ( $product->quantity == (int) $condition['value'] )
695
						return true;
696
				break;
697
698
				case 'greater'://Checks if the quantity of a product is greater than the condition value
699
					if ( $product->quantity > $condition['value'] )
700
						return true;
701
				break;
702
703
				case 'less'://Checks if the quantity of a product is less than the condition value
704
					if ( $product->quantity < $condition['value'] )
705
						return true;
706
				break;
707
708
				case 'contains'://Checks if the product name contains the condition value
709
					preg_match( "/(.*)" . $condition['value'] . "(.*)/", $product->quantity, $match );
710
					if ( ! empty( $match ) )
711
						return true;
712
				break;
713
714
				case 'not_contain'://Checks if the product name contains the condition value
715
					preg_match("/(.*)".$condition['value']."(.*)/",$product->quantity, $match );
716
					if ( empty( $match ) )
717
						return true;
718
				break;
719
720
				case 'begins'://Checks if the product name begins with condition value
721
					preg_match("/^".$condition['value']."/", $product->quantity, $match );
722
					if ( ! empty( $match ) )
723
						return true;
724
				break;
725
726
				case 'ends'://Checks if the product name ends with condition value
727
					preg_match( "/" . $condition['value'] . "$/", $product->quantity, $match );
728
					if ( ! empty( $match ) )
729
						return true;
730
					break;
731
				default:
732
					return false;
733
			}
734
		} else if ($condition['property'] == 'total_quantity') {
735
			$total_quantity = wpsc_cart_item_count();
736
			switch($condition['logic']) {
737
				case 'equal'://Checks if the quantity of products in the cart equals condition value
738
				if ($total_quantity == $condition['value'])
739
					return true;
740
				break;
741
742
				case 'greater'://Checks if the quantity in the cart is greater than the condition value
743
				if ($total_quantity > $condition['value'])
744
					return true;
745
				break;
746
747
				case 'less'://Checks if the quantity in the cart is less than the condition value
748
				if ($total_quantity < $condition['value'])
749
					return true;
750
				break;
751
752
				default:
753
				return false;
754
			}
0 ignored issues
show
introduced by
Blank line found after control structure
Loading history...
755
756
		} else if ( $condition['property'] == 'subtotal_amount' ) {
757
			$subtotal = wpsc_cart_total(false);
758
			switch($condition['logic']) {
759
				case 'equal'://Checks if the subtotal of products in the cart equals condition value
760
				if ($subtotal == $condition['value'])
761
					return true;
762
				break;
763
764
				case 'greater'://Checks if the subtotal of the cart is greater than the condition value
765
				if ($subtotal > $condition['value'])
766
					return true;
767
				break;
768
769
				case 'less'://Checks if the subtotal of the cart is less than the condition value
770
				if ($subtotal < $condition['value']){
0 ignored issues
show
Coding Style introduced by
The if-else statement can be simplified to return $subtotal < $condition['value'];.
Loading history...
771
					return true;
772
				}else{
773
					return false;
774
				}
775
776
				break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
777
778
				default:
779
				return false;
780
			}
781
		} else {
782
			return apply_filters( 'wpsc_coupon_compare_logic', false, $condition, $product );
783
		}
784
	}
785
786
	/**
787
	* uses coupons function, no parameters
788
	* @return boolean if true, items in the cart do use coupons
789
	*/
790
	function uses_coupons() {
791
		global $wpdb;
792
793
		$num_active_coupons = $wpdb->get_var("SELECT COUNT(id) as c FROM `".WPSC_TABLE_COUPON_CODES."` WHERE active='1'");
794
795
		return apply_filters( 'wpsc_uses_coupons', ( $num_active_coupons > 0 ) );
796
	}
797
}