Completed
Pull Request — master (#1832)
by Devin
04:50
created

Give_Donor   D

Complexity

Total Complexity 102

Size/Duplication

Total Lines 1083
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
dl 0
loc 1083
rs 4.4156
c 0
b 0
f 0
wmc 102
lcom 1
cbo 4

24 Methods

Rating   Name   Duplication   Size   Complexity  
D __construct() 0 28 9
B setup_donor() 0 33 6
A __get() 0 14 2
B create() 0 57 8
B update() 0 41 3
B attach_payment() 0 65 7
C remove_payment() 0 72 9
B increase_purchase_count() 0 36 4
B decrease_donation_count() 0 40 5
B increase_value() 0 31 2
B decrease_value() 0 35 3
A update_donation_value() 0 23 3
A get_notes() 0 13 4
A get_notes_count() 0 8 1
B add_note() 0 48 4
A get_raw_notes() 0 7 1
A get_meta() 0 3 1
A add_meta() 0 3 1
A update_meta() 0 3 1
A delete_meta() 0 3 1
C sanitize_columns() 0 52 11
C add_email() 0 31 7
A remove_email() 0 13 2
C set_primary_email() 0 35 7

How to fix   Complexity   

Complex Class

Complex classes like Give_Donor 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_Donor, 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 24 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
 * Donor
4
 *
5
 * @package     Give
6
 * @subpackage  Classes/Give_Donor
7
 * @copyright   Copyright (c) 2016, WordImpress
8
 * @license     https://opensource.org/licenses/gpl-license 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_Donor Class
19
 *
20
 * This class handles customers.
21
 *
22
 * @since 1.0
23
 */
24
class Give_Donor {
25
26
	/**
27
	 * The donor ID
28
	 *
29
	 * @since  1.0
30
	 * @access public
31
	 *
32
	 * @var    int
33
	 */
34
	public $id = 0;
35
36
	/**
37
	 * The donor's donation count.
38
	 *
39
	 * @since  1.0
40
	 * @access public
41
	 *
42
	 * @var    int
43
	 */
44
	public $purchase_count = 0;
45
46
	/**
47
	 * The donor's lifetime value.
48
	 *
49
	 * @since  1.0
50
	 * @access public
51
	 *
52
	 * @var    int
53
	 */
54
	public $purchase_value = 0;
55
56
	/**
57
	 * The donor's email.
58
	 *
59
	 * @since  1.0
60
	 * @access public
61
	 *
62
	 * @var    string
63
	 */
64
	public $email;
65
66
	/**
67
	 * The donor's emails.
68
	 *
69
	 * @since  1.7
70
	 * @access public
71
	 *
72
	 * @var    array
73
	 */
74
	public $emails;
75
76
	/**
77
	 * The donor's name.
78
	 *
79
	 * @since  1.0
80
	 * @access public
81
	 *
82
	 * @var    string
83
	 */
84
	public $name;
85
86
	/**
87
	 * The donor creation date.
88
	 *
89
	 * @since  1.0
90
	 * @access public
91
	 *
92
	 * @var    string
93
	 */
94
	public $date_created;
95
96
	/**
97
	 * The payment IDs associated with the donor.
98
	 *
99
	 * @since  1.0
100
	 * @access public
101
	 *
102
	 * @var    string
103
	 */
104
	public $payment_ids;
105
106
	/**
107
	 * The user ID associated with the donor.
108
	 *
109
	 * @since  1.0
110
	 * @access public
111
	 *
112
	 * @var    int
113
	 */
114
	public $user_id;
115
116
	/**
117
	 * Donor notes saved by admins.
118
	 *
119
	 * @since  1.0
120
	 * @access public
121
	 *
122
	 * @var    string
123
	 */
124
	public $notes;
125
126
	/**
127
	 * The Database Abstraction
128
	 *
129
	 * @since  1.0
130
	 * @access protected
131
	 *
132
	 * @var    Give_DB_Donors
133
	 */
134
	protected $db;
135
136
	/**
137
	 * Give_Donor constructor.
138
	 *
139
	 * @param bool $_id_or_email
140
	 * @param bool $by_user_id
141
	 */
142
	public function __construct( $_id_or_email = false, $by_user_id = false ) {
143
144
		$this->db = new Give_DB_Donors();
145
146
		if (
147
			false === $_id_or_email
148
			|| ( is_numeric( $_id_or_email ) && (int) $_id_or_email !== absint( $_id_or_email ) )
149
		) {
150
			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...
151
		}
152
153
		$by_user_id = is_bool( $by_user_id ) ? $by_user_id : false;
154
155
		if ( is_numeric( $_id_or_email ) ) {
156
			$field = $by_user_id ? 'user_id' : 'id';
157
		} else {
158
			$field = 'email';
159
		}
160
161
		$donor = $this->db->get_donor_by( $field, $_id_or_email );
162
163
		if ( empty( $donor ) || ! is_object( $donor ) ) {
164
			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...
165
		}
166
167
		$this->setup_donor( $donor );
168
169
	}
170
171
	/**
172
	 * Setup Donor
173
	 *
174
	 * Set donor variables.
175
	 *
176
	 * @since  1.0
177
	 * @access private
178
	 *
179
	 * @param  object $donor The Donor Object.
180
	 *
181
	 * @return bool             If the setup was successful or not.
182
	 */
183
	private function setup_donor( $donor ) {
184
185
		if ( ! is_object( $donor ) ) {
186
			return false;
187
		}
188
189
		foreach ( $donor as $key => $value ) {
190
191
			switch ( $key ) {
192
193
				case 'notes':
194
					$this->$key = $this->get_notes();
195
					break;
196
197
				default:
198
					$this->$key = $value;
199
					break;
200
201
			}
202
		}
203
204
		// Get donor's all email including primary email.
205
		$this->emails = (array) $this->get_meta( 'additional_email', false );
206
		$this->emails = array( 'primary' => $this->email ) + $this->emails;
207
208
		// Donor ID and email are the only things that are necessary, make sure they exist.
209
		if ( ! empty( $this->id ) && ! empty( $this->email ) ) {
210
			return true;
211
		}
212
213
		return false;
214
215
	}
216
217
	/**
218
	 * Magic __get function to dispatch a call to retrieve a private property.
219
	 *
220
	 * @since  1.0
221
	 * @access public
222
	 * @param $key
223
	 *
224
	 * @return mixed|\WP_Error
225
	 */
226
	public function __get( $key ) {
227
228
		if ( method_exists( $this, 'get_' . $key ) ) {
229
230
			return call_user_func( array( $this, 'get_' . $key ) );
231
232
		} else {
233
234
			/* translators: %s: property key */
235
			return new WP_Error( 'give-donor-invalid-property', sprintf( esc_html__( 'Can\'t get property %s.', 'give' ), $key ) );
236
237
		}
238
239
	}
240
241
	/**
242
	 * Creates a donor.
243
	 *
244
	 * @since  1.0
245
	 * @access public
246
	 *
247
	 * @param  array $data Array of attributes for a donor.
248
	 *
249
	 * @return bool|int    False if not a valid creation, donor ID if user is found or valid creation.
250
	 */
251
	public function create( $data = array() ) {
252
253
		if ( $this->id != 0 || empty( $data ) ) {
254
			return false;
255
		}
256
257
		$defaults = array(
258
			'payment_ids' => '',
259
		);
260
261
		$args = wp_parse_args( $data, $defaults );
262
		$args = $this->sanitize_columns( $args );
263
264
		if ( empty( $args['email'] ) || ! is_email( $args['email'] ) ) {
265
			return false;
266
		}
267
268
		if ( ! empty( $args['payment_ids'] ) && is_array( $args['payment_ids'] ) ) {
269
			$args['payment_ids'] = implode( ',', array_unique( array_values( $args['payment_ids'] ) ) );
270
		}
271
272
		/**
273
		 * Fires before creating donors.
274
		 *
275
		 * @since 1.0
276
		 *
277
		 * @param array $args Donor attributes.
278
		 */
279
		do_action( 'give_donor_pre_create', $args );
280
281
		$created = false;
282
283
		// The DB class 'add' implies an update if the donor being asked to be created already exists
284
		if ( $this->db->add( $data ) ) {
285
286
			// We've successfully added/updated the donor, reset the class vars with the new data
287
			$donor = $this->db->get_donor_by( 'email', $args['email'] );
288
289
			// Setup the donor data with the values from DB
290
			$this->setup_donor( $donor );
291
292
			$created = $this->id;
293
		}
294
295
		/**
296
		 * Fires after creating donors.
297
		 *
298
		 * @since 1.0
299
		 *
300
		 * @param bool|int $created False if not a valid creation, donor ID if user is found or valid creation.
301
		 * @param array $args Customer attributes.
302
		 */
303
		do_action( 'give_donor_post_create', $created, $args );
304
305
		return $created;
306
307
	}
308
309
	/**
310
	 * Updates a donor record.
311
	 *
312
	 * @since  1.0
313
	 * @access public
314
	 *
315
	 * @param  array $data Array of data attributes for a donor (checked via whitelist).
316
	 *
317
	 * @return bool        If the update was successful or not.
318
	 */
319
	public function update( $data = array() ) {
320
321
		if ( empty( $data ) ) {
322
			return false;
323
		}
324
325
		$data = $this->sanitize_columns( $data );
326
327
		/**
328
		 * Fires before updating donors.
329
		 *
330
		 * @since 1.0
331
		 *
332
		 * @param int $donor_id Donor id.
333
		 * @param array $data Donor attributes.
334
		 */
335
		do_action( 'give_donor_pre_update', $this->id, $data );
336
337
		$updated = false;
338
339
		if ( $this->db->update( $this->id, $data ) ) {
340
341
			$donor = $this->db->get_donor_by( 'id', $this->id );
342
			$this->setup_donor( $donor );
343
344
			$updated = true;
345
		}
346
347
		/**
348
		 * Fires after updating donors.
349
		 *
350
		 * @since 1.0
351
		 *
352
		 * @param bool $updated If the update was successful or not.
353
		 * @param int $donor_id Donor id.
354
		 * @param array $data Donor attributes.
355
		 */
356
		do_action( 'give_donor_post_update', $updated, $this->id, $data );
357
358
		return $updated;
359
	}
360
361
	/**
362
	 * Attach Payment
363
	 *
364
	 * Attach payment to the donor then triggers increasing stats.
365
	 *
366
	 * @since  1.0
367
	 * @access public
368
	 *
369
	 * @param  int $payment_id The payment ID to attach to the donor.
370
	 * @param  bool $update_stats For backwards compatibility, if we should increase the stats or not.
371
	 *
372
	 * @return bool            If the attachment was successfully.
373
	 */
374
	public function attach_payment( $payment_id = 0, $update_stats = true ) {
375
376
		if ( empty( $payment_id ) ) {
377
			return false;
378
		}
379
380
		if ( empty( $this->payment_ids ) ) {
381
382
			$new_payment_ids = $payment_id;
383
384
		} else {
385
386
			$payment_ids = array_map( 'absint', explode( ',', $this->payment_ids ) );
387
388
			if ( in_array( $payment_id, $payment_ids ) ) {
389
				$update_stats = false;
390
			}
391
392
			$payment_ids[] = $payment_id;
393
394
			$new_payment_ids = implode( ',', array_unique( array_values( $payment_ids ) ) );
395
396
		}
397
398
		/**
399
		 * Fires before attaching payments to customers.
400
		 *
401
		 * @since 1.0
402
		 *
403
		 * @param int $payment_id Payment id.
404
		 * @param int $donor_id Customer id.
405
		 */
406
		do_action( 'give_donor_pre_attach_payment', $payment_id, $this->id );
407
408
		$payment_added = $this->update( array( 'payment_ids' => $new_payment_ids ) );
409
410
		if ( $payment_added ) {
411
412
			$this->payment_ids = $new_payment_ids;
413
414
			// We added this payment successfully, increment the stats
415
			if ( $update_stats ) {
416
				$payment_amount = give_get_payment_amount( $payment_id );
417
418
				if ( ! empty( $payment_amount ) ) {
419
					$this->increase_value( $payment_amount );
420
				}
421
422
				$this->increase_purchase_count();
423
			}
424
		}
425
426
		/**
427
		 * Fires after attaching payments to the donor.
428
		 *
429
		 * @since 1.0
430
		 *
431
		 * @param bool $payment_added If the attachment was successfully.
432
		 * @param int $payment_id Payment id.
433
		 * @param int $donor_id Donor id.
434
		 */
435
		do_action( 'give_donor_post_attach_payment', $payment_added, $payment_id, $this->id );
436
437
		return $payment_added;
438
	}
439
440
	/**
441
	 * Remove Payment
442
	 *
443
	 * Remove a payment from this donor, then triggers reducing stats.
444
	 *
445
	 * @since  1.0
446
	 * @access public
447
	 *
448
	 * @param  int $payment_id The Payment ID to remove.
449
	 * @param  bool $update_stats For backwards compatibility, if we should increase the stats or not.
450
	 *
451
	 * @return boolean               If the removal was successful.
452
	 */
453
	public function remove_payment( $payment_id = 0, $update_stats = true ) {
454
455
		if ( empty( $payment_id ) ) {
456
			return false;
457
		}
458
459
		$payment = new Give_Payment( $payment_id );
460
461
		if ( 'publish' !== $payment->status && 'revoked' !== $payment->status ) {
462
			$update_stats = false;
463
		}
464
465
		$new_payment_ids = '';
466
467
		if ( ! empty( $this->payment_ids ) ) {
468
469
			$payment_ids = array_map( 'absint', explode( ',', $this->payment_ids ) );
470
471
			$pos = array_search( $payment_id, $payment_ids );
472
			if ( false === $pos ) {
473
				return false;
474
			}
475
476
			unset( $payment_ids[ $pos ] );
477
			$payment_ids = array_filter( $payment_ids );
478
479
			$new_payment_ids = implode( ',', array_unique( array_values( $payment_ids ) ) );
480
481
		}
482
483
		/**
484
		 * Fires before removing payments from customers.
485
		 *
486
		 * @since 1.0
487
		 *
488
		 * @param int $payment_id Payment id.
489
		 * @param int $donor_id Customer id.
490
		 */
491
		do_action( 'give_donor_pre_remove_payment', $payment_id, $this->id );
492
493
		$payment_removed = $this->update( array( 'payment_ids' => $new_payment_ids ) );
494
495
		if ( $payment_removed ) {
496
497
			$this->payment_ids = $new_payment_ids;
498
499
			if ( $update_stats ) {
500
				// We removed this payment successfully, decrement the stats
501
				$payment_amount = give_get_payment_amount( $payment_id );
502
503
				if ( ! empty( $payment_amount ) ) {
504
					$this->decrease_value( $payment_amount );
505
				}
506
507
				$this->decrease_donation_count();
508
			}
509
		}
510
511
		/**
512
		 * Fires after removing payments from donors.
513
		 *
514
		 * @since 1.0
515
		 *
516
		 * @param bool $payment_removed If the removal was successfully.
517
		 * @param int $payment_id Payment id.
518
		 * @param int $donor_id Donor id.
519
		 */
520
		do_action( 'give_donor_post_remove_payment', $payment_removed, $payment_id, $this->id );
521
522
		return $payment_removed;
523
524
	}
525
526
	/**
527
	 * Increase the donation count of a donor.
528
	 *
529
	 * @since  1.0
530
	 * @access public
531
	 *
532
	 * @param  int $count The number to increase by.
533
	 *
534
	 * @return int        The donation count.
0 ignored issues
show
Documentation introduced by
Should the return type not be false|integer?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
535
	 */
536
	public function increase_purchase_count( $count = 1 ) {
537
538
		// Make sure it's numeric and not negative.
539
		if ( ! is_numeric( $count ) || $count != absint( $count ) ) {
540
			return false;
541
		}
542
543
		$new_total = (int) $this->purchase_count + (int) $count;
544
545
		/**
546
		 * Fires before increasing the donor's donation count.
547
		 *
548
		 * @since 1.0
549
		 *
550
		 * @param int $count The number to increase by.
551
		 * @param int $donor_id Donor id.
552
		 */
553
		do_action( 'give_donor_pre_increase_donation_count', $count, $this->id );
554
555
		if ( $this->update( array( 'purchase_count' => $new_total ) ) ) {
556
			$this->purchase_count = $new_total;
557
		}
558
559
		/**
560
		 * Fires after increasing the donor's donation count.
561
		 *
562
		 * @since 1.0
563
		 *
564
		 * @param int $purchase_count Donor donation count.
565
		 * @param int $count The number increased by.
566
		 * @param int $donor_id Donor id.
567
		 */
568
		do_action( 'give_donor_post_increase_donation_count', $this->purchase_count, $count, $this->id );
569
570
		return $this->purchase_count;
571
	}
572
573
	/**
574
	 * Decrease the donor donation count.
575
	 *
576
	 * @since  1.0
577
	 * @access public
578
	 *
579
	 * @param  int $count The amount to decrease by.
580
	 *
581
	 * @return mixed      If successful, the new count, otherwise false.
582
	 */
583
	public function decrease_donation_count( $count = 1 ) {
584
585
		// Make sure it's numeric and not negative
586
		if ( ! is_numeric( $count ) || $count != absint( $count ) ) {
587
			return false;
588
		}
589
590
		$new_total = (int) $this->purchase_count - (int) $count;
591
592
		if ( $new_total < 0 ) {
593
			$new_total = 0;
594
		}
595
596
		/**
597
		 * Fires before decreasing the donor's donation count.
598
		 *
599
		 * @since 1.0
600
		 *
601
		 * @param int $count The number to decrease by.
602
		 * @param int $donor_id Customer id.
603
		 */
604
		do_action( 'give_donor_pre_decrease_donation_count', $count, $this->id );
605
606
		if ( $this->update( array( 'purchase_count' => $new_total ) ) ) {
607
			$this->purchase_count = $new_total;
608
		}
609
610
		/**
611
		 * Fires after decreasing the donor's donation count.
612
		 *
613
		 * @since 1.0
614
		 *
615
		 * @param int $purchase_count Donor's donation count.
616
		 * @param int $count The number decreased by.
617
		 * @param int $donor_id Donor id.
618
		 */
619
		do_action( 'give_donor_post_decrease_donation_count', $this->purchase_count, $count, $this->id );
620
621
		return $this->purchase_count;
622
	}
623
624
	/**
625
	 * Increase the donor's lifetime value.
626
	 *
627
	 * @since  1.0
628
	 * @access public
629
	 *
630
	 * @param  float $value The value to increase by.
631
	 *
632
	 * @return mixed        If successful, the new value, otherwise false.
633
	 */
634
	public function increase_value( $value = 0.00 ) {
635
636
		$new_value = floatval( $this->purchase_value ) + $value;
637
638
		/**
639
		 * Fires before increasing donor lifetime value.
640
		 *
641
		 * @since 1.0
642
		 *
643
		 * @param float $value The value to increase by.
644
		 * @param int $donor_id Customer id.
645
		 */
646
		do_action( 'give_donor_pre_increase_value', $value, $this->id );
647
648
		if ( $this->update( array( 'purchase_value' => $new_value ) ) ) {
649
			$this->purchase_value = $new_value;
650
		}
651
652
		/**
653
		 * Fires after increasing donor lifetime value.
654
		 *
655
		 * @since 1.0
656
		 *
657
		 * @param float $purchase_value Donor's lifetime value.
658
		 * @param float $value The value increased by.
659
		 * @param int $donor_id Donor id.
660
		 */
661
		do_action( 'give_donor_post_increase_value', $this->purchase_value, $value, $this->id );
662
663
		return $this->purchase_value;
664
	}
665
666
	/**
667
	 * Decrease a donor's lifetime value.
668
	 *
669
	 * @since  1.0
670
	 * @access public
671
	 *
672
	 * @param  float $value The value to decrease by.
673
	 *
674
	 * @return mixed        If successful, the new value, otherwise false.
675
	 */
676
	public function decrease_value( $value = 0.00 ) {
677
678
		$new_value = floatval( $this->purchase_value ) - $value;
679
680
		if ( $new_value < 0 ) {
681
			$new_value = 0.00;
682
		}
683
684
		/**
685
		 * Fires before decreasing donor lifetime value.
686
		 *
687
		 * @since 1.0
688
		 *
689
		 * @param float $value The value to decrease by.
690
		 * @param int $donor_id Donor id.
691
		 */
692
		do_action( 'give_donor_pre_decrease_value', $value, $this->id );
693
694
		if ( $this->update( array( 'purchase_value' => $new_value ) ) ) {
695
			$this->purchase_value = $new_value;
696
		}
697
698
		/**
699
		 * Fires after decreasing donor lifetime value.
700
		 *
701
		 * @since 1.0
702
		 *
703
		 * @param float $purchase_value Donor lifetime value.
704
		 * @param float $value The value decreased by.
705
		 * @param int $donor_id Donor id.
706
		 */
707
		do_action( 'give_donor_post_decrease_value', $this->purchase_value, $value, $this->id );
708
709
		return $this->purchase_value;
710
	}
711
712
	/**
713
	 * Decrease/Increase a donor's lifetime value.
714
	 *
715
	 * This function will update donation stat on basis of current amount and new amount donation difference.
716
	 * Difference value can positive or negative. Negative value will decrease user donation stat while positive value increase donation stat.
717
	 *
718
	 * @since  1.0
719
	 * @access public
720
	 *
721
	 * @param  float $curr_amount Current Donation amount.
722
	 * @param  float $new_amount New (changed) Donation amount.
723
	 *
724
	 * @return mixed              If successful, the new donation stat value, otherwise false.
725
	 */
726
	public function update_donation_value( $curr_amount, $new_amount ) {
727
		/**
728
		 * Payment total difference value can be:
729
		 *  zero   (in case amount not change)
730
		 *  or -ve (in case amount decrease)
731
		 *  or +ve (in case amount increase)
732
		 */
733
		$payment_total_diff = $new_amount - $curr_amount;
734
735
		// We do not need to update donation stat if donation did not change.
736
		if ( ! $payment_total_diff ) {
737
			return false;
738
		}
739
740
		if ( $payment_total_diff > 0 ) {
741
			$this->increase_value( $payment_total_diff );
742
		} else {
743
			// Pass payment total difference as +ve value to decrease amount from user lifetime stat.
744
			$this->decrease_value( - $payment_total_diff );
745
		}
746
747
		return $this->purchase_value;
748
	}
749
750
	/**
751
	 * Get the parsed notes for a donor as an array.
752
	 *
753
	 * @since  1.0
754
	 * @access public
755
	 *
756
	 * @param  int $length The number of notes to get.
757
	 * @param  int $paged What note to start at.
758
	 *
759
	 * @return array       The notes requested.
760
	 */
761
	public function get_notes( $length = 20, $paged = 1 ) {
762
763
		$length = is_numeric( $length ) ? $length : 20;
764
		$offset = is_numeric( $paged ) && $paged != 1 ? ( ( absint( $paged ) - 1 ) * $length ) : 0;
765
766
		$all_notes   = $this->get_raw_notes();
767
		$notes_array = array_reverse( array_filter( explode( "\n\n", $all_notes ) ) );
768
769
		$desired_notes = array_slice( $notes_array, $offset, $length );
770
771
		return $desired_notes;
772
773
	}
774
775
	/**
776
	 * Get the total number of notes we have after parsing.
777
	 *
778
	 * @since  1.0
779
	 * @access public
780
	 *
781
	 * @return int The number of notes for the donor.
782
	 */
783
	public function get_notes_count() {
784
785
		$all_notes   = $this->get_raw_notes();
786
		$notes_array = array_reverse( array_filter( explode( "\n\n", $all_notes ) ) );
787
788
		return count( $notes_array );
789
790
	}
791
792
	/**
793
	 * Add a note for the donor.
794
	 *
795
	 * @since  1.0
796
	 * @access public
797
	 *
798
	 * @param  string $note The note to add. Default is empty.
799
	 *
800
	 * @return string|boolean The new note if added successfully, false otherwise.
801
	 */
802
	public function add_note( $note = '' ) {
803
804
		$note = trim( $note );
805
		if ( empty( $note ) ) {
806
			return false;
807
		}
808
809
		$notes = $this->get_raw_notes();
810
811
		if ( empty( $notes ) ) {
812
			$notes = '';
813
		}
814
815
		$note_string = date_i18n( 'F j, Y H:i:s', current_time( 'timestamp' ) ) . ' - ' . $note;
816
		$new_note    = apply_filters( 'give_customer_add_note_string', $note_string );
817
		$notes       .= "\n\n" . $new_note;
818
819
		/**
820
		 * Fires before donor note is added.
821
		 *
822
		 * @since 1.0
823
		 *
824
		 * @param string $new_note New note to add.
825
		 * @param int $donor_id Donor id.
826
		 */
827
		do_action( 'give_donor_pre_add_note', $new_note, $this->id );
828
829
		$updated = $this->update( array( 'notes' => $notes ) );
830
831
		if ( $updated ) {
832
			$this->notes = $this->get_notes();
833
		}
834
835
		/**
836
		 * Fires after donor note added.
837
		 *
838
		 * @since 1.0
839
		 *
840
		 * @param array $donor_notes Donor notes.
841
		 * @param string $new_note New note added.
842
		 * @param int $donor_id Donor id.
843
		 */
844
		do_action( 'give_donor_post_add_note', $this->notes, $new_note, $this->id );
845
846
		// Return the formatted note, so we can test, as well as update any displays
847
		return $new_note;
848
849
	}
850
851
	/**
852
	 * Get the notes column for the donor
853
	 *
854
	 * @since  1.0
855
	 * @access private
856
	 *
857
	 * @return string The Notes for the donor, non-parsed.
858
	 */
859
	private function get_raw_notes() {
860
861
		$all_notes = $this->db->get_column( 'notes', $this->id );
862
863
		return $all_notes;
864
865
	}
866
867
	/**
868
	 * Retrieve a meta field for a donor.
869
	 *
870
	 * @since  1.6
871
	 * @access public
872
	 *
873
	 * @param  string $meta_key The meta key to retrieve. Default is empty.
874
	 * @param  bool $single Whether to return a single value. Default is true.
875
	 *
876
	 * @return mixed            Will be an array if $single is false. Will be value of meta data field if $single is true.
877
	 */
878
	public function get_meta( $meta_key = '', $single = true ) {
879
		return Give()->donor_meta->get_meta( $this->id, $meta_key, $single );
880
	}
881
882
	/**
883
	 * Add a meta data field to a donor.
884
	 *
885
	 * @since  1.6
886
	 * @access public
887
	 *
888
	 * @param  string $meta_key Metadata name. Default is empty.
889
	 * @param  mixed $meta_value Metadata value.
890
	 * @param  bool $unique Optional. Whether the same key should not be added. Default is false.
891
	 *
892
	 * @return bool               False for failure. True for success.
893
	 */
894
	public function add_meta( $meta_key = '', $meta_value, $unique = false ) {
895
		return Give()->donor_meta->add_meta( $this->id, $meta_key, $meta_value, $unique );
896
	}
897
898
	/**
899
	 * Update a meta field based on donor ID.
900
	 *
901
	 * @since  1.6
902
	 * @access public
903
	 *
904
	 * @param  string $meta_key Metadata key. Default is empty.
905
	 * @param  mixed $meta_value Metadata value.
906
	 * @param  mixed $prev_value Optional. Previous value to check before removing. Default is empty.
907
	 *
908
	 * @return bool               False on failure, true if success.
909
	 */
910
	public function update_meta( $meta_key = '', $meta_value, $prev_value = '' ) {
911
		return Give()->donor_meta->update_meta( $this->id, $meta_key, $meta_value, $prev_value );
912
	}
913
914
	/**
915
	 * Remove metadata matching criteria from a donor.
916
	 *
917
	 * @since  1.6
918
	 * @access public
919
	 *
920
	 * @param  string $meta_key Metadata name. Default is empty.
921
	 * @param  mixed $meta_value Optional. Metadata value. Default is empty.
922
	 *
923
	 * @return bool               False for failure. True for success.
924
	 */
925
	public function delete_meta( $meta_key = '', $meta_value = '' ) {
926
		return Give()->donor_meta->delete_meta( $this->id, $meta_key, $meta_value );
927
	}
928
929
	/**
930
	 * Sanitize the data for update/create
931
	 *
932
	 * @since  1.0
933
	 * @access private
934
	 *
935
	 * @param  array $data The data to sanitize.
936
	 *
937
	 * @return array       The sanitized data, based off column defaults.
938
	 */
939
	private function sanitize_columns( $data ) {
940
941
		$columns        = $this->db->get_columns();
942
		$default_values = $this->db->get_column_defaults();
943
944
		foreach ( $columns as $key => $type ) {
945
946
			// Only sanitize data that we were provided
947
			if ( ! array_key_exists( $key, $data ) ) {
948
				continue;
949
			}
950
951
			switch ( $type ) {
952
953
				case '%s':
954
					if ( 'email' == $key ) {
955
						$data[ $key ] = sanitize_email( $data[ $key ] );
956
					} elseif ( 'notes' == $key ) {
957
						$data[ $key ] = strip_tags( $data[ $key ] );
958
					} else {
959
						$data[ $key ] = sanitize_text_field( $data[ $key ] );
960
					}
961
					break;
962
963
				case '%d':
964
					if ( ! is_numeric( $data[ $key ] ) || (int) $data[ $key ] !== absint( $data[ $key ] ) ) {
965
						$data[ $key ] = $default_values[ $key ];
966
					} else {
967
						$data[ $key ] = absint( $data[ $key ] );
968
					}
969
					break;
970
971
				case '%f':
972
					// Convert what was given to a float
973
					$value = floatval( $data[ $key ] );
974
975
					if ( ! is_float( $value ) ) {
976
						$data[ $key ] = $default_values[ $key ];
977
					} else {
978
						$data[ $key ] = $value;
979
					}
980
					break;
981
982
				default:
983
					$data[ $key ] = sanitize_text_field( $data[ $key ] );
984
					break;
985
986
			}
987
		}
988
989
		return $data;
990
	}
991
992
	/**
993
	 * Attach an email to the donor
994
	 *
995
	 * @since  1.7
996
	 * @access public
997
	 *
998
	 * @param  string $email The email address to attach to the donor
999
	 * @param  bool $primary Allows setting the email added as the primary
1000
	 *
1001
	 * @return bool            If the email was added successfully
1002
	 */
1003
	public function add_email( $email = '', $primary = false ) {
1004
		if ( ! is_email( $email ) ) {
1005
			return false;
1006
		}
1007
		$existing = new Give_Donor( $email );
1008
1009
		if ( $existing->id > 0 ) {
1010
			// Email address already belongs to another donor
1011
			return false;
1012
		}
1013
1014
		if ( email_exists( $email ) ) {
1015
			$user = get_user_by( 'email', $email );
1016
			if ( $user->ID != $this->user_id ) {
1017
				return false;
1018
			}
1019
		}
1020
1021
		do_action( 'give_donor_pre_add_email', $email, $this->id, $this );
1022
1023
		// Add is used to ensure duplicate emails are not added
1024
		$ret = (bool) $this->add_meta( 'additional_email', $email );
1025
1026
		do_action( 'give_donor_post_add_email', $email, $this->id, $this );
1027
1028
		if ( $ret && true === $primary ) {
1029
			$this->set_primary_email( $email );
1030
		}
1031
1032
		return $ret;
1033
	}
1034
1035
	/**
1036
	 * Remove an email from the donor.
1037
	 *
1038
	 * @since  1.7
1039
	 * @access public
1040
	 *
1041
	 * @param  string $email The email address to remove from the donor.
1042
	 *
1043
	 * @return bool          If the email was removed successfully.
1044
	 */
1045
	public function remove_email( $email = '' ) {
1046
		if ( ! is_email( $email ) ) {
1047
			return false;
1048
		}
1049
1050
		do_action( 'give_donor_pre_remove_email', $email, $this->id, $this );
1051
1052
		$ret = (bool) $this->delete_meta( 'additional_email', $email );
1053
1054
		do_action( 'give_donor_post_remove_email', $email, $this->id, $this );
1055
1056
		return $ret;
1057
	}
1058
1059
	/**
1060
	 * Set an email address as the donor's primary email.
1061
	 *
1062
	 * This will move the donor's previous primary email to an additional email.
1063
	 *
1064
	 * @since  1.7
1065
	 * @access public
1066
	 *
1067
	 * @param  string $new_primary_email The email address to remove from the donor.
1068
	 *
1069
	 * @return bool                      If the email was set as primary successfully.
1070
	 */
1071
	public function set_primary_email( $new_primary_email = '' ) {
1072
		if ( ! is_email( $new_primary_email ) ) {
1073
			return false;
1074
		}
1075
1076
		do_action( 'give_donor_pre_set_primary_email', $new_primary_email, $this->id, $this );
1077
1078
		$existing = new Give_Donor( $new_primary_email );
1079
1080
		if ( $existing->id > 0 && (int) $existing->id !== (int) $this->id ) {
1081
			// This email belongs to another donor.
1082
			return false;
1083
		}
1084
1085
		$old_email = $this->email;
1086
1087
		// Update donor record with new email.
1088
		$update = $this->update( array( 'email' => $new_primary_email ) );
1089
1090
		// Remove new primary from list of additional emails.
1091
		$remove = $this->remove_email( $new_primary_email );
1092
1093
		// Add old email to additional emails list.
1094
		$add = $this->add_email( $old_email );
1095
1096
		$ret = $update && $remove && $add;
1097
1098
		if ( $ret ) {
1099
			$this->email = $new_primary_email;
1100
		}
1101
1102
		do_action( 'give_donor_post_set_primary_email', $new_primary_email, $this->id, $this );
1103
1104
		return $ret;
1105
	}
1106
}
1107