Test Failed
Push — master ( f5256c...25a383 )
by Devin
07:02
created

Give_Donor::add_note()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 48

Duplication

Lines 0
Ratio 0 %

Importance

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

There are different options of fixing this problem.

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

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

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

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