Completed
Pull Request — master (#613)
by Devin
60:55 queued 41:08
created

Give_DB_Customers::create_table()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 23
ccs 0
cts 0
cp 0
crap 2
rs 9.0856
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
	}
43
44
	/**
45
	 * Get columns and formats
46
	 *
47
	 * @access  public
48 34
	 * @since   1.0
49
	 */
50 34
	public function get_columns() {
51 34
		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
			'date_created'   => '%s',
61
		);
62
	}
63
64
	/**
65
	 * Get default column values
66
	 *
67
	 * @access  public
68 34
	 * @since   1.0
69
	 */
70 34
	public function get_column_defaults() {
71 34
		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
			'date_created'   => date( 'Y-m-d H:i:s' ),
80
		);
81
	}
82
83
	/**
84
	 * Add a customer
85
	 *
86
	 * @access  public
87 34
	 * @since   1.0
88
	 */
89
	public function add( $data = array() ) {
90
91 34
		$defaults = array(
92
			'payment_ids' => ''
93 34
		);
94
95 34
		$args = wp_parse_args( $data, $defaults );
96
97
		if ( empty( $args['email'] ) ) {
98
			return false;
99 34
		}
100
101
		if ( ! empty( $args['payment_ids'] ) && is_array( $args['payment_ids'] ) ) {
102
			$args['payment_ids'] = implode( ',', array_unique( array_values( $args['payment_ids'] ) ) );
103 34
		}
104
105 34
		$customer = $this->get_customer_by( 'email', $args['email'] );
106
107
		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 34
		} else {
135
136
			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 1
	 * @since   1.0
181
	 */
182 1
	public function exists( $value = '', $field = 'email' ) {
183
		
184
		$columns = $this->get_columns();
185
		if ( ! array_key_exists( $field, $columns ) ) {
186
			return false;
187
		}
188
189
		return (bool) $this->get_column_by( 'id', $field, $value );
190
191
	}
192 2
193
	/**
194 2
	 * Attaches a payment ID to a customer
195
	 *
196 2
	 * @access  public
197
	 * @since   1.0
198
	 */
199
	public function attach_payment( $customer_id = 0, $payment_id = 0 ) {
200
201 2
		$customer = new Give_Customer( $customer_id );
202
203
		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
		return $customer->attach_payment( $payment_id, false );
209
210
	}
211 1
212
	/**
213 1
	 * Removes a payment ID from a customer
214
	 *
215 1
	 * @access  public
216
	 * @since   1.0
217
	 */
218
	public function remove_payment( $customer_id = 0, $payment_id = 0 ) {
219
220 1
		$customer = new Give_Customer( $customer_id );
221
222
		if ( ! $customer ) {
223
			return false;
224
		}
225
226
		// Remove the payment, but don't decrease stats, as this function previously did not
227
		return $customer->remove_payment( $payment_id, false );
228
229
	}
230
231
	/**
232 34
	 * Increments customer purchase stats
233
	 *
234 34
	 * @param int   $customer_id
235
	 * @param float $amount
236 34
	 *
237
	 * @return bool
238
	 */
239
	public function increment_stats( $customer_id = 0, $amount = 0.00 ) {
240 34
241 34
		$customer = new Give_Customer( $customer_id );
242
243 34
		if ( empty( $customer->id ) ) {
244
			return false;
245
		}
246
247
		$increased_count = $customer->increase_purchase_count();
248
		$increased_value = $customer->increase_value( $amount );
249
250
		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 1
254
	/**
255 1
	 * Decrements customer purchase stats
256
	 *
257 1
	 * @access  public
258
	 * @since   1.0
259
	 */
260
	public function decrement_stats( $customer_id = 0, $amount = 0.00 ) {
261 1
262 1
		$customer = new Give_Customer( $customer_id );
263
264 1
		if ( ! $customer ) {
265
			return false;
266
		}
267
268
		$decreased_count = $customer->decrease_purchase_count();
269
		$decreased_value = $customer->decrease_value( $amount );
270
271
		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 34
	 * 
280 34
	 * @since   1.4.3
281
	 * 
282 34
	 * @param int $user_id
283
	 * @param $old_user_data
284
	 *
285
	 * @return bool
286 34
	 */
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 34
		$customer = new Give_Customer( $user_id, true );
290
291
		if( ! $customer ) {
292
			return false;
293 34
		}
294
295 34
		$user = get_userdata( $user_id );
296
297
		if( ! empty( $user ) && $user->user_email !== $customer->email ) {
298
299 34
			if( ! $this->get_customer_by( 'email', $user->user_email ) ) {
300
301 34
				$success = $this->update( $customer->id, array( 'email' => $user->user_email ) );
302
303
				if( $success ) {
304
					// Update some payment meta if we need to
305 34
					$payments_array = explode( ',', $customer->payment_ids );
306 34
307
					if( ! empty( $payments_array ) ) {
308 34
309
						foreach ( $payments_array as $payment_id ) {
310
311
							give_update_payment_meta( $payment_id, 'email', $user->user_email );
312
313 34
						}
314 34
315 34
					}
316 34
317 34
					do_action( 'give_update_customer_email_on_user_update', $user, $customer );
318 34
319 34
				}
320 16
321 16
			}
322 16
323
		}
324
325
	}
326
	
327 34
	/**
328 34
	 * Retrieves a single customer from the database
329
	 *
330
	 * @access public
331 34
	 * @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
	public function get_customer_by( $field = 'id', $value = 0 ) {
339
		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 2
341
		if ( empty( $field ) || empty( $value ) ) {
342 2
			return null;
343
		}
344
345 2
		if ( 'id' == $field || 'user_id' == $field ) {
346 2
			// Make sure the value is numeric to avoid casting objects, for example,
347 2
			// to int 1.
348 2
			if ( ! is_numeric( $value ) ) {
349
				return false;
350 2
			}
351
352 2
			$value = intval( $value );
353
354 2
			if ( $value < 1 ) {
355
				return false;
356
			}
357
358 2
		} elseif ( 'email' === $field ) {
359
360
			if ( ! is_email( $value ) ) {
361 2
				return false;
362
			}
363
364
			$value = trim( $value );
365
		}
366
367
		if ( ! $value ) {
368
			return false;
369
		}
370
371
		switch ( $field ) {
372
			case 'id':
373
				$db_field = 'id';
374 2
				break;
375
			case 'email':
376
				$value    = sanitize_text_field( $value );
377
				$db_field = 'email';
378
				break;
379
			case 'user_id':
380
				$db_field = 'user_id';
381
				break;
382
			default:
383
				return false;
384
		}
385
386
		if ( ! $customer = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $this->table_name WHERE $db_field = %s LIMIT 1", $value ) ) ) {
387 2
			return false;
388
		}
389
390
		return $customer;
391
	}
392
393
	/**
394
	 * Retrieve customers from the database
395
	 *
396
	 * @access  public
397
	 * @since   1.0
398
	 */
399
	public function get_customers( $args = array() ) {
400
401
		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
			'offset'  => 0,
406
			'user_id' => 0,
407
			'orderby' => 'id',
408
			'order'   => 'DESC'
409
		);
410
411
		$args = wp_parse_args( $args, $defaults );
412
413
		if ( $args['number'] < 1 ) {
414 2
			$args['number'] = 999999999999;
415
		}
416
417
		$where = ' WHERE 1=1 ';
418
419
		// specific customers
420
		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
		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 2
		//specific customers by email
446
		if( ! empty( $args['email'] ) ) {
447 2
448
			if( is_array( $args['email'] ) ) {
449
450
				$emails_count       = count( $args['email'] );
451 2
				$emails_placeholder = array_fill( 0, $emails_count, '%s' );
452
				$emails             = implode( ', ', $emails_placeholder );
453 2
454
				$where .= $wpdb->prepare( " AND `email` IN( $emails ) ", $args['email'] );
455 2
			} else {
456 2
				$where .= $wpdb->prepare( " AND `email` = %s ", $args['email'] );
457
			}
458 2
		}
459 2
460 2
		// specific customers by name
461 2
		if( ! empty( $args['name'] ) ) {
462
			$where .= $wpdb->prepare( " AND `name` LIKE '%%%%" . '%s' . "%%%%' ", $args['name'] );
463 2
		}
464
465
		// Customers created for a specific date or in a date range
466
		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 1
					$where .= " AND `date_created` >= '{$start}'";
475
476 1
				}
477
478 1
				if ( ! empty( $args['date']['end'] ) ) {
479
480 1
					$end = date( 'Y-m-d H:i:s', strtotime( $args['date']['end'] ) );
481
482 1
					$where .= " AND `date_created` <= '{$end}'";
483
484 1
				}
485 1
486
			} else {
487 1
488
				$year  = date( 'Y', strtotime( $args['date'] ) );
489 1
				$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
		$args['orderby'] = ! array_key_exists( $args['orderby'], $this->get_columns() ) ? 'id' : $args['orderby'];
498 1
499
		if ( 'purchase_value' == $args['orderby'] ) {
500
			$args['orderby'] = 'purchase_value+0';
501 1
		}
502
503 1
		$cache_key = md5( 'give_customers_' . serialize( $args ) );
504
505 1
		$customers = wp_cache_get( $cache_key, 'customers' );
506 1
507 1
		$args['orderby'] = esc_sql( $args['orderby'] );
508 1
		$args['order']   = esc_sql( $args['order'] );
509
510 1
		if ( $customers === false ) {
511
			$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
			wp_cache_set( $cache_key, $customers, 'customers', 3600 );
513
		}
514
515
		return $customers;
516
517
	}
518
519
520 3
	/**
521
	 * Count the total number of customers in the database
522 3
	 *
523
	 * @access  public
524 3
	 * @since   1.0
525
	 */
526 3
	public function count( $args = array() ) {
527
528
		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
		$where = ' WHERE 1=1 ';
531
532
		if ( ! empty( $args['date'] ) ) {
533
534
			if ( is_array( $args['date'] ) ) {
535
536
				$start = date( 'Y-m-d H:i:s', strtotime( $args['date']['start'] ) );
537
				$end   = date( 'Y-m-d H:i:s', strtotime( $args['date']['end'] ) );
538
539 3
				$where .= " AND `date_created` >= '{$start}' AND `date_created` <= '{$end}'";
540
541 3
			} else {
542
543 3
				$year  = date( 'Y', strtotime( $args['date'] ) );
544 3
				$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
		}
551
552
553
		$cache_key = md5( 'give_customers_count' . serialize( $args ) );
554
555
		$count = wp_cache_get( $cache_key, 'customers' );
556
557
		if ( $count === false ) {
558
			$count = $wpdb->get_var( "SELECT COUNT($this->primary_key) FROM " . $this->table_name . "{$where};" );
559
			wp_cache_set( $cache_key, $count, 'customers', 3600 );
560
		}
561
562
		return absint( $count );
563
564
	}
565
566
	/**
567
	 * Create the table
568
	 *
569
	 * @access  public
570
	 * @since   1.0
571
	 */
572
	public function create_table() {
573
		
574
		require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
575
576
		$sql = "CREATE TABLE " . $this->table_name . " (
577
		id bigint(20) NOT NULL AUTO_INCREMENT,
578
		user_id bigint(20) NOT NULL,
579
		email varchar(50) NOT NULL,
580
		name mediumtext NOT NULL,
581
		purchase_value mediumtext NOT NULL,
582
		purchase_count bigint(20) NOT NULL,
583
		payment_ids longtext NOT NULL,
584
		notes longtext NOT NULL,
585
		date_created datetime NOT NULL,
586
		PRIMARY KEY  (id),
587
		UNIQUE KEY email (email),
588
		KEY user (user_id)
589
		) CHARACTER SET utf8 COLLATE utf8_general_ci;";
590
591
		dbDelta( $sql );
592
593
		update_option( $this->table_name . '_db_version', $this->version );
594
	}
595
	
596
	/**
597
	 * Check if the Customers table was ever installed
598
	 *
599
	 * @since  1.4.3
600
	 * @return bool Returns if the customers table was installed and upgrade routine run
601
	 */
602
	public function installed() {
603
		return $this->table_exists( $this->table_name );
604
	}
605
}
606