Completed
Pull Request — master (#627)
by Devin
04:26
created

Give_Customer   C

Complexity

Total Complexity 77

Size/Duplication

Total Lines 644
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 91.15%

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 644
rs 5.2755
ccs 206
cts 226
cp 0.9115
wmc 77
lcom 1
cbo 1

16 Methods

Rating   Name   Duplication   Size   Complexity  
D __construct() 0 25 9
B setup_customer() 0 30 6
A __get() 0 13 2
B update() 0 24 3
C attach_payment() 0 49 7
A increase_purchase_count() 0 19 4
B decrease_purchase_count() 0 23 5
A increase_value() 0 14 2
A decrease_value() 0 18 3
A get_notes() 0 13 4
A get_notes_count() 0 8 1
B add_note() 0 31 4
A get_raw_notes() 0 7 1
C sanitize_columns() 0 53 11
C create() 0 42 8
C remove_payment() 0 50 7

How to fix   Complexity   

Complex Class

Complex classes like Give_Customer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Give_Customer, and based on these observations, apply Extract Interface, too.

1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 22 and the first side effect is on line 14.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/**
3
 * Customer (Donor) Object
4
 *
5
 * @package     Give
6
 * @subpackage  Classes/Customer
7
 * @copyright   Copyright (c) 2016, WordImpress
8
 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
9
 * @since       1.0
10
 */
11
12
// Exit if accessed directly
13
if ( ! defined( 'ABSPATH' ) ) {
14
	exit;
15
}
16
17
/**
18
 * Give_Customer Class
19
 *
20
 * @since 1.0
21
 */
22
class Give_Customer {
23
24
	/**
25
	 * The customer ID
26
	 *
27
	 * @since 1.0
28
	 */
29
	public $id = 0;
30
31
	/**
32
	 * The customer's purchase count
33
	 *
34
	 * @since 1.0
35
	 */
36
	public $purchase_count = 0;
37
38
	/**
39
	 * The customer's lifetime value
40
	 *
41
	 * @since 1.0
42
	 */
43
	public $purchase_value = 0;
44
45
	/**
46
	 * The customer's email
47
	 *
48
	 * @since 1.0
49
	 */
50
	public $email;
51
52
	/**
53
	 * The customer's name
54
	 *
55
	 * @since 1.0
56
	 */
57
	public $name;
58
59
	/**
60
	 * The customer's creation date
61
	 *
62
	 * @since 1.0
63
	 */
64
	public $date_created;
65
66
	/**
67
	 * The payment IDs associated with the customer
68
	 *
69
	 * @since  1.0
70
	 */
71
	public $payment_ids;
72
73
	/**
74
	 * The user ID associated with the customer
75
	 *
76
	 * @since  1.0
77
	 */
78
	public $user_id;
79
80
	/**
81
	 * Customer Notes
82
	 *
83
	 * @since  1.0
84
	 */
85
	public $notes;
86
87
	/**
88
	 * The Database Abstraction
89
	 *
90
	 * @since  1.0
91
	 */
92
	protected $db;
93
94
	/**
95
	 * Give_Customer constructor.
96
	 *
97
	 * @param bool $_id_or_email
98
	 * @param bool $by_user_id
99
	 */
100 52
	public function __construct( $_id_or_email = false, $by_user_id = false ) {
101
102 52
		$this->db = new Give_DB_Customers;
103
104 52
		if ( false === $_id_or_email || ( is_numeric( $_id_or_email ) && (int) $_id_or_email !== absint( $_id_or_email ) ) ) {
105
			return false;
106
		}
107
108 52
		$by_user_id = is_bool( $by_user_id ) ? $by_user_id : false;
109
110 52
		if ( is_numeric( $_id_or_email ) ) {
111 52
			$field = $by_user_id ? 'user_id' : 'id';
112 52
		} else {
113 52
			$field = 'email';
114
		}
115
116 52
		$customer = $this->db->get_customer_by( $field, $_id_or_email );
117
118 52
		if ( empty( $customer ) || ! is_object( $customer ) ) {
119 52
			return false;
120
		}
121
122 52
		$this->setup_customer( $customer );
123
124 52
	}
125
126
	/**
127
	 * Given the customer data, let's set the variables
128
	 *
129
	 * @since  1.0
130
	 *
131
	 * @param  object $customer The Customer Object
132
	 *
133
	 * @return bool             If the setup was successful or not
134
	 */
135 52
	private function setup_customer( $customer ) {
136
137 52
		if ( ! is_object( $customer ) ) {
138
			return false;
139
		}
140
141 52
		foreach ( $customer as $key => $value ) {
142
143
			switch ( $key ) {
144
145 52
				case 'notes':
146 52
					$this->$key = $this->get_notes();
147 52
					break;
148
149 52
				default:
150 52
					$this->$key = $value;
151 52
					break;
152
153 52
			}
154
155 52
		}
156
157
		// Customer ID and email are the only things that are necessary, make sure they exist
158 52
		if ( ! empty( $this->id ) && ! empty( $this->email ) ) {
159 52
			return true;
160
		}
161
162
		return false;
163
164
	}
165
166
	/**
167
	 * Magic __get function to dispatch a call to retrieve a private property
168
	 *
169
	 * @since 1.0
170
	 */
171 1
	public function __get( $key ) {
172
173 1
		if ( method_exists( $this, 'get_' . $key ) ) {
174
175
			return call_user_func( array( $this, 'get_' . $key ) );
176
177
		} else {
178
179 1
			return new WP_Error( 'give-customer-invalid-property', sprintf( __( 'Can\'t get property %s', 'give' ), $key ) );
180
181
		}
182
183
	}
184
185
	/**
186
	 * Creates a customer
187
	 *
188
	 * @since  1.0
189
	 *
190
	 * @param  array $data Array of attributes for a customer
191
	 *
192
	 * @return mixed        False if not a valid creation, Customer ID if user is found or valid creation
193
	 */
194 53
	public function create( $data = array() ) {
195
196 53
		if ( $this->id != 0 || empty( $data ) ) {
197
			return false;
198
		}
199
200
		$defaults = array(
201
			'payment_ids' => ''
202 53
		);
203
204 53
		$args = wp_parse_args( $data, $defaults );
205 53
		$args = $this->sanitize_columns( $args );
206
207 53
		if ( empty( $args['email'] ) || ! is_email( $args['email'] ) ) {
208 1
			return false;
209
		}
210
211 53
		if ( ! empty( $args['payment_ids'] ) && is_array( $args['payment_ids'] ) ) {
212
			$args['payment_ids'] = implode( ',', array_unique( array_values( $args['payment_ids'] ) ) );
213
		}
214
215 53
		do_action( 'give_customer_pre_create', $args );
216
217 53
		$created = false;
218
219
		// The DB class 'add' implies an update if the customer being asked to be created already exists
220 53
		if ( $this->db->add( $data ) ) {
221
222
			// We've successfully added/updated the customer, reset the class vars with the new data
223 53
			$customer = $this->db->get_customer_by( 'email', $args['email'] );
224
225
			// Setup the customer data with the values from DB
226 53
			$this->setup_customer( $customer );
227
228 53
			$created = $this->id;
229 53
		}
230
231 53
		do_action( 'give_customer_post_create', $created, $args );
232
233 53
		return $created;
234
235
	}
236
237
	/**
238
	 * Update a customer record
239
	 *
240
	 * @since  1.0
241
	 *
242
	 * @param  array $data Array of data attributes for a customer (checked via whitelist)
243
	 *
244
	 * @return bool         If the update was successful or not
245
	 */
246 53
	public function update( $data = array() ) {
247
248 53
		if ( empty( $data ) ) {
249 1
			return false;
250
		}
251
252 53
		$data = $this->sanitize_columns( $data );
253
254 53
		do_action( 'give_customer_pre_update', $this->id, $data );
255
256 53
		$updated = false;
257
258 53
		if ( $this->db->update( $this->id, $data ) ) {
259
260 53
			$customer = $this->db->get_customer_by( 'id', $this->id );
261 53
			$this->setup_customer( $customer );
262
263 53
			$updated = true;
264 53
		}
265
266 53
		do_action( 'give_customer_post_update', $updated, $this->id, $data );
267
268 53
		return $updated;
269
	}
270
271
272
	/**
273
	 * Attach payment to the customer then triggers increasing stats
274
	 *
275
	 * @since  1.0
276
	 *
277
	 * @param  int  $payment_id   The payment ID to attach to the customer
278
	 * @param  bool $update_stats For backwards compatibility, if we should increase the stats or not
279
	 *
280
	 * @return bool            If the attachment was successfuly
281
	 */
282 52
	public function attach_payment( $payment_id = 0, $update_stats = true ) {
283
284 52
		if ( empty( $payment_id ) ) {
285 1
			return false;
286
		}
287
288 52
		if ( empty( $this->payment_ids ) ) {
289
290 52
			$new_payment_ids = $payment_id;
291
292 52
		} else {
293
294 7
			$payment_ids = array_map( 'absint', explode( ',', $this->payment_ids ) );
295
296 7
			if ( in_array( $payment_id, $payment_ids ) ) {
297 1
				$update_stats = false;
298 1
			}
299
300 7
			$payment_ids[] = $payment_id;
301
302 7
			$new_payment_ids = implode( ',', array_unique( array_values( $payment_ids ) ) );
303
304
		}
305
306 52
		do_action( 'give_customer_pre_attach_payment', $payment_id, $this->id );
307
308 52
		$payment_added = $this->update( array( 'payment_ids' => $new_payment_ids ) );
309
310 52
		if ( $payment_added ) {
311
312 52
			$this->payment_ids = $new_payment_ids;
313
314
			// We added this payment successfully, increment the stats
315 52
			if ( $update_stats ) {
316 1
				$payment_amount = give_get_payment_amount( $payment_id );
317
318 1
				if ( ! empty( $payment_amount ) ) {
319
					$this->increase_value( $payment_amount );
320
				}
321
322 1
				$this->increase_purchase_count();
323 1
			}
324
325 52
		}
326
327 52
		do_action( 'give_customer_post_attach_payment', $payment_added, $payment_id, $this->id );
328
329 52
		return $payment_added;
330
	}
331
332
333
	/**
334
	 * Remove a payment from this customer, then triggers reducing stats
335
	 *
336
	 * @since  1.0
337
	 *
338
	 * @param  integer $payment_id   The Payment ID to remove
339
	 * @param  bool    $update_stats For backwards compatibility, if we should increase the stats or not
340
	 *
341
	 * @return boolean             If the removal was successful
342
	 */
343 3
	public function remove_payment( $payment_id = 0, $update_stats = true ) {
344
345 3
		if ( empty( $payment_id ) ) {
346
			return false;
347
		}
348
349 3
		$new_payment_ids = '';
350
351 3
		if ( ! empty( $this->payment_ids ) ) {
352
353 3
			$payment_ids = array_map( 'absint', explode( ',', $this->payment_ids ) );
354
355 3
			$pos = array_search( $payment_id, $payment_ids );
356 3
			if ( false === $pos ) {
357
				return false;
358
			}
359
360 3
			unset( $payment_ids[ $pos ] );
361 3
			$payment_ids = array_filter( $payment_ids );
362
363 3
			$new_payment_ids = implode( ',', array_unique( array_values( $payment_ids ) ) );
364
365 3
		}
366
367 3
		do_action( 'give_customer_pre_remove_payment', $payment_id, $this->id );
368
369 3
		$payment_removed = $this->update( array( 'payment_ids' => $new_payment_ids ) );
370
371 3
		if ( $payment_removed ) {
372
373 3
			$this->payment_ids = $new_payment_ids;
374
375 3
			if ( $update_stats ) {
376
				// We removed this payment successfully, decrement the stats
377 1
				$payment_amount = give_get_payment_amount( $payment_id );
378
379 1
				if ( ! empty( $payment_amount ) ) {
380 1
					$this->decrease_value( $payment_amount );
381 1
				}
382
383 1
				$this->decrease_purchase_count();
384 1
			}
385
386 3
		}
387
388 3
		do_action( 'give_customer_post_remove_payment', $payment_removed, $payment_id, $this->id );
389
390 3
		return $payment_removed;
391
392
	}
393
394
	/**
395
	 * Increase the purchase count of a customer
396
	 *
397
	 * @since  1.0
398
	 *
399
	 * @param  integer $count The number to increment by
400
	 *
401
	 * @return int            The purchase count
402
	 */
403 42
	public function increase_purchase_count( $count = 1 ) {
404
405
		// Make sure it's numeric and not negative
406 42
		if ( ! is_numeric( $count ) || $count != absint( $count ) ) {
407 1
			return false;
408
		}
409
410 42
		$new_total = (int) $this->purchase_count + (int) $count;
411
412 42
		do_action( 'give_customer_pre_increase_purchase_count', $count, $this->id );
413
414 42
		if ( $this->update( array( 'purchase_count' => $new_total ) ) ) {
415 41
			$this->purchase_count = $new_total;
416 41
		}
417
418 42
		do_action( 'give_customer_post_increase_purchase_count', $this->purchase_count, $count, $this->id );
419
420 42
		return $this->purchase_count;
421
	}
422
423
	/**
424
	 * Decrease the customer purchase count
425
	 *
426
	 * @since  1.0
427
	 *
428
	 * @param  integer $count The amount to decrease by
429
	 *
430
	 * @return mixed          If successful, the new count, otherwise false
431
	 */
432 7
	public function decrease_purchase_count( $count = 1 ) {
433
434
		// Make sure it's numeric and not negative
435 7
		if ( ! is_numeric( $count ) || $count != absint( $count ) ) {
436 1
			return false;
437
		}
438
439 7
		$new_total = (int) $this->purchase_count - (int) $count;
440
441 7
		if ( $new_total < 0 ) {
442 2
			$new_total = 0;
443 2
		}
444
445 7
		do_action( 'give_customer_pre_decrease_purchase_count', $count, $this->id );
446
447 7
		if ( $this->update( array( 'purchase_count' => $new_total ) ) ) {
448 7
			$this->purchase_count = $new_total;
449 7
		}
450
451 7
		do_action( 'give_customer_post_decrease_purchase_count', $this->purchase_count, $count, $this->id );
452
453 7
		return $this->purchase_count;
454
	}
455
456
	/**
457
	 * Increase the customer's lifetime value
458
	 *
459
	 * @since  1.0
460
	 *
461
	 * @param  float $value The value to increase by
462
	 *
463
	 * @return mixed         If successful, the new value, otherwise false
464
	 */
465 42
	public function increase_value( $value = 0.00 ) {
466
467 42
		$new_value = floatval( $this->purchase_value ) + $value;
468
469 42
		do_action( 'give_customer_pre_increase_value', $value, $this->id );
470
471 42
		if ( $this->update( array( 'purchase_value' => $new_value ) ) ) {
472 41
			$this->purchase_value = $new_value;
473 41
		}
474
475 42
		do_action( 'give_customer_post_increase_value', $this->purchase_value, $value, $this->id );
476
477 42
		return $this->purchase_value;
478
	}
479
480
	/**
481
	 * Decrease a customer's lifetime value
482
	 *
483
	 * @since  1.0
484
	 *
485
	 * @param  float $value The value to decrease by
486
	 *
487
	 * @return mixed         If successful, the new value, otherwise false
488
	 */
489 7
	public function decrease_value( $value = 0.00 ) {
490
491 7
		$new_value = floatval( $this->purchase_value ) - $value;
492
493 7
		if ( $new_value < 0 ) {
494 2
			$new_value = 0.00;
495 2
		}
496
497 7
		do_action( 'give_customer_pre_decrease_value', $value, $this->id );
498
499 7
		if ( $this->update( array( 'purchase_value' => $new_value ) ) ) {
500 7
			$this->purchase_value = $new_value;
501 7
		}
502
503 7
		do_action( 'give_customer_post_decrease_value', $this->purchase_value, $value, $this->id );
504
505 7
		return $this->purchase_value;
506
	}
507
508
	/**
509
	 * Get the parsed notes for a customer as an array
510
	 *
511
	 * @since  1.0
512
	 *
513
	 * @param  integer $length The number of notes to get
514
	 * @param  integer $paged  What note to start at
515
	 *
516
	 * @return array           The notes requsted
517
	 */
518 52
	public function get_notes( $length = 20, $paged = 1 ) {
519
520 52
		$length = is_numeric( $length ) ? $length : 20;
521 52
		$offset = is_numeric( $paged ) && $paged != 1 ? ( ( absint( $paged ) - 1 ) * $length ) : 0;
522
523 52
		$all_notes   = $this->get_raw_notes();
524 52
		$notes_array = array_reverse( array_filter( explode( "\n\n", $all_notes ) ) );
525
526 52
		$desired_notes = array_slice( $notes_array, $offset, $length );
527
528 52
		return $desired_notes;
529
530
	}
531
532
	/**
533
	 * Get the total number of notes we have after parsing
534
	 *
535
	 * @since  1.0
536
	 * @return int The number of notes for the customer
537
	 */
538 1
	public function get_notes_count() {
539
540 1
		$all_notes   = $this->get_raw_notes();
541 1
		$notes_array = array_reverse( array_filter( explode( "\n\n", $all_notes ) ) );
542
543 1
		return count( $notes_array );
544
545
	}
546
547
	/**
548
	 * Add a note for the customer
549
	 *
550
	 * @since  1.0
551
	 *
552
	 * @param string $note The note to add
553
	 *
554
	 * @return string|boolean The new note if added succesfully, false otherwise
555
	 */
556 1
	public function add_note( $note = '' ) {
557
558 1
		$note = trim( $note );
559 1
		if ( empty( $note ) ) {
560
			return false;
561
		}
562
563 1
		$notes = $this->get_raw_notes();
564
565 1
		if ( empty( $notes ) ) {
566 1
			$notes = '';
567 1
		}
568
569 1
		$note_string = date_i18n( 'F j, Y H:i:s', current_time( 'timestamp' ) ) . ' - ' . $note;
570 1
		$new_note    = apply_filters( 'give_customer_add_note_string', $note_string );
571 1
		$notes .= "\n\n" . $new_note;
572
573 1
		do_action( 'give_customer_pre_add_note', $new_note, $this->id );
574
575 1
		$updated = $this->update( array( 'notes' => $notes ) );
576
577 1
		if ( $updated ) {
578 1
			$this->notes = $this->get_notes();
579 1
		}
580
581 1
		do_action( 'give_customer_post_add_note', $this->notes, $new_note, $this->id );
582
583
		// Return the formatted note, so we can test, as well as update any displays
584 1
		return $new_note;
585
586
	}
587
588
	/**
589
	 * Get the notes column for the customer
590
	 *
591
	 * @since  1.0
592
	 * @return string The Notes for the customer, non-parsed
593
	 */
594 52
	private function get_raw_notes() {
595
596 52
		$all_notes = $this->db->get_column( 'notes', $this->id );
597
598 52
		return $all_notes;
599
600
	}
601
602
	/**
603
	 * Sanitize the data for update/create
604
	 *
605
	 * @since  1.0
606
	 *
607
	 * @param  array $data The data to sanitize
608
	 *
609
	 * @return array       The sanitized data, based off column defaults
610
	 */
611 52
	private function sanitize_columns( $data ) {
612
613 52
		$columns        = $this->db->get_columns();
614 52
		$default_values = $this->db->get_column_defaults();
615
616 52
		foreach ( $columns as $key => $type ) {
617
618
			// Only sanitize data that we were provided
619 52
			if ( ! array_key_exists( $key, $data ) ) {
620 52
				continue;
621
			}
622
623
			switch ( $type ) {
624
625 52
				case '%s':
626 52
					if ( 'email' == $key ) {
627 52
						$data[ $key ] = sanitize_email( $data[ $key ] );
628 52
					} elseif ( 'notes' == $key ) {
629 1
						$data[ $key ] = strip_tags( $data[ $key ] );
630 1
					} else {
631 52
						$data[ $key ] = sanitize_text_field( $data[ $key ] );
632
					}
633 52
					break;
634
635 52
				case '%d':
636 52
					if ( ! is_numeric( $data[ $key ] ) || (int) $data[ $key ] !== absint( $data[ $key ] ) ) {
637
						$data[ $key ] = $default_values[ $key ];
638
					} else {
639 52
						$data[ $key ] = absint( $data[ $key ] );
640
					}
641 52
					break;
642
643 42
				case '%f':
644
					// Convert what was given to a float
645 42
					$value = floatval( $data[ $key ] );
646
647 42
					if ( ! is_float( $value ) ) {
648
						$data[ $key ] = $default_values[ $key ];
649
					} else {
650 42
						$data[ $key ] = $value;
651
					}
652 42
					break;
653
654
				default:
655
					$data[ $key ] = sanitize_text_field( $data[ $key ] );
656
					break;
657
658
			}
659
660 52
		}
661
662 52
		return $data;
663
	}
664
665
}
666