Passed
Branch master (50908e)
by Stiofan
07:01
created

WPInv_Subscriptions_DB   F

Complexity

Total Complexity 69

Size/Duplication

Total Lines 576
Duplicated Lines 57.47 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
dl 331
loc 576
rs 2.88
c 0
b 0
f 0
wmc 69
lcom 1
cbo 2

6 Methods

Rating   Name   Duplication   Size   Complexity  
A get_column_defaults() 0 15 1
A create_table() 0 31 1
A __construct() 0 7 1
F count() 0 212 30
F get_subscriptions() 0 239 35
A get_columns() 0 17 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WPInv_Subscriptions_DB often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WPInv_Subscriptions_DB, and based on these observations, apply Extract Interface, too.

1
<?php
2
// Exit if accessed directly.
3
if (!defined( 'ABSPATH' ) ) exit;
4
5
/**
6
 * The Subscriptions DB Class
7
 *
8
 * @since  1.0.0
9
 */
10
11
class WPInv_Subscriptions_DB extends Wpinv_DB {
12
13
    /**
14
     * Get things started
15
     *
16
     * @access  public
17
     * @since   1.0.0
18
     */
19
    public function __construct() {
20
21
        global $wpdb;
22
23
        $this->table_name  = $wpdb->prefix . 'wpinv_subscriptions';
24
        $this->primary_key = 'id';
25
        $this->version     = '1.0.0';
26
27
    }
28
29
    /**
30
     * Get columns and formats
31
     *
32
     * @access  public
33
     * @since   1.0.0
34
     */
35
    public function get_columns() {
36
        return array(
37
            'id'                => '%d',
38
            'customer_id'       => '%d',
39
            'frequency'         => '%d',
40
            'period'            => '%s',
41
            'initial_amount'    => '%s',
42
            'recurring_amount'  => '%s',
43
            'bill_times'        => '%d',
44
            'transaction_id'    => '%s',
45
            'parent_payment_id' => '%d',
46
            'product_id'        => '%d',
47
            'created'           => '%s',
48
            'expiration'        => '%s',
49
            'trial_period'      => '%s',
50
            'status'            => '%s',
51
            'profile_id'        => '%s',
52
        );
53
    }
54
55
    /**
56
     * Get default column values
57
     *
58
     * @access  public
59
     * @since   1.0.0
60
     */
61
    public function get_column_defaults() {
62
        return array(
63
            'customer_id'       => 0,
64
            'period'            => '',
65
            'initial_amount'    => '',
66
            'recurring_amount'  => '',
67
            'bill_times'        => 0,
68
            'transaction_id'    => '',
69
            'parent_payment_id' => 0,
70
            'product_id'        => 0,
71
            'created'           => date( 'Y-m-d H:i:s' ),
72
            'expiration'        => date( 'Y-m-d H:i:s' ),
73
            'trial_period'      => '',
74
            'status'            => '',
75
            'profile_id'        => '',
76
        );
77
    }
78
79
    /**
80
     * Retrieve all subscriptions for a customer
81
     *
82
     * @access  public
83
     * @since   1.0.0
84
     */
85
    public function get_subscriptions( $args = array() ) {
86
        global $wpdb;
87
88
        $defaults = array(
89
            'number'       => get_option( 'posts_per_page' ),
90
            'offset'       => 0,
91
            'search'       => '',
92
            'customer_id'  => 0,
93
            'orderby'      => 'id',
94
            'order'        => 'DESC'
95
        );
96
97
        $args  = wp_parse_args( $args, $defaults );
0 ignored issues
show
Security Variable Injection introduced by
$args can contain request data and is used in variable name context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. Read from $_GET, and IssetNode ? $_GET['status'] : 'any' is assigned to $status
    in includes/admin/class-wpinv-subscriptions-list-table.php on line 339
  2. $status is assigned to $args
    in includes/admin/class-wpinv-subscriptions-list-table.php on line 355
  3. WPInv_Subscriptions_DB::get_subscriptions() is called
    in includes/admin/class-wpinv-subscriptions-list-table.php on line 358
  4. Enters via parameter $args
    in includes/class-wpinv-subscriptions-db.php on line 85

Used in variable context

  1. wp_parse_args() is called
    in includes/class-wpinv-subscriptions-db.php on line 97
  2. Enters via parameter $args
    in wordpress/wp-includes/functions.php on line 4294
  3. wp_parse_str() is called
    in wordpress/wp-includes/functions.php on line 4300
  4. Enters via parameter $string
    in wordpress/wp-includes/formatting.php on line 4865
  5. parse_str() is called
    in wordpress/wp-includes/formatting.php on line 4866

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
98
99
        if( $args['number'] < 1 ) {
100
            $args['number'] = 999999999999;
101
        }
102
103
        $where = ' WHERE 1=1 ';
104
105
        // specific customers
106
        if( ! empty( $args['id'] ) ) {
107
108
            if( is_array( $args['id'] ) ) {
109
                $ids = implode( ',', array_map('intval', $args['id'] ) );
110
            } else {
111
                $ids = intval( $args['id'] );
112
            }
113
114
            $where .= " AND `id` IN( {$ids} ) ";
115
116
        }
117
118
        // Specific products
119
        if( ! empty( $args['product_id'] ) ) {
120
121
            if( is_array( $args['product_id'] ) ) {
122
                $product_ids = implode( ',', array_map('intval', $args['product_id'] ) );
123
            } else {
124
                $product_ids = intval( $args['product_id'] );
125
            }
126
127
            $where .= " AND `product_id` IN( {$product_ids} ) ";
128
129
        }
130
131
        // Specific parent payments
132
        if( ! empty( $args['parent_payment_id'] ) ) {
133
134
            if( is_array( $args['parent_payment_id'] ) ) {
135
                $parent_payment_ids = implode( ',', array_map('intval', $args['parent_payment_id'] ) );
136
            } else {
137
                $parent_payment_ids = intval( $args['parent_payment_id'] );
138
            }
139
140
            $where .= " AND `parent_payment_id` IN( {$parent_payment_ids} ) ";
141
142
        }
143
144
        // Specific transaction IDs
145
        if( ! empty( $args['transaction_id'] ) ) {
146
147
            if( is_array( $args['transaction_id'] ) ) {
148
                $transaction_ids = implode( "','", array_map('sanitize_text_field', $args['transaction_id'] ) );
149
            } else {
150
                $transaction_ids = sanitize_text_field( $args['transaction_id'] );
151
            }
152
153
            $where .= " AND `transaction_id` IN ( '{$transaction_ids}' ) ";
154
155
        }
156
157
        // Subscriptoins for specific customers
158
        if( ! empty( $args['customer_id'] ) ) {
159
160
            if( is_array( $args['customer_id'] ) ) {
161
                $customer_ids = implode( ',', array_map('intval', $args['customer_id'] ) );
162
            } else {
163
                $customer_ids = intval( $args['customer_id'] );
164
            }
165
166
            $where .= " AND `customer_id` IN( {$customer_ids} ) ";
167
168
        }
169
170
        // Subscriptions for specific profile IDs
171
        if( ! empty( $args['profile_id'] ) ) {
172
173
            if( is_array( $args['profile_id'] ) ) {
174
                $profile_ids = implode( "','", array_map('sanitize_text_field', $args['profile_id'] ) );
175
            } else {
176
                $profile_ids = sanitize_text_field( $args['profile_id'] );
177
            }
178
179
            $where .= " AND `profile_id` IN( '{$profile_ids}' ) ";
180
181
        }
182
183
        // Subscriptions for specific statuses
184
        if( ! empty( $args['status'] ) ) {
185
186
            if( is_array( $args['status'] ) ) {
187
                $statuses = implode( "','", array_map( 'sanitize_text_field', $args['status'] ) );
188
            } else {
189
                $statuses = sanitize_text_field( $args['status'] );
190
            }
191
192
            $where .= " AND `status` IN( '{$statuses}' ) ";
193
194
        }
195
196
        // Subscriptions created for a specific date or in a date range
197
        if( ! empty( $args['date'] ) ) {
198
199
            if( is_array( $args['date'] ) ) {
200
201
                if( ! empty( $args['date']['start'] ) ) {
202
203
                    $start = date( 'Y-m-d H:i:s', strtotime( $args['date']['start'] ) );
204
205
                    $where .= " AND `created` >= '{$start}'";
206
207
                }
208
209
                if( ! empty( $args['date']['end'] ) ) {
210
211
                    $end = date( 'Y-m-d H:i:s', strtotime( $args['date']['end'] ) );
212
213
                    $where .= " AND `created` <= '{$end}'";
214
215
                }
216
217
            } else {
218
219
                $year  = date( 'Y', strtotime( $args['date'] ) );
220
                $month = date( 'm', strtotime( $args['date'] ) );
221
                $day   = date( 'd', strtotime( $args['date'] ) );
222
223
                $where .= " AND $year = YEAR ( created ) AND $month = MONTH ( created ) AND $day = DAY ( created )";
224
            }
225
226
        }
227
228
        // Subscriptions with a specific expiration date or in an expiration date range
229
        if( ! empty( $args['expiration'] ) ) {
230
231
            if( is_array( $args['expiration'] ) ) {
232
233
                if( ! empty( $args['expiration']['start'] ) ) {
234
235
                    $start = date( 'Y-m-d H:i:s', strtotime( $args['expiration']['start'] ) );
236
237
                    $where .= " AND `expiration` >= '{$start}'";
238
239
                }
240
241
                if( ! empty( $args['expiration']['end'] ) ) {
242
243
                    $end = date( 'Y-m-d H:i:s', strtotime( $args['expiration']['end'] ) );
244
245
                    $where .= " AND `expiration` <= '{$end}'";
246
247
                }
248
249
            } else {
250
251
                $year  = date( 'Y', strtotime( $args['expiration'] ) );
252
                $month = date( 'm', strtotime( $args['expiration'] ) );
253
                $day   = date( 'd', strtotime( $args['expiration'] ) );
254
255
                $where .= " AND $year = YEAR ( expiration ) AND $month = MONTH ( expiration ) AND $day = DAY ( expiration )";
256
            }
257
258
        }
259
260
        if ( ! empty( $args['search'] ) ) {
261
262
            if( false !== strpos( 'id:', $args['search'] ) ) {
263
264
                $args['search'] = trim( str_replace( 'id:', '', $args['search'] ) );
265
                $where .= " AND `id` = '" . esc_sql( $args['search'] ) . "'";
0 ignored issues
show
Bug introduced by
Are you sure esc_sql($args['search']) of type array|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

265
                $where .= " AND `id` = '" . /** @scrutinizer ignore-type */ esc_sql( $args['search'] ) . "'";
Loading history...
266
267
            } else if( false !== strpos( $args['search'], 'txn:' ) ) {
268
269
                $args['search'] = trim( str_replace( 'txn:', '', $args['search'] ) );
270
                $where .= " AND `transaction_id` = '" . esc_sql( $args['search'] ) . "'";
271
272
            } else if( false !== strpos( $args['search'], 'profile_id:' ) ) {
273
274
                $args['search'] = trim( str_replace( 'profile_id:', '', $args['search'] ) );
275
                $where .= " AND `profile_id` = '" . esc_sql( $args['search'] ) . "'";
276
277
            } else if( false !== strpos( $args['search'], 'product_id:' ) ) {
278
279
                $args['search'] = trim( str_replace( 'product_id:', '', $args['search'] ) );
280
                $where .= " AND `product_id` = '" . esc_sql( $args['search'] ) . "'";
281
282
            } else if( false !== strpos( $args['search'], 'customer_id:' ) ) {
283
284
                $args['search'] = trim( str_replace( 'customer_id:', '', $args['search'] ) );
285
                $where .= " AND `customer_id` = '" . esc_sql( $args['search'] ) . "'";
286
287
            } else {
288
289
                $where .= " AND ( `parent_payment_id` LIKE '%%" . esc_sql( $args['search'] ) . "%%' OR `profile_id` LIKE '%%" . esc_sql( $args['search'] ) . "%%' OR `transaction_id` LIKE '%%" . esc_sql( $args['search'] ) . "%%' OR `product_id` LIKE '%%" . esc_sql( $args['search'] ) . "%%' OR `id` = '" . esc_sql( $args['search'] ) . "' )";
290
291
            }
292
293
        }
294
295
        $args['orderby'] = ! array_key_exists( $args['orderby'], $this->get_columns() ) ? 'id' : $args['orderby'];
296
297
        if( 'amount' == $args['orderby'] ) {
298
            $args['orderby'] = 'amount+0';
299
        }
300
301
        $cache_key = md5( 'wpinv_subscriptions_' . serialize( $args ) );
302
303
        $subscriptions = wp_cache_get( $cache_key, 'subscriptions' );
304
305
        $args['orderby'] = esc_sql( $args['orderby'] );
306
        $args['order']   = esc_sql( $args['order'] );
307
308
        if( $subscriptions === false ) {
309
            $subscriptions = $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'] ) ), OBJECT );
310
311
            if( ! empty( $subscriptions ) ) {
312
313
                foreach( $subscriptions as $key => $subscription ) {
314
                    $subscriptions[ $key ] = new WPInv_Subscription( $subscription );
315
                }
316
317
                wp_cache_set( $cache_key, $subscriptions, 'subscriptions', 3600 );
318
319
            }
320
321
        }
322
323
        return $subscriptions;
324
    }
325
326
    /**
327
     * Count the total number of subscriptions in the database
328
     *
329
     * @access  public
330
     * @since   1.0.0
331
     */
332
    public function count( $args = array() ) {
333
334
        global $wpdb;
335
336
        $where = ' WHERE 1=1 ';
337
338
        // specific customers
339
        if( ! empty( $args['id'] ) ) {
340
341
            if( is_array( $args['id'] ) ) {
342
                $ids = implode( ',', array_map('intval', $args['id'] ) );
343
            } else {
344
                $ids = intval( $args['id'] );
345
            }
346
347
            $where .= " AND `id` IN( {$ids} ) ";
348
349
        }
350
351
        // Specific products
352
        if( ! empty( $args['product_id'] ) ) {
353
354
            if( is_array( $args['product_id'] ) ) {
355
                $product_ids = implode( ',', array_map('intval', $args['product_id'] ) );
356
            } else {
357
                $product_ids = intval( $args['product_id'] );
358
            }
359
360
            $where .= " AND `product_id` IN( {$product_ids} ) ";
361
362
        }
363
364
        // Specific parent payments
365
        if( ! empty( $args['parent_payment_id'] ) ) {
366
367
            if( is_array( $args['parent_payment_id'] ) ) {
368
                $parent_payment_ids = implode( ',', array_map('intval', $args['parent_payment_id'] ) );
369
            } else {
370
                $parent_payment_ids = intval( $args['parent_payment_id'] );
371
            }
372
373
            $where .= " AND `parent_payment_id` IN( {$parent_payment_ids} ) ";
374
375
        }
376
377
        // Subscriptoins for specific customers
378
        if( ! empty( $args['customer_id'] ) ) {
379
380
            if( is_array( $args['customer_id'] ) ) {
381
                $customer_ids = implode( ',', array_map('intval', $args['customer_id'] ) );
382
            } else {
383
                $customer_ids = intval( $args['customer_id'] );
384
            }
385
386
            $where .= " AND `customer_id` IN( {$customer_ids} ) ";
387
388
        }
389
390
        // Subscriptions for specific profile IDs
391
        if( ! empty( $args['profile_id'] ) ) {
392
393
            if( is_array( $args['profile_id'] ) ) {
394
                $profile_ids = implode( ',', array_map('intval', $args['profile_id'] ) );
395
            } else {
396
                $profile_ids = intval( $args['profile_id'] );
397
            }
398
399
            $where .= " AND `profile_id` IN( {$profile_ids} ) ";
400
401
        }
402
403
        // Specific transaction IDs
404
        if( ! empty( $args['transaction_id'] ) ) {
405
406
            if( is_array( $args['transaction_id'] ) ) {
407
                $transaction_ids = implode( ',', array_map('sanitize_text_field', $args['transaction_id'] ) );
408
            } else {
409
                $transaction_ids = sanitize_text_field( $args['transaction_id'] );
410
            }
411
412
            $where .= " AND `transaction_id` IN( {$transaction_ids} ) ";
413
414
        }
415
416
        // Subscriptions for specific statuses
417
        if( ! empty( $args['status'] ) ) {
418
419
            if( is_array( $args['status'] ) ) {
420
                $statuses = implode( ',', $args['status'] );
421
                $where  .= " AND `status` IN( {$statuses} ) ";
422
            } else {
423
                $statuses = $args['status'];
424
                $where  .= " AND `status` = '{$statuses}' ";
425
            }
426
427
428
429
        }
430
431
        // Subscriptions created for a specific date or in a date range
432
        if( ! empty( $args['date'] ) ) {
433
434
            if( is_array( $args['date'] ) ) {
435
436
                if( ! empty( $args['date']['start'] ) ) {
437
438
                    $start = date( 'Y-m-d H:i:s', strtotime( $args['date']['start'] ) );
439
440
                    $where .= " AND `created` >= '{$start}'";
441
442
                }
443
444
                if( ! empty( $args['date']['end'] ) ) {
445
446
                    $end = date( 'Y-m-d H:i:s', strtotime( $args['date']['end'] ) );
447
448
                    $where .= " AND `created` <= '{$end}'";
449
450
                }
451
452
            } else {
453
454
                $year  = date( 'Y', strtotime( $args['date'] ) );
455
                $month = date( 'm', strtotime( $args['date'] ) );
456
                $day   = date( 'd', strtotime( $args['date'] ) );
457
458
                $where .= " AND $year = YEAR ( created ) AND $month = MONTH ( created ) AND $day = DAY ( created )";
459
            }
460
461
        }
462
463
        // Subscriptions with a specific expiration date or in an expiration date range
464
        if( ! empty( $args['expiration'] ) ) {
465
466
            if( is_array( $args['expiration'] ) ) {
467
468
                if( ! empty( $args['expiration']['start'] ) ) {
469
470
                    $start = date( 'Y-m-d H:i:s', strtotime( $args['expiration']['start'] ) );
471
472
                    $where .= " AND `expiration` >= '{$start}'";
473
474
                }
475
476
                if( ! empty( $args['expiration']['end'] ) ) {
477
478
                    $end = date( 'Y-m-d H:i:s', strtotime( $args['expiration']['end'] ) );
479
480
                    $where .= " AND `expiration` <= '{$end}'";
481
482
                }
483
484
            } else {
485
486
                $year  = date( 'Y', strtotime( $args['expiration'] ) );
487
                $month = date( 'm', strtotime( $args['expiration'] ) );
488
                $day   = date( 'd', strtotime( $args['expiration'] ) );
489
490
                $where .= " AND $year = YEAR ( expiration ) AND $month = MONTH ( expiration ) AND $day = DAY ( expiration )";
491
            }
492
493
        }
494
495
        if ( ! empty( $args['search'] ) ) {
496
497
            if( false !== strpos( 'id:', $args['search'] ) ) {
498
499
                $args['search'] = trim( str_replace( 'id:', '', $args['search'] ) );
500
                $where .= " AND `id` = '" . esc_sql( $args['search'] ) . "'";
0 ignored issues
show
Bug introduced by
Are you sure esc_sql($args['search']) of type array|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

500
                $where .= " AND `id` = '" . /** @scrutinizer ignore-type */ esc_sql( $args['search'] ) . "'";
Loading history...
501
502
            } else if( false !== strpos( $args['search'], 'txn:' ) ) {
503
504
                $args['search'] = trim( str_replace( 'txn:', '', $args['search'] ) );
505
                $where .= " AND `transaction_id` = '" . esc_sql( $args['search'] ) . "'";
506
507
            } else if( false !== strpos( $args['search'], 'profile_id:' ) ) {
508
509
                $args['search'] = trim( str_replace( 'profile_id:', '', $args['search'] ) );
510
                $where .= " AND `profile_id` = '" . esc_sql( $args['search'] ) . "'";
511
512
            } else if( false !== strpos( $args['search'], 'product_id:' ) ) {
513
514
                $args['search'] = trim( str_replace( 'product_id:', '', $args['search'] ) );
515
                $where .= " AND `product_id` = '" . esc_sql( $args['search'] ) . "'";
516
517
            } else if( false !== strpos( $args['search'], 'customer_id:' ) ) {
518
519
                $args['search'] = trim( str_replace( 'customer_id:', '', $args['search'] ) );
520
                $where .= " AND `customer_id` = '" . esc_sql( $args['search'] ) . "'";
521
522
            } else {
523
524
                $where .= " AND ( `parent_payment_id` LIKE '%%" . esc_sql( $args['search'] ) . "%%' OR `profile_id` LIKE '%%" . esc_sql( $args['search'] ) . "%%' OR `transaction_id` LIKE '%%" . esc_sql( $args['search'] ) . "%%' OR `product_id` LIKE '%%" . esc_sql( $args['search'] ) . "%%' OR `id` = '" . esc_sql( $args['search'] ) . "' )";
525
526
            }
527
528
        }
529
530
        $cache_key = md5( 'wpinv_subscriptions_count' . serialize( $args ) );
531
532
        $count = wp_cache_get( $cache_key, 'subscriptions' );
533
534
        if( $count === false ) {
535
536
            $sql   = "SELECT COUNT($this->primary_key) FROM " . $this->table_name . "{$where};";
537
            $count = $wpdb->get_var( $sql );
538
539
            wp_cache_set( $cache_key, $count, 'subscriptions', 3600 );
540
541
        }
542
543
        return absint( $count );
544
545
    }
546
547
    /**
548
     * Create the table
549
     *
550
     * @access  public
551
     * @since   1.0.0
552
     */
553
    public function create_table() {
554
        global $wpdb;
555
556
        require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
557
558
        $sql = "CREATE TABLE " . $this->table_name . " (
559
        id bigint(20) NOT NULL AUTO_INCREMENT,
560
        customer_id bigint(20) NOT NULL,
561
        frequency int(11) NOT NULL DEFAULT '1',
562
        period varchar(20) NOT NULL,
563
        initial_amount mediumtext NOT NULL,
564
        recurring_amount mediumtext NOT NULL,
565
        bill_times bigint(20) NOT NULL,
566
        transaction_id varchar(60) NOT NULL,
567
        parent_payment_id bigint(20) NOT NULL,
568
        product_id bigint(20) NOT NULL,
569
        created datetime NOT NULL,
570
        expiration datetime NOT NULL,
571
        trial_period varchar(20) NOT NULL,
572
        status varchar(20) NOT NULL,
573
        profile_id varchar(60) NOT NULL,
574
        PRIMARY KEY  (id),
575
        KEY profile_id (profile_id),
576
        KEY customer (customer_id),
577
        KEY transaction (transaction_id),
578
        KEY customer_and_status ( customer_id, status)
579
        ) CHARACTER SET utf8 COLLATE utf8_general_ci;";
580
581
        dbDelta( $sql );
582
583
        update_option( $this->table_name . '_db_version', $this->version );
584
    }
585
586
}