Test Failed
Push — issue/3627 ( fa91e5...bf57b1 )
by Ravinder
10:14
created

Give_Donor::update_donation_value()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

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