Test Failed
Push — issue/2900 ( f2122b )
by Ravinder
07:32
created

Give_Donor::does_address_exist()   D

Complexity

Conditions 10
Paths 7

Size

Total Lines 41
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 19
nc 7
nop 2
dl 0
loc 41
rs 4.8196
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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    array
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 = array();
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 int|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 = Give()->donors;
155
156
		if ( false === $_id_or_email || ( is_numeric( $_id_or_email ) && (int) $_id_or_email !== absint( $_id_or_email ) ) ) {
157
			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...
158
		}
159
160
		$by_user_id = is_bool( $by_user_id ) ? $by_user_id : false;
161
162
		if ( is_numeric( $_id_or_email ) ) {
163
			$field = $by_user_id ? 'user_id' : 'id';
164
		} else {
165
			$field = 'email';
166
		}
167
168
		$donor = $this->db->get_donor_by( $field, $_id_or_email );
169
170
		if ( empty( $donor ) || ! is_object( $donor ) ) {
171
			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...
172
		}
173
174
		$this->setup_donor( $donor );
175
176
	}
177
178
	/**
179
	 * Setup Donor
180
	 *
181
	 * Set donor variables.
182
	 *
183
	 * @since  1.0
184
	 * @access private
185
	 *
186
	 * @param  object $donor The Donor Object.
187
	 *
188
	 * @return bool             If the setup was successful or not.
189
	 */
190
	private function setup_donor( $donor ) {
191
192
		if ( ! is_object( $donor ) ) {
193
			return false;
194
		}
195
196
		// Get cached donors.
197
		$donor_vars = Give_Cache::get_group( $donor->id, 'give-donors' );
198
199
		if ( is_null( $donor_vars ) ) {
200
			foreach ( $donor as $key => $value ) {
201
202
				switch ( $key ) {
203
204
					case 'notes':
205
						$this->$key = $this->get_notes();
206
						break;
207
208
					default:
209
						$this->$key = $value;
210
						break;
211
212
				}
213
			}
214
215
			// Get donor's all email including primary email.
216
			$this->emails = (array) $this->get_meta( 'additional_email', false );
217
			$this->emails = array( 'primary' => $this->email ) + $this->emails;
218
219
			$this->setup_address();
220
221
			Give_Cache::set_group( $donor->id, get_object_vars( $this ), 'give-donors' );
222
		} else {
223
			foreach ( $donor_vars as $donor_var => $value ) {
0 ignored issues
show
Bug introduced by
The expression $donor_vars of type object|integer|double|string|array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
224
				$this->$donor_var = $value;
225
			}
226
		}
227
228
		// Donor ID and email are the only things that are necessary, make sure they exist.
229
		if ( ! empty( $this->id ) && ! empty( $this->email ) ) {
230
			return true;
231
		}
232
233
		return false;
234
235
	}
236
237
238
	/**
239
	 * Setup donor address.
240
	 *
241
	 * @since  2.0
242
	 * @access public
243
	 */
244
	public function setup_address() {
245
		global $wpdb;
246
		$meta_type = Give()->donor_meta->meta_type;
247
248
		$addresses = $wpdb->get_results( $wpdb->prepare( "
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
249
				SELECT meta_key, meta_value FROM {$wpdb->donormeta}
250
				WHERE meta_key
251
				LIKE '%%%s%%'
252
				AND {$meta_type}_id=%d
253
				", 'give_donor_address', $this->id ), ARRAY_N );
254
255
		if ( empty( $addresses ) ) {
256
			return $this->address;
257
		}
258
259
		foreach ( $addresses as $address ) {
260
			$address[0] = str_replace( '_give_donor_address_', '', $address[0] );
261
			$address[0] = explode( '_', $address[0] );
262
263
			if ( 3 === count( $address[0] ) ) {
264
				$this->address[ $address[0][0] ][ $address[0][2] ][ $address[0][1] ] = $address[1];
265
			} else {
266
				$this->address[ $address[0][0] ][ $address[0][1] ] = $address[1];
267
			}
268
		}
269
	}
270
271
	/**
272
	 * Magic __get function to dispatch a call to retrieve a private property.
273
	 *
274
	 * @since  1.0
275
	 * @access public
276
	 *
277
	 * @param $key
278
	 *
279
	 * @return mixed|\WP_Error
280
	 */
281 View Code Duplication
	public function __get( $key ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
282
283
		if ( method_exists( $this, 'get_' . $key ) ) {
284
285
			return call_user_func( array( $this, 'get_' . $key ) );
286
287
		} else {
288
289
			/* translators: %s: property key */
290
			return new WP_Error( 'give-donor-invalid-property', sprintf( esc_html__( 'Can\'t get property %s.', 'give' ), $key ) );
291
292
		}
293
294
	}
295
296
	/**
297
	 * Creates a donor.
298
	 *
299
	 * @since  1.0
300
	 * @access public
301
	 *
302
	 * @param  array $data Array of attributes for a donor.
303
	 *
304
	 * @return bool|int    False if not a valid creation, donor ID if user is found or valid creation.
305
	 */
306
	public function create( $data = array() ) {
307
308
		if ( $this->id != 0 || empty( $data ) ) {
0 ignored issues
show
introduced by
Found "!= 0". Use Yoda Condition checks, you must
Loading history...
309
			return false;
310
		}
311
312
		$defaults = array(
313
			'payment_ids' => '',
314
		);
315
316
		$args = wp_parse_args( $data, $defaults );
317
		$args = $this->sanitize_columns( $args );
318
319
		if ( empty( $args['email'] ) || ! is_email( $args['email'] ) ) {
320
			return false;
321
		}
322
323 View Code Duplication
		if ( ! empty( $args['payment_ids'] ) && is_array( $args['payment_ids'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
324
			$args['payment_ids'] = implode( ',', array_unique( array_values( $args['payment_ids'] ) ) );
325
		}
326
327
		/**
328
		 * Fires before creating donors.
329
		 *
330
		 * @since 1.0
331
		 *
332
		 * @param array $args Donor attributes.
333
		 */
334
		do_action( 'give_donor_pre_create', $args );
335
336
		$created = false;
337
338
		// The DB class 'add' implies an update if the donor being asked to be created already exists
339 View Code Duplication
		if ( $this->db->add( $data ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
340
341
			// We've successfully added/updated the donor, reset the class vars with the new data
342
			$donor = $this->db->get_donor_by( 'email', $args['email'] );
343
344
			// Setup the donor data with the values from DB
345
			$this->setup_donor( $donor );
346
347
			$created = $this->id;
348
		}
349
350
		/**
351
		 * Fires after creating donors.
352
		 *
353
		 * @since 1.0
354
		 *
355
		 * @param bool|int $created False if not a valid creation, donor ID if user is found or valid creation.
356
		 * @param array    $args    Customer attributes.
357
		 */
358
		do_action( 'give_donor_post_create', $created, $args );
359
360
		return $created;
361
362
	}
363
364
	/**
365
	 * Updates a donor record.
366
	 *
367
	 * @since  1.0
368
	 * @access public
369
	 *
370
	 * @param  array $data Array of data attributes for a donor (checked via whitelist).
371
	 *
372
	 * @return bool        If the update was successful or not.
373
	 */
374
	public function update( $data = array() ) {
375
376
		if ( empty( $data ) ) {
377
			return false;
378
		}
379
380
		$data = $this->sanitize_columns( $data );
381
382
		/**
383
		 * Fires before updating donors.
384
		 *
385
		 * @since 1.0
386
		 *
387
		 * @param int   $donor_id Donor id.
388
		 * @param array $data     Donor attributes.
389
		 */
390
		do_action( 'give_donor_pre_update', $this->id, $data );
391
392
		$updated = false;
393
394 View Code Duplication
		if ( $this->db->update( $this->id, $data ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
395
396
			$donor = $this->db->get_donor_by( 'id', $this->id );
397
398
			$this->setup_donor( $donor );
399
400
			$updated = true;
401
		}
402
403
		/**
404
		 * Fires after updating donors.
405
		 *
406
		 * @since 1.0
407
		 *
408
		 * @param bool  $updated  If the update was successful or not.
409
		 * @param int   $donor_id Donor id.
410
		 * @param array $data     Donor attributes.
411
		 */
412
		do_action( 'give_donor_post_update', $updated, $this->id, $data );
413
414
		return $updated;
415
	}
416
417
	/**
418
	 * Attach Payment
419
	 *
420
	 * Attach payment to the donor then triggers increasing stats.
421
	 *
422
	 * @since  1.0
423
	 * @access public
424
	 *
425
	 * @param  int  $payment_id   The payment ID to attach to the donor.
426
	 * @param  bool $update_stats For backwards compatibility, if we should increase the stats or not.
427
	 *
428
	 * @return bool            If the attachment was successfully.
429
	 */
430
	public function attach_payment( $payment_id = 0, $update_stats = true ) {
431
432
		if ( empty( $payment_id ) ) {
433
			return false;
434
		}
435
436
		if ( empty( $this->payment_ids ) ) {
437
438
			$new_payment_ids = $payment_id;
439
440
		} else {
441
442
			$payment_ids = array_map( 'absint', explode( ',', $this->payment_ids ) );
443
444
			if ( in_array( $payment_id, $payment_ids ) ) {
445
				$update_stats = false;
446
			}
447
448
			$payment_ids[] = $payment_id;
449
450
			$new_payment_ids = implode( ',', array_unique( array_values( $payment_ids ) ) );
451
452
		}
453
454
		/**
455
		 * Fires before attaching payments to donors.
456
		 *
457
		 * @since 1.0
458
		 *
459
		 * @param int $payment_id Payment id.
460
		 * @param int $donor_id   Donor id.
461
		 */
462
		do_action( 'give_donor_pre_attach_payment', $payment_id, $this->id );
463
464
		$payment_added = $this->update( array( 'payment_ids' => $new_payment_ids ) );
465
466
		if ( $payment_added ) {
467
468
			$this->payment_ids = $new_payment_ids;
0 ignored issues
show
Documentation Bug introduced by
It seems like $new_payment_ids can also be of type integer. However, the property $payment_ids is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
469
470
			// We added this payment successfully, increment the stats
471
			if ( $update_stats ) {
472
				$payment_amount = give_donation_amount( $payment_id, array( 'type' => 'stats' ) );
473
474
				if ( ! empty( $payment_amount ) ) {
475
					$this->increase_value( $payment_amount );
476
				}
477
478
				$this->increase_purchase_count();
479
			}
480
		}
481
482
		/**
483
		 * Fires after attaching payments to the donor.
484
		 *
485
		 * @since 1.0
486
		 *
487
		 * @param bool $payment_added If the attachment was successfully.
488
		 * @param int  $payment_id    Payment id.
489
		 * @param int  $donor_id      Donor id.
490
		 */
491
		do_action( 'give_donor_post_attach_payment', $payment_added, $payment_id, $this->id );
492
493
		return $payment_added;
494
	}
495
496
	/**
497
	 * Remove Payment
498
	 *
499
	 * Remove a payment from this donor, then triggers reducing stats.
500
	 *
501
	 * @since  1.0
502
	 * @access public
503
	 *
504
	 * @param  int  $payment_id   The Payment ID to remove.
505
	 * @param  bool $update_stats For backwards compatibility, if we should increase the stats or not.
506
	 *
507
	 * @return boolean               If the removal was successful.
508
	 */
509
	public function remove_payment( $payment_id = 0, $update_stats = true ) {
510
511
		if ( empty( $payment_id ) ) {
512
			return false;
513
		}
514
515
		$payment = new Give_Payment( $payment_id );
516
517
		if ( 'publish' !== $payment->status && 'revoked' !== $payment->status ) {
518
			$update_stats = false;
519
		}
520
521
		$new_payment_ids = '';
522
523
		if ( ! empty( $this->payment_ids ) ) {
524
525
			$payment_ids = array_map( 'absint', explode( ',', $this->payment_ids ) );
526
527
			$pos = array_search( $payment_id, $payment_ids );
528
			if ( false === $pos ) {
529
				return false;
530
			}
531
532
			unset( $payment_ids[ $pos ] );
533
			$payment_ids = array_filter( $payment_ids );
534
535
			$new_payment_ids = implode( ',', array_unique( array_values( $payment_ids ) ) );
536
537
		}
538
539
		/**
540
		 * Fires before removing payments from customers.
541
		 *
542
		 * @since 1.0
543
		 *
544
		 * @param int $payment_id Payment id.
545
		 * @param int $donor_id   Customer id.
546
		 */
547
		do_action( 'give_donor_pre_remove_payment', $payment_id, $this->id );
548
549
		$payment_removed = $this->update( array( 'payment_ids' => $new_payment_ids ) );
550
551
		if ( $payment_removed ) {
552
553
			$this->payment_ids = $new_payment_ids;
554
555
			if ( $update_stats ) {
556
				// We removed this payment successfully, decrement the stats
557
				$payment_amount = give_donation_amount( $payment_id );
558
559
				if ( ! empty( $payment_amount ) ) {
560
					$this->decrease_value( $payment_amount );
561
				}
562
563
				$this->decrease_donation_count();
564
			}
565
		}
566
567
		/**
568
		 * Fires after removing payments from donors.
569
		 *
570
		 * @since 1.0
571
		 *
572
		 * @param bool $payment_removed If the removal was successfully.
573
		 * @param int  $payment_id      Payment id.
574
		 * @param int  $donor_id        Donor id.
575
		 */
576
		do_action( 'give_donor_post_remove_payment', $payment_removed, $payment_id, $this->id );
577
578
		return $payment_removed;
579
580
	}
581
582
	/**
583
	 * Increase the donation count of a donor.
584
	 *
585
	 * @since  1.0
586
	 * @access public
587
	 *
588
	 * @param  int $count The number to increase by.
589
	 *
590
	 * @return int        The donation count.
591
	 */
592
	public function increase_purchase_count( $count = 1 ) {
593
594
		// Make sure it's numeric and not negative.
595
		if ( ! is_numeric( $count ) || $count != absint( $count ) ) {
596
			return false;
597
		}
598
599
		$new_total = (int) $this->purchase_count + (int) $count;
600
601
		/**
602
		 * Fires before increasing the donor's donation count.
603
		 *
604
		 * @since 1.0
605
		 *
606
		 * @param int $count    The number to increase by.
607
		 * @param int $donor_id Donor id.
608
		 */
609
		do_action( 'give_donor_pre_increase_donation_count', $count, $this->id );
610
611
		if ( $this->update( array( 'purchase_count' => $new_total ) ) ) {
612
			$this->purchase_count = $new_total;
613
		}
614
615
		/**
616
		 * Fires after increasing the donor's donation count.
617
		 *
618
		 * @since 1.0
619
		 *
620
		 * @param int $purchase_count Donor donation count.
621
		 * @param int $count          The number increased by.
622
		 * @param int $donor_id       Donor id.
623
		 */
624
		do_action( 'give_donor_post_increase_donation_count', $this->purchase_count, $count, $this->id );
625
626
		return $this->purchase_count;
627
	}
628
629
	/**
630
	 * Decrease the donor donation count.
631
	 *
632
	 * @since  1.0
633
	 * @access public
634
	 *
635
	 * @param  int $count The amount to decrease by.
636
	 *
637
	 * @return mixed      If successful, the new count, otherwise false.
638
	 */
639
	public function decrease_donation_count( $count = 1 ) {
640
641
		// Make sure it's numeric and not negative
642
		if ( ! is_numeric( $count ) || $count != absint( $count ) ) {
643
			return false;
644
		}
645
646
		$new_total = (int) $this->purchase_count - (int) $count;
647
648
		if ( $new_total < 0 ) {
649
			$new_total = 0;
650
		}
651
652
		/**
653
		 * Fires before decreasing the donor's donation count.
654
		 *
655
		 * @since 1.0
656
		 *
657
		 * @param int $count    The number to decrease by.
658
		 * @param int $donor_id Customer id.
659
		 */
660
		do_action( 'give_donor_pre_decrease_donation_count', $count, $this->id );
661
662
		if ( $this->update( array( 'purchase_count' => $new_total ) ) ) {
663
			$this->purchase_count = $new_total;
664
		}
665
666
		/**
667
		 * Fires after decreasing the donor's donation count.
668
		 *
669
		 * @since 1.0
670
		 *
671
		 * @param int $purchase_count Donor's donation count.
672
		 * @param int $count          The number decreased by.
673
		 * @param int $donor_id       Donor id.
674
		 */
675
		do_action( 'give_donor_post_decrease_donation_count', $this->purchase_count, $count, $this->id );
676
677
		return $this->purchase_count;
678
	}
679
680
	/**
681
	 * Increase the donor's lifetime value.
682
	 *
683
	 * @since  1.0
684
	 * @access public
685
	 *
686
	 * @param  float $value The value to increase by.
687
	 *
688
	 * @return mixed        If successful, the new value, otherwise false.
689
	 */
690
	public function increase_value( $value = 0.00 ) {
691
692
		$new_value = floatval( $this->purchase_value ) + $value;
693
694
		/**
695
		 * Fires before increasing donor lifetime value.
696
		 *
697
		 * @since 1.0
698
		 *
699
		 * @param float $value    The value to increase by.
700
		 * @param int   $donor_id Customer id.
701
		 */
702
		do_action( 'give_donor_pre_increase_value', $value, $this->id );
703
704
		if ( $this->update( array( 'purchase_value' => $new_value ) ) ) {
705
			$this->purchase_value = $new_value;
0 ignored issues
show
Documentation Bug introduced by
The property $purchase_value was declared of type integer, but $new_value is of type double. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
706
		}
707
708
		/**
709
		 * Fires after increasing donor lifetime value.
710
		 *
711
		 * @since 1.0
712
		 *
713
		 * @param float $purchase_value Donor's lifetime value.
714
		 * @param float $value          The value increased by.
715
		 * @param int   $donor_id       Donor id.
716
		 */
717
		do_action( 'give_donor_post_increase_value', $this->purchase_value, $value, $this->id );
718
719
		return $this->purchase_value;
720
	}
721
722
	/**
723
	 * Decrease a donor's lifetime value.
724
	 *
725
	 * @since  1.0
726
	 * @access public
727
	 *
728
	 * @param  float $value The value to decrease by.
729
	 *
730
	 * @return mixed        If successful, the new value, otherwise false.
731
	 */
732
	public function decrease_value( $value = 0.00 ) {
733
734
		$new_value = floatval( $this->purchase_value ) - $value;
735
736
		if ( $new_value < 0 ) {
737
			$new_value = 0.00;
738
		}
739
740
		/**
741
		 * Fires before decreasing donor lifetime value.
742
		 *
743
		 * @since 1.0
744
		 *
745
		 * @param float $value    The value to decrease by.
746
		 * @param int   $donor_id Donor id.
747
		 */
748
		do_action( 'give_donor_pre_decrease_value', $value, $this->id );
749
750
		if ( $this->update( array( 'purchase_value' => $new_value ) ) ) {
751
			$this->purchase_value = $new_value;
0 ignored issues
show
Documentation Bug introduced by
The property $purchase_value was declared of type integer, but $new_value is of type double. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
752
		}
753
754
		/**
755
		 * Fires after decreasing donor lifetime value.
756
		 *
757
		 * @since 1.0
758
		 *
759
		 * @param float $purchase_value Donor lifetime value.
760
		 * @param float $value          The value decreased by.
761
		 * @param int   $donor_id       Donor id.
762
		 */
763
		do_action( 'give_donor_post_decrease_value', $this->purchase_value, $value, $this->id );
764
765
		return $this->purchase_value;
766
	}
767
768
	/**
769
	 * Decrease/Increase a donor's lifetime value.
770
	 *
771
	 * This function will update donation stat on basis of current amount and new amount donation difference.
772
	 * Difference value can positive or negative. Negative value will decrease user donation stat while positive value
773
	 * increase donation stat.
774
	 *
775
	 * @since  1.0
776
	 * @access public
777
	 *
778
	 * @param  float $curr_amount Current Donation amount.
779
	 * @param  float $new_amount  New (changed) Donation amount.
780
	 *
781
	 * @return mixed              If successful, the new donation stat value, otherwise false.
782
	 */
783
	public function update_donation_value( $curr_amount, $new_amount ) {
784
		/**
785
		 * Payment total difference value can be:
786
		 *  zero   (in case amount not change)
787
		 *  or -ve (in case amount decrease)
788
		 *  or +ve (in case amount increase)
789
		 */
790
		$payment_total_diff = $new_amount - $curr_amount;
791
792
		// We do not need to update donation stat if donation did not change.
793
		if ( ! $payment_total_diff ) {
794
			return false;
795
		}
796
797
		if ( $payment_total_diff > 0 ) {
798
			$this->increase_value( $payment_total_diff );
799
		} else {
800
			// Pass payment total difference as +ve value to decrease amount from user lifetime stat.
801
			$this->decrease_value( - $payment_total_diff );
802
		}
803
804
		return $this->purchase_value;
805
	}
806
807
	/**
808
	 * Get the parsed notes for a donor as an array.
809
	 *
810
	 * @since  1.0
811
	 * @access public
812
	 *
813
	 * @param  int $length The number of notes to get.
814
	 * @param  int $paged  What note to start at.
815
	 *
816
	 * @return array       The notes requested.
817
	 */
818
	public function get_notes( $length = 20, $paged = 1 ) {
819
820
		$length = is_numeric( $length ) ? $length : 20;
821
		$offset = is_numeric( $paged ) && $paged != 1 ? ( ( absint( $paged ) - 1 ) * $length ) : 0;
822
823
		$all_notes   = $this->get_raw_notes();
824
		$notes_array = array_reverse( array_filter( explode( "\n\n", $all_notes ) ) );
825
826
		$desired_notes = array_slice( $notes_array, $offset, $length );
827
828
		return $desired_notes;
829
830
	}
831
832
	/**
833
	 * Get the total number of notes we have after parsing.
834
	 *
835
	 * @since  1.0
836
	 * @access public
837
	 *
838
	 * @return int The number of notes for the donor.
839
	 */
840
	public function get_notes_count() {
841
842
		$all_notes   = $this->get_raw_notes();
843
		$notes_array = array_reverse( array_filter( explode( "\n\n", $all_notes ) ) );
844
845
		return count( $notes_array );
846
847
	}
848
849
	/**
850
	 * Get the total donation amount.
851
	 *
852
	 * @since 1.8.17
853
	 *
854
	 * @param array $args Pass any additional data.
855
	 *
856
	 * @return string|float
857
	 */
858
	public function get_total_donation_amount( $args = array() ) {
859
860
		/**
861
		 * Filter total donation amount.
862
		 *
863
		 * @since 1.8.17
864
		 *
865
		 * @param string|float $purchase_value Donor Purchase value.
866
		 * @param integer      $donor_id       Donor ID.
867
		 * @param array        $args           Pass additional data.
868
		 */
869
		return apply_filters( 'give_get_total_donation_amount', $this->purchase_value, $this->id, $args );
870
	}
871
872
	/**
873
	 * Add a note for the donor.
874
	 *
875
	 * @since  1.0
876
	 * @access public
877
	 *
878
	 * @param  string $note The note to add. Default is empty.
879
	 *
880
	 * @return string|boolean The new note if added successfully, false otherwise.
881
	 */
882
	public function add_note( $note = '' ) {
883
884
		$note = trim( $note );
885
		if ( empty( $note ) ) {
886
			return false;
887
		}
888
889
		$notes = $this->get_raw_notes();
890
891
		if ( empty( $notes ) ) {
892
			$notes = '';
893
		}
894
895
		$note_string = date_i18n( 'F j, Y H:i:s', current_time( 'timestamp' ) ) . ' - ' . $note;
896
		$new_note    = apply_filters( 'give_customer_add_note_string', $note_string );
897
		$notes       .= "\n\n" . $new_note;
898
899
		/**
900
		 * Fires before donor note is added.
901
		 *
902
		 * @since 1.0
903
		 *
904
		 * @param string $new_note New note to add.
905
		 * @param int    $donor_id Donor id.
906
		 */
907
		do_action( 'give_donor_pre_add_note', $new_note, $this->id );
908
909
		$updated = $this->update( array( 'notes' => $notes ) );
910
911
		if ( $updated ) {
912
			$this->notes = $this->get_notes();
913
		}
914
915
		/**
916
		 * Fires after donor note added.
917
		 *
918
		 * @since 1.0
919
		 *
920
		 * @param array  $donor_notes Donor notes.
921
		 * @param string $new_note    New note added.
922
		 * @param int    $donor_id    Donor id.
923
		 */
924
		do_action( 'give_donor_post_add_note', $this->notes, $new_note, $this->id );
925
926
		// Return the formatted note, so we can test, as well as update any displays
927
		return $new_note;
928
929
	}
930
931
	/**
932
	 * Get the notes column for the donor
933
	 *
934
	 * @since  1.0
935
	 * @access private
936
	 *
937
	 * @return string The Notes for the donor, non-parsed.
938
	 */
939
	private function get_raw_notes() {
940
941
		$all_notes = $this->db->get_column( 'notes', $this->id );
942
943
		return $all_notes;
944
945
	}
946
947
	/**
948
	 * Retrieve a meta field for a donor.
949
	 *
950
	 * @since  1.6
951
	 * @access public
952
	 *
953
	 * @param  string $meta_key The meta key to retrieve. Default is empty.
954
	 * @param  bool   $single   Whether to return a single value. Default is true.
955
	 *
956
	 * @return mixed            Will be an array if $single is false. Will be value of meta data field if $single is
957
	 *                          true.
958
	 */
959
	public function get_meta( $meta_key = '', $single = true ) {
960
		return Give()->donor_meta->get_meta( $this->id, $meta_key, $single );
961
	}
962
963
	/**
964
	 * Add a meta data field to a donor.
965
	 *
966
	 * @since  1.6
967
	 * @access public
968
	 *
969
	 * @param  string $meta_key   Metadata name. Default is empty.
970
	 * @param  mixed  $meta_value Metadata value.
971
	 * @param  bool   $unique     Optional. Whether the same key should not be added. Default is false.
972
	 *
973
	 * @return bool               False for failure. True for success.
974
	 */
975
	public function add_meta( $meta_key = '', $meta_value, $unique = false ) {
976
		return Give()->donor_meta->add_meta( $this->id, $meta_key, $meta_value, $unique );
977
	}
978
979
	/**
980
	 * Update a meta field based on donor ID.
981
	 *
982
	 * @since  1.6
983
	 * @access public
984
	 *
985
	 * @param  string $meta_key   Metadata key. Default is empty.
986
	 * @param  mixed  $meta_value Metadata value.
987
	 * @param  mixed  $prev_value Optional. Previous value to check before removing. Default is empty.
988
	 *
989
	 * @return bool               False on failure, true if success.
990
	 */
991
	public function update_meta( $meta_key = '', $meta_value, $prev_value = '' ) {
992
		return Give()->donor_meta->update_meta( $this->id, $meta_key, $meta_value, $prev_value );
993
	}
994
995
	/**
996
	 * Remove metadata matching criteria from a donor.
997
	 *
998
	 * @since  1.6
999
	 * @access public
1000
	 *
1001
	 * @param  string $meta_key   Metadata name. Default is empty.
1002
	 * @param  mixed  $meta_value Optional. Metadata value. Default is empty.
1003
	 *
1004
	 * @return bool               False for failure. True for success.
1005
	 */
1006
	public function delete_meta( $meta_key = '', $meta_value = '' ) {
1007
		return Give()->donor_meta->delete_meta( $this->id, $meta_key, $meta_value );
1008
	}
1009
1010
	/**
1011
	 * Sanitize the data for update/create
1012
	 *
1013
	 * @since  1.0
1014
	 * @access private
1015
	 *
1016
	 * @param  array $data The data to sanitize.
1017
	 *
1018
	 * @return array       The sanitized data, based off column defaults.
1019
	 */
1020
	private function sanitize_columns( $data ) {
1021
1022
		$columns        = $this->db->get_columns();
1023
		$default_values = $this->db->get_column_defaults();
1024
1025
		foreach ( $columns as $key => $type ) {
1026
1027
			// Only sanitize data that we were provided
1028
			if ( ! array_key_exists( $key, $data ) ) {
1029
				continue;
1030
			}
1031
1032
			switch ( $type ) {
1033
1034
				case '%s':
1035
					if ( 'email' == $key ) {
1036
						$data[ $key ] = sanitize_email( $data[ $key ] );
1037
					} elseif ( 'notes' == $key ) {
1038
						$data[ $key ] = strip_tags( $data[ $key ] );
1039
					} else {
1040
						$data[ $key ] = sanitize_text_field( $data[ $key ] );
1041
					}
1042
					break;
1043
1044
				case '%d':
1045
					if ( ! is_numeric( $data[ $key ] ) || (int) $data[ $key ] !== absint( $data[ $key ] ) ) {
1046
						$data[ $key ] = $default_values[ $key ];
1047
					} else {
1048
						$data[ $key ] = absint( $data[ $key ] );
1049
					}
1050
					break;
1051
1052
				case '%f':
1053
					// Convert what was given to a float
1054
					$value = floatval( $data[ $key ] );
1055
1056
					if ( ! is_float( $value ) ) {
1057
						$data[ $key ] = $default_values[ $key ];
1058
					} else {
1059
						$data[ $key ] = $value;
1060
					}
1061
					break;
1062
1063
				default:
1064
					$data[ $key ] = sanitize_text_field( $data[ $key ] );
1065
					break;
1066
1067
			}
1068
		}
1069
1070
		return $data;
1071
	}
1072
1073
	/**
1074
	 * Attach an email to the donor
1075
	 *
1076
	 * @since  1.7
1077
	 * @access public
1078
	 *
1079
	 * @param  string $email   The email address to attach to the donor
1080
	 * @param  bool   $primary Allows setting the email added as the primary
1081
	 *
1082
	 * @return bool            If the email was added successfully
1083
	 */
1084
	public function add_email( $email = '', $primary = false ) {
1085
		if ( ! is_email( $email ) ) {
1086
			return false;
1087
		}
1088
		$existing = new Give_Donor( $email );
1089
1090
		if ( $existing->id > 0 ) {
1091
			// Email address already belongs to another donor
1092
			return false;
1093
		}
1094
1095
		if ( email_exists( $email ) ) {
1096
			$user = get_user_by( 'email', $email );
1097
			if ( $user->ID != $this->user_id ) {
1098
				return false;
1099
			}
1100
		}
1101
1102
		do_action( 'give_donor_pre_add_email', $email, $this->id, $this );
1103
1104
		// Add is used to ensure duplicate emails are not added
1105
		$ret = (bool) $this->add_meta( 'additional_email', $email );
1106
1107
		do_action( 'give_donor_post_add_email', $email, $this->id, $this );
1108
1109
		if ( $ret && true === $primary ) {
1110
			$this->set_primary_email( $email );
1111
		}
1112
1113
		return $ret;
1114
	}
1115
1116
	/**
1117
	 * Remove an email from the donor.
1118
	 *
1119
	 * @since  1.7
1120
	 * @access public
1121
	 *
1122
	 * @param  string $email The email address to remove from the donor.
1123
	 *
1124
	 * @return bool          If the email was removed successfully.
1125
	 */
1126
	public function remove_email( $email = '' ) {
1127
		if ( ! is_email( $email ) ) {
1128
			return false;
1129
		}
1130
1131
		do_action( 'give_donor_pre_remove_email', $email, $this->id, $this );
1132
1133
		$ret = (bool) $this->delete_meta( 'additional_email', $email );
1134
1135
		do_action( 'give_donor_post_remove_email', $email, $this->id, $this );
1136
1137
		return $ret;
1138
	}
1139
1140
	/**
1141
	 * Set an email address as the donor's primary email.
1142
	 *
1143
	 * This will move the donor's previous primary email to an additional email.
1144
	 *
1145
	 * @since  1.7
1146
	 * @access public
1147
	 *
1148
	 * @param  string $new_primary_email The email address to remove from the donor.
1149
	 *
1150
	 * @return bool                      If the email was set as primary successfully.
1151
	 */
1152
	public function set_primary_email( $new_primary_email = '' ) {
1153
		if ( ! is_email( $new_primary_email ) ) {
1154
			return false;
1155
		}
1156
1157
		do_action( 'give_donor_pre_set_primary_email', $new_primary_email, $this->id, $this );
1158
1159
		$existing = new Give_Donor( $new_primary_email );
1160
1161
		if ( $existing->id > 0 && (int) $existing->id !== (int) $this->id ) {
1162
			// This email belongs to another donor.
1163
			return false;
1164
		}
1165
1166
		$old_email = $this->email;
1167
1168
		// Update donor record with new email.
1169
		$update = $this->update( array( 'email' => $new_primary_email ) );
1170
1171
		// Remove new primary from list of additional emails.
1172
		$remove = $this->remove_email( $new_primary_email );
1173
1174
		// Add old email to additional emails list.
1175
		$add = $this->add_email( $old_email );
1176
1177
		$ret = $update && $remove && $add;
1178
1179
		if ( $ret ) {
1180
			$this->email = $new_primary_email;
1181
		}
1182
1183
		do_action( 'give_donor_post_set_primary_email', $new_primary_email, $this->id, $this );
1184
1185
		return $ret;
1186
	}
1187
1188
	/**
1189
	 * Check if address valid or not.
1190
	 *
1191
	 * @since  2.0
1192
	 * @access private
1193
	 *
1194
	 * @param $address
1195
	 *
1196
	 * @return bool
1197
	 */
1198
	private function is_valid_address( $address ) {
1199
		$is_valid_address = true;
1200
1201
		// Address ready to process even if only one value set.
1202
		foreach ( $address as $address_type => $value ) {
1203
			// @todo: Handle state field validation on basis of country.
1204
			if ( in_array( $address_type, array( 'line2', 'state' ) ) ) {
1205
				continue;
1206
			}
1207
1208
			if ( empty( $value ) ) {
1209
				$is_valid_address = false;
1210
				break;
1211
			}
1212
		}
1213
1214
		return $is_valid_address;
1215
	}
1216
1217
	/**
1218
	 * Add donor address
1219
	 *
1220
	 * @since  2.0
1221
	 * @access public
1222
	 *
1223
	 * @param string $address_type
1224
	 * @param array  $address {
1225
	 *
1226
	 * @type string  $address2
1227
	 * @type string city
1228
	 * @type string zip
1229
	 * @type string state
1230
	 * @type string country
1231
	 * }
1232
	 *
1233
	 * @return bool
1234
	 */
1235
	public function add_address( $address_type, $address ) {
1236
		// Bailout.
1237
		if ( empty( $address_type ) || ! $this->is_valid_address( $address ) || ! $this->id ) {
1238
			return false;
1239
		}
1240
1241
		// Check if multiple address exist or not and set params.
1242
		$multi_address_id = null;
1243
		if ( $is_multi_address = ( false !== strpos( $address_type, '[]' ) ) ) {
1244
			$address_type = $is_multi_address ? str_replace( '[]', '', $address_type ) : $address_type;
1245
		} elseif ( $is_multi_address = ( false !== strpos( $address_type, '_' ) ) ) {
1246
			$multi_address_id = $is_multi_address ? array_pop( explode( '_', $address_type ) ) : $address_type;
1247
1248
			$address_type = $is_multi_address ? array_shift( explode( '_', $address_type ) ) : $address_type;
1249
		}
1250
1251
		// Bailout: do not save duplicate orders
1252
		if ( $this->does_address_exist( $address_type, $address ) ) {
1253
			return false;
1254
		}
1255
1256
		// Set default address.
1257
		$address = wp_parse_args( $address, array(
1258
			'line1'   => '',
1259
			'line2'   => '',
1260
			'city'    => '',
1261
			'state'   => '',
1262
			'country' => '',
1263
			'zip'     => '',
1264
		) );
1265
1266
		// Set meta key prefix.
1267
		global $wpdb;
1268
		$meta_key_prefix = "_give_donor_address_{$address_type}_{address_name}";
1269
		$meta_type       = Give()->donor_meta->meta_type;
1270
1271
		if ( $is_multi_address ) {
1272
			if ( is_null( $multi_address_id ) ) {
1273
				// Get latest address key to set multi address id.
1274
				$multi_address_id = $wpdb->get_var( $wpdb->prepare( "
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
1275
						SELECT meta_key FROM {$wpdb->donormeta}
1276
						WHERE meta_key
1277
						LIKE '%%%s%%'
1278
						AND {$meta_type}_id=%d
1279
						ORDER BY meta_id DESC
1280
						LIMIT 1
1281
						", "_give_donor_address_{$address_type}_line1", $this->id ) );
1282
1283
				if ( ! empty( $multi_address_id ) ) {
1284
					$multi_address_id = absint( substr( strrchr( $multi_address_id, '_' ), 1 ) );
1285
					$multi_address_id ++;
1286
				} else {
1287
					$multi_address_id = 0;
1288
				}
1289
			}
1290
1291
			$meta_key_prefix = "_give_donor_address_{$address_type}_{address_name}_{$multi_address_id}";
1292
		}
1293
1294
		// Save donor address.
1295
		foreach ( $address as $type => $value ) {
1296
			$meta_key = str_replace( '{address_name}', $type, $meta_key_prefix );
1297
			Give()->donor_meta->update_meta( $this->id, $meta_key, $value );
1298
		}
1299
1300
		$this->setup_address();
1301
1302
		return true;
1303
	}
1304
1305
	/**
1306
	 * Remove donor address
1307
	 *
1308
	 * @since  2.0
1309
	 * @access public
1310
	 * @global wpdb  $wpdb
1311
	 *
1312
	 * @param string $address_id
1313
	 *
1314
	 * @return bool
1315
	 */
1316
	public function remove_address( $address_id ) {
1317
		global $wpdb;
1318
1319
		// Get address type.
1320
		$is_multi_address = false !== strpos( $address_id, '_' ) ? true : false;
1321
1322
		$address_type = false !== strpos( $address_id, '_' ) ? array_shift( explode( '_', $address_id ) ) : $address_id;
1323
1324
		$address_count = false !== strpos( $address_id, '_' ) ? array_pop( explode( '_', $address_id ) ) : null;
1325
1326
		// Set meta key prefix.
1327
		$meta_key_prefix = "_give_donor_address_{$address_type}_%";
1328
		if ( $is_multi_address && is_numeric( $address_count ) ) {
1329
			$meta_key_prefix .= "_{$address_count}";
1330
		}
1331
1332
		$meta_type = Give()->donor_meta->meta_type;
1333
1334
		// Process query.
1335
		$row_affected = $wpdb->query( $wpdb->prepare( "
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
1336
				DELETE FROM {$wpdb->donormeta}
1337
				WHERE meta_key
1338
				LIKE '%s'
1339
				AND {$meta_type}_id=%d
1340
				", $meta_key_prefix, $this->id ) );
1341
1342
		$this->setup_address();
1343
1344
		return (bool) $row_affected;
1345
	}
1346
1347
	/**
1348
	 * Update donor address
1349
	 *
1350
	 * @since  2.0
1351
	 * @access public
1352
	 * @global wpdb  $wpdb
1353
	 *
1354
	 * @param string $address_id
1355
	 * @param array  $address
1356
	 *
1357
	 * @return bool
1358
	 */
1359
	public function update_address( $address_id, $address ) {
1360
		global $wpdb;
1361
1362
		// Get address type.
1363
		$is_multi_address = false !== strpos( $address_id, '_' ) ? true : false;
1364
1365
		$address_type = false !== strpos( $address_id, '_' ) ? array_shift( explode( '_', $address_id ) ) : $address_id;
1366
1367
		$address_count = false !== strpos( $address_id, '_' ) ? array_pop( explode( '_', $address_id ) ) : null;
1368
1369
		// Set meta key prefix.
1370
		$meta_key_prefix = "_give_donor_address_{$address_type}_%";
1371
		if ( $is_multi_address && is_numeric( $address_count ) ) {
1372
			$meta_key_prefix .= "_{$address_count}";
1373
		}
1374
1375
		$meta_type = Give()->donor_meta->meta_type;
1376
1377
		// Process query.
1378
		$row_affected = $wpdb->get_results( $wpdb->prepare( "
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
1379
				SELECT meta_key FROM {$wpdb->donormeta}
1380
				WHERE meta_key
1381
				LIKE '%s'
1382
				AND {$meta_type}_id=%d
1383
				", $meta_key_prefix, $this->id ) );
1384
1385
		// Return result.
1386
		if ( ! count( $row_affected ) ) {
1387
			return false;
1388
		}
1389
1390
		// Update address.
1391
		if ( ! $this->add_address( $address_id, $address ) ) {
1392
			return false;
1393
		}
1394
1395
		return true;
1396
	}
1397
1398
1399
	/**
1400
	 * Check if donor already has current address
1401
	 *
1402
	 * @since  2.0
1403
	 * @access public
1404
	 *
1405
	 * @param string $current_address_type
1406
	 * @param array  $current_address
1407
	 *
1408
	 * @return bool|null
1409
	 */
1410
	public function does_address_exist( $current_address_type, $current_address ) {
1411
		$status = false;
1412
1413
		// Bailout.
1414
		if ( empty( $current_address_type ) || empty( $current_address ) ) {
1415
			return null;
1416
		}
1417
1418
		// Bailout.
1419
		if ( empty( $this->address ) || empty( $this->address[ $current_address_type ] ) ) {
1420
			return $status;
1421
		}
1422
1423
		// Get address.
1424
		$address = $this->address[ $current_address_type ];
1425
1426
		switch ( true ) {
1427
1428
			// Single address.
1429
			case is_string( end( $address ) ) :
1430
				$status = $this->is_address_match( $current_address, $address );
1431
				break;
1432
1433
			// Multi address.
1434
			case is_array( end( $address ) ):
1435
				// Compare address.
1436
				foreach ( $address as $saved_address ) {
1437
					if ( empty( $saved_address ) ) {
1438
						continue;
1439
					}
1440
1441
					// Exit loop immediately if address exist.
1442
					if ( $status = $this->is_address_match( $current_address, $saved_address ) ) {
1443
						break;
1444
					}
1445
				}
1446
				break;
1447
		}
1448
1449
		return $status;
1450
	}
1451
1452
	/**
1453
	 * Compare address.
1454
	 *
1455
	 * @since  2.0
1456
	 * @access private
1457
	 *
1458
	 * @param array $address_1
1459
	 * @param array $address_2
1460
	 *
1461
	 * @return bool
1462
	 */
1463
	private function is_address_match( $address_1, $address_2 ) {
1464
		$result = array_diff( $address_1, $address_2 );
1465
1466
		return empty( $result );
1467
	}
1468
1469
	/**
1470
	 * Split donor name into first name and last name
1471
	 *
1472
	 * @param   int $id Donor ID
1473
	 *
1474
	 * @since   2.0
1475
	 * @return  object
1476
	 */
1477
	public function split_donor_name( $id ) {
1478
		$first_name = $last_name = '';
1479
		$donor      = new Give_Donor( $id );
1480
1481
		$split_donor_name = explode( ' ', $donor->name, 2 );
1482
1483
		// Check for existence of first name after split of donor name.
1484
		if ( is_array( $split_donor_name ) && ! empty( $split_donor_name[0] ) ) {
1485
			$first_name = $split_donor_name[0];
1486
		}
1487
1488
		// Check for existence of last name after split of donor name.
1489
		if ( is_array( $split_donor_name ) && ! empty( $split_donor_name[1] ) ) {
1490
			$last_name = $split_donor_name[1];
1491
		}
1492
1493
		return (object) array( 'first_name' => $first_name, 'last_name' => $last_name );
1494
	}
1495
1496
	/**
1497
	 * Retrieves first name of donor with backward compatibility
1498
	 *
1499
	 * @since   2.0
1500
	 * @return  string
1501
	 */
1502
	public function get_first_name() {
1503
		$first_name = $this->get_meta( '_give_donor_first_name' );
1504
		if ( ! $first_name ) {
1505
			$first_name = $this->split_donor_name( $this->id )->first_name;
1506
		}
1507
1508
		return $first_name;
1509
	}
1510
1511
	/**
1512
	 * Retrieves last name of donor with backward compatibility
1513
	 *
1514
	 * @since   2.0
1515
	 * @return  string
1516
	 */
1517
	public function get_last_name() {
1518
		$first_name = $this->get_meta( '_give_donor_first_name' );
1519
		$last_name  = $this->get_meta( '_give_donor_last_name' );
1520
1521
		// This condition will prevent unnecessary splitting of donor name to fetch last name.
1522
		if ( ! $first_name && ! $last_name ) {
1523
			$last_name = $this->split_donor_name( $this->id )->last_name;
1524
		}
1525
1526
		return ( $last_name ) ? $last_name : '';
1527
	}
1528
1529
	/**
1530
	 * Retrieves company name of donor
1531
	 *
1532
	 * @since   2.1
1533
	 *
1534
	 * @return  string $company_name Donor Company Name
1535
	 */
1536
	public function get_company_name() {
1537
		$company_name = $this->get_meta( '_give_donor_company' );
1538
1539
		return $company_name;
1540
	}
1541
1542
	/**
1543
	 * Retrieves last donation for the donor.
1544
	 *
1545
	 * @since   2.1
1546
	 *
1547
	 * @return  string $company_name Donor Company Name
1548
	 */
1549
	public function get_last_donation() {
1550
1551
		$payments = array_unique( array_values( explode( ',', $this->payment_ids ) ) );
1552
1553
		return end( $payments );
1554
1555
	}
1556
1557
	/**
1558
	 * Retrieves last donation for the donor.
1559
	 *
1560
	 * @since   2.1
1561
	 *
1562
	 * @param bool $formatted Whether to return with the date format or not.
1563
	 *
1564
	 * @return string The date of the last donation.
1565
	 */
1566
	public function get_last_donation_date( $formatted = false ) {
1567
1568
		$last_donation = $this->get_last_donation();
1569
1570
		$payment = new Give_Payment( $last_donation );
1571
1572
		if ( $formatted ) {
1573
			return date_i18n( give_date_format(), strtotime( $payment->completed_date ) );
1574
		}
1575
1576
		return $payment->completed_date;
1577
1578
	}
1579
1580
	/**
1581
	 * Retrieves a donor's initials (first name and last name).
1582
	 *
1583
	 * @since   2.1
1584
	 *
1585
	 * @return string The donor's two initials (no middle).
1586
	 */
1587
	public function get_donor_initals() {
1588
1589
		$first_name_initial = mb_substr( $this->get_first_name(), 0, 1, 'utf-8' );
1590
		$last_name_initial  = mb_substr( $this->get_last_name(), 0, 1, 'utf-8' );
1591
1592
		return apply_filters( 'get_donor_initals', $first_name_initial . $last_name_initial );
1593
1594
	}
1595
1596
}
1597