Issues (4296)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/class-give-donor.php (26 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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