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

WPInv_Item   F

Complexity

Total Complexity 110

Size/Duplication

Total Lines 503
Duplicated Lines 22.47 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
dl 113
loc 503
rs 2
c 0
b 0
f 0
wmc 110
lcom 1
cbo 0

37 Methods

Rating   Name   Duplication   Size   Complexity  
A get_ID() 0 2 1
A get_price() 0 12 3
A get_summary() 0 3 2
A save_metas() 0 12 4
A get_excerpt() 0 4 1
A is_recurring() 0 4 1
A get_name() 0 2 1
A get_vat_class() 0 10 3
A get_recurring_limit() 0 4 1
A can_purchase() 0 8 3
B get_trial_period() 0 25 7
A has_free_trial() 0 4 3
A get_status() 0 2 1
B create() 0 38 9
A is_free() 0 10 2
A get_minimum_price() 0 10 1
A get_custom_name() 0 4 1
A get_custom_id() 0 4 1
B update() 0 32 9
A get_custom_singular_name() 0 4 1
A update_meta() 0 12 3
A get_the_price() 0 4 1
A get_recurring_interval() 0 8 2
A setup_item() 0 22 5
A get_type() 0 10 3
A __construct() 0 3 1
A is_editable() 0 6 3
A get_trial_interval() 0 8 2
A get_is_dynamic_pricing() 0 4 1
B get_recurring_period() 0 25 7
A get_free_trial() 0 4 1
C get_fees() 0 41 17
A __get() 0 5 2
A get_vat_rule() 0 12 4
A get_is_recurring() 0 4 1
A get_editable() 0 4 1
A get_title() 0 2 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_Item 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_Item, and based on these observations, apply Extract Interface, too.

1
<?php
2
// Exit if accessed directly
3
if ( ! defined( 'ABSPATH' ) ) exit;
4
5
class WPInv_Item {
6
    public $ID = 0;
7
    private $type;
8
    private $title;
9
    private $custom_id;
10
    private $price;
11
    private $status;
12
    private $custom_name;
13
    private $custom_singular_name;
14
    private $vat_rule;
15
    private $vat_class;
16
    private $editable;
17
    private $excerpt;
18
    private $is_dynamic_pricing;
19
    private $minimum_price;
20
    private $is_recurring;
21
    private $recurring_period;
22
    private $recurring_interval;
23
    private $recurring_limit;
24
    private $free_trial;
25
    private $trial_period;
26
    private $trial_interval;
27
28
    public $post_author = 0;
29
    public $post_date = '0000-00-00 00:00:00';
30
    public $post_date_gmt = '0000-00-00 00:00:00';
31
    public $post_content = '';
32
    public $post_title = '';
33
    public $post_excerpt = '';
34
    public $post_status = 'publish';
35
    public $comment_status = 'open';
36
    public $ping_status = 'open';
37
    public $post_password = '';
38
    public $post_name = '';
39
    public $to_ping = '';
40
    public $pinged = '';
41
    public $post_modified = '0000-00-00 00:00:00';
42
    public $post_modified_gmt = '0000-00-00 00:00:00';
43
    public $post_content_filtered = '';
44
    public $post_parent = 0;
45
    public $guid = '';
46
    public $menu_order = 0;
47
    public $post_mime_type = '';
48
    public $comment_count = 0;
49
    public $filter;
50
51
52
    public function __construct( $_id = false, $_args = array() ) {
53
        $item = WP_Post::get_instance( $_id );
54
        return $this->setup_item( $item );
55
    }
56
57
    private function setup_item( $item ) {
58
        if( ! is_object( $item ) ) {
59
            return false;
60
        }
61
62
        if( ! is_a( $item, 'WP_Post' ) ) {
63
            return false;
64
        }
65
66
        if( 'wpi_item' !== $item->post_type ) {
67
            return false;
68
        }
69
70
        foreach ( $item as $key => $value ) {
71
            switch ( $key ) {
72
                default:
73
                    $this->$key = $value;
74
                    break;
75
            }
76
        }
77
78
        return true;
79
    }
80
81
    public function __get( $key ) {
82
        if ( method_exists( $this, 'get_' . $key ) ) {
83
            return call_user_func( array( $this, 'get_' . $key ) );
84
        } else {
85
            return new WP_Error( 'wpinv-item-invalid-property', sprintf( __( 'Can\'t get property %s', 'invoicing' ), $key ) );
86
        }
87
    }
88
89
    public function create( $data = array(), $wp_error = false ) {
90
        if ( $this->ID != 0 ) {
91
            return false;
92
        }
93
94
        $defaults = array(
95
            'post_type'   => 'wpi_item',
96
            'post_status' => 'draft',
97
            'post_title'  => __( 'New Invoice Item', 'invoicing' )
98
        );
99
100
        $args = wp_parse_args( $data, $defaults );
101
102
        do_action( 'wpinv_item_pre_create', $args );
103
104
        $id = wp_insert_post( $args, $wp_error );
105
        if ($wp_error && is_wp_error($id)) {
106
            return $id;
107
        }
108
        if ( !$id ) {
109
            return false;
110
        }
111
        
112
        $item = WP_Post::get_instance( $id );
0 ignored issues
show
Bug introduced by
It seems like $id can also be of type WP_Error; however, parameter $post_id of WP_Post::get_instance() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

112
        $item = WP_Post::get_instance( /** @scrutinizer ignore-type */ $id );
Loading history...
113
        
114
        if (!empty($item) && !empty($data['meta'])) {
115
            $this->ID = $item->ID;
116
            $this->save_metas($data['meta']);
117
        }
118
        
119
        // Set custom id if not set.
120
        if ( empty( $data['meta']['custom_id'] ) && !$this->get_custom_id() ) {
121
            $this->save_metas( array( 'custom_id' => $id ) );
122
        }
123
124
        do_action( 'wpinv_item_create', $id, $args );
125
126
        return $this->setup_item( $item );
127
    }
128
    
129
    public function update( $data = array(), $wp_error = false ) {
130
        if ( !$this->ID > 0 ) {
131
            return false;
132
        }
133
        
134
        $data['ID'] = $this->ID;
135
136
        do_action( 'wpinv_item_pre_update', $data );
137
        
138
        $id = wp_update_post( $data, $wp_error );
139
        if ($wp_error && is_wp_error($id)) {
140
            return $id;
141
        }
142
        
143
        if ( !$id ) {
144
            return false;
145
        }
146
147
        $item = WP_Post::get_instance( $id );
0 ignored issues
show
Bug introduced by
It seems like $id can also be of type WP_Error; however, parameter $post_id of WP_Post::get_instance() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

147
        $item = WP_Post::get_instance( /** @scrutinizer ignore-type */ $id );
Loading history...
148
        if (!empty($item) && !empty($data['meta'])) {
149
            $this->ID = $item->ID;
150
            $this->save_metas($data['meta']);
151
        }
152
153
        // Set custom id if not set.
154
        if ( empty( $data['meta']['custom_id'] ) && !$this->get_custom_id() ) {
155
            $this->save_metas( array( 'custom_id' => $id ) );
156
        }
157
158
        do_action( 'wpinv_item_update', $id, $data );
159
160
        return $this->setup_item( $item );
161
    }
162
163
    public function get_ID() {
164
        return $this->ID;
165
    }
166
167
    public function get_name() {
168
        return get_the_title( $this->ID );
169
    }
170
    
171
    public function get_title() {
172
        return get_the_title( $this->ID );
173
    }
174
    
175
    public function get_status() {
176
        return get_post_status( $this->ID );
177
    }
178
    
179
    public function get_summary() {
180
        $post = get_post( $this->ID );
181
        return !empty( $post->post_excerpt ) ? $post->post_excerpt : '';
182
    }
183
184
    public function get_price() {
185
        if ( ! isset( $this->price ) ) {
186
            $this->price = get_post_meta( $this->ID, '_wpinv_price', true );
187
            
188
            if ( $this->price ) {
189
                $this->price = wpinv_sanitize_amount( $this->price );
190
            } else {
191
                $this->price = 0;
192
            }
193
        }
194
        
195
        return apply_filters( 'wpinv_get_item_price', $this->price, $this->ID );
196
    }
197
    
198
    public function get_vat_rule() {
199
        global $wpinv_euvat;
200
        
201
        if( !isset( $this->vat_rule ) ) {
202
            $this->vat_rule = get_post_meta( $this->ID, '_wpinv_vat_rule', true );
203
204
            if ( empty( $this->vat_rule ) ) {        
205
                $this->vat_rule = $wpinv_euvat->allow_vat_rules() ? 'digital' : 'physical';
206
            }
207
        }
208
        
209
        return apply_filters( 'wpinv_get_item_vat_rule', $this->vat_rule, $this->ID );
210
    }
211
    
212
    public function get_vat_class() {
213
        if( !isset( $this->vat_class ) ) {
214
            $this->vat_class = get_post_meta( $this->ID, '_wpinv_vat_class', true );
215
216
            if ( empty( $this->vat_class ) ) {        
217
                $this->vat_class = '_standard';
218
            }
219
        }
220
        
221
        return apply_filters( 'wpinv_get_item_vat_class', $this->vat_class, $this->ID );
222
    }
223
224
    public function get_type() {
225
        if( ! isset( $this->type ) ) {
226
            $this->type = get_post_meta( $this->ID, '_wpinv_type', true );
227
228
            if ( empty( $this->type ) ) {
229
                $this->type = 'custom';
230
            }
231
        }
232
233
        return apply_filters( 'wpinv_get_item_type', $this->type, $this->ID );
234
    }
235
    
236
    public function get_custom_id() {
237
        $custom_id = get_post_meta( $this->ID, '_wpinv_custom_id', true );
238
239
        return apply_filters( 'wpinv_get_item_custom_id', $custom_id, $this->ID );
240
    }
241
    
242
    public function get_custom_name() {
243
        $custom_name = get_post_meta( $this->ID, '_wpinv_custom_name', true );
244
245
        return apply_filters( 'wpinv_get_item_custom_name', $custom_name, $this->ID );
246
    }
247
    
248
    public function get_custom_singular_name() {
249
        $custom_singular_name = get_post_meta( $this->ID, '_wpinv_custom_singular_name', true );
250
251
        return apply_filters( 'wpinv_get_item_custom_singular_name', $custom_singular_name, $this->ID );
252
    }
253
    
254
    public function get_editable() {
255
        $editable = get_post_meta( $this->ID, '_wpinv_editable', true );
256
257
        return apply_filters( 'wpinv_item_get_editable', $editable, $this->ID );
258
    }
259
    
260
    public function get_excerpt() {
261
        $excerpt = get_the_excerpt( $this->ID );
262
        
263
        return apply_filters( 'wpinv_item_get_excerpt', $excerpt, $this->ID );
264
    }
265
    
266
    /**
267
     * Checks whether the item allows a user to set their own price
268
     */
269
    public function get_is_dynamic_pricing() {
270
        $is_dynamic_pricing = get_post_meta( $this->ID, '_wpinv_dynamic_pricing', true );
271
272
        return (int) apply_filters( 'wpinv_item_get_is_dynamic_pricing', $is_dynamic_pricing, $this->ID );
273
274
    }
275
276
    /**
277
     * For dynamic prices, this is the minimum price that a user can set
278
     */
279
    public function get_minimum_price() {
280
281
        //Fetch the minimum price and cast it to a float
282
        $price = (float) get_post_meta( $this->ID, '_minimum_price', true );
283
            
284
        //Sanitize it
285
        $price = wpinv_sanitize_amount( $price );
286
287
        //Filter then return it
288
        return apply_filters( 'wpinv_item_get_minimum_price', $price, $this->ID );
289
290
    }
291
292
    public function get_is_recurring() {
293
        $is_recurring = get_post_meta( $this->ID, '_wpinv_is_recurring', true );
294
295
        return apply_filters( 'wpinv_item_get_is_recurring', $is_recurring, $this->ID );
296
297
    }
298
    
299
    public function get_recurring_period( $full = false ) {
300
        $period = get_post_meta( $this->ID, '_wpinv_recurring_period', true );
301
        
302
        if ( !in_array( $period, array( 'D', 'W', 'M', 'Y' ) ) ) {
303
            $period = 'D';
304
        }
305
        
306
        if ( $full ) {
307
            switch( $period ) {
308
                case 'D':
309
                    $period = 'day';
310
                break;
311
                case 'W':
312
                    $period = 'week';
313
                break;
314
                case 'M':
315
                    $period = 'month';
316
                break;
317
                case 'Y':
318
                    $period = 'year';
319
                break;
320
            }
321
        }
322
323
        return apply_filters( 'wpinv_item_recurring_period', $period, $full, $this->ID );
324
    }
325
    
326
    public function get_recurring_interval() {
327
        $interval = (int)get_post_meta( $this->ID, '_wpinv_recurring_interval', true );
328
        
329
        if ( !$interval > 0 ) {
330
            $interval = 1;
331
        }
332
333
        return apply_filters( 'wpinv_item_recurring_interval', $interval, $this->ID );
334
    }
335
    
336
    public function get_recurring_limit() {
337
        $limit = get_post_meta( $this->ID, '_wpinv_recurring_limit', true );
338
339
        return (int)apply_filters( 'wpinv_item_recurring_limit', $limit, $this->ID );
340
    }
341
    
342
    public function get_free_trial() {
343
        $free_trial = get_post_meta( $this->ID, '_wpinv_free_trial', true );
344
345
        return apply_filters( 'wpinv_item_get_free_trial', $free_trial, $this->ID );
346
    }
347
    
348
    public function get_trial_period( $full = false ) {
349
        $period = get_post_meta( $this->ID, '_wpinv_trial_period', true );
350
        
351
        if ( !in_array( $period, array( 'D', 'W', 'M', 'Y' ) ) ) {
352
            $period = 'D';
353
        }
354
        
355
        if ( $full ) {
356
            switch( $period ) {
357
                case 'D':
358
                    $period = 'day';
359
                break;
360
                case 'W':
361
                    $period = 'week';
362
                break;
363
                case 'M':
364
                    $period = 'month';
365
                break;
366
                case 'Y':
367
                    $period = 'year';
368
                break;
369
            }
370
        }
371
372
        return apply_filters( 'wpinv_item_trial_period', $period, $full, $this->ID );
373
    }
374
    
375
    public function get_trial_interval() {
376
        $interval = absint( get_post_meta( $this->ID, '_wpinv_trial_interval', true ) );
377
        
378
        if ( !$interval > 0 ) {
379
            $interval = 1;
380
        }
381
382
        return apply_filters( 'wpinv_item_trial_interval', $interval, $this->ID );
383
    }
384
    
385
    public function get_the_price() {
386
        $item_price = wpinv_price( wpinv_format_amount( $this->get_price() ) );
387
        
388
        return apply_filters( 'wpinv_get_the_item_price', $item_price, $this->ID );
389
    }
390
    
391
    public function is_recurring() {
392
        $is_recurring = $this->get_is_recurring();
393
394
        return (bool)apply_filters( 'wpinv_is_recurring_item', $is_recurring, $this->ID );
395
    }
396
    
397
    public function has_free_trial() {
398
        $free_trial = $this->is_recurring() && $this->get_free_trial() ? true : false;
399
400
        return (bool)apply_filters( 'wpinv_item_has_free_trial', $free_trial, $this->ID );
401
    }
402
403
    public function is_free() {
404
        $is_free = false;
405
        
406
        $price = get_post_meta( $this->ID, '_wpinv_price', true );
407
408
        if ( (float)$price == 0 ) {
409
            $is_free = true;
410
        }
411
412
        return (bool) apply_filters( 'wpinv_is_free_item', $is_free, $this->ID );
413
414
    }
415
    
416
    public function is_editable() {
417
        $editable = $this->get_editable();
418
419
        $is_editable = $editable === 0 || $editable === '0' ? false : true;
420
421
        return (bool) apply_filters( 'wpinv_item_is_editable', $is_editable, $this->ID );
422
    }
423
    
424
    public function save_metas( $metas = array() ) {
425
        if ( empty( $metas ) ) {
426
            return false;
427
        }
428
        
429
        foreach ( $metas as $meta_key => $meta_value ) {
430
            $meta_key = strpos($meta_key, '_wpinv_') !== 0 ? '_wpinv_' . $meta_key : $meta_key;
431
            
432
            $this->update_meta($meta_key, $meta_value);
433
        }
434
435
        return true;
436
    }
437
438
    public function update_meta( $meta_key = '', $meta_value = '', $prev_value = '' ) {
439
        if ( empty( $meta_key ) ) {
440
            return false;
441
        }
442
        
443
        if( '_wpinv_minimum_price' === $meta_key ) {
444
            $meta_key = '_minimum_price';
445
        }
446
447
        $meta_value = apply_filters( 'wpinv_update_item_meta_' . $meta_key, $meta_value, $this->ID );
448
449
        return update_post_meta( $this->ID, $meta_key, $meta_value, $prev_value );
450
    }
451
    
452
    public function get_fees( $type = 'fee', $item_id = 0 ) {
453
        global $wpi_session;
454
        
455
        $fees = $wpi_session->get( 'wpi_cart_fees' );
456
457
        if ( ! wpinv_get_cart_contents() ) {
458
            // We can only get item type fees when the cart is empty
459
            $type = 'custom';
460
        }
461
462
        if ( ! empty( $fees ) && ! empty( $type ) && 'all' !== $type ) {
463
            foreach( $fees as $key => $fee ) {
464
                if( ! empty( $fee['type'] ) && $type != $fee['type'] ) {
465
                    unset( $fees[ $key ] );
466
                }
467
            }
468
        }
469
470
        if ( ! empty( $fees ) && ! empty( $item_id ) ) {
471
            // Remove fees that don't belong to the specified Item
472
            foreach ( $fees as $key => $fee ) {
473
                if ( (int) $item_id !== (int)$fee['custom_id'] ) {
474
                    unset( $fees[ $key ] );
475
                }
476
            }
477
        }
478
479
        if ( ! empty( $fees ) ) {
480
            // Remove fees that belong to a specific item but are not in the cart
481
            foreach( $fees as $key => $fee ) {
482
                if( empty( $fee['custom_id'] ) ) {
483
                    continue;
484
                }
485
486
                if ( !wpinv_item_in_cart( $fee['custom_id'] ) ) {
487
                    unset( $fees[ $key ] );
488
                }
489
            }
490
        }
491
492
        return ! empty( $fees ) ? $fees : array();
493
    }
494
    
495
    public function can_purchase() {
496
        $can_purchase = true;
497
498
        if ( !current_user_can( 'edit_post', $this->ID ) && $this->post_status != 'publish' ) {
499
            $can_purchase = false;
500
        }
501
502
        return (bool)apply_filters( 'wpinv_can_purchase_item', $can_purchase, $this );
503
    }
504
505
    /**
506
     * Checks whether this item supports dynamic pricing or not
507
     */
508
    public function supports_dynamic_pricing() {
509
        return (bool) apply_filters( 'wpinv_item_supports_dynamic_pricing', true, $this );
510
    }
511
}
512