Test Failed
Push — backup/issues/1132 ( b1d18b )
by Ravinder
05:29
created

Give_Donor::is_address_exist()   C

Complexity

Conditions 12
Paths 12

Size

Total Lines 46
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 21
nc 12
nop 2
dl 0
loc 46
rs 5.15
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Donor
4
 *
5
 * @package     Give
6
 * @subpackage  Classes/Give_Donor
7
 * @copyright   Copyright (c) 2016, WordImpress
8
 * @license     https://opensource.org/licenses/gpl-license GNU Public License
9
 * @since       1.0
10
 */
11
12
// Exit if accessed directly.
13
if ( ! defined( 'ABSPATH' ) ) {
14
	exit;
15
}
16
17
/**
18
 * Give_Donor Class
19
 *
20
 * This class handles customers.
21
 *
22
 * @since 1.0
23
 */
24
class Give_Donor {
25
26
	/**
27
	 * The donor ID
28
	 *
29
	 * @since  1.0
30
	 * @access public
31
	 *
32
	 * @var    int
33
	 */
34
	public $id = 0;
35
36
	/**
37
	 * The donor's donation count.
38
	 *
39
	 * @since  1.0
40
	 * @access public
41
	 *
42
	 * @var    int
43
	 */
44
	public $purchase_count = 0;
45
46
	/**
47
	 * The donor's lifetime value.
48
	 *
49
	 * @since  1.0
50
	 * @access public
51
	 *
52
	 * @var    int
53
	 */
54
	public $purchase_value = 0;
55
56
	/**
57
	 * The donor's email.
58
	 *
59
	 * @since  1.0
60
	 * @access public
61
	 *
62
	 * @var    string
63
	 */
64
	public $email;
65
66
	/**
67
	 * The donor's emails.
68
	 *
69
	 * @since  1.7
70
	 * @access public
71
	 *
72
	 * @var    array
73
	 */
74
	public $emails;
75
76
	/**
77
	 * The donor's name.
78
	 *
79
	 * @since  1.0
80
	 * @access public
81
	 *
82
	 * @var    string
83
	 */
84
	public $name;
85
86
	/**
87
	 * The donor creation date.
88
	 *
89
	 * @since  1.0
90
	 * @access public
91
	 *
92
	 * @var    string
93
	 */
94
	public $date_created;
95
96
	/**
97
	 * The payment IDs associated with the donor.
98
	 *
99
	 * @since  1.0
100
	 * @access public
101
	 *
102
	 * @var    string
103
	 */
104
	public $payment_ids;
105
106
	/**
107
	 * The user ID associated with the donor.
108
	 *
109
	 * @since  1.0
110
	 * @access public
111
	 *
112
	 * @var    int
113
	 */
114
	public $user_id;
115
116
	/**
117
	 * Donor notes saved by admins.
118
	 *
119
	 * @since  1.0
120
	 * @access public
121
	 *
122
	 * @var    string
123
	 */
124
	public $notes;
125
126
	/**
127
	 * Donor address.
128
	 *
129
	 * @since  1.0
130
	 * @access public
131
	 *
132
	 * @var    array
133
	 */
134
	public $address;
135
136
	/**
137
	 * The Database Abstraction
138
	 *
139
	 * @since  1.0
140
	 * @access protected
141
	 *
142
	 * @var    Give_DB_Donors
143
	 */
144
	protected $db;
145
146
	/**
147
	 * Give_Donor constructor.
148
	 *
149
	 * @param bool $_id_or_email
150
	 * @param bool $by_user_id
151
	 */
152
	public function __construct( $_id_or_email = false, $by_user_id = false ) {
153
154
		$this->db = new Give_DB_Donors();
155
156
		if (
157
			false === $_id_or_email
158
			|| ( is_numeric( $_id_or_email ) && (int) $_id_or_email !== absint( $_id_or_email ) )
159
		) {
160
			return false;
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
161
		}
162
163
		$by_user_id = is_bool( $by_user_id ) ? $by_user_id : false;
164
165
		if ( is_numeric( $_id_or_email ) ) {
166
			$field = $by_user_id ? 'user_id' : 'id';
167
		} else {
168
			$field = 'email';
169
		}
170
171
		$donor = $this->db->get_donor_by( $field, $_id_or_email );
172
173
		if ( empty( $donor ) || ! is_object( $donor ) ) {
174
			return false;
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
175
		}
176
177
		$this->setup_donor( $donor );
178
179
	}
180
181
	/**
182
	 * Setup Donor
183
	 *
184
	 * Set donor variables.
185
	 *
186
	 * @since  1.0
187
	 * @access private
188
	 *
189
	 * @param  object $donor The Donor Object.
190
	 *
191
	 * @return bool             If the setup was successful or not.
192
	 */
193
	private function setup_donor( $donor ) {
194
195
		if ( ! is_object( $donor ) ) {
196
			return false;
197
		}
198
199
		foreach ( $donor as $key => $value ) {
200
201
			switch ( $key ) {
202
203
				case 'notes':
204
					$this->$key = $this->get_notes();
205
					break;
206
207
				default:
208
					$this->$key = $value;
209
					break;
210
211
			}
212
		}
213
214
		// Get donor's all email including primary email.
215
		$this->emails = (array) $this->get_meta( 'additional_email', false );
216
		$this->emails = array( 'primary' => $this->email ) + $this->emails;
217
218
		$this->setup_address();
219
220
		// Donor ID and email are the only things that are necessary, make sure they exist.
221
		if ( ! empty( $this->id ) && ! empty( $this->email ) ) {
222
			return true;
223
		}
224
225
		return false;
226
227
	}
228
229
230
	/**
231
	 * Setup donor address.
232
	 * 
233
	 * @since 2.0
234
	 * @access public
235
	 */
236
	public function setup_address() {
237
		global $wpdb;
238
		$meta_type = Give()->donor_meta->meta_type;
239
240
		$addresses = $wpdb->get_results(
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
241
			$wpdb->prepare(
242
				"
243
				SELECT meta_key, meta_value FROM {$wpdb->donormeta}
244
				WHERE meta_key
245
				LIKE '%%%s%%'
246
				AND {$meta_type}_id=%d
247
				",
248
				'give_donor_address',
249
				$this->id
250
			),
251
			ARRAY_N
252
		);
253
254
		if ( empty( $addresses ) ) {
255
			return array();
256
		}
257
258
		foreach ( $addresses as $address ) {
259
			$address[0] = str_replace( '_give_donor_address_', '', $address[0] );
260
			$address[0] = explode( '_', $address[0] );
261
262
			if ( 'address1' === $address[0][1] ) {
263
				$address[0][1] = 'line1';
264
			} elseif ( 'address2' === $address[0][1] ) {
265
				$address[0][1] = 'line2';
266
			}
267
268
			if ( 3 === count( $address[0] ) ) {
269
				$this->address[ $address[0][0] ][ $address[0][2] ][ $address[0][1] ] = $address[1];
270
			} else {
271
				$this->address[ $address[0][0] ][ $address[0][1] ] = $address[1];
272
			}
273
		}
274
	}
275
276
	/**
277
	 * Magic __get function to dispatch a call to retrieve a private property.
278
	 *
279
	 * @since  1.0
280
	 * @access public
281
	 * @param $key
282
	 *
283
	 * @return mixed|\WP_Error
284
	 */
285 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...
286
287
		if ( method_exists( $this, 'get_' . $key ) ) {
288
289
			return call_user_func( array( $this, 'get_' . $key ) );
290
291
		} else {
292
293
			/* translators: %s: property key */
294
			return new WP_Error( 'give-donor-invalid-property', sprintf( esc_html__( 'Can\'t get property %s.', 'give' ), $key ) );
295
296
		}
297
298
	}
299
300
	/**
301
	 * Creates a donor.
302
	 *
303
	 * @since  1.0
304
	 * @access public
305
	 *
306
	 * @param  array $data Array of attributes for a donor.
307
	 *
308
	 * @return bool|int    False if not a valid creation, donor ID if user is found or valid creation.
309
	 */
310
	public function create( $data = array() ) {
311
312
		if ( $this->id != 0 || empty( $data ) ) {
0 ignored issues
show
introduced by
Found "!= 0". Use Yoda Condition checks, you must
Loading history...
313
			return false;
314
		}
315
316
		$defaults = array(
317
			'payment_ids' => '',
318
		);
319
320
		$args = wp_parse_args( $data, $defaults );
321
		$args = $this->sanitize_columns( $args );
322
323
		if ( empty( $args['email'] ) || ! is_email( $args['email'] ) ) {
324
			return false;
325
		}
326
327 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...
328
			$args['payment_ids'] = implode( ',', array_unique( array_values( $args['payment_ids'] ) ) );
329
		}
330
331
		/**
332
		 * Fires before creating donors.
333
		 *
334
		 * @since 1.0
335
		 *
336
		 * @param array $args Donor attributes.
337
		 */
338
		do_action( 'give_donor_pre_create', $args );
339
340
		$created = false;
341
342
		// The DB class 'add' implies an update if the donor being asked to be created already exists
343 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...
344
345
			// We've successfully added/updated the donor, reset the class vars with the new data
346
			$donor = $this->db->get_donor_by( 'email', $args['email'] );
347
348
			// Setup the donor data with the values from DB
349
			$this->setup_donor( $donor );
350
351
			$created = $this->id;
352
		}
353
354
		/**
355
		 * Fires after creating donors.
356
		 *
357
		 * @since 1.0
358
		 *
359
		 * @param bool|int $created False if not a valid creation, donor ID if user is found or valid creation.
360
		 * @param array $args Customer attributes.
361
		 */
362
		do_action( 'give_donor_post_create', $created, $args );
363
364
		return $created;
365
366
	}
367
368
	/**
369
	 * Updates a donor record.
370
	 *
371
	 * @since  1.0
372
	 * @access public
373
	 *
374
	 * @param  array $data Array of data attributes for a donor (checked via whitelist).
375
	 *
376
	 * @return bool        If the update was successful or not.
377
	 */
378
	public function update( $data = array() ) {
379
380
		if ( empty( $data ) ) {
381
			return false;
382
		}
383
384
		$data = $this->sanitize_columns( $data );
385
386
		/**
387
		 * Fires before updating donors.
388
		 *
389
		 * @since 1.0
390
		 *
391
		 * @param int $donor_id Donor id.
392
		 * @param array $data Donor attributes.
393
		 */
394
		do_action( 'give_donor_pre_update', $this->id, $data );
395
396
		$updated = false;
397
398 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...
399
400
			$donor = $this->db->get_donor_by( 'id', $this->id );
401
			$this->setup_donor( $donor );
402
403
			$updated = true;
404
		}
405
406
		/**
407
		 * Fires after updating donors.
408
		 *
409
		 * @since 1.0
410
		 *
411
		 * @param bool $updated If the update was successful or not.
412
		 * @param int $donor_id Donor id.
413
		 * @param array $data Donor attributes.
414
		 */
415
		do_action( 'give_donor_post_update', $updated, $this->id, $data );
416
417
		return $updated;
418
	}
419
420
	/**
421
	 * Attach Payment
422
	 *
423
	 * Attach payment to the donor then triggers increasing stats.
424
	 *
425
	 * @since  1.0
426
	 * @access public
427
	 *
428
	 * @param  int $payment_id The payment ID to attach to the donor.
429
	 * @param  bool $update_stats For backwards compatibility, if we should increase the stats or not.
430
	 *
431
	 * @return bool            If the attachment was successfully.
432
	 */
433
	public function attach_payment( $payment_id = 0, $update_stats = true ) {
434
435
		if ( empty( $payment_id ) ) {
436
			return false;
437
		}
438
439
		if ( empty( $this->payment_ids ) ) {
440
441
			$new_payment_ids = $payment_id;
442
443
		} else {
444
445
			$payment_ids = array_map( 'absint', explode( ',', $this->payment_ids ) );
446
447
			if ( in_array( $payment_id, $payment_ids ) ) {
448
				$update_stats = false;
449
			}
450
451
			$payment_ids[] = $payment_id;
452
453
			$new_payment_ids = implode( ',', array_unique( array_values( $payment_ids ) ) );
454
455
		}
456
457
		/**
458
		 * Fires before attaching payments to customers.
459
		 *
460
		 * @since 1.0
461
		 *
462
		 * @param int $payment_id Payment id.
463
		 * @param int $donor_id Customer id.
464
		 */
465
		do_action( 'give_donor_pre_attach_payment', $payment_id, $this->id );
466
467
		$payment_added = $this->update( array( 'payment_ids' => $new_payment_ids ) );
468
469 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...
470
471
			$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...
472
473
			// We added this payment successfully, increment the stats
474
			if ( $update_stats ) {
475
				$payment_amount = give_get_payment_amount( $payment_id );
476
477
				if ( ! empty( $payment_amount ) ) {
478
					$this->increase_value( $payment_amount );
479
				}
480
481
				$this->increase_purchase_count();
482
			}
483
		}
484
485
		/**
486
		 * Fires after attaching payments to the donor.
487
		 *
488
		 * @since 1.0
489
		 *
490
		 * @param bool $payment_added If the attachment was successfully.
491
		 * @param int $payment_id Payment id.
492
		 * @param int $donor_id Donor id.
493
		 */
494
		do_action( 'give_donor_post_attach_payment', $payment_added, $payment_id, $this->id );
495
496
		return $payment_added;
497
	}
498
499
	/**
500
	 * Remove Payment
501
	 *
502
	 * Remove a payment from this donor, then triggers reducing stats.
503
	 *
504
	 * @since  1.0
505
	 * @access public
506
	 *
507
	 * @param  int $payment_id The Payment ID to remove.
508
	 * @param  bool $update_stats For backwards compatibility, if we should increase the stats or not.
509
	 *
510
	 * @return boolean               If the removal was successful.
511
	 */
512
	public function remove_payment( $payment_id = 0, $update_stats = true ) {
513
514
		if ( empty( $payment_id ) ) {
515
			return false;
516
		}
517
518
		$payment = new Give_Payment( $payment_id );
519
520
		if ( 'publish' !== $payment->status && 'revoked' !== $payment->status ) {
521
			$update_stats = false;
522
		}
523
524
		$new_payment_ids = '';
525
526
		if ( ! empty( $this->payment_ids ) ) {
527
528
			$payment_ids = array_map( 'absint', explode( ',', $this->payment_ids ) );
529
530
			$pos = array_search( $payment_id, $payment_ids );
531
			if ( false === $pos ) {
532
				return false;
533
			}
534
535
			unset( $payment_ids[ $pos ] );
536
			$payment_ids = array_filter( $payment_ids );
537
538
			$new_payment_ids = implode( ',', array_unique( array_values( $payment_ids ) ) );
539
540
		}
541
542
		/**
543
		 * Fires before removing payments from customers.
544
		 *
545
		 * @since 1.0
546
		 *
547
		 * @param int $payment_id Payment id.
548
		 * @param int $donor_id Customer id.
549
		 */
550
		do_action( 'give_donor_pre_remove_payment', $payment_id, $this->id );
551
552
		$payment_removed = $this->update( array( 'payment_ids' => $new_payment_ids ) );
553
554 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...
555
556
			$this->payment_ids = $new_payment_ids;
557
558
			if ( $update_stats ) {
559
				// We removed this payment successfully, decrement the stats
560
				$payment_amount = give_get_payment_amount( $payment_id );
561
562
				if ( ! empty( $payment_amount ) ) {
563
					$this->decrease_value( $payment_amount );
564
				}
565
566
				$this->decrease_donation_count();
567
			}
568
		}
569
570
		/**
571
		 * Fires after removing payments from donors.
572
		 *
573
		 * @since 1.0
574
		 *
575
		 * @param bool $payment_removed If the removal was successfully.
576
		 * @param int $payment_id Payment id.
577
		 * @param int $donor_id Donor id.
578
		 */
579
		do_action( 'give_donor_post_remove_payment', $payment_removed, $payment_id, $this->id );
580
581
		return $payment_removed;
582
583
	}
584
585
	/**
586
	 * Increase the donation count of a donor.
587
	 *
588
	 * @since  1.0
589
	 * @access public
590
	 *
591
	 * @param  int $count The number to increase by.
592
	 *
593
	 * @return int        The donation count.
594
	 */
595
	public function increase_purchase_count( $count = 1 ) {
596
597
		// Make sure it's numeric and not negative.
598
		if ( ! is_numeric( $count ) || $count != absint( $count ) ) {
599
			return false;
600
		}
601
602
		$new_total = (int) $this->purchase_count + (int) $count;
603
604
		/**
605
		 * Fires before increasing the donor's donation count.
606
		 *
607
		 * @since 1.0
608
		 *
609
		 * @param int $count The number to increase by.
610
		 * @param int $donor_id Donor id.
611
		 */
612
		do_action( 'give_donor_pre_increase_donation_count', $count, $this->id );
613
614
		if ( $this->update( array( 'purchase_count' => $new_total ) ) ) {
615
			$this->purchase_count = $new_total;
616
		}
617
618
		/**
619
		 * Fires after increasing the donor's donation count.
620
		 *
621
		 * @since 1.0
622
		 *
623
		 * @param int $purchase_count Donor donation count.
624
		 * @param int $count The number increased by.
625
		 * @param int $donor_id Donor id.
626
		 */
627
		do_action( 'give_donor_post_increase_donation_count', $this->purchase_count, $count, $this->id );
628
629
		return $this->purchase_count;
630
	}
631
632
	/**
633
	 * Decrease the donor donation count.
634
	 *
635
	 * @since  1.0
636
	 * @access public
637
	 *
638
	 * @param  int $count The amount to decrease by.
639
	 *
640
	 * @return mixed      If successful, the new count, otherwise false.
641
	 */
642
	public function decrease_donation_count( $count = 1 ) {
643
644
		// Make sure it's numeric and not negative
645
		if ( ! is_numeric( $count ) || $count != absint( $count ) ) {
646
			return false;
647
		}
648
649
		$new_total = (int) $this->purchase_count - (int) $count;
650
651
		if ( $new_total < 0 ) {
652
			$new_total = 0;
653
		}
654
655
		/**
656
		 * Fires before decreasing the donor's donation count.
657
		 *
658
		 * @since 1.0
659
		 *
660
		 * @param int $count The number to decrease by.
661
		 * @param int $donor_id Customer id.
662
		 */
663
		do_action( 'give_donor_pre_decrease_donation_count', $count, $this->id );
664
665
		if ( $this->update( array( 'purchase_count' => $new_total ) ) ) {
666
			$this->purchase_count = $new_total;
667
		}
668
669
		/**
670
		 * Fires after decreasing the donor's donation count.
671
		 *
672
		 * @since 1.0
673
		 *
674
		 * @param int $purchase_count Donor's donation count.
675
		 * @param int $count The number decreased by.
676
		 * @param int $donor_id Donor id.
677
		 */
678
		do_action( 'give_donor_post_decrease_donation_count', $this->purchase_count, $count, $this->id );
679
680
		return $this->purchase_count;
681
	}
682
683
	/**
684
	 * Increase the donor's lifetime value.
685
	 *
686
	 * @since  1.0
687
	 * @access public
688
	 *
689
	 * @param  float $value The value to increase by.
690
	 *
691
	 * @return mixed        If successful, the new value, otherwise false.
692
	 */
693
	public function increase_value( $value = 0.00 ) {
694
695
		$new_value = floatval( $this->purchase_value ) + $value;
696
697
		/**
698
		 * Fires before increasing donor lifetime value.
699
		 *
700
		 * @since 1.0
701
		 *
702
		 * @param float $value The value to increase by.
703
		 * @param int $donor_id Customer id.
704
		 */
705
		do_action( 'give_donor_pre_increase_value', $value, $this->id );
706
707
		if ( $this->update( array( 'purchase_value' => $new_value ) ) ) {
708
			$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...
709
		}
710
711
		/**
712
		 * Fires after increasing donor lifetime value.
713
		 *
714
		 * @since 1.0
715
		 *
716
		 * @param float $purchase_value Donor's lifetime value.
717
		 * @param float $value The value increased by.
718
		 * @param int $donor_id Donor id.
719
		 */
720
		do_action( 'give_donor_post_increase_value', $this->purchase_value, $value, $this->id );
721
722
		return $this->purchase_value;
723
	}
724
725
	/**
726
	 * Decrease a donor's lifetime value.
727
	 *
728
	 * @since  1.0
729
	 * @access public
730
	 *
731
	 * @param  float $value The value to decrease by.
732
	 *
733
	 * @return mixed        If successful, the new value, otherwise false.
734
	 */
735
	public function decrease_value( $value = 0.00 ) {
736
737
		$new_value = floatval( $this->purchase_value ) - $value;
738
739
		if ( $new_value < 0 ) {
740
			$new_value = 0.00;
741
		}
742
743
		/**
744
		 * Fires before decreasing donor lifetime value.
745
		 *
746
		 * @since 1.0
747
		 *
748
		 * @param float $value The value to decrease by.
749
		 * @param int $donor_id Donor id.
750
		 */
751
		do_action( 'give_donor_pre_decrease_value', $value, $this->id );
752
753
		if ( $this->update( array( 'purchase_value' => $new_value ) ) ) {
754
			$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...
755
		}
756
757
		/**
758
		 * Fires after decreasing donor lifetime value.
759
		 *
760
		 * @since 1.0
761
		 *
762
		 * @param float $purchase_value Donor lifetime value.
763
		 * @param float $value The value decreased by.
764
		 * @param int $donor_id Donor id.
765
		 */
766
		do_action( 'give_donor_post_decrease_value', $this->purchase_value, $value, $this->id );
767
768
		return $this->purchase_value;
769
	}
770
771
	/**
772
	 * Decrease/Increase a donor's lifetime value.
773
	 *
774
	 * This function will update donation stat on basis of current amount and new amount donation difference.
775
	 * Difference value can positive or negative. Negative value will decrease user donation stat while positive value increase donation stat.
776
	 *
777
	 * @since  1.0
778
	 * @access public
779
	 *
780
	 * @param  float $curr_amount Current Donation amount.
781
	 * @param  float $new_amount New (changed) Donation amount.
782
	 *
783
	 * @return mixed              If successful, the new donation stat value, otherwise false.
784
	 */
785
	public function update_donation_value( $curr_amount, $new_amount ) {
786
		/**
787
		 * Payment total difference value can be:
788
		 *  zero   (in case amount not change)
789
		 *  or -ve (in case amount decrease)
790
		 *  or +ve (in case amount increase)
791
		 */
792
		$payment_total_diff = $new_amount - $curr_amount;
793
794
		// We do not need to update donation stat if donation did not change.
795
		if ( ! $payment_total_diff ) {
796
			return false;
797
		}
798
799
		if ( $payment_total_diff > 0 ) {
800
			$this->increase_value( $payment_total_diff );
801
		} else {
802
			// Pass payment total difference as +ve value to decrease amount from user lifetime stat.
803
			$this->decrease_value( - $payment_total_diff );
804
		}
805
806
		return $this->purchase_value;
807
	}
808
809
	/**
810
	 * Get the parsed notes for a donor as an array.
811
	 *
812
	 * @since  1.0
813
	 * @access public
814
	 *
815
	 * @param  int $length The number of notes to get.
816
	 * @param  int $paged What note to start at.
817
	 *
818
	 * @return array       The notes requested.
819
	 */
820
	public function get_notes( $length = 20, $paged = 1 ) {
821
822
		$length = is_numeric( $length ) ? $length : 20;
823
		$offset = is_numeric( $paged ) && $paged != 1 ? ( ( absint( $paged ) - 1 ) * $length ) : 0;
824
825
		$all_notes   = $this->get_raw_notes();
826
		$notes_array = array_reverse( array_filter( explode( "\n\n", $all_notes ) ) );
827
828
		$desired_notes = array_slice( $notes_array, $offset, $length );
829
830
		return $desired_notes;
831
832
	}
833
834
	/**
835
	 * Get the total number of notes we have after parsing.
836
	 *
837
	 * @since  1.0
838
	 * @access public
839
	 *
840
	 * @return int The number of notes for the donor.
841
	 */
842
	public function get_notes_count() {
843
844
		$all_notes   = $this->get_raw_notes();
845
		$notes_array = array_reverse( array_filter( explode( "\n\n", $all_notes ) ) );
846
847
		return count( $notes_array );
848
849
	}
850
851
	/**
852
	 * Add a note for the donor.
853
	 *
854
	 * @since  1.0
855
	 * @access public
856
	 *
857
	 * @param  string $note The note to add. Default is empty.
858
	 *
859
	 * @return string|boolean The new note if added successfully, false otherwise.
860
	 */
861
	public function add_note( $note = '' ) {
862
863
		$note = trim( $note );
864
		if ( empty( $note ) ) {
865
			return false;
866
		}
867
868
		$notes = $this->get_raw_notes();
869
870
		if ( empty( $notes ) ) {
871
			$notes = '';
872
		}
873
874
		$note_string = date_i18n( 'F j, Y H:i:s', current_time( 'timestamp' ) ) . ' - ' . $note;
875
		$new_note    = apply_filters( 'give_customer_add_note_string', $note_string );
876
		$notes       .= "\n\n" . $new_note;
877
878
		/**
879
		 * Fires before donor note is added.
880
		 *
881
		 * @since 1.0
882
		 *
883
		 * @param string $new_note New note to add.
884
		 * @param int $donor_id Donor id.
885
		 */
886
		do_action( 'give_donor_pre_add_note', $new_note, $this->id );
887
888
		$updated = $this->update( array( 'notes' => $notes ) );
889
890
		if ( $updated ) {
891
			$this->notes = $this->get_notes();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->get_notes() of type array is incompatible with the declared type string of property $notes.

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

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

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1067
1068
		if ( $existing->id > 0 ) {
1069
			// Email address already belongs to another donor
1070
			return false;
1071
		}
1072
1073
		if ( email_exists( $email ) ) {
1074
			$user = get_user_by( 'email', $email );
1075
			if ( $user->ID != $this->user_id ) {
1076
				return false;
1077
			}
1078
		}
1079
1080
		do_action( 'give_donor_pre_add_email', $email, $this->id, $this );
1081
1082
		// Add is used to ensure duplicate emails are not added
1083
		$ret = (bool) $this->add_meta( 'additional_email', $email );
1084
1085
		do_action( 'give_donor_post_add_email', $email, $this->id, $this );
1086
1087
		if ( $ret && true === $primary ) {
1088
			$this->set_primary_email( $email );
1089
		}
1090
1091
		return $ret;
1092
	}
1093
1094
	/**
1095
	 * Remove an email from the donor.
1096
	 *
1097
	 * @since  1.7
1098
	 * @access public
1099
	 *
1100
	 * @param  string $email The email address to remove from the donor.
1101
	 *
1102
	 * @return bool          If the email was removed successfully.
1103
	 */
1104
	public function remove_email( $email = '' ) {
1105
		if ( ! is_email( $email ) ) {
1106
			return false;
1107
		}
1108
1109
		do_action( 'give_donor_pre_remove_email', $email, $this->id, $this );
1110
1111
		$ret = (bool) $this->delete_meta( 'additional_email', $email );
1112
1113
		do_action( 'give_donor_post_remove_email', $email, $this->id, $this );
1114
1115
		return $ret;
1116
	}
1117
1118
	/**
1119
	 * Set an email address as the donor's primary email.
1120
	 *
1121
	 * This will move the donor's previous primary email to an additional email.
1122
	 *
1123
	 * @since  1.7
1124
	 * @access public
1125
	 *
1126
	 * @param  string $new_primary_email The email address to remove from the donor.
1127
	 *
1128
	 * @return bool                      If the email was set as primary successfully.
1129
	 */
1130
	public function set_primary_email( $new_primary_email = '' ) {
1131
		if ( ! is_email( $new_primary_email ) ) {
1132
			return false;
1133
		}
1134
1135
		do_action( 'give_donor_pre_set_primary_email', $new_primary_email, $this->id, $this );
1136
1137
		$existing = new Give_Donor( $new_primary_email );
0 ignored issues
show
Documentation introduced by
$new_primary_email is of type string, but the function expects a boolean.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1138
1139
		if ( $existing->id > 0 && (int) $existing->id !== (int) $this->id ) {
1140
			// This email belongs to another donor.
1141
			return false;
1142
		}
1143
1144
		$old_email = $this->email;
1145
1146
		// Update donor record with new email.
1147
		$update = $this->update( array( 'email' => $new_primary_email ) );
1148
1149
		// Remove new primary from list of additional emails.
1150
		$remove = $this->remove_email( $new_primary_email );
1151
1152
		// Add old email to additional emails list.
1153
		$add = $this->add_email( $old_email );
1154
1155
		$ret = $update && $remove && $add;
1156
1157
		if ( $ret ) {
1158
			$this->email = $new_primary_email;
1159
		}
1160
1161
		do_action( 'give_donor_post_set_primary_email', $new_primary_email, $this->id, $this );
1162
1163
		return $ret;
1164
	}
1165
1166
	/**
1167
	 * Add donor address
1168
	 *
1169
	 * @since  2.0
1170
	 * @access public
1171
	 *
1172
	 * @param string $address_type
1173
	 * @param array  $address {
1174
	 *
1175
	 * @type string  $address2
1176
	 * @type string city
1177
	 * @type string zip
1178
	 * @type string state
1179
	 * @type string country
1180
	 * }
1181
	 *
1182
	 * @return void
1183
	 */
1184
	public function add_address( $address_type, $address ) {
1185
		$is_address_empty = true;
1186
1187
		// Address ready to process even if only one value set.
1188
		foreach ( $address as $value ) {
1189
			if( ! empty( $value ) ) {
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...
1190
				$is_address_empty = false;
1191
				break;
1192
			}
1193
		}
1194
1195
		// Bailout.
1196
		if ( empty( $address_type ) || $is_address_empty || ! $this->id ) {
1197
			return;
1198
		}
1199
1200
		$is_multi_address = ( false !== strpos( $address_type, '[]' ) );
1201
		$address_type  = $is_multi_address ?
1202
			str_replace( '[]', '', $address_type ) :
1203
			$address_type;
1204
1205
		// Bailout: do not save duplicate orders
1206
		if( $this->is_address_exist( $address_type, $address ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
1207
			return;
1208
		}
1209
1210
		// Set default address.
1211
		$address = wp_parse_args(
1212
			$address,
1213
			array(
1214
				'line1' => '',
1215
				'line2' => '',
1216
				'city'     => '',
1217
				'state'    => '',
1218
				'country'  => '',
1219
				'zip'      => '',
1220
			)
1221
		);
1222
1223
		// Set meta key prefix.
1224
		global $wpdb;
1225
		$meta_key_prefix = "_give_donor_address_{$address_type}_{address_name}";
1226
		$meta_type = Give()->donor_meta->meta_type;
1227
1228
		if ( $is_multi_address ) {
1229
			$address_count = $wpdb->get_var(
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
1230
				$wpdb->prepare(
1231
					"
1232
					SELECT COUNT(*) FROM {$wpdb->donormeta}
1233
					WHERE meta_key
1234
					LIKE '%%%s%%'
1235
					AND {$meta_type}_id=%d
1236
					",
1237
					"_give_donor_address_{$address_type}_address1",
1238
					$this->id
1239
				)
1240
			);
1241
1242
			$address_count   = $address_count ? $address_count : 0;
1243
			$meta_key_prefix = "_give_donor_address_{$address_type}_{address_name}_{$address_count}";
1244
		}
1245
1246
		// Save donor address.
1247
		foreach ( $address as $type => $value ) {
1248
			$meta_key = str_replace( '{address_name}', $type, $meta_key_prefix );
1249
			switch ( $type ) {
1250
				case 'line1':
1251
					$meta_key = str_replace( '{address_name}', 'address1', $meta_key_prefix );
1252
					Give()->donor_meta->update_meta( $this->id, $meta_key, $value );
1253
					break;
1254
1255
				case 'line2':
1256
					$meta_key = str_replace( '{address_name}', 'address2', $meta_key_prefix );
1257
					Give()->donor_meta->update_meta( $this->id, $meta_key, $value );
1258
					break;
1259
1260
				default:
1261
					Give()->donor_meta->update_meta( $this->id, $meta_key, $value );
1262
			}
1263
		}
1264
1265
		$this->setup_address();
1266
	}
1267
1268
1269
	/**
1270
	 * Check if donor already has current address
1271
	 *
1272
	 * @since 2.0
1273
	 * @access public
1274
	 *
1275
	 * @param string $current_address_type
1276
	 * @param array $current_address
1277
	 *
1278
	 * @return bool|null
1279
	 */
1280
	public function is_address_exist( $current_address_type, $current_address ) {
1281
		$status = false;
1282
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
1283
1284
		// Bailout.
1285
		if( empty( $current_address_type ) || empty( $current_address ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
1286
			return null;
1287
		}
1288
1289
		// Bailout.
1290
		if( empty( $this->address ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
1291
			return $status;
1292
		}
1293
0 ignored issues
show
Coding Style introduced by
Functions must not contain multiple empty lines in a row; found 2 empty lines
Loading history...
1294
1295
		// Compare address.
1296
		foreach ( $this->address as $address_type => $saved_address ) {
1297
			if( $current_address_type !== $address_type ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
1298
				continue;
1299
1300
			} elseif( empty( $saved_address[0] ) || ! is_array( $saved_address[0] ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
1301
				$status = ( $current_address == $saved_address );
1302
1303
			} else{
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
1304
				foreach ( $saved_address as $address ) {
1305
					if( empty( $saved_address ) ) {
0 ignored issues
show
introduced by
Space after opening control structure is required
Loading history...
introduced by
No space before opening parenthesis is prohibited
Loading history...
1306
						continue;
1307
					}
1308
1309
					$status = ( $current_address == $address );
1310
1311
					// Exit loop immediately if address exist.
1312
					if( $status ) {
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...
1313
						break;
1314
					}
1315
				}
1316
			}
1317
1318
			// Exit loop immediately if address exist.
1319
			if( $status ) {
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...
1320
				break;
1321
			}
1322
		}
1323
1324
		return $status;
1325
	}
1326
}
1327