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 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 |
||
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 ); |
||
|
|||
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 ); |
||
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() { |
||
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() { |
||
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() ) { |
||
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() { |
||
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 |