Completed
Push — master ( 647547...13dcfc )
by Devin
17:56
created

Give_Customer   C

Complexity

Total Complexity 79

Size/Duplication

Total Lines 650
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 88.7%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 650
ccs 204
cts 230
cp 0.887
rs 5.2312
c 1
b 0
f 0
wmc 79
lcom 1
cbo 2

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
C create() 0 42 8
B remove_payment() 0 56 9
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

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
		$payment = new Give_Payment( $payment_id );
350
351 3
		if ( 'publish' !== $payment->status && 'revoked' !== $payment->status ) {
0 ignored issues
show
Documentation introduced by
The property $status is declared protected in Give_Payment. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
352 3
			$update_stats = false;
353 3
		}
354
355 3
		$new_payment_ids = '';
356
357 3
		if ( ! empty( $this->payment_ids ) ) {
358
359 3
			$payment_ids = array_map( 'absint', explode( ',', $this->payment_ids ) );
360
361 3
			$pos = array_search( $payment_id, $payment_ids );
362 3
			if ( false === $pos ) {
363
				return false;
364
			}
365
366 3
			unset( $payment_ids[ $pos ] );
367 3
			$payment_ids = array_filter( $payment_ids );
368
369 3
			$new_payment_ids = implode( ',', array_unique( array_values( $payment_ids ) ) );
370
371 3
		}
372
373 3
		do_action( 'give_customer_pre_remove_payment', $payment_id, $this->id );
374
375 3
		$payment_removed = $this->update( array( 'payment_ids' => $new_payment_ids ) );
376
377 3
		if ( $payment_removed ) {
378
379 3
			$this->payment_ids = $new_payment_ids;
380
381 3
			if ( $update_stats ) {
382
				// We removed this payment successfully, decrement the stats
383
				$payment_amount = give_get_payment_amount( $payment_id );
384
385
				if ( ! empty( $payment_amount ) ) {
386
					$this->decrease_value( $payment_amount );
387
				}
388
389
				$this->decrease_purchase_count();
390
			}
391
392 3
		}
393
394 3
		do_action( 'give_customer_post_remove_payment', $payment_removed, $payment_id, $this->id );
395
396 3
		return $payment_removed;
397
398
	}
399
400
	/**
401
	 * Increase the purchase count of a customer
402
	 *
403
	 * @since  1.0
404
	 *
405
	 * @param  integer $count The number to increment by
406
	 *
407
	 * @return int            The purchase count
408
	 */
409 42
	public function increase_purchase_count( $count = 1 ) {
410
411
		// Make sure it's numeric and not negative
412 42
		if ( ! is_numeric( $count ) || $count != absint( $count ) ) {
413 1
			return false;
414
		}
415
416 42
		$new_total = (int) $this->purchase_count + (int) $count;
417
418 42
		do_action( 'give_customer_pre_increase_purchase_count', $count, $this->id );
419
420 42
		if ( $this->update( array( 'purchase_count' => $new_total ) ) ) {
421 41
			$this->purchase_count = $new_total;
422 41
		}
423
424 42
		do_action( 'give_customer_post_increase_purchase_count', $this->purchase_count, $count, $this->id );
425
426 42
		return $this->purchase_count;
427
	}
428
429
	/**
430
	 * Decrease the customer purchase count
431
	 *
432
	 * @since  1.0
433
	 *
434
	 * @param  integer $count The amount to decrease by
435
	 *
436
	 * @return mixed          If successful, the new count, otherwise false
437
	 */
438 6
	public function decrease_purchase_count( $count = 1 ) {
439
440
		// Make sure it's numeric and not negative
441 6
		if ( ! is_numeric( $count ) || $count != absint( $count ) ) {
442 1
			return false;
443
		}
444
445 6
		$new_total = (int) $this->purchase_count - (int) $count;
446
447 6
		if ( $new_total < 0 ) {
448 1
			$new_total = 0;
449 1
		}
450
451 6
		do_action( 'give_customer_pre_decrease_purchase_count', $count, $this->id );
452
453 6
		if ( $this->update( array( 'purchase_count' => $new_total ) ) ) {
454 6
			$this->purchase_count = $new_total;
455 6
		}
456
457 6
		do_action( 'give_customer_post_decrease_purchase_count', $this->purchase_count, $count, $this->id );
458
459 6
		return $this->purchase_count;
460
	}
461
462
	/**
463
	 * Increase the customer's lifetime value
464
	 *
465
	 * @since  1.0
466
	 *
467
	 * @param  float $value The value to increase by
468
	 *
469
	 * @return mixed         If successful, the new value, otherwise false
470
	 */
471 42
	public function increase_value( $value = 0.00 ) {
472
473 42
		$new_value = floatval( $this->purchase_value ) + $value;
474
475 42
		do_action( 'give_customer_pre_increase_value', $value, $this->id );
476
477 42
		if ( $this->update( array( 'purchase_value' => $new_value ) ) ) {
478 41
			$this->purchase_value = $new_value;
479 41
		}
480
481 42
		do_action( 'give_customer_post_increase_value', $this->purchase_value, $value, $this->id );
482
483 42
		return $this->purchase_value;
484
	}
485
486
	/**
487
	 * Decrease a customer's lifetime value
488
	 *
489
	 * @since  1.0
490
	 *
491
	 * @param  float $value The value to decrease by
492
	 *
493
	 * @return mixed         If successful, the new value, otherwise false
494
	 */
495 7
	public function decrease_value( $value = 0.00 ) {
496
497 7
		$new_value = floatval( $this->purchase_value ) - $value;
498
499 7
		if ( $new_value < 0 ) {
500 2
			$new_value = 0.00;
501 2
		}
502
503 7
		do_action( 'give_customer_pre_decrease_value', $value, $this->id );
504
505 7
		if ( $this->update( array( 'purchase_value' => $new_value ) ) ) {
506 6
			$this->purchase_value = $new_value;
507 6
		}
508
509 7
		do_action( 'give_customer_post_decrease_value', $this->purchase_value, $value, $this->id );
510
511 7
		return $this->purchase_value;
512
	}
513
514
	/**
515
	 * Get the parsed notes for a customer as an array
516
	 *
517
	 * @since  1.0
518
	 *
519
	 * @param  integer $length The number of notes to get
520
	 * @param  integer $paged  What note to start at
521
	 *
522
	 * @return array           The notes requsted
523
	 */
524 52
	public function get_notes( $length = 20, $paged = 1 ) {
525
526 52
		$length = is_numeric( $length ) ? $length : 20;
527 52
		$offset = is_numeric( $paged ) && $paged != 1 ? ( ( absint( $paged ) - 1 ) * $length ) : 0;
528
529 52
		$all_notes   = $this->get_raw_notes();
530 52
		$notes_array = array_reverse( array_filter( explode( "\n\n", $all_notes ) ) );
531
532 52
		$desired_notes = array_slice( $notes_array, $offset, $length );
533
534 52
		return $desired_notes;
535
536
	}
537
538
	/**
539
	 * Get the total number of notes we have after parsing
540
	 *
541
	 * @since  1.0
542
	 * @return int The number of notes for the customer
543
	 */
544 1
	public function get_notes_count() {
545
546 1
		$all_notes   = $this->get_raw_notes();
547 1
		$notes_array = array_reverse( array_filter( explode( "\n\n", $all_notes ) ) );
548
549 1
		return count( $notes_array );
550
551
	}
552
553
	/**
554
	 * Add a note for the customer
555
	 *
556
	 * @since  1.0
557
	 *
558
	 * @param string $note The note to add
559
	 *
560
	 * @return string|boolean The new note if added succesfully, false otherwise
561
	 */
562 1
	public function add_note( $note = '' ) {
563
564 1
		$note = trim( $note );
565 1
		if ( empty( $note ) ) {
566
			return false;
567
		}
568
569 1
		$notes = $this->get_raw_notes();
570
571 1
		if ( empty( $notes ) ) {
572 1
			$notes = '';
573 1
		}
574
575 1
		$note_string = date_i18n( 'F j, Y H:i:s', current_time( 'timestamp' ) ) . ' - ' . $note;
576 1
		$new_note    = apply_filters( 'give_customer_add_note_string', $note_string );
577 1
		$notes .= "\n\n" . $new_note;
578
579 1
		do_action( 'give_customer_pre_add_note', $new_note, $this->id );
580
581 1
		$updated = $this->update( array( 'notes' => $notes ) );
582
583 1
		if ( $updated ) {
584 1
			$this->notes = $this->get_notes();
585 1
		}
586
587 1
		do_action( 'give_customer_post_add_note', $this->notes, $new_note, $this->id );
588
589
		// Return the formatted note, so we can test, as well as update any displays
590 1
		return $new_note;
591
592
	}
593
594
	/**
595
	 * Get the notes column for the customer
596
	 *
597
	 * @since  1.0
598
	 * @return string The Notes for the customer, non-parsed
599
	 */
600 52
	private function get_raw_notes() {
601
602 52
		$all_notes = $this->db->get_column( 'notes', $this->id );
603
604 52
		return $all_notes;
605
606
	}
607
608
	/**
609
	 * Sanitize the data for update/create
610
	 *
611
	 * @since  1.0
612
	 *
613
	 * @param  array $data The data to sanitize
614
	 *
615
	 * @return array       The sanitized data, based off column defaults
616
	 */
617 52
	private function sanitize_columns( $data ) {
618
619 52
		$columns        = $this->db->get_columns();
620 52
		$default_values = $this->db->get_column_defaults();
621
622 52
		foreach ( $columns as $key => $type ) {
623
624
			// Only sanitize data that we were provided
625 52
			if ( ! array_key_exists( $key, $data ) ) {
626 52
				continue;
627
			}
628
629
			switch ( $type ) {
630
631 52
				case '%s':
632 52
					if ( 'email' == $key ) {
633 52
						$data[ $key ] = sanitize_email( $data[ $key ] );
634 52
					} elseif ( 'notes' == $key ) {
635 1
						$data[ $key ] = strip_tags( $data[ $key ] );
636 1
					} else {
637 52
						$data[ $key ] = sanitize_text_field( $data[ $key ] );
638
					}
639 52
					break;
640
641 52
				case '%d':
642 52
					if ( ! is_numeric( $data[ $key ] ) || (int) $data[ $key ] !== absint( $data[ $key ] ) ) {
643
						$data[ $key ] = $default_values[ $key ];
644
					} else {
645 52
						$data[ $key ] = absint( $data[ $key ] );
646
					}
647 52
					break;
648
649 42
				case '%f':
650
					// Convert what was given to a float
651 42
					$value = floatval( $data[ $key ] );
652
653 42
					if ( ! is_float( $value ) ) {
654
						$data[ $key ] = $default_values[ $key ];
655
					} else {
656 42
						$data[ $key ] = $value;
657
					}
658 42
					break;
659
660
				default:
661
					$data[ $key ] = sanitize_text_field( $data[ $key ] );
662
					break;
663
664
			}
665
666 52
		}
667
668 52
		return $data;
669
	}
670
671
}
672