Test Failed
Pull Request — master (#3256)
by Devin
07:52 queued 10s
created

Give_Donor::update_donation_value()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 23
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 2
dl 0
loc 23
rs 9.0856
c 0
b 0
f 0
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 (
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
	 * Returns the saved address for a donor
283
	 *
284
	 * @access public
285
	 *
286
	 * @since  2.1.3
287
	 *
288
	 * @param array $args donor address.
289
	 *
290
	 * @return array The donor's address, if any
291
	 */
292
	public function get_donor_address( $args = array() ) {
293
		$args = wp_parse_args(
294
			$args,
295
			array(
296
				'address_type' => 'billing',
297
			)
298
		);
299
300
		$default_address = array(
301
			'line1'   => '',
302
			'line2'   => '',
303
			'city'    => '',
304
			'state'   => '',
305
			'country' => '',
306
			'zip'     => '',
307
		);
308
309
		// Backward compatibility.
310 View Code Duplication
		if ( ! give_has_upgrade_completed( 'v20_upgrades_user_address' ) ) {
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...
311
312
			// Backward compatibility for user id param.
313
			return wp_parse_args( (array) get_user_meta( $this->user_id, '_give_user_address', true ), $default_address );
0 ignored issues
show
introduced by
get_user_meta() usage is highly discouraged, check VIP documentation on "Working with wp_users"
Loading history...
314
315
		}
316
317
		if ( ! $this->id || empty( $this->address ) || ! array_key_exists( $args['address_type'], $this->address ) ) {
318
			return $default_address;
319
		}
320
321
		switch ( true ) {
322 View Code Duplication
			case is_string( end( $this->address[ $args['address_type'] ] ) ):
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...
323
				$address = wp_parse_args( $this->address[ $args['address_type'] ], $default_address );
324
				break;
325
326 View Code Duplication
			case is_array( end( $this->address[ $args['address_type'] ] ) ):
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...
327
				$address = wp_parse_args( array_shift( $this->address[ $args['address_type'] ] ), $default_address );
328
				break;
329
		}
330
331
		return $address;
332
	}
333
334
	/**
335
	 * Magic __get function to dispatch a call to retrieve a private property.
336
	 *
337
	 * @since  1.0
338
	 * @access public
339
	 * @param $key
340
	 *
341
	 * @return mixed|\WP_Error
342
	 */
343 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...
344
345
		if ( method_exists( $this, 'get_' . $key ) ) {
346
347
			return call_user_func( array( $this, 'get_' . $key ) );
348
349
		} else {
350
351
			/* translators: %s: property key */
352
			return new WP_Error( 'give-donor-invalid-property', sprintf( esc_html__( 'Can\'t get property %s.', 'give' ), $key ) );
353
354
		}
355
356
	}
357
358
	/**
359
	 * Creates a donor.
360
	 *
361
	 * @since  1.0
362
	 * @access public
363
	 *
364
	 * @param  array $data Array of attributes for a donor.
365
	 *
366
	 * @return bool|int    False if not a valid creation, donor ID if user is found or valid creation.
367
	 */
368
	public function create( $data = array() ) {
369
370
		if ( $this->id != 0 || empty( $data ) ) {
0 ignored issues
show
introduced by
Found "!= 0". Use Yoda Condition checks, you must
Loading history...
371
			return false;
372
		}
373
374
		$defaults = array(
375
			'payment_ids' => '',
376
		);
377
378
		$args = wp_parse_args( $data, $defaults );
379
		$args = $this->sanitize_columns( $args );
380
381
		if ( empty( $args['email'] ) || ! is_email( $args['email'] ) ) {
382
			return false;
383
		}
384
385 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...
386
			$args['payment_ids'] = implode( ',', array_unique( array_values( $args['payment_ids'] ) ) );
387
		}
388
389
		/**
390
		 * Fires before creating donors.
391
		 *
392
		 * @since 1.0
393
		 *
394
		 * @param array $args Donor attributes.
395
		 */
396
		do_action( 'give_donor_pre_create', $args );
397
398
		$created = false;
399
400
		// The DB class 'add' implies an update if the donor being asked to be created already exists
401 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...
402
403
			// We've successfully added/updated the donor, reset the class vars with the new data
404
			$donor = $this->db->get_donor_by( 'email', $args['email'] );
405
406
			// Setup the donor data with the values from DB
407
			$this->setup_donor( $donor );
408
409
			$created = $this->id;
410
		}
411
412
		/**
413
		 * Fires after creating donors.
414
		 *
415
		 * @since 1.0
416
		 *
417
		 * @param bool|int $created False if not a valid creation, donor ID if user is found or valid creation.
418
		 * @param array $args Customer attributes.
419
		 */
420
		do_action( 'give_donor_post_create', $created, $args );
421
422
		return $created;
423
424
	}
425
426
	/**
427
	 * Updates a donor record.
428
	 *
429
	 * @since  1.0
430
	 * @access public
431
	 *
432
	 * @param  array $data Array of data attributes for a donor (checked via whitelist).
433
	 *
434
	 * @return bool        If the update was successful or not.
435
	 */
436
	public function update( $data = array() ) {
437
438
		if ( empty( $data ) ) {
439
			return false;
440
		}
441
442
		$data = $this->sanitize_columns( $data );
443
444
		/**
445
		 * Fires before updating donors.
446
		 *
447
		 * @since 1.0
448
		 *
449
		 * @param int $donor_id Donor id.
450
		 * @param array $data Donor attributes.
451
		 */
452
		do_action( 'give_donor_pre_update', $this->id, $data );
453
454
		$updated = false;
455
456 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...
457
458
			$donor = $this->db->get_donor_by( 'id', $this->id );
459
460
			$this->setup_donor( $donor );
461
462
			$updated = true;
463
		}
464
465
		/**
466
		 * Fires after updating donors.
467
		 *
468
		 * @since 1.0
469
		 *
470
		 * @param bool $updated If the update was successful or not.
471
		 * @param int $donor_id Donor id.
472
		 * @param array $data Donor attributes.
473
		 */
474
		do_action( 'give_donor_post_update', $updated, $this->id, $data );
475
476
		return $updated;
477
	}
478
479
	/**
480
	 * Attach Payment
481
	 *
482
	 * Attach payment to the donor then triggers increasing stats.
483
	 *
484
	 * @since  1.0
485
	 * @access public
486
	 *
487
	 * @param  int $payment_id The payment ID to attach to the donor.
488
	 * @param  bool $update_stats For backwards compatibility, if we should increase the stats or not.
489
	 *
490
	 * @return bool            If the attachment was successfully.
491
	 */
492
	public function attach_payment( $payment_id = 0, $update_stats = true ) {
493
494
		if ( empty( $payment_id ) ) {
495
			return false;
496
		}
497
498
		if ( empty( $this->payment_ids ) ) {
499
500
			$new_payment_ids = $payment_id;
501
502
		} else {
503
504
			$payment_ids = array_map( 'absint', explode( ',', $this->payment_ids ) );
505
506
			if ( in_array( $payment_id, $payment_ids ) ) {
507
				$update_stats = false;
508
			}
509
510
			$payment_ids[] = $payment_id;
511
512
			$new_payment_ids = implode( ',', array_unique( array_values( $payment_ids ) ) );
513
514
		}
515
516
		/**
517
		 * Fires before attaching payments to customers.
518
		 *
519
		 * @since 1.0
520
		 *
521
		 * @param int $payment_id Payment id.
522
		 * @param int $donor_id Customer id.
523
		 */
524
		do_action( 'give_donor_pre_attach_payment', $payment_id, $this->id );
525
526
		$payment_added = $this->update( array( 'payment_ids' => $new_payment_ids ) );
527
528 View Code Duplication
		if ( $payment_added ) {
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...
529
530
			$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...
531
532
			// We added this payment successfully, increment the stats
533
			if ( $update_stats ) {
534
				$payment_amount = give_donation_amount( $payment_id, array( 'type' => 'stats' ) );
535
536
				if ( ! empty( $payment_amount ) ) {
537
					$this->increase_value( $payment_amount );
538
				}
539
540
				$this->increase_purchase_count();
541
			}
542
		}
543
544
		/**
545
		 * Fires after attaching payments to the donor.
546
		 *
547
		 * @since 1.0
548
		 *
549
		 * @param bool $payment_added If the attachment was successfully.
550
		 * @param int $payment_id Payment id.
551
		 * @param int $donor_id Donor id.
552
		 */
553
		do_action( 'give_donor_post_attach_payment', $payment_added, $payment_id, $this->id );
554
555
		return $payment_added;
556
	}
557
558
	/**
559
	 * Remove Payment
560
	 *
561
	 * Remove a payment from this donor, then triggers reducing stats.
562
	 *
563
	 * @since  1.0
564
	 * @access public
565
	 *
566
	 * @param  int $payment_id The Payment ID to remove.
567
	 * @param  bool $update_stats For backwards compatibility, if we should increase the stats or not.
568
	 *
569
	 * @return boolean               If the removal was successful.
570
	 */
571
	public function remove_payment( $payment_id = 0, $update_stats = true ) {
572
573
		if ( empty( $payment_id ) ) {
574
			return false;
575
		}
576
577
		$payment = new Give_Payment( $payment_id );
578
579
		if ( 'publish' !== $payment->status && 'revoked' !== $payment->status ) {
580
			$update_stats = false;
581
		}
582
583
		$new_payment_ids = '';
584
585
		if ( ! empty( $this->payment_ids ) ) {
586
587
			$payment_ids = array_map( 'absint', explode( ',', $this->payment_ids ) );
588
589
			$pos = array_search( $payment_id, $payment_ids );
590
			if ( false === $pos ) {
591
				return false;
592
			}
593
594
			unset( $payment_ids[ $pos ] );
595
			$payment_ids = array_filter( $payment_ids );
596
597
			$new_payment_ids = implode( ',', array_unique( array_values( $payment_ids ) ) );
598
599
		}
600
601
		/**
602
		 * Fires before removing payments from customers.
603
		 *
604
		 * @since 1.0
605
		 *
606
		 * @param int $payment_id Payment id.
607
		 * @param int $donor_id Customer id.
608
		 */
609
		do_action( 'give_donor_pre_remove_payment', $payment_id, $this->id );
610
611
		$payment_removed = $this->update( array( 'payment_ids' => $new_payment_ids ) );
612
613 View Code Duplication
		if ( $payment_removed ) {
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...
614
615
			$this->payment_ids = $new_payment_ids;
616
617
			if ( $update_stats ) {
618
				// We removed this payment successfully, decrement the stats
619
				$payment_amount = give_donation_amount( $payment_id );
620
621
				if ( ! empty( $payment_amount ) ) {
622
					$this->decrease_value( $payment_amount );
623
				}
624
625
				$this->decrease_donation_count();
626
			}
627
		}
628
629
		/**
630
		 * Fires after removing payments from donors.
631
		 *
632
		 * @since 1.0
633
		 *
634
		 * @param bool $payment_removed If the removal was successfully.
635
		 * @param int $payment_id Payment id.
636
		 * @param int $donor_id Donor id.
637
		 */
638
		do_action( 'give_donor_post_remove_payment', $payment_removed, $payment_id, $this->id );
639
640
		return $payment_removed;
641
642
	}
643
644
	/**
645
	 * Increase the donation count of a donor.
646
	 *
647
	 * @since  1.0
648
	 * @access public
649
	 *
650
	 * @param  int $count The number to increase by.
651
	 *
652
	 * @return int        The donation count.
653
	 */
654
	public function increase_purchase_count( $count = 1 ) {
655
656
		// Make sure it's numeric and not negative.
657
		if ( ! is_numeric( $count ) || $count != absint( $count ) ) {
658
			return false;
659
		}
660
661
		$new_total = (int) $this->purchase_count + (int) $count;
662
663
		/**
664
		 * Fires before increasing the donor's donation count.
665
		 *
666
		 * @since 1.0
667
		 *
668
		 * @param int $count The number to increase by.
669
		 * @param int $donor_id Donor id.
670
		 */
671
		do_action( 'give_donor_pre_increase_donation_count', $count, $this->id );
672
673
		if ( $this->update( array( 'purchase_count' => $new_total ) ) ) {
674
			$this->purchase_count = $new_total;
675
		}
676
677
		/**
678
		 * Fires after increasing the donor's donation count.
679
		 *
680
		 * @since 1.0
681
		 *
682
		 * @param int $purchase_count Donor donation count.
683
		 * @param int $count The number increased by.
684
		 * @param int $donor_id Donor id.
685
		 */
686
		do_action( 'give_donor_post_increase_donation_count', $this->purchase_count, $count, $this->id );
687
688
		return $this->purchase_count;
689
	}
690
691
	/**
692
	 * Decrease the donor donation count.
693
	 *
694
	 * @since  1.0
695
	 * @access public
696
	 *
697
	 * @param  int $count The amount to decrease by.
698
	 *
699
	 * @return mixed      If successful, the new count, otherwise false.
700
	 */
701
	public function decrease_donation_count( $count = 1 ) {
702
703
		// Make sure it's numeric and not negative
704
		if ( ! is_numeric( $count ) || $count != absint( $count ) ) {
705
			return false;
706
		}
707
708
		$new_total = (int) $this->purchase_count - (int) $count;
709
710
		if ( $new_total < 0 ) {
711
			$new_total = 0;
712
		}
713
714
		/**
715
		 * Fires before decreasing the donor's donation count.
716
		 *
717
		 * @since 1.0
718
		 *
719
		 * @param int $count The number to decrease by.
720
		 * @param int $donor_id Customer id.
721
		 */
722
		do_action( 'give_donor_pre_decrease_donation_count', $count, $this->id );
723
724
		if ( $this->update( array( 'purchase_count' => $new_total ) ) ) {
725
			$this->purchase_count = $new_total;
726
		}
727
728
		/**
729
		 * Fires after decreasing the donor's donation count.
730
		 *
731
		 * @since 1.0
732
		 *
733
		 * @param int $purchase_count Donor's donation count.
734
		 * @param int $count The number decreased by.
735
		 * @param int $donor_id Donor id.
736
		 */
737
		do_action( 'give_donor_post_decrease_donation_count', $this->purchase_count, $count, $this->id );
738
739
		return $this->purchase_count;
740
	}
741
742
	/**
743
	 * Increase the donor's lifetime value.
744
	 *
745
	 * @since  1.0
746
	 * @access public
747
	 *
748
	 * @param  float $value The value to increase by.
749
	 *
750
	 * @return mixed        If successful, the new value, otherwise false.
751
	 */
752
	public function increase_value( $value = 0.00 ) {
753
754
		$new_value = floatval( $this->purchase_value ) + $value;
755
756
		/**
757
		 * Fires before increasing donor lifetime value.
758
		 *
759
		 * @since 1.0
760
		 *
761
		 * @param float $value The value to increase by.
762
		 * @param int $donor_id Customer id.
763
		 */
764
		do_action( 'give_donor_pre_increase_value', $value, $this->id );
765
766
		if ( $this->update( array( 'purchase_value' => $new_value ) ) ) {
767
			$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...
768
		}
769
770
		/**
771
		 * Fires after increasing donor lifetime value.
772
		 *
773
		 * @since 1.0
774
		 *
775
		 * @param float $purchase_value Donor's lifetime value.
776
		 * @param float $value The value increased by.
777
		 * @param int $donor_id Donor id.
778
		 */
779
		do_action( 'give_donor_post_increase_value', $this->purchase_value, $value, $this->id );
780
781
		return $this->purchase_value;
782
	}
783
784
	/**
785
	 * Decrease a donor's lifetime value.
786
	 *
787
	 * @since  1.0
788
	 * @access public
789
	 *
790
	 * @param  float $value The value to decrease by.
791
	 *
792
	 * @return mixed        If successful, the new value, otherwise false.
793
	 */
794
	public function decrease_value( $value = 0.00 ) {
795
796
		$new_value = floatval( $this->purchase_value ) - $value;
797
798
		if ( $new_value < 0 ) {
799
			$new_value = 0.00;
800
		}
801
802
		/**
803
		 * Fires before decreasing donor lifetime value.
804
		 *
805
		 * @since 1.0
806
		 *
807
		 * @param float $value The value to decrease by.
808
		 * @param int $donor_id Donor id.
809
		 */
810
		do_action( 'give_donor_pre_decrease_value', $value, $this->id );
811
812
		if ( $this->update( array( 'purchase_value' => $new_value ) ) ) {
813
			$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...
814
		}
815
816
		/**
817
		 * Fires after decreasing donor lifetime value.
818
		 *
819
		 * @since 1.0
820
		 *
821
		 * @param float $purchase_value Donor lifetime value.
822
		 * @param float $value The value decreased by.
823
		 * @param int $donor_id Donor id.
824
		 */
825
		do_action( 'give_donor_post_decrease_value', $this->purchase_value, $value, $this->id );
826
827
		return $this->purchase_value;
828
	}
829
830
	/**
831
	 * Decrease/Increase a donor's lifetime value.
832
	 *
833
	 * This function will update donation stat on basis of current amount and new amount donation difference.
834
	 * Difference value can positive or negative. Negative value will decrease user donation stat while positive value increase donation stat.
835
	 *
836
	 * @since  1.0
837
	 * @access public
838
	 *
839
	 * @param  float $curr_amount Current Donation amount.
840
	 * @param  float $new_amount New (changed) Donation amount.
841
	 *
842
	 * @return mixed              If successful, the new donation stat value, otherwise false.
843
	 */
844
	public function update_donation_value( $curr_amount, $new_amount ) {
845
		/**
846
		 * Payment total difference value can be:
847
		 *  zero   (in case amount not change)
848
		 *  or -ve (in case amount decrease)
849
		 *  or +ve (in case amount increase)
850
		 */
851
		$payment_total_diff = $new_amount - $curr_amount;
852
853
		// We do not need to update donation stat if donation did not change.
854
		if ( ! $payment_total_diff ) {
855
			return false;
856
		}
857
858
		if ( $payment_total_diff > 0 ) {
859
			$this->increase_value( $payment_total_diff );
860
		} else {
861
			// Pass payment total difference as +ve value to decrease amount from user lifetime stat.
862
			$this->decrease_value( - $payment_total_diff );
863
		}
864
865
		return $this->purchase_value;
866
	}
867
868
	/**
869
	 * Get the parsed notes for a donor as an array.
870
	 *
871
	 * @since  1.0
872
	 * @access public
873
	 *
874
	 * @param  int $length The number of notes to get.
875
	 * @param  int $paged What note to start at.
876
	 *
877
	 * @return array       The notes requested.
878
	 */
879
	public function get_notes( $length = 20, $paged = 1 ) {
880
881
		$length = is_numeric( $length ) ? $length : 20;
882
		$offset = is_numeric( $paged ) && $paged != 1 ? ( ( absint( $paged ) - 1 ) * $length ) : 0;
883
884
		$all_notes   = $this->get_raw_notes();
885
		$notes_array = array_reverse( array_filter( explode( "\n\n", $all_notes ) ) );
886
887
		$desired_notes = array_slice( $notes_array, $offset, $length );
888
889
		return $desired_notes;
890
891
	}
892
893
	/**
894
	 * Get the total number of notes we have after parsing.
895
	 *
896
	 * @since  1.0
897
	 * @access public
898
	 *
899
	 * @return int The number of notes for the donor.
900
	 */
901
	public function get_notes_count() {
902
903
		$all_notes   = $this->get_raw_notes();
904
		$notes_array = array_reverse( array_filter( explode( "\n\n", $all_notes ) ) );
905
906
		return count( $notes_array );
907
908
	}
909
910
	/**
911
	 * Get the total donation amount.
912
	 *
913
	 * @since 1.8.17
914
	 *
915
	 * @param array $args Pass any additional data.
916
	 *
917
	 * @return string|float
918
	 */
919
	public function get_total_donation_amount( $args = array() ) {
920
921
		/**
922
		 * Filter total donation amount.
923
		 *
924
		 * @since 1.8.17
925
		 *
926
		 * @param string|float $purchase_value Donor Purchase value.
927
		 * @param integer      $donor_id       Donor ID.
928
		 * @param array        $args           Pass additional data.
929
		 */
930
		return apply_filters( 'give_get_total_donation_amount', $this->purchase_value, $this->id, $args );
931
	}
932
933
	/**
934
	 * Add a note for the donor.
935
	 *
936
	 * @since  1.0
937
	 * @access public
938
	 *
939
	 * @param  string $note The note to add. Default is empty.
940
	 *
941
	 * @return string|boolean The new note if added successfully, false otherwise.
942
	 */
943
	public function add_note( $note = '' ) {
944
945
		$note = trim( $note );
946
		if ( empty( $note ) ) {
947
			return false;
948
		}
949
950
		$notes = $this->get_raw_notes();
951
952
		if ( empty( $notes ) ) {
953
			$notes = '';
954
		}
955
956
		$note_string = date_i18n( 'F j, Y H:i:s', current_time( 'timestamp' ) ) . ' - ' . $note;
957
		$new_note    = apply_filters( 'give_customer_add_note_string', $note_string );
958
		$notes       .= "\n\n" . $new_note;
959
960
		/**
961
		 * Fires before donor note is added.
962
		 *
963
		 * @since 1.0
964
		 *
965
		 * @param string $new_note New note to add.
966
		 * @param int $donor_id Donor id.
967
		 */
968
		do_action( 'give_donor_pre_add_note', $new_note, $this->id );
969
970
		$updated = $this->update( array( 'notes' => $notes ) );
971
972
		if ( $updated ) {
973
			$this->notes = $this->get_notes();
974
		}
975
976
		/**
977
		 * Fires after donor note added.
978
		 *
979
		 * @since 1.0
980
		 *
981
		 * @param array $donor_notes Donor notes.
982
		 * @param string $new_note New note added.
983
		 * @param int $donor_id Donor id.
984
		 */
985
		do_action( 'give_donor_post_add_note', $this->notes, $new_note, $this->id );
986
987
		// Return the formatted note, so we can test, as well as update any displays
988
		return $new_note;
989
990
	}
991
992
	/**
993
	 * Get the notes column for the donor
994
	 *
995
	 * @since  1.0
996
	 * @access private
997
	 *
998
	 * @return string The Notes for the donor, non-parsed.
999
	 */
1000
	private function get_raw_notes() {
1001
1002
		$all_notes = $this->db->get_column( 'notes', $this->id );
1003
1004
		return $all_notes;
1005
1006
	}
1007
1008
	/**
1009
	 * Retrieve a meta field for a donor.
1010
	 *
1011
	 * @since  1.6
1012
	 * @access public
1013
	 *
1014
	 * @param  string $meta_key The meta key to retrieve. Default is empty.
1015
	 * @param  bool $single Whether to return a single value. Default is true.
1016
	 *
1017
	 * @return mixed            Will be an array if $single is false. Will be value of meta data field if $single is true.
1018
	 */
1019
	public function get_meta( $meta_key = '', $single = true ) {
1020
		return Give()->donor_meta->get_meta( $this->id, $meta_key, $single );
1021
	}
1022
1023
	/**
1024
	 * Add a meta data field to a donor.
1025
	 *
1026
	 * @since  1.6
1027
	 * @access public
1028
	 *
1029
	 * @param  string $meta_key Metadata name. Default is empty.
1030
	 * @param  mixed $meta_value Metadata value.
1031
	 * @param  bool $unique Optional. Whether the same key should not be added. Default is false.
1032
	 *
1033
	 * @return bool               False for failure. True for success.
1034
	 */
1035
	public function add_meta( $meta_key = '', $meta_value, $unique = false ) {
1036
		return Give()->donor_meta->add_meta( $this->id, $meta_key, $meta_value, $unique );
1037
	}
1038
1039
	/**
1040
	 * Update a meta field based on donor ID.
1041
	 *
1042
	 * @since  1.6
1043
	 * @access public
1044
	 *
1045
	 * @param  string $meta_key Metadata key. Default is empty.
1046
	 * @param  mixed $meta_value Metadata value.
1047
	 * @param  mixed $prev_value Optional. Previous value to check before removing. Default is empty.
1048
	 *
1049
	 * @return bool               False on failure, true if success.
1050
	 */
1051
	public function update_meta( $meta_key = '', $meta_value, $prev_value = '' ) {
1052
		return Give()->donor_meta->update_meta( $this->id, $meta_key, $meta_value, $prev_value );
1053
	}
1054
1055
	/**
1056
	 * Remove metadata matching criteria from a donor.
1057
	 *
1058
	 * @since  1.6
1059
	 * @access public
1060
	 *
1061
	 * @param  string $meta_key Metadata name. Default is empty.
1062
	 * @param  mixed $meta_value Optional. Metadata value. Default is empty.
1063
	 *
1064
	 * @return bool               False for failure. True for success.
1065
	 */
1066
	public function delete_meta( $meta_key = '', $meta_value = '' ) {
1067
		return Give()->donor_meta->delete_meta( $this->id, $meta_key, $meta_value );
1068
	}
1069
1070
	/**
1071
	 * Sanitize the data for update/create
1072
	 *
1073
	 * @since  1.0
1074
	 * @access private
1075
	 *
1076
	 * @param  array $data The data to sanitize.
1077
	 *
1078
	 * @return array       The sanitized data, based off column defaults.
1079
	 */
1080
	private function sanitize_columns( $data ) {
1081
1082
		$columns        = $this->db->get_columns();
1083
		$default_values = $this->db->get_column_defaults();
1084
1085
		foreach ( $columns as $key => $type ) {
1086
1087
			// Only sanitize data that we were provided
1088
			if ( ! array_key_exists( $key, $data ) ) {
1089
				continue;
1090
			}
1091
1092
			switch ( $type ) {
1093
1094
				case '%s':
1095
					if ( 'email' == $key ) {
1096
						$data[ $key ] = sanitize_email( $data[ $key ] );
1097
					} elseif ( 'notes' == $key ) {
1098
						$data[ $key ] = strip_tags( $data[ $key ] );
1099
					} else {
1100
						$data[ $key ] = sanitize_text_field( $data[ $key ] );
1101
					}
1102
					break;
1103
1104
				case '%d':
1105
					if ( ! is_numeric( $data[ $key ] ) || (int) $data[ $key ] !== absint( $data[ $key ] ) ) {
1106
						$data[ $key ] = $default_values[ $key ];
1107
					} else {
1108
						$data[ $key ] = absint( $data[ $key ] );
1109
					}
1110
					break;
1111
1112
				case '%f':
1113
					// Convert what was given to a float
1114
					$value = floatval( $data[ $key ] );
1115
1116
					if ( ! is_float( $value ) ) {
1117
						$data[ $key ] = $default_values[ $key ];
1118
					} else {
1119
						$data[ $key ] = $value;
1120
					}
1121
					break;
1122
1123
				default:
1124
					$data[ $key ] = sanitize_text_field( $data[ $key ] );
1125
					break;
1126
1127
			}
1128
		}
1129
1130
		return $data;
1131
	}
1132
1133
	/**
1134
	 * Attach an email to the donor
1135
	 *
1136
	 * @since  1.7
1137
	 * @access public
1138
	 *
1139
	 * @param  string $email The email address to attach to the donor
1140
	 * @param  bool $primary Allows setting the email added as the primary
1141
	 *
1142
	 * @return bool            If the email was added successfully
1143
	 */
1144
	public function add_email( $email = '', $primary = false ) {
1145
		if ( ! is_email( $email ) ) {
1146
			return false;
1147
		}
1148
		$existing = new Give_Donor( $email );
1149
1150
		if ( $existing->id > 0 ) {
1151
			// Email address already belongs to another donor
1152
			return false;
1153
		}
1154
1155
		if ( email_exists( $email ) ) {
1156
			$user = get_user_by( 'email', $email );
1157
			if ( $user->ID != $this->user_id ) {
1158
				return false;
1159
			}
1160
		}
1161
1162
		do_action( 'give_donor_pre_add_email', $email, $this->id, $this );
1163
1164
		// Add is used to ensure duplicate emails are not added
1165
		$ret = (bool) $this->add_meta( 'additional_email', $email );
1166
1167
		do_action( 'give_donor_post_add_email', $email, $this->id, $this );
1168
1169
		if ( $ret && true === $primary ) {
1170
			$this->set_primary_email( $email );
1171
		}
1172
1173
		return $ret;
1174
	}
1175
1176
	/**
1177
	 * Remove an email from the donor.
1178
	 *
1179
	 * @since  1.7
1180
	 * @access public
1181
	 *
1182
	 * @param  string $email The email address to remove from the donor.
1183
	 *
1184
	 * @return bool          If the email was removed successfully.
1185
	 */
1186
	public function remove_email( $email = '' ) {
1187
		if ( ! is_email( $email ) ) {
1188
			return false;
1189
		}
1190
1191
		do_action( 'give_donor_pre_remove_email', $email, $this->id, $this );
1192
1193
		$ret = (bool) $this->delete_meta( 'additional_email', $email );
1194
1195
		do_action( 'give_donor_post_remove_email', $email, $this->id, $this );
1196
1197
		return $ret;
1198
	}
1199
1200
	/**
1201
	 * Set an email address as the donor's primary email.
1202
	 *
1203
	 * This will move the donor's previous primary email to an additional email.
1204
	 *
1205
	 * @since  1.7
1206
	 * @access public
1207
	 *
1208
	 * @param  string $new_primary_email The email address to remove from the donor.
1209
	 *
1210
	 * @return bool                      If the email was set as primary successfully.
1211
	 */
1212
	public function set_primary_email( $new_primary_email = '' ) {
1213
		if ( ! is_email( $new_primary_email ) ) {
1214
			return false;
1215
		}
1216
1217
		do_action( 'give_donor_pre_set_primary_email', $new_primary_email, $this->id, $this );
1218
1219
		$existing = new Give_Donor( $new_primary_email );
1220
1221
		if ( $existing->id > 0 && (int) $existing->id !== (int) $this->id ) {
1222
			// This email belongs to another donor.
1223
			return false;
1224
		}
1225
1226
		$old_email = $this->email;
1227
1228
		// Update donor record with new email.
1229
		$update = $this->update( array( 'email' => $new_primary_email ) );
1230
1231
		// Remove new primary from list of additional emails.
1232
		$remove = $this->remove_email( $new_primary_email );
1233
1234
		// Add old email to additional emails list.
1235
		$add = $this->add_email( $old_email );
1236
1237
		$ret = $update && $remove && $add;
1238
1239
		if ( $ret ) {
1240
			$this->email = $new_primary_email;
1241
		}
1242
1243
		do_action( 'give_donor_post_set_primary_email', $new_primary_email, $this->id, $this );
1244
1245
		return $ret;
1246
	}
1247
1248
	/**
1249
	 * Check if address valid or not.
1250
	 *
1251
	 * @since  2.0
1252
	 * @access private
1253
	 *
1254
	 * @param $address
1255
	 *
1256
	 * @return bool
1257
	 */
1258
	private function is_valid_address( $address ) {
1259
		$is_valid_address = true;
1260
		
1261
		// Address ready to process even if only one value set.
1262
		foreach ( $address as $address_type => $value ) {
1263
			// @todo: Handle state field validation on basis of country.
1264
			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...
1265
				continue;
1266
			}
1267
1268
			if ( empty( $value ) ) {
1269
				$is_valid_address = false;
1270
				break;
1271
			}
1272
		}
1273
1274
		return $is_valid_address;
1275
	}
1276
1277
	/**
1278
	 * Add donor address
1279
	 *
1280
	 * @since  2.0
1281
	 * @access public
1282
	 *
1283
	 * @param string $address_type
1284
	 * @param array  $address {
1285
	 *
1286
	 * @type string  $address2
1287
	 * @type string city
1288
	 * @type string zip
1289
	 * @type string state
1290
	 * @type string country
1291
	 * }
1292
	 *
1293
	 * @return bool
1294
	 */
1295
	public function add_address( $address_type, $address ) {
1296
		// Bailout.
1297
		if ( empty( $address_type ) || ! $this->is_valid_address( $address ) || ! $this->id ) {
1298
			return false;
1299
		}
1300
1301
		// Check if multiple address exist or not and set params.
1302
		$multi_address_id = null;
1303
		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...
1304
			$address_type  = $is_multi_address ?
1305
				str_replace( '[]', '', $address_type ) :
1306
				$address_type;
1307
		} elseif ( $is_multi_address = ( false !== strpos( $address_type, '_' ) ) ){
1308
			$multi_address_id =  $is_multi_address ?
0 ignored issues
show
introduced by
Expected 1 space after "="; 2 found
Loading history...
1309
				array_pop( explode( '_', $address_type ) ) :
1310
				$address_type;
1311
1312
			$address_type  = $is_multi_address ?
1313
				array_shift( explode( '_', $address_type ) ) :
1314
				$address_type;
1315
		}
1316
1317
		// Bailout: do not save duplicate orders
1318
		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...
1319
			return false;
1320
		}
1321
1322
		// Set default address.
1323
		$address = wp_parse_args(
1324
			$address,
1325
			array(
1326
				'line1' => '',
1327
				'line2' => '',
1328
				'city'     => '',
1329
				'state'    => '',
1330
				'country'  => '',
1331
				'zip'      => '',
1332
			)
1333
		);
1334
1335
		// Set meta key prefix.
1336
		global $wpdb;
1337
		$meta_key_prefix = "_give_donor_address_{$address_type}_{address_name}";
1338
		$meta_type = Give()->donor_meta->meta_type;
1339
1340
		if ( $is_multi_address ) {
1341
			if ( is_null( $multi_address_id ) ) {
1342
				// Get latest address key to set multi address id.
1343
				$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...
1344
					$wpdb->prepare(
1345
						"
1346
						SELECT meta_key FROM {$wpdb->donormeta}
1347
						WHERE meta_key
1348
						LIKE '%%%s%%'
1349
						AND {$meta_type}_id=%d
1350
						ORDER BY meta_id DESC
1351
						LIMIT 1
1352
						",
1353
						"_give_donor_address_{$address_type}_line1",
1354
						$this->id
1355
					)
1356
				);
1357
1358
				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...
1359
					$multi_address_id = absint( substr( strrchr( $multi_address_id, '_' ), 1 ) );
1360
					$multi_address_id++;
1361
				} 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...
1362
					$multi_address_id = 0;
1363
				}
1364
			}
1365
1366
			$meta_key_prefix = "_give_donor_address_{$address_type}_{address_name}_{$multi_address_id}";
1367
		}
1368
1369
		// Save donor address.
1370
		foreach ( $address as $type => $value ) {
1371
			$meta_key = str_replace( '{address_name}', $type, $meta_key_prefix );
1372
			Give()->donor_meta->update_meta( $this->id, $meta_key, $value );
1373
		}
1374
1375
		$this->setup_address();
1376
1377
		return true;
1378
	}
1379
1380
	/**
1381
	 * Remove donor address
1382
	 *
1383
	 * @since  2.0
1384
	 * @access public
1385
	 * @global wpdb  $wpdb
1386
	 *
1387
	 * @param string $address_id
1388
	 *
1389
	 * @return bool
1390
	 */
1391
	public function remove_address( $address_id ) {
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->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...
1415
			$wpdb->prepare(
1416
				"
1417
				DELETE 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
		$this->setup_address();
1428
1429
		return (bool) $row_affected;
1430
	}
1431
1432
	/**
1433
	 * Update donor address
1434
	 *
1435
	 * @since  2.0
1436
	 * @access public
1437
	 * @global wpdb  $wpdb
1438
	 *
1439
	 * @param string $address_id
1440
	 * @param array  $address
1441
	 *
1442
	 * @return bool
1443
	 */
1444
	public function update_address( $address_id, $address ) {
1445
		global $wpdb;
1446
1447
		// Get address type.
1448
		$is_multi_address = false !== strpos( $address_id, '_' ) ? true : false;
1449
1450
		$address_type = false !== strpos( $address_id, '_' ) ?
1451
			array_shift( explode( '_', $address_id ) ) :
1452
			$address_id;
1453
1454
		$address_count = false !== strpos( $address_id, '_' ) ?
1455
			array_pop( explode( '_', $address_id ) ) :
1456
			null;
1457
1458
		// Set meta key prefix.
1459
		$meta_key_prefix = "_give_donor_address_{$address_type}_%";
1460
		if ( $is_multi_address && is_numeric( $address_count ) ) {
1461
			$meta_key_prefix .= "_{$address_count}";
1462
		}
1463
1464
		$meta_type = Give()->donor_meta->meta_type;
1465
1466
		// Process query.
1467
		$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...
1468
			$wpdb->prepare(
1469
				"
1470
				SELECT meta_key FROM {$wpdb->donormeta}
1471
				WHERE meta_key
1472
				LIKE '%s'
1473
				AND {$meta_type}_id=%d
1474
				",
1475
				$meta_key_prefix,
1476
				$this->id
1477
			)
1478
		);
1479
		
1480
		// Return result.
1481
		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...
1482
			return false;
1483
		}
1484
1485
		// Update address.
1486
		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...
1487
			return false;
1488
		}
1489
1490
		return true;
1491
	}
1492
1493
1494
	/**
1495
	 * Check if donor already has current address
1496
	 *
1497
	 * @since 2.0
1498
	 * @access public
1499
	 *
1500
	 * @param string $current_address_type
1501
	 * @param array $current_address
1502
	 *
1503
	 * @return bool|null
1504
	 */
1505
	public function is_address_exist( $current_address_type, $current_address ) {
1506
		$status = false;
1507
1508
		// Bailout.
1509
		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...
1510
			return null;
1511
		}
1512
1513
		// Bailout.
1514
		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...
1515
			return $status;
1516
		}
1517
1518
		// Get address.
1519
		$address = $this->address[ $current_address_type ];
1520
1521
		switch ( true ){
1522
1523
			// Single address.
1524
			case is_string( end( $address ) ) :
1525
				$status = $this->is_address_match( $current_address, $address );
1526
				break;
1527
1528
			// Multi address.
1529
			case is_array( end( $address ) ):
1530
				// Compare address.
1531
				foreach ( $address as $saved_address ) {
1532
					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...
1533
						continue;
1534
					}
1535
1536
					// Exit loop immediately if address exist.
1537
					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...
1538
						break;
1539
					}
1540
				}
1541
				break;
1542
		}
1543
1544
		return $status;
1545
	}
1546
1547
	/**
1548
	 * Compare address.
1549
	 *
1550
	 * @since  2.0
1551
	 * @access private
1552
	 *
1553
	 * @param array $address_1
1554
	 * @param array $address_2
1555
	 *
1556
	 * @return bool
1557
	 */
1558
	private function is_address_match( $address_1, $address_2 ) {
1559
		$result = array_diff( $address_1, $address_2 );
1560
1561
		return empty( $result );
1562
	}
1563
1564
	/**
1565
	 * Split donor name into first name and last name
1566
	 *
1567
	 * @param   int     $id     Donor ID
1568
	 * @since   2.0
1569
	 * @return  object
1570
	 */
1571
	public function split_donor_name( $id ) {
1572
		$first_name = $last_name  = '';
1573
		$donor      = new Give_Donor( $id );
1574
1575
		$split_donor_name = explode( ' ', $donor->name, 2 );
1576
1577
		// Check for existence of first name after split of donor name.
1578
		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...
1579
			$first_name = $split_donor_name[0];
1580
		}
1581
1582
		// Check for existence of last name after split of donor name.
1583
		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...
1584
			$last_name = $split_donor_name[1];
1585
		}
1586
		return (object) array( 'first_name' => $first_name, 'last_name' => $last_name );
1587
	}
1588
1589
	/**
1590
	 * Retrieves first name of donor with backward compatibility
1591
	 *
1592
	 * @since   2.0
1593
	 * @return  string
1594
	 */
1595
	public function get_first_name() {
1596
		$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...
1597
		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...
1598
			$first_name = $this->split_donor_name( $this->id )->first_name;
1599
		}
1600
1601
		return $first_name;
1602
	}
1603
1604
	/**
1605
	 * Retrieves last name of donor with backward compatibility
1606
	 *
1607
	 * @since   2.0
1608
	 * @return  string
1609
	 */
1610
	public function get_last_name() {
1611
		$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...
1612
		$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...
1613
1614
		// This condition will prevent unnecessary splitting of donor name to fetch last name.
1615
		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...
1616
			$last_name = $this->split_donor_name( $this->id )->last_name;
1617
		}
1618
1619
		return ( $last_name ) ? $last_name : '';
1620
	}
1621
1622
	/**
1623
	 * Retrieves company name of donor
1624
	 *
1625
	 * @since   2.1.0
1626
	 *
1627
	 * @return  string $company_name Donor Company Name
1628
	 */
1629
	public function get_company_name() {
1630
		$company_name = $this->get_meta( '_give_donor_company' );
1631
1632
		return $company_name;
1633
	}
1634
}
1635