Test Failed
Push — backup/issues/1132 ( b1d18b )
by Ravinder
05:29
created

includes/class-give-donor.php (26 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
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
	 * Donor address.
128
	 *
129
	 * @since  1.0
130
	 * @access public
131
	 *
132
	 * @var    array
133
	 */
134
	public $address;
135
136
	/**
137
	 * The Database Abstraction
138
	 *
139
	 * @since  1.0
140
	 * @access protected
141
	 *
142
	 * @var    Give_DB_Donors
143
	 */
144
	protected $db;
145
146
	/**
147
	 * Give_Donor constructor.
148
	 *
149
	 * @param bool $_id_or_email
150
	 * @param bool $by_user_id
151
	 */
152
	public function __construct( $_id_or_email = false, $by_user_id = false ) {
153
154
		$this->db = new Give_DB_Donors();
155
156
		if (
157
			false === $_id_or_email
158
			|| ( is_numeric( $_id_or_email ) && (int) $_id_or_email !== absint( $_id_or_email ) )
159
		) {
160
			return false;
161
		}
162
163
		$by_user_id = is_bool( $by_user_id ) ? $by_user_id : false;
164
165
		if ( is_numeric( $_id_or_email ) ) {
166
			$field = $by_user_id ? 'user_id' : 'id';
167
		} else {
168
			$field = 'email';
169
		}
170
171
		$donor = $this->db->get_donor_by( $field, $_id_or_email );
172
173
		if ( empty( $donor ) || ! is_object( $donor ) ) {
174
			return false;
175
		}
176
177
		$this->setup_donor( $donor );
178
179
	}
180
181
	/**
182
	 * Setup Donor
183
	 *
184
	 * Set donor variables.
185
	 *
186
	 * @since  1.0
187
	 * @access private
188
	 *
189
	 * @param  object $donor The Donor Object.
190
	 *
191
	 * @return bool             If the setup was successful or not.
192
	 */
193
	private function setup_donor( $donor ) {
194
195
		if ( ! is_object( $donor ) ) {
196
			return false;
197
		}
198
199
		foreach ( $donor as $key => $value ) {
200
201
			switch ( $key ) {
202
203
				case 'notes':
204
					$this->$key = $this->get_notes();
205
					break;
206
207
				default:
208
					$this->$key = $value;
209
					break;
210
211
			}
212
		}
213
214
		// Get donor's all email including primary email.
215
		$this->emails = (array) $this->get_meta( 'additional_email', false );
216
		$this->emails = array( 'primary' => $this->email ) + $this->emails;
217
218
		$this->setup_address();
219
220
		// Donor ID and email are the only things that are necessary, make sure they exist.
221
		if ( ! empty( $this->id ) && ! empty( $this->email ) ) {
222
			return true;
223
		}
224
225
		return false;
226
227
	}
228
229
230
	/**
231
	 * Setup donor address.
232
	 * 
233
	 * @since 2.0
234
	 * @access public
235
	 */
236
	public function setup_address() {
237
		global $wpdb;
238
		$meta_type = Give()->donor_meta->meta_type;
239
240
		$addresses = $wpdb->get_results(
0 ignored issues
show
Usage of a direct database call is discouraged.
Loading history...
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
241
			$wpdb->prepare(
242
				"
243
				SELECT meta_key, meta_value FROM {$wpdb->donormeta}
244
				WHERE meta_key
245
				LIKE '%%%s%%'
246
				AND {$meta_type}_id=%d
247
				",
248
				'give_donor_address',
249
				$this->id
250
			),
251
			ARRAY_N
252
		);
253
254
		if ( empty( $addresses ) ) {
255
			return array();
256
		}
257
258
		foreach ( $addresses as $address ) {
259
			$address[0] = str_replace( '_give_donor_address_', '', $address[0] );
260
			$address[0] = explode( '_', $address[0] );
261
262
			if ( 'address1' === $address[0][1] ) {
263
				$address[0][1] = 'line1';
264
			} elseif ( 'address2' === $address[0][1] ) {
265
				$address[0][1] = 'line2';
266
			}
267
268
			if ( 3 === count( $address[0] ) ) {
269
				$this->address[ $address[0][0] ][ $address[0][2] ][ $address[0][1] ] = $address[1];
270
			} else {
271
				$this->address[ $address[0][0] ][ $address[0][1] ] = $address[1];
272
			}
273
		}
274
	}
275
276
	/**
277
	 * Magic __get function to dispatch a call to retrieve a private property.
278
	 *
279
	 * @since  1.0
280
	 * @access public
281
	 * @param $key
282
	 *
283
	 * @return mixed|\WP_Error
284
	 */
285 View Code Duplication
	public function __get( $key ) {
286
287
		if ( method_exists( $this, 'get_' . $key ) ) {
288
289
			return call_user_func( array( $this, 'get_' . $key ) );
290
291
		} else {
292
293
			/* translators: %s: property key */
294
			return new WP_Error( 'give-donor-invalid-property', sprintf( esc_html__( 'Can\'t get property %s.', 'give' ), $key ) );
295
296
		}
297
298
	}
299
300
	/**
301
	 * Creates a donor.
302
	 *
303
	 * @since  1.0
304
	 * @access public
305
	 *
306
	 * @param  array $data Array of attributes for a donor.
307
	 *
308
	 * @return bool|int    False if not a valid creation, donor ID if user is found or valid creation.
309
	 */
310
	public function create( $data = array() ) {
311
312
		if ( $this->id != 0 || empty( $data ) ) {
313
			return false;
314
		}
315
316
		$defaults = array(
317
			'payment_ids' => '',
318
		);
319
320
		$args = wp_parse_args( $data, $defaults );
321
		$args = $this->sanitize_columns( $args );
322
323
		if ( empty( $args['email'] ) || ! is_email( $args['email'] ) ) {
324
			return false;
325
		}
326
327 View Code Duplication
		if ( ! empty( $args['payment_ids'] ) && is_array( $args['payment_ids'] ) ) {
328
			$args['payment_ids'] = implode( ',', array_unique( array_values( $args['payment_ids'] ) ) );
329
		}
330
331
		/**
332
		 * Fires before creating donors.
333
		 *
334
		 * @since 1.0
335
		 *
336
		 * @param array $args Donor attributes.
337
		 */
338
		do_action( 'give_donor_pre_create', $args );
339
340
		$created = false;
341
342
		// The DB class 'add' implies an update if the donor being asked to be created already exists
343 View Code Duplication
		if ( $this->db->add( $data ) ) {
344
345
			// We've successfully added/updated the donor, reset the class vars with the new data
346
			$donor = $this->db->get_donor_by( 'email', $args['email'] );
347
348
			// Setup the donor data with the values from DB
349
			$this->setup_donor( $donor );
350
351
			$created = $this->id;
352
		}
353
354
		/**
355
		 * Fires after creating donors.
356
		 *
357
		 * @since 1.0
358
		 *
359
		 * @param bool|int $created False if not a valid creation, donor ID if user is found or valid creation.
360
		 * @param array $args Customer attributes.
361
		 */
362
		do_action( 'give_donor_post_create', $created, $args );
363
364
		return $created;
365
366
	}
367
368
	/**
369
	 * Updates a donor record.
370
	 *
371
	 * @since  1.0
372
	 * @access public
373
	 *
374
	 * @param  array $data Array of data attributes for a donor (checked via whitelist).
375
	 *
376
	 * @return bool        If the update was successful or not.
377
	 */
378
	public function update( $data = array() ) {
379
380
		if ( empty( $data ) ) {
381
			return false;
382
		}
383
384
		$data = $this->sanitize_columns( $data );
385
386
		/**
387
		 * Fires before updating donors.
388
		 *
389
		 * @since 1.0
390
		 *
391
		 * @param int $donor_id Donor id.
392
		 * @param array $data Donor attributes.
393
		 */
394
		do_action( 'give_donor_pre_update', $this->id, $data );
395
396
		$updated = false;
397
398 View Code Duplication
		if ( $this->db->update( $this->id, $data ) ) {
399
400
			$donor = $this->db->get_donor_by( 'id', $this->id );
401
			$this->setup_donor( $donor );
402
403
			$updated = true;
404
		}
405
406
		/**
407
		 * Fires after updating donors.
408
		 *
409
		 * @since 1.0
410
		 *
411
		 * @param bool $updated If the update was successful or not.
412
		 * @param int $donor_id Donor id.
413
		 * @param array $data Donor attributes.
414
		 */
415
		do_action( 'give_donor_post_update', $updated, $this->id, $data );
416
417
		return $updated;
418
	}
419
420
	/**
421
	 * Attach Payment
422
	 *
423
	 * Attach payment to the donor then triggers increasing stats.
424
	 *
425
	 * @since  1.0
426
	 * @access public
427
	 *
428
	 * @param  int $payment_id The payment ID to attach to the donor.
429
	 * @param  bool $update_stats For backwards compatibility, if we should increase the stats or not.
430
	 *
431
	 * @return bool            If the attachment was successfully.
432
	 */
433
	public function attach_payment( $payment_id = 0, $update_stats = true ) {
434
435
		if ( empty( $payment_id ) ) {
436
			return false;
437
		}
438
439
		if ( empty( $this->payment_ids ) ) {
440
441
			$new_payment_ids = $payment_id;
442
443
		} else {
444
445
			$payment_ids = array_map( 'absint', explode( ',', $this->payment_ids ) );
446
447
			if ( in_array( $payment_id, $payment_ids ) ) {
448
				$update_stats = false;
449
			}
450
451
			$payment_ids[] = $payment_id;
452
453
			$new_payment_ids = implode( ',', array_unique( array_values( $payment_ids ) ) );
454
455
		}
456
457
		/**
458
		 * Fires before attaching payments to customers.
459
		 *
460
		 * @since 1.0
461
		 *
462
		 * @param int $payment_id Payment id.
463
		 * @param int $donor_id Customer id.
464
		 */
465
		do_action( 'give_donor_pre_attach_payment', $payment_id, $this->id );
466
467
		$payment_added = $this->update( array( 'payment_ids' => $new_payment_ids ) );
468
469 View Code Duplication
		if ( $payment_added ) {
470
471
			$this->payment_ids = $new_payment_ids;
472
473
			// We added this payment successfully, increment the stats
474
			if ( $update_stats ) {
475
				$payment_amount = give_get_payment_amount( $payment_id );
476
477
				if ( ! empty( $payment_amount ) ) {
478
					$this->increase_value( $payment_amount );
479
				}
480
481
				$this->increase_purchase_count();
482
			}
483
		}
484
485
		/**
486
		 * Fires after attaching payments to the donor.
487
		 *
488
		 * @since 1.0
489
		 *
490
		 * @param bool $payment_added If the attachment was successfully.
491
		 * @param int $payment_id Payment id.
492
		 * @param int $donor_id Donor id.
493
		 */
494
		do_action( 'give_donor_post_attach_payment', $payment_added, $payment_id, $this->id );
495
496
		return $payment_added;
497
	}
498
499
	/**
500
	 * Remove Payment
501
	 *
502
	 * Remove a payment from this donor, then triggers reducing stats.
503
	 *
504
	 * @since  1.0
505
	 * @access public
506
	 *
507
	 * @param  int $payment_id The Payment ID to remove.
508
	 * @param  bool $update_stats For backwards compatibility, if we should increase the stats or not.
509
	 *
510
	 * @return boolean               If the removal was successful.
511
	 */
512
	public function remove_payment( $payment_id = 0, $update_stats = true ) {
513
514
		if ( empty( $payment_id ) ) {
515
			return false;
516
		}
517
518
		$payment = new Give_Payment( $payment_id );
519
520
		if ( 'publish' !== $payment->status && 'revoked' !== $payment->status ) {
521
			$update_stats = false;
522
		}
523
524
		$new_payment_ids = '';
525
526
		if ( ! empty( $this->payment_ids ) ) {
527
528
			$payment_ids = array_map( 'absint', explode( ',', $this->payment_ids ) );
529
530
			$pos = array_search( $payment_id, $payment_ids );
531
			if ( false === $pos ) {
532
				return false;
533
			}
534
535
			unset( $payment_ids[ $pos ] );
536
			$payment_ids = array_filter( $payment_ids );
537
538
			$new_payment_ids = implode( ',', array_unique( array_values( $payment_ids ) ) );
539
540
		}
541
542
		/**
543
		 * Fires before removing payments from customers.
544
		 *
545
		 * @since 1.0
546
		 *
547
		 * @param int $payment_id Payment id.
548
		 * @param int $donor_id Customer id.
549
		 */
550
		do_action( 'give_donor_pre_remove_payment', $payment_id, $this->id );
551
552
		$payment_removed = $this->update( array( 'payment_ids' => $new_payment_ids ) );
553
554 View Code Duplication
		if ( $payment_removed ) {
555
556
			$this->payment_ids = $new_payment_ids;
557
558
			if ( $update_stats ) {
559
				// We removed this payment successfully, decrement the stats
560
				$payment_amount = give_get_payment_amount( $payment_id );
561
562
				if ( ! empty( $payment_amount ) ) {
563
					$this->decrease_value( $payment_amount );
564
				}
565
566
				$this->decrease_donation_count();
567
			}
568
		}
569
570
		/**
571
		 * Fires after removing payments from donors.
572
		 *
573
		 * @since 1.0
574
		 *
575
		 * @param bool $payment_removed If the removal was successfully.
576
		 * @param int $payment_id Payment id.
577
		 * @param int $donor_id Donor id.
578
		 */
579
		do_action( 'give_donor_post_remove_payment', $payment_removed, $payment_id, $this->id );
580
581
		return $payment_removed;
582
583
	}
584
585
	/**
586
	 * Increase the donation count of a donor.
587
	 *
588
	 * @since  1.0
589
	 * @access public
590
	 *
591
	 * @param  int $count The number to increase by.
592
	 *
593
	 * @return int        The donation count.
594
	 */
595
	public function increase_purchase_count( $count = 1 ) {
596
597
		// Make sure it's numeric and not negative.
598
		if ( ! is_numeric( $count ) || $count != absint( $count ) ) {
599
			return false;
600
		}
601
602
		$new_total = (int) $this->purchase_count + (int) $count;
603
604
		/**
605
		 * Fires before increasing the donor's donation count.
606
		 *
607
		 * @since 1.0
608
		 *
609
		 * @param int $count The number to increase by.
610
		 * @param int $donor_id Donor id.
611
		 */
612
		do_action( 'give_donor_pre_increase_donation_count', $count, $this->id );
613
614
		if ( $this->update( array( 'purchase_count' => $new_total ) ) ) {
615
			$this->purchase_count = $new_total;
616
		}
617
618
		/**
619
		 * Fires after increasing the donor's donation count.
620
		 *
621
		 * @since 1.0
622
		 *
623
		 * @param int $purchase_count Donor donation count.
624
		 * @param int $count The number increased by.
625
		 * @param int $donor_id Donor id.
626
		 */
627
		do_action( 'give_donor_post_increase_donation_count', $this->purchase_count, $count, $this->id );
628
629
		return $this->purchase_count;
630
	}
631
632
	/**
633
	 * Decrease the donor donation count.
634
	 *
635
	 * @since  1.0
636
	 * @access public
637
	 *
638
	 * @param  int $count The amount to decrease by.
639
	 *
640
	 * @return mixed      If successful, the new count, otherwise false.
641
	 */
642
	public function decrease_donation_count( $count = 1 ) {
643
644
		// Make sure it's numeric and not negative
645
		if ( ! is_numeric( $count ) || $count != absint( $count ) ) {
646
			return false;
647
		}
648
649
		$new_total = (int) $this->purchase_count - (int) $count;
650
651
		if ( $new_total < 0 ) {
652
			$new_total = 0;
653
		}
654
655
		/**
656
		 * Fires before decreasing the donor's donation count.
657
		 *
658
		 * @since 1.0
659
		 *
660
		 * @param int $count The number to decrease by.
661
		 * @param int $donor_id Customer id.
662
		 */
663
		do_action( 'give_donor_pre_decrease_donation_count', $count, $this->id );
664
665
		if ( $this->update( array( 'purchase_count' => $new_total ) ) ) {
666
			$this->purchase_count = $new_total;
667
		}
668
669
		/**
670
		 * Fires after decreasing the donor's donation count.
671
		 *
672
		 * @since 1.0
673
		 *
674
		 * @param int $purchase_count Donor's donation count.
675
		 * @param int $count The number decreased by.
676
		 * @param int $donor_id Donor id.
677
		 */
678
		do_action( 'give_donor_post_decrease_donation_count', $this->purchase_count, $count, $this->id );
679
680
		return $this->purchase_count;
681
	}
682
683
	/**
684
	 * Increase the donor's lifetime value.
685
	 *
686
	 * @since  1.0
687
	 * @access public
688
	 *
689
	 * @param  float $value The value to increase by.
690
	 *
691
	 * @return mixed        If successful, the new value, otherwise false.
692
	 */
693
	public function increase_value( $value = 0.00 ) {
694
695
		$new_value = floatval( $this->purchase_value ) + $value;
696
697
		/**
698
		 * Fires before increasing donor lifetime value.
699
		 *
700
		 * @since 1.0
701
		 *
702
		 * @param float $value The value to increase by.
703
		 * @param int $donor_id Customer id.
704
		 */
705
		do_action( 'give_donor_pre_increase_value', $value, $this->id );
706
707
		if ( $this->update( array( 'purchase_value' => $new_value ) ) ) {
708
			$this->purchase_value = $new_value;
709
		}
710
711
		/**
712
		 * Fires after increasing donor lifetime value.
713
		 *
714
		 * @since 1.0
715
		 *
716
		 * @param float $purchase_value Donor's lifetime value.
717
		 * @param float $value The value increased by.
718
		 * @param int $donor_id Donor id.
719
		 */
720
		do_action( 'give_donor_post_increase_value', $this->purchase_value, $value, $this->id );
721
722
		return $this->purchase_value;
723
	}
724
725
	/**
726
	 * Decrease a donor's lifetime value.
727
	 *
728
	 * @since  1.0
729
	 * @access public
730
	 *
731
	 * @param  float $value The value to decrease by.
732
	 *
733
	 * @return mixed        If successful, the new value, otherwise false.
734
	 */
735
	public function decrease_value( $value = 0.00 ) {
736
737
		$new_value = floatval( $this->purchase_value ) - $value;
738
739
		if ( $new_value < 0 ) {
740
			$new_value = 0.00;
741
		}
742
743
		/**
744
		 * Fires before decreasing donor lifetime value.
745
		 *
746
		 * @since 1.0
747
		 *
748
		 * @param float $value The value to decrease by.
749
		 * @param int $donor_id Donor id.
750
		 */
751
		do_action( 'give_donor_pre_decrease_value', $value, $this->id );
752
753
		if ( $this->update( array( 'purchase_value' => $new_value ) ) ) {
754
			$this->purchase_value = $new_value;
755
		}
756
757
		/**
758
		 * Fires after decreasing donor lifetime value.
759
		 *
760
		 * @since 1.0
761
		 *
762
		 * @param float $purchase_value Donor lifetime value.
763
		 * @param float $value The value decreased by.
764
		 * @param int $donor_id Donor id.
765
		 */
766
		do_action( 'give_donor_post_decrease_value', $this->purchase_value, $value, $this->id );
767
768
		return $this->purchase_value;
769
	}
770
771
	/**
772
	 * Decrease/Increase a donor's lifetime value.
773
	 *
774
	 * This function will update donation stat on basis of current amount and new amount donation difference.
775
	 * Difference value can positive or negative. Negative value will decrease user donation stat while positive value increase donation stat.
776
	 *
777
	 * @since  1.0
778
	 * @access public
779
	 *
780
	 * @param  float $curr_amount Current Donation amount.
781
	 * @param  float $new_amount New (changed) Donation amount.
782
	 *
783
	 * @return mixed              If successful, the new donation stat value, otherwise false.
784
	 */
785
	public function update_donation_value( $curr_amount, $new_amount ) {
786
		/**
787
		 * Payment total difference value can be:
788
		 *  zero   (in case amount not change)
789
		 *  or -ve (in case amount decrease)
790
		 *  or +ve (in case amount increase)
791
		 */
792
		$payment_total_diff = $new_amount - $curr_amount;
793
794
		// We do not need to update donation stat if donation did not change.
795
		if ( ! $payment_total_diff ) {
796
			return false;
797
		}
798
799
		if ( $payment_total_diff > 0 ) {
800
			$this->increase_value( $payment_total_diff );
801
		} else {
802
			// Pass payment total difference as +ve value to decrease amount from user lifetime stat.
803
			$this->decrease_value( - $payment_total_diff );
804
		}
805
806
		return $this->purchase_value;
807
	}
808
809
	/**
810
	 * Get the parsed notes for a donor as an array.
811
	 *
812
	 * @since  1.0
813
	 * @access public
814
	 *
815
	 * @param  int $length The number of notes to get.
816
	 * @param  int $paged What note to start at.
817
	 *
818
	 * @return array       The notes requested.
819
	 */
820
	public function get_notes( $length = 20, $paged = 1 ) {
821
822
		$length = is_numeric( $length ) ? $length : 20;
823
		$offset = is_numeric( $paged ) && $paged != 1 ? ( ( absint( $paged ) - 1 ) * $length ) : 0;
824
825
		$all_notes   = $this->get_raw_notes();
826
		$notes_array = array_reverse( array_filter( explode( "\n\n", $all_notes ) ) );
827
828
		$desired_notes = array_slice( $notes_array, $offset, $length );
829
830
		return $desired_notes;
831
832
	}
833
834
	/**
835
	 * Get the total number of notes we have after parsing.
836
	 *
837
	 * @since  1.0
838
	 * @access public
839
	 *
840
	 * @return int The number of notes for the donor.
841
	 */
842
	public function get_notes_count() {
843
844
		$all_notes   = $this->get_raw_notes();
845
		$notes_array = array_reverse( array_filter( explode( "\n\n", $all_notes ) ) );
846
847
		return count( $notes_array );
848
849
	}
850
851
	/**
852
	 * Add a note for the donor.
853
	 *
854
	 * @since  1.0
855
	 * @access public
856
	 *
857
	 * @param  string $note The note to add. Default is empty.
858
	 *
859
	 * @return string|boolean The new note if added successfully, false otherwise.
860
	 */
861
	public function add_note( $note = '' ) {
862
863
		$note = trim( $note );
864
		if ( empty( $note ) ) {
865
			return false;
866
		}
867
868
		$notes = $this->get_raw_notes();
869
870
		if ( empty( $notes ) ) {
871
			$notes = '';
872
		}
873
874
		$note_string = date_i18n( 'F j, Y H:i:s', current_time( 'timestamp' ) ) . ' - ' . $note;
875
		$new_note    = apply_filters( 'give_customer_add_note_string', $note_string );
876
		$notes       .= "\n\n" . $new_note;
877
878
		/**
879
		 * Fires before donor note is added.
880
		 *
881
		 * @since 1.0
882
		 *
883
		 * @param string $new_note New note to add.
884
		 * @param int $donor_id Donor id.
885
		 */
886
		do_action( 'give_donor_pre_add_note', $new_note, $this->id );
887
888
		$updated = $this->update( array( 'notes' => $notes ) );
889
890
		if ( $updated ) {
891
			$this->notes = $this->get_notes();
892
		}
893
894
		/**
895
		 * Fires after donor note added.
896
		 *
897
		 * @since 1.0
898
		 *
899
		 * @param array $donor_notes Donor notes.
900
		 * @param string $new_note New note added.
901
		 * @param int $donor_id Donor id.
902
		 */
903
		do_action( 'give_donor_post_add_note', $this->notes, $new_note, $this->id );
904
905
		// Return the formatted note, so we can test, as well as update any displays
906
		return $new_note;
907
908
	}
909
910
	/**
911
	 * Get the notes column for the donor
912
	 *
913
	 * @since  1.0
914
	 * @access private
915
	 *
916
	 * @return string The Notes for the donor, non-parsed.
917
	 */
918
	private function get_raw_notes() {
919
920
		$all_notes = $this->db->get_column( 'notes', $this->id );
921
922
		return $all_notes;
923
924
	}
925
926
	/**
927
	 * Retrieve a meta field for a donor.
928
	 *
929
	 * @since  1.6
930
	 * @access public
931
	 *
932
	 * @param  string $meta_key The meta key to retrieve. Default is empty.
933
	 * @param  bool $single Whether to return a single value. Default is true.
934
	 *
935
	 * @return mixed            Will be an array if $single is false. Will be value of meta data field if $single is true.
936
	 */
937
	public function get_meta( $meta_key = '', $single = true ) {
938
		return Give()->donor_meta->get_meta( $this->id, $meta_key, $single );
939
	}
940
941
	/**
942
	 * Add a meta data field to a donor.
943
	 *
944
	 * @since  1.6
945
	 * @access public
946
	 *
947
	 * @param  string $meta_key Metadata name. Default is empty.
948
	 * @param  mixed $meta_value Metadata value.
949
	 * @param  bool $unique Optional. Whether the same key should not be added. Default is false.
950
	 *
951
	 * @return bool               False for failure. True for success.
952
	 */
953
	public function add_meta( $meta_key = '', $meta_value, $unique = false ) {
954
		return Give()->donor_meta->add_meta( $this->id, $meta_key, $meta_value, $unique );
955
	}
956
957
	/**
958
	 * Update a meta field based on donor ID.
959
	 *
960
	 * @since  1.6
961
	 * @access public
962
	 *
963
	 * @param  string $meta_key Metadata key. Default is empty.
964
	 * @param  mixed $meta_value Metadata value.
965
	 * @param  mixed $prev_value Optional. Previous value to check before removing. Default is empty.
966
	 *
967
	 * @return bool               False on failure, true if success.
968
	 */
969
	public function update_meta( $meta_key = '', $meta_value, $prev_value = '' ) {
970
		return Give()->donor_meta->update_meta( $this->id, $meta_key, $meta_value, $prev_value );
971
	}
972
973
	/**
974
	 * Remove metadata matching criteria from a donor.
975
	 *
976
	 * @since  1.6
977
	 * @access public
978
	 *
979
	 * @param  string $meta_key Metadata name. Default is empty.
980
	 * @param  mixed $meta_value Optional. Metadata value. Default is empty.
981
	 *
982
	 * @return bool               False for failure. True for success.
983
	 */
984
	public function delete_meta( $meta_key = '', $meta_value = '' ) {
985
		return Give()->donor_meta->delete_meta( $this->id, $meta_key, $meta_value );
986
	}
987
988
	/**
989
	 * Sanitize the data for update/create
990
	 *
991
	 * @since  1.0
992
	 * @access private
993
	 *
994
	 * @param  array $data The data to sanitize.
995
	 *
996
	 * @return array       The sanitized data, based off column defaults.
997
	 */
998
	private function sanitize_columns( $data ) {
999
1000
		$columns        = $this->db->get_columns();
1001
		$default_values = $this->db->get_column_defaults();
1002
1003
		foreach ( $columns as $key => $type ) {
1004
1005
			// Only sanitize data that we were provided
1006
			if ( ! array_key_exists( $key, $data ) ) {
1007
				continue;
1008
			}
1009
1010
			switch ( $type ) {
1011
1012
				case '%s':
1013
					if ( 'email' == $key ) {
1014
						$data[ $key ] = sanitize_email( $data[ $key ] );
1015
					} elseif ( 'notes' == $key ) {
1016
						$data[ $key ] = strip_tags( $data[ $key ] );
1017
					} else {
1018
						$data[ $key ] = sanitize_text_field( $data[ $key ] );
1019
					}
1020
					break;
1021
1022
				case '%d':
1023
					if ( ! is_numeric( $data[ $key ] ) || (int) $data[ $key ] !== absint( $data[ $key ] ) ) {
1024
						$data[ $key ] = $default_values[ $key ];
1025
					} else {
1026
						$data[ $key ] = absint( $data[ $key ] );
1027
					}
1028
					break;
1029
1030
				case '%f':
1031
					// Convert what was given to a float
1032
					$value = floatval( $data[ $key ] );
1033
1034
					if ( ! is_float( $value ) ) {
1035
						$data[ $key ] = $default_values[ $key ];
1036
					} else {
1037
						$data[ $key ] = $value;
1038
					}
1039
					break;
1040
1041
				default:
1042
					$data[ $key ] = sanitize_text_field( $data[ $key ] );
1043
					break;
1044
1045
			}
1046
		}
1047
1048
		return $data;
1049
	}
1050
1051
	/**
1052
	 * Attach an email to the donor
1053
	 *
1054
	 * @since  1.7
1055
	 * @access public
1056
	 *
1057
	 * @param  string $email The email address to attach to the donor
1058
	 * @param  bool $primary Allows setting the email added as the primary
1059
	 *
1060
	 * @return bool            If the email was added successfully
1061
	 */
1062
	public function add_email( $email = '', $primary = false ) {
1063
		if ( ! is_email( $email ) ) {
1064
			return false;
1065
		}
1066
		$existing = new Give_Donor( $email );
1067
1068
		if ( $existing->id > 0 ) {
1069
			// Email address already belongs to another donor
1070
			return false;
1071
		}
1072
1073
		if ( email_exists( $email ) ) {
1074
			$user = get_user_by( 'email', $email );
1075
			if ( $user->ID != $this->user_id ) {
1076
				return false;
1077
			}
1078
		}
1079
1080
		do_action( 'give_donor_pre_add_email', $email, $this->id, $this );
1081
1082
		// Add is used to ensure duplicate emails are not added
1083
		$ret = (bool) $this->add_meta( 'additional_email', $email );
1084
1085
		do_action( 'give_donor_post_add_email', $email, $this->id, $this );
1086
1087
		if ( $ret && true === $primary ) {
1088
			$this->set_primary_email( $email );
1089
		}
1090
1091
		return $ret;
1092
	}
1093
1094
	/**
1095
	 * Remove an email from the donor.
1096
	 *
1097
	 * @since  1.7
1098
	 * @access public
1099
	 *
1100
	 * @param  string $email The email address to remove from the donor.
1101
	 *
1102
	 * @return bool          If the email was removed successfully.
1103
	 */
1104
	public function remove_email( $email = '' ) {
1105
		if ( ! is_email( $email ) ) {
1106
			return false;
1107
		}
1108
1109
		do_action( 'give_donor_pre_remove_email', $email, $this->id, $this );
1110
1111
		$ret = (bool) $this->delete_meta( 'additional_email', $email );
1112
1113
		do_action( 'give_donor_post_remove_email', $email, $this->id, $this );
1114
1115
		return $ret;
1116
	}
1117
1118
	/**
1119
	 * Set an email address as the donor's primary email.
1120
	 *
1121
	 * This will move the donor's previous primary email to an additional email.
1122
	 *
1123
	 * @since  1.7
1124
	 * @access public
1125
	 *
1126
	 * @param  string $new_primary_email The email address to remove from the donor.
1127
	 *
1128
	 * @return bool                      If the email was set as primary successfully.
1129
	 */
1130
	public function set_primary_email( $new_primary_email = '' ) {
1131
		if ( ! is_email( $new_primary_email ) ) {
1132
			return false;
1133
		}
1134
1135
		do_action( 'give_donor_pre_set_primary_email', $new_primary_email, $this->id, $this );
1136
1137
		$existing = new Give_Donor( $new_primary_email );
1138
1139
		if ( $existing->id > 0 && (int) $existing->id !== (int) $this->id ) {
1140
			// This email belongs to another donor.
1141
			return false;
1142
		}
1143
1144
		$old_email = $this->email;
1145
1146
		// Update donor record with new email.
1147
		$update = $this->update( array( 'email' => $new_primary_email ) );
1148
1149
		// Remove new primary from list of additional emails.
1150
		$remove = $this->remove_email( $new_primary_email );
1151
1152
		// Add old email to additional emails list.
1153
		$add = $this->add_email( $old_email );
1154
1155
		$ret = $update && $remove && $add;
1156
1157
		if ( $ret ) {
1158
			$this->email = $new_primary_email;
1159
		}
1160
1161
		do_action( 'give_donor_post_set_primary_email', $new_primary_email, $this->id, $this );
1162
1163
		return $ret;
1164
	}
1165
1166
	/**
1167
	 * Add donor address
1168
	 *
1169
	 * @since  2.0
1170
	 * @access public
1171
	 *
1172
	 * @param string $address_type
1173
	 * @param array  $address {
1174
	 *
1175
	 * @type string  $address2
1176
	 * @type string city
1177
	 * @type string zip
1178
	 * @type string state
1179
	 * @type string country
1180
	 * }
1181
	 *
1182
	 * @return void
1183
	 */
1184
	public function add_address( $address_type, $address ) {
1185
		$is_address_empty = true;
1186
1187
		// Address ready to process even if only one value set.
1188
		foreach ( $address as $value ) {
1189
			if( ! empty( $value ) ) {
0 ignored issues
show
Space after opening control structure is required
Loading history...
No space before opening parenthesis is prohibited
Loading history...
1190
				$is_address_empty = false;
1191
				break;
1192
			}
1193
		}
1194
1195
		// Bailout.
1196
		if ( empty( $address_type ) || $is_address_empty || ! $this->id ) {
1197
			return;
1198
		}
1199
1200
		$is_multi_address = ( false !== strpos( $address_type, '[]' ) );
1201
		$address_type  = $is_multi_address ?
1202
			str_replace( '[]', '', $address_type ) :
1203
			$address_type;
1204
1205
		// Bailout: do not save duplicate orders
1206
		if( $this->is_address_exist( $address_type, $address ) ) {
0 ignored issues
show
Space after opening control structure is required
Loading history...
No space before opening parenthesis is prohibited
Loading history...
1207
			return;
1208
		}
1209
1210
		// Set default address.
1211
		$address = wp_parse_args(
1212
			$address,
1213
			array(
1214
				'line1' => '',
1215
				'line2' => '',
1216
				'city'     => '',
1217
				'state'    => '',
1218
				'country'  => '',
1219
				'zip'      => '',
1220
			)
1221
		);
1222
1223
		// Set meta key prefix.
1224
		global $wpdb;
1225
		$meta_key_prefix = "_give_donor_address_{$address_type}_{address_name}";
1226
		$meta_type = Give()->donor_meta->meta_type;
1227
1228
		if ( $is_multi_address ) {
1229
			$address_count = $wpdb->get_var(
0 ignored issues
show
Usage of a direct database call is discouraged.
Loading history...
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
1230
				$wpdb->prepare(
1231
					"
1232
					SELECT COUNT(*) FROM {$wpdb->donormeta}
1233
					WHERE meta_key
1234
					LIKE '%%%s%%'
1235
					AND {$meta_type}_id=%d
1236
					",
1237
					"_give_donor_address_{$address_type}_address1",
1238
					$this->id
1239
				)
1240
			);
1241
1242
			$address_count   = $address_count ? $address_count : 0;
1243
			$meta_key_prefix = "_give_donor_address_{$address_type}_{address_name}_{$address_count}";
1244
		}
1245
1246
		// Save donor address.
1247
		foreach ( $address as $type => $value ) {
1248
			$meta_key = str_replace( '{address_name}', $type, $meta_key_prefix );
1249
			switch ( $type ) {
1250
				case 'line1':
1251
					$meta_key = str_replace( '{address_name}', 'address1', $meta_key_prefix );
1252
					Give()->donor_meta->update_meta( $this->id, $meta_key, $value );
1253
					break;
1254
1255
				case 'line2':
1256
					$meta_key = str_replace( '{address_name}', 'address2', $meta_key_prefix );
1257
					Give()->donor_meta->update_meta( $this->id, $meta_key, $value );
1258
					break;
1259
1260
				default:
1261
					Give()->donor_meta->update_meta( $this->id, $meta_key, $value );
1262
			}
1263
		}
1264
1265
		$this->setup_address();
1266
	}
1267
1268
1269
	/**
1270
	 * Check if donor already has current address
1271
	 *
1272
	 * @since 2.0
1273
	 * @access public
1274
	 *
1275
	 * @param string $current_address_type
1276
	 * @param array $current_address
1277
	 *
1278
	 * @return bool|null
1279
	 */
1280
	public function is_address_exist( $current_address_type, $current_address ) {
1281
		$status = false;
1282
0 ignored issues
show
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
1283
1284
		// Bailout.
1285
		if( empty( $current_address_type ) || empty( $current_address ) ) {
0 ignored issues
show
Space after opening control structure is required
Loading history...
No space before opening parenthesis is prohibited
Loading history...
1286
			return null;
1287
		}
1288
1289
		// Bailout.
1290
		if( empty( $this->address ) ) {
0 ignored issues
show
Space after opening control structure is required
Loading history...
No space before opening parenthesis is prohibited
Loading history...
1291
			return $status;
1292
		}
1293
0 ignored issues
show
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
1294
1295
		// Compare address.
1296
		foreach ( $this->address as $address_type => $saved_address ) {
1297
			if( $current_address_type !== $address_type ) {
0 ignored issues
show
Space after opening control structure is required
Loading history...
No space before opening parenthesis is prohibited
Loading history...
1298
				continue;
1299
1300
			} elseif( empty( $saved_address[0] ) || ! is_array( $saved_address[0] ) ) {
0 ignored issues
show
Space after opening control structure is required
Loading history...
No space before opening parenthesis is prohibited
Loading history...
1301
				$status = ( $current_address == $saved_address );
1302
1303
			} else{
0 ignored issues
show
Space after opening control structure is required
Loading history...
No space before opening parenthesis is prohibited
Loading history...
1304
				foreach ( $saved_address as $address ) {
1305
					if( empty( $saved_address ) ) {
0 ignored issues
show
Space after opening control structure is required
Loading history...
No space before opening parenthesis is prohibited
Loading history...
1306
						continue;
1307
					}
1308
1309
					$status = ( $current_address == $address );
1310
1311
					// Exit loop immediately if address exist.
1312
					if( $status ) {
0 ignored issues
show
Space after opening control structure is required
Loading history...
No space before opening parenthesis is prohibited
Loading history...
1313
						break;
1314
					}
1315
				}
1316
			}
1317
1318
			// Exit loop immediately if address exist.
1319
			if( $status ) {
0 ignored issues
show
Space after opening control structure is required
Loading history...
No space before opening parenthesis is prohibited
Loading history...
1320
				break;
1321
			}
1322
		}
1323
1324
		return $status;
1325
	}
1326
}
1327