Test Failed
Push — release/2.0 ( 6ea777...f7bd0b )
by Ravinder
05:21
created

Give_Donor   D

Complexity

Total Complexity 163

Size/Duplication

Total Lines 1545
Duplicated Lines 2.2 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
dl 34
loc 1545
rs 4.4102
c 0
b 0
f 0
wmc 163
lcom 1
cbo 5

35 Methods

Rating   Name   Duplication   Size   Complexity  
D __construct() 0 28 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
A get_total_donation_amount() 0 13 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
A is_valid_address() 0 18 4
D add_address() 0 84 14
B remove_address() 0 40 6
C setup_donor() 0 46 8
B setup_address() 0 33 4
A __get() 14 14 2
B create() 13 57 8
B update() 7 42 3
C update_address() 0 48 8
D is_address_exist() 0 41 10
A is_address_match() 0 5 1
B split_donor_name() 0 17 5
A get_first_name() 0 8 2
A get_last_name() 0 11 4
B attach_payment() 0 65 7
C remove_payment() 0 72 9

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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
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 = 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 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 (
157
			false === $_id_or_email
158
			|| ( is_numeric( $_id_or_email ) && (int) $_id_or_email !== absint( $_id_or_email ) )
159
		) {
160
			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...
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;
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...
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
		// Get cached donors.
200
		$donor_vars = Give_Cache::get_group( $donor->id, 'give-donors' );
201
202
		if( is_null( $donor_vars ) ){
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
203
			foreach ( $donor as $key => $value ) {
204
205
				switch ( $key ) {
206
207
					case 'notes':
208
						$this->$key = $this->get_notes();
209
						break;
210
211
					default:
212
						$this->$key = $value;
213
						break;
214
215
				}
216
			}
217
218
			// Get donor's all email including primary email.
219
			$this->emails = (array) $this->get_meta( 'additional_email', false );
220
			$this->emails = array( 'primary' => $this->email ) + $this->emails;
221
222
			$this->setup_address();
223
224
			Give_Cache::set_group( $donor->id, get_object_vars( $this ), 'give-donors' );
225
		} else{
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
226
			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...
227
				$this->$donor_var = $value;
228
			}
229
		}
230
231
		// Donor ID and email are the only things that are necessary, make sure they exist.
232
		if ( ! empty( $this->id ) && ! empty( $this->email ) ) {
233
			return true;
234
		}
235
236
		return false;
237
238
	}
239
240
241
	/**
242
	 * Setup donor address.
243
	 *
244
	 * @since 2.0
245
	 * @access public
246
	 */
247
	public function setup_address() {
248
		global $wpdb;
249
		$meta_type = Give()->donor_meta->meta_type;
250
251
		$addresses = $wpdb->get_results(
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...
252
			$wpdb->prepare(
253
				"
254
				SELECT meta_key, meta_value FROM {$wpdb->donormeta}
255
				WHERE meta_key
256
				LIKE '%%%s%%'
257
				AND {$meta_type}_id=%d
258
				",
259
				'give_donor_address',
260
				$this->id
261
			),
262
			ARRAY_N
263
		);
264
265
		if ( empty( $addresses ) ) {
266
			return $this->address;
267
		}
268
269
		foreach ( $addresses as $address ) {
270
			$address[0] = str_replace( '_give_donor_address_', '', $address[0] );
271
			$address[0] = explode( '_', $address[0] );
272
273
			if ( 3 === count( $address[0] ) ) {
274
				$this->address[ $address[0][0] ][ $address[0][2] ][ $address[0][1] ] = $address[1];
275
			} else {
276
				$this->address[ $address[0][0] ][ $address[0][1] ] = $address[1];
277
			}
278
		}
279
	}
280
281
	/**
282
	 * Magic __get function to dispatch a call to retrieve a private property.
283
	 *
284
	 * @since  1.0
285
	 * @access public
286
	 * @param $key
287
	 *
288
	 * @return mixed|\WP_Error
289
	 */
290 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...
291
292
		if ( method_exists( $this, 'get_' . $key ) ) {
293
294
			return call_user_func( array( $this, 'get_' . $key ) );
295
296
		} else {
297
298
			/* translators: %s: property key */
299
			return new WP_Error( 'give-donor-invalid-property', sprintf( esc_html__( 'Can\'t get property %s.', 'give' ), $key ) );
300
301
		}
302
303
	}
304
305
	/**
306
	 * Creates a donor.
307
	 *
308
	 * @since  1.0
309
	 * @access public
310
	 *
311
	 * @param  array $data Array of attributes for a donor.
312
	 *
313
	 * @return bool|int    False if not a valid creation, donor ID if user is found or valid creation.
314
	 */
315
	public function create( $data = array() ) {
316
317
		if ( $this->id != 0 || empty( $data ) ) {
0 ignored issues
show
introduced by
Found "!= 0". Use Yoda Condition checks, you must
Loading history...
318
			return false;
319
		}
320
321
		$defaults = array(
322
			'payment_ids' => '',
323
		);
324
325
		$args = wp_parse_args( $data, $defaults );
326
		$args = $this->sanitize_columns( $args );
327
328
		if ( empty( $args['email'] ) || ! is_email( $args['email'] ) ) {
329
			return false;
330
		}
331
332 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...
333
			$args['payment_ids'] = implode( ',', array_unique( array_values( $args['payment_ids'] ) ) );
334
		}
335
336
		/**
337
		 * Fires before creating donors.
338
		 *
339
		 * @since 1.0
340
		 *
341
		 * @param array $args Donor attributes.
342
		 */
343
		do_action( 'give_donor_pre_create', $args );
344
345
		$created = false;
346
347
		// The DB class 'add' implies an update if the donor being asked to be created already exists
348 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...
349
350
			// We've successfully added/updated the donor, reset the class vars with the new data
351
			$donor = $this->db->get_donor_by( 'email', $args['email'] );
352
353
			// Setup the donor data with the values from DB
354
			$this->setup_donor( $donor );
355
356
			$created = $this->id;
357
		}
358
359
		/**
360
		 * Fires after creating donors.
361
		 *
362
		 * @since 1.0
363
		 *
364
		 * @param bool|int $created False if not a valid creation, donor ID if user is found or valid creation.
365
		 * @param array $args Customer attributes.
366
		 */
367
		do_action( 'give_donor_post_create', $created, $args );
368
369
		return $created;
370
371
	}
372
373
	/**
374
	 * Updates a donor record.
375
	 *
376
	 * @since  1.0
377
	 * @access public
378
	 *
379
	 * @param  array $data Array of data attributes for a donor (checked via whitelist).
380
	 *
381
	 * @return bool        If the update was successful or not.
382
	 */
383
	public function update( $data = array() ) {
384
385
		if ( empty( $data ) ) {
386
			return false;
387
		}
388
389
		$data = $this->sanitize_columns( $data );
390
391
		/**
392
		 * Fires before updating donors.
393
		 *
394
		 * @since 1.0
395
		 *
396
		 * @param int $donor_id Donor id.
397
		 * @param array $data Donor attributes.
398
		 */
399
		do_action( 'give_donor_pre_update', $this->id, $data );
400
401
		$updated = false;
402
403 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...
404
405
			$donor = $this->db->get_donor_by( 'id', $this->id );
406
407
			$this->setup_donor( $donor );
408
409
			$updated = true;
410
		}
411
412
		/**
413
		 * Fires after updating donors.
414
		 *
415
		 * @since 1.0
416
		 *
417
		 * @param bool $updated If the update was successful or not.
418
		 * @param int $donor_id Donor id.
419
		 * @param array $data Donor attributes.
420
		 */
421
		do_action( 'give_donor_post_update', $updated, $this->id, $data );
422
423
		return $updated;
424
	}
425
426
	/**
427
	 * Attach Payment
428
	 *
429
	 * Attach payment to the donor then triggers increasing stats.
430
	 *
431
	 * @since  1.0
432
	 * @access public
433
	 *
434
	 * @param  int $payment_id The payment ID to attach to the donor.
435
	 * @param  bool $update_stats For backwards compatibility, if we should increase the stats or not.
436
	 *
437
	 * @return bool            If the attachment was successfully.
438
	 */
439
	public function attach_payment( $payment_id = 0, $update_stats = true ) {
440
441
		if ( empty( $payment_id ) ) {
442
			return false;
443
		}
444
445
		if ( empty( $this->payment_ids ) ) {
446
447
			$new_payment_ids = $payment_id;
448
449
		} else {
450
451
			$payment_ids = array_map( 'absint', explode( ',', $this->payment_ids ) );
452
453
			if ( in_array( $payment_id, $payment_ids ) ) {
454
				$update_stats = false;
455
			}
456
457
			$payment_ids[] = $payment_id;
458
459
			$new_payment_ids = implode( ',', array_unique( array_values( $payment_ids ) ) );
460
461
		}
462
463
		/**
464
		 * Fires before attaching payments to customers.
465
		 *
466
		 * @since 1.0
467
		 *
468
		 * @param int $payment_id Payment id.
469
		 * @param int $donor_id Customer id.
470
		 */
471
		do_action( 'give_donor_pre_attach_payment', $payment_id, $this->id );
472
473
		$payment_added = $this->update( array( 'payment_ids' => $new_payment_ids ) );
474
475
		if ( $payment_added ) {
476
477
			$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...
478
479
			// We added this payment successfully, increment the stats
480
			if ( $update_stats ) {
481
				$payment_amount = give_donation_amount( $payment_id, array( 'type' => 'stats' ) );
482
483
				if ( ! empty( $payment_amount ) ) {
484
					$this->increase_value( $payment_amount );
485
				}
486
487
				$this->increase_purchase_count();
488
			}
489
		}
490
491
		/**
492
		 * Fires after attaching payments to the donor.
493
		 *
494
		 * @since 1.0
495
		 *
496
		 * @param bool $payment_added If the attachment was successfully.
497
		 * @param int $payment_id Payment id.
498
		 * @param int $donor_id Donor id.
499
		 */
500
		do_action( 'give_donor_post_attach_payment', $payment_added, $payment_id, $this->id );
501
502
		return $payment_added;
503
	}
504
505
	/**
506
	 * Remove Payment
507
	 *
508
	 * Remove a payment from this donor, then triggers reducing stats.
509
	 *
510
	 * @since  1.0
511
	 * @access public
512
	 *
513
	 * @param  int $payment_id The Payment ID to remove.
514
	 * @param  bool $update_stats For backwards compatibility, if we should increase the stats or not.
515
	 *
516
	 * @return boolean               If the removal was successful.
517
	 */
518
	public function remove_payment( $payment_id = 0, $update_stats = true ) {
519
520
		if ( empty( $payment_id ) ) {
521
			return false;
522
		}
523
524
		$payment = new Give_Payment( $payment_id );
525
526
		if ( 'publish' !== $payment->status && 'revoked' !== $payment->status ) {
527
			$update_stats = false;
528
		}
529
530
		$new_payment_ids = '';
531
532
		if ( ! empty( $this->payment_ids ) ) {
533
534
			$payment_ids = array_map( 'absint', explode( ',', $this->payment_ids ) );
535
536
			$pos = array_search( $payment_id, $payment_ids );
537
			if ( false === $pos ) {
538
				return false;
539
			}
540
541
			unset( $payment_ids[ $pos ] );
542
			$payment_ids = array_filter( $payment_ids );
543
544
			$new_payment_ids = implode( ',', array_unique( array_values( $payment_ids ) ) );
545
546
		}
547
548
		/**
549
		 * Fires before removing payments from customers.
550
		 *
551
		 * @since 1.0
552
		 *
553
		 * @param int $payment_id Payment id.
554
		 * @param int $donor_id Customer id.
555
		 */
556
		do_action( 'give_donor_pre_remove_payment', $payment_id, $this->id );
557
558
		$payment_removed = $this->update( array( 'payment_ids' => $new_payment_ids ) );
559
560
		if ( $payment_removed ) {
561
562
			$this->payment_ids = $new_payment_ids;
563
564
			if ( $update_stats ) {
565
				// We removed this payment successfully, decrement the stats
566
				$payment_amount = give_donation_amount( $payment_id );
567
568
				if ( ! empty( $payment_amount ) ) {
569
					$this->decrease_value( $payment_amount );
570
				}
571
572
				$this->decrease_donation_count();
573
			}
574
		}
575
576
		/**
577
		 * Fires after removing payments from donors.
578
		 *
579
		 * @since 1.0
580
		 *
581
		 * @param bool $payment_removed If the removal was successfully.
582
		 * @param int $payment_id Payment id.
583
		 * @param int $donor_id Donor id.
584
		 */
585
		do_action( 'give_donor_post_remove_payment', $payment_removed, $payment_id, $this->id );
586
587
		return $payment_removed;
588
589
	}
590
591
	/**
592
	 * Increase the donation count of a donor.
593
	 *
594
	 * @since  1.0
595
	 * @access public
596
	 *
597
	 * @param  int $count The number to increase by.
598
	 *
599
	 * @return int        The donation count.
600
	 */
601
	public function increase_purchase_count( $count = 1 ) {
602
603
		// Make sure it's numeric and not negative.
604
		if ( ! is_numeric( $count ) || $count != absint( $count ) ) {
605
			return false;
606
		}
607
608
		$new_total = (int) $this->purchase_count + (int) $count;
609
610
		/**
611
		 * Fires before increasing the donor's donation count.
612
		 *
613
		 * @since 1.0
614
		 *
615
		 * @param int $count The number to increase by.
616
		 * @param int $donor_id Donor id.
617
		 */
618
		do_action( 'give_donor_pre_increase_donation_count', $count, $this->id );
619
620
		if ( $this->update( array( 'purchase_count' => $new_total ) ) ) {
621
			$this->purchase_count = $new_total;
622
		}
623
624
		/**
625
		 * Fires after increasing the donor's donation count.
626
		 *
627
		 * @since 1.0
628
		 *
629
		 * @param int $purchase_count Donor donation count.
630
		 * @param int $count The number increased by.
631
		 * @param int $donor_id Donor id.
632
		 */
633
		do_action( 'give_donor_post_increase_donation_count', $this->purchase_count, $count, $this->id );
634
635
		return $this->purchase_count;
636
	}
637
638
	/**
639
	 * Decrease the donor donation count.
640
	 *
641
	 * @since  1.0
642
	 * @access public
643
	 *
644
	 * @param  int $count The amount to decrease by.
645
	 *
646
	 * @return mixed      If successful, the new count, otherwise false.
647
	 */
648
	public function decrease_donation_count( $count = 1 ) {
649
650
		// Make sure it's numeric and not negative
651
		if ( ! is_numeric( $count ) || $count != absint( $count ) ) {
652
			return false;
653
		}
654
655
		$new_total = (int) $this->purchase_count - (int) $count;
656
657
		if ( $new_total < 0 ) {
658
			$new_total = 0;
659
		}
660
661
		/**
662
		 * Fires before decreasing the donor's donation count.
663
		 *
664
		 * @since 1.0
665
		 *
666
		 * @param int $count The number to decrease by.
667
		 * @param int $donor_id Customer id.
668
		 */
669
		do_action( 'give_donor_pre_decrease_donation_count', $count, $this->id );
670
671
		if ( $this->update( array( 'purchase_count' => $new_total ) ) ) {
672
			$this->purchase_count = $new_total;
673
		}
674
675
		/**
676
		 * Fires after decreasing the donor's donation count.
677
		 *
678
		 * @since 1.0
679
		 *
680
		 * @param int $purchase_count Donor's donation count.
681
		 * @param int $count The number decreased by.
682
		 * @param int $donor_id Donor id.
683
		 */
684
		do_action( 'give_donor_post_decrease_donation_count', $this->purchase_count, $count, $this->id );
685
686
		return $this->purchase_count;
687
	}
688
689
	/**
690
	 * Increase the donor's lifetime value.
691
	 *
692
	 * @since  1.0
693
	 * @access public
694
	 *
695
	 * @param  float $value The value to increase by.
696
	 *
697
	 * @return mixed        If successful, the new value, otherwise false.
698
	 */
699
	public function increase_value( $value = 0.00 ) {
700
701
		$new_value = floatval( $this->purchase_value ) + $value;
702
703
		/**
704
		 * Fires before increasing donor lifetime value.
705
		 *
706
		 * @since 1.0
707
		 *
708
		 * @param float $value The value to increase by.
709
		 * @param int $donor_id Customer id.
710
		 */
711
		do_action( 'give_donor_pre_increase_value', $value, $this->id );
712
713
		if ( $this->update( array( 'purchase_value' => $new_value ) ) ) {
714
			$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...
715
		}
716
717
		/**
718
		 * Fires after increasing donor lifetime value.
719
		 *
720
		 * @since 1.0
721
		 *
722
		 * @param float $purchase_value Donor's lifetime value.
723
		 * @param float $value The value increased by.
724
		 * @param int $donor_id Donor id.
725
		 */
726
		do_action( 'give_donor_post_increase_value', $this->purchase_value, $value, $this->id );
727
728
		return $this->purchase_value;
729
	}
730
731
	/**
732
	 * Decrease a donor's lifetime value.
733
	 *
734
	 * @since  1.0
735
	 * @access public
736
	 *
737
	 * @param  float $value The value to decrease by.
738
	 *
739
	 * @return mixed        If successful, the new value, otherwise false.
740
	 */
741
	public function decrease_value( $value = 0.00 ) {
742
743
		$new_value = floatval( $this->purchase_value ) - $value;
744
745
		if ( $new_value < 0 ) {
746
			$new_value = 0.00;
747
		}
748
749
		/**
750
		 * Fires before decreasing donor lifetime value.
751
		 *
752
		 * @since 1.0
753
		 *
754
		 * @param float $value The value to decrease by.
755
		 * @param int $donor_id Donor id.
756
		 */
757
		do_action( 'give_donor_pre_decrease_value', $value, $this->id );
758
759
		if ( $this->update( array( 'purchase_value' => $new_value ) ) ) {
760
			$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...
761
		}
762
763
		/**
764
		 * Fires after decreasing donor lifetime value.
765
		 *
766
		 * @since 1.0
767
		 *
768
		 * @param float $purchase_value Donor lifetime value.
769
		 * @param float $value The value decreased by.
770
		 * @param int $donor_id Donor id.
771
		 */
772
		do_action( 'give_donor_post_decrease_value', $this->purchase_value, $value, $this->id );
773
774
		return $this->purchase_value;
775
	}
776
777
	/**
778
	 * Decrease/Increase a donor's lifetime value.
779
	 *
780
	 * This function will update donation stat on basis of current amount and new amount donation difference.
781
	 * Difference value can positive or negative. Negative value will decrease user donation stat while positive value increase donation stat.
782
	 *
783
	 * @since  1.0
784
	 * @access public
785
	 *
786
	 * @param  float $curr_amount Current Donation amount.
787
	 * @param  float $new_amount New (changed) Donation amount.
788
	 *
789
	 * @return mixed              If successful, the new donation stat value, otherwise false.
790
	 */
791
	public function update_donation_value( $curr_amount, $new_amount ) {
792
		/**
793
		 * Payment total difference value can be:
794
		 *  zero   (in case amount not change)
795
		 *  or -ve (in case amount decrease)
796
		 *  or +ve (in case amount increase)
797
		 */
798
		$payment_total_diff = $new_amount - $curr_amount;
799
800
		// We do not need to update donation stat if donation did not change.
801
		if ( ! $payment_total_diff ) {
802
			return false;
803
		}
804
805
		if ( $payment_total_diff > 0 ) {
806
			$this->increase_value( $payment_total_diff );
807
		} else {
808
			// Pass payment total difference as +ve value to decrease amount from user lifetime stat.
809
			$this->decrease_value( - $payment_total_diff );
810
		}
811
812
		return $this->purchase_value;
813
	}
814
815
	/**
816
	 * Get the parsed notes for a donor as an array.
817
	 *
818
	 * @since  1.0
819
	 * @access public
820
	 *
821
	 * @param  int $length The number of notes to get.
822
	 * @param  int $paged What note to start at.
823
	 *
824
	 * @return array       The notes requested.
825
	 */
826
	public function get_notes( $length = 20, $paged = 1 ) {
827
828
		$length = is_numeric( $length ) ? $length : 20;
829
		$offset = is_numeric( $paged ) && $paged != 1 ? ( ( absint( $paged ) - 1 ) * $length ) : 0;
830
831
		$all_notes   = $this->get_raw_notes();
832
		$notes_array = array_reverse( array_filter( explode( "\n\n", $all_notes ) ) );
833
834
		$desired_notes = array_slice( $notes_array, $offset, $length );
835
836
		return $desired_notes;
837
838
	}
839
840
	/**
841
	 * Get the total number of notes we have after parsing.
842
	 *
843
	 * @since  1.0
844
	 * @access public
845
	 *
846
	 * @return int The number of notes for the donor.
847
	 */
848
	public function get_notes_count() {
849
850
		$all_notes   = $this->get_raw_notes();
851
		$notes_array = array_reverse( array_filter( explode( "\n\n", $all_notes ) ) );
852
853
		return count( $notes_array );
854
855
	}
856
857
	/**
858
	 * Get the total donation amount.
859
	 *
860
	 * @since 1.8.17
861
	 *
862
	 * @param array $args Pass any additional data.
863
	 *
864
	 * @return string|float
865
	 */
866
	public function get_total_donation_amount( $args = array() ) {
867
868
		/**
869
		 * Filter total donation amount.
870
		 *
871
		 * @since 1.8.17
872
		 *
873
		 * @param string|float $purchase_value Donor Purchase value.
874
		 * @param integer      $donor_id       Donor ID.
875
		 * @param array        $args           Pass additional data.
876
		 */
877
		return apply_filters( 'give_get_total_donation_amount', $this->purchase_value, $this->id, $args );
878
	}
879
880
	/**
881
	 * Add a note for the donor.
882
	 *
883
	 * @since  1.0
884
	 * @access public
885
	 *
886
	 * @param  string $note The note to add. Default is empty.
887
	 *
888
	 * @return string|boolean The new note if added successfully, false otherwise.
889
	 */
890
	public function add_note( $note = '' ) {
891
892
		$note = trim( $note );
893
		if ( empty( $note ) ) {
894
			return false;
895
		}
896
897
		$notes = $this->get_raw_notes();
898
899
		if ( empty( $notes ) ) {
900
			$notes = '';
901
		}
902
903
		$note_string = date_i18n( 'F j, Y H:i:s', current_time( 'timestamp' ) ) . ' - ' . $note;
904
		$new_note    = apply_filters( 'give_customer_add_note_string', $note_string );
905
		$notes       .= "\n\n" . $new_note;
906
907
		/**
908
		 * Fires before donor note is added.
909
		 *
910
		 * @since 1.0
911
		 *
912
		 * @param string $new_note New note to add.
913
		 * @param int $donor_id Donor id.
914
		 */
915
		do_action( 'give_donor_pre_add_note', $new_note, $this->id );
916
917
		$updated = $this->update( array( 'notes' => $notes ) );
918
919
		if ( $updated ) {
920
			$this->notes = $this->get_notes();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->get_notes() of type array is incompatible with the declared type string of property $notes.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
921
		}
922
923
		/**
924
		 * Fires after donor note added.
925
		 *
926
		 * @since 1.0
927
		 *
928
		 * @param array $donor_notes Donor notes.
929
		 * @param string $new_note New note added.
930
		 * @param int $donor_id Donor id.
931
		 */
932
		do_action( 'give_donor_post_add_note', $this->notes, $new_note, $this->id );
933
934
		// Return the formatted note, so we can test, as well as update any displays
935
		return $new_note;
936
937
	}
938
939
	/**
940
	 * Get the notes column for the donor
941
	 *
942
	 * @since  1.0
943
	 * @access private
944
	 *
945
	 * @return string The Notes for the donor, non-parsed.
946
	 */
947
	private function get_raw_notes() {
948
949
		$all_notes = $this->db->get_column( 'notes', $this->id );
950
951
		return $all_notes;
952
953
	}
954
955
	/**
956
	 * Retrieve a meta field for a donor.
957
	 *
958
	 * @since  1.6
959
	 * @access public
960
	 *
961
	 * @param  string $meta_key The meta key to retrieve. Default is empty.
962
	 * @param  bool $single Whether to return a single value. Default is true.
963
	 *
964
	 * @return mixed            Will be an array if $single is false. Will be value of meta data field if $single is true.
965
	 */
966
	public function get_meta( $meta_key = '', $single = true ) {
967
		return Give()->donor_meta->get_meta( $this->id, $meta_key, $single );
968
	}
969
970
	/**
971
	 * Add a meta data field to a donor.
972
	 *
973
	 * @since  1.6
974
	 * @access public
975
	 *
976
	 * @param  string $meta_key Metadata name. Default is empty.
977
	 * @param  mixed $meta_value Metadata value.
978
	 * @param  bool $unique Optional. Whether the same key should not be added. Default is false.
979
	 *
980
	 * @return bool               False for failure. True for success.
981
	 */
982
	public function add_meta( $meta_key = '', $meta_value, $unique = false ) {
983
		return Give()->donor_meta->add_meta( $this->id, $meta_key, $meta_value, $unique );
984
	}
985
986
	/**
987
	 * Update a meta field based on donor ID.
988
	 *
989
	 * @since  1.6
990
	 * @access public
991
	 *
992
	 * @param  string $meta_key Metadata key. Default is empty.
993
	 * @param  mixed $meta_value Metadata value.
994
	 * @param  mixed $prev_value Optional. Previous value to check before removing. Default is empty.
995
	 *
996
	 * @return bool               False on failure, true if success.
997
	 */
998
	public function update_meta( $meta_key = '', $meta_value, $prev_value = '' ) {
999
		return Give()->donor_meta->update_meta( $this->id, $meta_key, $meta_value, $prev_value );
1000
	}
1001
1002
	/**
1003
	 * Remove metadata matching criteria from a donor.
1004
	 *
1005
	 * @since  1.6
1006
	 * @access public
1007
	 *
1008
	 * @param  string $meta_key Metadata name. Default is empty.
1009
	 * @param  mixed $meta_value Optional. Metadata value. Default is empty.
1010
	 *
1011
	 * @return bool               False for failure. True for success.
1012
	 */
1013
	public function delete_meta( $meta_key = '', $meta_value = '' ) {
1014
		return Give()->donor_meta->delete_meta( $this->id, $meta_key, $meta_value );
1015
	}
1016
1017
	/**
1018
	 * Sanitize the data for update/create
1019
	 *
1020
	 * @since  1.0
1021
	 * @access private
1022
	 *
1023
	 * @param  array $data The data to sanitize.
1024
	 *
1025
	 * @return array       The sanitized data, based off column defaults.
1026
	 */
1027
	private function sanitize_columns( $data ) {
1028
1029
		$columns        = $this->db->get_columns();
1030
		$default_values = $this->db->get_column_defaults();
1031
1032
		foreach ( $columns as $key => $type ) {
1033
1034
			// Only sanitize data that we were provided
1035
			if ( ! array_key_exists( $key, $data ) ) {
1036
				continue;
1037
			}
1038
1039
			switch ( $type ) {
1040
1041
				case '%s':
1042
					if ( 'email' == $key ) {
1043
						$data[ $key ] = sanitize_email( $data[ $key ] );
1044
					} elseif ( 'notes' == $key ) {
1045
						$data[ $key ] = strip_tags( $data[ $key ] );
1046
					} else {
1047
						$data[ $key ] = sanitize_text_field( $data[ $key ] );
1048
					}
1049
					break;
1050
1051
				case '%d':
1052
					if ( ! is_numeric( $data[ $key ] ) || (int) $data[ $key ] !== absint( $data[ $key ] ) ) {
1053
						$data[ $key ] = $default_values[ $key ];
1054
					} else {
1055
						$data[ $key ] = absint( $data[ $key ] );
1056
					}
1057
					break;
1058
1059
				case '%f':
1060
					// Convert what was given to a float
1061
					$value = floatval( $data[ $key ] );
1062
1063
					if ( ! is_float( $value ) ) {
1064
						$data[ $key ] = $default_values[ $key ];
1065
					} else {
1066
						$data[ $key ] = $value;
1067
					}
1068
					break;
1069
1070
				default:
1071
					$data[ $key ] = sanitize_text_field( $data[ $key ] );
1072
					break;
1073
1074
			}
1075
		}
1076
1077
		return $data;
1078
	}
1079
1080
	/**
1081
	 * Attach an email to the donor
1082
	 *
1083
	 * @since  1.7
1084
	 * @access public
1085
	 *
1086
	 * @param  string $email The email address to attach to the donor
1087
	 * @param  bool $primary Allows setting the email added as the primary
1088
	 *
1089
	 * @return bool            If the email was added successfully
1090
	 */
1091
	public function add_email( $email = '', $primary = false ) {
1092
		if ( ! is_email( $email ) ) {
1093
			return false;
1094
		}
1095
		$existing = new Give_Donor( $email );
0 ignored issues
show
Documentation introduced by
$email is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1096
1097
		if ( $existing->id > 0 ) {
1098
			// Email address already belongs to another donor
1099
			return false;
1100
		}
1101
1102
		if ( email_exists( $email ) ) {
1103
			$user = get_user_by( 'email', $email );
1104
			if ( $user->ID != $this->user_id ) {
1105
				return false;
1106
			}
1107
		}
1108
1109
		do_action( 'give_donor_pre_add_email', $email, $this->id, $this );
1110
1111
		// Add is used to ensure duplicate emails are not added
1112
		$ret = (bool) $this->add_meta( 'additional_email', $email );
1113
1114
		do_action( 'give_donor_post_add_email', $email, $this->id, $this );
1115
1116
		if ( $ret && true === $primary ) {
1117
			$this->set_primary_email( $email );
1118
		}
1119
1120
		return $ret;
1121
	}
1122
1123
	/**
1124
	 * Remove an email from the donor.
1125
	 *
1126
	 * @since  1.7
1127
	 * @access public
1128
	 *
1129
	 * @param  string $email The email address to remove from the donor.
1130
	 *
1131
	 * @return bool          If the email was removed successfully.
1132
	 */
1133
	public function remove_email( $email = '' ) {
1134
		if ( ! is_email( $email ) ) {
1135
			return false;
1136
		}
1137
1138
		do_action( 'give_donor_pre_remove_email', $email, $this->id, $this );
1139
1140
		$ret = (bool) $this->delete_meta( 'additional_email', $email );
1141
1142
		do_action( 'give_donor_post_remove_email', $email, $this->id, $this );
1143
1144
		return $ret;
1145
	}
1146
1147
	/**
1148
	 * Set an email address as the donor's primary email.
1149
	 *
1150
	 * This will move the donor's previous primary email to an additional email.
1151
	 *
1152
	 * @since  1.7
1153
	 * @access public
1154
	 *
1155
	 * @param  string $new_primary_email The email address to remove from the donor.
1156
	 *
1157
	 * @return bool                      If the email was set as primary successfully.
1158
	 */
1159
	public function set_primary_email( $new_primary_email = '' ) {
1160
		if ( ! is_email( $new_primary_email ) ) {
1161
			return false;
1162
		}
1163
1164
		do_action( 'give_donor_pre_set_primary_email', $new_primary_email, $this->id, $this );
1165
1166
		$existing = new Give_Donor( $new_primary_email );
0 ignored issues
show
Documentation introduced by
$new_primary_email is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1167
1168
		if ( $existing->id > 0 && (int) $existing->id !== (int) $this->id ) {
1169
			// This email belongs to another donor.
1170
			return false;
1171
		}
1172
1173
		$old_email = $this->email;
1174
1175
		// Update donor record with new email.
1176
		$update = $this->update( array( 'email' => $new_primary_email ) );
1177
1178
		// Remove new primary from list of additional emails.
1179
		$remove = $this->remove_email( $new_primary_email );
1180
1181
		// Add old email to additional emails list.
1182
		$add = $this->add_email( $old_email );
1183
1184
		$ret = $update && $remove && $add;
1185
1186
		if ( $ret ) {
1187
			$this->email = $new_primary_email;
1188
		}
1189
1190
		do_action( 'give_donor_post_set_primary_email', $new_primary_email, $this->id, $this );
1191
1192
		return $ret;
1193
	}
1194
1195
	/**
1196
	 * Check if address valid or not.
1197
	 *
1198
	 * @since  2.0
1199
	 * @access private
1200
	 *
1201
	 * @param $address
1202
	 *
1203
	 * @return bool
1204
	 */
1205
	private function is_valid_address( $address ) {
1206
		$is_valid_address = true;
1207
		
1208
		// Address ready to process even if only one value set.
1209
		foreach ( $address as $address_type => $value ) {
1210
			// @todo: Handle state field validation on basis of country.
1211
			if( in_array( $address_type, array( 'line2', 'state' )) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
1212
				continue;
1213
			}
1214
1215
			if ( empty( $value ) ) {
1216
				$is_valid_address = false;
1217
				break;
1218
			}
1219
		}
1220
1221
		return $is_valid_address;
1222
	}
1223
1224
	/**
1225
	 * Add donor address
1226
	 *
1227
	 * @since  2.0
1228
	 * @access public
1229
	 *
1230
	 * @param string $address_type
1231
	 * @param array  $address {
1232
	 *
1233
	 * @type string  $address2
1234
	 * @type string city
1235
	 * @type string zip
1236
	 * @type string state
1237
	 * @type string country
1238
	 * }
1239
	 *
1240
	 * @return bool
1241
	 */
1242
	public function add_address( $address_type, $address ) {
1243
		// Bailout.
1244
		if ( empty( $address_type ) || ! $this->is_valid_address( $address ) || ! $this->id ) {
1245
			return false;
1246
		}
1247
1248
		// Check if multiple address exist or not and set params.
1249
		$multi_address_id = null;
1250
		if( $is_multi_address = ( false !== strpos( $address_type, '[]' ) ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
1251
			$address_type  = $is_multi_address ?
1252
				str_replace( '[]', '', $address_type ) :
1253
				$address_type;
1254
		} elseif ( $is_multi_address = ( false !== strpos( $address_type, '_' ) ) ){
1255
			$multi_address_id =  $is_multi_address ?
0 ignored issues
show
introduced by
Expected 1 space after "="; 2 found
Loading history...
1256
				array_pop( explode( '_', $address_type ) ) :
1257
				$address_type;
1258
1259
			$address_type  = $is_multi_address ?
1260
				array_shift( explode( '_', $address_type ) ) :
1261
				$address_type;
1262
		}
1263
1264
		// Bailout: do not save duplicate orders
1265
		if( $this->is_address_exist( $address_type, $address ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
1266
			return false;
1267
		}
1268
1269
		// Set default address.
1270
		$address = wp_parse_args(
1271
			$address,
1272
			array(
1273
				'line1' => '',
1274
				'line2' => '',
1275
				'city'     => '',
1276
				'state'    => '',
1277
				'country'  => '',
1278
				'zip'      => '',
1279
			)
1280
		);
1281
1282
		// Set meta key prefix.
1283
		global $wpdb;
1284
		$meta_key_prefix = "_give_donor_address_{$address_type}_{address_name}";
1285
		$meta_type = Give()->donor_meta->meta_type;
1286
1287
		if ( $is_multi_address ) {
1288
			if ( is_null( $multi_address_id ) ) {
1289
				// Get latest address key to set multi address id.
1290
				$multi_address_id = $wpdb->get_var(
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...
1291
					$wpdb->prepare(
1292
						"
1293
						SELECT meta_key FROM {$wpdb->donormeta}
1294
						WHERE meta_key
1295
						LIKE '%%%s%%'
1296
						AND {$meta_type}_id=%d
1297
						ORDER BY meta_id DESC
1298
						LIMIT 1
1299
						",
1300
						"_give_donor_address_{$address_type}_line1",
1301
						$this->id
1302
					)
1303
				);
1304
1305
				if( ! empty( $multi_address_id ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
1306
					$multi_address_id = absint( substr( strrchr( $multi_address_id, '_' ), 1 ) );
1307
					$multi_address_id++;
1308
				} else{
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
1309
					$multi_address_id = 0;
1310
				}
1311
			}
1312
1313
			$meta_key_prefix = "_give_donor_address_{$address_type}_{address_name}_{$multi_address_id}";
1314
		}
1315
1316
		// Save donor address.
1317
		foreach ( $address as $type => $value ) {
1318
			$meta_key = str_replace( '{address_name}', $type, $meta_key_prefix );
1319
			Give()->donor_meta->update_meta( $this->id, $meta_key, $value );
1320
		}
1321
1322
		$this->setup_address();
1323
1324
		return true;
1325
	}
1326
1327
	/**
1328
	 * Remove donor address
1329
	 *
1330
	 * @since  2.0
1331
	 * @access public
1332
	 * @global wpdb  $wpdb
1333
	 *
1334
	 * @param string $address_id
1335
	 *
1336
	 * @return bool
1337
	 */
1338
	public function remove_address( $address_id ) {
1339
		global $wpdb;
1340
1341
		// Get address type.
1342
		$is_multi_address = false !== strpos( $address_id, '_' ) ? true : false;
1343
1344
		$address_type = false !== strpos( $address_id, '_' ) ?
1345
			array_shift( explode( '_', $address_id ) ) :
1346
			$address_id;
1347
1348
		$address_count = false !== strpos( $address_id, '_' ) ?
1349
			array_pop( explode( '_', $address_id ) ) :
1350
			null;
1351
1352
		// Set meta key prefix.
1353
		$meta_key_prefix = "_give_donor_address_{$address_type}_%";
1354
		if ( $is_multi_address && is_numeric( $address_count ) ) {
1355
			$meta_key_prefix .= "_{$address_count}";
1356
		}
1357
1358
		$meta_type = Give()->donor_meta->meta_type;
1359
1360
		// Process query.
1361
		$row_affected = $wpdb->query(
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...
1362
			$wpdb->prepare(
1363
				"
1364
				DELETE FROM {$wpdb->donormeta}
1365
				WHERE meta_key
1366
				LIKE '%s'
1367
				AND {$meta_type}_id=%d
1368
				",
1369
				$meta_key_prefix,
1370
				$this->id
1371
			)
1372
		);
1373
1374
		$this->setup_address();
1375
1376
		return (bool) $row_affected;
1377
	}
1378
1379
	/**
1380
	 * Update donor address
1381
	 *
1382
	 * @since  2.0
1383
	 * @access public
1384
	 * @global wpdb  $wpdb
1385
	 *
1386
	 * @param string $address_id
1387
	 * @param array  $address
1388
	 *
1389
	 * @return bool
1390
	 */
1391
	public function update_address( $address_id, $address ) {
1392
		global $wpdb;
1393
1394
		// Get address type.
1395
		$is_multi_address = false !== strpos( $address_id, '_' ) ? true : false;
1396
1397
		$address_type = false !== strpos( $address_id, '_' ) ?
1398
			array_shift( explode( '_', $address_id ) ) :
1399
			$address_id;
1400
1401
		$address_count = false !== strpos( $address_id, '_' ) ?
1402
			array_pop( explode( '_', $address_id ) ) :
1403
			null;
1404
1405
		// Set meta key prefix.
1406
		$meta_key_prefix = "_give_donor_address_{$address_type}_%";
1407
		if ( $is_multi_address && is_numeric( $address_count ) ) {
1408
			$meta_key_prefix .= "_{$address_count}";
1409
		}
1410
1411
		$meta_type = Give()->donor_meta->meta_type;
1412
1413
		// Process query.
1414
		$row_affected = $wpdb->get_results(
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...
1415
			$wpdb->prepare(
1416
				"
1417
				SELECT meta_key FROM {$wpdb->donormeta}
1418
				WHERE meta_key
1419
				LIKE '%s'
1420
				AND {$meta_type}_id=%d
1421
				",
1422
				$meta_key_prefix,
1423
				$this->id
1424
			)
1425
		);
1426
		
1427
		// Return result.
1428
		if( ! count( $row_affected ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
1429
			return false;
1430
		}
1431
1432
		// Update address.
1433
		if( ! $this->add_address( $address_id, $address ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
1434
			return false;
1435
		}
1436
1437
		return true;
1438
	}
1439
1440
1441
	/**
1442
	 * Check if donor already has current address
1443
	 *
1444
	 * @since 2.0
1445
	 * @access public
1446
	 *
1447
	 * @param string $current_address_type
1448
	 * @param array $current_address
1449
	 *
1450
	 * @return bool|null
1451
	 */
1452
	public function is_address_exist( $current_address_type, $current_address ) {
1453
		$status = false;
1454
1455
		// Bailout.
1456
		if( empty( $current_address_type ) || empty( $current_address ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
1457
			return null;
1458
		}
1459
1460
		// Bailout.
1461
		if( empty( $this->address ) || empty( $this->address[ $current_address_type ] ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
1462
			return $status;
1463
		}
1464
1465
		// Get address.
1466
		$address = $this->address[ $current_address_type ];
1467
1468
		switch ( true ){
1469
1470
			// Single address.
1471
			case is_string( end( $address ) ) :
1472
				$status = $this->is_address_match( $current_address, $address );
1473
				break;
1474
1475
			// Multi address.
1476
			case is_array( end( $address ) ):
1477
				// Compare address.
1478
				foreach ( $address as $saved_address ) {
1479
					if( empty( $saved_address ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
1480
						continue;
1481
					}
1482
1483
					// Exit loop immediately if address exist.
1484
					if( $status = $this->is_address_match( $current_address, $saved_address ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
1485
						break;
1486
					}
1487
				}
1488
				break;
1489
		}
1490
1491
		return $status;
1492
	}
1493
1494
	/**
1495
	 * Compare address.
1496
	 *
1497
	 * @since  2.0
1498
	 * @access private
1499
	 *
1500
	 * @param array $address_1
1501
	 * @param array $address_2
1502
	 *
1503
	 * @return bool
1504
	 */
1505
	private function is_address_match( $address_1, $address_2 ) {
1506
		$result = array_diff( $address_1, $address_2 );
1507
1508
		return empty( $result );
1509
	}
1510
1511
	/**
1512
	 * Split donor name into first name and last name
1513
	 *
1514
	 * @param   int     $id     Donor ID
1515
	 * @since   2.0
1516
	 * @return  object
1517
	 */
1518
	public function split_donor_name( $id ) {
1519
		$first_name = $last_name  = '';
1520
		$donor      = new Give_Donor( $id );
0 ignored issues
show
Documentation introduced by
$id is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1521
1522
		$split_donor_name = explode( ' ', $donor->name, 2 );
1523
1524
		// Check for existence of first name after split of donor name.
1525
		if( is_array( $split_donor_name ) && ! empty( $split_donor_name[0] ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
1526
			$first_name = $split_donor_name[0];
1527
		}
1528
1529
		// Check for existence of last name after split of donor name.
1530
		if( is_array( $split_donor_name ) && ! empty( $split_donor_name[1] ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
1531
			$last_name = $split_donor_name[1];
1532
		}
1533
		return (object) array( 'first_name' => $first_name, 'last_name' => $last_name );
1534
	}
1535
1536
	/**
1537
	 * Retrieves first name of donor with backward compatibility
1538
	 *
1539
	 * @since   2.0
1540
	 * @return  string
1541
	 */
1542
	public function get_first_name() {
1543
		$first_name = $this->get_meta( '_give_donor_first_name');
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
1544
		if( ! $first_name ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
1545
			$first_name = $this->split_donor_name( $this->id )->first_name;
1546
		}
1547
1548
		return $first_name;
1549
	}
1550
1551
	/**
1552
	 * Retrieves last name of donor with backward compatibility
1553
	 *
1554
	 * @since   2.0
1555
	 * @return  string
1556
	 */
1557
	public function get_last_name() {
1558
		$first_name = $this->get_meta( '_give_donor_first_name');
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
1559
		$last_name = $this->get_meta( '_give_donor_last_name');
0 ignored issues
show
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
1560
1561
		// This condition will prevent unnecessary splitting of donor name to fetch last name.
1562
		if( ! $first_name && ! $last_name ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
1563
			$last_name = $this->split_donor_name( $this->id )->last_name;
1564
		}
1565
1566
		return ( $last_name ) ? $last_name : '';
1567
	}
1568
}
1569