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 WC_Coupon 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 WC_Coupon, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 36 | class WC_Coupon { |
||
| 37 | |||
| 38 | // Coupon message codes |
||
| 39 | const E_WC_COUPON_INVALID_FILTERED = 100; |
||
| 40 | const E_WC_COUPON_INVALID_REMOVED = 101; |
||
| 41 | const E_WC_COUPON_NOT_YOURS_REMOVED = 102; |
||
| 42 | const E_WC_COUPON_ALREADY_APPLIED = 103; |
||
| 43 | const E_WC_COUPON_ALREADY_APPLIED_INDIV_USE_ONLY = 104; |
||
| 44 | const E_WC_COUPON_NOT_EXIST = 105; |
||
| 45 | const E_WC_COUPON_USAGE_LIMIT_REACHED = 106; |
||
| 46 | const E_WC_COUPON_EXPIRED = 107; |
||
| 47 | const E_WC_COUPON_MIN_SPEND_LIMIT_NOT_MET = 108; |
||
| 48 | const E_WC_COUPON_NOT_APPLICABLE = 109; |
||
| 49 | const E_WC_COUPON_NOT_VALID_SALE_ITEMS = 110; |
||
| 50 | const E_WC_COUPON_PLEASE_ENTER = 111; |
||
| 51 | const E_WC_COUPON_MAX_SPEND_LIMIT_MET = 112; |
||
| 52 | const E_WC_COUPON_EXCLUDED_PRODUCTS = 113; |
||
| 53 | const E_WC_COUPON_EXCLUDED_CATEGORIES = 114; |
||
| 54 | const WC_COUPON_SUCCESS = 200; |
||
| 55 | const WC_COUPON_REMOVED = 201; |
||
| 56 | |||
| 57 | /** @public string Coupon code. */ |
||
| 58 | public $code = ''; |
||
| 59 | |||
| 60 | /** @public int Coupon ID. */ |
||
| 61 | public $id = 0; |
||
| 62 | |||
| 63 | /** @public bool Coupon exists */ |
||
| 64 | public $exists = false; |
||
| 65 | |||
| 66 | /** |
||
| 67 | * Coupon constructor. Loads coupon data. |
||
| 68 | * |
||
| 69 | * @access public |
||
| 70 | * @param mixed $code code of the coupon to load |
||
| 71 | */ |
||
| 72 | public function __construct( $code ) { |
||
| 75 | |||
| 76 | /** |
||
| 77 | * __isset function. |
||
| 78 | * |
||
| 79 | * @param mixed $key |
||
| 80 | * @return bool |
||
| 81 | */ |
||
| 82 | public function __isset( $key ) { |
||
| 88 | |||
| 89 | /** |
||
| 90 | * __get function. |
||
| 91 | * |
||
| 92 | * @param mixed $key |
||
| 93 | * @return mixed |
||
| 94 | */ |
||
| 95 | public function __get( $key ) { |
||
| 108 | |||
| 109 | /** |
||
| 110 | * Checks the coupon type. |
||
| 111 | * |
||
| 112 | * @param string $type Array or string of types |
||
| 113 | * @return bool |
||
| 114 | */ |
||
| 115 | public function is_type( $type ) { |
||
| 118 | |||
| 119 | /** |
||
| 120 | * Gets an coupon from the database. |
||
| 121 | * |
||
| 122 | * @param string $code |
||
| 123 | * @return bool |
||
| 124 | */ |
||
| 125 | private function get_coupon( $code ) { |
||
| 145 | |||
| 146 | /** |
||
| 147 | * Get a coupon ID from it's code. |
||
| 148 | * @since 2.5.0 woocommerce_coupon_code_query was removed in favour of woocommerce_get_coupon_id_from_code filter on the return. wp_cache was also implemented. |
||
| 149 | * @param string $code |
||
| 150 | * @return int |
||
| 151 | */ |
||
| 152 | private function get_coupon_id_from_code( $code ) { |
||
| 153 | global $wpdb; |
||
| 154 | |||
| 155 | $coupon_id = wp_cache_get( WC_Cache_Helper::get_cache_prefix( 'coupons' ) . 'coupon_id_from_code_' . $code, 'coupons' ); |
||
| 156 | |||
| 157 | if ( false === $coupon_id ) { |
||
| 158 | $sql = $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_title = %s AND post_type = 'shop_coupon' AND post_status = 'publish' ORDER BY post_date DESC LIMIT 1;", $this->code ); |
||
| 159 | |||
| 160 | if ( $coupon_id = apply_filters( 'woocommerce_get_coupon_id_from_code', $wpdb->get_var( $sql ), $this->code ) ) { |
||
| 161 | wp_cache_set( WC_Cache_Helper::get_cache_prefix( 'coupons' ) . 'coupon_id_from_code_' . $code, $coupon_id, 'coupons' ); |
||
| 162 | } |
||
| 163 | } |
||
| 164 | |||
| 165 | return absint( $coupon_id ); |
||
| 166 | } |
||
| 167 | |||
| 168 | /** |
||
| 169 | * Populates an order from the loaded post data. |
||
| 170 | */ |
||
| 171 | private function populate( $data = array() ) { |
||
| 229 | |||
| 230 | /** |
||
| 231 | * Format loaded data as array. |
||
| 232 | * @param string|array $array |
||
| 233 | * @return array |
||
| 234 | */ |
||
| 235 | public function format_array( $array ) { |
||
| 245 | |||
| 246 | /** |
||
| 247 | * Check if coupon needs applying before tax. |
||
| 248 | * |
||
| 249 | * @return bool |
||
| 250 | */ |
||
| 251 | public function apply_before_tax() { |
||
| 254 | |||
| 255 | /** |
||
| 256 | * Check if a coupon enables free shipping. |
||
| 257 | * |
||
| 258 | * @return bool |
||
| 259 | */ |
||
| 260 | public function enable_free_shipping() { |
||
| 263 | |||
| 264 | /** |
||
| 265 | * Check if a coupon excludes sale items. |
||
| 266 | * |
||
| 267 | * @return bool |
||
| 268 | */ |
||
| 269 | public function exclude_sale_items() { |
||
| 272 | |||
| 273 | /** |
||
| 274 | * Increase usage count for current coupon. |
||
| 275 | * |
||
| 276 | * @param string $used_by Either user ID or billing email |
||
| 277 | */ |
||
| 278 | public function inc_usage_count( $used_by = '' ) { |
||
| 288 | |||
| 289 | /** |
||
| 290 | * Decrease usage count for current coupon. |
||
| 291 | * |
||
| 292 | * @param string $used_by Either user ID or billing email |
||
| 293 | */ |
||
| 294 | public function dcr_usage_count( $used_by = '' ) { |
||
| 312 | |||
| 313 | /** |
||
| 314 | * Get records of all users who have used the current coupon. |
||
| 315 | * |
||
| 316 | * @access public |
||
| 317 | * @return array |
||
| 318 | */ |
||
| 319 | public function get_used_by() { |
||
| 324 | |||
| 325 | /** |
||
| 326 | * Returns the error_message string. |
||
| 327 | * |
||
| 328 | * @access public |
||
| 329 | * @return string |
||
| 330 | */ |
||
| 331 | public function get_error_message() { |
||
| 334 | |||
| 335 | /** |
||
| 336 | * Ensure coupon exists or throw exception. |
||
| 337 | * |
||
| 338 | * @throws Exception |
||
| 339 | */ |
||
| 340 | private function validate_exists() { |
||
| 345 | |||
| 346 | /** |
||
| 347 | * Ensure coupon usage limit is valid or throw exception. |
||
| 348 | * |
||
| 349 | * @throws Exception |
||
| 350 | */ |
||
| 351 | private function validate_usage_limit() { |
||
| 356 | |||
| 357 | /** |
||
| 358 | * Ensure coupon user usage limit is valid or throw exception. |
||
| 359 | * |
||
| 360 | * Per user usage limit - check here if user is logged in (against user IDs). |
||
| 361 | * Checked again for emails later on in WC_Cart::check_customer_coupons(). |
||
| 362 | * |
||
| 363 | * @param int $user_id |
||
| 364 | * @throws Exception |
||
| 365 | */ |
||
| 366 | private function validate_user_usage_limit( $user_id = 0 ) { |
||
| 379 | |||
| 380 | /** |
||
| 381 | * Ensure coupon date is valid or throw exception. |
||
| 382 | * |
||
| 383 | * @throws Exception |
||
| 384 | */ |
||
| 385 | private function validate_expiry_date() { |
||
| 390 | |||
| 391 | /** |
||
| 392 | * Ensure coupon amount is valid or throw exception. |
||
| 393 | * |
||
| 394 | * @throws Exception |
||
| 395 | */ |
||
| 396 | private function validate_minimum_amount() { |
||
| 401 | |||
| 402 | /** |
||
| 403 | * Ensure coupon amount is valid or throw exception. |
||
| 404 | * |
||
| 405 | * @throws Exception |
||
| 406 | */ |
||
| 407 | private function validate_maximum_amount() { |
||
| 412 | |||
| 413 | /** |
||
| 414 | * Ensure coupon is valid for products in the cart is valid or throw exception. |
||
| 415 | * |
||
| 416 | * @throws Exception |
||
| 417 | */ |
||
| 418 | View Code Duplication | private function validate_product_ids() { |
|
| 433 | |||
| 434 | /** |
||
| 435 | * Ensure coupon is valid for product categories in the cart is valid or throw exception. |
||
| 436 | * |
||
| 437 | * @throws Exception |
||
| 438 | */ |
||
| 439 | View Code Duplication | private function validate_product_categories() { |
|
| 440 | if ( sizeof( $this->product_categories ) > 0 ) { |
||
| 441 | $valid_for_cart = false; |
||
| 442 | if ( ! WC()->cart->is_empty() ) { |
||
| 443 | foreach( WC()->cart->get_cart() as $cart_item_key => $cart_item ) { |
||
| 444 | $product_cats = wc_get_product_cat_ids( $cart_item['product_id'] ); |
||
| 445 | |||
| 446 | // If we find an item with a cat in our allowed cat list, the coupon is valid |
||
| 447 | if ( sizeof( array_intersect( $product_cats, $this->product_categories ) ) > 0 ) { |
||
| 448 | $valid_for_cart = true; |
||
| 449 | } |
||
| 450 | } |
||
| 451 | } |
||
| 452 | if ( ! $valid_for_cart ) { |
||
| 453 | throw new Exception( self::E_WC_COUPON_NOT_APPLICABLE ); |
||
| 454 | } |
||
| 455 | } |
||
| 456 | } |
||
| 457 | |||
| 458 | /** |
||
| 459 | * Ensure coupon is valid for product categories in the cart is valid or throw exception. |
||
| 460 | * |
||
| 461 | * @throws Exception |
||
| 462 | */ |
||
| 463 | View Code Duplication | private function validate_excluded_product_categories() { |
|
| 481 | |||
| 482 | /** |
||
| 483 | * Ensure coupon is valid for sale items in the cart is valid or throw exception. |
||
| 484 | * |
||
| 485 | * @throws Exception |
||
| 486 | */ |
||
| 487 | private function validate_sale_items() { |
||
| 488 | if ( 'yes' === $this->exclude_sale_items && $this->is_type( wc_get_product_coupon_types() ) ) { |
||
| 489 | $valid_for_cart = false; |
||
| 490 | $product_ids_on_sale = wc_get_product_ids_on_sale(); |
||
| 491 | |||
| 492 | if ( ! WC()->cart->is_empty() ) { |
||
| 493 | foreach( WC()->cart->get_cart() as $cart_item_key => $cart_item ) { |
||
| 494 | if ( ! empty( $cart_item['variation_id'] ) ) { |
||
| 495 | if ( ! in_array( $cart_item['variation_id'], $product_ids_on_sale, true ) ) { |
||
| 496 | $valid_for_cart = true; |
||
| 497 | } |
||
| 498 | } elseif ( ! in_array( $cart_item['product_id'], $product_ids_on_sale, true ) ) { |
||
| 499 | $valid_for_cart = true; |
||
| 500 | } |
||
| 501 | } |
||
| 502 | } |
||
| 503 | if ( ! $valid_for_cart ) { |
||
| 504 | throw new Exception( self::E_WC_COUPON_NOT_VALID_SALE_ITEMS ); |
||
| 505 | } |
||
| 506 | } |
||
| 507 | } |
||
| 508 | |||
| 509 | /** |
||
| 510 | * Cart discounts cannot be added if non-eligble product is found in cart. |
||
| 511 | */ |
||
| 512 | private function validate_cart_excluded_items() { |
||
| 519 | |||
| 520 | /** |
||
| 521 | * Exclude products from cart. |
||
| 522 | * |
||
| 523 | * @throws Exception |
||
| 524 | */ |
||
| 525 | View Code Duplication | private function validate_cart_excluded_product_ids() { |
|
| 541 | |||
| 542 | /** |
||
| 543 | * Exclude categories from cart. |
||
| 544 | * |
||
| 545 | * @throws Exception |
||
| 546 | */ |
||
| 547 | View Code Duplication | private function validate_cart_excluded_product_categories() { |
|
| 565 | |||
| 566 | /** |
||
| 567 | * Exclude sale items from cart. |
||
| 568 | * |
||
| 569 | * @throws Exception |
||
| 570 | */ |
||
| 571 | private function validate_cart_excluded_sale_items() { |
||
| 572 | if ( $this->exclude_sale_items == 'yes' ) { |
||
| 573 | $valid_for_cart = true; |
||
| 574 | $product_ids_on_sale = wc_get_product_ids_on_sale(); |
||
| 575 | if ( ! WC()->cart->is_empty() ) { |
||
| 576 | foreach( WC()->cart->get_cart() as $cart_item_key => $cart_item ) { |
||
| 577 | if ( ! empty( $cart_item['variation_id'] ) ) { |
||
| 578 | if ( in_array( $cart_item['variation_id'], $product_ids_on_sale, true ) ) { |
||
| 579 | $valid_for_cart = false; |
||
| 580 | } |
||
| 581 | } elseif ( in_array( $cart_item['product_id'], $product_ids_on_sale, true ) ) { |
||
| 582 | $valid_for_cart = false; |
||
| 583 | } |
||
| 584 | } |
||
| 585 | } |
||
| 586 | if ( ! $valid_for_cart ) { |
||
| 587 | throw new Exception( self::E_WC_COUPON_NOT_VALID_SALE_ITEMS ); |
||
| 588 | } |
||
| 589 | } |
||
| 590 | } |
||
| 591 | |||
| 592 | /** |
||
| 593 | * Check if a coupon is valid. |
||
| 594 | * |
||
| 595 | * @return boolean validity |
||
| 596 | * @throws Exception |
||
| 597 | */ |
||
| 598 | public function is_valid() { |
||
| 622 | |||
| 623 | /** |
||
| 624 | * Check if a coupon is valid. |
||
| 625 | * |
||
| 626 | * @return bool |
||
| 627 | */ |
||
| 628 | public function is_valid_for_cart() { |
||
| 631 | |||
| 632 | /** |
||
| 633 | * Check if a coupon is valid for a product. |
||
| 634 | * |
||
| 635 | * @param WC_Product $product |
||
| 636 | * @return boolean |
||
| 637 | */ |
||
| 638 | public function is_valid_for_product( $product, $values = array() ) { |
||
| 694 | |||
| 695 | /** |
||
| 696 | * Get discount amount for a cart item. |
||
| 697 | * |
||
| 698 | * @param float $discounting_amount Amount the coupon is being applied to |
||
| 699 | * @param array|null $cart_item Cart item being discounted if applicable |
||
| 700 | * @param boolean $single True if discounting a single qty item, false if its the line |
||
| 701 | * @return float Amount this coupon has discounted |
||
| 702 | */ |
||
| 703 | public function get_discount_amount( $discounting_amount, $cart_item = null, $single = false ) { |
||
| 755 | |||
| 756 | /** |
||
| 757 | * Converts one of the WC_Coupon message/error codes to a message string and. |
||
| 758 | * displays the message/error. |
||
| 759 | * |
||
| 760 | * @param int $msg_code Message/error code. |
||
| 761 | */ |
||
| 762 | public function add_coupon_message( $msg_code ) { |
||
| 776 | |||
| 777 | /** |
||
| 778 | * Map one of the WC_Coupon message codes to a message string. |
||
| 779 | * |
||
| 780 | * @param integer $msg_code |
||
| 781 | * @return string| Message/error string |
||
| 782 | */ |
||
| 783 | View Code Duplication | public function get_coupon_message( $msg_code ) { |
|
| 797 | |||
| 798 | /** |
||
| 799 | * Map one of the WC_Coupon error codes to a message string. |
||
| 800 | * |
||
| 801 | * @param int $err_code Message/error code. |
||
| 802 | * @return string| Message/error string |
||
| 803 | */ |
||
| 804 | public function get_coupon_error( $err_code ) { |
||
| 880 | |||
| 881 | /** |
||
| 882 | * Map one of the WC_Coupon error codes to an error string. |
||
| 883 | * No coupon instance will be available where a coupon does not exist, |
||
| 884 | * so this static method exists. |
||
| 885 | * |
||
| 886 | * @param int $err_code Error code |
||
| 887 | * @return string| Error string |
||
| 888 | */ |
||
| 889 | View Code Duplication | public static function get_generic_coupon_error( $err_code ) { |
|
| 904 | } |
||
| 905 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.