Completed
Pull Request — master (#612)
by Devin
29:06 queued 10:27
created

Give_DB_Customers::delete()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20
Metric Value
dl 0
loc 20
ccs 0
cts 9
cp 0
rs 9.2
cc 4
eloc 10
nc 5
nop 1
crap 20
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 24 and the first side effect is on line 16.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/**
3
 * Customers DB class
4
 *
5
 * This class is for interacting with the customers' database table
6
 *
7
 * @package     Give
8
 * @subpackage  Classes/DB Customers
9
 * @copyright   Copyright (c) 2016, WordImpress
10
 * @license     http://opensource.org/licenses/gpl-2.0.php GNU Public License
11
 * @since       1.0
12
 */
13
14
// Exit if accessed directly
15
if ( ! defined( 'ABSPATH' ) ) {
16
	exit;
17
}
18
19
/**
20
 * Give_DB_Customers Class
21
 *
22
 * @since 1.0
23
 */
24
class Give_DB_Customers extends Give_DB {
25
26
	/**
27
	 * Get things started
28
	 *
29
	 * @access  public
30
	 * @since   1.0
31
	 */
32 34
	public function __construct() {
33
34 34
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
35
36 34
		$this->table_name  = $wpdb->prefix . 'give_customers';
37 34
		$this->primary_key = 'id';
38 34
		$this->version     = '1.0';
39
		
40 34
		add_action( 'profile_update', array( $this, 'update_customer_email_on_user_update' ), 10, 2 );
41
42 34
	}
43
44
	/**
45
	 * Get columns and formats
46
	 *
47
	 * @access  public
48
	 * @since   1.0
49
	 */
50 34
	public function get_columns() {
51
		return array(
52 34
			'id'             => '%d',
53 34
			'user_id'        => '%d',
54 34
			'name'           => '%s',
55 34
			'email'          => '%s',
56 34
			'payment_ids'    => '%s',
57 34
			'purchase_value' => '%f',
58 34
			'purchase_count' => '%d',
59 34
			'notes'          => '%s',
60 34
			'date_created'   => '%s',
61 34
		);
62
	}
63
64
	/**
65
	 * Get default column values
66
	 *
67
	 * @access  public
68
	 * @since   1.0
69
	 */
70 34
	public function get_column_defaults() {
71
		return array(
72 34
			'user_id'        => 0,
73 34
			'email'          => '',
74 34
			'name'           => '',
75 34
			'payment_ids'    => '',
76 34
			'purchase_value' => 0.00,
77 34
			'purchase_count' => 0,
78 34
			'notes'          => '',
79 34
			'date_created'   => date( 'Y-m-d H:i:s' ),
80 34
		);
81
	}
82
83
	/**
84
	 * Add a customer
85
	 *
86
	 * @access  public
87
	 * @since   1.0
88
	 */
89 34
	public function add( $data = array() ) {
90
91
		$defaults = array(
92
			'payment_ids' => ''
93 34
		);
94
95 34
		$args = wp_parse_args( $data, $defaults );
96
97 34
		if ( empty( $args['email'] ) ) {
98
			return false;
99
		}
100
101 34
		if ( ! empty( $args['payment_ids'] ) && is_array( $args['payment_ids'] ) ) {
102
			$args['payment_ids'] = implode( ',', array_unique( array_values( $args['payment_ids'] ) ) );
103
		}
104
105 34
		$customer = $this->get_customer_by( 'email', $args['email'] );
106
107 34
		if ( $customer ) {
108
			// update an existing customer
109
110
			// Update the payment IDs attached to the customer
111
			if ( ! empty( $args['payment_ids'] ) ) {
112
113
				if ( empty( $customer->payment_ids ) ) {
114
115
					$customer->payment_ids = $args['payment_ids'];
116
117
				} else {
118
119
					$existing_ids          = array_map( 'absint', explode( ',', $customer->payment_ids ) );
120
					$payment_ids           = array_map( 'absint', explode( ',', $args['payment_ids'] ) );
121
					$payment_ids           = array_merge( $payment_ids, $existing_ids );
122
					$customer->payment_ids = implode( ',', array_unique( array_values( $payment_ids ) ) );
123
124
				}
125
126
				$args['payment_ids'] = $customer->payment_ids;
127
128
			}
129
130
			$this->update( $customer->id, $args );
131
132
			return $customer->id;
133
134
		} else {
135
136 34
			return $this->insert( $args, 'customer' );
137
138
		}
139
140
	}
141
142
	/**
143
	 * Delete a customer
144
	 *
145
	 * NOTE: This should not be called directly as it does not make necessary changes to
146
	 * the payment meta and logs. Use give_customer_delete() instead
147
	 *
148
	 * @access  public
149
	 * @since   1.0
150
	 *
151
	 * @param bool|false $_id_or_email
152
	 *
153
	 * @return bool|false|int
154
	 */
155
	public function delete( $_id_or_email = false ) {
156
157
		if ( empty( $_id_or_email ) ) {
158
			return false;
159
		}
160
161
		$column   = is_email( $_id_or_email ) ? 'email' : 'id';
162
		$customer = $this->get_customer_by( $column, $_id_or_email );
163
164
		if ( $customer->id > 0 ) {
165
166
			global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
167
168
			return $wpdb->delete( $this->table_name, array( 'id' => $customer->id ), array( '%d' ) );
169
170
		} else {
171
			return false;
172
		}
173
174
	}
175
176
	/**
177
	 * Checks if a customer exists
178
	 *
179
	 * @access  public
180
	 * @since   1.0
181
	 */
182 1
	public function exists( $value = '', $field = 'email' ) {
183
		
184 1
		$columns = $this->get_columns();
185 1
		if ( ! array_key_exists( $field, $columns ) ) {
186
			return false;
187
		}
188
189 1
		return (bool) $this->get_column_by( 'id', $field, $value );
190
191
	}
192
193
	/**
194
	 * Attaches a payment ID to a customer
195
	 *
196
	 * @access  public
197
	 * @since   1.0
198
	 */
199 2
	public function attach_payment( $customer_id = 0, $payment_id = 0 ) {
200
201 2
		$customer = new Give_Customer( $customer_id );
202
203 2
		if ( empty( $customer->id ) ) {
204
			return false;
205
		}
206
207
		// Attach the payment, but don't increment stats, as this function previously did not
208 2
		return $customer->attach_payment( $payment_id, false );
209
210
	}
211
212
	/**
213
	 * Removes a payment ID from a customer
214
	 *
215
	 * @access  public
216
	 * @since   1.0
217
	 */
218 1
	public function remove_payment( $customer_id = 0, $payment_id = 0 ) {
219
220 1
		$customer = new Give_Customer( $customer_id );
221
222 1
		if ( ! $customer ) {
223
			return false;
224
		}
225
226
		// Remove the payment, but don't decrease stats, as this function previously did not
227 1
		return $customer->remove_payment( $payment_id, false );
228
229
	}
230
231
	/**
232
	 * Increments customer purchase stats
233
	 *
234
	 * @param int   $customer_id
235
	 * @param float $amount
236
	 *
237
	 * @return bool
238
	 */
239 34
	public function increment_stats( $customer_id = 0, $amount = 0.00 ) {
240
241 34
		$customer = new Give_Customer( $customer_id );
242
243 34
		if ( empty( $customer->id ) ) {
244
			return false;
245
		}
246
247 34
		$increased_count = $customer->increase_purchase_count();
248 34
		$increased_value = $customer->increase_value( $amount );
249
250 34
		return ( $increased_count && $increased_value ) ? true : false;
0 ignored issues
show
Bug Best Practice introduced by
The expression $increased_count of type false|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
251
252
	}
253
254
	/**
255
	 * Decrements customer purchase stats
256
	 *
257
	 * @access  public
258
	 * @since   1.0
259
	 */
260 1
	public function decrement_stats( $customer_id = 0, $amount = 0.00 ) {
261
262 1
		$customer = new Give_Customer( $customer_id );
263
264 1
		if ( ! $customer ) {
265
			return false;
266
		}
267
268 1
		$decreased_count = $customer->decrease_purchase_count();
269 1
		$decreased_value = $customer->decrease_value( $amount );
270
271 1
		return ( $decreased_count && $decreased_value ) ? true : false;
0 ignored issues
show
Bug Best Practice introduced by
The expression $decreased_count of type false|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
272
273
	}
274
275
	/**
276
	 * Updates the email address of a customer record when the email on a user is updated
277
	 *
278
	 * @access  public
279
	 * 
280
	 * @since   1.4.3
281
	 * 
282
	 * @param int $user_id
283
	 * @param $old_user_data
284
	 *
285
	 * @return bool
286
	 */
287
	public function update_customer_email_on_user_update( $user_id = 0, $old_user_data ) {
0 ignored issues
show
Unused Code introduced by
The parameter $old_user_data is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
288
289
		$customer = new Give_Customer( $user_id, true );
290
291
		if( ! $customer ) {
292
			return false;
293
		}
294
295
		$user = get_userdata( $user_id );
296
297
		if( ! empty( $user ) && $user->user_email !== $customer->email ) {
298
299
			if( ! $this->get_customer_by( 'email', $user->user_email ) ) {
300
301
				$success = $this->update( $customer->id, array( 'email' => $user->user_email ) );
302
303
				if( $success ) {
304
					// Update some payment meta if we need to
305
					$payments_array = explode( ',', $customer->payment_ids );
306
307
					if( ! empty( $payments_array ) ) {
308
309
						foreach ( $payments_array as $payment_id ) {
310
311
							give_update_payment_meta( $payment_id, 'email', $user->user_email );
312
313
						}
314
315
					}
316
317
					do_action( 'give_update_customer_email_on_user_update', $user, $customer );
318
319
				}
320
321
			}
322
323
		}
324
325
	}
326
	
327
	/**
328
	 * Retrieves a single customer from the database
329
	 *
330
	 * @access public
331
	 * @since  1.0
332
	 *
333
	 * @param  string $field id or email
334
	 * @param  mixed  $value The Customer ID or email to search
335
	 *
336
	 * @return mixed          Upon success, an object of the customer. Upon failure, NULL
337
	 */
338 34
	public function get_customer_by( $field = 'id', $value = 0 ) {
339 34
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
340
341 34
		if ( empty( $field ) || empty( $value ) ) {
342
			return null;
343
		}
344
345 34
		if ( 'id' == $field || 'user_id' == $field ) {
346
			// Make sure the value is numeric to avoid casting objects, for example,
347
			// to int 1.
348 34
			if ( ! is_numeric( $value ) ) {
349
				return false;
350
			}
351
352 34
			$value = intval( $value );
353
354 34
			if ( $value < 1 ) {
355
				return false;
356
			}
357
358 34
		} elseif ( 'email' === $field ) {
359
360 34
			if ( ! is_email( $value ) ) {
361
				return false;
362
			}
363
364 34
			$value = trim( $value );
365 34
		}
366
367 34
		if ( ! $value ) {
368
			return false;
369
		}
370
371
		switch ( $field ) {
372 34
			case 'id':
373 34
				$db_field = 'id';
374 34
				break;
375 34
			case 'email':
376 34
				$value    = sanitize_text_field( $value );
377 34
				$db_field = 'email';
378 34
				break;
379 16
			case 'user_id':
380 16
				$db_field = 'user_id';
381 16
				break;
382
			default:
383
				return false;
384
		}
385
386 34
		if ( ! $customer = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $this->table_name WHERE $db_field = %s LIMIT 1", $value ) ) ) {
387 34
			return false;
388
		}
389
390 34
		return $customer;
391
	}
392
393
	/**
394
	 * Retrieve customers from the database
395
	 *
396
	 * @access  public
397
	 * @since   1.0
398
	 */
399 2
	public function get_customers( $args = array() ) {
400
401 2
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
402
403
		$defaults = array(
404 2
			'number'  => 20,
405 2
			'offset'  => 0,
406 2
			'user_id' => 0,
407 2
			'orderby' => 'id',
408
			'order'   => 'DESC'
409 2
		);
410
411 2
		$args = wp_parse_args( $args, $defaults );
412
413 2
		if ( $args['number'] < 1 ) {
414
			$args['number'] = 999999999999;
415
		}
416
417 2
		$where = ' WHERE 1=1 ';
418
419
		// specific customers
420 2
		if ( ! empty( $args['id'] ) ) {
421
422
			if ( is_array( $args['id'] ) ) {
423
				$ids = implode( ',', array_map( 'intval', $args['id'] ) );
424
			} else {
425
				$ids = intval( $args['id'] );
426
			}
427
428
			$where .= " AND `id` IN( {$ids} ) ";
429
430
		}
431
432
		// customers for specific user accounts
433 2
		if ( ! empty( $args['user_id'] ) ) {
434
435
			if ( is_array( $args['user_id'] ) ) {
436
				$user_ids = implode( ',', array_map( 'intval', $args['user_id'] ) );
437
			} else {
438
				$user_ids = intval( $args['user_id'] );
439
			}
440
441
			$where .= " AND `user_id` IN( {$user_ids} ) ";
442
443
		}
444
445
		//specific customers by email
446 2
		if( ! empty( $args['email'] ) ) {
447
448
			if( is_array( $args['email'] ) ) {
449
450
				$emails_count       = count( $args['email'] );
451
				$emails_placeholder = array_fill( 0, $emails_count, '%s' );
452
				$emails             = implode( ', ', $emails_placeholder );
453
454
				$where .= $wpdb->prepare( " AND `email` IN( $emails ) ", $args['email'] );
455
			} else {
456
				$where .= $wpdb->prepare( " AND `email` = %s ", $args['email'] );
457
			}
458
		}
459
460
		// specific customers by name
461 2
		if( ! empty( $args['name'] ) ) {
462
			$where .= $wpdb->prepare( " AND `name` LIKE '%%%%" . '%s' . "%%%%' ", $args['name'] );
463
		}
464
465
		// Customers created for a specific date or in a date range
466 2
		if ( ! empty( $args['date'] ) ) {
467
468
			if ( is_array( $args['date'] ) ) {
469
470
				if ( ! empty( $args['date']['start'] ) ) {
471
472
					$start = date( 'Y-m-d H:i:s', strtotime( $args['date']['start'] ) );
473
474
					$where .= " AND `date_created` >= '{$start}'";
475
476
				}
477
478
				if ( ! empty( $args['date']['end'] ) ) {
479
480
					$end = date( 'Y-m-d H:i:s', strtotime( $args['date']['end'] ) );
481
482
					$where .= " AND `date_created` <= '{$end}'";
483
484
				}
485
486
			} else {
487
488
				$year  = date( 'Y', strtotime( $args['date'] ) );
489
				$month = date( 'm', strtotime( $args['date'] ) );
490
				$day   = date( 'd', strtotime( $args['date'] ) );
491
492
				$where .= " AND $year = YEAR ( date_created ) AND $month = MONTH ( date_created ) AND $day = DAY ( date_created )";
493
			}
494
495
		}
496
497 2
		$args['orderby'] = ! array_key_exists( $args['orderby'], $this->get_columns() ) ? 'id' : $args['orderby'];
498
499 2
		if ( 'purchase_value' == $args['orderby'] ) {
500
			$args['orderby'] = 'purchase_value+0';
501
		}
502
503 2
		$cache_key = md5( 'give_customers_' . serialize( $args ) );
504
505 2
		$customers = wp_cache_get( $cache_key, 'customers' );
506
507 2
		$args['orderby'] = esc_sql( $args['orderby'] );
508 2
		$args['order']   = esc_sql( $args['order'] );
509
510 2
		if ( $customers === false ) {
511 2
			$customers = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM  $this->table_name $where ORDER BY {$args['orderby']} {$args['order']} LIMIT %d,%d;", absint( $args['offset'] ), absint( $args['number'] ) ) );
512 2
			wp_cache_set( $cache_key, $customers, 'customers', 3600 );
513 2
		}
514
515 2
		return $customers;
516
517
	}
518
519
520
	/**
521
	 * Count the total number of customers in the database
522
	 *
523
	 * @access  public
524
	 * @since   1.0
525
	 */
526 1
	public function count( $args = array() ) {
527
528 1
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
529
530 1
		$where = ' WHERE 1=1 ';
531
532 1
		if ( ! empty( $args['date'] ) ) {
533
534 1
			if ( is_array( $args['date'] ) ) {
535
536 1
				$start = date( 'Y-m-d H:i:s', strtotime( $args['date']['start'] ) );
537 1
				$end   = date( 'Y-m-d H:i:s', strtotime( $args['date']['end'] ) );
538
539 1
				$where .= " AND `date_created` >= '{$start}' AND `date_created` <= '{$end}'";
540
541 1
			} else {
542
543
				$year  = date( 'Y', strtotime( $args['date'] ) );
544
				$month = date( 'm', strtotime( $args['date'] ) );
545
				$day   = date( 'd', strtotime( $args['date'] ) );
546
547
				$where .= " AND $year = YEAR ( date_created ) AND $month = MONTH ( date_created ) AND $day = DAY ( date_created )";
548
			}
549
550 1
		}
551
552
553 1
		$cache_key = md5( 'give_customers_count' . serialize( $args ) );
554
555 1
		$count = wp_cache_get( $cache_key, 'customers' );
556
557 1
		if ( $count === false ) {
558 1
			$count = $wpdb->get_var( "SELECT COUNT($this->primary_key) FROM " . $this->table_name . "{$where};" );
559 1
			wp_cache_set( $cache_key, $count, 'customers', 3600 );
560 1
		}
561
562 1
		return absint( $count );
563
564
	}
565
566
	/**
567
	 * Create the table
568
	 *
569
	 * @access  public
570
	 * @since   1.0
571
	 */
572 3
	public function create_table() {
573
574 3
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
575
576 3
		require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
577
578 3
		$sql = "CREATE TABLE " . $this->table_name . " (
579
		id bigint(20) NOT NULL AUTO_INCREMENT,
580
		user_id bigint(20) NOT NULL,
581
		email varchar(50) NOT NULL,
582
		name mediumtext NOT NULL,
583
		purchase_value mediumtext NOT NULL,
584
		purchase_count bigint(20) NOT NULL,
585
		payment_ids longtext NOT NULL,
586
		notes longtext NOT NULL,
587
		date_created datetime NOT NULL,
588
		PRIMARY KEY  (id),
589
		UNIQUE KEY email (email),
590
		KEY user (user_id)
591 3
		) CHARACTER SET utf8 COLLATE utf8_general_ci;";
592
593 3
		dbDelta( $sql );
594
595 3
		update_option( $this->table_name . '_db_version', $this->version );
596 3
	}
597
}
598