WooCommerce Extended Coupon Features - Version 2.3.0

Version Description

  • (PRO) FEATURE: Allow customer to choose a free product
  • (PRO) FEATURE: Setting the priority of auto coupons (Useful for Individual use coupons)
  • (PRO) FEATURE: Display extra columns on the Coupon Admin page (auto coupon, individual use, priority, free products)
  • (PRO) TWEAK: Free products: Display 'Free!' as subtotal for free products, (adaptable with filter 'wjecf_free_cart_item_subtotal' )
  • (PRO) FIX: Free products: Plugin wouldn't always detect the free products in cart and kept appending free products
  • (PRO) Introduction of the API for developers, see wjecf-pro-api.php
  • FEATURE: Filter to only display Auto Coupons on the Coupon Admin page
  • FIX: Compatibilty PHP 5.4
  • FIX: Rewritten and simplified Autocoupon removal/addition routine making it more robust
  • FIX: Multiplier value calculation (as for now only used for Free Products)
  • FIX: Coupon must never be valid for free products (wjecf_free_product_coupon set in cartitem)
  • INTERNAL: Refactoring of all classes
  • INTERNAL: New log for debugging
Download this release

Release Info

Developer josk79
Plugin Icon 128x128 WooCommerce Extended Coupon Features
Version 2.3.0
Comparing to
See all releases

Code changes from version 2.2.5.1 to 2.3.0

assets/screenshot-1.png DELETED
Binary file
assets/screenshot-2.png DELETED
Binary file
assets/screenshot-3.png DELETED
Binary file
assets/screenshot-4.png DELETED
Binary file
assets/wjecf.css ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ WJECF columns system
3
+ */
4
+ ul.wjecf-cols { list-style-type: none;margin:0}
5
+ ul.wjecf-cols > li { float:left;margin-right:3%;margin-left:0; }
6
+ .woocommerce-cart table.cart ul.wjecf-cols > li > img { width:auto; }
7
+
8
+ ul.wjecf-cols.cols-2 li { width:48.5%; }
9
+ ul.wjecf-cols.cols-3 li { width:31.3%; }
10
+ ul.wjecf-cols.cols-4 li { width:22.7%; }
11
+ ul.wjecf-cols.cols-5 li { width:17.6%; }
12
+ ul.wjecf-cols.cols-6 li { width:14.1%; }
13
+
14
+ ul.wjecf-cols.cols-2 li:nth-child(2n+2),
15
+ ul.wjecf-cols.cols-3 li:nth-child(3n+3),
16
+ ul.wjecf-cols.cols-4 li:nth-child(4n+4),
17
+ ul.wjecf-cols.cols-5 li:nth-child(5n+5),
18
+ ul.wjecf-cols.cols-6 li:nth-child(6n+6) { margin-right:0!important; }
includes/abstract-wjecf-plugin.php ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Abstract_WJECF_Plugin {
4
+ /**
5
+ * Log a message (for debugging)
6
+ *
7
+ * @param string $message The message to log
8
+ *
9
+ */
10
+ protected function log ( $message ) {
11
+ WJECF()->log( $message, 1 );
12
+ }
13
+ }
includes/{wjecf-coupon-extensions.php → admin/wjecf-admin.php} RENAMED
@@ -2,103 +2,25 @@
2
 
3
  defined('ABSPATH') or die();
4
 
5
- if ( class_exists('WC_Jos_Extended_Coupon_Features_Controller') ) {
6
  return;
7
  }
8
 
9
- class WC_Jos_Extended_Coupon_Features_Controller {
10
- private $options = false;
11
-
12
- /**
13
- * Singleton Instance
14
- *
15
- * @static
16
- * @return Singleton Instance
17
- */
18
- public static function instance() {
19
- if ( is_null( self::$_instance ) ) {
20
- self::$_instance = new self();
21
- }
22
- return self::$_instance;
23
- }
24
- protected static $_instance = null;
25
-
26
 
27
  public function __construct() {
28
- add_action('init', array( &$this, 'controller_init' ));
29
- }
30
-
31
- public function controller_init() {
32
- if ( ! class_exists('WC_Coupon') ) {
33
- return;
34
- }
35
-
36
- $this->init_options();
37
-
38
- //Admin hooks
39
- add_action( 'admin_init', array( &$this, 'admin_init' ) );
40
-
41
- //Frontend hooks
42
- add_filter('woocommerce_coupon_is_valid', array( &$this, 'coupon_is_valid' ), 10, 2 );
43
- add_action('woocommerce_coupon_loaded', array( $this, 'woocommerce_coupon_loaded' ), 10, 1);
44
- add_filter('woocommerce_coupon_get_discount_amount', array( $this, 'woocommerce_coupon_get_discount_amount' ), 10, 5);
45
- }
46
-
47
- public function init_options() {
48
- $this->options = get_option( 'wjecf_options' );
49
- if (false === $this->options) {
50
- $this->options = array( 'db_version' => 0 );
51
- }
52
- }
53
-
54
- /**
55
- * Check whether WooCommerce version is greater or equal than $req_version
56
- * @param string @req_version The version to compare to
57
- * @return bool true if WooCommerce is at least the given version
58
- */
59
- public static function check_woocommerce_version( $req_version ) {
60
- return version_compare( self::get_woocommerce_version(), $req_version, '>=' );
61
  }
62
 
63
- private static $wc_version = null;
64
- /**
65
- * Get the WooCommerce version number
66
- * @return string|bool WC Version number or false if WC not detected
67
- */
68
- public static function get_woocommerce_version() {
69
- if (self::$wc_version === null) {
70
- // If get_plugins() isn't available, require it
71
- if ( ! function_exists( 'get_plugins' ) ) {
72
- require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
73
- }
74
-
75
- // Create the plugins folder and file variables
76
- $plugin_folder = get_plugins( '/woocommerce' );
77
- $plugin_file = 'woocommerce.php';
78
-
79
- // If the plugin version number is set, return it
80
- if ( isset( $plugin_folder[$plugin_file]['Version'] ) ) {
81
- self::$wc_version = $plugin_folder[$plugin_file]['Version'];
82
- } else {
83
- self::$wc_version = false; // Not found
84
- }
85
-
86
- //echo "<pre>";var_dump( get_site_option( 'active_sitewide_plugins' ) );die();
87
- //echo "<pre>";var_dump( get_plugins() );die();
88
- //die(self::$wc_version);
89
- }
90
- return self::$wc_version;
91
- }
92
-
93
  //Upgrade options on version change
94
  public function auto_upgrade_options() {
95
  $current_db_version = 1;
96
  //Check version
97
- if ( $this->options['db_version'] < $current_db_version ) {
98
  global $wpdb;
99
 
100
  //DB_VERSION 1: Since 2.1.0-b5
101
- if ($this->options['db_version'] < 1) {
102
  //RENAME meta_key _wjecf_matching_product_qty TO _wjecf_min_matching_product_qty
103
  $where = array("meta_key" => "_wjecf_matching_product_qty");
104
  $set = array('meta_key' => "_wjecf_min_matching_product_qty");
@@ -109,11 +31,11 @@ class WC_Jos_Extended_Coupon_Features_Controller {
109
  $set = array('meta_key' => "_wjecf_is_auto_coupon");
110
  $wpdb->update( _get_meta_table('post'), $set, $where );
111
  //Now we're version 1
112
- $this->options['db_version'] = 1;
113
  }
114
 
115
  // Write options to database
116
- update_option( 'wjecf_options' , $this->options, false );
117
  }
118
  }
119
 
@@ -166,7 +88,7 @@ class WC_Jos_Extended_Coupon_Features_Controller {
166
  }
167
  private function admin_coupon_data_footer() {
168
  $documentation_url = plugins_url( 'docs/index.html', dirname( __FILE__ ) );
169
- if ( ! $this->is_pro() ) {
170
  $documentation_url = 'http://www.soft79.nl/documentation/wjecf';
171
  ?>
172
  <h3><?php _e( 'Do you find WooCommerce Extended Coupon Features useful?', 'woocommerce-jos-autocoupon'); ?></h3>
@@ -313,7 +235,7 @@ class WC_Jos_Extended_Coupon_Features_Controller {
313
  <p class="form-field"><label for="wjecf_shipping_methods"><?php _e( 'Shipping methods', 'woocommerce-jos-autocoupon' ); ?></label>
314
  <select id="wjecf_shipping_methods" name="wjecf_shipping_methods[]" style="width: 50%;" class="wc-enhanced-select" multiple="multiple" data-placeholder="<?php _e( 'Any shipping method', 'woocommerce-jos-autocoupon' ); ?>">
315
  <?php
316
- $coupon_shipping_method_ids = $this->get_coupon_shipping_method_ids( $thepostid );
317
  $shipping_methods = WC()->shipping->load_shipping_methods();
318
 
319
  if ( $shipping_methods ) foreach ( $shipping_methods as $shipping_method ) {
@@ -329,7 +251,7 @@ class WC_Jos_Extended_Coupon_Features_Controller {
329
  <p class="form-field"><label for="wjecf_payment_methods"><?php _e( 'Payment methods', 'woocommerce-jos-autocoupon' ); ?></label>
330
  <select id="wjecf_payment_methods" name="wjecf_payment_methods[]" style="width: 50%;" class="wc-enhanced-select" multiple="multiple" data-placeholder="<?php _e( 'Any payment method', 'woocommerce-jos-autocoupon' ); ?>">
331
  <?php
332
- $coupon_payment_method_ids = $this->get_coupon_payment_method_ids( $thepostid );
333
  //DONT USE WC()->payment_gateways->available_payment_gateways() AS IT CAN CRASH IN UNKNOWN OCCASIONS
334
  $payment_methods = WC()->payment_gateways->payment_gateways();
335
  if ( $payment_methods ) foreach ( $payment_methods as $payment_method ) {
@@ -354,7 +276,7 @@ class WC_Jos_Extended_Coupon_Features_Controller {
354
  ?>
355
  <p class="form-field"><label><?php _e( 'Allowed Customers', 'woocommerce-jos-autocoupon' ); ?></label>
356
  <input type="hidden" class="wc-customer-search" data-multiple="true" style="width: 50%;" name="wjecf_customer_ids" data-placeholder="<?php _e( 'Any customer', 'woocommerce-jos-autocoupon' ); ?>" data-action="woocommerce_json_search_customers" data-selected="<?php
357
- $coupon_customer_ids = $this->get_coupon_customer_ids( $thepostid );
358
  $json_ids = array();
359
 
360
  foreach ( $coupon_customer_ids as $customer_id ) {
@@ -376,7 +298,7 @@ class WC_Jos_Extended_Coupon_Features_Controller {
376
  <p class="form-field"><label for="wjecf_customer_roles"><?php _e( 'Allowed User Roles', 'woocommerce-jos-autocoupon' ); ?></label>
377
  <select id="wjecf_customer_roles" name="wjecf_customer_roles[]" style="width: 50%;" class="wc-enhanced-select" multiple="multiple" data-placeholder="<?php _e( 'Any role', 'woocommerce-jos-autocoupon' ); ?>">
378
  <?php
379
- $coupon_customer_roles = $this->get_coupon_customer_roles( $thepostid );
380
 
381
  $available_customer_roles = array_reverse( get_editable_roles() );
382
  foreach ( $available_customer_roles as $role_id => $role ) {
@@ -398,7 +320,7 @@ class WC_Jos_Extended_Coupon_Features_Controller {
398
  <p class="form-field"><label for="wjecf_excluded_customer_roles"><?php _e( 'Disallowed User Roles', 'woocommerce-jos-autocoupon' ); ?></label>
399
  <select id="wjecf_customer_roles" name="wjecf_excluded_customer_roles[]" style="width: 50%;" class="wc-enhanced-select" multiple="multiple" data-placeholder="<?php _e( 'Any role', 'woocommerce-jos-autocoupon' ); ?>">
400
  <?php
401
- $coupon_excluded_customer_roles = $this->get_coupon_excluded_customer_roles( $thepostid );
402
 
403
  foreach ( $available_customer_roles as $role_id => $role ) {
404
  $role_name = translate_user_role($role['name'] );
@@ -467,365 +389,6 @@ class WC_Jos_Extended_Coupon_Features_Controller {
467
 
468
  }
469
 
470
- /* FRONTEND HOOKS */
471
-
472
- //2.2.2
473
- function woocommerce_coupon_loaded ( $coupon ) {
474
- if ( ! is_admin() ) {
475
- //2.2.2 Allow coupon even if minimum spend not reached
476
- if ( get_post_meta( $coupon->id, '_wjecf_allow_below_minimum_spend', true ) == 'yes' ) {
477
- //HACK: Overwrite the minimum amount with 0 so WooCommerce will allow the coupon
478
- $coupon->wjecf_minimum_amount_for_discount = $coupon->minimum_amount;
479
- $coupon->minimum_amount = 0;
480
- }
481
- }
482
- }
483
-
484
- //2.2.2
485
- function woocommerce_coupon_get_discount_amount ( $discount, $discounting_amount, $cart_item, $single, $coupon ) {
486
- //2.2.2 No value if minimum spend not reached
487
- if (isset( $coupon->wjecf_minimum_amount_for_discount ) ) {
488
- if ( wc_format_decimal( $coupon->wjecf_minimum_amount_for_discount ) > wc_format_decimal( WC()->cart->subtotal ) ) {
489
- return 0;
490
- };
491
- }
492
- return $discount;
493
- }
494
-
495
-
496
- /* Extra validation rules for coupons */
497
- function coupon_is_valid ( $valid, $coupon ) {
498
- //Not valid? Then it will never validate, so get out of here
499
- if ( ! $valid ) {
500
- return false;
501
- }
502
-
503
- //============================
504
- //Test if ALL products are in the cart (if AND-operator selected in stead of the default OR)
505
- $products_and = get_post_meta( $coupon->id, '_wjecf_products_and', true ) == 'yes';
506
- if ( $products_and && sizeof( $coupon->product_ids ) > 1 ) { // We use > 1, because if size == 1, 'AND' makes no difference
507
- //Get array of all cart product and variation ids
508
- $cart_item_ids = array();
509
- foreach( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
510
- $cart_item_ids[] = $cart_item['product_id'];
511
- $cart_item_ids[] = $cart_item['variation_id'];
512
- }
513
- //check if every single product is in the cart
514
- foreach( $coupon->product_ids as $product_id ) {
515
- if ( ! in_array( $product_id, $cart_item_ids ) ) {
516
- return false;
517
- }
518
- }
519
- }
520
-
521
- //============================
522
- //Test if products form ALL categories are in the cart (if AND-operator selected in stead of the default OR)
523
- $categories_and = get_post_meta( $coupon->id, '_wjecf_categories_and', true ) == 'yes';
524
- if ( $categories_and && sizeof( $coupon->product_categories ) > 1 ) { // We use > 1, because if size == 1, 'AND' makes no difference
525
- //Get array of all cart product and variation ids
526
- $cart_product_cats = array();
527
- foreach( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
528
- $cart_product_cats = array_merge ( $cart_product_cats, wp_get_post_terms( $cart_item['product_id'], 'product_cat', array( "fields" => "ids" ) ) );
529
- }
530
- //check if every single category is in the cart
531
- foreach( $coupon->product_categories as $cat_id ) {
532
- if ( ! in_array( $cat_id, $cart_product_cats ) ) {
533
- return false;
534
- }
535
- }
536
-
537
- }
538
-
539
- //============================
540
- //Test min/max quantity of matching products
541
- //
542
- //For all items in the cart:
543
- // If coupon contains both a product AND category inclusion filter: the item is counted if it matches either one of them
544
- // If coupon contains either a product OR category exclusion filter: the item will NOT be counted if it matches either one of them
545
- // If sale items are excluded by the coupon: the item will NOT be counted if it is a sale item
546
- // If no filter exist, all items will be counted
547
-
548
- $multiplier = null; //null = not initialized
549
-
550
- //Validate quantity
551
- $min_matching_product_qty = intval(get_post_meta( $coupon->id, '_wjecf_min_matching_product_qty', true ));
552
- $max_matching_product_qty = intval(get_post_meta( $coupon->id, '_wjecf_max_matching_product_qty', true ));
553
- if ( $min_matching_product_qty > 0 || $max_matching_product_qty > 0 ) {
554
- //Count the products
555
- $qty = $this->get_quantity_of_matching_products( $coupon );
556
- if ($min_matching_product_qty > 0 && $qty < $min_matching_product_qty) return false;
557
- if ($max_matching_product_qty > 0 && $qty > $max_matching_product_qty) return false;
558
- $multiplier = self::min_value( floor( $qty / $min_matching_product_qty ), $multiplier );
559
- }
560
-
561
- //Validate subtotal (2.2.2)
562
- $min_matching_product_subtotal = intval(get_post_meta( $coupon->id, '_wjecf_min_matching_product_subtotal', true ));
563
- $max_matching_product_subtotal = intval(get_post_meta( $coupon->id, '_wjecf_max_matching_product_subtotal', true ));
564
- if ( $min_matching_product_subtotal > 0 || $max_matching_product_subtotal > 0 ) {
565
- $subtotal = $this->get_subtotal_of_matching_products( $coupon );
566
- if ($min_matching_product_subtotal > 0 && $subtotal < $min_matching_product_subtotal) return false;
567
- if ($max_matching_product_subtotal > 0 && $subtotal > $max_matching_product_subtotal) return false;
568
- $multiplier = self::min_value( floor( $subtotal / $min_matching_product_subtotal ), $multiplier );
569
- }
570
-
571
- //============================
572
- //Test restricted shipping methods
573
- $shipping_method_ids = $this->get_coupon_shipping_method_ids( $coupon->id );
574
- if ( sizeof( $shipping_method_ids ) > 0 ) {
575
- $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
576
- $chosen_shipping = $chosen_shipping_methods[0];
577
-
578
- if ( ! in_array( $chosen_shipping, $shipping_method_ids ) ) {
579
- return false;
580
- }
581
- }
582
-
583
- //============================
584
- //Test restricted payment methods
585
- $payment_method_ids = $this->get_coupon_payment_method_ids( $coupon->id );
586
- if ( sizeof( $payment_method_ids ) > 0 ) {
587
- $chosen_payment_method = isset( WC()->session->chosen_payment_method ) ? WC()->session->chosen_payment_method : array();
588
-
589
- if ( ! in_array( $chosen_payment_method, $payment_method_ids ) ) {
590
- return false;
591
- }
592
- }
593
-
594
-
595
- //============================
596
- //Test restricted user ids and roles
597
- //NOTE: If both customer id and role restrictions are provided, the coupon matches if either the id or the role matches
598
- $coupon_customer_ids = $this->get_coupon_customer_ids( $coupon->id );
599
- $coupon_customer_roles = $this->get_coupon_customer_roles( $coupon->id );
600
- if ( sizeof( $coupon_customer_ids ) > 0 || sizeof( $coupon_customer_roles ) > 0 ) {
601
- $user = wp_get_current_user();
602
-
603
- //If both fail we invalidate. Otherwise it's ok
604
- if ( ! in_array( $user->ID, $coupon_customer_ids ) && ! array_intersect( $user->roles, $coupon_customer_roles ) ) {
605
- return false;
606
- }
607
- }
608
-
609
- //============================
610
- //Test excluded user roles
611
- $coupon_excluded_customer_roles = $this->get_coupon_excluded_customer_roles( $coupon->id );
612
- if ( sizeof( $coupon_excluded_customer_roles ) > 0 ) {
613
- $user = wp_get_current_user();
614
-
615
- //Excluded customer roles
616
- if ( array_intersect( $user->roles, $coupon_excluded_customer_roles ) ) {
617
- return false;
618
- }
619
- }
620
-
621
- if ( $coupon->minimum_amount ) {
622
- $multiplier = self::min_value( floor( WC()->cart->subtotal / $coupon->minimum_amount ), $multiplier );
623
- }
624
-
625
-
626
- $this->coupon_multiplier_values[ $coupon->code ] = $multiplier;
627
-
628
- return true;
629
- }
630
-
631
- /**
632
- * Return the lowest multiplier value
633
- */
634
- private static function min_value( $value, $current_multiplier_value = null ) {
635
- return ( $current_multiplier_value === null || $value < $current_multiplier_value ) ? $value : $current_multiplier_value;
636
- }
637
-
638
- /**
639
- * The amount of times the minimum spend / quantity / subtotal values are reached
640
- * @return int 1 or more
641
- */
642
- public function get_coupon_multiplier_value( $coupon ) {
643
- $coupon = $this->get_coupon( $coupon );
644
-
645
- if ( ! isset( $this->coupon_multiplier_values[ $coupon->code ] ) || ! $this->coupon_multiplier_values[ $coupon->code ] ) {
646
- return 1;
647
- }
648
-
649
- return $this->coupon_multiplier_values[ $coupon->code ];
650
- }
651
-
652
- //Temporary storage
653
- private $coupon_multiplier_values = array();
654
-
655
-
656
- /**
657
- * PUBLIC USAGE OF THIS FUNCTION IS ALLOWED!
658
- * The total amount of the products in the cart that match the coupon restrictions
659
- * since 2.2.2-b3
660
- */
661
- public function get_quantity_of_matching_products( $coupon ) {
662
- $coupon = $this->get_coupon( $coupon );
663
-
664
- $qty = 0;
665
- foreach( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
666
- $_product = $cart_item['data'];
667
- if ($this->coupon_is_valid_for_product( $coupon, $_product, $cart_item ) ) {
668
- $qty += $cart_item['quantity'];
669
- }
670
- }
671
- return $qty;
672
- }
673
-
674
- /**
675
- * PUBLIC USAGE OF THIS FUNCTION IS ALLOWED!
676
- * The total value of the products in the cart that match the coupon restrictions
677
- * since 2.2.2-b3
678
- */
679
- public function get_subtotal_of_matching_products( $coupon ) {
680
- $coupon = $this->get_coupon( $coupon );
681
-
682
- $subtotal = 0;
683
- foreach( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
684
- $_product = $cart_item['data'];
685
- if ($this->coupon_is_valid_for_product( $coupon, $_product, $cart_item ) ) {
686
- $subtotal += $_product->get_price() * $cart_item['quantity'];
687
- }
688
- }
689
- return $subtotal;
690
- }
691
-
692
- //Test if coupon is valid for the product
693
- // (this function is used to count the quantity of matching products)
694
- public function coupon_is_valid_for_product( $coupon, $product, $values = array() ) {
695
- //Ugly hack: $coupon->is_valid_for_product only works for these types
696
- $original_discount_type = $coupon->discount_type;
697
- if ( ! $this->coupon_is_type( $coupon, array( 'fixed_product', 'percent_product' ) ) ) {
698
- $coupon->discount_type = 'fixed_product';
699
- }
700
- $valid = $coupon->is_valid_for_product( $product, $values );
701
- $coupon->discount_type = $original_discount_type;
702
-
703
- return $valid;
704
- }
705
-
706
-
707
-
708
- // =====================
709
-
710
- /**
711
- * Retrieve the id of the product or the variation id if it's a variant.
712
- * @Returns int|bool The variation or product id. False if not a valid product
713
- */
714
- public function get_product_or_variation_id( $product ) {
715
- if ( is_a( $product, 'WC_Product_Variation' ) ) {
716
- return $product->variation_id;
717
- } elseif ( is_a( $product, 'WC_Product' ) ) {
718
- return $product->id;
719
- } else {
720
- return false;
721
- }
722
- }
723
-
724
- /**
725
- * Get a WC_Coupon object
726
- * @param WC_Coupon|string $coupon The coupon code or a WC_Coupon object
727
- * @return WC_Coupon The coupon object
728
- */
729
- public function get_coupon( $coupon ) {
730
- if ( ! ( $coupon instanceof WC_Coupon ) ) {
731
- $coupon = new WC_Coupon( $coupon );
732
- }
733
- return $coupon;
734
- }
735
-
736
- /**
737
- * Check the type of the coupon
738
- * @param WC_Coupon $coupon The coupon to check
739
- * @param string|array $type The type(s) we want to check for
740
- * @return bool True if the coupon is of the type
741
- */
742
- private function coupon_is_type( $coupon, $type ) {
743
- //Backwards compatibility 2.2.11
744
- if ( method_exists( $coupon, 'is_type' ) ) {
745
- return $coupon->is_type( $type );
746
- } else {
747
- return ( $coupon->discount_type == $type || ( is_array( $type ) && in_array( $coupon->discount_type, $type ) ) ) ? true : false;
748
- }
749
- }
750
-
751
- /**
752
- * Get array of the selected shipping methods ids.
753
- * @param int $coupon_id The coupon id
754
- * @return array Id's of the shipping methods or an empty array.
755
- */
756
- private function get_coupon_shipping_method_ids($coupon_id) {
757
- $v = get_post_meta( $coupon_id, '_wjecf_shipping_methods', true );
758
- if ($v == '') {
759
- $v = array();
760
- }
761
-
762
- return $v;
763
- }
764
-
765
- /**
766
- * Get array of the selected payment method ids.
767
- * @param int $coupon_id The coupon id
768
- * @return array Id's of the payment methods or an empty array.
769
- */
770
- private function get_coupon_payment_method_ids($coupon_id) {
771
- $v = get_post_meta( $coupon_id, '_wjecf_payment_methods', true );
772
- if ($v == '') {
773
- $v = array();
774
- }
775
-
776
- return $v;
777
- }
778
-
779
- /**
780
- * Get array of the selected customer ids.
781
- * @param int $coupon_id The coupon id
782
- * @return array Id's of the customers (users) or an empty array.
783
- */
784
- private function get_coupon_customer_ids($coupon_id) {
785
- $v = get_post_meta( $coupon_id, '_wjecf_customer_ids', true );
786
- //$v = array_map( 'intval', explode(",", get_post_meta( $coupon_id, '_wjecf_customer_ids', true ) ) );
787
- if ($v == '') {
788
- $v = array();
789
- } else {
790
- $v = array_map( 'intval', explode(",", $v ) );
791
- }
792
-
793
- return $v;
794
- }
795
-
796
- /**
797
- * Get array of the selected customer role ids.
798
- * @param int $coupon_id The coupon id
799
- * @return array Id's (string) of the customer roles or an empty array.
800
- */
801
- private function get_coupon_customer_roles($coupon_id) {
802
- $v = get_post_meta( $coupon_id, '_wjecf_customer_roles', true );
803
- if ($v == '') {
804
- $v = array();
805
- }
806
-
807
- return $v;
808
- }
809
-
810
- /**
811
- * Get array of the excluded customer role ids.
812
- * @param int $coupon_id The coupon id
813
- * @return array Id's (string) of the excluded customer roles or an empty array.
814
- */
815
- private function get_coupon_excluded_customer_roles($coupon_id) {
816
- $v = get_post_meta( $coupon_id, '_wjecf_excluded_customer_roles', true );
817
- if ($v == '') {
818
- $v = array();
819
- }
820
-
821
- return $v;
822
- }
823
-
824
- public function is_pro() {
825
- return class_exists( 'WC_Jos_Pro_Controller' );
826
- }
827
-
828
-
829
  public static function get_donate_url() {
830
  return "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=5T9XQBCS2QHRY&lc=NL&item_name=Jos%20Koenis&item_number=wordpress%2dplugin&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted";
831
  }
2
 
3
  defined('ABSPATH') or die();
4
 
5
+ if ( class_exists('WJECF_Admin') ) {
6
  return;
7
  }
8
 
9
+ class WJECF_Admin extends Abstract_WJECF_Plugin {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  public function __construct() {
12
+ add_action( 'admin_init', array( $this, 'admin_init' ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  }
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  //Upgrade options on version change
16
  public function auto_upgrade_options() {
17
  $current_db_version = 1;
18
  //Check version
19
+ if ( WJECF()->options['db_version'] < $current_db_version ) {
20
  global $wpdb;
21
 
22
  //DB_VERSION 1: Since 2.1.0-b5
23
+ if (WJECF()->options['db_version'] < 1) {
24
  //RENAME meta_key _wjecf_matching_product_qty TO _wjecf_min_matching_product_qty
25
  $where = array("meta_key" => "_wjecf_matching_product_qty");
26
  $set = array('meta_key' => "_wjecf_min_matching_product_qty");
31
  $set = array('meta_key' => "_wjecf_is_auto_coupon");
32
  $wpdb->update( _get_meta_table('post'), $set, $where );
33
  //Now we're version 1
34
+ WJECF()->options['db_version'] = 1;
35
  }
36
 
37
  // Write options to database
38
+ update_option( 'wjecf_options' , WJECF()->options, false );
39
  }
40
  }
41
 
88
  }
89
  private function admin_coupon_data_footer() {
90
  $documentation_url = plugins_url( 'docs/index.html', dirname( __FILE__ ) );
91
+ if ( ! WJECF()->is_pro() ) {
92
  $documentation_url = 'http://www.soft79.nl/documentation/wjecf';
93
  ?>
94
  <h3><?php _e( 'Do you find WooCommerce Extended Coupon Features useful?', 'woocommerce-jos-autocoupon'); ?></h3>
235
  <p class="form-field"><label for="wjecf_shipping_methods"><?php _e( 'Shipping methods', 'woocommerce-jos-autocoupon' ); ?></label>
236
  <select id="wjecf_shipping_methods" name="wjecf_shipping_methods[]" style="width: 50%;" class="wc-enhanced-select" multiple="multiple" data-placeholder="<?php _e( 'Any shipping method', 'woocommerce-jos-autocoupon' ); ?>">
237
  <?php
238
+ $coupon_shipping_method_ids = WJECF()->get_coupon_shipping_method_ids( $thepostid );
239
  $shipping_methods = WC()->shipping->load_shipping_methods();
240
 
241
  if ( $shipping_methods ) foreach ( $shipping_methods as $shipping_method ) {
251
  <p class="form-field"><label for="wjecf_payment_methods"><?php _e( 'Payment methods', 'woocommerce-jos-autocoupon' ); ?></label>
252
  <select id="wjecf_payment_methods" name="wjecf_payment_methods[]" style="width: 50%;" class="wc-enhanced-select" multiple="multiple" data-placeholder="<?php _e( 'Any payment method', 'woocommerce-jos-autocoupon' ); ?>">
253
  <?php
254
+ $coupon_payment_method_ids = WJECF()->get_coupon_payment_method_ids( $thepostid );
255
  //DONT USE WC()->payment_gateways->available_payment_gateways() AS IT CAN CRASH IN UNKNOWN OCCASIONS
256
  $payment_methods = WC()->payment_gateways->payment_gateways();
257
  if ( $payment_methods ) foreach ( $payment_methods as $payment_method ) {
276
  ?>
277
  <p class="form-field"><label><?php _e( 'Allowed Customers', 'woocommerce-jos-autocoupon' ); ?></label>
278
  <input type="hidden" class="wc-customer-search" data-multiple="true" style="width: 50%;" name="wjecf_customer_ids" data-placeholder="<?php _e( 'Any customer', 'woocommerce-jos-autocoupon' ); ?>" data-action="woocommerce_json_search_customers" data-selected="<?php
279
+ $coupon_customer_ids = WJECF()->get_coupon_customer_ids( $thepostid );
280
  $json_ids = array();
281
 
282
  foreach ( $coupon_customer_ids as $customer_id ) {
298
  <p class="form-field"><label for="wjecf_customer_roles"><?php _e( 'Allowed User Roles', 'woocommerce-jos-autocoupon' ); ?></label>
299
  <select id="wjecf_customer_roles" name="wjecf_customer_roles[]" style="width: 50%;" class="wc-enhanced-select" multiple="multiple" data-placeholder="<?php _e( 'Any role', 'woocommerce-jos-autocoupon' ); ?>">
300
  <?php
301
+ $coupon_customer_roles = WJECF()->get_coupon_customer_roles( $thepostid );
302
 
303
  $available_customer_roles = array_reverse( get_editable_roles() );
304
  foreach ( $available_customer_roles as $role_id => $role ) {
320
  <p class="form-field"><label for="wjecf_excluded_customer_roles"><?php _e( 'Disallowed User Roles', 'woocommerce-jos-autocoupon' ); ?></label>
321
  <select id="wjecf_customer_roles" name="wjecf_excluded_customer_roles[]" style="width: 50%;" class="wc-enhanced-select" multiple="multiple" data-placeholder="<?php _e( 'Any role', 'woocommerce-jos-autocoupon' ); ?>">
322
  <?php
323
+ $coupon_excluded_customer_roles = WJECF()->get_coupon_excluded_customer_roles( $thepostid );
324
 
325
  foreach ( $available_customer_roles as $role_id => $role ) {
326
  $role_name = translate_user_role($role['name'] );
389
 
390
  }
391
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
392
  public static function get_donate_url() {
393
  return "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=5T9XQBCS2QHRY&lc=NL&item_name=Jos%20Koenis&item_number=wordpress%2dplugin&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted";
394
  }
includes/wjecf-autocoupon.php CHANGED
@@ -2,13 +2,9 @@
2
 
3
  defined('ABSPATH') or die();
4
 
5
- if ( class_exists('WC_Jos_AutoCoupon_Controller') ) {
6
- return;
7
- }
8
 
9
- class WC_Jos_AutoCoupon_Controller {
10
-
11
- private $_autocoupon_codes = null;
12
 
13
  private $_user_emails = null;
14
 
@@ -28,9 +24,9 @@ class WC_Jos_AutoCoupon_Controller {
28
 
29
  //Frontend hooks - logic
30
  add_action( 'woocommerce_after_calculate_totals', array( &$this, 'update_matched_autocoupons' ) );
31
- add_action( 'woocommerce_check_cart_items', array( &$this, 'update_matched_autocoupons' ) , 0 ); //Remove coupon before WC does it and shows a message
32
  //Last check for coupons with restricted_emails
33
- add_action( 'woocommerce_checkout_update_order_review', array( $this, 'fetch_billing_email' ), 10 ); // AJAX One page checkout
34
 
35
  //Frontend hooks - visualisation
36
  add_filter('woocommerce_cart_totals_coupon_label', array( &$this, 'coupon_label' ), 10, 2 );
@@ -49,8 +45,79 @@ class WC_Jos_AutoCoupon_Controller {
49
  public function admin_init() {
50
  add_action( 'wjecf_woocommerce_coupon_options_extended_features', array( $this, 'admin_coupon_options_extended_features' ), 20, 2 );
51
  add_action( 'woocommerce_process_shop_coupon_meta', array( $this, 'process_shop_coupon_meta' ), 10, 2 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  }
53
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  public function admin_coupon_options_extended_features( $thepostid, $post ) {
55
 
56
  //=============================
@@ -66,6 +133,19 @@ class WC_Jos_AutoCoupon_Controller {
66
  'description' => __( "Automatically add the coupon to the cart if the restrictions are met. Please enter a description when you check this box, the description will be shown in the customer's cart if the coupon is applied.", 'woocommerce-jos-autocoupon' )
67
  ) );
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  //=============================
70
  // Apply without notice
71
  woocommerce_wp_checkbox( array(
@@ -73,6 +153,7 @@ class WC_Jos_AutoCoupon_Controller {
73
  'label' => __( 'Apply silently', 'woocommerce-jos-autocoupon' ),
74
  'description' => __( "Don't display a message when this coupon is automatically applied.", 'woocommerce-jos-autocoupon' ),
75
  ) );
 
76
 
77
  ?>
78
  <script type="text/javascript">
@@ -81,9 +162,9 @@ class WC_Jos_AutoCoupon_Controller {
81
  if ( animation === undefined ) animation = 'slow';
82
 
83
  if (jQuery("#_wjecf_is_auto_coupon").prop('checked')) {
84
- jQuery("._wjecf_apply_silently_field").show( animation );
85
  } else {
86
- jQuery("._wjecf_apply_silently_field").hide( animation );
87
  }
88
  }
89
  update_wjecf_apply_silently_field( 0 );
@@ -95,8 +176,13 @@ class WC_Jos_AutoCoupon_Controller {
95
  }
96
 
97
  public function process_shop_coupon_meta( $post_id, $post ) {
98
- update_post_meta( $post_id, '_wjecf_is_auto_coupon', isset( $_POST['_wjecf_is_auto_coupon'] ) ? 'yes' : 'no' );
 
 
99
  update_post_meta( $post_id, '_wjecf_apply_silently', isset( $_POST['_wjecf_apply_silently'] ) ? 'yes' : 'no' );
 
 
 
100
  }
101
 
102
  /* FRONTEND HOOKS */
@@ -175,42 +261,84 @@ class WC_Jos_AutoCoupon_Controller {
175
  }
176
  }
177
 
178
- //Infinite loop protection
179
- private $_update_matched_autocoupons_count = 0;
180
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  /**
182
  * Apply matched autocoupons and remove unmatched autocoupons.
183
  * @return void
184
  */
185
  function update_matched_autocoupons() {
186
- if ( $this->_update_matched_autocoupons_count++ > 20 ) {
187
- $this->log ( 'update_matched_autocoupons counter reached limit 20' );
188
  return;
189
  }
190
-
191
- //$this->log ( 'update_matched_autocoupons' );
192
- $calc_needed = $this->remove_unmatched_autocoupons();
193
- foreach ( $this->get_all_auto_coupons() as $coupon_code ) {
194
- if ( ! WC()->cart->has_discount( $coupon_code ) ) {
195
- $coupon = new WC_Coupon( $coupon_code );
196
- if ( $this->coupon_can_be_applied( $coupon ) && $this->coupon_has_a_value( $coupon ) ) {
197
- //$this->log( sprintf( "Applying %s", $coupon_code ) );
198
- WC()->cart->add_discount( $coupon_code );
199
- $calc_needed = false; //Already done by adding the discount
200
- $apply_silently = get_post_meta( $coupon->id, '_wjecf_apply_silently', true ) == 'yes';
201
- $this->overwrite_success_message( $coupon, $apply_silently );
202
- } else {
203
- //$this->log( sprintf( "Not applicable: %s", $coupon_code ) );
204
- }
 
 
 
 
 
 
 
 
205
  }
206
  }
207
-
 
 
208
  if ( $calc_needed ) {
209
  WC()->cart->calculate_totals();
210
  }
211
 
212
  }
213
-
 
 
 
 
 
 
 
 
 
 
214
  /**
215
  * Test whether the coupon is valid and has a discount > 0
216
  * @return bool
@@ -221,23 +349,11 @@ class WC_Jos_AutoCoupon_Controller {
221
  //Test validity
222
  if ( ! $coupon->is_valid() ) {
223
  $can_be_applied = false;
224
- //$this->log( "Not valid. ");
225
- //echo "<pre>";
226
- //print_r (WC()->cart->get_cart());
227
- //echo "</pre>";
228
- }
229
- //Test individual use
230
- elseif ( $coupon->individual_use == 'yes' ) {
231
- if ( count( WC()->cart->applied_coupons ) > 1 ) {
232
- $can_be_applied = false;
233
- } elseif ( count( WC()->cart->applied_coupons ) == 1 && ! in_array( $coupon->code, WC()->cart->applied_coupons ) ) {
234
- $can_be_applied = false;
235
- }
236
  }
237
 
238
  //Test restricted emails
239
  //See WooCommerce: class-wc-cart.php function check_customer_coupons
240
- if ( $can_be_applied && is_array( $coupon->customer_email ) && sizeof( $coupon->customer_email ) > 0 ) {
241
  $user_emails = array_map( 'sanitize_email', array_map( 'strtolower', $this->get_user_emails() ) );
242
  $coupon_emails = array_map( 'sanitize_email', array_map( 'strtolower', $coupon->customer_email ) );
243
 
@@ -279,28 +395,6 @@ class WC_Jos_AutoCoupon_Controller {
279
  }
280
 
281
 
282
- /**
283
- * Remove unmatched autocoupons. No message will be shown.
284
- * NOTE: This function must be called before WooCommerce removes the coupon, to inhibit WooCommerces "coupon not valid"-message!
285
- * @return bool True if coupons were removed, otherwise False;
286
- */
287
- function remove_unmatched_autocoupons() {
288
- global $woocommerce;
289
-
290
- $calc_needed = false;
291
- foreach ( $this->get_all_auto_coupons() as $coupon_code ) {
292
- if ( $woocommerce->cart->has_discount( $coupon_code ) ) {
293
- $coupon = new WC_Coupon($coupon_code);
294
- if ( ! $this->coupon_can_be_applied($coupon) ) {
295
- //$this->log( sprintf( "Removing %s", $coupon_code ) );
296
- WC()->cart->remove_coupon( $coupon_code );
297
- $calc_needed = true;
298
- }
299
- }
300
- }
301
- return $calc_needed;
302
- }
303
-
304
  /**
305
  * Overwrite the default "Coupon added" notice with a more descriptive message.
306
  * @param WC_Coupon $coupon The coupon data
@@ -361,6 +455,16 @@ class WC_Jos_AutoCoupon_Controller {
361
  private function is_auto_coupon($coupon) {
362
  return get_post_meta( $coupon->id, '_wjecf_is_auto_coupon', true ) == 'yes';
363
  }
 
 
 
 
 
 
 
 
 
 
364
 
365
 
366
  /**
@@ -419,44 +523,84 @@ class WC_Jos_AutoCoupon_Controller {
419
  $this->append_user_emails( $posted['billing_email'] );
420
  }
421
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
  }
423
 
424
  /**
425
  * Get a list of all auto coupon codes
426
  * @return array All auto coupon codes
427
  */
428
- private function get_all_auto_coupons() {
429
-
430
- if ( ! is_array( $this->_autocoupon_codes ) ) {
431
- $this->_autocoupon_codes = array();
432
 
433
  $query_args = array(
434
  'posts_per_page' => -1,
435
  'post_type' => 'shop_coupon',
436
  'post_status' => 'publish',
437
- 'orderby' => 'title',
438
- 'meta_query' => array(
439
- array(
440
- 'key' => '_wjecf_is_auto_coupon',
441
- 'value' => 'yes',
442
- 'compare' => '=',
443
- ),
444
- )
445
  );
446
 
447
  $query = new WP_Query($query_args);
448
  foreach ($query->posts as $post) {
449
  $coupon = new WC_Coupon($post->post_title);
450
  if ( $this->is_auto_coupon($coupon) ) {
451
- $this->_autocoupon_codes[] = $post->post_title;
452
  }
453
- }
 
 
 
 
 
 
 
 
 
 
454
  }
455
- return $this->_autocoupon_codes;
456
- }
457
 
458
- //FOR DEBUGGING ONLY
459
- private function log ( $string ) {
460
- error_log( date("Y-m-d | h:i:sa") . ": " . get_class( $this ) . ": " . $string );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
461
  }
462
  }
2
 
3
  defined('ABSPATH') or die();
4
 
5
+ class WJECF_AutoCoupon extends Abstract_WJECF_Plugin {
 
 
6
 
7
+ private $_autocoupons = null;
 
 
8
 
9
  private $_user_emails = null;
10
 
24
 
25
  //Frontend hooks - logic
26
  add_action( 'woocommerce_after_calculate_totals', array( &$this, 'update_matched_autocoupons' ) );
27
+ add_action( 'woocommerce_check_cart_items', array( &$this, 'remove_unmatched_autocoupons' ) , 0, 0 ); //Remove coupon before WC does it and shows a message
28
  //Last check for coupons with restricted_emails
29
+ add_action( 'woocommerce_checkout_update_order_review', array( &$this, 'fetch_billing_email' ), 10 ); // AJAX One page checkout
30
 
31
  //Frontend hooks - visualisation
32
  add_filter('woocommerce_cart_totals_coupon_label', array( &$this, 'coupon_label' ), 10, 2 );
45
  public function admin_init() {
46
  add_action( 'wjecf_woocommerce_coupon_options_extended_features', array( $this, 'admin_coupon_options_extended_features' ), 20, 2 );
47
  add_action( 'woocommerce_process_shop_coupon_meta', array( $this, 'process_shop_coupon_meta' ), 10, 2 );
48
+
49
+ //Inject columns
50
+ if ( WJECF()->is_pro() ) {
51
+ WJECF()->inject_coupon_column(
52
+ '_wjecf_auto_coupon',
53
+ __( 'Auto coupon', 'woocommerce-jos-autocoupon' ),
54
+ array( $this, 'admin_render_shop_coupon_columns' ), 'coupon_code'
55
+ );
56
+ WJECF()->inject_coupon_column(
57
+ '_wjecf_individual_use',
58
+ __( 'Individual use', 'woocommerce-jos-autocoupon' ),
59
+ array( $this, 'admin_render_shop_coupon_columns' ), 'coupon_code'
60
+ );
61
+ }
62
+
63
+ add_filter( 'views_edit-shop_coupon', array( $this, 'admin_views_edit_coupon' ) );
64
+ add_filter( 'request', array( $this, 'admin_request_query' ) );
65
  }
66
+
67
+ /**
68
+ * Output a coupon custom column value
69
+ *
70
+ * @param string $column
71
+ * @param WP_Post The coupon post object
72
+ */
73
+ public function admin_render_shop_coupon_columns( $column, $post ) {
74
+
75
+ switch ( $column ) {
76
+ case '_wjecf_auto_coupon' :
77
+ $is_auto_coupon = get_post_meta( $post->ID, '_wjecf_is_auto_coupon', true ) == 'yes';
78
+ echo $is_auto_coupon ? __( 'Yes', 'woocommerce' ) : __( 'No', 'woocommerce' );
79
+ if ( $is_auto_coupon ) {
80
+ $prio = get_post_meta( $post->ID, '_wjecf_coupon_priority', true );
81
+ if ( $prio ) echo " (" . intval( $prio ) . ")";
82
+ }
83
+ break;
84
+ case '_wjecf_individual_use' :
85
+ $individual = get_post_meta( $post->ID, 'individual_use', true ) == 'yes';
86
+ echo $individual ? __( 'Yes', 'woocommerce' ) : __( 'No', 'woocommerce' );
87
+ break;
88
+ }
89
+ }
90
+
91
+ public function admin_views_edit_coupon( $views ) {
92
+ global $post_type, $wp_query;
93
+
94
+ $class = ( isset( $wp_query->query['meta_key'] ) && $wp_query->query['meta_key'] == '_wjecf_is_auto_coupon' ) ? 'current' : '';
95
+ $query_string = remove_query_arg(array( 'wjecf_is_auto_coupon' ));
96
+ $query_string = add_query_arg( 'wjecf_is_auto_coupon', '1', $query_string );
97
+ $views['wjecf_is_auto_coupon'] = '<a href="' . esc_url( $query_string ) . '" class="' . esc_attr( $class ) . '">' . __( 'Auto coupons', 'woocommerce' ) . '</a>';
98
+
99
+ return $views;
100
+ }
101
+
102
+ /**
103
+ * Filters and sorting handler
104
+ *
105
+ * @param array $vars
106
+ * @return array
107
+ */
108
+ public function admin_request_query( $vars ) {
109
+ global $typenow, $wp_query, $wp_post_statuses;
110
+
111
+ if ( 'shop_coupon' === $typenow ) {
112
+ if ( isset( $_GET['wjecf_is_auto_coupon'] ) ) {
113
+ $vars['meta_key'] = '_wjecf_is_auto_coupon';
114
+ $vars['meta_value'] = $_GET['wjecf_is_auto_coupon'] == '1' ? 'yes' : 'no';
115
+ }
116
+ }
117
+
118
+ return $vars;
119
+ }
120
+
121
  public function admin_coupon_options_extended_features( $thepostid, $post ) {
122
 
123
  //=============================
133
  'description' => __( "Automatically add the coupon to the cart if the restrictions are met. Please enter a description when you check this box, the description will be shown in the customer's cart if the coupon is applied.", 'woocommerce-jos-autocoupon' )
134
  ) );
135
 
136
+ echo '<div class="_wjecf_show_if_autocoupon">';
137
+ if ( WJECF()->is_pro() ) {
138
+ // Maximum quantity of matching products (product/category)
139
+ woocommerce_wp_text_input( array(
140
+ 'id' => '_wjecf_coupon_priority',
141
+ 'label' => __( 'Priority', 'woocommerce-jos-autocoupon' ),
142
+ 'placeholder' => __( 'No priority', 'woocommerce' ),
143
+ 'description' => __( 'When \'individual use\' is checked, auto coupons with a higher value will have priority over other auto coupons.', 'woocommerce-jos-autocoupon' ),
144
+ 'data_type' => 'decimal',
145
+ 'desc_tip' => true
146
+ ) );
147
+ }
148
+
149
  //=============================
150
  // Apply without notice
151
  woocommerce_wp_checkbox( array(
153
  'label' => __( 'Apply silently', 'woocommerce-jos-autocoupon' ),
154
  'description' => __( "Don't display a message when this coupon is automatically applied.", 'woocommerce-jos-autocoupon' ),
155
  ) );
156
+ echo '</div>';
157
 
158
  ?>
159
  <script type="text/javascript">
162
  if ( animation === undefined ) animation = 'slow';
163
 
164
  if (jQuery("#_wjecf_is_auto_coupon").prop('checked')) {
165
+ jQuery("._wjecf_show_if_autocoupon").show( animation );
166
  } else {
167
+ jQuery("._wjecf_show_if_autocoupon").hide( animation );
168
  }
169
  }
170
  update_wjecf_apply_silently_field( 0 );
176
  }
177
 
178
  public function process_shop_coupon_meta( $post_id, $post ) {
179
+ $autocoupon = isset( $_POST['_wjecf_is_auto_coupon'] );
180
+
181
+ update_post_meta( $post_id, '_wjecf_is_auto_coupon', $autocoupon ? 'yes' : 'no' );
182
  update_post_meta( $post_id, '_wjecf_apply_silently', isset( $_POST['_wjecf_apply_silently'] ) ? 'yes' : 'no' );
183
+ if ( WJECF()->is_pro() ) {
184
+ update_post_meta( $post_id, '_wjecf_coupon_priority', intval( $_POST['_wjecf_coupon_priority'] ) );
185
+ }
186
  }
187
 
188
  /* FRONTEND HOOKS */
261
  }
262
  }
263
 
264
+
265
+ function remove_unmatched_autocoupons( $valid_coupon_codes = null ) {
266
+ if ( $valid_coupon_codes === null ) {
267
+ //Get the coupons that should be in the cart
268
+ $valid_coupons = $this->valid_auto_coupons();
269
+ $valid_coupons = $this->individual_use_filter( $valid_coupons );
270
+ $valid_coupon_codes = array();
271
+ foreach ( $valid_coupons as $coupon ) {
272
+ $valid_coupon_codes[] = $coupon->code;
273
+ }
274
+ }
275
+
276
+ //Remove invalids
277
+ $calc_needed = false;
278
+ foreach ( $this->get_all_auto_coupons() as $coupon ) {
279
+ if ( WC()->cart->has_discount( $coupon->code ) && ! in_array( $coupon->code, $valid_coupon_codes ) ) {
280
+ $this->log( sprintf( "Removing %s", $coupon->code ) );
281
+ WC()->cart->remove_coupon( $coupon->code );
282
+ $calc_needed = true;
283
+ }
284
+ }
285
+ return $calc_needed;
286
+ }
287
+
288
+ private $update_matched_autocoupons_executed = false;
289
+
290
  /**
291
  * Apply matched autocoupons and remove unmatched autocoupons.
292
  * @return void
293
  */
294
  function update_matched_autocoupons() {
295
+ if ( $this->update_matched_autocoupons_executed ) {
 
296
  return;
297
  }
298
+ $this->update_matched_autocoupons_executed = true;
299
+ $this->log( "()" );
300
+
301
+ //Get the coupons that should be in the cart
302
+ $valid_coupons = $this->valid_auto_coupons();
303
+ $valid_coupons = $this->individual_use_filter( $valid_coupons );
304
+ $valid_coupon_codes = array();
305
+ foreach ( $valid_coupons as $coupon ) {
306
+ $valid_coupon_codes[] = $coupon->code;
307
+ }
308
+
309
+ //$this->log( sprintf( "Coupons that should be in cart: %s", implode( ', ', $valid_coupon_codes ) ) );
310
+
311
+ $calc_needed = $this->remove_unmatched_autocoupons( $valid_coupon_codes );
312
+
313
+ //Add valids
314
+ foreach( $valid_coupons as $coupon ) {
315
+ if ( ! WC()->cart->has_discount( $coupon->code ) ) {
316
+ $this->log( sprintf( "Applying %s", $coupon->code ) );
317
+ WC()->cart->add_discount( $coupon->code ); //Causes calculation and will remove other coupons if it's a individual coupon
318
+ $calc_needed = false; //Already done by adding the discount
319
+ $apply_silently = get_post_meta( $coupon->id, '_wjecf_apply_silently', true ) == 'yes';
320
+ $this->overwrite_success_message( $coupon, $apply_silently );
321
  }
322
  }
323
+
324
+ $this->log( 'Coupons in cart: ' . implode( ', ', WC()->cart->applied_coupons ) . ($calc_needed ? ". RECALC" : "") );
325
+
326
  if ( $calc_needed ) {
327
  WC()->cart->calculate_totals();
328
  }
329
 
330
  }
331
+
332
+ private function valid_auto_coupons( ) {
333
+ $valid_coupons = array();
334
+ foreach ( $this->get_all_auto_coupons() as $coupon ) {
335
+ if ( $this->coupon_can_be_applied( $coupon ) && $this->coupon_has_a_value( $coupon ) ) {
336
+ $valid_coupons[] = $coupon;
337
+ }
338
+ }
339
+ return $valid_coupons;
340
+ }
341
+
342
  /**
343
  * Test whether the coupon is valid and has a discount > 0
344
  * @return bool
349
  //Test validity
350
  if ( ! $coupon->is_valid() ) {
351
  $can_be_applied = false;
 
 
 
 
 
 
 
 
 
 
 
 
352
  }
353
 
354
  //Test restricted emails
355
  //See WooCommerce: class-wc-cart.php function check_customer_coupons
356
+ else if ( $can_be_applied && is_array( $coupon->customer_email ) && sizeof( $coupon->customer_email ) > 0 ) {
357
  $user_emails = array_map( 'sanitize_email', array_map( 'strtolower', $this->get_user_emails() ) );
358
  $coupon_emails = array_map( 'sanitize_email', array_map( 'strtolower', $coupon->customer_email ) );
359
 
395
  }
396
 
397
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
398
  /**
399
  * Overwrite the default "Coupon added" notice with a more descriptive message.
400
  * @param WC_Coupon $coupon The coupon data
455
  private function is_auto_coupon($coupon) {
456
  return get_post_meta( $coupon->id, '_wjecf_is_auto_coupon', true ) == 'yes';
457
  }
458
+
459
+ private function get_coupon_priority($coupon) {
460
+ if ( WJECF()->is_pro() ) {
461
+ $prio = get_post_meta( $coupon->id, '_wjecf_coupon_priority', true );
462
+ if ( ! empty( $prio ) ) {
463
+ return intval( $prio );
464
+ }
465
+ }
466
+ return 0;
467
+ }
468
 
469
 
470
  /**
523
  $this->append_user_emails( $posted['billing_email'] );
524
  }
525
 
526
+ }
527
+
528
+ /**
529
+ * Return an array of WC_Coupons with coupons that shouldn't cause individual use conflicts
530
+ */
531
+ private function individual_use_filter( $valid_auto_coupons ) {
532
+ $filtered = array();
533
+
534
+ //Any individual use non-autocoupons in the cart?
535
+ foreach ( WC()->cart->get_applied_coupons() as $coupon_code ) {
536
+ $coupon = new WC_Coupon( $coupon_code );
537
+ if ( $coupon->individual_use == 'yes' && ! $this->is_auto_coupon( $coupon ) ) {
538
+ return $filtered; //Don't allow any auto coupon
539
+ }
540
+ }
541
+ foreach ( $valid_auto_coupons as $coupon ) {
542
+ if ( $coupon->individual_use != 'yes' || empty( $filtered ) ) {
543
+ $filtered[] = $coupon;
544
+ if ( $coupon->individual_use == 'yes' ) {
545
+ break;
546
+ }
547
+ }
548
+ }
549
+ return $filtered;
550
  }
551
 
552
  /**
553
  * Get a list of all auto coupon codes
554
  * @return array All auto coupon codes
555
  */
556
+ public function get_all_auto_coupons() {
557
+ if ( ! is_array( $this->_autocoupons ) ) {
558
+ $this->_autocoupons = array();
 
559
 
560
  $query_args = array(
561
  'posts_per_page' => -1,
562
  'post_type' => 'shop_coupon',
563
  'post_status' => 'publish',
564
+ 'orderby' => array( 'title' => 'ASC' ),
565
+ 'meta_query' => array( 'key' => '_wjecf_is_auto_coupon', 'compare' => '=', 'value' => 'yes')
 
 
 
 
 
 
566
  );
567
 
568
  $query = new WP_Query($query_args);
569
  foreach ($query->posts as $post) {
570
  $coupon = new WC_Coupon($post->post_title);
571
  if ( $this->is_auto_coupon($coupon) ) {
572
+ $this->_autocoupons[$coupon->code] = $coupon;
573
  }
574
+ }
575
+
576
+ //Sort by prio
577
+ @uasort( $this->_autocoupons , array( $this, 'sort_auto_coupons' ) ); //Ignore error PHP Bug #50688
578
+
579
+ $coupon_codes = array();
580
+ foreach( $this->_autocoupons as $coupon ) {
581
+ $coupon_codes[] = $coupon->code;
582
+ }
583
+
584
+ $this->log( "Autocoupons: " . implode(", ", $coupon_codes ) );
585
  }
 
 
586
 
587
+ return $this->_autocoupons;
588
+ }
589
+
590
+ /**
591
+ * Compare function to sort coupons by priority
592
+ * @param type $a
593
+ * @param type $b
594
+ * @return type
595
+ */
596
+ private function sort_auto_coupons( $coupon_a, $coupon_b ) {
597
+ $prio_a = $this->get_coupon_priority( $coupon_a );
598
+ $prio_b = $this->get_coupon_priority( $coupon_b );
599
+ $this->log("A: $prio_a B: $prio_b ");
600
+ if ( $prio_a == $prio_b ) {
601
+ return $a->code < $b->code ? -1 : 1; //By title ASC
602
+ } else {
603
+ return $prio_a > $prio_b ? -1 : 1; //By prio DESC
604
+ }
605
  }
606
  }
includes/wjecf-controller.php ADDED
@@ -0,0 +1,609 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ defined('ABSPATH') or die();
4
+
5
+ /**
6
+ * The main controller for WooCommerce Extended Coupon Features
7
+ */
8
+ class WJECF_Controller {
9
+
10
+ protected $debug_mode = false;
11
+ protected $log = array();
12
+ public $options = false;
13
+
14
+ /**
15
+ * Singleton Instance
16
+ *
17
+ * @static
18
+ * @return Singleton Instance
19
+ */
20
+ public static function instance() {
21
+ if ( is_null( self::$_instance ) ) {
22
+ self::$_instance = new self();
23
+ }
24
+ return self::$_instance;
25
+ }
26
+ protected static $_instance = null;
27
+
28
+
29
+ public function __construct() {
30
+ add_action('init', array( &$this, 'controller_init' ));
31
+ }
32
+
33
+ public function controller_init() {
34
+ if ( ! class_exists('WC_Coupon') ) {
35
+ return;
36
+ }
37
+ $this->debug_mode = false; //defined( 'WP_DEBUG' ) && WP_DEBUG;
38
+ $this->log( "INIT " . ( is_ajax() ? "AJAX" : is_admin() ? "ADMIN" : "FRONTEND" ) . " " . $_SERVER['REQUEST_URI'] );
39
+
40
+ $this->init_options();
41
+
42
+ //Frontend hooks
43
+ add_filter('woocommerce_coupon_is_valid', array( &$this, 'coupon_is_valid' ), 10, 2 );
44
+ add_action('woocommerce_coupon_loaded', array( $this, 'woocommerce_coupon_loaded' ), 10, 1);
45
+ add_filter('woocommerce_coupon_get_discount_amount', array( $this, 'woocommerce_coupon_get_discount_amount' ), 10, 5);
46
+ add_action( 'wp_footer', array( &$this, 'render_log' ) ); //Log
47
+ }
48
+
49
+ protected $plugins = array();
50
+
51
+ /**
52
+ * Load a WJECF Plugin (class name)
53
+ * @param string $class_name The class name of the plugin
54
+ * @return bool True if succeeded, otherwise false
55
+ */
56
+ public function add_plugin( $class_name ) {
57
+ if ( class_exists( $class_name ) ) {
58
+ $this->plugins[ $class_name ] = new $class_name();
59
+ return true;
60
+ }
61
+ return false;
62
+ }
63
+
64
+ public function get_plugins() {
65
+ return $this->plugins;
66
+ }
67
+
68
+ public function get_plugin( $class_name ) {
69
+ return $this->plugins[ $class_name ];
70
+ }
71
+
72
+ public function init_options() {
73
+ $this->options = get_option( 'wjecf_options' );
74
+ if (false === $this->options) {
75
+ $this->options = array( 'db_version' => 0 );
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Check whether WooCommerce version is greater or equal than $req_version
81
+ * @param string @req_version The version to compare to
82
+ * @return bool true if WooCommerce is at least the given version
83
+ */
84
+ public static function check_woocommerce_version( $req_version ) {
85
+ return version_compare( self::get_woocommerce_version(), $req_version, '>=' );
86
+ }
87
+
88
+ private static $wc_version = null;
89
+ /**
90
+ * Get the WooCommerce version number
91
+ * @return string|bool WC Version number or false if WC not detected
92
+ */
93
+ public static function get_woocommerce_version() {
94
+ if (self::$wc_version === null) {
95
+ // If get_plugins() isn't available, require it
96
+ if ( ! function_exists( 'get_plugins' ) ) {
97
+ require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
98
+ }
99
+
100
+ // Create the plugins folder and file variables
101
+ $plugin_folder = get_plugins( '/woocommerce' );
102
+ $plugin_file = 'woocommerce.php';
103
+
104
+ // If the plugin version number is set, return it
105
+ if ( isset( $plugin_folder[$plugin_file]['Version'] ) ) {
106
+ self::$wc_version = $plugin_folder[$plugin_file]['Version'];
107
+ } else {
108
+ self::$wc_version = false; // Not found
109
+ }
110
+
111
+ }
112
+ return self::$wc_version;
113
+ }
114
+
115
+ /* FRONTEND HOOKS */
116
+
117
+ //2.2.2
118
+ public function woocommerce_coupon_loaded ( $coupon ) {
119
+ if ( ! is_admin() ) {
120
+ //2.2.2 Allow coupon even if minimum spend not reached
121
+ if ( get_post_meta( $coupon->id, '_wjecf_allow_below_minimum_spend', true ) == 'yes' ) {
122
+ //HACK: Overwrite the minimum amount with 0 so WooCommerce will allow the coupon
123
+ $coupon->wjecf_minimum_amount_for_discount = $coupon->minimum_amount;
124
+ $coupon->minimum_amount = 0;
125
+ }
126
+ }
127
+ }
128
+
129
+ //2.2.2
130
+ public function woocommerce_coupon_get_discount_amount ( $discount, $discounting_amount, $cart_item, $single, $coupon ) {
131
+ //2.2.2 No value if minimum spend not reached
132
+ if (isset( $coupon->wjecf_minimum_amount_for_discount ) ) {
133
+ if ( wc_format_decimal( $coupon->wjecf_minimum_amount_for_discount ) > wc_format_decimal( WC()->cart->subtotal ) ) {
134
+ return 0;
135
+ };
136
+ }
137
+ return $discount;
138
+ }
139
+
140
+
141
+ /* Extra validation rules for coupons */
142
+ public function coupon_is_valid ( $valid, $coupon ) {
143
+ //error_log("is valid? " . $coupon->code );
144
+ //Not valid? Then it will never validate, so get out of here
145
+ if ( ! $valid ) {
146
+ return false;
147
+ }
148
+
149
+ //============================
150
+ //Test if ALL products are in the cart (if AND-operator selected in stead of the default OR)
151
+ $products_and = get_post_meta( $coupon->id, '_wjecf_products_and', true ) == 'yes';
152
+ if ( $products_and && sizeof( $coupon->product_ids ) > 1 ) { // We use > 1, because if size == 1, 'AND' makes no difference
153
+ //Get array of all cart product and variation ids
154
+ $cart_item_ids = array();
155
+ foreach( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
156
+ $cart_item_ids[] = $cart_item['product_id'];
157
+ $cart_item_ids[] = $cart_item['variation_id'];
158
+ }
159
+ //check if every single product is in the cart
160
+ foreach( $coupon->product_ids as $product_id ) {
161
+ if ( ! in_array( $product_id, $cart_item_ids ) ) {
162
+ return false;
163
+ }
164
+ }
165
+ }
166
+
167
+ //============================
168
+ //Test if products form ALL categories are in the cart (if AND-operator selected in stead of the default OR)
169
+ $categories_and = get_post_meta( $coupon->id, '_wjecf_categories_and', true ) == 'yes';
170
+ if ( $categories_and && sizeof( $coupon->product_categories ) > 1 ) { // We use > 1, because if size == 1, 'AND' makes no difference
171
+ //Get array of all cart product and variation ids
172
+ $cart_product_cats = array();
173
+ foreach( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
174
+ $cart_product_cats = array_merge ( $cart_product_cats, wp_get_post_terms( $cart_item['product_id'], 'product_cat', array( "fields" => "ids" ) ) );
175
+ }
176
+ //check if every single category is in the cart
177
+ foreach( $coupon->product_categories as $cat_id ) {
178
+ if ( ! in_array( $cat_id, $cart_product_cats ) ) {
179
+ return false;
180
+ }
181
+ }
182
+
183
+ }
184
+
185
+ //============================
186
+ //Test min/max quantity of matching products
187
+ //
188
+ //For all items in the cart:
189
+ // If coupon contains both a product AND category inclusion filter: the item is counted if it matches either one of them
190
+ // If coupon contains either a product OR category exclusion filter: the item will NOT be counted if it matches either one of them
191
+ // If sale items are excluded by the coupon: the item will NOT be counted if it is a sale item
192
+ // If no filter exist, all items will be counted
193
+
194
+ $multiplier = null; //null = not initialized
195
+
196
+ //Validate quantity
197
+ $min_matching_product_qty = intval(get_post_meta( $coupon->id, '_wjecf_min_matching_product_qty', true ));
198