Give_Donor::__construct()   B
last analyzed

Complexity

Conditions 9
Paths 13

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
nc 13
nop 2
dl 0
loc 25
rs 8.0555
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, GiveWP
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
		// Backward compatibility.
949
		$note_string        = date_i18n( 'F j, Y H:i:s', current_time( 'timestamp' ) ) . ' - ' . $note;
950
		$formatted_new_note = apply_filters( 'give_customer_add_note_string', $note_string );
951
		$notes              .= "\n\n" . $formatted_new_note;
952
953
		/**
954
		 * Fires before donor note is added.
955
		 *
956
		 * @since 1.0
957
		 *
958
		 * @param string $formatted_new_note Formatted new note to add.
959
		 * @param int    $donor_id           Donor id.
960
		 */
961
		do_action( 'give_donor_pre_add_note', $formatted_new_note, $this->id );
962
963
		if ( ! give_has_upgrade_completed( 'v230_move_donor_note' ) ) {
964
			// Backward compatibility.
965
			$updated = $this->update( array( 'notes' => $notes ) );
966 View Code Duplication
		} else {
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...
967
			$updated = Give()->comment->db->add(
968
				array(
969
					'comment_content' => $note,
970
					'user_id'         => get_current_user_id(),
971
					'comment_parent'  => $this->id,
972
					'comment_type'    => 'donor',
973
				)
974
			);
975
		}
976
977
		if ( $updated ) {
978
			$this->notes = $this->get_notes();
979
		}
980
981
		/**
982
		 * Fires after donor note added.
983
		 *
984
		 * @since 1.0
985
		 *
986
		 * @param array  $donor_notes        Donor notes.
987
		 * @param string $formatted_new_note Formatted new note added.
988
		 * @param int    $donor_id           Donor id.
989
		 */
990
		do_action( 'give_donor_post_add_note', $this->notes, $formatted_new_note, $this->id );
991
992
		// Return the formatted note, so we can test, as well as update any displays
993
		return $formatted_new_note;
994
	}
995
996
	/**
997
	 * Get the notes column for the donor
998
	 *
999
	 * @since  1.0
1000
	 * @access private
1001
	 *
1002
	 * @return string The Notes for the donor, non-parsed.
1003
	 */
1004
	private function get_raw_notes() {
1005
		$all_notes = '';
1006
		$comments = Give()->comment->db->get_results_by( array( 'comment_parent' => $this->id ) );
1007
1008
		// Generate notes output as we are doing before 2.3.0.
1009
		if( ! empty( $comments ) ) {
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...
1010
			/* @var stdClass $comment */
1011
			foreach ( $comments  as $comment ) {
1012
				$all_notes .= date_i18n( 'F j, Y H:i:s', strtotime( $comment->comment_date ) ) . " - {$comment->comment_content}\n\n";
1013
			}
1014
		}
1015
1016
		// Backward compatibility.
1017
		if( ! give_has_upgrade_completed('v230_move_donor_note') ) {
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 after opening bracket; 0 found
Loading history...
Coding Style introduced by
Expected 1 spaces before closing bracket; 0 found
Loading history...
1018
			$all_notes = $this->db->get_column( 'notes', $this->id );
1019
		}
1020
1021
		return $all_notes;
1022
1023
	}
1024
1025
	/**
1026
	 * Retrieve a meta field for a donor.
1027
	 *
1028
	 * @since  1.6
1029
	 * @access public
1030
	 *
1031
	 * @param  string $meta_key The meta key to retrieve. Default is empty.
1032
	 * @param  bool   $single   Whether to return a single value. Default is true.
1033
	 *
1034
	 * @return mixed            Will be an array if $single is false. Will be value of meta data field if $single is
1035
	 *                          true.
1036
	 */
1037
	public function get_meta( $meta_key = '', $single = true ) {
1038
		return Give()->donor_meta->get_meta( $this->id, $meta_key, $single );
1039
	}
1040
1041
	/**
1042
	 * Add a meta data field to a donor.
1043
	 *
1044
	 * @since  1.6
1045
	 * @access public
1046
	 *
1047
	 * @param  string $meta_key   Metadata name. Default is empty.
1048
	 * @param  mixed  $meta_value Metadata value.
1049
	 * @param  bool   $unique     Optional. Whether the same key should not be added. Default is false.
1050
	 *
1051
	 * @return bool               False for failure. True for success.
1052
	 */
1053
	public function add_meta( $meta_key = '', $meta_value, $unique = false ) {
1054
		return Give()->donor_meta->add_meta( $this->id, $meta_key, $meta_value, $unique );
1055
	}
1056
1057
	/**
1058
	 * Update a meta field based on donor ID.
1059
	 *
1060
	 * @since  1.6
1061
	 * @access public
1062
	 *
1063
	 * @param  string $meta_key   Metadata key. Default is empty.
1064
	 * @param  mixed  $meta_value Metadata value.
1065
	 * @param  mixed  $prev_value Optional. Previous value to check before removing. Default is empty.
1066
	 *
1067
	 * @return bool               False on failure, true if success.
1068
	 */
1069
	public function update_meta( $meta_key = '', $meta_value, $prev_value = '' ) {
1070
		return Give()->donor_meta->update_meta( $this->id, $meta_key, $meta_value, $prev_value );
1071
	}
1072
1073
	/**
1074
	 * Remove metadata matching criteria from a donor.
1075
	 *
1076
	 * @since  1.6
1077
	 * @access public
1078
	 *
1079
	 * @param  string $meta_key   Metadata name. Default is empty.
1080
	 * @param  mixed  $meta_value Optional. Metadata value. Default is empty.
1081
	 *
1082
	 * @return bool               False for failure. True for success.
1083
	 */
1084
	public function delete_meta( $meta_key = '', $meta_value = '' ) {
1085
		return Give()->donor_meta->delete_meta( $this->id, $meta_key, $meta_value );
1086
	}
1087
1088
	/**
1089
	 * Sanitize the data for update/create
1090
	 *
1091
	 * @since  1.0
1092
	 * @access private
1093
	 *
1094
	 * @param  array $data The data to sanitize.
1095
	 *
1096
	 * @return array       The sanitized data, based off column defaults.
1097
	 */
1098
	private function sanitize_columns( $data ) {
1099
1100
		$columns        = $this->db->get_columns();
1101
		$default_values = $this->db->get_column_defaults();
1102
1103
		foreach ( $columns as $key => $type ) {
1104
1105
			// Only sanitize data that we were provided
1106
			if ( ! array_key_exists( $key, $data ) ) {
1107
				continue;
1108
			}
1109
1110
			switch ( $type ) {
1111
1112
				case '%s':
1113
					if ( 'email' == $key ) {
1114
						$data[ $key ] = sanitize_email( $data[ $key ] );
1115
					} elseif ( 'notes' == $key ) {
1116
						$data[ $key ] = strip_tags( $data[ $key ] );
1117
					} else {
1118
						$data[ $key ] = sanitize_text_field( $data[ $key ] );
1119
					}
1120
					break;
1121
1122
				case '%d':
1123
					if ( ! is_numeric( $data[ $key ] ) || (int) $data[ $key ] !== absint( $data[ $key ] ) ) {
1124
						$data[ $key ] = $default_values[ $key ];
1125
					} else {
1126
						$data[ $key ] = absint( $data[ $key ] );
1127
					}
1128
					break;
1129
1130
				case '%f':
1131
					// Convert what was given to a float
1132
					$value = floatval( $data[ $key ] );
1133
1134
					if ( ! is_float( $value ) ) {
1135
						$data[ $key ] = $default_values[ $key ];
1136
					} else {
1137
						$data[ $key ] = $value;
1138
					}
1139
					break;
1140
1141
				default:
1142
					$data[ $key ] = sanitize_text_field( $data[ $key ] );
1143
					break;
1144
1145
			}
1146
		}
1147
1148
		return $data;
1149
	}
1150
1151
	/**
1152
	 * Attach an email to the donor
1153
	 *
1154
	 * @since  1.7
1155
	 * @access public
1156
	 *
1157
	 * @param  string $email   The email address to attach to the donor
1158
	 * @param  bool   $primary Allows setting the email added as the primary
1159
	 *
1160
	 * @return bool            If the email was added successfully
1161
	 */
1162
	public function add_email( $email = '', $primary = false ) {
1163
		if ( ! is_email( $email ) ) {
1164
			return false;
1165
		}
1166
		$existing = new Give_Donor( $email );
1167
1168
		if ( $existing->id > 0 ) {
1169
			// Email address already belongs to another donor
1170
			return false;
1171
		}
1172
1173
		if ( email_exists( $email ) ) {
1174
			$user = get_user_by( 'email', $email );
1175
			if ( $user->ID != $this->user_id ) {
1176
				return false;
1177
			}
1178
		}
1179
1180
		do_action( 'give_donor_pre_add_email', $email, $this->id, $this );
1181
1182
		// Add is used to ensure duplicate emails are not added
1183
		$ret = (bool) $this->add_meta( 'additional_email', $email );
1184
1185
		do_action( 'give_donor_post_add_email', $email, $this->id, $this );
1186
1187
		if ( $ret && true === $primary ) {
1188
			$this->set_primary_email( $email );
1189
		}
1190
1191
		return $ret;
1192
	}
1193
1194
	/**
1195
	 * Remove an email from the donor.
1196
	 *
1197
	 * @since  1.7
1198
	 * @access public
1199
	 *
1200
	 * @param  string $email The email address to remove from the donor.
1201
	 *
1202
	 * @return bool          If the email was removed successfully.
1203
	 */
1204
	public function remove_email( $email = '' ) {
1205
		if ( ! is_email( $email ) ) {
1206
			return false;
1207
		}
1208
1209
		do_action( 'give_donor_pre_remove_email', $email, $this->id, $this );
1210
1211
		$ret = (bool) $this->delete_meta( 'additional_email', $email );
1212
1213
		do_action( 'give_donor_post_remove_email', $email, $this->id, $this );
1214
1215
		return $ret;
1216
	}
1217
1218
	/**
1219
	 * Set an email address as the donor's primary email.
1220
	 *
1221
	 * This will move the donor's previous primary email to an additional email.
1222
	 *
1223
	 * @since  1.7
1224
	 * @access public
1225
	 *
1226
	 * @param  string $new_primary_email The email address to remove from the donor.
1227
	 *
1228
	 * @return bool                      If the email was set as primary successfully.
1229
	 */
1230
	public function set_primary_email( $new_primary_email = '' ) {
1231
		if ( ! is_email( $new_primary_email ) ) {
1232
			return false;
1233
		}
1234
1235
		do_action( 'give_donor_pre_set_primary_email', $new_primary_email, $this->id, $this );
1236
1237
		$existing = new Give_Donor( $new_primary_email );
1238
1239
		if ( $existing->id > 0 && (int) $existing->id !== (int) $this->id ) {
1240
			// This email belongs to another donor.
1241
			return false;
1242
		}
1243
1244
		$old_email = $this->email;
1245
1246
		// Update donor record with new email.
1247
		$update = $this->update( array( 'email' => $new_primary_email ) );
1248
1249
		// Remove new primary from list of additional emails.
1250
		$remove = $this->remove_email( $new_primary_email );
1251
1252
		// Add old email to additional emails list.
1253
		$add = $this->add_email( $old_email );
1254
1255
		$ret = $update && $remove && $add;
1256
1257
		if ( $ret ) {
1258
			$this->email = $new_primary_email;
1259
		}
1260
1261
		do_action( 'give_donor_post_set_primary_email', $new_primary_email, $this->id, $this );
1262
1263
		return $ret;
1264
	}
1265
1266
	/**
1267
	 * Check if address valid or not.
1268
	 *
1269
	 * @since  2.0
1270
	 * @access private
1271
	 *
1272
	 * @param $address
1273
	 *
1274
	 * @return bool
1275
	 */
1276
	private function is_valid_address( $address ) {
1277
		$is_valid_address = true;
1278
1279
		// Address ready to process even if only one value set.
1280
		foreach ( $address as $address_type => $value ) {
1281
			// @todo: Handle state field validation on basis of country.
1282
			if ( in_array( $address_type, array( 'line2', 'state' ) ) ) {
1283
				continue;
1284
			}
1285
1286
			if ( empty( $value ) ) {
1287
				$is_valid_address = false;
1288
				break;
1289
			}
1290
		}
1291
1292
		return $is_valid_address;
1293
	}
1294
1295
	/**
1296
	 * Add donor address
1297
	 *
1298
	 * @since  2.0
1299
	 * @access public
1300
	 *
1301
	 * @param string $address_type
1302
	 * @param array  $address {
1303
	 *
1304
	 * @type string  $address2
1305
	 * @type string city
1306
	 * @type string zip
1307
	 * @type string state
1308
	 * @type string country
1309
	 * }
1310
	 *
1311
	 * @return bool
1312
	 */
1313
	public function add_address( $address_type, $address ) {
1314
		// Bailout.
1315
		if ( empty( $address_type ) || ! $this->is_valid_address( $address ) || ! $this->id ) {
1316
			return false;
1317
		}
1318
1319
		// Check if multiple address exist or not and set params.
1320
		$multi_address_id = null;
1321
		if ( $is_multi_address = ( false !== strpos( $address_type, '[]' ) ) ) {
1322
			$address_type = $is_multi_address ? str_replace( '[]', '', $address_type ) : $address_type;
1323
		} elseif ( $is_multi_address = ( false !== strpos( $address_type, '_' ) ) ) {
1324
			$exploded_address_type = explode( '_', $address_type );
1325
			$multi_address_id      = $is_multi_address ? array_pop( $exploded_address_type ) : $address_type;
1326
1327
			$address_type = $is_multi_address ? array_shift( $exploded_address_type ) : $address_type;
1328
		}
1329
1330
		// Bailout: do not save duplicate orders
1331
		if ( $this->does_address_exist( $address_type, $address ) ) {
1332
			return false;
1333
		}
1334
1335
		// Set default address.
1336
		$address = wp_parse_args( $address, array(
1337
			'line1'   => '',
1338
			'line2'   => '',
1339
			'city'    => '',
1340
			'state'   => '',
1341
			'country' => '',
1342
			'zip'     => '',
1343
		) );
1344
1345
		// Set meta key prefix.
1346
		global $wpdb;
1347
		$meta_key_prefix = "_give_donor_address_{$address_type}_{address_name}";
1348
		$meta_type       = Give()->donor_meta->meta_type;
1349
1350
		if ( $is_multi_address ) {
1351
			if ( is_null( $multi_address_id ) ) {
1352
				// Get latest address key to set multi address id.
1353
				$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...
1354
						SELECT meta_key FROM {$wpdb->donormeta}
1355
						WHERE meta_key
1356
						LIKE '%%%s%%'
1357
						AND {$meta_type}_id=%d
1358
						ORDER BY meta_id DESC
1359
						LIMIT 1
1360
						", "_give_donor_address_{$address_type}_line1", $this->id ) );
1361
1362
				if ( ! empty( $multi_address_id ) ) {
1363
					$multi_address_id = absint( substr( strrchr( $multi_address_id, '_' ), 1 ) );
1364
					$multi_address_id ++;
1365
				} else {
1366
					$multi_address_id = 0;
1367
				}
1368
			}
1369
1370
			$meta_key_prefix = "_give_donor_address_{$address_type}_{address_name}_{$multi_address_id}";
1371
		}
1372
1373
		// Save donor address.
1374
		foreach ( $address as $type => $value ) {
1375
			$meta_key = str_replace( '{address_name}', $type, $meta_key_prefix );
1376
			Give()->donor_meta->update_meta( $this->id, $meta_key, $value );
1377
		}
1378
1379
		$this->setup_address();
1380
1381
		return true;
1382
	}
1383
1384
	/**
1385
	 * Remove donor address
1386
	 *
1387
	 * @since  2.0
1388
	 * @access public
1389
	 * @global wpdb  $wpdb
1390
	 *
1391
	 * @param string $address_id
1392
	 *
1393
	 * @return bool
1394
	 */
1395
	public function remove_address( $address_id ) {
1396
		global $wpdb;
1397
1398
		// Get address type.
1399
		$is_multi_address = false !== strpos( $address_id, '_' ) ? true : false;
1400
1401
		$address_type = false !== strpos( $address_id, '_' ) ? array_shift( explode( '_', $address_id ) ) : $address_id;
1402
1403
		$address_count = false !== strpos( $address_id, '_' ) ? array_pop( explode( '_', $address_id ) ) : 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( $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...
1415
				DELETE FROM {$wpdb->donormeta}
1416
				WHERE meta_key
1417
				LIKE '%s'
1418
				AND {$meta_type}_id=%d
1419
				", $meta_key_prefix, $this->id ) );
1420
1421
		$this->setup_address();
1422
1423
		return (bool) $row_affected;
1424
	}
1425
1426
	/**
1427
	 * Update donor address
1428
	 *
1429
	 * @since  2.0
1430
	 * @access public
1431
	 * @global wpdb  $wpdb
1432
	 *
1433
	 * @param string $address_id
1434
	 * @param array  $address
1435
	 *
1436
	 * @return bool
1437
	 */
1438
	public function update_address( $address_id, $address ) {
1439
		global $wpdb;
1440
1441
		// Get address type.
1442
		$is_multi_address = false !== strpos( $address_id, '_' ) ? true : false;
1443
		$exploded_address_id = explode( '_', $address_id );
1444
1445
		$address_type = false !== strpos( $address_id, '_' ) ? array_shift( $exploded_address_id ) : $address_id;
1446
1447
		$address_count = false !== strpos( $address_id, '_' ) ? array_pop( $exploded_address_id ) : null;
1448
1449
		// Set meta key prefix.
1450
		$meta_key_prefix = "_give_donor_address_{$address_type}_%";
1451
		if ( $is_multi_address && is_numeric( $address_count ) ) {
1452
			$meta_key_prefix .= "_{$address_count}";
1453
		}
1454
1455
		$meta_type = Give()->donor_meta->meta_type;
1456
1457
		// Process query.
1458
		$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...
1459
				SELECT meta_key FROM {$wpdb->donormeta}
1460
				WHERE meta_key
1461
				LIKE '%s'
1462
				AND {$meta_type}_id=%d
1463
				", $meta_key_prefix, $this->id ) );
1464
1465
		// Return result.
1466
		if ( ! count( $row_affected ) ) {
1467
			return false;
1468
		}
1469
1470
		// Update address.
1471
		if ( ! $this->add_address( $address_id, $address ) ) {
1472
			return false;
1473
		}
1474
1475
		return true;
1476
	}
1477
1478
1479
	/**
1480
	 * Check if donor already has current address
1481
	 *
1482
	 * @since  2.0
1483
	 * @access public
1484
	 *
1485
	 * @param string $current_address_type
1486
	 * @param array  $current_address
1487
	 *
1488
	 * @return bool|null
1489
	 */
1490
	public function does_address_exist( $current_address_type, $current_address ) {
1491
		$status = false;
1492
1493
		// Bailout.
1494
		if ( empty( $current_address_type ) || empty( $current_address ) ) {
1495
			return null;
1496
		}
1497
1498
		// Bailout.
1499
		if ( empty( $this->address ) || empty( $this->address[ $current_address_type ] ) ) {
1500
			return $status;
1501
		}
1502
1503
		// Get address.
1504
		$address = $this->address[ $current_address_type ];
1505
1506
		switch ( true ) {
1507
1508
			// Single address.
1509
			case is_string( end( $address ) ) :
1510
				$status = $this->is_address_match( $current_address, $address );
1511
				break;
1512
1513
			// Multi address.
1514
			case is_array( end( $address ) ):
1515
				// Compare address.
1516
				foreach ( $address as $saved_address ) {
1517
					if ( empty( $saved_address ) ) {
1518
						continue;
1519
					}
1520
1521
					// Exit loop immediately if address exist.
1522
					if ( $status = $this->is_address_match( $current_address, $saved_address ) ) {
1523
						break;
1524
					}
1525
				}
1526
				break;
1527
		}
1528
1529
		return $status;
1530
	}
1531
1532
	/**
1533
	 * Compare address.
1534
	 *
1535
	 * @since  2.0
1536
	 * @access private
1537
	 *
1538
	 * @param array $address_1
1539
	 * @param array $address_2
1540
	 *
1541
	 * @return bool
1542
	 */
1543
	private function is_address_match( $address_1, $address_2 ) {
1544
		$result = array_diff_assoc( $address_1, $address_2 );
1545
1546
		return empty( $result );
1547
	}
1548
1549
	/**
1550
	 * Split donor name into first name and last name
1551
	 *
1552
	 * @param   int $id Donor ID
1553
	 *
1554
	 * @since   2.0
1555
	 * @return  object
1556
	 */
1557
	public function split_donor_name( $id ) {
1558
		$first_name = $last_name = '';
1559
		$donor      = new Give_Donor( $id );
1560
1561
		$split_donor_name = explode( ' ', $donor->name, 2 );
1562
1563
		// Check for existence of first name after split of donor name.
1564
		if ( is_array( $split_donor_name ) && ! empty( $split_donor_name[0] ) ) {
1565
			$first_name = $split_donor_name[0];
1566
		}
1567
1568
		// Check for existence of last name after split of donor name.
1569
		if ( is_array( $split_donor_name ) && ! empty( $split_donor_name[1] ) ) {
1570
			$last_name = $split_donor_name[1];
1571
		}
1572
1573
		return (object) array( 'first_name' => $first_name, 'last_name' => $last_name );
1574
	}
1575
1576
	/**
1577
	 * Retrieves first name of donor with backward compatibility
1578
	 *
1579
	 * @since   2.0
1580
	 * @return  string
1581
	 */
1582
	public function get_first_name() {
1583
		$first_name = $this->get_meta( '_give_donor_first_name' );
1584
		if ( ! $first_name ) {
1585
			$first_name = $this->split_donor_name( $this->id )->first_name;
1586
		}
1587
1588
		return $first_name;
1589
	}
1590
1591
	/**
1592
	 * Retrieves last name of donor with backward compatibility
1593
	 *
1594
	 * @since   2.0
1595
	 * @return  string
1596
	 */
1597
	public function get_last_name() {
1598
		$first_name = $this->get_meta( '_give_donor_first_name' );
1599
		$last_name  = $this->get_meta( '_give_donor_last_name' );
1600
1601
		// This condition will prevent unnecessary splitting of donor name to fetch last name.
1602
		if ( ! $first_name && ! $last_name ) {
1603
			$last_name = $this->split_donor_name( $this->id )->last_name;
1604
		}
1605
1606
		return ( $last_name ) ? $last_name : '';
1607
	}
1608
1609
	/**
1610
	 * Retrieves company name of donor
1611
	 *
1612
	 * @since   2.1
1613
	 *
1614
	 * @return  string $company_name Donor Company Name
1615
	 */
1616
	public function get_company_name() {
1617
		$company_name = $this->get_meta( '_give_donor_company' );
1618
1619
		return $company_name;
1620
	}
1621
1622
	/**
1623
	 * Retrieves last donation for the donor.
1624
	 *
1625
	 * @since   2.1
1626
	 *
1627
	 * @return  string $company_name Donor Company Name
1628
	 */
1629
	public function get_last_donation() {
1630
1631
		$payments = array_unique( array_values( explode( ',', $this->payment_ids ) ) );
1632
1633
		return end( $payments );
1634
1635
	}
1636
1637
	/**
1638
	 * Retrieves last donation for the donor.
1639
	 *
1640
	 * @since   2.1
1641
	 *
1642
	 * @param bool $formatted Whether to return with the date format or not.
1643
	 *
1644
	 * @return string The date of the last donation.
1645
	 */
1646
	public function get_last_donation_date( $formatted = false ) {
1647
		$completed_data = '';
1648
1649
		// Return if donation id is invalid.
1650
		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...
1651
			return $completed_data;
1652
		}
1653
1654
		$completed_data = give_get_payment_completed_date( $last_donation );
1655
1656
		if ( $formatted ) {
1657
			return date_i18n( give_date_format(), strtotime( $completed_data ) );
1658
		}
1659
1660
		return $completed_data;
1661
1662
	}
1663
1664
	/**
1665
	 * Retrieves a donor's initials (first name and last name).
1666
	 *
1667
	 * @since   2.1
1668
	 *
1669
	 * @return string The donor's two initials (no middle).
1670
	 */
1671
	public function get_donor_initals() {
1672
		/**
1673
		 * Filter the donor name initials
1674
		 *
1675
		 * @since 2.1.0
1676
		 */
1677
		return apply_filters(
1678
			'get_donor_initals',
1679
			give_get_name_initial( array(
1680
				'firstname' =>  $this->get_first_name(),
0 ignored issues
show
introduced by
Expected 1 space after "=>"; 2 found
Loading history...
1681
				'lastname' =>  $this->get_last_name()
0 ignored issues
show
introduced by
Expected 1 space after "=>"; 2 found
Loading history...
1682
			) )
1683
		);
1684
1685
	}
1686
1687
}
1688