WooCommerce Extended Coupon Features - Version 2.6.3

Version Description

Release Date - 2018-06-04 * FIX: WJECF_Controller: Don't use wp_get_current_user() for admin-orders * FIX: WJECF_Controller: Don't use WC()->cart->subtotal for admin-orders * FIX: Possible division by zero when calculating multiplier value * FEATURE: Filter 'wjecf_coupon_multiplier_value' to allow overriding the coupon's multiplier value

Download this release

Release Info

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

Code changes from version 2.6.2 to 2.6.3

includes/Abstract_WJECF_Plugin.php CHANGED
@@ -1,126 +1,126 @@
1
- <?php
2
-
3
- abstract class Abstract_WJECF_Plugin {
4
-
5
- //Override these functions in the WJECF plugin
6
-
7
- public function init_hook() {}
8
-
9
- public function init_admin_hook() {}
10
-
11
- /**
12
- * Returns an array with the meta_keys for this plugin and the sanitation to apply.
13
- * Instead of a sanitation a callback can be supplied; which must return the meta value to save to the database
14
- *
15
- * e.g. [
16
- * '_wjecf_some_comma_separated_ints' => 'int,',
17
- * '_wjecf_some_callback' => [ 'callback' => [ callback ] ],
18
- * ]
19
- *
20
- * @return array The fields for this plugin
21
- */
22
- public function admin_coupon_meta_fields( $coupon ) {
23
- return array();
24
- }
25
-
26
- /**
27
- * Asserts that all dependencies are respected. If not an Exception is thrown. Override this function for extra assertions (e.g. minimum plugin versions)
28
- * @return void
29
- */
30
- public function assert_dependencies() {
31
- foreach( $this->get_plugin_dependencies() as $dependency ) {
32
- if ( ! isset( $this->plugins[ $dependency ] ) ) {
33
- throw new Exception( sprintf( 'Missing dependency %s', $dependency) );
34
- }
35
- }
36
-
37
- if ( ! empty ( $this->plugin_data['minimal_wjecf_version'] ) ) {
38
- $this->assert_wjecf_version( $this->plugin_data['minimal_wjecf_version'] );
39
- }
40
- }
41
-
42
- /**
43
- * Assert minimum WJECF version number
44
- * @param string $required_version
45
- * @return void
46
- */
47
- protected function assert_wjecf_version( $required_version ) {
48
- if ( version_compare( WJECF()->plugin_version(), $required_version, '<' ) ) {
49
- throw new Exception( sprintf( __( 'WooCommerce Extended Coupon Features version %s is required. You have version %s', 'woocommerce-jos-autocoupon' ), $required_version, WJECF()->plugin_version() ) );
50
- }
51
- }
52
-
53
- //
54
-
55
- /**
56
- * Log a message (for debugging)
57
- *
58
- * @param string $message The message to log
59
- *
60
- */
61
- protected function log( $level, $message = null ) {
62
- //Backwards compatibility; $level was introduced in 2.4.4
63
- if ( is_null( $message ) ) {
64
- $message = $level;
65
- $level = 'debug';
66
- }
67
- WJECF()->log( $level, $message, 1 );
68
- }
69
-
70
- private $plugin_data = array();
71
-
72
- /**
73
- * Information about the WJECF plugin
74
- * @param string|null $key The data to look up. Will return an array with all data when omitted
75
- * @return mixed
76
- */
77
- protected function get_plugin_data( $key = null ) {
78
- $default_data = array(
79
- 'description' => '',
80
- 'can_be_disabled' => false,
81
- 'hidden' => false, // Does not display on settings page
82
- 'dependencies' => array(),
83
- 'minimal_wjecf_version' => ''
84
- );
85
- $plugin_data = array_merge( $default_data, $this->plugin_data );
86
- if ( $key === null ) {
87
- return $plugin_data;
88
- }
89
- return $plugin_data[$key];
90
- }
91
-
92
- /**
93
- * Set information about the WJECF plugin
94
- * @param array $plugin_data The data for this plugin
95
- * @return void
96
- */
97
- protected function set_plugin_data( $plugin_data ) {
98
- $this->plugin_data = $plugin_data;
99
- }
100
-
101
- /**
102
- * Get the description if this WJECF plugin.
103
- * @return string
104
- */
105
- public function get_plugin_description() {
106
- return $this->get_plugin_data( 'description' );
107
- }
108
-
109
- /**
110
- * Get the class name of this WJECF plugin.
111
- * @return string
112
- */
113
- public function get_plugin_class_name() {
114
- return get_class( $this );
115
- }
116
-
117
- public function get_plugin_dependencies() {
118
- return $this->get_plugin_data( 'dependencies' );
119
- }
120
-
121
- public function plugin_is_enabled() {
122
- if ( ! $this->get_plugin_data( 'can_be_disabled' ) ) return true;
123
- return ! in_array( $this->get_plugin_class_name(), WJECF()->get_option('disabled_plugins') );
124
- }
125
-
126
- }
1
+ <?php
2
+
3
+ abstract class Abstract_WJECF_Plugin {
4
+
5
+ //Override these functions in the WJECF plugin
6
+
7
+ public function init_hook() {}
8
+
9
+ public function init_admin_hook() {}
10
+
11
+ /**
12
+ * Returns an array with the meta_keys for this plugin and the sanitation to apply.
13
+ * Instead of a sanitation a callback can be supplied; which must return the meta value to save to the database
14
+ *
15
+ * e.g. [
16
+ * '_wjecf_some_comma_separated_ints' => 'int,',
17
+ * '_wjecf_some_callback' => [ 'callback' => [ callback ] ],
18
+ * ]
19
+ *
20
+ * @return array The fields for this plugin
21
+ */
22
+ public function admin_coupon_meta_fields( $coupon ) {
23
+ return array();
24
+ }
25
+
26
+ /**
27
+ * Asserts that all dependencies are respected. If not an Exception is thrown. Override this function for extra assertions (e.g. minimum plugin versions)
28
+ * @return void
29
+ */
30
+ public function assert_dependencies() {
31
+ foreach( $this->get_plugin_dependencies() as $dependency ) {
32
+ if ( ! isset( $this->plugins[ $dependency ] ) ) {
33
+ throw new Exception( sprintf( 'Missing dependency %s', $dependency) );
34
+ }
35
+ }
36
+
37
+ if ( ! empty ( $this->plugin_data['minimal_wjecf_version'] ) ) {
38
+ $this->assert_wjecf_version( $this->plugin_data['minimal_wjecf_version'] );
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Assert minimum WJECF version number
44
+ * @param string $required_version
45
+ * @return void
46
+ */
47
+ protected function assert_wjecf_version( $required_version ) {
48
+ if ( version_compare( WJECF()->plugin_version(), $required_version, '<' ) ) {
49
+ throw new Exception( sprintf( __( 'WooCommerce Extended Coupon Features version %s is required. You have version %s', 'woocommerce-jos-autocoupon' ), $required_version, WJECF()->plugin_version() ) );
50
+ }
51
+ }
52
+
53
+ //
54
+
55
+ /**
56
+ * Log a message (for debugging)
57
+ *
58
+ * @param string $message The message to log
59
+ *
60
+ */
61
+ protected function log( $level, $message = null ) {
62
+ //Backwards compatibility; $level was introduced in 2.4.4
63
+ if ( is_null( $message ) ) {
64
+ $message = $level;
65
+ $level = 'debug';
66
+ }
67
+ WJECF()->log( $level, $message, 1 );
68
+ }
69
+
70
+ private $plugin_data = array();
71
+
72
+ /**
73
+ * Information about the WJECF plugin
74
+ * @param string|null $key The data to look up. Will return an array with all data when omitted
75
+ * @return mixed
76
+ */
77
+ protected function get_plugin_data( $key = null ) {
78
+ $default_data = array(
79
+ 'description' => '',
80
+ 'can_be_disabled' => false,
81
+ 'hidden' => false, // Does not display on settings page
82
+ 'dependencies' => array(),
83
+ 'minimal_wjecf_version' => ''
84
+ );
85
+ $plugin_data = array_merge( $default_data, $this->plugin_data );
86
+ if ( $key === null ) {
87
+ return $plugin_data;
88
+ }
89
+ return $plugin_data[$key];
90
+ }
91
+
92
+ /**
93
+ * Set information about the WJECF plugin
94
+ * @param array $plugin_data The data for this plugin
95
+ * @return void
96
+ */
97
+ protected function set_plugin_data( $plugin_data ) {
98
+ $this->plugin_data = $plugin_data;
99
+ }
100
+
101
+ /**
102
+ * Get the description if this WJECF plugin.
103
+ * @return string
104
+ */
105
+ public function get_plugin_description() {
106
+ return $this->get_plugin_data( 'description' );
107
+ }
108
+
109
+ /**
110
+ * Get the class name of this WJECF plugin.
111
+ * @return string
112
+ */
113
+ public function get_plugin_class_name() {
114
+ return get_class( $this );
115
+ }
116
+
117
+ public function get_plugin_dependencies() {
118
+ return $this->get_plugin_data( 'dependencies' );
119
+ }
120
+
121
+ public function plugin_is_enabled() {
122
+ if ( ! $this->get_plugin_data( 'can_be_disabled' ) ) return true;
123
+ return ! in_array( $this->get_plugin_class_name(), WJECF()->get_option('disabled_plugins') );
124
+ }
125
+
126
+ }
includes/WJECF_AutoCoupon.php CHANGED
@@ -1,728 +1,728 @@
1
- <?php
2
-
3
- defined('ABSPATH') or die();
4
-
5
- class WJECF_AutoCoupon extends Abstract_WJECF_Plugin {
6
-
7
- protected $options = null; // WJECF_Options object
8
- protected $_autocoupons = null;
9
- protected $_executed_coupon_by_url = false;
10
-
11
- public function __construct() {
12
- $this->set_plugin_data( array(
13
- 'description' => __( 'Allow coupons to be automatically applied to the cart when restrictions are met or by url.', 'woocommerce-jos-autocoupon' ),
14
- 'dependencies' => array(),
15
- 'can_be_disabled' => true
16
- ) );
17
- }
18
-
19
- public function init_hook() {
20
- //Since 2.6.2 check for frontend request. Prevents 'Call to undefined function wc_add_notice()'.
21
- if ( ! class_exists('WC_Coupon') || ! WJECF()->is_request( 'frontend' ) ) {
22
- return;
23
- }
24
-
25
- //NOTE: WPML Causes an extra calculate_totals() !!!
26
-
27
- //Frontend hooks - logic
28
- if ( WJECF_WC()->check_woocommerce_version('2.3.0')) {
29
- WJECF()->safe_add_action( 'woocommerce_after_calculate_totals', array( $this, 'update_matched_autocoupons' ) );
30
- } else {
31
- //WC Versions prior to 2.3.0 don't have after_calculate_totals hook, this is a fallback
32
- WJECF()->safe_add_action( 'woocommerce_cart_updated', array( $this, 'update_matched_autocoupons' ) );
33
- }
34
-
35
- add_action( 'woocommerce_check_cart_items', array( $this, 'remove_unmatched_autocoupons' ) , 0, 0 ); //Remove coupon before WC does it and shows a message
36
-
37
- //Frontend hooks - visualisation
38
- add_filter('woocommerce_cart_totals_coupon_label', array( $this, 'coupon_label' ), 10, 2 );
39
- add_filter('woocommerce_cart_totals_coupon_html', array( $this, 'coupon_html' ), 10, 2 );
40
-
41
- //Inhibit redirect to cart when apply_coupon supplied
42
- add_filter('option_woocommerce_cart_redirect_after_add', array ( $this, 'option_woocommerce_cart_redirect_after_add') );
43
-
44
- if ( ! is_ajax() ) {
45
- //Get cart should not be called before the wp_loaded action nor the add_to_cart_action (class-wc-form-handler)
46
- add_action( 'wp_loaded', array( &$this, 'coupon_by_url' ), 90 ); //Coupon through url
47
- }
48
-
49
- /**
50
- * Mark removed autocoupons to prevent them from being automatically applied again
51
- * (PRO Only)
52
- * @since 2.5.4
53
- */
54
- add_action( 'woocommerce_applied_coupon', array( $this, 'action_applied_coupon' ), 10, 1 );
55
- add_action( 'woocommerce_removed_coupon', array( $this, 'action_removed_coupon' ), 10, 1 );
56
- add_action( 'woocommerce_cart_emptied', array( $this, 'action_cart_emptied' ), 10, 0 );
57
- }
58
-
59
- /* ADMIN HOOKS */
60
- public function init_admin_hook() {
61
- add_action( 'wjecf_woocommerce_coupon_options_extended_features', array( $this, 'admin_coupon_options_extended_features' ), 20, 2 );
62
-
63
- //Inject columns
64
- if ( WJECF()->is_pro() ) {
65
- WJECF()->inject_coupon_column(
66
- '_wjecf_auto_coupon',
67
- __( 'Auto coupon', 'woocommerce-jos-autocoupon' ),
68
- array( $this, 'admin_render_shop_coupon_columns' ), 'coupon_code'
69
- );
70
- WJECF()->inject_coupon_column(
71
- '_wjecf_individual_use',
72
- __( 'Individual use', 'woocommerce-jos-autocoupon' ),
73
- array( $this, 'admin_render_shop_coupon_columns' ), 'coupon_code'
74
- );
75
- }
76
-
77
- add_filter( 'views_edit-shop_coupon', array( $this, 'admin_views_edit_coupon' ) );
78
- add_filter( 'request', array( $this, 'admin_request_query' ) );
79
-
80
- add_action( 'wjecf_admin_before_settings', array( $this, 'wjecf_admin_before_settings' ), 20 );
81
- add_filter( 'wjecf_admin_validate_settings', array( $this, 'wjecf_admin_validate_settings' ), 10, 2 );
82
- }
83
-
84
- public function wjecf_admin_before_settings() {
85
- $page = WJECF_Admin_Settings::SETTINGS_PAGE;
86
-
87
- if ( WJECF()->is_pro() ) {
88
- add_settings_section(
89
- WJECF_Admin_Settings::DOM_PREFIX . 'section_autocoupon',
90
- __( 'Auto coupons', 'woocommerce-jos-autocoupon' ),
91
- array( $this, 'render_section' ),
92
- $page
93
- );
94
-
95
- add_settings_field(
96
- WJECF_Admin_Settings::DOM_PREFIX . 'autocoupon_allow_remove',
97
- __( 'Allow remove \'Auto Coupons\'', 'woocommerce-jos-autocoupon' ),
98
- array( $this, 'render_setting_allow_remove_auto_coupon' ),
99
- $page,
100
- WJECF_Admin_Settings::DOM_PREFIX . 'section_autocoupon'
101
- );
102
- }
103
- }
104
-
105
- public function render_section( $section ) {
106
- switch ( $section['id'] ) {
107
- case WJECF_Admin_Settings::DOM_PREFIX . 'section_autocoupon':
108
- //$body = ".....";
109
- //printf( '<p>%s</p>', $body );
110
- break;
111
- }
112
- }
113
-
114
- public function render_setting_allow_remove_auto_coupon() {
115
- $option_name = 'autocoupon_allow_remove';
116
- $args = array(
117
- 'type' => 'checkbox',
118
- 'id' => WJECF_Admin_Settings::DOM_PREFIX . $option_name,
119
- 'name' => sprintf( "%s[%s]", WJECF_Admin_Settings::OPTION_NAME, $option_name ),
120
- 'value' => WJECF()->get_option( $option_name, false ) ? 'yes' : 'no'
121
- );
122
-
123
- WJECF_Admin_Html::render_input( $args );
124
- WJECF_Admin_Html::render_tag(
125
- 'label',
126
- array( "for" => esc_attr( $args['id'] ) ),
127
- __( 'Enabled', 'woocommerce' )
128
- );
129
- WJECF_Admin_Html::render_tag(
130
- 'p',
131
- array( 'class' => 'description'),
132
- __( 'Check this box to allow the customer to remove \'Auto Coupons\' from the cart.', 'woocommerce-jos-autocoupon' )
133
- );
134
-
135
- }
136
-
137
- public function wjecf_admin_validate_settings( $options, $input ) {
138
- $options['autocoupon_allow_remove'] = isset( $input['autocoupon_allow_remove'] ) && $input['autocoupon_allow_remove'] === 'yes';
139
- return $options;
140
- }
141
-
142
- /**
143
- * Output a coupon custom column value
144
- *
145
- * @param string $column
146
- * @param WP_Post The coupon post object
147
- */
148
- public function admin_render_shop_coupon_columns( $column, $post ) {
149
- $wrap_coupon = WJECF_Wrap( intval( $post->ID ) );
150
-
151
- switch ( $column ) {
152
- case '_wjecf_auto_coupon' :
153
- $is_auto_coupon = $wrap_coupon->get_meta( '_wjecf_is_auto_coupon', true ) == 'yes';
154
- echo $is_auto_coupon ? __( 'Yes', 'woocommerce' ) : __( 'No', 'woocommerce' );
155
- if ( $is_auto_coupon ) {
156
- $prio = $wrap_coupon->get_meta( '_wjecf_coupon_priority', true );
157
- if ( $prio ) echo " (" . intval( $prio ) . ")";
158
- }
159
- break;
160
- case '_wjecf_individual_use' :
161
- $individual = $wrap_coupon->get_individual_use();
162
- echo $individual ? __( 'Yes', 'woocommerce' ) : __( 'No', 'woocommerce' );
163
- break;
164
- }
165
- }
166
-
167
- public function admin_views_edit_coupon( $views ) {
168
- global $post_type, $wp_query;
169
-
170
- $class = ( isset( $wp_query->query['meta_key'] ) && $wp_query->query['meta_key'] == '_wjecf_is_auto_coupon' ) ? 'current' : '';
171
- $query_string = remove_query_arg(array( 'wjecf_is_auto_coupon' ));
172
- $query_string = add_query_arg( 'wjecf_is_auto_coupon', '1', $query_string );
173
- $views['wjecf_is_auto_coupon'] = '<a href="' . esc_url( $query_string ) . '" class="' . esc_attr( $class ) . '">' . __( 'Auto coupons', 'woocommerce-jos-autocoupon' ) . '</a>';
174
-
175
- return $views;
176
- }
177
-
178
- /**
179
- * Filters and sorting handler
180
- *
181
- * @param array $vars
182
- * @return array
183
- */
184
- public function admin_request_query( $vars ) {
185
- global $typenow, $wp_query, $wp_post_statuses;
186
-
187
- if ( 'shop_coupon' === $typenow ) {
188
- if ( isset( $_GET['wjecf_is_auto_coupon'] ) ) {
189
- $vars['meta_key'] = '_wjecf_is_auto_coupon';
190
- $vars['meta_value'] = $_GET['wjecf_is_auto_coupon'] == '1' ? 'yes' : 'no';
191
- }
192
- }
193
-
194
- return $vars;
195
- }
196
-
197
- public function admin_coupon_options_extended_features( $thepostid, $post ) {
198
-
199
- //=============================
200
- //Title
201
- echo "<h3>" . esc_html( __( 'Auto coupon', 'woocommerce-jos-autocoupon' ) ). "</h3>\n";
202
-
203
-
204
- //=============================
205
- // Auto coupon checkbox
206
- woocommerce_wp_checkbox( array(
207
- 'id' => '_wjecf_is_auto_coupon',
208
- 'label' => __( 'Auto coupon', 'woocommerce-jos-autocoupon' ),
209
- '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' )
210
- ) );
211
-
212
- echo '<div class="_wjecf_show_if_autocoupon">';
213
- if ( WJECF()->is_pro() ) {
214
- // Maximum quantity of matching products (product/category)
215
- woocommerce_wp_text_input( array(
216
- 'id' => '_wjecf_coupon_priority',
217
- 'label' => __( 'Priority', 'woocommerce-jos-autocoupon' ),
218
- 'placeholder' => __( 'No priority', 'woocommerce' ),
219
- 'description' => __( 'When \'individual use\' is checked, auto coupons with a higher value will have priority over other auto coupons.', 'woocommerce-jos-autocoupon' ),
220
- 'data_type' => 'decimal',
221
- 'desc_tip' => true
222
- ) );
223
- }
224
-
225
- //=============================
226
- // Apply without notice
227
- woocommerce_wp_checkbox( array(
228
- 'id' => '_wjecf_apply_silently',
229
- 'label' => __( 'Apply silently', 'woocommerce-jos-autocoupon' ),
230
- 'description' => __( "Don't display a message when this coupon is automatically applied.", 'woocommerce-jos-autocoupon' ),
231
- ) );
232
- echo '</div>';
233
-
234
- ?>
235
- <script type="text/javascript">
236
- //Hide/show when AUTO-COUPON value changes
237
- function update_wjecf_apply_silently_field( animation ) {
238
- if ( animation === undefined ) animation = 'slow';
239
-
240
- if (jQuery("#_wjecf_is_auto_coupon").prop('checked')) {
241
- jQuery("._wjecf_show_if_autocoupon").show( animation );
242
- jQuery("._wjecf_hide_if_autocoupon").hide( animation );
243
- } else {
244
- jQuery("._wjecf_show_if_autocoupon").hide( animation );
245
- jQuery("._wjecf_hide_if_autocoupon").show( animation );
246
- }
247
- }
248
-
249
- jQuery( function( $ ) {
250
- update_wjecf_apply_silently_field( 0 );
251
- $("#_wjecf_is_auto_coupon").click( update_wjecf_apply_silently_field );
252
- } );
253
- </script>
254
- <?php
255
-
256
- }
257
-
258
- public function admin_coupon_meta_fields( $coupon ) {
259
- $fields = array(
260
- '_wjecf_is_auto_coupon' => 'yesno',
261
- '_wjecf_apply_silently' => 'yesno',
262
- );
263
-
264
- if ( WJECF()->is_pro() ) {
265
- $fields['_wjecf_coupon_priority'] = 'int';
266
- }
267
- return $fields;
268
- }
269
-
270
- /* FRONTEND HOOKS */
271
-
272
- /**
273
- ** Inhibit redirect to cart when apply_coupon supplied
274
- */
275
- public function option_woocommerce_cart_redirect_after_add ( $value ) {
276
- if ( ! $this->_executed_coupon_by_url && isset( $_GET['apply_coupon'] ) ) {
277
- $value = 'no';
278
- }
279
- return $value;
280
- }
281
-
282
- /**
283
- * Add coupon through url
284
- */
285
- public function coupon_by_url() {
286
- $must_redirect = false;
287
-
288
- //Apply coupon by url
289
- if ( isset( $_GET['apply_coupon'] ) ) {
290
- $must_redirect = true;
291
- $this->_executed_coupon_by_url = true;
292
- $split = explode( ",", wc_clean( $_GET['apply_coupon'] ) );
293
- //2.2.2 Make sure a session cookie is set
294
- if( ! WC()->session->has_session() )
295
- {
296
- WC()->session->set_customer_session_cookie( true );
297
- }
298
-
299
- $cart = WC()->cart;
300
- foreach ( $split as $coupon_code ) {
301
- $coupon = WJECF_WC()->get_coupon( $coupon_code );
302
- if ( WJECF_WC()->check_woocommerce_version('2.3.0') && ! WJECF_Wrap( $coupon )->exists() ) {
303
- wc_add_notice( $coupon->get_coupon_error( WC_Coupon::E_WC_COUPON_NOT_EXIST ), 'error' );
304
- } else {
305
- $valid = $coupon->is_valid();
306
- if ( $valid ) {
307
- $cart->add_discount( $coupon_code );
308
- }
309
- }
310
- }
311
- }
312
-
313
- //Redirect to page without autocoupon query args
314
- if ( $must_redirect ) {
315
- $requested_url = is_ssl() ? 'https://' : 'http://';
316
- $requested_url .= $_SERVER['HTTP_HOST'];
317
- $requested_url .= $_SERVER['REQUEST_URI'];
318
-
319
- wp_safe_redirect( remove_query_arg( array( 'apply_coupon', 'add-to-cart' ), ( $requested_url ) ) );
320
- exit;
321
- }
322
- }
323
-
324
- /**
325
- * Overwrite the html created by wc_cart_totals_coupon_label() so a descriptive text will be shown for the discount.
326
- * @param string $originaltext The default text created by wc_cart_totals_coupon_label()
327
- * @param WC_Coupon $coupon The coupon data
328
- * @return string The overwritten text
329
- */
330
- function coupon_label( $originaltext, $coupon ) {
331
-
332
- if ( $this->is_auto_coupon($coupon) ) {
333
- return WJECF_Wrap( $coupon )->get_description();
334
- } else {
335
- return $originaltext;
336
- }
337
- }
338
-
339
- /**
340
- * Overwrite the html created by wc_cart_totals_coupon_html(). This function is required to remove the "Remove" link.
341
- * @param string $originaltext The html created by wc_cart_totals_coupon_html()
342
- * @param WC_Coupon $coupon The coupon data
343
- * @return string The overwritten html
344
- */
345
- function coupon_html( $originaltext, $coupon ) {
346
- if ( $this->is_auto_coupon( $coupon ) && ! $this->get_option_autocoupon_allow_remove() ) {
347
- $value = array();
348
-
349
- if ( $amount = WC()->cart->get_coupon_discount_amount( WJECF_Wrap( $coupon )->get_code(), WC()->cart->display_cart_ex_tax ) ) {
350
- $discount_html = '-' . wc_price( $amount );
351
- } else {
352
- $discount_html = '';
353
- }
354
-
355
- $value[] = apply_filters( 'woocommerce_coupon_discount_amount_html', $discount_html, $coupon );
356
-
357
- if ( WJECF_Wrap( $coupon )->get_free_shipping() ) {
358
- $value[] = __( 'Free shipping coupon', 'woocommerce' );
359
- }
360
-
361
- return implode( ', ', array_filter($value) ); //Remove empty array elements
362
- } else {
363
- return $originaltext;
364
- }
365
- }
366
-
367
- function remove_unmatched_autocoupons( $valid_coupon_codes = null ) {
368
- if ( $valid_coupon_codes === null ) {
369
- //Get the coupons that should be in the cart
370
- $valid_coupons = $this->get_valid_auto_coupons();
371
- $valid_coupons = $this->individual_use_filter( $valid_coupons );
372
- $valid_coupon_codes = array();
373
- foreach ( $valid_coupons as $coupon ) {
374
- $valid_coupon_codes[] = WJECF_Wrap( $coupon )->get_code();
375
- }
376
- }
377
-
378
- //Remove invalids
379
- $calc_needed = false;
380
- foreach ( $this->get_all_auto_coupons() as $coupon ) {
381
- $coupon_code = WJECF_Wrap( $coupon )->get_code();
382
- if ( WC()->cart->has_discount( $coupon_code ) && ! in_array( $coupon_code, $valid_coupon_codes ) ) {
383
- $this->log( 'debug', sprintf( "Removing %s", $coupon_code ) );
384
- $this->ignore_removal[$coupon_code] = $coupon_code;
385
- WC()->cart->remove_coupon( $coupon_code );
386
- unset( $this->ignore_removal[$coupon_code] );
387
- $calc_needed = true;
388
- }
389
- }
390
- return $calc_needed;
391
- }
392
-
393
-
394
- // ============ BEGIN wjecf_autocoupon_removed_coupons =======================
395
-
396
- // When a custom clicks the [remove]-button of an auto coupon, the coupon will not be applied automatically anymore...
397
- // After that the coupon can only be applied manually by entering the coupon code.
398
-
399
- //Will ignore the remove_coupon action for the given keys
400
- private $ignore_removal = array( /* coupon_code => coupon_code */ );
401
-
402
- /**
403
- * Remove the coupon from session 'wjecf_autocoupon_removed_coupons'
404
- * @since 2.5.4
405
- */
406
- function action_applied_coupon( $coupon_code ) {
407
- if ( ! $this->is_auto_coupon( $coupon_code ) || ! $this->get_option_autocoupon_allow_remove() ) {
408
- return;
409
- }
410
-
411
- $removed_autocoupon_codes = $this->get_removed_autocoupon_codes();
412
- if ( ! isset( $removed_autocoupon_codes[$coupon_code] ) ) {
413
- return;
414
- }
415
-
416
- unset( $removed_autocoupon_codes[$coupon_code] ) ;
417
- $this->set_removed_autocoupon_codes( $removed_autocoupon_codes );
418
- }
419
-
420
- /**
421
- * Add the coupon to session 'wjecf_autocoupon_removed_coupons'
422
- *
423
- * @since 2.5.4
424
- */
425
- function action_removed_coupon( $coupon_code ) {
426
- if ( ! $this->is_auto_coupon( $coupon_code ) || ! $this->get_option_autocoupon_allow_remove() ) {
427
- return;
428
- }
429
- //Ignore, because the auto-coupon was removed automatically (not manually by the customer)
430
- if ( isset( $this->ignore_removal[$coupon_code] ) ) {
431
- return;
432
- }
433
-
434
- $removed_autocoupon_codes = $this->get_removed_autocoupon_codes();
435
- $removed_autocoupon_codes[$coupon_code] = $coupon_code;
436
- $this->set_removed_autocoupon_codes( $removed_autocoupon_codes );
437
- }
438
-
439
- /**
440
- * Remove 'wjecf_autocoupon_removed_coupons' from the session when cart is emptied
441
- *
442
- * @since 2.5.4
443
- */
444
- function action_cart_emptied() {
445
- $this->set_removed_autocoupon_codes( null );
446
- }
447
-
448
- /**
449
- * Get the removed auto coupon-codes from the session
450
- *
451
- * @since 2.5.4
452
- * @return array The queued coupon codes
453
- */
454
- private function get_removed_autocoupon_codes( ) {
455
- $coupon_codes = WC()->session->get( 'wjecf_autocoupon_removed_coupons' , array() );
456
- return $coupon_codes;
457
- }
458
-
459
- /**
460
- * Save the removed auto coupon-codes in the session
461
- *
462
- * @since 2.5.4
463
- * @param array $coupon_codes
464
- * @return void
465
- */
466
- private function set_removed_autocoupon_codes( $coupon_codes ) {
467
- WC()->session->set( 'wjecf_autocoupon_removed_coupons' , $coupon_codes );
468
- }
469
-
470
- /**
471
- * Reads the option 'autocoupon_allow_remove'
472
- *
473
- * @since 2.5.4
474
- * @return bool
475
- */
476
- private function get_option_autocoupon_allow_remove() {
477
- return WJECF()->is_pro() && WJECF()->get_option( 'autocoupon_allow_remove', false );
478
- }
479
-
480
- // ============ END wjecf_autocoupon_removed_coupons =======================
481
-
482
-
483
- /**
484
- * Apply matched autocoupons and remove unmatched autocoupons.
485
- * @return void
486
- */
487
- function update_matched_autocoupons() {
488
-
489
- //2.3.3 Keep track of queued coupons and apply when they validate
490
- $queuer = WJECF()->get_plugin('WJECF_Pro_Coupon_Queueing');
491
- if ( $queuer !== false ) {
492
- $queuer->apply_valid_queued_coupons();
493
- }
494
-
495
- //Get the coupons that should be in the cart
496
- $valid_coupons = $this->get_valid_auto_coupons();
497
- $valid_coupons = $this->individual_use_filter( $valid_coupons );
498
-
499
- $valid_coupon_codes = array();
500
- foreach ( $valid_coupons as $coupon ) {
501
- $valid_coupon_codes[] = WJECF_Wrap( $coupon )->get_code();
502
- }
503
-
504
- $this->log( 'debug', sprintf( "Auto coupons that should be in cart: %s", implode( ', ', $valid_coupon_codes ) ) );
505
-
506
- $calc_needed = $this->remove_unmatched_autocoupons( $valid_coupon_codes );
507
-
508
- //Add valids
509
- foreach( $valid_coupons as $coupon ) {
510
- $coupon_code = WJECF_Wrap( $coupon )->get_code();
511
- if ( ! WC()->cart->has_discount( $coupon_code ) ) {
512
- $this->log( 'debug', sprintf( "Applying auto coupon %s", $coupon_code ) );
513
-
514
- $apply_silently = WJECF_Wrap( $coupon )->get_meta( '_wjecf_apply_silently' ) == 'yes';
515
-
516
- if ( $apply_silently ) {
517
- $new_succss_msg = ''; // no message
518
- } else {
519
- $coupon_excerpt = WJECF_Wrap( $coupon )->get_description();
520
- $new_succss_msg = sprintf(
521
- __("Discount applied: %s", 'woocommerce-jos-autocoupon'),
522
- __( empty( $coupon_excerpt ) ? $coupon_code : $coupon_excerpt, 'woocommerce-jos-autocoupon')
523
- );
524
- }
525
-
526
- WJECF()->start_overwrite_success_message( $coupon, $new_succss_msg );
527
- WC()->cart->add_discount( $coupon_code ); //Causes calculation and will remove other coupons if it's a individual coupon
528
- WJECF()->stop_overwrite_success_message();
529
-
530
- $calc_needed = false; //Already done by adding the discount
531
-
532
- }
533
- }
534
-
535
- $this->log( 'debug', 'Coupons in cart: ' . implode( ', ', WC()->cart->applied_coupons ) . ($calc_needed ? ". RECALC" : "") );
536
-
537
- if ( $calc_needed ) {
538
- WC()->cart->calculate_totals();
539
- }
540
-
541
- }
542
-
543
- private function get_valid_auto_coupons( ) {
544
- $valid_coupons = array();
545
-
546
- //Array will only have values if option autocoupon_allow_remove == true
547
- $removed_autocoupon_codes = $this->get_option_autocoupon_allow_remove() ? $this->get_removed_autocoupon_codes() : array();
548
-
549
- foreach ( $this->get_all_auto_coupons() as $coupon_code => $coupon ) {
550
- if ( isset( $removed_autocoupon_codes[ $coupon_code ] ) ) continue;
551
- if ( ! $this->coupon_can_be_applied( $coupon ) ) continue;
552
- if ( ! $this->coupon_has_a_value( $coupon ) ) continue;
553
-
554
- $valid_coupons[] = $coupon;
555
- }
556
- return $valid_coupons;
557
- }
558
-
559
- /**
560
- * Test whether the coupon is valid (to be auto-applied)
561
- * @return bool
562
- */
563
- private function coupon_can_be_applied( $coupon ) {
564
- $wrap_coupon = WJECF_Wrap( $coupon );
565
- $can_be_applied = true;
566
-
567
- //Test validity
568
- if ( ! $coupon->is_valid() ) {
569
- $can_be_applied = false;
570
- }
571
- else if ( $can_be_applied && is_array( $wrap_coupon->get_email_restrictions() ) && sizeof( $wrap_coupon->get_email_restrictions() ) > 0 ) {
572
- //Test restricted emails
573
- //See WooCommerce: class-wc-cart.php function check_customer_coupons
574
- $user_emails = array_map( 'sanitize_email', array_map( 'strtolower', WJECF()->get_user_emails() ) );
575
- $coupon_emails = array_map( 'sanitize_email', array_map( 'strtolower', $wrap_coupon->get_email_restrictions() ) );
576
-
577
- if ( 0 == sizeof( array_intersect( $user_emails, $coupon_emails ) ) ) {
578
- $can_be_applied = false;
579
- }
580
-
581
- $this->log( sprintf( 'Coupon emails: [ %s ] User emails: [ %s ] Match: %s',
582
- join( ', ', $coupon_emails ),
583
- join( ', ', $user_emails ),
584
- $can_be_applied ? 'yes' : 'no'
585
- ) );
586
- }
587
- return apply_filters( 'wjecf_coupon_can_be_applied', $can_be_applied, $coupon );
588
-
589
- }
590
-
591
- /**
592
- * Does the coupon have a value? (autocoupon should not be applied if it has no value)
593
- * @param WC_Coupon $coupon The coupon data
594
- * @return bool True if it has a value (discount, free shipping, whatever) otherwise false)
595
- **/
596
- function coupon_has_a_value( $coupon ) {
597
-
598
- $has_a_value = false;
599
-
600
- if ( $coupon->is_type('free_gift') ) { // 'WooCommerce Free Gift Coupons'-plugin
601
- $has_a_value = true;
602
- }
603
- elseif ( WJECF_Wrap( $coupon )->get_free_shipping() ) {
604
- $has_a_value = true;
605
- }
606
- else {
607
- //Test whether discount > 0
608
- //See WooCommerce: class-wc-cart.php function get_discounted_price
609
- global $woocommerce;
610
- foreach ( $woocommerce->cart->get_cart() as $cart_item ) {
611
- if ( $coupon->is_valid_for_cart() || $coupon->is_valid_for_product( $cart_item['data'], $cart_item ) ) {
612
- $has_a_value = WJECF_Wrap( $coupon )->get_amount() > 0;
613
- break;
614
- }
615
- }
616
- }
617
-
618
- return apply_filters( 'wjecf_coupon_has_a_value', $has_a_value, $coupon );
619
- }
620
-
621
-
622
-
623
- /**
624
- * Check wether the coupon is an "Auto coupon".
625
- * @param WC_Coupon $coupon The coupon data
626
- * @return bool true if it is an "Auto coupon"
627
- */
628
- public function is_auto_coupon( $coupon ) {
629
- return WJECF_Wrap( $coupon )->get_meta( '_wjecf_is_auto_coupon' ) == 'yes';
630
- }
631
-
632
- private function get_coupon_priority($coupon) {
633
- if ( WJECF()->is_pro() ) {
634
- $prio = WJECF_Wrap( $coupon )->get_meta( '_wjecf_coupon_priority' );
635
- if ( ! empty( $prio ) ) {
636
- return intval( $prio );
637
- }
638
- }
639
- return 0;
640
- }
641
-
642
-
643
-
644
- /**
645
- * Return an array of WC_Coupons with coupons that shouldn't cause individual use conflicts
646
- */
647
- private function individual_use_filter( $valid_auto_coupons ) {
648
- $filtered = array();
649
-
650
- //Any individual use non-autocoupons in the cart?
651
- foreach ( WC()->cart->get_applied_coupons() as $coupon_code ) {
652
- $coupon = new WC_Coupon( $coupon_code );
653
- if ( WJECF_Wrap( $coupon )->get_individual_use() && ! $this->is_auto_coupon( $coupon ) ) {
654
- return $filtered; //Don't allow any auto coupon
655
- }
656
- }
657
- foreach ( $valid_auto_coupons as $coupon ) {
658
- if ( ! WJECF_Wrap( $coupon )->get_individual_use() || empty( $filtered ) ) {
659
- $filtered[] = $coupon;
660
- if ( WJECF_Wrap( $coupon )->get_individual_use() ) {
661
- break;
662
- }
663
- }
664
- }
665
- return $filtered;
666
- }
667
-
668
- /**
669
- * Get an array of all auto coupons [ $coupon_code => $coupon ]
670
- * @return array All auto coupon codes
671
- */
672
- public function get_all_auto_coupons() {
673
- if ( ! is_array( $this->_autocoupons ) ) {
674
- $this->_autocoupons = array();
675
-
676
- $query_args = array(
677
- 'posts_per_page' => -1,
678
- 'post_type' => 'shop_coupon',
679
- 'post_status' => 'publish',
680
- 'orderby' => array( 'title' => 'ASC' ),
681
- 'meta_query' => array(
682
- array(
683
- 'key' => '_wjecf_is_auto_coupon',
684
- 'compare' => '=',
685
- 'value' => 'yes',
686
- ),
687
- )
688
- );
689
-
690
- $query = new WP_Query($query_args);
691
- foreach ($query->posts as $post) {
692
- $coupon = new WC_Coupon($post->post_title);
693
- if ( $this->is_auto_coupon($coupon) ) {
694
- $this->_autocoupons[ WJECF_Wrap( $coupon )->get_code() ] = $coupon;
695
- }
696
- }
697
-
698
- //Sort by priority
699
- @uasort( $this->_autocoupons , array( $this, 'sort_auto_coupons' ) ); //Ignore error PHP Bug #50688
700
-
701
- $coupon_codes = array();
702
- foreach( $this->_autocoupons as $coupon ) {
703
- $coupon_codes[] = WJECF_Wrap( $coupon )->get_code();
704
- }
705
-
706
- $this->log( 'debug', "Autocoupons: " . implode(", ", $coupon_codes ) );
707
- }
708
-
709
- return $this->_autocoupons;
710
- }
711
-
712
- /**
713
- * Compare function to sort coupons by priority
714
- * @param type $a
715
- * @param type $b
716
- * @return type
717
- */
718
- private function sort_auto_coupons( $coupon_a, $coupon_b ) {
719
- $prio_a = $this->get_coupon_priority( $coupon_a );
720
- $prio_b = $this->get_coupon_priority( $coupon_b );
721
- //$this->log( 'debug', "A: $prio_a B: $prio_b " );
722
- if ( $prio_a == $prio_b ) {
723
- return WJECF_Wrap( $coupon_a )->get_code() < WJECF_Wrap( $coupon_b )->get_code() ? -1 : 1; //By title ASC
724
- } else {
725
- return $prio_a > $prio_b ? -1 : 1; //By prio DESC
726
- }
727
- }
728
- }
1
+ <?php
2
+
3
+ defined('ABSPATH') or die();
4
+
5
+ class WJECF_AutoCoupon extends Abstract_WJECF_Plugin {
6
+
7
+ protected $options = null; // WJECF_Options object
8
+ protected $_autocoupons = null;
9
+ protected $_executed_coupon_by_url = false;
10
+
11
+ public function __construct() {
12
+ $this->set_plugin_data( array(
13
+ 'description' => __( 'Allow coupons to be automatically applied to the cart when restrictions are met or by url.', 'woocommerce-jos-autocoupon' ),
14
+ 'dependencies' => array(),
15
+ 'can_be_disabled' => true
16
+ ) );
17
+ }
18
+
19
+ public function init_hook() {
20
+ //Since 2.6.2 check for frontend request. Prevents 'Call to undefined function wc_add_notice()'.
21
+ if ( ! class_exists('WC_Coupon') || ! WJECF()->is_request( 'frontend' ) ) {
22
+ return;
23
+ }
24
+
25
+ //NOTE: WPML Causes an extra calculate_totals() !!!
26
+
27
+ //Frontend hooks - logic
28
+ if ( WJECF_WC()->check_woocommerce_version('2.3.0')) {
29
+ WJECF()->safe_add_action( 'woocommerce_after_calculate_totals', array( $this, 'update_matched_autocoupons' ) );
30
+ } else {
31
+ //WC Versions prior to 2.3.0 don't have after_calculate_totals hook, this is a fallback
32
+ WJECF()->safe_add_action( 'woocommerce_cart_updated', array( $this, 'update_matched_autocoupons' ) );
33
+ }
34
+
35
+ add_action( 'woocommerce_check_cart_items', array( $this, 'remove_unmatched_autocoupons' ) , 0, 0 ); //Remove coupon before WC does it and shows a message
36
+
37
+ //Frontend hooks - visualisation
38
+ add_filter('woocommerce_cart_totals_coupon_label', array( $this, 'coupon_label' ), 10, 2 );
39
+ add_filter('woocommerce_cart_totals_coupon_html', array( $this, 'coupon_html' ), 10, 2 );
40
+
41
+ //Inhibit redirect to cart when apply_coupon supplied
42
+ add_filter('option_woocommerce_cart_redirect_after_add', array ( $this, 'option_woocommerce_cart_redirect_after_add') );
43
+
44
+ if ( ! WJECF()->is_request( 'ajax' ) ) {
45
+ //Get cart should not be called before the wp_loaded action nor the add_to_cart_action (class-wc-form-handler)
46
+ add_action( 'wp_loaded', array( &$this, 'coupon_by_url' ), 90 ); //Coupon through url
47
+ }
48
+
49
+ /**
50
+ * Mark removed autocoupons to prevent them from being automatically applied again
51
+ * (PRO Only)
52
+ * @since 2.5.4
53
+ */
54
+ add_action( 'woocommerce_applied_coupon', array( $this, 'action_applied_coupon' ), 10, 1 );
55
+ add_action( 'woocommerce_removed_coupon', array( $this, 'action_removed_coupon' ), 10, 1 );
56
+ add_action( 'woocommerce_cart_emptied', array( $this, 'action_cart_emptied' ), 10, 0 );
57
+ }
58
+
59
+ /* ADMIN HOOKS */
60
+ public function init_admin_hook() {
61
+ add_action( 'wjecf_woocommerce_coupon_options_extended_features', array( $this, 'admin_coupon_options_extended_features' ), 20, 2 );
62
+
63
+ //Inject columns
64
+ if ( WJECF()->is_pro() ) {
65
+ WJECF()->inject_coupon_column(
66
+ '_wjecf_auto_coupon',
67
+ __( 'Auto coupon', 'woocommerce-jos-autocoupon' ),
68
+ array( $this, 'admin_render_shop_coupon_columns' ), 'coupon_code'
69
+ );
70
+ WJECF()->inject_coupon_column(
71
+ '_wjecf_individual_use',
72
+ __( 'Individual use', 'woocommerce-jos-autocoupon' ),
73
+ array( $this, 'admin_render_shop_coupon_columns' ), 'coupon_code'
74
+ );
75
+ }
76
+
77
+ add_filter( 'views_edit-shop_coupon', array( $this, 'admin_views_edit_coupon' ) );
78
+ add_filter( 'request', array( $this, 'admin_request_query' ) );
79
+
80
+ add_action( 'wjecf_admin_before_settings', array( $this, 'wjecf_admin_before_settings' ), 20 );
81
+ add_filter( 'wjecf_admin_validate_settings', array( $this, 'wjecf_admin_validate_settings' ), 10, 2 );
82
+ }
83
+
84
+ public function wjecf_admin_before_settings() {
85
+ $page = WJECF_Admin_Settings::SETTINGS_PAGE;
86
+
87
+ if ( WJECF()->is_pro() ) {
88
+ add_settings_section(
89
+ WJECF_Admin_Settings::DOM_PREFIX . 'section_autocoupon',
90
+ __( 'Auto coupons', 'woocommerce-jos-autocoupon' ),
91
+ array( $this, 'render_section' ),
92
+ $page
93
+ );
94
+
95
+ add_settings_field(
96
+ WJECF_Admin_Settings::DOM_PREFIX . 'autocoupon_allow_remove',
97
+ __( 'Allow remove \'Auto Coupons\'', 'woocommerce-jos-autocoupon' ),
98
+ array( $this, 'render_setting_allow_remove_auto_coupon' ),
99
+ $page,
100
+ WJECF_Admin_Settings::DOM_PREFIX . 'section_autocoupon'
101
+ );
102
+ }
103
+ }
104
+
105
+ public function render_section( $section ) {
106
+ switch ( $section['id'] ) {
107
+ case WJECF_Admin_Settings::DOM_PREFIX . 'section_autocoupon':
108
+ //$body = ".....";
109
+ //printf( '<p>%s</p>', $body );
110
+ break;
111
+ }
112
+ }
113
+
114
+ public function render_setting_allow_remove_auto_coupon() {
115
+ $option_name = 'autocoupon_allow_remove';
116
+ $args = array(
117
+ 'type' => 'checkbox',
118
+ 'id' => WJECF_Admin_Settings::DOM_PREFIX . $option_name,
119
+ 'name' => sprintf( "%s[%s]", WJECF_Admin_Settings::OPTION_NAME, $option_name ),
120
+ 'value' => WJECF()->get_option( $option_name, false ) ? 'yes' : 'no'
121
+ );
122
+
123
+ WJECF_Admin_Html::render_input( $args );
124
+ WJECF_Admin_Html::render_tag(
125
+ 'label',
126
+ array( "for" => esc_attr( $args['id'] ) ),
127
+ __( 'Enabled', 'woocommerce' )
128
+ );
129
+ WJECF_Admin_Html::render_tag(
130
+ 'p',
131
+ array( 'class' => 'description'),
132
+ __( 'Check this box to allow the customer to remove \'Auto Coupons\' from the cart.', 'woocommerce-jos-autocoupon' )
133
+ );
134
+
135
+ }
136
+
137
+ public function wjecf_admin_validate_settings( $options, $input ) {
138
+ $options['autocoupon_allow_remove'] = isset( $input['autocoupon_allow_remove'] ) && $input['autocoupon_allow_remove'] === 'yes';
139
+ return $options;
140
+ }
141
+
142
+ /**
143
+ * Output a coupon custom column value
144
+ *
145
+ * @param string $column
146
+ * @param WP_Post The coupon post object
147
+ */
148
+ public function admin_render_shop_coupon_columns( $column, $post ) {
149
+ $wrap_coupon = WJECF_Wrap( intval( $post->ID ) );
150
+
151
+ switch ( $column ) {
152
+ case '_wjecf_auto_coupon' :
153
+ $is_auto_coupon = $wrap_coupon->get_meta( '_wjecf_is_auto_coupon', true ) == 'yes';
154
+ echo $is_auto_coupon ? __( 'Yes', 'woocommerce' ) : __( 'No', 'woocommerce' );
155
+ if ( $is_auto_coupon ) {
156
+ $prio = $wrap_coupon->get_meta( '_wjecf_coupon_priority', true );
157
+ if ( $prio ) echo " (" . intval( $prio ) . ")";
158
+ }
159
+ break;
160
+ case '_wjecf_individual_use' :
161
+ $individual = $wrap_coupon->get_individual_use();
162
+ echo $individual ? __( 'Yes', 'woocommerce' ) : __( 'No', 'woocommerce' );
163
+ break;
164
+ }
165
+ }
166
+
167
+ public function admin_views_edit_coupon( $views ) {
168
+ global $post_type, $wp_query;
169
+
170
+ $class = ( isset( $wp_query->query['meta_key'] ) && $wp_query->query['meta_key'] == '_wjecf_is_auto_coupon' ) ? 'current' : '';
171
+ $query_string = remove_query_arg(array( 'wjecf_is_auto_coupon' ));
172
+ $query_string = add_query_arg( 'wjecf_is_auto_coupon', '1', $query_string );
173
+ $views['wjecf_is_auto_coupon'] = '<a href="' . esc_url( $query_string ) . '" class="' . esc_attr( $class ) . '">' . __( 'Auto coupons', 'woocommerce-jos-autocoupon' ) . '</a>';
174
+
175
+ return $views;
176
+ }
177
+
178
+ /**
179
+ * Filters and sorting handler
180
+ *
181
+ * @param array $vars
182
+ * @return array
183
+ */
184
+ public function admin_request_query( $vars ) {
185
+ global $typenow, $wp_query, $wp_post_statuses;
186
+
187
+ if ( 'shop_coupon' === $typenow ) {
188
+ if ( isset( $_GET['wjecf_is_auto_coupon'] ) ) {
189
+ $vars['meta_key'] = '_wjecf_is_auto_coupon';
190
+ $vars['meta_value'] = $_GET['wjecf_is_auto_coupon'] == '1' ? 'yes' : 'no';
191
+ }
192
+ }
193
+
194
+ return $vars;
195
+ }
196
+
197
+ public function admin_coupon_options_extended_features( $thepostid, $post ) {
198
+
199
+ //=============================
200
+ //Title
201
+ echo "<h3>" . esc_html( __( 'Auto coupon', 'woocommerce-jos-autocoupon' ) ). "</h3>\n";
202
+
203
+
204
+ //=============================
205
+ // Auto coupon checkbox
206
+ woocommerce_wp_checkbox( array(
207
+ 'id' => '_wjecf_is_auto_coupon',
208
+ 'label' => __( 'Auto coupon', 'woocommerce-jos-autocoupon' ),
209
+ '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' )
210
+ ) );
211
+
212
+ echo '<div class="_wjecf_show_if_autocoupon">';
213
+ if ( WJECF()->is_pro() ) {
214
+ // Maximum quantity of matching products (product/category)
215
+ woocommerce_wp_text_input( array(
216
+ 'id' => '_wjecf_coupon_priority',
217
+ 'label' => __( 'Priority', 'woocommerce-jos-autocoupon' ),
218
+ 'placeholder' => __( 'No priority', 'woocommerce' ),
219
+ 'description' => __( 'When \'individual use\' is checked, auto coupons with a higher value will have priority over other auto coupons.', 'woocommerce-jos-autocoupon' ),
220
+ 'data_type' => 'decimal',
221
+ 'desc_tip' => true
222
+ ) );
223
+ }
224
+
225
+ //=============================
226
+ // Apply without notice
227
+ woocommerce_wp_checkbox( array(
228
+ 'id' => '_wjecf_apply_silently',
229
+ 'label' => __( 'Apply silently', 'woocommerce-jos-autocoupon' ),
230
+ 'description' => __( "Don't display a message when this coupon is automatically applied.", 'woocommerce-jos-autocoupon' ),
231
+ ) );
232
+ echo '</div>';
233
+
234
+ ?>
235
+ <script type="text/javascript">
236
+ //Hide/show when AUTO-COUPON value changes
237
+ function update_wjecf_apply_silently_field( animation ) {
238
+ if ( animation === undefined ) animation = 'slow';
239
+
240
+ if (jQuery("#_wjecf_is_auto_coupon").prop('checked')) {
241
+ jQuery("._wjecf_show_if_autocoupon").show( animation );
242
+ jQuery("._wjecf_hide_if_autocoupon").hide( animation );
243
+ } else {
244
+ jQuery("._wjecf_show_if_autocoupon").hide( animation );
245
+ jQuery("._wjecf_hide_if_autocoupon").show( animation );
246
+ }
247
+ }
248
+
249
+ jQuery( function( $ ) {
250
+ update_wjecf_apply_silently_field( 0 );
251
+ $("#_wjecf_is_auto_coupon").click( update_wjecf_apply_silently_field );
252
+ } );
253
+ </script>
254
+ <?php
255
+
256
+ }
257
+
258
+ public function admin_coupon_meta_fields( $coupon ) {
259
+ $fields = array(
260
+ '_wjecf_is_auto_coupon' => 'yesno',
261
+ '_wjecf_apply_silently' => 'yesno',
262
+ );
263
+
264
+ if ( WJECF()->is_pro() ) {
265
+ $fields['_wjecf_coupon_priority'] = 'int';
266
+ }
267
+ return $fields;
268
+ }
269
+
270
+ /* FRONTEND HOOKS */
271
+
272
+ /**
273
+ ** Inhibit redirect to cart when apply_coupon supplied
274
+ */
275
+ public function option_woocommerce_cart_redirect_after_add( $value ) {
276
+ if ( ! $this->_executed_coupon_by_url && isset( $_GET['apply_coupon'] ) ) {
277
+ $value = 'no';
278
+ }
279
+ return $value;
280
+ }
281
+
282
+ /**
283
+ * Add coupon through url
284
+ */
285
+ public function coupon_by_url() {
286
+ $must_redirect = false;
287
+
288
+ //Apply coupon by url
289
+ if ( isset( $_GET['apply_coupon'] ) ) {
290
+ $must_redirect = true;
291
+ $this->_executed_coupon_by_url = true;
292
+ $split = explode( ",", wc_clean( $_GET['apply_coupon'] ) );
293
+ //2.2.2 Make sure a session cookie is set
294
+ if( ! WC()->session->has_session() )
295
+ {
296
+ WC()->session->set_customer_session_cookie( true );
297
+ }
298
+
299
+ $cart = WC()->cart;
300
+ foreach ( $split as $coupon_code ) {
301
+ $coupon = WJECF_WC()->get_coupon( $coupon_code );
302
+ if ( WJECF_WC()->check_woocommerce_version('2.3.0') && ! WJECF_Wrap( $coupon )->exists() ) {
303
+ wc_add_notice( $coupon->get_coupon_error( WC_Coupon::E_WC_COUPON_NOT_EXIST ), 'error' );
304
+ } else {
305
+ $valid = $coupon->is_valid();
306
+ if ( $valid ) {
307
+ $cart->add_discount( $coupon_code );
308
+ }
309
+ }
310
+ }
311
+ }
312
+
313
+ //Redirect to page without autocoupon query args
314
+ if ( $must_redirect ) {
315
+ $requested_url = is_ssl() ? 'https://' : 'http://';
316
+ $requested_url .= $_SERVER['HTTP_HOST'];
317
+ $requested_url .= $_SERVER['REQUEST_URI'];
318
+
319
+ wp_safe_redirect( remove_query_arg( array( 'apply_coupon', 'add-to-cart' ), ( $requested_url ) ) );
320
+ exit;
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Overwrite the html created by wc_cart_totals_coupon_label() so a descriptive text will be shown for the discount.
326
+ * @param string $originaltext The default text created by wc_cart_totals_coupon_label()
327
+ * @param WC_Coupon $coupon The coupon data
328
+ * @return string The overwritten text
329
+ */
330
+ function coupon_label( $originaltext, $coupon ) {
331
+
332
+ if ( $this->is_auto_coupon($coupon) ) {
333
+ return WJECF_Wrap( $coupon )->get_description();
334
+ } else {
335
+ return $originaltext;
336
+ }
337
+ }
338
+
339
+ /**
340
+ * Overwrite the html created by wc_cart_totals_coupon_html(). This function is required to remove the "Remove" link.
341
+ * @param string $originaltext The html created by wc_cart_totals_coupon_html()
342
+ * @param WC_Coupon $coupon The coupon data
343
+ * @return string The overwritten html
344
+ */
345
+ function coupon_html( $originaltext, $coupon ) {
346
+ if ( $this->is_auto_coupon( $coupon ) && ! $this->get_option_autocoupon_allow_remove() ) {
347
+ $value = array();
348
+
349
+ if ( $amount = WC()->cart->get_coupon_discount_amount( WJECF_Wrap( $coupon )->get_code(), WC()->cart->display_cart_ex_tax ) ) {
350
+ $discount_html = '-' . wc_price( $amount );
351
+ } else {
352
+ $discount_html = '';
353
+ }
354
+
355
+ $value[] = apply_filters( 'woocommerce_coupon_discount_amount_html', $discount_html, $coupon );
356
+
357
+ if ( WJECF_Wrap( $coupon )->get_free_shipping() ) {
358
+ $value[] = __( 'Free shipping coupon', 'woocommerce' );
359
+ }
360
+
361
+ return implode( ', ', array_filter($value) ); //Remove empty array elements
362
+ } else {
363
+ return $originaltext;
364
+ }
365
+ }
366
+
367
+ function remove_unmatched_autocoupons( $valid_coupon_codes = null ) {
368
+ if ( $valid_coupon_codes === null ) {
369
+ //Get the coupons that should be in the cart
370
+ $valid_coupons = $this->get_valid_auto_coupons();
371
+ $valid_coupons = $this->individual_use_filter( $valid_coupons );
372
+ $valid_coupon_codes = array();
373
+ foreach ( $valid_coupons as $coupon ) {
374
+ $valid_coupon_codes[] = WJECF_Wrap( $coupon )->get_code();
375
+ }
376
+ }
377
+
378
+ //Remove invalids
379
+ $calc_needed = false;
380
+ foreach ( $this->get_all_auto_coupons() as $coupon ) {
381
+ $coupon_code = WJECF_Wrap( $coupon )->get_code();
382
+ if ( WC()->cart->has_discount( $coupon_code ) && ! in_array( $coupon_code, $valid_coupon_codes ) ) {
383
+ $this->log( 'debug', sprintf( "Removing %s", $coupon_code ) );
384
+ $this->ignore_removal[$coupon_code] = $coupon_code;
385
+ WC()->cart->remove_coupon( $coupon_code );
386
+ unset( $this->ignore_removal[$coupon_code] );
387
+ $calc_needed = true;
388
+ }
389
+ }
390
+ return $calc_needed;
391
+ }
392
+
393
+
394
+ // ============ BEGIN wjecf_autocoupon_removed_coupons =======================
395
+
396
+ // When a custom clicks the [remove]-button of an auto coupon, the coupon will not be applied automatically anymore...
397
+ // After that the coupon can only be applied manually by entering the coupon code.
398
+
399
+ //Will ignore the remove_coupon action for the given keys
400
+ private $ignore_removal = array( /* coupon_code => coupon_code */ );
401
+
402
+ /**
403
+ * Remove the coupon from session 'wjecf_autocoupon_removed_coupons'
404
+ * @since 2.5.4
405
+ */
406
+ function action_applied_coupon( $coupon_code ) {
407
+ if ( ! $this->is_auto_coupon( $coupon_code ) || ! $this->get_option_autocoupon_allow_remove() ) {
408
+ return;
409
+ }
410
+
411
+ $removed_autocoupon_codes = $this->get_removed_autocoupon_codes();
412
+ if ( ! isset( $removed_autocoupon_codes[$coupon_code] ) ) {
413
+ return;
414
+ }
415
+
416
+ unset( $removed_autocoupon_codes[$coupon_code] ) ;
417
+ $this->set_removed_autocoupon_codes( $removed_autocoupon_codes );
418
+ }
419
+
420
+ /**
421
+ * Add the coupon to session 'wjecf_autocoupon_removed_coupons'
422
+ *
423
+ * @since 2.5.4
424
+ */
425
+ function action_removed_coupon( $coupon_code ) {
426
+ if ( ! $this->is_auto_coupon( $coupon_code ) || ! $this->get_option_autocoupon_allow_remove() ) {
427
+ return;
428
+ }
429
+ //Ignore, because the auto-coupon was removed automatically (not manually by the customer)
430
+ if ( isset( $this->ignore_removal[$coupon_code] ) ) {
431
+ return;
432
+ }
433
+
434
+ $removed_autocoupon_codes = $this->get_removed_autocoupon_codes();
435
+ $removed_autocoupon_codes[$coupon_code] = $coupon_code;
436
+ $this->set_removed_autocoupon_codes( $removed_autocoupon_codes );
437
+ }
438
+
439
+ /**
440
+ * Remove 'wjecf_autocoupon_removed_coupons' from the session when cart is emptied
441
+ *
442
+ * @since 2.5.4
443
+ */
444
+ function action_cart_emptied() {
445
+ $this->set_removed_autocoupon_codes( null );
446
+ }
447
+
448
+ /**
449
+ * Get the removed auto coupon-codes from the session
450
+ *
451
+ * @since 2.5.4
452
+ * @return array The queued coupon codes
453
+ */
454
+ private function get_removed_autocoupon_codes( ) {
455
+ $coupon_codes = WC()->session->get( 'wjecf_autocoupon_removed_coupons' , array() );
456
+ return $coupon_codes;
457
+ }
458
+
459
+ /**
460
+ * Save the removed auto coupon-codes in the session
461
+ *
462
+ * @since 2.5.4
463
+ * @param array $coupon_codes
464
+ * @return void
465
+ */
466
+ private function set_removed_autocoupon_codes( $coupon_codes ) {
467
+ WC()->session->set( 'wjecf_autocoupon_removed_coupons' , $coupon_codes );
468
+ }
469
+
470
+ /**
471
+ * Reads the option 'autocoupon_allow_remove'
472
+ *
473
+ * @since 2.5.4
474
+ * @return bool
475
+ */
476
+ private function get_option_autocoupon_allow_remove() {
477
+ return WJECF()->is_pro() && WJECF()->get_option( 'autocoupon_allow_remove', false );
478
+ }
479
+
480
+ // ============ END wjecf_autocoupon_removed_coupons =======================
481
+
482
+
483
+ /**
484
+ * Apply matched autocoupons and remove unmatched autocoupons.
485
+ * @return void
486
+ */
487
+ function update_matched_autocoupons() {
488
+
489
+ //2.3.3 Keep track of queued coupons and apply when they validate
490
+ $queuer = WJECF()->get_plugin('WJECF_Pro_Coupon_Queueing');
491
+ if ( $queuer !== false ) {
492
+ $queuer->apply_valid_queued_coupons();
493
+ }
494
+
495
+ //Get the coupons that should be in the cart
496
+ $valid_coupons = $this->get_valid_auto_coupons();
497
+ $valid_coupons = $this->individual_use_filter( $valid_coupons );
498
+
499
+ $valid_coupon_codes = array();
500
+ foreach ( $valid_coupons as $coupon ) {
501
+ $valid_coupon_codes[] = WJECF_Wrap( $coupon )->get_code();
502
+ }
503
+
504
+ $this->log( 'debug', sprintf( "Auto coupons that should be in cart: %s", implode( ', ', $valid_coupon_codes ) ) );
505
+
506
+ $calc_needed = $this->remove_unmatched_autocoupons( $valid_coupon_codes );
507
+
508
+ //Add valids
509
+ foreach( $valid_coupons as $coupon ) {
510
+ $coupon_code = WJECF_Wrap( $coupon )->get_code();
511
+ if ( ! WC()->cart->has_discount( $coupon_code ) ) {
512
+ $this->log( 'debug', sprintf( "Applying auto coupon %s", $coupon_code ) );
513
+
514
+ $apply_silently = WJECF_Wrap( $coupon )->get_meta( '_wjecf_apply_silently' ) == 'yes';
515
+
516
+ if ( $apply_silently ) {
517
+ $new_succss_msg = ''; // no message
518
+ } else {
519
+ $coupon_excerpt = WJECF_Wrap( $coupon )->get_description();
520
+ $new_succss_msg = sprintf(
521
+ __("Discount applied: %s", 'woocommerce-jos-autocoupon'),
522
+ __( empty( $coupon_excerpt ) ? $coupon_code : $coupon_excerpt, 'woocommerce-jos-autocoupon')
523
+ );
524
+ }
525
+
526
+ WJECF()->start_overwrite_success_message( $coupon, $new_succss_msg );
527
+ WC()->cart->add_discount( $coupon_code ); //Causes calculation and will remove other coupons if it's a individual coupon
528
+ WJECF()->stop_overwrite_success_message();
529
+
530
+ $calc_needed = false; //Already done by adding the discount
531
+
532
+ }
533
+ }
534
+
535
+ $this->log( 'debug', 'Coupons in cart: ' . implode( ', ', WC()->cart->applied_coupons ) . ($calc_needed ? ". RECALC" : "") );
536
+
537
+ if ( $calc_needed ) {
538
+ WC()->cart->calculate_totals();
539
+ }
540
+
541
+ }
542
+
543
+ private function get_valid_auto_coupons( ) {
544
+ $valid_coupons = array();
545
+
546
+ //Array will only have values if option autocoupon_allow_remove == true
547
+ $removed_autocoupon_codes = $this->get_option_autocoupon_allow_remove() ? $this->get_removed_autocoupon_codes() : array();
548
+
549
+ foreach ( $this->get_all_auto_coupons() as $coupon_code => $coupon ) {
550
+ if ( isset( $removed_autocoupon_codes[ $coupon_code ] ) ) continue;
551
+ if ( ! $this->coupon_can_be_applied( $coupon ) ) continue;
552
+ if ( ! $this->coupon_has_a_value( $coupon ) ) continue;
553
+
554
+ $valid_coupons[] = $coupon;
555
+ }
556
+ return $valid_coupons;
557
+ }
558
+
559
+ /**
560
+ * Test whether the coupon is valid (to be auto-applied)
561
+ * @return bool
562
+ */
563
+ private function coupon_can_be_applied( $coupon ) {
564
+ $wrap_coupon = WJECF_Wrap( $coupon );
565
+ $can_be_applied = true;
566
+
567
+ //Test validity
568
+ if ( ! $coupon->is_valid() ) {
569
+ $can_be_applied = false;
570
+ }
571
+ else if ( $can_be_applied && is_array( $wrap_coupon->get_email_restrictions() ) && sizeof( $wrap_coupon->get_email_restrictions() ) > 0 ) {
572
+ //Test restricted emails
573
+ //See WooCommerce: class-wc-cart.php function check_customer_coupons
574
+ $user_emails = array_map( 'sanitize_email', array_map( 'strtolower', WJECF()->get_user_emails() ) );
575
+ $coupon_emails = array_map( 'sanitize_email', array_map( 'strtolower', $wrap_coupon->get_email_restrictions() ) );
576
+
577
+ if ( 0 == sizeof( array_intersect( $user_emails, $coupon_emails ) ) ) {
578
+ $can_be_applied = false;
579
+ }
580
+
581
+ $this->log( sprintf( 'Coupon emails: [ %s ] User emails: [ %s ] Match: %s',
582
+ join( ', ', $coupon_emails ),
583
+ join( ', ', $user_emails ),
584
+ $can_be_applied ? 'yes' : 'no'
585
+ ) );
586
+ }
587
+ return apply_filters( 'wjecf_coupon_can_be_applied', $can_be_applied, $coupon );
588
+
589
+ }
590
+
591
+ /**
592
+ * Does the coupon have a value? (autocoupon should not be applied if it has no value)
593
+ * @param WC_Coupon $coupon The coupon data
594
+ * @return bool True if it has a value (discount, free shipping, whatever) otherwise false)
595
+ **/
596
+ function coupon_has_a_value( $coupon ) {
597
+
598
+ $has_a_value = false;
599
+
600
+ if ( $coupon->is_type('free_gift') ) { // 'WooCommerce Free Gift Coupons'-plugin
601
+ $has_a_value = true;
602
+ }
603
+ elseif ( WJECF_Wrap( $coupon )->get_free_shipping() ) {
604
+ $has_a_value = true;
605
+ }
606
+ else {
607
+ //Test whether discount > 0
608
+ //See WooCommerce: class-wc-cart.php function get_discounted_price
609
+ global $woocommerce;
610
+ foreach ( $woocommerce->cart->get_cart() as $cart_item ) {
611
+ if ( $coupon->is_valid_for_cart() || $coupon->is_valid_for_product( $cart_item['data'], $cart_item ) ) {
612
+ $has_a_value = WJECF_Wrap( $coupon )->get_amount() > 0;
613
+ break;
614
+ }
615
+ }
616
+ }
617
+
618
+ return apply_filters( 'wjecf_coupon_has_a_value', $has_a_value, $coupon );
619
+ }
620
+
621
+
622
+
623
+ /**
624
+ * Check wether the coupon is an "Auto coupon".
625
+ * @param WC_Coupon $coupon The coupon data
626
+ * @return bool true if it is an "Auto coupon"
627
+ */
628
+ public function is_auto_coupon( $coupon ) {
629
+ return WJECF_Wrap( $coupon )->get_meta( '_wjecf_is_auto_coupon' ) == 'yes';
630
+ }
631
+
632
+ private function get_coupon_priority($coupon) {
633
+ if ( WJECF()->is_pro() ) {
634
+ $prio = WJECF_Wrap( $coupon )->get_meta( '_wjecf_coupon_priority' );
635
+ if ( ! empty( $prio ) ) {
636
+ return intval( $prio );
637
+ }
638
+ }
639
+ return 0;
640
+ }
641
+
642
+
643
+
644
+ /**
645
+ * Return an array of WC_Coupons with coupons that shouldn't cause individual use conflicts
646
+ */
647
+ private function individual_use_filter( $valid_auto_coupons ) {
648
+ $filtered = array();
649
+
650
+ //Any individual use non-autocoupons in the cart?
651
+ foreach ( WC()->cart->get_applied_coupons() as $coupon_code ) {
652
+ $coupon = new WC_Coupon( $coupon_code );
653
+ if ( WJECF_Wrap( $coupon )->get_individual_use() && ! $this->is_auto_coupon( $coupon ) ) {
654
+ return $filtered; //Don't allow any auto coupon
655
+ }
656
+ }
657
+ foreach ( $valid_auto_coupons as $coupon ) {
658
+ if ( ! WJECF_Wrap( $coupon )->get_individual_use() || empty( $filtered ) ) {
659
+ $filtered[] = $coupon;
660
+ if ( WJECF_Wrap( $coupon )->get_individual_use() ) {
661
+ break;
662
+ }
663
+ }
664
+ }
665
+ return $filtered;
666
+ }
667
+
668
+ /**
669
+ * Get an array of all auto coupons [ $coupon_code => $coupon ]
670
+ * @return array All auto coupon codes
671
+ */
672
+ public function get_all_auto_coupons() {
673
+ if ( ! is_array( $this->_autocoupons ) ) {
674
+ $this->_autocoupons = array();
675
+
676
+ $query_args = array(
677
+ 'posts_per_page' => -1,
678
+ 'post_type' => 'shop_coupon',
679
+ 'post_status' => 'publish',
680
+ 'orderby' => array( 'title' => 'ASC' ),
681
+ 'meta_query' => array(
682
+ array(
683
+ 'key' => '_wjecf_is_auto_coupon',
684
+ 'compare' => '=',
685
+ 'value' => 'yes',
686
+ ),
687
+ )
688
+ );
689
+
690
+ $query = new WP_Query($query_args);
691
+ foreach ($query->posts as $post) {
692
+ $coupon = new WC_Coupon($post->post_title);
693
+ if ( $this->is_auto_coupon($coupon) ) {
694
+ $this->_autocoupons[ WJECF_Wrap( $coupon )->get_code() ] = $coupon;
695
+ }
696
+ }
697
+
698
+ //Sort by priority
699
+ @uasort( $this->_autocoupons , array( $this, 'sort_auto_coupons' ) ); //Ignore error PHP Bug #50688
700
+
701
+ $coupon_codes = array();
702
+ foreach( $this->_autocoupons as $coupon ) {
703
+ $coupon_codes[] = WJECF_Wrap( $coupon )->get_code();
704
+ }
705
+
706
+ $this->log( 'debug', "Autocoupons: " . implode(", ", $coupon_codes ) );
707
+ }
708
+
709
+ return $this->_autocoupons;
710
+ }
711
+
712
+ /**
713
+ * Compare function to sort coupons by priority
714
+ * @param type $a
715
+ * @param type $b
716
+ * @return type
717
+ */
718
+ private function sort_auto_coupons( $coupon_a, $coupon_b ) {
719
+ $prio_a = $this->get_coupon_priority( $coupon_a );
720
+ $prio_b = $this->get_coupon_priority( $coupon_b );
721
+ //$this->log( 'debug', "A: $prio_a B: $prio_b " );
722
+ if ( $prio_a == $prio_b ) {
723
+ return WJECF_Wrap( $coupon_a )->get_code() < WJECF_Wrap( $coupon_b )->get_code() ? -1 : 1; //By title ASC
724
+ } else {
725
+ return $prio_a > $prio_b ? -1 : 1; //By prio DESC
726
+ }
727
+ }
728
+ }
includes/WJECF_Bootstrap.php CHANGED
@@ -1,130 +1,130 @@
1
- <?php
2
-
3
- if ( ! defined('ABSPATH') ) die();
4
-
5
- /**
6
- * Loads the plugin
7
- */
8
- class WJECF_Bootstrap {
9
-
10
- public static function execute() {
11
- self::instance()->bootstrap();
12
- }
13
-
14
- protected function bootstrap() {
15
-
16
- $this->require_once_php( 'Abstract_WJECF_Plugin' );
17
- $this->require_once_php( 'WJECF_Options' );
18
- $this->require_once_php( 'WJECF_Sanitizer' );
19
-
20
- $this->require_once_php( 'WJECF_Controller' );
21
- $pro = $this->try_include_php( 'pro/WJECF_Pro_Controller' );
22
- $this->require_once_php( 'WJECF_WC' );
23
- $this->require_once_php( 'WJECF_Wrap' );
24
-
25
- $this->add_plugin( 'WJECF_Debug' );
26
- $this->require_once_php( 'admin/WJECF_Admin_Html' );
27
- $this->add_plugin('admin/WJECF_Admin');
28
- $this->try_add_plugin('admin/WJECF_Admin_Data_Update');
29
-
30
- $this->try_add_plugin('admin/WJECF_Admin_Settings');
31
- $this->try_add_plugin('WJECF_AutoCoupon');
32
- $this->try_add_plugin('WJECF_WPML');
33
-
34
- if ( $pro ) {
35
- $this->try_include_php( 'pro/WJECF_Pro_API' );
36
- $this->try_add_plugin('pro/admin/WJECF_Pro_Admin_Auto_Update');
37
-
38
- $this->try_add_plugin('pro/WJECF_Pro_Free_Products/WJECF_Pro_Free_Products');
39
- $this->try_add_plugin('pro/WJECF_Pro_Coupon_Queueing');
40
- $this->try_add_plugin('pro/WJECF_Pro_Product_Filter');
41
- $this->try_add_plugin('pro/WJECF_Pro_Limit_Discount_Quantities');
42
- }
43
-
44
- WJECF()->start();
45
-
46
- //WP-cli for debugging
47
- if ( defined( 'WP_CLI' ) && WP_CLI ) {
48
- if ( $this->try_include_php( 'WJECF_Debug_CLI' ) ) {
49
- WJECF_Debug_CLI::add_command();
50
- }
51
- }
52
-
53
- }
54
-
55
- /**
56
- * Singleton Instance
57
- *
58
- * @static
59
- * @return Singleton Instance
60
- */
61
- public static function instance() {
62
- if ( is_null( self::$_instance ) ) {
63
- self::$_instance = new self();
64
- }
65
- return self::$_instance;
66
- }
67
- protected static $_instance = null;
68
-
69
-
70
- /**
71
- * require_once( $path + '.php' )
72
- * @param string $path Path to the php file (excluding extension) relative to the current path
73
- * @return bool True if succesful
74
- */
75
- protected function require_once_php( $path ) {
76
- $fullpath = $this->get_path( $path ) . '.php';
77
- require_once( $fullpath );
78
- }
79
-
80
- /**
81
- * tries to include_once( $path + '.php' )
82
- * @param string $path Path to the php file (excluding extension) relative to the current path
83
- * @return bool True if succesful
84
- */
85
- protected function try_include_php( $path ) {
86
- $fullpath = $this->get_path( $path ) . '.php';
87
-
88
- if ( ! file_exists( $fullpath ) ) {
89
- return false;
90
- }
91
-
92
- include_once( $fullpath );
93
- return true;
94
- }
95
-
96
- /**
97
- * Loads the class file and adds the plugin to WJECF(). Throws exception on failure.
98
- * @param string $path Path to the php file (excluding extension) relative to the current path
99
- * @return void
100
- */
101
- protected function add_plugin( $path ) {
102
- $class_name = basename( $path );
103
-
104
- $this->require_once_php( $path );
105
-
106
- if ( ! WJECF()->add_plugin( $class_name ) ) {
107
- throw new Exception( sprintf( 'Unable to add plugin %s', $class_name ) );
108
- }
109
- }
110
-
111
- /**
112
- * Tries loading the class file and adding the plugin to WJECF().
113
- * @param string $path Path to the php file (excluding extension) relative to the current path
114
- * @return bool True if succesful
115
- */
116
- protected function try_add_plugin( $path ) {
117
- $class_name = basename( $path );
118
- return $this->try_include_php( $path ) && WJECF()->add_plugin( $class_name );
119
- }
120
-
121
- /**
122
- * Gets the path relative to the includes/ directory
123
- * @param string $path
124
- * @return string
125
- */
126
- private function get_path( $path ) {
127
- return plugin_dir_path( __FILE__ ) . $path;
128
- }
129
-
130
  }
1
+ <?php
2
+
3
+ if ( ! defined('ABSPATH') ) die();
4
+
5
+ /**
6
+ * Loads the plugin
7
+ */
8
+ class WJECF_Bootstrap {
9
+
10
+ public static function execute() {
11
+ self::instance()->bootstrap();
12
+ }
13
+
14
+ protected function bootstrap() {
15
+
16
+ $this->require_once_php( 'Abstract_WJECF_Plugin' );
17
+ $this->require_once_php( 'WJECF_Options' );
18
+ $this->require_once_php( 'WJECF_Sanitizer' );
19
+
20
+ $this->require_once_php( 'WJECF_Controller' );
21
+ $pro = $this->try_include_php( 'pro/WJECF_Pro_Controller' );
22
+ $this->require_once_php( 'WJECF_WC' );
23
+ $this->require_once_php( 'WJECF_Wrap' );
24
+
25
+ $this->add_plugin( 'WJECF_Debug' );
26
+ $this->require_once_php( 'admin/WJECF_Admin_Html' );
27
+ $this->add_plugin('admin/WJECF_Admin');
28
+ $this->try_add_plugin('admin/WJECF_Admin_Data_Update');
29
+
30
+ $this->try_add_plugin('admin/WJECF_Admin_Settings');
31
+ $this->try_add_plugin('WJECF_AutoCoupon');
32
+ $this->try_add_plugin('WJECF_WPML');
33
+
34
+ if ( $pro ) {
35
+ $this->try_include_php( 'pro/WJECF_Pro_API' );
36
+ $this->try_add_plugin('pro/admin/WJECF_Pro_Admin_Auto_Update');
37
+
38
+ $this->try_add_plugin('pro/WJECF_Pro_Free_Products/WJECF_Pro_Free_Products');
39
+ $this->try_add_plugin('pro/WJECF_Pro_Coupon_Queueing');
40
+ $this->try_add_plugin('pro/WJECF_Pro_Product_Filter');
41
+ $this->try_add_plugin('pro/WJECF_Pro_Limit_Discount_Quantities');
42
+ }
43
+
44
+ WJECF()->start();
45
+
46
+ //WP-cli for debugging
47
+ if ( defined( 'WP_CLI' ) && WP_CLI ) {
48
+ if ( $this->try_include_php( 'WJECF_Debug_CLI' ) ) {
49
+ WJECF_Debug_CLI::add_command();
50
+ }
51
+ }
52
+
53
+ }
54
+
55
+ /**
56
+ * Singleton Instance
57
+ *
58
+ * @static
59
+ * @return Singleton Instance
60
+ */
61
+ public static function instance() {
62
+ if ( is_null( self::$_instance ) ) {
63
+ self::$_instance = new self();
64
+ }
65
+ return self::$_instance;
66
+ }
67
+ protected static $_instance = null;
68
+
69
+
70
+ /**
71
+ * require_once( $path + '.php' )
72
+ * @param string $path Path to the php file (excluding extension) relative to the current path
73
+ * @return bool True if succesful
74
+ */
75
+ protected function require_once_php( $path ) {
76
+ $fullpath = $this->get_path( $path ) . '.php';
77
+ require_once( $fullpath );
78
+ }
79
+
80
+ /**
81
+ * tries to include_once( $path + '.php' )
82
+ * @param string $path Path to the php file (excluding extension) relative to the current path
83
+ * @return bool True if succesful
84
+ */
85
+ protected function try_include_php( $path ) {
86
+ $fullpath = $this->get_path( $path ) . '.php';
87
+
88
+ if ( ! file_exists( $fullpath ) ) {
89
+ return false;
90
+ }
91
+
92
+ include_once( $fullpath );
93
+ return true;
94
+ }
95
+
96
+ /**
97
+ * Loads the class file and adds the plugin to WJECF(). Throws exception on failure.
98
+ * @param string $path Path to the php file (excluding extension) relative to the current path
99
+ * @return void
100
+ */
101
+ protected function add_plugin( $path ) {
102
+ $class_name = basename( $path );
103
+
104
+ $this->require_once_php( $path );
105
+
106
+ if ( ! WJECF()->add_plugin( $class_name ) ) {
107
+ throw new Exception( sprintf( 'Unable to add plugin %s', $class_name ) );
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Tries loading the class file and adding the plugin to WJECF().
113
+ * @param string $path Path to the php file (excluding extension) relative to the current path
114
+ * @return bool True if succesful
115
+ */
116
+ protected function try_add_plugin( $path ) {
117
+ $class_name = basename( $path );
118
+ return $this->try_include_php( $path ) && WJECF()->add_plugin( $class_name );
119
+ }
120
+
121
+ /**
122
+ * Gets the path relative to the includes/ directory
123
+ * @param string $path
124
+ * @return string
125
+ */
126
+ private function get_path( $path ) {
127
+ return plugin_dir_path( __FILE__ ) . $path;
128
+ }
129
+
130
  }
includes/WJECF_Controller.php CHANGED
@@ -1,970 +1,1001 @@
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
- // Coupon message codes
11
- //NOTE: I use prefix 79 for this plugin; there's no guarantee that other plugins don't use the same values!
12
- const E_WC_COUPON_MIN_MATCHING_SUBTOTAL_NOT_MET = 79100;
13
- const E_WC_COUPON_MAX_MATCHING_SUBTOTAL_NOT_MET = 79101;
14
- const E_WC_COUPON_MIN_MATCHING_QUANTITY_NOT_MET = 79102;
15
- const E_WC_COUPON_MAX_MATCHING_QUANTITY_NOT_MET = 79103;
16
- const E_WC_COUPON_SHIPPING_METHOD_NOT_MET = 79104;
17
- const E_WC_COUPON_PAYMENT_METHOD_NOT_MET = 79105;
18
- const E_WC_COUPON_NOT_FOR_THIS_USER = 79106;
19
- const E_WC_COUPON_FIRST_PURCHASE_ONLY = 79107;
20
-
21
- private $options = null;
22
- private $_user_emails = null;
23
-
24
- /**
25
- * Singleton Instance
26
- *
27
- * @static
28
- * @return Singleton Instance
29
- */
30
- public static function instance() {
31
- if ( is_null( self::$_instance ) ) {
32
- self::$_instance = new self();
33
- }
34
- return self::$_instance;
35
- }
36
- protected static $_instance = null;
37
-
38
-
39
- public function __construct() {
40
- $this->options = new WJECF_Options(
41
- 'wjecf_options',
42
- array(
43
- 'db_version' => 0, // integer
44
- 'debug_mode' => false, // true or false
45
- 'disabled_plugins' => array(), // e.g. [ 'WJECF_AutoCoupon' ]
46
- 'autocoupon_allow_remove' => false
47
- )
48
- );
49
- }
50
-
51
- public function start() {
52
- add_action('init', array( $this, 'init_hook' ));
53
- }
54
-
55
- public function init_hook() {
56
- if ( ! class_exists('WC_Coupon') ) {
57
- add_action( 'admin_notices', array( $this, 'admin_notice_woocommerce_not_found' ) );
58
- return;
59
- }
60
-
61
- $this->controller_init();
62
-
63
- /**
64
- * Fires before the WJECF plugins are initialised.
65
- *
66
- * Perfect hook for themes or plugins to load custom WJECF plugins.
67
- *
68
- * @since 2.3.7
69
- **/
70
- do_action( 'wjecf_init_plugins');
71
-
72
- //Start the plugins
73
- foreach ( WJECF()->get_plugins() as $name => $plugin ) {
74
- if ( $plugin->plugin_is_enabled() ) {
75
-
76
- foreach( $plugin->get_plugin_dependencies() as $dependency_name ) {
77
- $dependency = $this->get_plugin( $dependency_name );
78
- if ( ! $dependency || ! $dependency->plugin_is_enabled() ) {
79
- $this->log( 'error', 'Unable to initialize ' . $name . ' because it requires ' . $dependency_name );
80
- continue;
81
- }
82
- }
83
-
84
- $plugin->init_hook();
85
- if ( is_admin() ) {
86
- $plugin->init_admin_hook();
87
- }
88
- }
89
- }
90
- }
91
-
92
- public function controller_init() {
93
-
94
- $this->log( 'debug', "INIT " . ( is_ajax() ? "AJAX" : is_admin() ? "ADMIN" : "FRONTEND" ) . " " . $_SERVER['REQUEST_URI'] );
95
-
96
- //Frontend hooks
97
-
98
- //assert_coupon_is_valid (which raises exception on invalid coupon) can only be used on WC 2.3.0 and up
99
- if ( WJECF_WC()->check_woocommerce_version('2.3.0') ) {
100
- add_filter('woocommerce_coupon_is_valid', array( $this, 'assert_coupon_is_valid' ), 10, 3 ); //Since WC3.2 WC_Discounts is passed as a 3rd argument
101
- } else {
102
- add_filter('woocommerce_coupon_is_valid', array( $this, 'coupon_is_valid' ), 10, 2 );
103
- }
104
-
105
- //Last check for coupons with restricted_emails (moved from WJECF_AutoCoupon since 2.5.6)
106
- add_action( 'woocommerce_checkout_update_order_review', array( $this, 'fetch_billing_email' ), 10 ); // AJAX One page checkout
107
- add_action( 'woocommerce_after_checkout_validation', array( $this, 'fetch_billing_email' ), 10 ); // Checkout posted
108
-
109
- add_filter('woocommerce_coupon_error', array( $this, 'woocommerce_coupon_error' ), 10, 3 );
110
- }
111
-
112
- protected $plugins = array();
113
-
114
- /**
115
- * Load a WJECF Plugin (class name)
116
- * @param string $class_name The class name of the plugin
117
- * @return bool True if succeeded, otherwise false
118
- */
119
- public function add_plugin( $class_name ) {
120
- if ( isset( $this->plugins[ $class_name ] ) ) {
121
- return false; //Already loaded
122
- }
123
-
124
- if ( ! class_exists( $class_name ) ) {
125
- $this->log( 'warning', 'Unknown plugin: ' . $class_name );
126
- return false; //Not found
127
- }
128
-
129
- $plugin = new $class_name();
130
- foreach( $plugin->get_plugin_dependencies() as $dependency ) {
131
- if ( ! class_exists( $dependency ) ) {
132
- $this->log( 'warning', 'Unknown dependency: ' . $dependency . ' for plugin ' . $class_name );
133
- return false;
134
- }
135
-
136
- if ( isset( $this->plugins[ $class_name ] ) ) {
137
- continue; //dependency is al geladen
138
- }
139
-
140
- $this->add_plugin( $dependency );
141
- }
142
-
143
- //Assert dependencies
144
- try {
145
- $plugin->assert_dependencies();
146
- } catch (Exception $ex) {
147
- $msg = sprintf('Failed loading %s. %s', $class_name, $ex->getMessage() );
148
-
149
- $this->log( 'error', $msg );
150
-
151
- if ( $wjecf_admin = WJECF()->get_plugin('WJECF_Admin') ) {
152
- $wjecf_admin->enqueue_notice( $msg, 'error' );
153
- }
154
- return false;
155
- }
156
-
157
-
158
- $this->plugins[ $class_name ] = $plugin;
159
- $this->log( 'debug', 'Loaded plugin: ' . $class_name );
160
-
161
- return true;
162
- }
163
-
164
-
165
- /**
166
- * Description
167
- * @param type $plugin
168
- * @return bool true if succesful
169
- */
170
- private function load_dependencies( $plugin ) {
171
-
172
- return true;
173
- }
174
-
175
- public function get_plugins() {
176
- return $this->plugins;
177
- }
178
-
179
- /**
180
- * Retrieves the WJECF Plugin
181
- * @param string $class_name
182
- * @return object|bool The plugin if found, otherwise returns false
183
- */
184
- public function get_plugin( $class_name ) {
185
- if ( isset( $this->plugins[ $class_name ] ) ) {
186
- return $this->plugins[ $class_name ];
187
- } else {
188
- return false;
189
- }
190
- }
191
-
192
- /* OPTIONS */
193
-
194
- public function get_options() {
195
- return $this->options->get();
196
- }
197
-
198
- public function get_option( $key, $default = null ) {
199
- return $this->options->get( $key, $default );
200
- }
201
-
202
- public function set_option( $key, $value ) {
203
- $this->options->set( $key, $value );
204
- }
205
-
206
- public function save_options() {
207
- if ( ! is_admin() ) {
208
- $this->log( 'error', 'WJECF Options must only be saved from admin.' );
209
- return;
210
- }
211
- $this->options->save();
212
- }
213
-
214
- public function sanitizer() {
215
- return WJECF_Sanitizer::instance();
216
- }
217
-
218
- /**
219
- * Same as WordPress add_action(), but prevents the callback to be recursively called
220
- *
221
- * @param string $tag
222
- * @param callable $function_to_add
223
- * @param int $priority
224
- * @param int $accepted_args
225
- */
226
- public function safe_add_action( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
227
- $_RECURSION_LIMIT = 5;
228
- WJECF_Action_Or_Filter::action( $tag, $function_to_add, $priority, $accepted_args, $_RECURSION_LIMIT );
229
- }
230
-
231
- /* FRONTEND HOOKS */
232
-
233
- /**
234
- * Notifies that WooCommerce has not been detected.
235
- * @return void
236
- */
237
- public function admin_notice_woocommerce_not_found() {
238
- $msg = __( 'WooCommerce Extended Coupon Features is disabled because WooCommerce could not be detected.', 'woocommerce-jos-autocoupon' );
239
- echo '<div class="error"><p>' . $msg . '</p></div>';
240
- }
241
-
242
- /**
243
- * Overwrite coupon error message, if $err_code is an error code of this plugin
244
- * @param string $err Original error message
245
- * @param int $err_code Error code
246
- * @param WC_Coupon $coupon The coupon
247
- * @return string Overwritten error message
248
- */
249
- public function woocommerce_coupon_error( $err, $err_code, $coupon ) {
250
- switch ( $err_code ) {
251
- case self::E_WC_COUPON_MIN_MATCHING_SUBTOTAL_NOT_MET:
252
- $min_price = wc_price( WJECF_Wrap( $coupon )->get_meta( '_wjecf_min_matching_product_subtotal' ) );
253
- $err = sprintf( __( 'The minimum subtotal of the matching products for this coupon is %s.', 'woocommerce-jos-autocoupon' ), $min_price );
254
- break;
255
- case self::E_WC_COUPON_MAX_MATCHING_SUBTOTAL_NOT_MET:
256
- $max_price = wc_price( WJECF_Wrap( $coupon )->get_meta( '_wjecf_max_matching_product_subtotal' ) );
257
- $err = sprintf( __( 'The maximum subtotal of the matching products for this coupon is %s.', 'woocommerce-jos-autocoupon' ), $max_price );
258
- break;
259
- case self::E_WC_COUPON_MIN_MATCHING_QUANTITY_NOT_MET:
260
- $min_matching_product_qty = intval( WJECF_Wrap( $coupon )->get_meta( '_wjecf_min_matching_product_qty' ) );
261
- $err = sprintf( __( 'The minimum quantity of matching products for this coupon is %s.', 'woocommerce-jos-autocoupon' ), $min_matching_product_qty );
262
- break;
263
- case self::E_WC_COUPON_MAX_MATCHING_QUANTITY_NOT_MET:
264
- $max_matching_product_qty = intval( WJECF_Wrap( $coupon )->get_meta( '_wjecf_min_matching_product_qty' ) );
265
- $err = sprintf( __( 'The maximum quantity of matching products for this coupon is %s.', 'woocommerce-jos-autocoupon' ), $max_matching_product_qty );
266
- break;
267
- case self::E_WC_COUPON_SHIPPING_METHOD_NOT_MET:
268
- $err = __( 'The coupon is not valid for the currently selected shipping method.', 'woocommerce-jos-autocoupon' );
269
- break;
270
- case self::E_WC_COUPON_PAYMENT_METHOD_NOT_MET:
271
- $err = __( 'The coupon is not valid for the currently selected payment method.', 'woocommerce-jos-autocoupon' );
272
- break;
273
- case self::E_WC_COUPON_NOT_FOR_THIS_USER:
274
- $err = sprintf( __( 'Sorry, it seems the coupon "%s" is not yours.', 'woocommerce-jos-autocoupon' ), WJECF_Wrap( $coupon )->get_code() );
275
- break;
276
- case self::E_WC_COUPON_FIRST_PURCHASE_ONLY:
277
- $err = sprintf( __( 'Sorry, coupon "%s" is only valid on your first purchase.', 'woocommerce-jos-autocoupon' ), WJECF_Wrap( $coupon )->get_code() );
278
- break;
279
- default:
280
- //Do nothing
281
- break;
282
- }
283
- return $err;
284
- }
285
-
286
- /**
287
- * Extra validation rules for coupons.
288
- * @param bool $valid
289
- * @param WC_Coupon $coupon
290
- * @param WC_Discounts $discounts
291
- * @return bool True if valid; False if not valid.
292
- */
293
- public function coupon_is_valid( $valid, $coupon, $wc_discounts = null ) {
294
- try {
295
- return $this->assert_coupon_is_valid( $valid, $coupon, $wc_discounts );
296
- } catch ( Exception $e ) {
297
- return false;
298
- }
299
- }
300
-
301
- /**
302
- * Extra validation rules for coupons. Throw an exception when not valid.
303
- * @param bool $valid
304
- * @param WC_Coupon $coupon
305
- * @param WC_Discounts $discounts
306
- * @return bool True if valid; False if already invalid on function call. In any other case an Exception will be thrown.
307
- */
308
- public function assert_coupon_is_valid( $valid, $coupon, $wc_discounts = null ) {
309
-
310
- //Not valid? Then it will never validate, so get out of here
311
- if ( ! $valid ) {
312
- return false;
313
- }
314
-
315
- $wrap_coupon = WJECF_Wrap( $coupon );
316
- $items = WJECF_WC()->get_discount_items( $wc_discounts );
317
-
318
- //$this->log( 'debug', 'Asserting validity of coupon: ' . $wrap_coupon->get_code() );
319
-
320
- //============================
321
- //Test if ALL products are in the cart (if AND-operator selected instead of the default OR)
322
- $products_and = $wrap_coupon->get_meta( '_wjecf_products_and' ) == 'yes';
323
- if ( $products_and && sizeof( $wrap_coupon->get_product_ids() ) > 1 ) { // We use > 1, because if size == 1, 'AND' makes no difference
324
- //Get array of all cart product and variation ids
325
- $item_ids = array();
326
-
327
- foreach( $items as $item_key => $item ) {
328
- if ( $item->product === false ) continue;
329
- $wrap_product = WJECF_Wrap( $item->product );
330
-
331
- $item_ids[] = $wrap_product->get_id();
332
- if ( $item->product->is_type( 'variation' ) ) $item_ids[] = $wrap_product->get_variable_product_id();
333
- }
334
- //Filter used by WJECF_WPML hook
335
- $item_ids = apply_filters( 'wjecf_get_product_ids', array_unique( $item_ids ) );
336
-
337
- //check if every single product is in the cart
338
- foreach( apply_filters( 'wjecf_get_product_ids', $wrap_coupon->get_product_ids() ) as $product_id ) {
339
- if ( ! in_array( $product_id, $item_ids ) ) {
340
- throw new Exception( WC_Coupon::E_WC_COUPON_NOT_APPLICABLE );
341
- }
342
- }
343
- }
344
-
345
- //============================
346
- //Test if products form ALL categories are in the cart (if AND-operator selected instead of the default OR)
347
- $categories_and = $wrap_coupon->get_meta( '_wjecf_categories_and' ) == 'yes';
348
- if ( $categories_and && sizeof( $wrap_coupon->get_product_categories() ) > 1 ) { // We use > 1, because if size == 1, 'AND' makes no difference
349
- //Get array of all cart product and variation ids
350
- $product_cats = array();
351
-
352
- foreach( $items as $item_key => $item ) {
353
- if ( $item->product === false ) continue;
354
-
355
- $product_id = WJECF_Wrap( $item->product )->get_id();
356
- if ('product_variation' == get_post_type( $product_id )) $product_id = WJECF_Wrap( $item->product )->get_variable_product_id();
357
- $product_cats = array_merge( $product_cats, wp_get_post_terms( $product_id, 'product_cat', array( "fields" => "ids" ) ) );
358
- }
359
- //Filter used by WJECF_WPML hook
360
- $product_cats = apply_filters( 'wjecf_get_product_cat_ids', $product_cats );
361
- //check if every single category is in the cart
362
- foreach( apply_filters( 'wjecf_get_product_cat_ids', $wrap_coupon->get_product_categories() ) as $cat_id ) {
363
- if ( ! in_array( $cat_id, $product_cats ) ) {
364
- $this->log( 'debug', $cat_id . " is not in " . implode( ',', $product_cats ));
365
- throw new Exception( WC_Coupon::E_WC_COUPON_NOT_APPLICABLE );
366
- }
367
- }
368
- }
369
-
370
- //============================
371
- //Test min/max quantity of matching products
372
- //
373
- //For all items in the cart:
374
- // If coupon contains both a product AND category inclusion filter: the item is counted if it matches either one of them
375
- // If coupon contains either a product OR category exclusion filter: the item will NOT be counted if it matches either one of them
376
- // If sale items are excluded by the coupon: the item will NOT be counted if it is a sale item
377
- // If no filter exist, all items will be counted
378
-
379
- $multiplier = null; //null = not initialized
380
-
381
- //Validate quantity
382
- $min_matching_product_qty = intval( $wrap_coupon->get_meta( '_wjecf_min_matching_product_qty' ) );
383
- $max_matching_product_qty = intval( $wrap_coupon->get_meta( '_wjecf_max_matching_product_qty' ) );
384
- if ( $min_matching_product_qty > 0 || $max_matching_product_qty > 0 ) {
385
- //Count the products
386
- $qty = $this->get_quantity_of_matching_products( $coupon, $wc_discounts );
387
- //$this->log( 'debug', 'Quantity of matching products: ' . $qty );
388
- if ( $min_matching_product_qty > 0 && $qty < $min_matching_product_qty ) throw new Exception( self::E_WC_COUPON_MIN_MATCHING_QUANTITY_NOT_MET );
389
- if ( $max_matching_product_qty > 0 && $qty > $max_matching_product_qty ) throw new Exception( self::E_WC_COUPON_MAX_MATCHING_QUANTITY_NOT_MET );
390
-
391
- if ( $min_matching_product_qty > 0 ) {
392
- $multiplier = self::min_value( floor( $qty / $min_matching_product_qty ), $multiplier );
393
- }
394
- }
395
-
396
- //Validate subtotal (2.2.2)
397
- $min_matching_product_subtotal = floatval( $wrap_coupon->get_meta( '_wjecf_min_matching_product_subtotal' ) );
398
- $max_matching_product_subtotal = floatval( $wrap_coupon->get_meta( '_wjecf_max_matching_product_subtotal' ) );
399
- if ( $min_matching_product_subtotal > 0 || $max_matching_product_subtotal > 0 ) {
400
- $subtotal = $this->get_subtotal_of_matching_products( $coupon, $wc_discounts );
401
- //$this->log( 'debug', 'Subtotal of matching products: ' . $subtotal );
402
- if ( $min_matching_product_subtotal > 0 && $subtotal < $min_matching_product_subtotal ) throw new Exception( self::E_WC_COUPON_MIN_MATCHING_SUBTOTAL_NOT_MET );
403
- if ( $max_matching_product_subtotal > 0 && $subtotal > $max_matching_product_subtotal ) throw new Exception( self::E_WC_COUPON_MAX_MATCHING_SUBTOTAL_NOT_MET );
404
-
405
- if ( $min_matching_product_subtotal > 0 ) {
406
- $multiplier = self::min_value( floor( $subtotal / $min_matching_product_subtotal ), $multiplier );
407
- }
408
- }
409
-
410
- //============================
411
- //Test restricted shipping methods
412
- $shipping_method_ids = $this->get_coupon_shipping_method_ids( $coupon );
413
- if ( sizeof( $shipping_method_ids ) > 0 ) {
414
- $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
415
- $chosen_shipping = empty( $chosen_shipping_methods ) ? '' : $chosen_shipping_methods[0];
416
- //$this->log('debug', 'Current shipping method: ' . ( $chosen_shipping === '' ? 'none' : $chosen_shipping ));
417
- $chosen_shipping = explode( ':', $chosen_shipping); //UPS and USPS stores extra data, seperated by colon
418
- $chosen_shipping = $chosen_shipping[0];
419
-
420
- if ( ! in_array( $chosen_shipping, $shipping_method_ids ) ) {
421
- throw new Exception( self::E_WC_COUPON_SHIPPING_METHOD_NOT_MET );
422
- }
423
- }
424
-
425
- //============================
426
- //Test restricted payment methods
427
- $payment_method_ids = $this->get_coupon_payment_method_ids( $coupon );
428
- if ( sizeof( $payment_method_ids ) > 0 ) {
429
- $chosen_payment_method = isset( WC()->session->chosen_payment_method ) ? WC()->session->chosen_payment_method : array();
430
-
431
- if ( ! in_array( $chosen_payment_method, $payment_method_ids ) ) {
432
- throw new Exception( self::E_WC_COUPON_PAYMENT_METHOD_NOT_MET );
433
- }
434
- }
435
-
436
-
437
- //============================
438
- //Test restricted user ids and roles
439
- //NOTE: If both customer id and role restrictions are provided, the coupon matches if either the id or the role matches
440
- $coupon_customer_ids = $this->get_coupon_customer_ids( $coupon );
441
- $coupon_customer_roles = $this->get_coupon_customer_roles( $coupon );
442
- if ( sizeof( $coupon_customer_ids ) > 0 || sizeof( $coupon_customer_roles ) > 0 ) {
443
- $user = wp_get_current_user();
444
-
445
- //If both fail we invalidate. Otherwise it's ok
446
- if ( ! in_array( $user->ID, $coupon_customer_ids ) && ! array_intersect( $user->roles, $coupon_customer_roles ) ) {
447
- throw new Exception( self::E_WC_COUPON_NOT_FOR_THIS_USER );
448
- }
449
- }
450
-
451
- //============================
452
- //Test excluded user roles
453
- $coupon_excluded_customer_roles = $this->get_coupon_excluded_customer_roles( $coupon );
454
- if ( sizeof( $coupon_excluded_customer_roles ) > 0 ) {
455
- $user = wp_get_current_user();
456
-
457
- //Excluded customer roles
458
- if ( array_intersect( $user->roles, $coupon_excluded_customer_roles ) ) {
459
- throw new Exception( self::E_WC_COUPON_NOT_FOR_THIS_USER );
460
- }
461
- }
462
-
463
- //We use our own filter (instead of woocommerce_coupon_is_valid) for easier compatibility management
464
- //e.g. WC prior to 2.3.0 can't handle Exceptions; while 2.3.0 and above require exceptions
465
- do_action( 'wjecf_assert_coupon_is_valid', $coupon, $wc_discounts );
466
-
467
- if ( $wrap_coupon->get_minimum_amount() ) {
468
- $multiplier = self::min_value( floor( WC()->cart->subtotal / $wrap_coupon->get_minimum_amount() ), $multiplier );
469
- }
470
-
471
-
472
- $this->coupon_multiplier_values[ $wrap_coupon->get_code() ] = $multiplier;
473
-
474
- //$this->log( 'debug', 'Coupon ' . $wrap_coupon->get_code() . ' is valid. Multiplyer value: ' . $multiplier );
475
-
476
- return true; // VALID!
477
- }
478
-
479
- /**
480
- * Return the lowest multiplier value
481
- */
482
- private static function min_value( $value, $current_multiplier_value = null ) {
483
- return ( $current_multiplier_value === null || $value < $current_multiplier_value ) ? $value : $current_multiplier_value;
484
- }
485
-
486
- /**
487
- * The amount of times the minimum spend / quantity / subtotal values are reached
488
- * @return int 1 or more if coupon is valid, otherwise 0
489
- */
490
- public function get_coupon_multiplier_value( $coupon ) {
491
- $coupon = WJECF_WC()->get_coupon( $coupon );
492
-
493
- //If coupon validation was not executed, the value is unknown
494
- if ( ! isset( $this->coupon_multiplier_values[ WJECF_Wrap( $coupon )->get_code() ] ) ) {
495
- if ( ! $this->coupon_is_valid( true, $coupon ) ) {
496
- return 0;
497
- }
498
- }
499
- $multiplier = $this->coupon_multiplier_values[ WJECF_Wrap( $coupon )->get_code() ];
500
-
501
- //error_log("get multiplier " . WJECF_Wrap( $coupon )->get_code() . " = " . $multiplier );
502
- return $multiplier;
503
- }
504
-
505
- //Temporary storage
506
- private $coupon_multiplier_values = array();
507
-
508
-
509
- /**
510
- * (API FUNCTION)
511
- * The total amount of the products in the cart that match the coupon restrictions
512
- * since 2.2.2-b3
513
- */
514
- public function get_quantity_of_matching_products( $coupon, $wc_discounts = null ) {
515
- $coupon = WJECF_WC()->get_coupon( $coupon );
516
- $items = WJECF_WC()->get_discount_items( $wc_discounts );
517
-
518
- $qty = 0;
519
- foreach( $items as $item_key => $item ) {
520
- if ( $item->product === false ) continue;
521
-
522
- if ($this->coupon_is_valid_for_product( $coupon, $item->product, $item->object ) ) {
523
- $qty += $item->quantity;
524
- }
525
- }
526
- return $qty;
527
- }
528
-
529
- /**
530
- * (API FUNCTION)
531
- * The total value of the products in the cart that match the coupon restrictions
532
- * since 2.2.2-b3
533
- */
534
- public function get_subtotal_of_matching_products( $coupon, $wc_discounts = null ) {
535
- $coupon = WJECF_WC()->get_coupon( $coupon );
536
- $items = WJECF_WC()->get_discount_items( $wc_discounts );
537
-
538
- $subtotal_precise = 0;
539
- foreach( $items as $item_key => $item ) {
540
- if ( $item->product === false ) continue;
541
-
542
- if ($this->coupon_is_valid_for_product( $coupon, $item->product, $item->object ) ) {
543
- $subtotal_precise += $item->price;
544
- }
545
- }
546
-
547
- $subtotal = WJECF_WC()->wc_remove_number_precision( $subtotal_precise );
548
- return $subtotal;
549
- }
550
-
551
- /**
552
- * (API FUNCTION)
553
- * Test if coupon is valid for the product
554
- * (this function is used to count the quantity of matching products)
555
- */
556
- public function coupon_is_valid_for_product( $coupon, $product, $values = array() ) {
557
- //Do not count the free products
558
- if ( isset( $values['_wjecf_free_product_coupon'] ) ) {
559
- return false;
560
- }
561
-
562
- //Get the original coupon, without values overwritten by WJECF
563
- $duplicate_coupon = $this->get_original_coupon( $coupon );
564
-
565
- //$coupon->is_valid_for_product() only works for fixed_product or percent_product discounts
566
- if ( ! $duplicate_coupon->is_type( WJECF_WC()->wc_get_product_coupon_types() ) ) {
567
- WJECF_Wrap( $duplicate_coupon )->set_discount_type( 'fixed_product' );
568
- }
569
-
570
- $valid = $duplicate_coupon->is_valid_for_product( $product, $values );
571
- //$this->log( sprintf("%s valid for %s? %s", WJECF_Wrap( $coupon )->get_code(), WJECF_Wrap( $product )->get_name(), $valid ? 'yes':'no' ) );
572
- return $valid;
573
- }
574
-
575
-
576
- // =====================
577
-
578
- /**
579
- * Get array of the selected shipping methods ids.
580
- * @param WC_Coupon|string $coupon The coupon code or a WC_Coupon object
581
- * @return array Id's of the shipping methods or an empty array.
582
- */
583
- public function get_coupon_shipping_method_ids( $coupon ) {
584
- $v = WJECF_Wrap( $coupon )->get_meta( '_wjecf_shipping_methods' );
585
- return is_array( $v ) ? $v : array();
586
- }
587
-
588
- /**
589
- * Get array of the selected payment method ids.
590
- * @param WC_Coupon|string $coupon The coupon code or a WC_Coupon object
591
- * @return array Id's of the payment methods or an empty array.
592
- */
593
- public function get_coupon_payment_method_ids( $coupon ) {
594
- $v = WJECF_Wrap( $coupon )->get_meta( '_wjecf_payment_methods' );
595
- return is_array( $v ) ? $v : array();
596
- }
597
-
598
- /**
599
- * Get array of the selected customer ids.
600
- * @param WC_Coupon|string $coupon The coupon code or a WC_Coupon object
601
- * @return array Id's of the customers (users) or an empty array.
602
- */
603
- public function get_coupon_customer_ids( $coupon ) {
604
- $v = WJECF_Wrap( $coupon )->get_meta( '_wjecf_customer_ids' );
605
- return WJECF()->sanitizer()->sanitize( $v, 'int[]' );
606
- }
607
-
608
- /**
609
- * Get array of the selected customer role ids.
610
- * @param WC_Coupon|string $coupon The coupon code or a WC_Coupon object
611
- * @return array Id's (string) of the customer roles or an empty array.
612
- */
613
- public function get_coupon_customer_roles( $coupon ) {
614
- $v = WJECF_Wrap( $coupon )->get_meta( '_wjecf_customer_roles' );
615
- return is_array( $v ) ? $v : array();
616
- }
617
-
618
- /**
619
- * Get array of the excluded customer role ids.
620
- * @param WC_Coupon|string $coupon The coupon code or a WC_Coupon object
621
- * @return array Id's (string) of the excluded customer roles or an empty array.
622
- */
623
- public function get_coupon_excluded_customer_roles( $coupon ) {
624
- $v = WJECF_Wrap( $coupon )->get_meta( '_wjecf_excluded_customer_roles' );
625
- return is_array( $v ) ? $v : array();
626
- }
627
-
628
-
629
- // ===========================================================================
630
- // User identification
631
- // ===========================================================================
632
-
633
- /**
634
- * Get a list of the users' known email addresses
635
- *
636
- * NOTE: Also called in AutoCoupon
637
- *
638
- * @since 2.5.6 (Moved from WJECF_AutoCoupon)
639
- */
640
- public function get_user_emails() {
641
- if ( ! is_array($this->_user_emails) ) {
642
- $this->_user_emails = array();
643
- //Email of the logged in user
644
- if ( is_user_logged_in() ) {
645
- $current_user = wp_get_current_user();
646
- $this->_user_emails[] = $current_user->user_email;
647
- }
648
- }
649
- $user_emails = $this->_user_emails;
650
-
651
- $billing_email = $this->get_session('billing_email', '');
652
- if ( is_email( $billing_email ) ) $user_emails[] = $billing_email;
653
-
654
- $user_emails = array_map( 'strtolower', $user_emails );
655
- $user_emails = array_map( 'sanitize_email', $user_emails );
656
- $user_emails = array_filter( $user_emails, 'is_email' );
657
- return array_unique( $user_emails );
658
- }
659
-
660
- /**
661
- * Called on action: woocommerce_checkout_update_order_review
662
- *
663
- * Collects billing email address from the checkout-form
664
- */
665
- public function fetch_billing_email( $post_data ) {
666
- //post_data can be an array, or a query=string&like=this
667
- if ( ! is_array( $post_data ) ) {
668
- parse_str( $post_data, $posted );
669
- } else {
670
- $posted = $post_data;
671
- }
672
-
673
- if ( isset( $posted['billing_email'] ) ) {
674
- //$this->log('debug', 'billing:' . $posted['billing_email']);
675
- WJECF()->set_session( 'billing_email', $posted['billing_email'] );
676
- }
677
- }
678
-
679
- public function is_pro() {
680
- return $this instanceof WJECF_Pro_Controller;
681
- }
682
-
683
- // ===========================================================================
684
- // START - OVERWRITE INFO MESSAGES
685
- // ===========================================================================
686
-
687
- /**
688
- * 2.3.4
689
- * If a 'Coupon applied' message is displayed by WooCommerce, replace it by another message (or no message)
690
- * @param WC_Coupon $coupon The coupon to replace the message for
691
- * @param string $new_message The new message. Set to empty string if no message must be displayed
692
- */
693
- public function start_overwrite_success_message( $coupon, $new_message = '' ) {
694
- $this->overwrite_coupon_message[ WJECF_Wrap( $coupon )->get_code() ] = array( $coupon->get_coupon_message( WC_Coupon::WC_COUPON_SUCCESS ) => $new_message );
695
- add_filter( 'woocommerce_coupon_message', array( $this, 'filter_woocommerce_coupon_message' ), 10, 3 );
696
- }
697
-
698
- /**
699
- * 2.3.4
700
- * Stop overwriting messages
701
- */
702
- public function stop_overwrite_success_message() {
703
- remove_filter( 'woocommerce_coupon_message', array( $this, 'filter_woocommerce_coupon_message' ), 10 );
704
- $this->overwrite_coupon_message = array();
705
- }
706
-
707
- private $overwrite_coupon_message = array(); /* [ 'coupon_code' => [ old_message' => 'new_message' ] ] */
708
-
709
- function filter_woocommerce_coupon_message( $msg, $msg_code, $coupon ) {
710
- if ( isset( $this->overwrite_coupon_message[ WJECF_Wrap( $coupon )->get_code() ][ $msg ] ) ) {
711
- $msg = $this->overwrite_coupon_message[ WJECF_Wrap( $coupon )->get_code() ][ $msg ];
712
- }
713
- return $msg;
714
- }
715
-
716
- // ===========================================================================
717
- // END - OVERWRITE INFO MESSAGES
718
- // ===========================================================================
719
-
720
- /**
721
- * @since 2.4.4
722
- *
723
- * Get a coupon, but inhibit the woocommerce_coupon_loaded to overwrite values.
724
- * @param WC_Coupon|string $coupon The coupon code or a WC_Coupon object
725
- * @return WC_Coupon The coupon object
726
- */
727
- public function get_original_coupon( $coupon ) {
728
- //Prevent returning the same instance
729
- if ( $coupon instanceof WC_Coupon ) {
730
- $coupon = WJECF_Wrap( $coupon )->get_code();
731
- }
732
- $this->inhibit_overwrite++;
733
- $coupon = WJECF_WC()->get_coupon( $coupon );
734
- $this->inhibit_overwrite--;
735
- return $coupon;
736
- }
737
-
738
- private $inhibit_overwrite = 0;
739
-
740
- /**
741
- * @since 2.4.4
742
- *
743
- * May coupon values be overwritten by this plugin upon load?
744
- * @return bool
745
- */
746
- public function allow_overwrite_coupon_values() {
747
- return ( $this->inhibit_overwrite == 0 ) && ! is_admin();
748
- }
749
-
750
- //============
751
-
752
- private $_session_data = null;
753
- /**
754
- * Read something from the session.
755
- *
756
- * If key is omitted; all the session data will be returned as an array
757
- *
758
- * @param string $key The key for identification
759
- * @param any $default The default value (Default: false)
760
- *
761
- * @return The saved value if found, otherwise the default value
762
- */
763
- public function get_session( $key = null, $default = false ) {
764
- if ( $this->_session_data == null ) {
765
- if ( WC()->session == null ) {
766
- $this->log( 'error', 'Trying to access WC()->session while it was not yet initialized.' );
767
- return null;
768
- }
769
- $this->_session_data = WC()->session->get( '_wjecf_session_data', array() );
770
- }
771
-
772
- if ( ! isset( $key ) ) return $this->_session_data;
773
- if ( ! isset( $this->_session_data[ $key ] ) ) return $default;
774
- return $this->_session_data[ $key ];
775
- }
776
-
777
- /**
778
- * Save something in the session
779
- *
780
- * @param string $key The key for identification
781
- * @param anything $value The value to store. Use 'null' to remove the value
782
- */
783
- public function set_session( $key, $value ) {
784
- if ( $this->_session_data == null) {
785
- if ( WC()->session == null ) {
786
- $this->log( 'error', 'Trying to access WC()->session while it was not yet initialized.' );
787
- return null;
788
- }
789
- $this->_session_data = WC()->session->get( '_wjecf_session_data', array() );
790
- }
791
- if ( is_null( $value ) ) {
792
- unset( $this->_session_data[ $key ] );
793
- } else {
794
- $this->_session_data[ $key ] = $value;
795
- }
796
-
797
- WC()->session->set( '_wjecf_session_data', $this->_session_data );
798
- }
799
-
800
- /**
801
- * (Copied from class-woocommerce.php) What type of request is this?
802
- *
803
- * @since 2.6.2
804
- * @param string $type admin, ajax, cron or frontend.
805
- * @return bool
806
- */
807
- public function is_request( $type ) {
808
- switch ( $type ) {
809
- case 'admin':
810
- return is_admin();
811
- case 'ajax':
812
- return defined( 'DOING_AJAX' );
813
- case 'cron':
814
- return defined( 'DOING_CRON' );
815
- case 'frontend':
816
- return ( ! is_admin() || defined( 'DOING_AJAX' ) ) && ! defined( 'DOING_CRON' );
817
- }
818
- }
819
-
820
- /**
821
- * Get overwritable template filename
822
- *
823
- * Template can be overwritten in wp-content/themes/YOUR_THEME/woocommerce-auto-added-coupons/
824
- * @param string $template_name
825
- * @return string Template filename
826
- */
827
- public function get_template_filename( $template_name ) {
828
- $template_path = 'woocommerce-auto-added-coupons';
829
-
830
- //Get template overwritten file
831
- $template = locate_template( trailingslashit( $template_path ) . $template_name );
832
-
833
- // Get default template
834
- if ( ! $template ) {
835
- $plugin_template_path = plugin_dir_path( dirname(__FILE__) ) . 'templates/';
836
- $template = $plugin_template_path . $template_name;
837
- }
838
-
839
- return $template;
840
- }
841
-
842
- /**
843
- * Include a template file, either from this plugins directory or overwritten in the themes directory
844
- * @param type $template_name
845
- * @return type
846
- */
847
- public function include_template( $template_name, $variables = array() ) {
848
- extract( $variables );
849
- include( $this->get_template_filename( $template_name ) );
850
- }
851
-
852
- // ========================
853
- // INFO ABOUT WJECF PLUGIN
854
- // ========================
855
-
856
- /**
857
- * Filename of this plugin including the containing directory.
858
- * @return string
859
- */
860
- public function plugin_file() {
861
- $filename = $this->is_pro() ? "woocommerce-jos-autocoupon-pro.php" : "woocommerce-jos-autocoupon.php" ;
862
- return trailingslashit( basename( dirname( dirname( __FILE__ ) ) ) ) . $filename;
863
- }
864
-
865
- public function plugin_basename() {
866
- return plugin_basename( $this->plugin_file() );
867
- }
868
-
869
-
870
- /**
871
- * url to the base directory of this plugin (wp-content/woocommerce-jos-autocoupon/) with trailing slash
872
- * @return string
873
- */
874
- public function plugin_url( $suffix = '' ) {
875
- return plugins_url( '/', dirname( __FILE__ ) ) . $suffix;
876
- }
877
-
878
- public function plugin_version() {
879
- return WJECF_VERSION;
880
- }
881
-
882
- // ========================
883
- // LOGGING
884
- // ========================
885
-
886
-
887
- /**
888
- * Log a message for debugging.
889
- *
890
- * If debug_mode is false; messages with level 'debug' will be ignored.
891
- *
892
- * @param string $level The level of the message. e.g. 'debug' or 'warning'
893
- * @param string $string The message to log
894
- * @param int $skip_backtrace Defaults to 0, amount of items to skip in backtrace to fetch class and method name
895
- */
896
- public function log( $level, $message = null, $skip_backtrace = 0) {
897
- if ( ! $this->debugger ) {
898
- $this->debugger = $this->get_plugin( 'WJECF_Debug' );
899
- }
900
-
901
- if ( $this->debugger ) {
902
- $this->debugger->log( $level, $message, $skip_backtrace + 1 );
903
- }
904
- }
905
- private $debugger = null;
906
-
907
- }
908
-
909
- /**
910
- * Class that prevents an action or filter to be recursively called
911
- */
912
- class WJECF_Action_Or_Filter {
913
-
914
- private $tag;
915
- private $function_to_add;
916
- private $priority;
917
- private $accepted_args;
918
- private $limit_calls;
919
-
920
- /**
921
- *
922
- * @param string $tag name of the action or filter
923
- * @param callable $function_to_add
924
- * @param int $priority
925
- * @param int $accepted_args
926
- * @param int $limit_calls When > 0 the calls will be limited to this amount (prevents recursive calls)
927
- */
928
- private function __construct( $tag, $function_to_add, $priority, $accepted_args, $limit_calls = 0 ) {
929
- $this->tag = $tag;
930
- $this->function_to_add = $function_to_add;
931
- $this->priority = $priority;
932
- $this->accepted_args = $accepted_args;
933
-
934
- $this->limit_calls = $limit_calls;
935
- }
936
-
937
- private $inhibit = false;
938
- private $counter = 0;
939
-
940
- //Must be public for WC
941
- public function execute() {
942
- if ($this->inhibit) return;
943
-
944
- $this->counter++;
945
-
946
- $this->inhibit = true;
947
- $func_args = func_get_args(); // $func_args variable required for PHP5.2
948
- $retval = call_user_func_array( $this->function_to_add, $func_args );
949
- $this->inhibit = false;
950
-
951
- if ($this->limit_calls > 0 && $this->counter >= $this->limit_calls) {
952
- remove_action( $this->tag, array( $this, 'execute' ), $this->priority ); //unhook the action
953
- }
954
-
955
- return $retval;
956
- }
957
-
958
- /**
959
- * Same as WordPress add_action(), but prevents the callback to be recursively called
960
- *
961
- * @param string $tag
962
- * @param callable $function_to_add
963
- * @param int $priority
964
- * @param int $accepted_args
965
- */
966
- public static function action( $tag, $function_to_add, $priority = 10, $accepted_args = 1, $limit_calls ) {
967
- $me = new WJECF_Action_Or_Filter( $tag, $function_to_add, $priority, $accepted_args, $limit_calls );
968
- add_action( $tag, array( $me, 'execute' ), $priority, $accepted_args );
969
- }
970
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ // Coupon message codes
11
+ //NOTE: I use prefix 79 for this plugin; there's no guarantee that other plugins don't use the same values!
12
+ const E_WC_COUPON_MIN_MATCHING_SUBTOTAL_NOT_MET = 79100;
13
+ const E_WC_COUPON_MAX_MATCHING_SUBTOTAL_NOT_MET = 79101;
14
+ const E_WC_COUPON_MIN_MATCHING_QUANTITY_NOT_MET = 79102;
15
+ const E_WC_COUPON_MAX_MATCHING_QUANTITY_NOT_MET = 79103;
16
+ const E_WC_COUPON_SHIPPING_METHOD_NOT_MET = 79104;
17
+ const E_WC_COUPON_PAYMENT_METHOD_NOT_MET = 79105;
18
+ const E_WC_COUPON_NOT_FOR_THIS_USER = 79106;
19
+ const E_WC_COUPON_FIRST_PURCHASE_ONLY = 79107;
20
+
21
+ private $options = null;
22
+ private $_user_emails = null;
23
+
24
+ /**
25
+ * Singleton Instance
26
+ *
27
+ * @static
28
+ * @return Singleton Instance
29
+ */
30
+ public static function instance() {
31
+ if ( is_null( self::$_instance ) ) {
32
+ self::$_instance = new self();
33
+ }
34
+ return self::$_instance;
35
+ }
36
+ protected static $_instance = null;
37
+
38
+
39
+ public function __construct() {
40
+ $this->options = new WJECF_Options(
41
+ 'wjecf_options',
42
+ array(
43
+ 'db_version' => 0, // integer
44
+ 'debug_mode' => false, // true or false
45
+ 'disabled_plugins' => array(), // e.g. [ 'WJECF_AutoCoupon' ]
46
+ 'autocoupon_allow_remove' => false
47
+ )
48
+ );
49
+ }
50
+
51
+ public function start() {
52
+ add_action('init', array( $this, 'init_hook' ));
53
+ }
54
+
55
+ public function init_hook() {
56
+ if ( ! class_exists('WC_Coupon') ) {
57
+ add_action( 'admin_notices', array( $this, 'admin_notice_woocommerce_not_found' ) );
58
+ return;
59
+ }
60
+
61
+ $this->controller_init();
62
+
63
+ /**
64
+ * Fires before the WJECF plugins are initialised.
65
+ *
66
+ * Perfect hook for themes or plugins to load custom WJECF plugins.
67
+ *
68
+ * @since 2.3.7
69
+ **/
70
+ do_action( 'wjecf_init_plugins');
71
+
72
+ //Start the plugins
73
+ foreach ( WJECF()->get_plugins() as $name => $plugin ) {
74
+ if ( $plugin->plugin_is_enabled() ) {
75
+
76
+ foreach( $plugin->get_plugin_dependencies() as $dependency_name ) {
77
+ $dependency = $this->get_plugin( $dependency_name );
78
+ if ( ! $dependency || ! $dependency->plugin_is_enabled() ) {
79
+ $this->log( 'error', 'Unable to initialize ' . $name . ' because it requires ' . $dependency_name );
80
+ continue;
81
+ }
82
+ }
83
+
84
+ $plugin->init_hook();
85
+ if ( is_admin() ) {
86
+ $plugin->init_admin_hook();
87
+ }
88
+ }
89
+ }
90
+ }
91
+
92
+ public function controller_init() {
93
+
94
+ $this->log( 'debug', "INIT " . ( is_ajax() ? "AJAX" : is_admin() ? "ADMIN" : "FRONTEND" ) . " " . $_SERVER['REQUEST_URI'] );
95
+
96
+ //Frontend hooks
97
+
98
+ //assert_coupon_is_valid (which raises exception on invalid coupon) can only be used on WC 2.3.0 and up
99
+ if ( WJECF_WC()->check_woocommerce_version('2.3.0') ) {
100
+ add_filter('woocommerce_coupon_is_valid', array( $this, 'assert_coupon_is_valid' ), 10, 3 ); //Since WC3.2 WC_Discounts is passed as a 3rd argument
101
+ } else {
102
+ add_filter('woocommerce_coupon_is_valid', array( $this, 'coupon_is_valid' ), 10, 2 );
103
+ }
104
+
105
+ //Last check for coupons with restricted_emails (moved from WJECF_AutoCoupon since 2.5.6)
106
+ add_action( 'woocommerce_checkout_update_order_review', array( $this, 'fetch_billing_email' ), 10 ); // AJAX One page checkout
107
+ add_action( 'woocommerce_after_checkout_validation', array( $this, 'fetch_billing_email' ), 10 ); // Checkout posted
108
+
109
+ add_filter('woocommerce_coupon_error', array( $this, 'woocommerce_coupon_error' ), 10, 3 );
110
+ }
111
+
112
+ protected $plugins = array();
113
+
114
+ /**
115
+ * Load a WJECF Plugin (class name)
116
+ * @param string $class_name The class name of the plugin
117
+ * @return bool True if succeeded, otherwise false
118
+ */
119
+ public function add_plugin( $class_name ) {
120
+ if ( isset( $this->plugins[ $class_name ] ) ) {
121
+ return false; //Already loaded
122
+ }
123
+
124
+ if ( ! class_exists( $class_name ) ) {
125
+ $this->log( 'warning', 'Unknown plugin: ' . $class_name );
126
+ return false; //Not found
127
+ }
128
+
129
+ $plugin = new $class_name();
130
+ foreach( $plugin->get_plugin_dependencies() as $dependency ) {
131
+ if ( ! class_exists( $dependency ) ) {
132
+ $this->log( 'warning', 'Unknown dependency: ' . $dependency . ' for plugin ' . $class_name );
133
+ return false;
134
+ }
135
+
136
+ if ( isset( $this->plugins[ $class_name ] ) ) {
137
+ continue; //dependency is al geladen
138
+ }
139
+
140
+ $this->add_plugin( $dependency );
141
+ }
142
+
143
+ //Assert dependencies
144
+ try {
145
+ $plugin->assert_dependencies();
146
+ } catch (Exception $ex) {
147
+ $msg = sprintf('Failed loading %s. %s', $class_name, $ex->getMessage() );
148
+
149
+ $this->log( 'error', $msg );
150
+
151
+ if ( $wjecf_admin = WJECF()->get_plugin('WJECF_Admin') ) {
152
+ $wjecf_admin->enqueue_notice( $msg, 'error' );
153
+ }
154
+ return false;
155
+ }
156
+
157
+
158
+ $this->plugins[ $class_name ] = $plugin;
159
+ $this->log( 'debug', 'Loaded plugin: ' . $class_name );
160
+
161
+ return true;
162
+ }
163
+
164
+
165
+ /**
166
+ * Description
167
+ * @param type $plugin
168
+ * @return bool true if succesful
169
+ */
170
+ private function load_dependencies( $plugin ) {
171
+
172
+ return true;
173
+ }
174
+
175
+ public function get_plugins() {
176
+ return $this->plugins;
177
+ }
178
+
179
+ /**
180
+ * Retrieves the WJECF Plugin
181
+ * @param string $class_name
182
+ * @return object|bool The plugin if found, otherwise returns false
183
+ */
184
+ public function get_plugin( $class_name ) {
185
+ if ( isset( $this->plugins[ $class_name ] ) ) {
186
+ return $this->plugins[ $class_name ];
187
+ } else {
188
+ return false;
189
+ }
190
+ }
191
+
192
+ /* OPTIONS */
193
+
194
+ public function get_options() {
195
+ return $this->options->get();
196
+ }
197
+
198
+ public function get_option( $key, $default = null ) {
199
+ return $this->options->get( $key, $default );
200
+ }
201
+
202
+ public function set_option( $key, $value ) {
203
+ $this->options->set( $key, $value );
204
+ }
205
+
206
+ public function save_options() {
207
+ if ( ! is_admin() ) {
208
+ $this->log( 'error', 'WJECF Options must only be saved from admin.' );
209
+ return;
210
+ }
211
+ $this->options->save();
212
+ }
213
+
214
+ public function sanitizer() {
215
+ return WJECF_Sanitizer::instance();
216
+ }
217
+
218
+ /**
219
+ * Same as WordPress add_action(), but prevents the callback to be recursively called
220
+ *
221
+ * @param string $tag
222
+ * @param callable $function_to_add
223
+ * @param int $priority
224
+ * @param int $accepted_args
225
+ */
226
+ public function safe_add_action( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
227
+ $_RECURSION_LIMIT = 5;
228
+ WJECF_Action_Or_Filter::action( $tag, $function_to_add, $priority, $accepted_args, $_RECURSION_LIMIT );
229
+ }
230
+
231
+ /* FRONTEND HOOKS */
232
+
233
+ /**
234
+ * Notifies that WooCommerce has not been detected.
235
+ * @return void
236
+ */
237
+ public function admin_notice_woocommerce_not_found() {
238
+ $msg = __( 'WooCommerce Extended Coupon Features is disabled because WooCommerce could not be detected.', 'woocommerce-jos-autocoupon' );
239
+ echo '<div class="error"><p>' . $msg . '</p></div>';
240
+ }
241
+
242
+ /**
243
+ * Overwrite coupon error message, if $err_code is an error code of this plugin
244
+ * @param string $err Original error message
245
+ * @param int $err_code Error code
246
+ * @param WC_Coupon $coupon The coupon
247
+ * @return string Overwritten error message
248
+ */
249
+ public function woocommerce_coupon_error( $err, $err_code, $coupon ) {
250
+ switch ( $err_code ) {
251
+ case self::E_WC_COUPON_MIN_MATCHING_SUBTOTAL_NOT_MET:
252
+ $min_price = wc_price( WJECF_Wrap( $coupon )->get_meta( '_wjecf_min_matching_product_subtotal' ) );
253
+ $err = sprintf( __( 'The minimum subtotal of the matching products for this coupon is %s.', 'woocommerce-jos-autocoupon' ), $min_price );
254
+ break;
255
+ case self::E_WC_COUPON_MAX_MATCHING_SUBTOTAL_NOT_MET:
256
+ $max_price = wc_price( WJECF_Wrap( $coupon )->get_meta( '_wjecf_max_matching_product_subtotal' ) );
257
+ $err = sprintf( __( 'The maximum subtotal of the matching products for this coupon is %s.', 'woocommerce-jos-autocoupon' ), $max_price );
258
+ break;
259
+ case self::E_WC_COUPON_MIN_MATCHING_QUANTITY_NOT_MET:
260
+ $min_matching_product_qty = intval( WJECF_Wrap( $coupon )->get_meta( '_wjecf_min_matching_product_qty' ) );
261
+ $err = sprintf( __( 'The minimum quantity of matching products for this coupon is %s.', 'woocommerce-jos-autocoupon' ), $min_matching_product_qty );
262
+ break;
263
+ case self::E_WC_COUPON_MAX_MATCHING_QUANTITY_NOT_MET:
264
+ $max_matching_product_qty = intval( WJECF_Wrap( $coupon )->get_meta( '_wjecf_min_matching_product_qty' ) );
265
+ $err = sprintf( __( 'The maximum quantity of matching products for this coupon is %s.', 'woocommerce-jos-autocoupon' ), $max_matching_product_qty );
266
+ break;
267
+ case self::E_WC_COUPON_SHIPPING_METHOD_NOT_MET:
268
+ $err = __( 'The coupon is not valid for the currently selected shipping method.', 'woocommerce-jos-autocoupon' );
269
+ break;
270
+ case self::E_WC_COUPON_PAYMENT_METHOD_NOT_MET:
271
+ $err = __( 'The coupon is not valid for the currently selected payment method.', 'woocommerce-jos-autocoupon' );
272
+ break;
273
+ case self::E_WC_COUPON_NOT_FOR_THIS_USER:
274
+ $err = sprintf( __( 'Sorry, it seems the coupon "%s" is not yours.', 'woocommerce-jos-autocoupon' ), WJECF_Wrap( $coupon )->get_code() );
275
+ break;
276
+ case self::E_WC_COUPON_FIRST_PURCHASE_ONLY:
277
+ $err = sprintf( __( 'Sorry, coupon "%s" is only valid on your first purchase.', 'woocommerce-jos-autocoupon' ), WJECF_Wrap( $coupon )->get_code() );
278
+ break;
279
+ default:
280
+ //Do nothing
281
+ break;
282
+ }
283
+ return $err;
284
+ }
285
+
286
+ /**
287
+ * Extra validation rules for coupons.
288
+ * @param bool $valid
289
+ * @param WC_Coupon $coupon
290
+ * @param WC_Discounts $discounts
291
+ * @return bool True if valid; False if not valid.
292
+ */
293
+ public function coupon_is_valid( $valid, $coupon, $wc_discounts = null ) {
294
+ try {
295
+ return $this->assert_coupon_is_valid( $valid, $coupon, $wc_discounts );
296
+ } catch ( Exception $e ) {
297
+ return false;
298
+ }
299
+ }
300
+
301
+ /**
302
+ * Extra validation rules for coupons. Throw an exception when not valid.
303
+ * @param bool $valid
304
+ * @param WC_Coupon $coupon
305
+ * @param WC_Discounts $discounts
306
+ * @return bool True if valid; False if already invalid on function call. In any other case an Exception will be thrown.
307
+ */
308
+ public function assert_coupon_is_valid( $valid, $coupon, $wc_discounts = null ) {
309
+
310
+ //Not valid? Then it will never validate, so get out of here
311
+ if ( ! $valid ) {
312
+ return false;
313
+ }
314
+
315
+ $wrap_coupon = WJECF_Wrap( $coupon );
316
+ $items = WJECF_WC()->get_discount_items( $wc_discounts );
317
+
318
+ //$this->log( 'debug', 'Asserting validity of coupon: ' . $wrap_coupon->get_code() );
319
+
320
+ //============================
321
+ //Test if ALL products are in the cart (if AND-operator selected instead of the default OR)
322
+ $products_and = $wrap_coupon->get_meta( '_wjecf_products_and' ) == 'yes';
323
+ if ( $products_and && sizeof( $wrap_coupon->get_product_ids() ) > 1 ) { // We use > 1, because if size == 1, 'AND' makes no difference
324
+ //Get array of all cart product and variation ids
325
+ $item_ids = array();
326
+
327
+ foreach( $items as $item_key => $item ) {
328
+ if ( $item->product === false ) continue;
329
+ $wrap_product = WJECF_Wrap( $item->product );
330
+
331
+ $item_ids[] = $wrap_product->get_id();
332
+ if ( $item->product->is_type( 'variation' ) ) $item_ids[] = $wrap_product->get_variable_product_id();
333
+ }
334
+ //Filter used by WJECF_WPML hook
335
+ $item_ids = apply_filters( 'wjecf_get_product_ids', array_unique( $item_ids ) );
336
+
337
+ //check if every single product is in the cart
338
+ foreach( apply_filters( 'wjecf_get_product_ids', $wrap_coupon->get_product_ids() ) as $product_id ) {
339
+ if ( ! in_array( $product_id, $item_ids ) ) {
340
+ throw new Exception( WC_Coupon::E_WC_COUPON_NOT_APPLICABLE );
341
+ }
342
+ }
343
+ }
344
+
345
+ //============================
346
+ //Test if products form ALL categories are in the cart (if AND-operator selected instead of the default OR)
347
+ $categories_and = $wrap_coupon->get_meta( '_wjecf_categories_and' ) == 'yes';
348
+ if ( $categories_and && sizeof( $wrap_coupon->get_product_categories() ) > 1 ) { // We use > 1, because if size == 1, 'AND' makes no difference
349
+ //Get array of all cart product and variation ids
350
+ $product_cats = array();
351
+
352
+ foreach( $items as $item_key => $item ) {
353
+ if ( $item->product === false ) continue;
354
+
355
+ $product_id = WJECF_Wrap( $item->product )->get_id();
356
+ if ('product_variation' == get_post_type( $product_id )) $product_id = WJECF_Wrap( $item->product )->get_variable_product_id();
357
+ $product_cats = array_merge( $product_cats, wp_get_post_terms( $product_id, 'product_cat', array( "fields" => "ids" ) ) );
358
+ }
359
+ //Filter used by WJECF_WPML hook
360
+ $product_cats = apply_filters( 'wjecf_get_product_cat_ids', $product_cats );
361
+ //check if every single category is in the cart
362
+ foreach( apply_filters( 'wjecf_get_product_cat_ids', $wrap_coupon->get_product_categories() ) as $cat_id ) {
363
+ if ( ! in_array( $cat_id, $product_cats ) ) {
364
+ $this->log( 'debug', $cat_id . " is not in " . implode( ',', $product_cats ));
365
+ throw new Exception( WC_Coupon::E_WC_COUPON_NOT_APPLICABLE );
366
+ }
367
+ }
368
+ }
369
+
370
+ //============================
371
+ //Test min/max quantity of matching products
372
+ //
373
+ //For all items in the cart:
374
+ // If coupon contains both a product AND category inclusion filter: the item is counted if it matches either one of them
375
+ // If coupon contains either a product OR category exclusion filter: the item will NOT be counted if it matches either one of them
376
+ // If sale items are excluded by the coupon: the item will NOT be counted if it is a sale item
377
+ // If no filter exist, all items will be counted
378
+
379
+ unset( $this->coupon_multiplier_values[ $wrap_coupon->get_code() ] );
380
+ $multiplier = null; //null = not initialized
381
+
382
+ //Validate quantity
383
+ $min_matching_product_qty = intval( $wrap_coupon->get_meta( '_wjecf_min_matching_product_qty' ) );
384
+ $max_matching_product_qty = intval( $wrap_coupon->get_meta( '_wjecf_max_matching_product_qty' ) );
385
+ if ( $min_matching_product_qty > 0 || $max_matching_product_qty > 0 ) {
386
+ //Count the products
387
+ $qty = $this->get_quantity_of_matching_products( $coupon, $wc_discounts );
388
+ //$this->log( 'debug', 'Quantity of matching products: ' . $qty );
389
+ if ( $min_matching_product_qty > 0 && $qty < $min_matching_product_qty ) throw new Exception( self::E_WC_COUPON_MIN_MATCHING_QUANTITY_NOT_MET );
390
+ if ( $max_matching_product_qty > 0 && $qty > $max_matching_product_qty ) throw new Exception( self::E_WC_COUPON_MAX_MATCHING_QUANTITY_NOT_MET );
391
+
392
+ if ( $min_matching_product_qty > 0 ) {
393
+ $multiplier = self::min_value( floor( $qty / $min_matching_product_qty ), $multiplier );
394
+ }
395
+ }
396
+
397
+ //Validate subtotal (2.2.2)
398
+ $min_matching_product_subtotal = floatval( $wrap_coupon->get_meta( '_wjecf_min_matching_product_subtotal' ) );
399
+ $max_matching_product_subtotal = floatval( $wrap_coupon->get_meta( '_wjecf_max_matching_product_subtotal' ) );
400
+ if ( $min_matching_product_subtotal > 0 || $max_matching_product_subtotal > 0 ) {
401
+ $subtotal = $this->get_subtotal_of_matching_products( $coupon, $wc_discounts );
402
+ //$this->log( 'debug', 'Subtotal of matching products: ' . $subtotal );
403
+ if ( $min_matching_product_subtotal > 0 && $subtotal < $min_matching_product_subtotal ) throw new Exception( self::E_WC_COUPON_MIN_MATCHING_SUBTOTAL_NOT_MET );
404
+ if ( $max_matching_product_subtotal > 0 && $subtotal > $max_matching_product_subtotal ) throw new Exception( self::E_WC_COUPON_MAX_MATCHING_SUBTOTAL_NOT_MET );
405
+
406
+ if ( $min_matching_product_subtotal > 0 ) {
407
+ $multiplier = self::min_value( floor( $subtotal / $min_matching_product_subtotal ), $multiplier );
408
+ }
409
+ }
410
+
411
+ //============================
412
+ //Test restricted shipping methods
413
+ $shipping_method_ids = $this->get_coupon_shipping_method_ids( $coupon );
414
+ if ( sizeof( $shipping_method_ids ) > 0 ) {
415
+ $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
416
+ $chosen_shipping = empty( $chosen_shipping_methods ) ? '' : $chosen_shipping_methods[0];
417
+ //$this->log('debug', 'Current shipping method: ' . ( $chosen_shipping === '' ? 'none' : $chosen_shipping ));
418
+ $chosen_shipping = explode( ':', $chosen_shipping); //UPS and USPS stores extra data, seperated by colon
419
+ $chosen_shipping = $chosen_shipping[0];
420
+
421
+ if ( ! in_array( $chosen_shipping, $shipping_method_ids ) ) {
422
+ throw new Exception( self::E_WC_COUPON_SHIPPING_METHOD_NOT_MET );
423
+ }
424
+ }
425
+
426
+ //============================
427
+ //Test restricted payment methods
428
+ $payment_method_ids = $this->get_coupon_payment_method_ids( $coupon );
429
+ if ( sizeof( $payment_method_ids ) > 0 ) {
430
+ $chosen_payment_method = isset( WC()->session->chosen_payment_method ) ? WC()->session->chosen_payment_method : array();
431
+
432
+ if ( ! in_array( $chosen_payment_method, $payment_method_ids ) ) {
433
+ throw new Exception( self::E_WC_COUPON_PAYMENT_METHOD_NOT_MET );
434
+ }
435
+ }
436
+
437
+ //Since 2.6.3: only verify customer if on frontend
438
+ if ( $this->is_request( 'frontend' ) ) {
439
+ //============================
440
+ //Test restricted user ids and roles
441
+ //NOTE: If both customer id and role restrictions are provided, the coupon matches if either the id or the role matches
442
+ $coupon_customer_ids = $this->get_coupon_customer_ids( $coupon );
443
+ $coupon_customer_roles = $this->get_coupon_customer_roles( $coupon );
444
+ if ( sizeof( $coupon_customer_ids ) > 0 || sizeof( $coupon_customer_roles ) > 0 ) {
445
+ $user = wp_get_current_user();
446
+
447
+ //If both fail we invalidate. Otherwise it's ok
448
+ if ( ! in_array( $user->ID, $coupon_customer_ids ) && ! array_intersect( $user->roles, $coupon_customer_roles ) ) {
449
+ throw new Exception( self::E_WC_COUPON_NOT_FOR_THIS_USER );
450
+ }
451
+ }
452
+
453
+ //============================
454
+ //Test excluded user roles
455
+ $coupon_excluded_customer_roles = $this->get_coupon_excluded_customer_roles( $coupon );
456
+ if ( sizeof( $coupon_excluded_customer_roles ) > 0 ) {
457
+ $user = wp_get_current_user();
458
+
459
+ //Excluded customer roles
460
+ if ( array_intersect( $user->roles, $coupon_excluded_customer_roles ) ) {
461
+ throw new Exception( self::E_WC_COUPON_NOT_FOR_THIS_USER );
462
+ }
463
+ }
464
+ }
465
+
466
+ //We use our own filter (instead of woocommerce_coupon_is_valid) for easier compatibility management
467
+ //e.g. WC prior to 2.3.0 can't handle Exceptions; while 2.3.0 and above require exceptions
468
+ do_action( 'wjecf_assert_coupon_is_valid', $coupon, $wc_discounts );
469
+
470
+ if ( (float) $wrap_coupon->get_minimum_amount() ) {
471
+ $multiplier = self::min_value( floor( $this->get_subtotal( $wc_discounts ) / $wrap_coupon->get_minimum_amount() ), $multiplier );
472
+ }
473
+
474
+ /**
475
+ * Filters the (product-)multiplier value of the coupon.
476
+ *
477
+ * @since 2.6.3
478
+ *
479
+ * @param float|null $multiplier Current multiplier value (or null if no multiplier yet known)
480
+ * @param WC_Coupon $coupon The coupon
481
+ * @param WC_Discounts $wc_discounts Discounts class containing the cart items (NOTE: Will be a WJECF_WC_Discounts for WC < 3.2.0)
482
+ */
483
+ $multiplier = apply_filters( 'wjecf_coupon_multiplier_value', $multiplier, $coupon, $wc_discounts );
484
+ $this->coupon_multiplier_values[ $wrap_coupon->get_code() ] = isset( $multiplier ) ? $multiplier : 1;
485
+
486
+ //$this->log( 'debug', 'Coupon ' . $wrap_coupon->get_code() . ' is valid. Multiplyer value: ' . $multiplier );
487
+
488
+ return true; // VALID!
489
+ }
490
+
491
+ /**
492
+ * Return the lowest multiplier value
493
+ * @param float $value
494
+ * @param float|null $current_multiplier_value Null means the value is not yet known
495
+ * @return float
496
+ */
497
+ private static function min_value( $value, $current_multiplier_value = null ) {
498
+ return ( $current_multiplier_value === null || $value < $current_multiplier_value ) ? $value : $current_multiplier_value;
499
+ }
500
+
501
+ /**
502
+ * The amount of times the minimum spend / quantity / subtotal values are reached
503
+ * @return int 1 or more if coupon is valid, otherwise 0
504
+ */
505
+ public function get_coupon_multiplier_value( $coupon ) {
506
+ $coupon = WJECF_WC()->get_coupon( $coupon );
507
+
508
+ //If coupon validation was not executed, the value is unknown
509
+ if ( ! isset( $this->coupon_multiplier_values[ WJECF_Wrap( $coupon )->get_code() ] ) ) {
510
+ if ( ! $this->coupon_is_valid( true, $coupon ) ) {
511
+ return 0;
512
+ }
513
+ //Calling coupon_is_valid enforces $this->coupon_multiplier_values to be set; if the coupon is valid.
514
+ }
515
+
516
+ return $this->coupon_multiplier_values[ WJECF_Wrap( $coupon )->get_code() ];
517
+ }
518
+
519
+ //Temporary storage
520
+ private $coupon_multiplier_values = array();
521
+
522
+
523
+ /**
524
+ * (API FUNCTION)
525
+ * The total amount of the products in the cart that match the coupon restrictions
526
+ * since 2.2.2-b3
527
+ */
528
+ public function get_quantity_of_matching_products( $coupon, $wc_discounts = null ) {
529
+ $coupon = WJECF_WC()->get_coupon( $coupon );
530
+ $items = WJECF_WC()->get_discount_items( $wc_discounts );
531
+
532
+ $qty = 0;
533
+ foreach( $items as $item_key => $item ) {
534
+ if ( $item->product === false ) continue;
535
+
536
+ if ($this->coupon_is_valid_for_product( $coupon, $item->product, $item->object ) ) {
537
+ $qty += $item->quantity;
538
+ }
539
+ }
540
+ return $qty;
541
+ }
542
+
543
+ /**
544
+ * (API FUNCTION)
545
+ * The total value of the products in the cart that match the coupon restrictions
546
+ * since 2.2.2-b3
547
+ */
548
+ public function get_subtotal_of_matching_products( $coupon, $wc_discounts = null ) {
549
+ $coupon = WJECF_WC()->get_coupon( $coupon );
550
+ $items = WJECF_WC()->get_discount_items( $wc_discounts );
551
+
552
+ $subtotal_precise = 0;
553
+ foreach( $items as $item_key => $item ) {
554
+ if ( $item->product === false ) continue;
555
+
556
+ if ($this->coupon_is_valid_for_product( $coupon, $item->product, $item->object ) ) {
557
+ $subtotal_precise += $item->price;
558
+ }
559
+ }
560
+
561
+ $subtotal = WJECF_WC()->wc_remove_number_precision( $subtotal_precise );
562
+ return $subtotal;
563
+ }
564
+
565
+ /**
566
+ * The total value of the products in the cart
567
+ * since 2.2.2-b3
568
+ */
569
+ public function get_subtotal( $wc_discounts = null ) {
570
+ $items = WJECF_WC()->get_discount_items( $wc_discounts );
571
+
572
+ $subtotal_precise = 0;
573
+ foreach( $items as $item_key => $item ) {
574
+ if ( $item->product === false ) continue;
575
+ $subtotal_precise += $item->price;
576
+ }
577
+
578
+ $subtotal = WJECF_WC()->wc_remove_number_precision( $subtotal_precise );
579
+ return $subtotal;
580
+ }
581
+
582
+ /**
583
+ * (API FUNCTION)
584
+ * Test if coupon is valid for the product
585
+ * (this function is used to count the quantity of matching products)
586
+ */
587
+ public function coupon_is_valid_for_product( $coupon, $product, $values = array() ) {
588
+ //Do not count the free products
589
+ if ( isset( $values['_wjecf_free_product_coupon'] ) ) {
590
+ return false;
591
+ }
592
+
593
+ //Get the original coupon, without values overwritten by WJECF
594
+ $duplicate_coupon = $this->get_original_coupon( $coupon );
595
+
596
+ //$coupon->is_valid_for_product() only works for fixed_product or percent_product discounts
597
+ if ( ! $duplicate_coupon->is_type( WJECF_WC()->wc_get_product_coupon_types() ) ) {
598
+ WJECF_Wrap( $duplicate_coupon )->set_discount_type( 'fixed_product' );
599
+ }
600
+
601
+ $valid = $duplicate_coupon->is_valid_for_product( $product, $values );
602
+ //$this->log( sprintf("%s valid for %s? %s", WJECF_Wrap( $coupon )->get_code(), WJECF_Wrap( $product )->get_name(), $valid ? 'yes':'no' ) );
603
+ return $valid;
604
+ }
605
+
606
+
607
+ // =====================
608
+
609
+ /**
610
+ * Get array of the selected shipping methods ids.
611
+ * @param WC_Coupon|string $coupon The coupon code or a WC_Coupon object
612
+ * @return array Id's of the shipping methods or an empty array.
613
+ */
614
+ public function get_coupon_shipping_method_ids( $coupon ) {
615
+ $v = WJECF_Wrap( $coupon )->get_meta( '_wjecf_shipping_methods' );
616
+ return is_array( $v ) ? $v : array();
617
+ }
618
+
619
+ /**
620
+ * Get array of the selected payment method ids.
621
+ * @param WC_Coupon|string $coupon The coupon code or a WC_Coupon object
622
+ * @return array Id's of the payment methods or an empty array.
623
+ */
624
+ public function get_coupon_payment_method_ids( $coupon ) {
625
+ $v = WJECF_Wrap( $coupon )->get_meta( '_wjecf_payment_methods' );
626
+ return is_array( $v ) ? $v : array();
627
+ }
628
+
629
+ /**
630
+ * Get array of the selected customer ids.
631
+ * @param WC_Coupon|string $coupon The coupon code or a WC_Coupon object
632
+ * @return array Id's of the customers (users) or an empty array.
633
+ */
634
+ public function get_coupon_customer_ids( $coupon ) {
635
+ $v = WJECF_Wrap( $coupon )->get_meta( '_wjecf_customer_ids' );
636
+ return WJECF()->sanitizer()->sanitize( $v, 'int[]' );
637
+ }
638
+
639
+ /**
640
+ * Get array of the selected customer role ids.
641
+ * @param WC_Coupon|string $coupon The coupon code or a WC_Coupon object
642
+ * @return array Id's (string) of the customer roles or an empty array.
643
+ */
644
+ public function get_coupon_customer_roles( $coupon ) {
645
+ $v = WJECF_Wrap( $coupon )->get_meta( '_wjecf_customer_roles' );
646
+ return is_array( $v ) ? $v : array();
647
+ }
648
+
649
+ /**
650
+ * Get array of the excluded customer role ids.
651
+ * @param WC_Coupon|string $coupon The coupon code or a WC_Coupon object
652
+ * @return array Id's (string) of the excluded customer roles or an empty array.
653
+ */
654
+ public function get_coupon_excluded_customer_roles( $coupon ) {
655
+ $v = WJECF_Wrap( $coupon )->get_meta( '_wjecf_excluded_customer_roles' );
656
+ return is_array( $v ) ? $v : array();
657
+ }
658
+
659
+
660
+ // ===========================================================================
661
+ // User identification
662
+ // ===========================================================================
663
+
664
+ /**
665
+ * Get a list of the users' known email addresses
666
+ *
667
+ * NOTE: Also called in AutoCoupon
668
+ *
669
+ * @since 2.5.6 (Moved from WJECF_AutoCoupon)
670
+ */
671
+ public function get_user_emails() {
672
+ if ( ! is_array($this->_user_emails) ) {
673
+ $this->_user_emails = array();
674
+ //Email of the logged in user
675
+ if ( is_user_logged_in() ) {
676
+ $current_user = wp_get_current_user();
677
+ $this->_user_emails[] = $current_user->user_email;
678
+ }
679
+ }
680
+ $user_emails = $this->_user_emails;
681
+
682
+ $billing_email = $this->get_session('billing_email', '');
683
+ if ( is_email( $billing_email ) ) $user_emails[] = $billing_email;
684
+
685
+ $user_emails = array_map( 'strtolower', $user_emails );
686
+ $user_emails = array_map( 'sanitize_email', $user_emails );
687
+ $user_emails = array_filter( $user_emails, 'is_email' );
688
+ return array_unique( $user_emails );
689
+ }
690
+
691
+ /**
692
+ * Called on action: woocommerce_checkout_update_order_review
693
+ *
694
+ * Collects billing email address from the checkout-form
695
+ */
696
+ public function fetch_billing_email( $post_data ) {
697
+ //post_data can be an array, or a query=string&like=this
698
+ if ( ! is_array( $post_data ) ) {
699
+ parse_str( $post_data, $posted );
700
+ } else {
701
+ $posted = $post_data;
702
+ }
703
+
704
+ if ( isset( $posted['billing_email'] ) ) {
705
+ //$this->log('debug', 'billing:' . $posted['billing_email']);
706
+ WJECF()->set_session( 'billing_email', $posted['billing_email'] );
707
+ }
708
+ }
709
+
710
+ public function is_pro() {
711
+ return $this instanceof WJECF_Pro_Controller;
712
+ }
713
+
714
+ // ===========================================================================
715
+ // START - OVERWRITE INFO MESSAGES
716
+ // ===========================================================================
717
+
718
+ /**
719
+ * 2.3.4
720
+ * If a 'Coupon applied' message is displayed by WooCommerce, replace it by another message (or no message)
721
+ * @param WC_Coupon $coupon The coupon to replace the message for
722
+ * @param string $new_message The new message. Set to empty string if no message must be displayed
723
+ */
724
+ public function start_overwrite_success_message( $coupon, $new_message = '' ) {
725
+ $this->overwrite_coupon_message[ WJECF_Wrap( $coupon )->get_code() ] = array( $coupon->get_coupon_message( WC_Coupon::WC_COUPON_SUCCESS ) => $new_message );
726
+ add_filter( 'woocommerce_coupon_message', array( $this, 'filter_woocommerce_coupon_message' ), 10, 3 );
727
+ }
728
+
729
+ /**
730
+ * 2.3.4
731
+ * Stop overwriting messages
732
+ */
733
+ public function stop_overwrite_success_message() {
734
+ remove_filter( 'woocommerce_coupon_message', array( $this, 'filter_woocommerce_coupon_message' ), 10 );
735
+ $this->overwrite_coupon_message = array();
736
+ }
737
+
738
+ private $overwrite_coupon_message = array(); /* [ 'coupon_code' => [ old_message' => 'new_message' ] ] */
739
+
740
+ function filter_woocommerce_coupon_message( $msg, $msg_code, $coupon ) {
741
+ if ( isset( $this->overwrite_coupon_message[ WJECF_Wrap( $coupon )->get_code() ][ $msg ] ) ) {
742
+ $msg = $this->overwrite_coupon_message[ WJECF_Wrap( $coupon )->get_code() ][ $msg ];
743
+ }
744
+ return $msg;
745
+ }
746
+
747
+ // ===========================================================================
748
+ // END - OVERWRITE INFO MESSAGES
749
+ // ===========================================================================
750
+
751
+ /**
752
+ * @since 2.4.4
753
+ *
754
+ * Get a coupon, but inhibit the woocommerce_coupon_loaded to overwrite values.
755
+ * @param WC_Coupon|string $coupon The coupon code or a WC_Coupon object
756
+ * @return WC_Coupon The coupon object
757
+ */
758
+ public function get_original_coupon( $coupon ) {
759
+ //Prevent returning the same instance
760
+ if ( $coupon instanceof WC_Coupon ) {
761
+ $coupon = WJECF_Wrap( $coupon )->get_code();
762
+ }
763
+ $this->inhibit_overwrite++;
764
+ $coupon = WJECF_WC()->get_coupon( $coupon );
765
+ $this->inhibit_overwrite--;
766
+ return $coupon;
767
+ }
768
+
769
+ private $inhibit_overwrite = 0;
770
+
771
+ /**
772
+ * @since 2.4.4
773
+ *
774
+ * May coupon values be overwritten by this plugin upon load?
775
+ * @return bool
776
+ */
777
+ public function allow_overwrite_coupon_values() {
778
+ return ( $this->inhibit_overwrite == 0 ) && $this->is_request( 'frontend' );
779
+ }
780
+
781
+ //============
782
+
783
+ private $_session_data = null;
784
+ /**
785
+ * Read something from the session.
786
+ *
787
+ * If key is omitted; all the session data will be returned as an array
788
+ *
789
+ * @param string $key The key for identification
790
+ * @param any $default The default value (Default: false)
791
+ *
792
+ * @return The saved value if found, otherwise the default value
793
+ */
794
+ public function get_session( $key = null, $default = false ) {
795
+ if ( $this->_session_data == null ) {
796
+ if ( WC()->session == null ) {
797
+ $this->log( 'error', 'Trying to access WC()->session while it was not yet initialized.' );
798
+ return null;
799
+ }
800
+ $this->_session_data = WC()->session->get( '_wjecf_session_data', array() );
801
+ }
802
+
803
+ if ( ! isset( $key ) ) return $this->_session_data;
804
+ if ( ! isset( $this->_session_data[ $key ] ) ) return $default;
805
+ return $this->_session_data[ $key ];
806
+ }
807
+
808
+ /**
809
+ * Save something in the session
810
+ *
811
+ * @param string $key The key for identification
812
+ * @param anything $value The value to store. Use 'null' to remove the value
813
+ */
814
+ public function set_session( $key, $value ) {
815
+ if ( $this->_session_data == null) {
816
+ if ( WC()->session == null ) {
817
+ $this->log( 'error', 'Trying to access WC()->session while it was not yet initialized.' );
818
+ return null;
819
+ }
820
+ $this->_session_data = WC()->session->get( '_wjecf_session_data', array() );
821
+ }
822
+ if ( is_null( $value ) ) {
823
+ unset( $this->_session_data[ $key ] );
824
+ } else {
825
+ $this->_session_data[ $key ] = $value;
826
+ }
827
+
828
+ WC()->session->set( '_wjecf_session_data', $this->_session_data );
829
+ }
830
+
831
+ /**
832
+ * (Copied from class-woocommerce.php) What type of request is this?
833
+ *
834
+ * @since 2.6.2
835
+ * @param string $type admin, ajax, cron or frontend.
836
+ * @return bool
837
+ */
838
+ public function is_request( $type ) {
839
+ switch ( $type ) {
840
+ case 'admin':
841
+ return is_admin();
842
+ case 'ajax':
843
+ return defined( 'DOING_AJAX' );
844
+ case 'cron':
845
+ return defined( 'DOING_CRON' );
846
+ case 'frontend':
847
+ return ( ! is_admin() || defined( 'DOING_AJAX' ) ) && ! defined( 'DOING_CRON' );
848
+ }
849
+ }
850
+
851
+ /**
852
+ * Get overwritable template filename
853
+ *
854
+ * Template can be overwritten in wp-content/themes/YOUR_THEME/woocommerce-auto-added-coupons/
855
+ * @param string $template_name
856
+ * @return string Template filename
857
+ */
858
+ public function get_template_filename( $template_name ) {
859
+ $template_path = 'woocommerce-auto-added-coupons';
860
+
861
+ //Get template overwritten file
862
+ $template = locate_template( trailingslashit( $template_path ) . $template_name );
863
+
864
+ // Get default template
865
+ if ( ! $template ) {
866
+ $plugin_template_path = plugin_dir_path( dirname(__FILE__) ) . 'templates/';
867
+ $template = $plugin_template_path . $template_name;
868
+ }
869
+
870
+ return $template;
871
+ }
872
+
873
+ /**
874
+ * Include a template file, either from this plugins directory or overwritten in the themes directory
875
+ * @param type $template_name
876
+ * @return type
877
+ */
878
+ public function include_template( $template_name, $variables = array() ) {
879
+ extract( $variables );
880
+ include( $this->get_template_filename( $template_name ) );
881
+ }
882
+
883
+ // ========================
884
+ // INFO ABOUT WJECF PLUGIN
885
+ // ========================
886
+
887
+ /**
888
+ * Filename of this plugin including the containing directory.
889
+ * @return string
890
+ */
891
+ public function plugin_file() {
892
+ $filename = $this->is_pro() ? "woocommerce-jos-autocoupon-pro.php" : "woocommerce-jos-autocoupon.php" ;
893
+ return trailingslashit( basename( dirname( dirname( __FILE__ ) ) ) ) . $filename;
894
+ }
895
+
896
+ public function plugin_basename() {
897
+ return plugin_basename( $this->plugin_file() );
898
+ }
899
+
900
+
901
+ /**
902
+ * url to the base directory of this plugin (wp-content/woocommerce-jos-autocoupon/) with trailing slash
903
+ * @return string
904
+ */
905
+ public function plugin_url( $suffix = '' ) {
906
+ return plugins_url( '/', dirname( __FILE__ ) ) . $suffix;
907
+ }
908
+
909
+ public function plugin_version() {
910
+ return WJECF_VERSION;
911
+ }
912
+
913
+ // ========================
914
+ // LOGGING
915
+ // ========================
916
+
917
+
918
+ /**
919
+ * Log a message for debugging.
920
+ *
921
+ * If debug_mode is false; messages with level 'debug' will be ignored.
922
+ *
923
+ * @param string $level The level of the message. e.g. 'debug' or 'warning'
924
+ * @param string $string The message to log
925
+ * @param int $skip_backtrace Defaults to 0, amount of items to skip in backtrace to fetch class and method name
926
+ */
927
+ public function log( $level, $message = null, $skip_backtrace = 0) {
928
+ if ( ! $this->debugger ) {
929
+ $this->debugger = $this->get_plugin( 'WJECF_Debug' );
930
+ }
931
+
932
+ if ( $this->debugger ) {
933
+ $this->debugger->log( $level, $message, $skip_backtrace + 1 );
934
+ }
935
+ }
936
+ private $debugger = null;
937
+
938
+ }
939
+
940
+ /**
941
+ * Class that prevents an action or filter to be recursively called
942
+ */
943
+ class WJECF_Action_Or_Filter {
944
+
945
+ private $tag;
946
+ private $function_to_add;
947
+ private $priority;
948
+ private $accepted_args;
949
+ private $limit_calls;
950
+
951
+ /**
952
+ *
953
+ * @param string $tag name of the action or filter
954
+ * @param callable $function_to_add
955
+ * @param int $priority
956
+ * @param int $accepted_args
957
+ * @param int $limit_calls When > 0 the calls will be limited to this amount (prevents recursive calls)
958
+ */
959
+ private function __construct( $tag, $function_to_add, $priority, $accepted_args, $limit_calls = 0 ) {
960
+ $this->tag = $tag;
961
+ $this->function_to_add = $function_to_add;
962
+ $this->priority = $priority;
963
+ $this->accepted_args = $accepted_args;
964
+
965
+ $this->limit_calls = $limit_calls;
966
+ }
967
+
968
+ private $inhibit = false;
969
+ private $counter = 0;
970
+
971
+ //Must be public for WC
972
+ public function execute() {
973
+ if ($this->inhibit) return;
974
+
975
+ $this->counter++;
976
+
977
+ $this->inhibit = true;
978
+ $func_args = func_get_args(); // $func_args variable required for PHP5.2
979
+ $retval = call_user_func_array( $this->function_to_add, $func_args );
980
+ $this->inhibit = false;
981
+
982
+ if ($this->limit_calls > 0 && $this->counter >= $this->limit_calls) {
983
+ remove_action( $this->tag, array( $this, 'execute' ), $this->priority ); //unhook the action
984
+ }
985
+
986
+ return $retval;
987
+ }
988
+
989
+ /**
990
+ * Same as WordPress add_action(), but prevents the callback to be recursively called
991
+ *
992
+ * @param string $tag
993
+ * @param callable $function_to_add
994
+ * @param int $priority
995
+ * @param int $accepted_args
996
+ */
997
+ public static function action( $tag, $function_to_add, $priority = 10, $accepted_args = 1, $limit_calls ) {
998
+ $me = new WJECF_Action_Or_Filter( $tag, $function_to_add, $priority, $accepted_args, $limit_calls );
999
+ add_action( $tag, array( $me, 'execute' ), $priority, $accepted_args );
1000
+ }
1001
+ }
includes/WJECF_Debug.php CHANGED
@@ -1,200 +1,200 @@
1
- <?php
2
-
3
- defined('ABSPATH') or die();
4
-
5
- /**
6
- * Debugging functions for wjecf
7
- *
8
- * @since 2.6.0
9
- */
10
- class WJECF_Debug extends Abstract_WJECF_Plugin {
11
-
12
- private $enable_logging = false;
13
-
14
- public function __construct() {
15
- $this->set_plugin_data( array(
16
- 'description' => __( 'Debugging methods for WooCommerce Extended Coupon Features.', 'woocommerce-jos-autocoupon' ),
17
- 'dependencies' => array(),
18
- 'can_be_disabled' => false,
19
- 'hidden' => false
20
- ) );
21
- }
22
-
23
- public function init_hook() {
24
- $this->enable_logging = true;
25
-
26
- add_action( 'wp_loaded', array( $this, 'handle_querystring' ), 90 );
27
- add_action( 'wp_footer', array( $this, 'render_log' ) ); //Log
28
- }
29
-
30
- public function init_admin_hook() {
31
- if ( current_user_can( 'manage_options' ) && $this->debug_mode() )
32
- {
33
- if ( $wjecf_admin = WJECF()->get_plugin('WJECF_Admin') ) {
34
- $msg = __( 'Debug mode is enabled. Please disable it when you\'re done debugging.', 'woocommerce-jos-autocoupon' );
35
- $wjecf_admin_settings = WJECF()->get_plugin('WJECF_Admin_Settings');
36
- if ($wjecf_admin_settings) {
37
- $msg .= ' <a href="' . $wjecf_admin_settings ->get_settings_page_url() . '">' . __( 'Go to settings page', 'woocommerce-jos-autocoupon' ) . '</a>';
38
- }
39
- $wjecf_admin->enqueue_notice( $msg, 'notice-warning' );
40
- }
41
- add_action( 'wjecf_coupon_metabox_misc', array( $this, 'wjecf_coupon_metabox_misc' ), 20, 2 );
42
- }
43
- }
44
-
45
- // ========================
46
- // ACTION HOOKS
47
- // ========================
48
-
49
- public function wjecf_coupon_metabox_misc( $thepostid, $post ) {
50
- echo "<h3>" . esc_html( __( 'Debug', 'woocommerce-jos-autocoupon' ) ). "</h3>\n";
51
-
52
- $coupon_code = WJECF_Wrap( WJECF_WC()->get_coupon( $post ) )->get_code();
53
- $url = add_query_arg( array( 'wjecf_dump' => $coupon_code ), get_site_url() );
54
- $text = __( 'Coupon data as json', 'woocommerce-jos-autocoupon');
55
- echo '<p>';
56
- printf( '<a href="%s" target="_blank">%s</a>', $url, $text );
57
- echo '</p>';
58
- }
59
-
60
- /**
61
- * Output the log as html
62
- */
63
- public function render_log() {
64
- if ( ! $this->debug_mode() && ! current_user_can( 'manage_options' ) ) return;
65
- if ( ! WJECF()->get_session('wjecf_log') ) return;
66
-
67
- $this->log( 'debug', "Session: " . print_r( WJECF()->get_session(), true ) );
68
- $this->log( 'debug', 'Current coupons in cart: ' . implode( ", ", WC()->cart->applied_coupons ) );
69
-
70
- if ( $this->log_output ) {
71
- WJECF()->include_template( 'debug/log.php', array( 'log' => $this->log_output ) );
72
- }
73
- }
74
-
75
- /**
76
- * Query argument 'wjecf_debug' toggles rendering of the debug-log for any user (only allowed when debug mode is enabled)
77
- * @return type
78
- */
79
- public function handle_querystring() {
80
- $this->handle_querystring_wjecf_log();
81
- $this->handle_querystring_wjecf_dump_coupon();
82
- }
83
-
84
- public function handle_querystring_wjecf_log() {
85
- // wjecf_log=1 / 0 Enable log on the frontend for guest users
86
- if ( isset( $_GET['wjecf_log'] ) ) {
87
- WJECF()->set_session( 'wjecf_log', $_GET['wjecf_log'] ? true : null );
88
- }
89
- }
90
-
91
- public function handle_querystring_wjecf_dump_coupon() {
92
- // wjecf_dump_coupon=coupon_code Dump the coupon data on the frontend
93
- if ( ! current_user_can( 'manage_options' ) && ! $this->debug_mode() ) return;
94
-
95
- if ( isset( $_GET['wjecf_dump'] ) ) {
96
- $coupon_code = $_GET['wjecf_dump'];
97
- $coupon = new WC_Coupon( $coupon_code );
98
- $wrap_coupon = WJECF_Wrap( $coupon );
99
- $coupon_id = $wrap_coupon->get_id();
100
-
101
- $array = array( 'result' => 'error' );
102
- if ( $coupon_id ) {
103
- $meta = array();
104
- foreach( array_keys( get_post_meta( $coupon_id ) ) as $key ) {
105
- $meta[$key] = $wrap_coupon->get_meta( $key );
106
- }
107
- ksort( $meta );
108
- $array['coupons'] = array(
109
- 'coupon_id' => $coupon_id,
110
- 'coupon_code' => $coupon->get_code(),
111
- 'meta' => $meta,
112
- );
113
- $array['result'] = 'ok';
114
- } else {
115
- }
116
- header( 'Content-Type: application/json' );
117
- echo json_encode( $array );
118
- die();
119
- }
120
- }
121
-
122
- /**
123
- * Is debug mode enabled?
124
- *
125
- * @since 2.6.0
126
- * @return bool
127
- */
128
- public function debug_mode() {
129
- return WJECF()->get_option('debug_mode');
130
- }
131
-
132
-
133
- // ========================
134
- // LOGGING
135
- // ========================
136
-
137
- private $debug_mode = false;
138
- private $log_output = array();
139
-
140
- /**
141
- * Log a message for debugging.
142
- *
143
- * If debug_mode is false; messages with level 'debug' will be ignored.
144
- *
145
- * @param string $level The level of the message. e.g. 'debug' or 'warning'
146
- * @param string $string The message to log
147
- * @param int $skip_backtrace Defaults to 0, amount of items to skip in backtrace to fetch class and method name
148
- */
149
- public function log( $level, $message = null, $skip_backtrace = 0 ) {
150
- if ( ! $this->enable_logging ) return;
151
-
152
- if ( ! $this->debug_mode() && $level === 'debug' ) return;
153
-
154
- //Backwards compatibility; $level was introduced in 2.4.4
155
- if ( $message == null ) {
156
- $message = $level;
157
- $level = 'debug';
158
- } else if ( is_int( $message ) && ! $this->is_valid_log_level( $level ) ) {
159
- $skip_backtrace = $message;
160
- $message = $level;
161
- $level = 'debug';
162
- }
163
-
164
- if ( ! $this->debug_mode() && $level === 'debug' ) return;
165
-
166
- $nth = 1 + $skip_backtrace;
167
- $bt = debug_backtrace();
168
- $class = $bt[$nth]['class'];
169
- $function = $bt[$nth]['function'];
170
-
171
- $row = array(
172
- 'level' => $level,
173
- 'time' => time(),
174
- 'class' => $class,
175
- 'function' => $function,
176
- 'filter' => current_filter(),
177
- 'message' => $message,
178
- );
179
-
180
- $nice_str = $row['filter'] . ' ' . $row['class'] . '::' . $row['function'] . ' ' . $row['message'];
181
-
182
- //Since WC2.7
183
- if ( function_exists( 'wc_get_logger' ) ) {
184
- $logger = wc_get_logger();
185
- $context = array( 'source' => 'WooCommerce Extended Coupon Features' );
186
- $logger->log( $level, $nice_str, $context );
187
- if ( $level !== 'debug' ) error_log( 'WooCommerce Extended Coupon Features ' . $level . ': ' . $row['message'] );
188
- } else {
189
- //Legacy
190
- error_log( $level . ': ' . $nice_str );
191
- }
192
-
193
- $this->log_output[] = $row;
194
- }
195
-
196
- private function is_valid_log_level( $level ) {
197
- return in_array( $level, array( 'debug', 'informational', 'notice', 'warning', 'error', 'critical', 'alert', 'emergency' ) );
198
- }
199
-
200
  }
1
+ <?php
2
+
3
+ defined('ABSPATH') or die();
4
+
5
+ /**
6
+ * Debugging functions for wjecf
7
+ *
8
+ * @since 2.6.0
9
+ */
10
+ class WJECF_Debug extends Abstract_WJECF_Plugin {
11
+
12
+ private $enable_logging = false;
13
+
14
+ public function __construct() {
15
+ $this->set_plugin_data( array(
16
+ 'description' => __( 'Debugging methods for WooCommerce Extended Coupon Features.', 'woocommerce-jos-autocoupon' ),
17
+ 'dependencies' => array(),
18
+ 'can_be_disabled' => false,
19
+ 'hidden' => false
20
+ ) );
21
+ }
22
+
23
+ public function init_hook() {
24
+ $this->enable_logging = true;
25
+
26
+ add_action( 'wp_loaded', array( $this, 'handle_querystring' ), 90 );
27
+ add_action( 'wp_footer', array( $this, 'render_log' ) ); //Log
28
+ }
29
+
30
+ public function init_admin_hook() {
31
+ if ( current_user_can( 'manage_options' ) && $this->debug_mode() )
32
+ {
33
+ if ( $wjecf_admin = WJECF()->get_plugin('WJECF_Admin') ) {
34
+ $msg = __( 'Debug mode is enabled. Please disable it when you\'re done debugging.', 'woocommerce-jos-autocoupon' );
35
+ $wjecf_admin_settings = WJECF()->get_plugin('WJECF_Admin_Settings');
36
+ if ($wjecf_admin_settings) {
37
+ $msg .= ' <a href="' . $wjecf_admin_settings ->get_settings_page_url() . '">' . __( 'Go to settings page', 'woocommerce-jos-autocoupon' ) . '</a>';
38
+ }
39
+ $wjecf_admin->enqueue_notice( $msg, 'notice-warning' );
40
+ }
41
+ add_action( 'wjecf_coupon_metabox_misc', array( $this, 'wjecf_coupon_metabox_misc' ), 20, 2 );
42
+ }
43
+ }
44
+
45
+ // ========================
46
+ // ACTION HOOKS
47
+ // ========================
48
+
49
+ public function wjecf_coupon_metabox_misc( $thepostid, $post ) {
50
+ echo "<h3>" . esc_html( __( 'Debug', 'woocommerce-jos-autocoupon' ) ). "</h3>\n";
51
+
52
+ $coupon_code = WJECF_Wrap( WJECF_WC()->get_coupon( $post ) )->get_code();
53
+ $url = add_query_arg( array( 'wjecf_dump' => $coupon_code ), get_site_url() );
54
+ $text = __( 'Coupon data as json', 'woocommerce-jos-autocoupon');
55
+ echo '<p>';
56
+ printf( '<a href="%s" target="_blank">%s</a>', $url, $text );
57
+ echo '</p>';
58
+ }
59
+
60
+ /**
61
+ * Output the log as html
62
+ */
63
+ public function render_log() {
64
+ if ( ! $this->debug_mode() && ! current_user_can( 'manage_options' ) ) return;
65
+ if ( ! WJECF()->get_session('wjecf_log') ) return;
66
+
67
+ $this->log( 'debug', "Session: " . print_r( WJECF()->get_session(), true ) );
68
+ $this->log( 'debug', 'Current coupons in cart: ' . implode( ", ", WC()->cart->applied_coupons ) );
69
+
70
+ if ( $this->log_output ) {
71
+ WJECF()->include_template( 'debug/log.php', array( 'log' => $this->log_output ) );
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Query argument 'wjecf_debug' toggles rendering of the debug-log for any user (only allowed when debug mode is enabled)
77
+ * @return type
78
+ */
79
+ public function handle_querystring() {
80
+ $this->handle_querystring_wjecf_log();
81
+ $this->handle_querystring_wjecf_dump_coupon();
82
+ }
83
+
84
+ public function handle_querystring_wjecf_log() {
85
+ // wjecf_log=1 / 0 Enable log on the frontend for guest users
86
+ if ( isset( $_GET['wjecf_log'] ) ) {
87
+ WJECF()->set_session( 'wjecf_log', $_GET['wjecf_log'] ? true : null );
88
+ }
89
+ }
90
+
91
+ public function handle_querystring_wjecf_dump_coupon() {
92
+ // wjecf_dump_coupon=coupon_code Dump the coupon data on the frontend
93
+ if ( ! current_user_can( 'manage_options' ) && ! $this->debug_mode() ) return;
94
+
95
+ if ( isset( $_GET['wjecf_dump'] ) ) {
96
+ $coupon_code = $_GET['wjecf_dump'];
97
+ $coupon = new WC_Coupon( $coupon_code );
98
+ $wrap_coupon = WJECF_Wrap( $coupon );
99
+ $coupon_id = $wrap_coupon->get_id();
100
+
101
+ $array = array( 'result' => 'error' );
102
+ if ( $coupon_id ) {
103
+ $meta = array();
104
+ foreach( array_keys( get_post_meta( $coupon_id ) ) as $key ) {
105
+ $meta[$key] = $wrap_coupon->get_meta( $key );
106
+ }
107
+ ksort( $meta );
108
+ $array['coupons'] = array(
109
+ 'coupon_id' => $coupon_id,
110
+ 'coupon_code' => $coupon->get_code(),
111
+ 'meta' => $meta,
112
+ );
113
+ $array['result'] = 'ok';
114
+ } else {
115
+ }
116
+ header( 'Content-Type: application/json' );
117
+ echo json_encode( $array );
118
+ die();
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Is debug mode enabled?
124
+ *
125
+ * @since 2.6.0
126
+ * @return bool
127
+ */
128
+ public function debug_mode() {
129
+ return WJECF()->get_option('debug_mode');
130
+ }
131
+
132
+
133
+ // ========================
134
+ // LOGGING
135
+ // ========================
136
+
137
+ private $debug_mode = false;
138
+ private $log_output = array();
139
+
140
+ /**
141
+ * Log a message for debugging.
142
+ *
143
+ * If debug_mode is false; messages with level 'debug' will be ignored.
144
+ *
145
+ * @param string $level The level of the message. e.g. 'debug' or 'warning'
146
+ * @param string $string The message to log
147
+ * @param int $skip_backtrace Defaults to 0, amount of items to skip in backtrace to fetch class and method name
148
+ */
149
+ public function log( $level, $message = null, $skip_backtrace = 0 ) {
150
+ if ( ! $this->enable_logging ) return;
151
+
152
+ if ( ! $this->debug_mode() && $level === 'debug' ) return;
153
+
154
+ //Backwards compatibility; $level was introduced in 2.4.4
155
+ if ( $message == null ) {
156
+ $message = $level;
157
+ $level = 'debug';
158
+ } else if ( is_int( $message ) && ! $this->is_valid_log_level( $level ) ) {
159
+ $skip_backtrace = $message;
160
+ $message = $level;
161
+ $level = 'debug';
162
+ }
163
+
164
+ if ( ! $this->debug_mode() && $level === 'debug' ) return;
165
+
166
+ $nth = 1 + $skip_backtrace;
167
+ $bt = debug_backtrace();
168
+ $class = $bt[$nth]['class'];
169
+ $function = $bt[$nth]['function'];
170
+
171
+ $row = array(
172
+ 'level' => $level,
173
+ 'time' => time(),
174
+ 'class' => $class,
175
+ 'function' => $function,
176
+ 'filter' => current_filter(),
177
+ 'message' => $message,
178
+ );
179
+
180
+ $nice_str = $row['filter'] . ' ' . $row['class'] . '::' . $row['function'] . ' ' . $row['message'];
181
+
182
+ //Since WC2.7
183
+ if ( function_exists( 'wc_get_logger' ) ) {
184
+ $logger = wc_get_logger();
185
+ $context = array( 'source' => 'WooCommerce Extended Coupon Features' );
186
+ $logger->log( $level, $nice_str, $context );
187
+ if ( $level !== 'debug' ) error_log( 'WooCommerce Extended Coupon Features ' . $level . ': ' . $row['message'] );
188
+ } else {
189
+ //Legacy
190
+ error_log( $level . ': ' . $nice_str );
191
+ }
192
+
193
+ $this->log_output[] = $row;
194
+ }
195
+
196
+ private function is_valid_log_level( $level ) {
197
+ return in_array( $level, array( 'debug', 'informational', 'notice', 'warning', 'error', 'critical', 'alert', 'emergency' ) );
198
+ }
199
+
200
  }
includes/WJECF_Debug_CLI.php CHANGED
@@ -1,267 +1,267 @@
1
- <?php
2
-
3
- defined('ABSPATH') or die();
4
-
5
- class WJECF_Debug_CLI extends WP_CLI_Command {
6
-
7
- public static function add_command() {
8
- if ( defined( 'WP_CLI' ) && WP_CLI ) {
9
- WP_CLI::add_command( 'wjecf', __CLASS__ );
10
- }
11
- }
12
-
13
- public function plugin_info() {
14
- WP_CLI::log( sprintf("WJECF Version: %s", WJECF()->plugin_version() ) );
15
- WP_CLI::log( sprintf("WJECF File: %s", WJECF()->plugin_file() ) );
16
- WP_CLI::log( sprintf("WJECF Url: %s", WJECF()->plugin_url() ) );
17
-
18
- }
19
-
20
- /**
21
- * Test API functions for one or more coupons
22
- *
23
- * ## OPTIONS
24
- *
25
- * [couponcode, ...]
26
- * : The coupon codes for which to run the tests. If omitted the test will be executed for all auto coupons.
27
- *
28
- * @param array $args CLI arguments
29
- */
30
- public function test_api( $args ) {
31
- require_once( 'pro/wjecf-pro-api-example.php' );
32
-
33
- if ( count( $args ) > 0 ) {
34
- $all = $args;
35
- } else {
36
- $all = WJECF_API()->get_all_auto_coupons();
37
- }
38
-
39
- foreach( $all as $coupon ) {
40
- $values = WJECF_API_Test_Coupon( $coupon );
41
- foreach( $values as $key => $value ) {
42
- WP_CLI::log( sprintf( "%s: %s", $key, print_r( $value, true ) ) );
43
- }
44
- }
45
- }
46
-
47
- /**
48
- * Check whether the customer has ordered before.
49
- *
50
- * ## OPTIONS
51
- *
52
- * <customer>
53
- * : The email-address or userid of the customer
54
- *
55
- * @param array $args CLI arguments
56
- */
57
- public function test_first_order( $args )
58
- {
59
- if ( count( $args ) <= 0 ) {
60
- WP_CLI::error( sprintf("Please provide an email-address" ) );
61
- return;
62
- }
63
- $customer = $args[0];
64
- $is_first = ! WJECF()->has_customer_ordered_before( $customer );
65
- WP_CLI::log( sprintf("First order for %s: %s", $customer, $is_first ? 'yes' : 'no' ) );
66
- }
67
-
68
- /**
69
- * Test the sanitizers
70
- */
71
- public function test_sanitizer() {
72
- $array_tests = array(
73
- array(),
74
- array(0),
75
- array(1,0,2.0,"3")
76
- );
77
- foreach( $array_tests as $array ) {
78
- $comma = join( ',', $array );
79
- $ints_array = array_map( 'intval', $array);
80
- $ints_comma = join( ',', $ints_array );
81
-
82
- $this->single_test_sanitizer( $comma, 'int[]', $ints_array );
83
- $this->single_test_sanitizer( $array, 'int[]', $ints_array );
84
-
85
- $this->single_test_sanitizer( $comma, 'int,', $ints_comma );
86
- $this->single_test_sanitizer( $array, 'int,', $ints_comma );
87
- }
88
-
89
- $this->single_test_sanitizer( null, 'int[]', array() );
90
-
91
- $this->single_test_sanitizer( null, 'int,', '' );
92
- $this->single_test_sanitizer( '', 'int,', '' );
93
-
94
- $this->single_test_sanitizer( null, 'int', null );
95
- $this->single_test_sanitizer( '', 'int', null );
96
- $this->single_test_sanitizer( '1.234', 'int', 1 );
97
-
98
- $this->single_test_sanitizer( '1.234', 'decimal', '1.234' );
99
- $this->single_test_sanitizer( null, 'decimal', null );
100
- }
101
-
102
- private function single_test_sanitizer( $value, $rule, $expected ) {
103
- //$msg = sprintf( "Sanitized %s: %s to %s", $rule, print_r( $value, true ), print_r( $expected, true ) );
104
- $msg = sprintf(
105
- "Sanitized %s: %s to %s",
106
- $rule, $value === null ? 'null' : $value,
107
- $expected === null ? 'null' : $expected
108
- );
109
- $this->assert( WJECF()->sanitizer()->sanitize( $value, $rule ) === $expected, $msg);
110
-
111
- }
112
- /**
113
- * Tests if the wrappers return the same values for WooCommerce 3.0 and older WC versions
114
- */
115
- public function test_wrappers() {
116
-
117
- $args = array(
118
- 'posts_per_page' => -1,
119
- 'orderby' => 'title',
120
- 'order' => 'asc',
121
- 'post_type' => 'shop_coupon',
122
- 'post_status' => 'publish',
123
- );
124
- $posts = get_posts( $args );
125
- foreach ( $posts as $post ) {
126
- $coupon = WJECF_WC()->get_coupon( $post->ID );
127
- $this->execute_test_for_coupon( $coupon );
128
- }
129
-
130
- $args = array(
131
- 'posts_per_page' => -1,
132
- 'orderby' => 'title',
133
- 'order' => 'asc',
134
- 'post_type' => array( 'product', 'product_variation' ),
135
- 'post_status' => 'publish',
136
- );
137
- $posts = get_posts( $args );
138
- foreach ( $posts as $post ) {
139
- $product = wc_get_product( $post->ID );
140
- $this->execute_test_for_product( $product );
141
- }
142
-
143
- $msg = sprintf("%d tests executed. Fails: %d Passes: %d", $this->tests, $this->fails, $this->passes );
144
- if ( $this->fails != 0 )
145
- WP_CLI::error( $msg );
146
- else
147
- WP_CLI::success( $msg );
148
-
149
- }
150
-
151
- protected function execute_test_for_coupon( $coupon ) {
152
- //WP_CLI::log( sprintf("Coupon fields: %s", print_r( $coupon->coupon_custom_fields ) ) );
153
- //WP_CLI::log( sprintf("Coupon fields: %s", print_r( $coupon->get_meta_data() ) ) );
154
-
155
- $meta_keys = array_keys( $coupon->coupon_custom_fields );
156
- $meta_keys[] = '__non_existing__';
157
-
158
- //WP_CLI::log( sprintf("Coupon fields: %s", print_r( $coupon->coupon_custom_fields ) ) );
159
- //WP_CLI::log( sprintf("Coupon fields: %s", print_r( $coupon->get_meta_data() ) ) );
160
-
161
- $wrap_leg = WJECF_WC()->wrap( $coupon, false ); $wrap_leg->use_wc27 = false;
162
- $wrap_new = WJECF_WC()->wrap( $coupon, false ); $wrap_new->use_wc27 = true;
163
-
164
- $results = array();
165
- $results['new'] = $wrap_new->get_id();
166
- $results['legacy'] = $wrap_leg->get_id();
167
- $results['old'] = $coupon->id;
168
- $this->assert_same( $results, sprintf('Same coupon id %s', current( $results ) ) );
169
-
170
- foreach( $meta_keys as $meta_key ) {
171
- for($i=1; $i>=0; $i--) {
172
- $single = $i>0;
173
-
174
- $results = array();
175
- $results['new'] = $wrap_new->get_meta( $meta_key, $single );
176
- $results['legacy'] = $wrap_leg->get_meta( $meta_key, $single );
177
- $results['old'] = get_post_meta( $coupon->id, $meta_key, $single );
178
- $this->assert_same( $results, sprintf('%s: Same value %s', $meta_key, $single ? 'single' : 'multi' ) );
179
-
180
- }
181
- }
182
- }
183
-
184
- protected function execute_test_for_product( $product ) {
185
- $wrap_leg = WJECF_WC()->wrap( $product, false ); $wrap_leg->use_wc27 = false;
186
- $wrap_new = WJECF_WC()->wrap( $product, false ); $wrap_new->use_wc27 = true;
187
-
188
- if ($product instanceof WC_Product_Variation) {
189
- $results = array();
190
- $results['new'] = $wrap_new->get_product_or_variation_id();
191
- $results['legacy'] = $wrap_leg->get_product_or_variation_id();
192
- $results['old'] = $product->variation_id;
193
- $this->assert_same( $results, sprintf('Same variation id %s', current( $results ) ) );
194
-
195
- $results = array();
196
- $results['new'] = $wrap_new->get_variable_product_id();
197
- $results['legacy'] = $wrap_leg->get_variable_product_id();
198
- $results['old'] = $wrap_leg->get_variable_product_id();
199
- $this->assert_same( $results, sprintf('Same variable product (parent) id %s', current( $results ) ) );
200
- } else {
201
- $results = array();
202
- $results['new'] = $wrap_new->get_id();
203
- $results['legacy'] = $wrap_leg->get_id();
204
- $results['old'] = $product->id;
205
- $this->assert_same( $results, sprintf('Same product id %s', current( $results ) ) );
206
- }
207
-
208
- $meta_keys = array( 'total_sales', '_price', '__non_existing__');
209
- foreach( $meta_keys as $meta_key ) {
210
- for($i=1; $i>=0; $i--) {
211
- $single = $i>0;
212
-
213
- $results = array();
214
- $results['new'] = $wrap_new->get_field( $meta_key, $single );
215
- $results['legacy'] = $wrap_leg->get_field( $meta_key, $single );
216
- $results['old'] = get_post_meta( $wrap_new->get_product_or_variation_id(), $meta_key, $single );
217
- $this->assert_same( $results, sprintf('%s: Same value %s. %s', $meta_key, $single ? 'single' : 'multi', $this->dd( current( $results ) ) ) );
218
-
219
- }
220
- }
221
-
222
-
223
-
224
-
225
- }
226
-
227
- protected $tests = 0;
228
- protected $fails = 0;
229
- protected $passess = 0;
230
-
231
- protected function assert( $true, $test_description ) {
232
- if ( $true !== true ) {
233
- WP_CLI::error( $test_description );
234
- die();
235
- }
236
- WP_CLI::success( $test_description );
237
- }
238
-
239
- protected function assert_same( $results, $test_description ) {
240
- $success = true;
241
- foreach( $results as $result ) {
242
- if ( isset( $prev_result ) && $result !== $prev_result ) {
243
- $success = false;
244
- break;
245
- }
246
- $prev_result = $result;
247
- }
248
-
249
- $this->tests++;
250
-
251
- if ( $success ) {
252
- $this->passes++;
253
- WP_CLI::success( $test_description );
254
- } else {
255
- $this->fails++;
256
- foreach( $results as $key => $result ) {
257
- WP_CLI::log( sprintf("%s : %s", $key, $this->dd( $result ) ) );
258
- }
259
- WP_CLI::error( $test_description );
260
- }
261
- }
262
-
263
- protected function dd( $variable ) {
264
- return print_r( $variable, true );
265
- }
266
-
267
  }
1
+ <?php
2
+
3
+ defined('ABSPATH') or die();
4
+
5
+ class WJECF_Debug_CLI extends WP_CLI_Command {
6
+
7
+ public static function add_command() {
8
+ if ( defined( 'WP_CLI' ) && WP_CLI ) {
9
+ WP_CLI::add_command( 'wjecf', __CLASS__ );
10
+ }
11
+ }
12
+
13
+ public function plugin_info() {
14
+ WP_CLI::log( sprintf("WJECF Version: %s", WJECF()->plugin_version() ) );
15
+ WP_CLI::log( sprintf("WJECF File: %s", WJECF()->plugin_file() ) );
16
+ WP_CLI::log( sprintf("WJECF Url: %s", WJECF()->plugin_url() ) );
17
+
18
+ }
19
+
20
+ /**
21
+ * Test API functions for one or more coupons
22
+ *
23
+ * ## OPTIONS
24
+ *
25
+ * [couponcode, ...]
26
+ * : The coupon codes for which to run the tests. If omitted the test will be executed for all auto coupons.
27
+ *
28
+ * @param array $args CLI arguments
29
+ */
30
+ public function test_api( $args ) {
31
+ require_once( 'pro/wjecf-pro-api-example.php' );
32
+
33
+ if ( count( $args ) > 0 ) {
34
+ $all = $args;
35
+ } else {
36
+ $all = WJECF_API()->get_all_auto_coupons();
37
+ }
38
+
39
+ foreach( $all as $coupon ) {
40
+ $values = WJECF_API_Test_Coupon( $coupon );
41
+ foreach( $values as $key => $value ) {
42
+ WP_CLI::log( sprintf( "%s: %s", $key, print_r( $value, true ) ) );
43
+ }
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Check whether the customer has ordered before.
49
+ *
50
+ * ## OPTIONS
51
+ *
52
+ * <customer>
53
+ * : The email-address or userid of the customer
54
+ *
55
+ * @param array $args CLI arguments
56
+ */
57
+ public function test_first_order( $args )
58
+ {
59
+ if ( count( $args ) <= 0 ) {
60
+ WP_CLI::error( sprintf("Please provide an email-address" ) );
61
+ return;
62
+ }
63
+ $customer = $args[0];
64
+ $is_first = ! WJECF()->has_customer_ordered_before( $customer );
65
+ WP_CLI::log( sprintf("First order for %s: %s", $customer, $is_first ? 'yes' : 'no' ) );
66
+ }
67
+
68
+ /**
69
+ * Test the sanitizers
70
+ */
71
+ public function test_sanitizer() {
72
+ $array_tests = array(
73
+ array(),
74
+ array(0),
75
+ array(1,0,2.0,"3")
76
+ );
77
+ foreach( $array_tests as $array ) {
78
+ $comma = join( ',', $array );
79
+ $ints_array = array_map( 'intval', $array);
80
+ $ints_comma = join( ',', $ints_array );
81
+
82
+ $this->single_test_sanitizer( $comma, 'int[]', $ints_array );
83
+ $this->single_test_sanitizer( $array, 'int[]', $ints_array );
84
+
85
+ $this->single_test_sanitizer( $comma, 'int,', $ints_comma );
86
+ $this->single_test_sanitizer( $array, 'int,', $ints_comma );
87
+ }
88
+
89
+ $this->single_test_sanitizer( null, 'int[]', array() );
90
+
91
+ $this->single_test_sanitizer( null, 'int,', '' );
92
+ $this->single_test_sanitizer( '', 'int,', '' );
93
+
94
+ $this->single_test_sanitizer( null, 'int', null );
95
+ $this->single_test_sanitizer( '', 'int', null );
96
+ $this->single_test_sanitizer( '1.234', 'int', 1 );
97
+
98
+ $this->single_test_sanitizer( '1.234', 'decimal', '1.234' );
99
+ $this->single_test_sanitizer( null, 'decimal', null );
100
+ }
101
+
102
+ private function single_test_sanitizer( $value, $rule, $expected ) {
103
+ //$msg = sprintf( "Sanitized %s: %s to %s", $rule, print_r( $value, true ), print_r( $expected, true ) );
104
+ $msg = sprintf(
105
+ "Sanitized %s: %s to %s",
106
+ $rule, $value === null ? 'null' : $value,
107
+ $expected === null ? 'null' : $expected
108
+ );
109
+ $this->assert( WJECF()->sanitizer()->sanitize( $value, $rule ) === $expected, $msg);
110
+
111
+ }
112
+ /**
113
+ * Tests if the wrappers return the same values for WooCommerce 3.0 and older WC versions
114
+ */
115
+ public function test_wrappers() {
116
+
117
+ $args = array(
118
+ 'posts_per_page' => -1,
119
+ 'orderby' => 'title',
120
+ 'order' => 'asc',
121
+ 'post_type' => 'shop_coupon',
122
+ 'post_status' => 'publish',
123
+ );
124
+ $posts = get_posts( $args );
125
+ foreach ( $posts as $post ) {
126
+ $coupon = WJECF_WC()->get_coupon( $post->ID );
127
+ $this->execute_test_for_coupon( $coupon );
128
+ }
129
+
130
+ $args = array(
131
+ 'posts_per_page' => -1,
132
+ 'orderby' => 'title',
133
+ 'order' => 'asc',
134
+ 'post_type' => array( 'product', 'product_variation' ),
135
+ 'post_status' => 'publish',
136
+ );
137
+ $posts = get_posts( $args );
138
+ foreach ( $posts as $post ) {
139
+ $product = wc_get_product( $post->ID );
140
+ $this->execute_test_for_product( $product );
141
+ }
142
+
143
+ $msg = sprintf("%d tests executed. Fails: %d Passes: %d", $this->tests, $this->fails, $this->passes );
144
+ if ( $this->fails != 0 )
145
+ WP_CLI::error( $msg );
146
+ else
147
+ WP_CLI::success( $msg );
148
+
149
+ }
150
+
151
+ protected function execute_test_for_coupon( $coupon ) {
152
+ //WP_CLI::log( sprintf("Coupon fields: %s", print_r( $coupon->coupon_custom_fields ) ) );
153
+ //WP_CLI::log( sprintf("Coupon fields: %s", print_r( $coupon->get_meta_data() ) ) );
154
+
155
+ $meta_keys = array_keys( $coupon->coupon_custom_fields );
156
+ $meta_keys[] = '__non_existing__';
157
+
158
+ //WP_CLI::log( sprintf("Coupon fields: %s", print_r( $coupon->coupon_custom_fields ) ) );
159
+ //WP_CLI::log( sprintf("Coupon fields: %s", print_r( $coupon->get_meta_data() ) ) );
160
+
161
+ $wrap_leg = WJECF_WC()->wrap( $coupon, false ); $wrap_leg->use_wc27 = false;
162
+ $wrap_new = WJECF_WC()->wrap( $coupon, false ); $wrap_new->use_wc27 = true;
163
+
164
+ $results = array();
165
+ $results['new'] = $wrap_new->get_id();
166
+ $results['legacy'] = $wrap_leg->get_id();
167
+ $results['old'] = $coupon->id;
168
+ $this->assert_same( $results, sprintf('Same coupon id %s', current( $results ) ) );
169
+
170
+ foreach( $meta_keys as $meta_key ) {
171
+ for($i=1; $i>=0; $i--) {
172
+ $single = $i>0;
173
+
174
+ $results = array();
175
+ $results['new'] = $wrap_new->get_meta( $meta_key, $single );
176
+ $results['legacy'] = $wrap_leg->get_meta( $meta_key, $single );
177
+ $results['old'] = get_post_meta( $coupon->id, $meta_key, $single );
178
+ $this->assert_same( $results, sprintf('%s: Same value %s', $meta_key, $single ? 'single' : 'multi' ) );
179
+
180
+ }
181
+ }
182
+ }
183
+
184
+ protected function execute_test_for_product( $product ) {
185
+ $wrap_leg = WJECF_WC()->wrap( $product, false ); $wrap_leg->use_wc27 = false;
186
+ $wrap_new = WJECF_WC()->wrap( $product, false ); $wrap_new->use_wc27 = true;
187
+
188
+ if ($product instanceof WC_Product_Variation) {
189
+ $results = array();
190
+ $results['new'] = $wrap_new->get_product_or_variation_id();
191
+ $results['legacy'] = $wrap_leg->get_product_or_variation_id();
192
+ $results['old'] = $product->variation_id;
193
+ $this->assert_same( $results, sprintf('Same variation id %s', current( $results ) ) );
194
+
195
+ $results = array();
196
+ $results['new'] = $wrap_new->get_variable_product_id();
197
+ $results['legacy'] = $wrap_leg->get_variable_product_id();
198
+ $results['old'] = $wrap_leg->get_variable_product_id();
199
+ $this->assert_same( $results, sprintf('Same variable product (parent) id %s', current( $results ) ) );
200
+ } else {
201
+ $results = array();
202
+ $results['new'] = $wrap_new->get_id();
203
+ $results['legacy'] = $wrap_leg->get_id();
204
+ $results['old'] = $product->id;
205
+ $this->assert_same( $results, sprintf('Same product id %s', current( $results ) ) );
206
+ }
207
+
208
+ $meta_keys = array( 'total_sales', '_price', '__non_existing__');
209
+ foreach( $meta_keys as $meta_key ) {
210
+ for($i=1; $i>=0; $i--) {
211
+ $single = $i>0;
212
+
213
+ $results = array();
214
+ $results['new'] = $wrap_new->get_field( $meta_key, $single );
215
+ $results['legacy'] = $wrap_leg->get_field( $meta_key, $single );
216
+ $results['old'] = get_post_meta( $wrap_new->get_product_or_variation_id(), $meta_key, $single );
217
+ $this->assert_same( $results, sprintf('%s: Same value %s. %s', $meta_key, $single ? 'single' : 'multi', $this->dd( current( $results ) ) ) );
218
+
219
+ }
220
+ }
221
+
222
+
223
+
224
+
225
+ }
226
+
227
+ protected $tests = 0;
228
+ protected $fails = 0;
229
+ protected $passess = 0;
230
+
231
+ protected function assert( $true, $test_description ) {
232
+ if ( $true !== true ) {
233
+ WP_CLI::error( $test_description );
234
+ die();
235
+ }
236
+ WP_CLI::success( $test_description );
237
+ }
238
+
239
+ protected function assert_same( $results, $test_description ) {
240
+ $success = true;
241
+ foreach( $results as $result ) {
242
+ if ( isset( $prev_result ) && $result !== $prev_result ) {
243
+ $success = false;
244
+ break;
245
+ }
246
+ $prev_result = $result;
247
+ }
248
+
249
+ $this->tests++;
250
+
251
+ if ( $success ) {
252
+ $this->passes++;
253
+ WP_CLI::success( $test_description );
254
+ } else {
255
+ $this->fails++;
256
+ foreach( $results as $key => $result ) {
257
+ WP_CLI::log( sprintf("%s : %s", $key, $this->dd( $result ) ) );
258
+ }
259
+ WP_CLI::error( $test_description );
260
+ }
261
+ }
262
+
263
+ protected function dd( $variable ) {
264
+ return print_r( $variable, true );
265
+ }
266
+
267
  }
includes/WJECF_Options.php CHANGED
@@ -1,99 +1,99 @@
1
- <?php
2
-
3
-
4
- /**
5
- * Manages an Array of values that can be saved in the WP options table
6
- */
7
- class WJECF_Options {
8
-
9
- private $options = null; // [ 'key' => 'value' ]
10
- private $defaults = array(); // [ 'key' => 'value' ]
11
-
12
- private $option_name;
13
-
14
- /**
15
- * Description
16
- * @param string $option_name
17
- * @param array $defaults Default values
18
- * @param bool $auto_reload_on_save If true, the options shall be reloaded after safe
19
- * @return type
20
- */
21
- public function __construct( $option_name, $defaults = array(), $auto_reload_on_save = true ) {
22
- $this->option_name = $option_name;
23
- $this->defaults = $defaults;
24
-
25
- if ( $auto_reload_on_save ) {
26
- //reload options directly after save
27
- add_action( 'update_option_' . $option_name, array( $this, 'invalidate' ), 1, 0 );
28
- }
29
- }
30
-
31
- /**
32
- * Forces reloading of the options; invalidates the current values
33
- * @return void
34
- */
35
- public function invalidate() {
36
- $this->options = null;
37
- }
38
-
39
- /**
40
- * The option_name used in the WP options table
41
- * @return string
42
- */
43
- public function get_option_name() {
44
- return $this->option_name;
45
- }
46
-
47
- /**
48
- * Loads the options from the database
49
- * @return void
50
- */
51
- protected function load_options() {
52
- $options = get_option( $this->option_name );
53
- if ( ! is_array( $options ) || empty ( $options ) ) {
54
- $this->options = $this->defaults;
55
- } else {
56
- $this->options = array_merge( $this->defaults, $options );
57
- }
58
- }
59
-
60
- /**
61
- * Get option [ $key ]. If $key is not given, return all options.
62
- * @param string $key
63
- * @param mixed $default The default value to return (only if $key is given)
64
- * @return mixed The value of the option
65
- */
66
- public function get( $key = null, $default = null ) {
67
- if ( ! isset( $this->options ) ) {
68
- $this->load_options();
69
- }
70
-
71
- //Return all options
72
- if ( is_null( $key ) ) {
73
- return $this->options;
74
- }
75
-
76
- //Return option[$key]
77
- $value = isset( $this->options[$key] ) ? $this->options[$key] : $default;
78
- return $value;
79
- }
80
-
81
- /**
82
- * Set option [ $key ]
83
- * @param string $key
84
- * @param mixed $value
85
- */
86
- public function set( $key, $value ) {
87
- if ( ! isset( $this->options ) ) {
88
- $this->load_options();
89
- }
90
- $this->options[$key] = $value;
91
- }
92
-
93
- /**
94
- * Save options to the database
95
- */
96
- public function save() {
97
- update_option( $this->option_name , $this->options, false );
98
- }
99
  }
1
+ <?php
2
+
3
+
4
+ /**
5
+ * Manages an Array of values that can be saved in the WP options table
6
+ */
7
+ class WJECF_Options {
8
+
9
+ private $options = null; // [ 'key' => 'value' ]
10
+ private $defaults = array(); // [ 'key' => 'value' ]
11
+
12
+ private $option_name;
13
+
14
+ /**
15
+ * Description
16
+ * @param string $option_name
17
+ * @param array $defaults Default values
18
+ * @param bool $auto_reload_on_save If true, the options shall be reloaded after safe
19
+ * @return type
20
+ */
21
+ public function __construct( $option_name, $defaults = array(), $auto_reload_on_save = true ) {
22
+ $this->option_name = $option_name;
23
+ $this->defaults = $defaults;
24
+
25
+ if ( $auto_reload_on_save ) {
26
+ //reload options directly after save
27
+ add_action( 'update_option_' . $option_name, array( $this, 'invalidate' ), 1, 0 );
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Forces reloading of the options; invalidates the current values
33
+ * @return void
34
+ */
35
+ public function invalidate() {
36
+ $this->options = null;
37
+ }
38
+
39
+ /**
40
+ * The option_name used in the WP options table
41
+ * @return string
42
+ */
43
+ public function get_option_name() {
44
+ return $this->option_name;
45
+ }
46
+
47
+ /**
48
+ * Loads the options from the database
49
+ * @return void
50
+ */
51
+ protected function load_options() {
52
+ $options = get_option( $this->option_name );
53
+ if ( ! is_array( $options ) || empty ( $options ) ) {
54
+ $this->options = $this->defaults;
55
+ } else {
56
+ $this->options = array_merge( $this->defaults, $options );
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Get option [ $key ]. If $key is not given, return all options.
62
+ * @param string $key
63
+ * @param mixed $default The default value to return (only if $key is given)
64
+ * @return mixed The value of the option
65
+ */
66
+ public function get( $key = null, $default = null ) {
67
+ if ( ! isset( $this->options ) ) {
68
+ $this->load_options();
69
+ }
70
+
71
+ //Return all options
72
+ if ( is_null( $key ) ) {
73
+ return $this->options;
74
+ }
75
+
76
+ //Return option[$key]
77
+ $value = isset( $this->options[$key] ) ? $this->options[$key] : $default;
78
+ return $value;
79
+ }
80
+
81
+ /**
82
+ * Set option [ $key ]
83
+ * @param string $key
84
+ * @param mixed $value
85
+ */
86
+ public function set( $key, $value ) {
87
+ if ( ! isset( $this->options ) ) {
88
+ $this->load_options();
89
+ }
90
+ $this->options[$key] = $value;
91
+ }
92
+
93
+ /**
94
+ * Save options to the database
95
+ */
96
+ public function save() {
97
+ update_option( $this->option_name , $this->options, false );
98
+ }
99
  }
includes/WJECF_Sanitizer.php CHANGED
@@ -1,72 +1,72 @@
1
- <?php
2
-
3
- class WJECF_Sanitizer {
4
-
5
- /**
6
- * Singleton Instance
7
- *
8
- * @static
9
- * @return Singleton Instance
10
- */
11
- public static function instance() {
12
- if ( is_null( self::$_instance ) ) {
13
- self::$_instance = new self();
14
- }
15
- return self::$_instance;
16
- }
17
- protected static $_instance = null;
18
-
19
- /**
20
- * Sanitizes form input for database output
21
- *
22
- * @param mixed $value
23
- * @param string $requested_format The output format requested
24
- * @param mixed|null $fallback_value Value to return in case of invalid value
25
- * @return mixed Sanitized
26
- */
27
- public function sanitize( $value, $requested_format, $fallback_value = null ) {
28
-
29
- switch ($requested_format) {
30
- case '':
31
- return (string) $value;
32
-
33
- case 'html':
34
- return wp_kses_post( $value );
35
-
36
- case 'clean':
37
- //applies sanitize_text_field; or recursively if it's an array
38
- return wc_clean( $value );
39
-
40
- case 'int,':
41
- case 'int[]':
42
- if ( is_array( $value ) ) {
43
- $values = $value;
44
- } elseif ( $value === '' || $value === null || $value === false ) {
45
- $values = array();
46
- } else {
47
- $values = explode( ',', $value ); // int[] also accepts comma separated string
48
- }
49
- $retval = array();
50
- foreach( $values as $value) {
51
- $sane = $this->sanitize( $value, 'int' );
52
- if ( $sane !== null ) $retval[] = $sane;
53
- }
54
- if ($requested_format === 'int,') {
55
- return implode( ',', $retval );
56
- }
57
- return $retval;
58
-
59
- case 'int':
60
- return is_numeric( $value ) ? intval( $value ) : $fallback_value;
61
-
62
- case 'yesno':
63
- return $value === 'yes' ? 'yes' : 'no';
64
-
65
- case 'decimal':
66
- $value = wc_format_decimal( $value );
67
- return ( $value === '' || $value === false ) ? $fallback_value : $value;
68
- }
69
-
70
- throw new Exception("Unknown sanitization rule " . $requested_format);
71
- }
72
  }
1
+ <?php
2
+
3
+ class WJECF_Sanitizer {
4
+
5
+ /**
6
+ * Singleton Instance
7
+ *
8
+ * @static
9
+ * @return Singleton Instance
10
+ */
11
+ public static function instance() {
12
+ if ( is_null( self::$_instance ) ) {
13
+ self::$_instance = new self();
14
+ }
15
+ return self::$_instance;
16
+ }
17
+ protected static $_instance = null;
18
+
19
+ /**
20
+ * Sanitizes form input for database output
21
+ *
22
+ * @param mixed $value
23
+ * @param string $requested_format The output format requested
24
+ * @param mixed|null $fallback_value Value to return in case of invalid value
25
+ * @return mixed Sanitized
26
+ */
27
+ public function sanitize( $value, $requested_format, $fallback_value = null ) {
28
+
29
+ switch ($requested_format) {
30
+ case '':
31
+ return (string) $value;
32
+
33
+ case 'html':
34
+ return wp_kses_post( $value );
35
+
36
+ case 'clean':
37
+ //applies sanitize_text_field; or recursively if it's an array
38
+ return wc_clean( $value );
39
+
40
+ case 'int,':
41
+ case 'int[]':
42
+ if ( is_array( $value ) ) {
43
+ $values = $value;
44
+ } elseif ( $value === '' || $value === null || $value === false ) {
45
+ $values = array();
46
+ } else {
47
+ $values = explode( ',', $value ); // int[] also accepts comma separated string
48
+ }
49
+ $retval = array();
50
+ foreach( $values as $value) {
51
+ $sane = $this->sanitize( $value, 'int' );
52
+ if ( $sane !== null ) $retval[] = $sane;
53
+ }
54
+ if ($requested_format === 'int,') {
55
+ return implode( ',', $retval );
56
+ }
57
+ return $retval;
58
+
59
+ case 'int':
60
+ return is_numeric( $value ) ? intval( $value ) : $fallback_value;
61
+
62
+ case 'yesno':
63
+ return $value === 'yes' ? 'yes' : 'no';
64
+
65
+ case 'decimal':
66
+ $value = wc_format_decimal( $value );
67
+ return ( $value === '' || $value === false ) ? $fallback_value : $value;
68
+ }
69
+
70
+ throw new Exception("Unknown sanitization rule " . $requested_format);
71
+ }
72
  }
includes/WJECF_WC.php CHANGED
@@ -1,417 +1,417 @@
1
- <?php
2
-
3
- defined('ABSPATH') or die();
4
-
5
- /**
6
- *
7
- * Interface to WooCommerce. Handles version differences / backwards compatibility.
8
- *
9
- * @since 2.3.7.2
10
- */
11
- class WJECF_WC {
12
-
13
- protected $wrappers = array();
14
-
15
- /**
16
- * Wrap a data object (WC 2.7 introduced WC_Data)
17
- * @param type $object
18
- * @return type
19
- */
20
- public function wrap( $object, $use_pool = true ) {
21
- if ( $use_pool ) {
22
- //Prevent a huge amount of wrappers to be initiated; one wrapper per object instance should do the trick
23
- foreach( $this->wrappers as $wrapper ) {
24
- if ($wrapper->holds( $object ) ) {
25
- //error_log('Reusing wrapper ' . get_class( $object ) );
26
- return $wrapper;
27
- }
28
- }
29
- }
30
-
31
- if ( is_numeric( $object ) ) {
32
- $post_type = get_post_type( $object );
33
- if ( $post_type == 'shop_coupon' ) {
34
- $object = WJECF_WC()->get_coupon( $object );
35
- } elseif ( $post_type == 'product' ) {
36
- $object = new WC_Product( $object );
37
- }
38
- }
39
- if ( is_string( $object ) ) {
40
- $object = WJECF_WC()->get_coupon( $object );
41
- }
42
-
43
-
44
- if ( $object instanceof WC_Coupon ) {
45
- return $this->wrappers[] = new WJECF_Wrap_Coupon( $object );
46
- }
47
-
48
- if ( $object instanceof WC_Customer ) {
49
- return $this->wrappers[] = new WJECF_Wrap_Customer( $object );
50
- }
51
-
52
- if ( $object instanceof WC_Product ) {
53
- return $this->wrappers[] = new WJECF_Wrap_Product( $object );
54
- }
55
-
56
- throw new Exception( 'Cannot wrap ' . get_class( $object ) );
57
- }
58
-
59
- /**
60
- * Returns an array of items like stored in WC_Discounts since WC3.2.0
61
- *
62
- * Note: $item->price is in cents; use wc_remove_number_precision( $item->price ) to compare it to the actual amount
63
- *
64
- * $item->key = $key;
65
- * $item->object = $cart_item;
66
- * $item->product = $cart_item['data'];
67
- * $item->quantity = $cart_item['quantity'];
68
- * $item->price = self::wc_add_number_precision_deep( $item->product->get_price() ) * $item->quantity;
69
- *
70
- * @param WC_Discounts|null $wc_discounts
71
- * @return array of items (stdClass)
72
- */
73
- public function get_discount_items( $wc_discounts = null ) {
74
- if ( $wc_discounts == null ) {
75
- require_once( 'WJECF_WC_Discounts.php' );
76
- $wc_discounts = new WJECF_WC_Discounts( WC()->cart );
77
- }
78
- return $wc_discounts->get_items();
79
- }
80
-
81
- /**
82
- * Convert a cart_item to an item as generated by WC_Discounts
83
- *
84
- * @since 2.6.0
85
- */
86
- public function cart_item_to_discount_item( $cart_item, $key = null ) {
87
- $item = new stdClass();
88
- $item->key = $key === null && isset( $cart_item['key'] ) ? $cart_item['key'] : $key; //Note: might yield null
89
- $item->object = $cart_item;
90
- $item->product = $cart_item['data'];
91
- $item->quantity = $cart_item['quantity'];
92
- $item->price = $this->wc_add_number_precision_deep( $item->product->get_price() ) * $item->quantity;
93
- return $item;
94
- }
95
-
96
- /**
97
- * Add precision to an array of number and return an array of int.
98
- *
99
- * @since 2.6.0 (taken from WC 3.2.3)
100
- * @param array $value Number to add precision to.
101
- * @return int
102
- */
103
- public function wc_add_number_precision_deep( $value ) {
104
- if ( function_exists( 'wc_add_number_precision_deep' ) ) return wc_add_number_precision_deep( $value );
105
-
106
- if ( is_array( $value ) ) {
107
- foreach ( $value as $key => $subvalue ) {
108
- $value[ $key ] = $this->wc_add_number_precision_deep( $subvalue );
109
- }
110
- } else {
111
- $value = $this->wc_add_number_precision( $value );
112
- }
113
- return $value;
114
- }
115
-
116
- /**
117
- * Add precision to a number and return an int.
118
- *
119
- * @since 2.6.0 (taken from WC 3.2.3)
120
- * @param float $value Number to add precision to.
121
- * @return int
122
- */
123
- public function wc_add_number_precision( $value ) {
124
- if ( function_exists( 'wc_add_number_precision' ) ) return wc_add_number_precision( $value );
125
-
126
- $precision = pow( 10, wc_get_price_decimals() ); //Since WC 2.3.0
127
- return intval( round( $value * $precision ) );
128
- }
129
-
130
- /**
131
- * Remove precision from a number and return a float.
132
- *
133
- * @since 2.6.0 (taken from WC 3.2.3)
134
- * @param float $value Number to add precision to.
135
- * @return float
136
- */
137
- public function wc_remove_number_precision( $value ) {
138
- if ( function_exists( 'wc_remove_number_precision' ) ) return wc_remove_number_precision( $value );
139
-
140
- $precision = pow( 10, wc_get_price_decimals() ); //Since WC 2.3.0
141
- return $value / $precision;
142
- }
143
-
144
-
145
- /**
146
- * Returns a specific item in the cart.
147
- *
148
- * @param string $cart_item_key Cart item key.
149
- * @return array Item data
150
- */
151
- public function get_cart_item( $cart_item_key ) {
152
- if ( $this->check_woocommerce_version('2.2.9') ) {
153
- return WC()->cart->get_cart_item( $cart_item_key );
154
- }
155
-
156
- return isset( WC()->cart->cart_contents[ $cart_item_key ] ) ? WC()->cart->cart_contents[ $cart_item_key ] : array();
157
- }
158
-
159
- /**
160
- * Get categories of a product (and anchestors)
161
- * @param int $product_id
162
- * @return array product_cat_ids
163
- */
164
- public function wc_get_product_cat_ids( $product_id ) {
165
- //Since WC 2.5.0
166
- if ( function_exists( 'wc_get_product_cat_ids' ) ) {
167
- return wc_get_product_cat_ids( $product_id );
168
- }
169
-
170
- $product_cats = wp_get_post_terms( $product_id, 'product_cat', array( "fields" => "ids" ) );
171
-
172
- foreach ( $product_cats as $product_cat ) {
173
- $product_cats = array_merge( $product_cats, get_ancestors( $product_cat, 'product_cat' ) );
174
- }
175
- return $product_cats;
176
- }
177
-
178
- /**
179
- * Coupon types that apply to individual products. Controls which validation rules will apply.
180
- *
181
- * @since 2.5.0
182
- * @return array
183
- */
184
- public function wc_get_product_coupon_types() {
185
- //Since WC 2.5.0
186
- if ( function_exists( 'wc_get_product_coupon_types' ) ) {
187
- return wc_get_product_coupon_types();
188
- }
189
- return array( 'fixed_product', 'percent_product' );
190
- }
191
-
192
- public function wc_get_cart_coupon_types() {
193
- //Since WC 2.5.0
194
- if ( function_exists( 'wc_get_cart_coupon_types' ) ) {
195
- return wc_get_cart_coupon_types();
196
- }
197
- return array( 'fixed_cart', 'percent' );
198
- }
199
-
200
- /**
201
- * Output a list of variation attributes for use in the cart forms.
202
- *
203
- * @param array $args
204
- * @since 2.5.1
205
- */
206
- public function wc_dropdown_variation_attribute_options( $args = array() ) {
207
- if ( function_exists( 'wc_dropdown_variation_attribute_options' ) ) {
208
- return wc_dropdown_variation_attribute_options( $args );
209
- }
210
-
211
- //Copied from WC2.4.0 wc-template-functions.php
212
- $args = wp_parse_args( $args, array(
213
- 'options' => false,
214
- 'attribute' => false,
215
- 'product' => false,
216
- 'selected' => false,
217
- 'name' => '',
218
- 'id' => '',
219
- 'show_option_none' => __( 'Choose an option', 'woocommerce' )
220
- ) );
221
-
222
- $options = $args['options'];
223
- $product = $args['product'];
224
- $attribute = $args['attribute'];
225
- $name = $args['name'] ? $args['name'] : 'attribute_' . sanitize_title( $attribute );
226
- $id = $args['id'] ? $args['id'] : sanitize_title( $attribute );
227
-
228
- if ( empty( $options ) && ! empty( $product ) && ! empty( $attribute ) ) {
229
- $attributes = $product->get_variation_attributes();
230
- $options = $attributes[ $attribute ];
231
- }
232
-
233
- echo '<select id="' . esc_attr( $id ) . '" name="' . esc_attr( $name ) . '" data-attribute_name="attribute_' . esc_attr( sanitize_title( $attribute ) ) . '">';
234
-
235
- if ( $args['show_option_none'] ) {
236
- echo '<option value="">' . esc_html( $args['show_option_none'] ) . '</option>';
237
- }
238
-
239
- if ( ! empty( $options ) ) {
240
- if ( $product && taxonomy_exists( $attribute ) ) {
241
- // Get terms if this is a taxonomy - ordered. We need the names too.
242
- $terms = wc_get_product_terms( $product->id, $attribute, array( 'fields' => 'all' ) );
243
-
244
- foreach ( $terms as $term ) {
245
- if ( in_array( $term->slug, $options ) ) {
246
- echo '<option value="' . esc_attr( $term->slug ) . '" ' . selected( sanitize_title( $args['selected'] ), $term->slug, false ) . '>' . apply_filters( 'woocommerce_variation_option_name', $term->name ) . '</option>';
247
- }
248
- }
249
- } else {
250
- foreach ( $options as $option ) {
251
- // This handles < 2.4.0 bw compatibility where text attributes were not sanitized.
252
- $selected = sanitize_title( $args['selected'] ) === $args['selected'] ? selected( $args['selected'], sanitize_title( $option ), false ) : selected( $args['selected'], $option, false );
253
- echo '<option value="' . esc_attr( $option ) . '" ' . $selected . '>' . esc_html( apply_filters( 'woocommerce_variation_option_name', $option ) ) . '</option>';
254
- }
255
- }
256
- }
257
-
258
- echo '</select>';
259
- }
260
-
261
- /**
262
- * Get attibutes/data for an individual variation from the database and maintain its integrity.
263
- * @since 2.5.1
264
- * @param int $variation_id
265
- * @return array
266
- */
267
- public function wc_get_product_variation_attributes( $variation_id ) {
268
- if ( function_exists( 'wc_get_product_variation_attributes' ) ) {
269
- return wc_get_product_variation_attributes( $variation_id );
270
- }
271
-
272
- //Copied from WC2.4.0 wc-product-functions.php
273
-
274
- // Build variation data from meta
275
- $all_meta = get_post_meta( $variation_id );
276
- $parent_id = wp_get_post_parent_id( $variation_id );
277
- $parent_attributes = array_filter( (array) get_post_meta( $parent_id, '_product_attributes', true ) );
278
- $found_parent_attributes = array();
279
- $variation_attributes = array();
280
-
281
- // Compare to parent variable product attributes and ensure they match
282
- foreach ( $parent_attributes as $attribute_name => $options ) {
283
- $attribute = 'attribute_' . sanitize_title( $attribute_name );
284
- $found_parent_attributes[] = $attribute;
285
- if ( ! array_key_exists( $attribute, $variation_attributes ) ) {
286
- $variation_attributes[ $attribute ] = ''; // Add it - 'any' will be asumed
287
- }
288
- }
289
-
290
- // Get the variation attributes from meta
291
- foreach ( $all_meta as $name => $value ) {
292
- // Only look at valid attribute meta, and also compare variation level attributes and remove any which do not exist at parent level
293
- if ( 0 !== strpos( $name, 'attribute_' ) || ! in_array( $name, $found_parent_attributes ) ) {
294
- unset( $variation_attributes[ $name ] );
295
- continue;
296
- }
297
- /**
298
- * Pre 2.4 handling where 'slugs' were saved instead of the full text attribute.
299
- * Attempt to get full version of the text attribute from the parent.
300
- */
301
- if ( sanitize_title( $value[0] ) === $value[0] && version_compare( get_post_meta( $parent_id, '_product_version', true ), '2.4.0', '<' ) ) {
302
- foreach ( $parent_attributes as $attribute ) {
303
- if ( $name !== 'attribute_' . sanitize_title( $attribute['name'] ) ) {
304
- continue;
305
- }
306
- $text_attributes = wc_get_text_attributes( $attribute['value'] );
307
-
308
- foreach ( $text_attributes as $text_attribute ) {
309
- if ( sanitize_title( $text_attribute ) === $value[0] ) {
310
- $value[0] = $text_attribute;
311
- break;
312
- }
313
- }
314
- }
315
- }
316
-
317
- $variation_attributes[ $name ] = $value[0];
318
- }
319
-
320
- return $variation_attributes;
321
- }
322
-
323
- public function find_matching_product_variation( $product, $match_attributes = array() ) {
324
- if ( $this->check_woocommerce_version( '3.0') ) {
325
- $data_store = WC_Data_Store::load( 'product' );
326
- $variation_id = $data_store->find_matching_product_variation( $product, $match_attributes );
327
- return $variation_id;
328
- }
329
-
330
- return $product->get_matching_variation( $match_attributes );
331
- }
332
-
333
- /**
334
- * @since 2.4.0 for WC 2.7 compatibility
335
- *
336
- * Get a WC_Coupon object
337
- * @param WC_Coupon|string|WP_Post $coupon The coupon code or a WC_Coupon object
338
- * @return WC_Coupon The coupon object
339
- */
340
- public function get_coupon( $coupon ) {
341
- if ( $coupon instanceof WP_Post ) {
342
- $coupon = $coupon->ID;
343
- }
344
- if ( is_numeric( $coupon ) && ! $this->check_woocommerce_version( '3.0' ) ) {
345
- //By id; not neccesary for WC3.0; as the WC_Coupon constructor accepts an id
346
- global $wpdb;
347
- $coupon_code = $wpdb->get_var( $wpdb->prepare( "SELECT post_title FROM $wpdb->posts WHERE id = %d AND post_type = 'shop_coupon'", $coupon ) );
348
- if ( $coupon_code !== null) {
349
- $coupon = $coupon_code;
350
- }
351
- }
352
- if ( ! ( $coupon instanceof WC_Coupon ) ) {
353
- //By code
354
- $coupon = new WC_Coupon( $coupon );
355
- }
356
- return $coupon;
357
- }
358
-
359
-
360
- //VERSION
361
-
362
- /**
363
- * Check whether WooCommerce version is greater or equal than $req_version
364
- * @param string @req_version The version to compare to
365
- * @return bool true if WooCommerce is at least the given version
366
- */
367
- public function check_woocommerce_version( $req_version ) {
368
- return version_compare( $this->get_woocommerce_version(), $req_version, '>=' );
369
- }
370
-
371
- private $wc_version = null;
372
-
373
- /**
374
- * Get the WooCommerce version number
375
- * @return string|bool WC Version number or false if WC not detected
376
- */
377
- public function get_woocommerce_version() {
378
- if ( isset( $this->wc_version ) ) {
379
- return $this->wc_version;
380
- }
381
-
382
- if ( defined( 'WC_VERSION' ) ) {
383
- return $this->wc_version = WC_VERSION;
384
- }
385
-
386
- // If get_plugins() isn't available, require it
387
- if ( ! function_exists( 'get_plugins' ) ) {
388
- require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
389
- }
390
- // Create the plugins folder and file variables
391
- $plugin_folder = get_plugins( '/woocommerce' );
392
- $plugin_file = 'woocommerce.php';
393
-
394
- // If the plugin version number is set, return it
395
- if ( isset( $plugin_folder[$plugin_file]['Version'] ) ) {
396
- return $this->wc_version = $plugin_folder[$plugin_file]['Version'];
397
- }
398
-
399
- return $this->wc_version = false; // Not found
400
- }
401
-
402
- //INSTANCE
403
-
404
- /**
405
- * Singleton Instance
406
- *
407
- * @static
408
- * @return Singleton Instance
409
- */
410
- public static function instance() {
411
- if ( is_null( self::$_instance ) ) {
412
- self::$_instance = new self();
413
- }
414
- return self::$_instance;
415
- }
416
- protected static $_instance = null;
417
- }
1
+ <?php
2
+
3
+ defined('ABSPATH') or die();
4
+
5
+ /**
6
+ *
7
+ * Interface to WooCommerce. Handles version differences / backwards compatibility.
8
+ *
9
+ * @since 2.3.7.2
10
+ */
11
+ class WJECF_WC {
12
+
13
+ protected $wrappers = array();
14
+
15
+ /**
16
+ * Wrap a data object (WC 2.7 introduced WC_Data)
17
+ * @param type $object
18
+ * @return type
19
+ */
20
+ public function wrap( $object, $use_pool = true ) {
21
+ if ( $use_pool ) {
22
+ //Prevent a huge amount of wrappers to be initiated; one wrapper per object instance should do the trick
23
+ foreach( $this->wrappers as $wrapper ) {
24
+ if ($wrapper->holds( $object ) ) {
25
+ //error_log('Reusing wrapper ' . get_class( $object ) );
26
+ return $wrapper;
27
+ }
28
+ }
29
+ }
30
+
31
+ if ( is_numeric( $object ) ) {
32
+ $post_type = get_post_type( $object );
33
+ if ( $post_type == 'shop_coupon' ) {
34
+ $object = WJECF_WC()->get_coupon( $object );
35
+ } elseif ( $post_type == 'product' ) {
36
+ $object = new WC_Product( $object );
37
+ }
38
+ }
39
+ if ( is_string( $object ) ) {
40
+ $object = WJECF_WC()->get_coupon( $object );
41
+ }
42
+
43
+
44
+ if ( $object instanceof WC_Coupon ) {
45
+ return $this->wrappers[] = new WJECF_Wrap_Coupon( $object );
46
+ }
47
+
48
+ if ( $object instanceof WC_Customer ) {
49
+ return $this->wrappers[] = new WJECF_Wrap_Customer( $object );
50
+ }
51
+
52
+ if ( $object instanceof WC_Product ) {
53
+ return $this->wrappers[] = new WJECF_Wrap_Product( $object );
54
+ }
55
+
56
+ throw new Exception( 'Cannot wrap ' . get_class( $object ) );
57
+ }
58
+
59
+ /**
60
+ * Returns an array of items like stored in WC_Discounts since WC3.2.0
61
+ *
62
+ * Note: $item->price is in cents; use wc_remove_number_precision( $item->price ) to compare it to the actual amount
63
+ *
64
+ * $item->key = $key;
65
+ * $item->object = $cart_item;
66
+ * $item->product = $cart_item['data'];
67
+ * $item->quantity = $cart_item['quantity'];
68
+ * $item->price = self::wc_add_number_precision_deep( $item->product->get_price() ) * $item->quantity;
69
+ *
70
+ * @param WC_Discounts|null $wc_discounts
71
+ * @return array of items (stdClass)
72
+ */
73
+ public function get_discount_items( $wc_discounts = null ) {
74
+ if ( $wc_discounts == null ) {
75
+ require_once( 'WJECF_WC_Discounts.php' );
76
+ $wc_discounts = new WJECF_WC_Discounts( WC()->cart );
77
+ }
78
+ return $wc_discounts->get_items();
79
+ }
80
+
81
+ /**
82
+ * Convert a cart_item to an item as generated by WC_Discounts
83
+ *
84
+ * @since 2.6.0
85
+ */
86
+ public function cart_item_to_discount_item( $cart_item, $key = null ) {
87
+ $item = new stdClass();
88
+ $item->key = $key === null && isset( $cart_item['key'] ) ? $cart_item['key'] : $key; //Note: might yield null
89
+ $item->object = $cart_item;
90
+ $item->product = $cart_item['data'];
91
+ $item->quantity = $cart_item['quantity'];
92
+ $item->price = $this->wc_add_number_precision_deep( $item->product->get_price() ) * $item->quantity;
93
+ return $item;
94
+ }
95
+
96
+ /**
97
+ * Add precision to an array of number and return an array of int.
98
+ *
99
+ * @since 2.6.0 (taken from WC 3.2.3)
100
+ * @param array $value Number to add precision to.
101
+ * @return int
102
+ */
103
+ public function wc_add_number_precision_deep( $value ) {
104
+ if ( function_exists( 'wc_add_number_precision_deep' ) ) return wc_add_number_precision_deep( $value );
105
+
106
+ if ( is_array( $value ) ) {
107
+ foreach ( $value as $key => $subvalue ) {
108
+ $value[ $key ] = $this->wc_add_number_precision_deep( $subvalue );
109
+ }
110
+ } else {
111
+ $value = $this->wc_add_number_precision( $value );
112
+ }
113
+ return $value;
114
+ }
115
+
116
+ /**
117
+ * Add precision to a number and return an int.
118
+ *
119
+ * @since 2.6.0 (taken from WC 3.2.3)
120
+ * @param float $value Number to add precision to.
121
+ * @return int
122
+ */
123
+ public function wc_add_number_precision( $value ) {
124
+ if ( function_exists( 'wc_add_number_precision' ) ) return wc_add_number_precision( $value );
125
+
126
+ $precision = pow( 10, wc_get_price_decimals() ); //Since WC 2.3.0
127
+ return intval( round( $value * $precision ) );
128
+ }
129
+
130
+ /**
131
+ * Remove precision from a number and return a float.
132
+ *
133
+ * @since 2.6.0 (taken from WC 3.2.3)
134
+ * @param float $value Number to add precision to.
135
+ * @return float
136
+ */
137
+ public function wc_remove_number_precision( $value ) {
138
+ if ( function_exists( 'wc_remove_number_precision' ) ) return wc_remove_number_precision( $value );
139
+
140
+ $precision = pow( 10, wc_get_price_decimals() ); //Since WC 2.3.0
141
+ return $value / $precision;
142
+ }
143
+
144
+
145
+ /**
146
+ * Returns a specific item in the cart.
147
+ *
148
+ * @param string $cart_item_key Cart item key.
149
+ * @return array Item data
150
+ */
151
+ public function get_cart_item( $cart_item_key ) {
152
+ if ( $this->check_woocommerce_version('2.2.9') ) {
153
+ return WC()->cart->get_cart_item( $cart_item_key );
154
+ }
155
+
156
+ return isset( WC()->cart->cart_contents[ $cart_item_key ] ) ? WC()->cart->cart_contents[ $cart_item_key ] : array();
157
+ }
158
+
159
+ /**
160
+ * Get categories of a product (and anchestors)
161
+ * @param int $product_id
162
+ * @return array product_cat_ids
163
+ */
164
+ public function wc_get_product_cat_ids( $product_id ) {
165
+ //Since WC 2.5.0
166
+ if ( function_exists( 'wc_get_product_cat_ids' ) ) {
167
+ return wc_get_product_cat_ids( $product_id );
168
+ }
169
+
170
+ $product_cats = wp_get_post_terms( $product_id, 'product_cat', array( "fields" => "ids" ) );
171
+
172
+ foreach ( $product_cats as $product_cat ) {
173
+ $product_cats = array_merge( $product_cats, get_ancestors( $product_cat, 'product_cat' ) );
174
+ }
175
+ return $product_cats;
176
+ }
177
+
178
+ /**
179
+ * Coupon types that apply to individual products. Controls which validation rules will apply.
180
+ *
181
+ * @since 2.5.0
182
+ * @return array
183
+ */
184
+ public function wc_get_product_coupon_types() {
185
+ //Since WC 2.5.0
186
+ if ( function_exists( 'wc_get_product_coupon_types' ) ) {
187
+ return wc_get_product_coupon_types();
188
+ }
189
+ return array( 'fixed_product', 'percent_product' );
190
+ }
191
+
192
+ public function wc_get_cart_coupon_types() {
193
+ //Since WC 2.5.0
194
+ if ( function_exists( 'wc_get_cart_coupon_types' ) ) {
195
+ return wc_get_cart_coupon_types();
196
+ }
197
+ return array( 'fixed_cart', 'percent' );
198
+ }
199
+
200
+ /**
201
+ * Output a list of variation attributes for use in the cart forms.
202
+ *
203
+ * @param array $args
204
+ * @since 2.5.1
205
+ */
206
+ public function wc_dropdown_variation_attribute_options( $args = array() ) {
207
+ if ( function_exists( 'wc_dropdown_variation_attribute_options' ) ) {
208
+ return wc_dropdown_variation_attribute_options( $args );
209
+ }
210
+
211
+ //Copied from WC2.4.0 wc-template-functions.php
212
+ $args = wp_parse_args( $args, array(
213
+ 'options' => false,
214
+ 'attribute' => false,
215
+ 'product' => false,
216
+ 'selected' => false,
217
+ 'name' => '',
218
+ 'id' => '',
219
+ 'show_option_none' => __( 'Choose an option', 'woocommerce' )
220
+ ) );
221
+
222
+ $options = $args['options'];
223
+ $product = $args['product'];
224
+ $attribute = $args['attribute'];
225
+ $name = $args['name'] ? $args['name'] : 'attribute_' . sanitize_title( $attribute );
226
+ $id = $args['id'] ? $args['id'] : sanitize_title( $attribute );
227
+
228
+ if ( empty( $options ) && ! empty( $product ) && ! empty( $attribute ) ) {
229
+ $attributes = $product->get_variation_attributes();
230
+ $options = $attributes[ $attribute ];
231
+ }
232
+
233
+ echo '<select id="' . esc_attr( $id ) . '" name="' . esc_attr( $name ) . '" data-attribute_name="attribute_' . esc_attr( sanitize_title( $attribute ) ) . '">';
234
+
235
+ if ( $args['show_option_none'] ) {
236
+ echo '<option value="">' . esc_html( $args['show_option_none'] ) . '</option>';
237
+ }
238
+
239
+ if ( ! empty( $options ) ) {
240
+ if ( $product && taxonomy_exists( $attribute ) ) {
241
+ // Get terms if this is a taxonomy - ordered. We need the names too.
242
+ $terms = wc_get_product_terms( $product->id, $attribute, array( 'fields' => 'all' ) );
243
+
244
+ foreach ( $terms as $term ) {
245
+ if ( in_array( $term->slug, $options ) ) {
246
+ echo '<option value="' . esc_attr( $term->slug ) . '" ' . selected( sanitize_title( $args['selected'] ), $term->slug, false ) . '>' . apply_filters( 'woocommerce_variation_option_name', $term->name ) . '</option>';
247
+ }
248
+ }
249
+ } else {
250
+ foreach ( $options as $option ) {
251
+ // This handles < 2.4.0 bw compatibility where text attributes were not sanitized.
252
+ $selected = sanitize_title( $args['selected'] ) === $args['selected'] ? selected( $args['selected'], sanitize_title( $option ), false ) : selected( $args['selected'], $option, false );
253
+ echo '<option value="' . esc_attr( $option ) . '" ' . $selected . '>' . esc_html( apply_filters( 'woocommerce_variation_option_name', $option ) ) . '</option>';
254
+ }
255
+ }
256
+ }
257
+
258
+ echo '</select>';
259
+ }
260
+
261
+ /**
262
+ * Get attibutes/data for an individual variation from the database and maintain its integrity.
263
+ * @since 2.5.1
264
+ * @param int $variation_id
265
+ * @return array
266
+ */
267
+ public function wc_get_product_variation_attributes( $variation_id ) {
268
+ if ( function_exists( 'wc_get_product_variation_attributes' ) ) {
269
+ return wc_get_product_variation_attributes( $variation_id );
270
+ }
271
+
272
+ //Copied from WC2.4.0 wc-product-functions.php
273
+
274
+ // Build variation data from meta
275
+ $all_meta = get_post_meta( $variation_id );
276
+ $parent_id = wp_get_post_parent_id( $variation_id );
277
+ $parent_attributes = array_filter( (array) get_post_meta( $parent_id, '_product_attributes', true ) );
278
+ $found_parent_attributes = array();
279
+ $variation_attributes = array();
280
+
281
+ // Compare to parent variable product attributes and ensure they match
282
+ foreach ( $parent_attributes as $attribute_name => $options ) {
283
+ $attribute = 'attribute_' . sanitize_title( $attribute_name );
284
+ $found_parent_attributes[] = $attribute;
285
+ if ( ! array_key_exists( $attribute, $variation_attributes ) ) {
286
+ $variation_attributes[ $attribute ] = ''; // Add it - 'any' will be asumed
287
+ }
288
+ }
289
+
290
+ // Get the variation attributes from meta
291
+ foreach ( $all_meta as $name => $value ) {
292
+ // Only look at valid attribute meta, and also compare variation level attributes and remove any which do not exist at parent level
293
+ if ( 0 !== strpos( $name, 'attribute_' ) || ! in_array( $name, $found_parent_attributes ) ) {
294
+ unset( $variation_attributes[ $name ] );
295
+ continue;
296
+ }
297
+ /**
298
+ * Pre 2.4 handling where 'slugs' were saved instead of the full text attribute.
299
+ * Attempt to get full version of the text attribute from the parent.
300
+ */
301
+ if ( sanitize_title( $value[0] ) === $value[0] && version_compare( get_post_meta( $parent_id, '_product_version', true ), '2.4.0', '<' ) ) {
302
+ foreach ( $parent_attributes as $attribute ) {
303
+ if ( $name !== 'attribute_' . sanitize_title( $attribute['name'] ) ) {
304
+ continue;
305
+ }
306
+ $text_attributes = wc_get_text_attributes( $attribute['value'] );
307
+
308
+ foreach ( $text_attributes as $text_attribute ) {
309
+ if ( sanitize_title( $text_attribute ) === $value[0] ) {
310
+ $value[0] = $text_attribute;
311
+ break;
312
+ }
313
+ }
314
+ }
315
+ }
316
+
317
+ $variation_attributes[ $name ] = $value[0];
318
+ }
319
+
320
+ return $variation_attributes;
321
+ }
322
+
323
+ public function find_matching_product_variation( $product, $match_attributes = array() ) {
324
+ if ( $this->check_woocommerce_version( '3.0') ) {
325
+ $data_store = WC_Data_Store::load( 'product' );
326
+ $variation_id = $data_store->find_matching_product_variation( $product, $match_attributes );
327
+ return $variation_id;
328
+ }
329
+
330
+ return $product->get_matching_variation( $match_attributes );
331
+ }
332
+
333
+ /**
334
+ * @since 2.4.0 for WC 2.7 compatibility
335
+ *
336
+ * Get a WC_Coupon object
337
+ * @param WC_Coupon|string|WP_Post $coupon The coupon code or a WC_Coupon object
338
+ * @return WC_Coupon The coupon object
339
+ */
340
+ public function get_coupon( $coupon ) {
341
+ if ( $coupon instanceof WP_Post ) {
342
+ $coupon = $coupon->ID;
343
+ }
344
+ if ( is_numeric( $coupon ) && ! $this->check_woocommerce_version( '3.0' ) ) {
345
+ //By id; not neccesary for WC3.0; as the WC_Coupon constructor accepts an id
346
+ global $wpdb;
347
+ $coupon_code = $wpdb->get_var( $wpdb->prepare( "SELECT post_title FROM $wpdb->posts WHERE id = %d AND post_type = 'shop_coupon'", $coupon ) );
348
+ if ( $coupon_code !== null) {
349
+ $coupon = $coupon_code;
350
+ }
351
+ }
352
+ if ( ! ( $coupon instanceof WC_Coupon ) ) {
353
+ //By code
354
+ $coupon = new WC_Coupon( $coupon );
355
+ }
356
+ return $coupon;
357
+ }
358
+
359
+
360
+ //VERSION
361
+
362
+ /**
363
+ * Check whether WooCommerce version is greater or equal than $req_version
364
+ * @param string @req_version The version to compare to
365
+ * @return bool true if WooCommerce is at least the given version
366
+ */
367
+ public function check_woocommerce_version( $req_version ) {
368
+ return version_compare( $this->get_woocommerce_version(), $req_version, '>=' );
369
+ }
370
+
371
+ private $wc_version = null;
372
+
373
+ /**
374
+ * Get the WooCommerce version number
375
+ * @return string|bool WC Version number or false if WC not detected
376
+ */
377
+ public function get_woocommerce_version() {
378
+ if ( isset( $this->wc_version ) ) {
379
+ return $this->wc_version;
380
+ }
381
+
382
+ if ( defined( 'WC_VERSION' ) ) {
383
+ return $this->wc_version = WC_VERSION;
384
+ }
385
+
386
+ // If get_plugins() isn't available, require it
387
+ if ( ! function_exists( 'get_plugins' ) ) {
388
+ require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
389
+ }
390
+ // Create the plugins folder and file variables
391
+ $plugin_folder = get_plugins( '/woocommerce' );
392
+ $plugin_file = 'woocommerce.php';
393
+
394
+ // If the plugin version number is set, return it
395
+ if ( isset( $plugin_folder[$plugin_file]['Version'] ) ) {
396
+ return $this->wc_version = $plugin_folder[$plugin_file]['Version'];
397
+ }
398
+
399
+ return $this->wc_version = false; // Not found
400
+ }
401
+
402
+ //INSTANCE
403
+
404
+ /**
405
+ * Singleton Instance
406
+ *
407
+ * @static
408
+ * @return Singleton Instance
409
+ */
410
+ public static function instance() {
411
+ if ( is_null( self::$_instance ) ) {
412
+ self::$_instance = new self();
413
+ }
414
+ return self::$_instance;
415
+ }
416
+ protected static $_instance = null;
417
+ }
includes/WJECF_WC_Discounts.php CHANGED
@@ -1,70 +1,70 @@
1
- <?php
2
-
3
- defined('ABSPATH') or die();
4
-
5
-
6
- /**
7
- * Class that replaces the get_items() a WC_Discounts (WC3.2.0) object for WC < 3.2.0
8
- *
9
- * @since 2.6.0
10
- */
11
- class WJECF_WC_Discounts {
12
-
13
- public function __construct( $object = array() ) {
14
- if ( is_a( $object, 'WC_Cart' ) ) {
15
- $this->set_items_from_cart( $object );
16
- return;
17
- }
18
- throw new Exception('WJECF_WC_Discounts must be passed a WC_Cart object');
19
- }
20
-
21
- /**
22
- * Get items.
23
- *
24
- * @since 3.2.0
25
- * @return object[]
26
- */
27
- public function get_items() {
28
- return $this->items;
29
- }
30
-
31
- /**
32
- * Normalise cart items which will be discounted.
33
- *
34
- * @since 3.2.0
35
- * @param WC_Cart $cart Cart object.
36
- */
37
- public function set_items_from_cart( $cart ) {
38
- $this->items = $this->discounts = array();
39
-
40
- if ( ! is_a( $cart, 'WC_Cart' ) ) {
41
- return;
42
- }
43
-
44
- $this->object = $cart;
45
-
46
- foreach ( $cart->get_cart() as $key => $cart_item ) {
47
- $this->items[ $key ] = WJECF_WC()->cart_item_to_discount_item( $cart_item, $key );
48
- }
49
-
50
- uasort( $this->items, array( $this, 'sort_by_price' ) );
51
- }
52
-
53
- /**
54
- * Sort by price.
55
- *
56
- * @since 3.2.0
57
- * @param array $a First element.
58
- * @param array $b Second element.
59
- * @return int
60
- */
61
- protected function sort_by_price( $a, $b ) {
62
- $price_1 = $a->price * $a->quantity;
63
- $price_2 = $b->price * $b->quantity;
64
- if ( $price_1 === $price_2 ) {
65
- return 0;
66
- }
67
- return ( $price_1 < $price_2 ) ? 1 : -1;
68
- }
69
-
70
- }
1
+ <?php
2
+
3
+ defined('ABSPATH') or die();
4
+
5
+
6
+ /**
7
+ * Class that replaces the get_items() a WC_Discounts (WC3.2.0) object for WC < 3.2.0
8
+ *
9
+ * @since 2.6.0
10
+ */
11
+ class WJECF_WC_Discounts {
12
+
13
+ public function __construct( $object = array() ) {
14
+ if ( is_a( $object, 'WC_Cart' ) ) {
15
+ $this->set_items_from_cart( $object );
16
+ return;
17
+ }
18
+ throw new Exception('WJECF_WC_Discounts must be passed a WC_Cart object');
19
+ }
20
+
21
+ /**
22
+ * Get items.
23
+ *
24
+ * @since 3.2.0
25
+ * @return object[]
26
+ */
27
+ public function get_items() {
28
+ return $this->items;
29
+ }
30
+
31
+ /**
32
+ * Normalise cart items which will be discounted.
33
+ *
34
+ * @since 3.2.0
35
+ * @param WC_Cart $cart Cart object.
36
+ */
37
+ public function set_items_from_cart( $cart ) {
38
+ $this->items = $this->discounts = array();
39
+
40
+ if ( ! is_a( $cart, 'WC_Cart' ) ) {
41
+ return;
42
+ }
43
+
44
+ $this->object = $cart;
45
+
46
+ foreach ( $cart->get_cart() as $key => $cart_item ) {
47
+ $this->items[ $key ] = WJECF_WC()->cart_item_to_discount_item( $cart_item, $key );
48
+ }
49
+
50
+ uasort( $this->items, array( $this, 'sort_by_price' ) );
51
+ }
52
+
53
+ /**
54
+ * Sort by price.
55
+ *
56
+ * @since 3.2.0
57
+ * @param array $a First element.
58
+ * @param array $b Second element.
59
+ * @return int
60
+ */
61
+ protected function sort_by_price( $a, $b ) {
62
+ $price_1 = $a->price * $a->quantity;
63
+ $price_2 = $b->price * $b->quantity;
64
+ if ( $price_1 === $price_2 ) {
65
+ return 0;
66
+ }
67
+ return ( $price_1 < $price_2 ) ? 1 : -1;
68
+ }
69
+
70
+ }
includes/WJECF_WPML.php CHANGED
@@ -1,80 +1,80 @@
1
- <?php
2
-
3
- defined('ABSPATH') or die();
4
-
5
- /**
6
- * Class to make WJECF compatible with WPML
7
- */
8
- class WJECF_WPML extends Abstract_WJECF_Plugin {
9
-
10
- public function __construct() {
11
- $this->set_plugin_data( array(
12
- 'description' => __( 'Compatiblity with WPML.', 'woocommerce-jos-autocoupon' ),
13
- 'dependencies' => array(),
14
- 'can_be_disabled' => true
15
- ) );
16
- }
17
-
18
- public function init_hook() {
19
- global $sitepress;
20
- if ( isset( $sitepress ) ) {
21
- //WJECF_Controller hooks
22
- add_filter( 'wjecf_get_product_id', array( $this, 'filter_get_product_id' ), 10 );
23
- add_filter( 'wjecf_get_product_ids', array( $this, 'filter_get_product_ids' ), 10 );
24
- add_filter( 'wjecf_get_product_cat_ids', array( $this, 'filter_get_product_cat_ids' ), 10 );
25
- }
26
- }
27
-
28
- //HOOKS
29
-
30
- public function filter_get_product_ids( $product_ids ) {
31
- return $this->get_translated_object_ids( $product_ids, 'product' );
32
- }
33
-
34
- public function filter_get_product_cat_ids( $product_cat_ids ) {
35
- return $this->get_translated_object_ids( $product_cat_ids, 'product_cat' );
36
- }
37
-
38
- public function filter_get_product_id( $product_id ) {
39
- return $this->get_translated_object_id( $product_id, 'product' );
40
- }
41
-
42
- //FUNCTIONS
43
-
44
-
45
- /**
46
- * Get the ids of all the translations. Otherwise return the original array
47
- *
48
- * @param int|array $product_ids The product_ids to find the translations for
49
- * @return array The product ids of all translations
50
- *
51
- */
52
- public function get_translated_object_ids( $object_ids, $object_type ) {
53
- //Make sure it's an array
54
- if ( ! is_array( $object_ids ) ) {
55
- $object_ids = array( $object_ids );
56
- }
57
-
58
- $translated_object_ids = array();
59
- foreach( $object_ids as $object_id) {
60
- $translated_object_ids[] = apply_filters( 'wpml_object_id', $object_id, $object_type );
61
- }
62
- $this->log( 'debug', 'Translated ' . $object_type . ': ' . implode( ',', $object_ids ) . ' to: ' . implode( ',', $translated_object_ids ) );
63
- return $translated_object_ids;
64
- }
65
-
66
- /**
67
- * Get translated object id
68
- * @param int $object_id
69
- * @param string $object_type
70
- * @return int|bool false if not found
71
- */
72
- public function get_translated_object_id( $object_id, $object_type ) {
73
- $translated_object_ids = $this->get_translated_object_ids( array( $object_id ), $object_type );
74
- if ( empty( $translated_object_ids ) ) {
75
- return false;
76
- }
77
- return reset( $translated_object_ids );
78
- }
79
-
80
- }
1
+ <?php
2
+
3
+ defined('ABSPATH') or die();
4
+
5
+ /**
6
+ * Class to make WJECF compatible with WPML
7
+ */
8
+ class WJECF_WPML extends Abstract_WJECF_Plugin {
9
+
10
+ public function __construct() {
11
+ $this->set_plugin_data( array(
12
+ 'description' => __( 'Compatiblity with WPML.', 'woocommerce-jos-autocoupon' ),
13
+ 'dependencies' => array(),
14
+ 'can_be_disabled' => true
15
+ ) );
16
+ }
17
+
18
+ public function init_hook() {
19
+ global $sitepress;
20
+ if ( isset( $sitepress ) ) {
21
+ //WJECF_Controller hooks
22
+ add_filter( 'wjecf_get_product_id', array( $this, 'filter_get_product_id' ), 10 );
23
+ add_filter( 'wjecf_get_product_ids', array( $this, 'filter_get_product_ids' ), 10 );
24
+ add_filter( 'wjecf_get_product_cat_ids', array( $this, 'filter_get_product_cat_ids' ), 10 );
25
+ }
26
+ }
27
+
28
+ //HOOKS
29
+
30
+ public function filter_get_product_ids( $product_ids ) {
31
+ return $this->get_translated_object_ids( $product_ids, 'product' );
32
+ }
33
+
34
+ public function filter_get_product_cat_ids( $product_cat_ids ) {
35
+ return $this->get_translated_object_ids( $product_cat_ids, 'product_cat' );
36
+ }
37
+
38
+ public function filter_get_product_id( $product_id ) {
39
+ return $this->get_translated_object_id( $product_id, 'product' );
40
+ }
41
+
42
+ //FUNCTIONS
43
+
44
+
45
+ /**
46
+ * Get the ids of all the translations. Otherwise return the original array
47
+ *
48
+ * @param int|array $product_ids The product_ids to find the translations for
49
+ * @return array The product ids of all translations
50
+ *
51
+ */
52
+ public function get_translated_object_ids( $object_ids, $object_type ) {
53
+ //Make sure it's an array
54
+ if ( ! is_array( $object_ids ) ) {
55
+ $object_ids = array( $object_ids );
56
+ }
57
+
58
+ $translated_object_ids = array();
59
+ foreach( $object_ids as $object_id) {
60
+ $translated_object_ids[] = apply_filters( 'wpml_object_id', $object_id, $object_type );
61
+ }
62
+ $this->log( 'debug', 'Translated ' . $object_type . ': ' . implode( ',', $object_ids ) . ' to: ' . implode( ',', $translated_object_ids ) );
63
+ return $translated_object_ids;
64
+ }
65
+
66
+ /**
67
+ * Get translated object id
68
+ * @param int $object_id
69
+ * @param string $object_type
70
+ * @return int|bool false if not found
71
+ */
72
+ public function get_translated_object_id( $object_id, $object_type ) {
73
+ $translated_object_ids = $this->get_translated_object_ids( array( $object_id ), $object_type );
74
+ if ( empty( $translated_object_ids ) ) {
75
+ return false;
76
+ }
77
+ return reset( $translated_object_ids );
78
+ }
79
+
80
+ }
includes/WJECF_Wrap.php CHANGED
@@ -1,497 +1,497 @@
1
- <?php
2
-
3
- defined('ABSPATH') or die();
4
-
5
- /**
6
- * Wrapper for WC objects. Helps to maintain compatibility between both WC2 and WC3
7
- */
8
- class WJECF_Wrap {
9
- protected $object = null;
10
-
11
- public function __construct( $object ) {
12
- $this->object = $object;
13
- //error_log('Wrapping ' . get_class( $object ) );
14
- }
15
-
16
- public $use_wc27 = true;
17
- public function get_id() {
18
- //Since WC 2.7
19
- if ( $this->use_wc27 && is_callable( array( $this->object, 'get_id' ) ) ) {
20
- return $this->object->get_id();
21
- }
22
- return $this->object->id;
23
- }
24
-
25
- public function holds( $object ) {
26
- return $object === $this->object;
27
- }
28
-
29
- /**
30
- * Get Meta Data by Key.
31
- *
32
- * If no value found:
33
- * If $single is true, an empty string is returned.
34
- * If $single is false, an empty array is returned.
35
- *
36
- * @since 2.4.0
37
- * @param string $key
38
- * @param bool $single return first found meta, or all
39
- * @return mixed
40
- */
41
- public final function get_meta( $meta_key, $single = true ) {
42
- if ( $this->use_wc27 && is_callable( array( $this->object, 'get_meta' ) ) ) {
43
- return $this->get_meta_wc27( $meta_key, $single );
44
- }
45
-
46
- return $this->get_meta_legacy( $meta_key, $single );
47
- }
48
-
49
- protected function get_meta_wc27( $meta_key, $single = true ) {
50
- $values = $this->object->get_meta( $meta_key, $single );
51
- if ($single) {
52
- return $values; //it's just one, dispite the plural in the name!
53
- }
54
-
55
- if ( $values === '' ) {
56
- return array(); //get_meta returns empty string if meta does not exist
57
- }
58
-
59
- return wp_list_pluck( array_values( $values ), 'value' ); //when not using array_values; the index might not start with 0
60
- }
61
-
62
- protected function get_meta_legacy( $meta_key, $single = true ) {
63
- throw new Exception( sprintf( '%s::get_meta_legacy not implemented', get_class( $this ) ) );
64
- }
65
-
66
- /**
67
- * Update single meta data item by meta key.
68
- * Call save() if the values must to be persisted.
69
- * @since 2.4.0
70
- * @param string $meta_key
71
- * @param mixed $value The value; use null to clear
72
- */
73
- public final function set_meta( $meta_key, $value ) {
74
- if ( $this->use_wc27 && is_callable( array( $this->object, 'update_meta_data' ) ) ) {
75
- if ( $value === null ) {
76
- $this->object->delete_meta_data( $meta_key );
77
- } else {
78
- $this->object->update_meta_data( $meta_key, $value );
79
- }
80
- return;
81
- }
82
-
83
- $this->set_meta_legacy( $meta_key, $value );
84
- }
85
-
86
- protected function set_meta_legacy( $meta_key, $value ) {
87
- throw new Exception( sprintf( '%s::set_meta_legacy not implemented', get_class( $this ) ) );
88
- }
89
-
90
- }
91
-
92
- /**
93
- * Wrap a data object ( Coupons and products were converted to WC_Data since WC 2.7.0 )
94
- */
95
- class WJECF_Wrap_Coupon extends WJECF_Wrap {
96
-
97
- public function exists() {
98
- return $this->get_id() > 0;
99
- }
100
-
101
- public function get_code() {
102
- if ( $this->use_wc27 && is_callable( array( $this->object, 'get_code' ) ) ) {
103
- return $this->object->get_code();
104
- }
105
-
106
- return $this->object->code;
107
- }
108
-
109
- public function get_description() {
110
- if ( $this->use_wc27 && is_callable( array( $this->object, 'get_description' ) ) ) {
111
- return $this->object->get_description();
112
- }
113
-
114
- $post = get_post( $this->get_id() );
115
- return $post->post_excerpt;
116
- }
117
-
118
- public function get_amount() {
119
- if ( $this->use_wc27 && is_callable( array( $this->object, 'get_amount' ) ) ) {
120
- return $this->object->get_amount();
121
- }
122
-
123
- return $this->object->coupon_amount;
124
- }
125
-
126
- public function get_individual_use() {
127
- if ( $this->use_wc27 && is_callable( array( $this->object, 'get_individual_use' ) ) ) {
128
- return $this->object->get_individual_use();
129
- }
130
-
131
- return $this->object->individual_use == 'yes';
132
- }
133
-
134
- public function get_limit_usage_to_x_items() {
135
- if ( $this->use_wc27 && is_callable( array( $this->object, 'get_limit_usage_to_x_items' ) ) ) {
136
- return $this->object->get_limit_usage_to_x_items();
137
- }
138
-
139
- return $this->object->limit_usage_to_x_items;
140
- }
141
-
142
- public function set_limit_usage_to_x_items( $limit_usage_to_x_items ) {
143
- if ( $this->use_wc27 && is_callable( array( $this->object, 'set_limit_usage_to_x_items' ) ) ) {
144
- $this->object->set_limit_usage_to_x_items( $limit_usage_to_x_items );
145
- } else {
146
- $this->object->limit_usage_to_x_items = $limit_usage_to_x_items;
147
- }
148
- }
149
-
150
- public function get_discount_type() {
151
- if ( $this->use_wc27 && is_callable( array( $this->object, 'get_discount_type' ) ) ) {
152
- return $this->object->get_discount_type();
153
- }
154
-
155
- return $this->object->discount_type;
156
- }
157
-
158
- public function set_discount_type( $discount_type ) {
159
- if ( $this->use_wc27 && is_callable( array( $this->object, 'set_discount_type' ) ) ) {
160
- $this->object->set_discount_type( $discount_type );
161
- } else {
162
- $this->object->discount_type = $discount_type;
163
- $this->object->type = $discount_type;
164
- }
165
- }
166
-
167
-
168
- public function get_email_restrictions() {
169
- if ( $this->use_wc27 && is_callable( array( $this->object, 'get_email_restrictions' ) ) ) {
170
- return $this->object->get_email_restrictions();
171
- }
172
-
173
- return $this->object->customer_email;
174
- }
175
-
176
- public function get_product_ids() {
177
- if ( $this->use_wc27 && is_callable( array( $this->object, 'get_product_ids' ) ) ) {
178
- return $this->object->get_product_ids();
179
- }
180
-
181
- return $this->object->product_ids;
182
- }
183
-
184
- public function get_free_shipping() {
185
- if ( $this->use_wc27 && is_callable( array( $this->object, 'get_free_shipping' ) ) ) {
186
- return $this->object->get_free_shipping();
187
- }
188
-
189
- return $this->object->enable_free_shipping();
190
- }
191
-
192
- public function get_product_categories() {
193
- if ( $this->use_wc27 && is_callable( array( $this->object, 'get_product_categories' ) ) ) {
194
- return $this->object->get_product_categories();
195
- }
196
-
197
- return $this->object->product_categories;
198
- }
199
-
200
- public function get_minimum_amount() {
201
- if ( $this->use_wc27 && is_callable( array( $this->object, 'get_minimum_amount' ) ) ) {
202
- return $this->object->get_minimum_amount();
203
- }
204
-
205
- return $this->object->minimum_amount;
206
- }
207
-
208
- /**
209
- * Set the product IDs this coupon cannot be used with.
210
- * @since 2.4.2 (For WC3.0)
211
- * @param array $excluded_product_ids
212
- * @throws WC_Data_Exception
213
- */
214
- public function set_excluded_product_ids( $excluded_product_ids ) {
215
- if ( $this->use_wc27 && is_callable( array( $this->object, 'set_excluded_product_ids' ) ) ) {
216
- $this->object->set_excluded_product_ids( $excluded_product_ids );
217
- } else {
218
- //NOTE: Prior to WC2.7 it was called exclude_ instead of excluded_
219
- $this->object->exclude_product_ids = $excluded_product_ids;
220
- }
221
- }
222
-
223
- /**
224
- * Set the product category IDs this coupon cannot be used with.
225
- * @since 2.4.2 (For WC3.0)
226
- * @param array $excluded_product_categories
227
- * @throws WC_Data_Exception
228
- */
229
- public function set_excluded_product_categories( $excluded_product_categories ) {
230
- if ( $this->use_wc27 && is_callable( array( $this->object, 'set_excluded_product_categories' ) ) ) {
231
- $this->object->set_excluded_product_categories( $excluded_product_categories );
232
- } else {
233
- //NOTE: Prior to WC2.7 it was called exclude_ instead of excluded_
234
- $this->object->exclude_product_categories = $excluded_product_categories;
235
- }
236
- }
237
-
238
- /**
239
- * Set if this coupon should excluded sale items or not.
240
- * @since 2.4.2 (For WC3.0)
241
- * @param bool $exclude_sale_items
242
- * @throws WC_Data_Exception
243
- */
244
- public function set_exclude_sale_items( $exclude_sale_items ) {
245
- if ( $this->use_wc27 && is_callable( array( $this->object, 'set_exclude_sale_items' ) ) ) {
246
- $this->object->set_exclude_sale_items( $exclude_sale_items );
247
- } else {
248
- //NOTE: Prior to WC2.7 it was yes/no instead of boolean
249
- $this->object->exclude_sale_items = $exclude_sale_items ? 'yes' : 'no';
250
- }
251
- }
252
-
253
- /**
254
- * Check the type of the coupon
255
- * @param string|array $type The type(s) we want to check for
256
- * @return bool True if the coupon is of the type
257
- */
258
- public function is_type( $type ) {
259
- //Backwards compatibility 2.2.11
260
- if ( method_exists( $this->object, 'is_type' ) ) {
261
- return $this->object->is_type( $type );
262
- }
263
-
264
- return ( $this->object->discount_type == $type || ( is_array( $type ) && in_array( $this->object->discount_type, $type ) ) ) ? true : false;
265
- }
266
-
267
- protected function set_meta_legacy( $meta_key, $value ) {
268
- $this->maybe_get_custom_fields();
269
- //WJECF()->log('...setting legacy meta ' . $meta_key );
270
- $this->legacy_custom_fields[ $meta_key ] = array( $value );
271
- $this->legacy_unsaved_keys[] = $meta_key;
272
- }
273
-
274
- /**
275
- * Save the metadata
276
- * @return id of this object
277
- */
278
- public function save() {
279
- //WJECF()->log('Saving ' . $this->get_id() );
280
- if ( $this->use_wc27 && is_callable( array( $this->object, 'save' ) ) ) {
281
- return $this->object->save();
282
- }
283
-
284
- //Save the unsaved...
285
- foreach( $this->legacy_unsaved_keys as $meta_key ) {
286
- //WJECF()->log('...saving legacy meta ' . $meta_key );
287
- $value = reset( $this->legacy_custom_fields[ $meta_key ] );
288
- if ( $value === null ) {
289
- delete_post_meta( $this->get_id(), $meta_key );
290
- } else {
291
- update_post_meta( $this->get_id(), $meta_key, $value );
292
- }
293
- }
294
- $this->legacy_unsaved_keys = array();
295
-
296
- return $this->get_id();
297
- }
298
-
299
- protected $legacy_custom_fields = null; // [ 'meta_key' => [ array_of_values ] ]
300
- protected $legacy_unsaved_keys = array();
301
-
302
- protected function maybe_get_custom_fields() {
303
- //Read custom fields if not yet done
304
- if ( is_null( $this->legacy_custom_fields ) ) {
305
- $this->legacy_custom_fields = $this->object->coupon_custom_fields;
306
- }
307
- }
308
-
309
- protected function get_meta_legacy( $meta_key, $single = true ) {
310
- //Read custom fields if not yet done
311
- $this->maybe_get_custom_fields();
312
-
313
- if ( isset( $this->legacy_custom_fields[ $meta_key ] ) ) {
314
- $values = $this->legacy_custom_fields[ $meta_key ];
315
- //WP_CLI::log( "LEGACY:" . print_r( $values, true ));
316
- if ($single) {
317
- return maybe_unserialize( reset( $values ) ); //reset yields the first
318
- }
319
- $values = array_map( 'maybe_unserialize', $values );
320
- return $values;
321
- }
322
-
323
- return $single ? '' : array();
324
- }
325
-
326
- }
327
-
328
- class WJECF_Wrap_Product extends WJECF_Wrap {
329
-
330
- protected $legacy_custom_fields = null; // [ 'meta_key' => [ array_of_values ] ]
331
- protected $legacy_unsaved_keys = array();
332
-
333
- protected function get_meta_legacy( $meta_key, $single = true ) {
334
- if ( isset( $this->legacy_custom_fields[ $meta_key ] ) ) {
335
- $values = $this->legacy_custom_fields[ $meta_key ];
336
- //WP_CLI::log( "LEGACY:" . print_r( $values, true ));
337
- if ($single) {
338
- return maybe_unserialize( reset( $values ) ); //reset yields the first
339
- }
340
- $values = array_map( 'maybe_unserialize', $values );
341
- return $values;
342
- }
343
-
344
- return get_post_meta( $this->get_product_or_variation_id(), $meta_key, $single );
345
- }
346
-
347
- public function set_meta_legacy( $meta_key, $value ) {
348
- $this->legacy_custom_fields[ $meta_key ] = array( 0 => $value );
349
- $this->legacy_unsaved_keys[] = $meta_key;
350
- }
351
-
352
- /**
353
- * Combines get_meta or get_prop (in legacy WC those were the same thing, in WC3.0+ there is a difference)
354
- * @param string $field_name
355
- * @param bool $single
356
- * @return
357
- */
358
- public function get_field($field_name, $single = true) {
359
- if ( $this->use_wc27 ) {
360
- $values = $this->get_meta($field_name, $single);
361
- if ( ! empty( $values ) ) return $values;
362
-
363
- if ( is_callable( array( $this->object, 'get_prop' ) ) ) {
364
- $value = $this->object->get_prop($field_name);
365
- if ( ! empty( $value ) ) return $single ? $value : array( $value );
366
- }
367
- }
368
-
369
- return get_post_meta( $this->get_product_or_variation_id(), $field_name, $single );
370
- }
371
-
372
- private function is_variation() {
373
- return $this->object instanceof WC_Product_Variation;
374
- }
375
-
376
- /**
377
- * Retrieve the id of the product or the variation id if it's a variant.
378
- *
379
- * (2.4.0: Moved from WJECF_Controller to WJECF_WC)
380
- *
381
- * @param WC_Product $product
382
- * @return int|bool The variation or product id. False if not a valid product
383
- */
384
- public function get_product_or_variation_id() {
385
- if ( $this->is_variation() ) {
386
- return $this->get_variation_id();
387
- } elseif ( $this->object instanceof WC_Product ) {
388
- return $this->get_id();
389
- } else {
390
- return false;
391
- }
392
- }
393
-
394
- /**
395
- * Retrieve the id of the parent product if it's a variation; otherwise retrieve this products id
396
- *
397
- * (2.4.0: Moved from WJECF_Controller to WJECF_WC)
398
- *
399
- * @param WC_Product $product
400
- * @return int|bool The product id. False if this product is not a variation
401
- */
402
- public function get_variable_product_id() {
403
- if ( ! $this->is_variation() ) {
404
- return false;
405
- }
406
-
407
- if ( $this->use_wc27 && is_callable( array( $this->object, 'get_parent_id' ) ) ) {
408
- return $this->object->get_parent_id();
409
- } else {
410
- return wp_get_post_parent_id( $this->object->variation_id );
411
- }
412
- }
413
-
414
- /**
415
- * Get current variation id
416
- * @return int|bool False if this is not a variation
417
- */
418
- protected function get_variation_id() {
419
- if ( ! $this->is_variation() ) {
420
- return false;
421
- }
422
-
423
- if ( $this->use_wc27 && is_callable( array( $this->object, 'get_id' ) ) ) {
424
- return $this->object->get_id();
425
- } elseif ( $this->use_wc27 && is_callable( array( $this->object, 'get_variation_id' ) ) ) {
426
- return $this->object->get_variation_id();
427
- }
428
- return $this->object->variation_id;
429
- }
430
-
431
-
432
- public function get_name() {
433
- if ( $this->use_wc27 && is_callable( array( $this->object, 'get_name' ) ) ) {
434
- return $this->object->get_name();
435
- } else {
436
- return $this->object->post->post_title;
437
- }
438
- }
439
-
440
- public function get_description() {
441
- if ( $this->use_wc27 && is_callable( array( $this->object, 'get_description' ) ) ) {
442
- return $this->object->get_description();
443
- } else {
444
- return $this->object->post->post_content;
445
- }
446
- }
447
-
448
- public function get_short_description() {
449
- if ( $this->use_wc27 && is_callable( array( $this->object, 'get_short_description' ) ) ) {
450
- return $this->object->get_short_description();
451
- } else {
452
- return $this->object->post->post_excerpt;
453
- }
454
- }
455
-
456
- public function get_status() {
457
- if ( $this->use_wc27 && is_callable( array( $this->object, 'get_status' ) ) ) {
458
- return $this->object->get_status();
459
- } else {
460
- return $this->object->post->post_status;
461
- }
462
- }
463
-
464
- public function get_tag_ids() {
465
- if ( $this->use_wc27 && is_callable( array( $this->object, 'get_tag_ids' ) ) ) {
466
- return $this->object->get_tag_ids();
467
- } else {
468
- return $this->legacy_get_term_ids( 'product_tag' );
469
- }
470
- }
471
-
472
- protected function legacy_get_term_ids( $taxonomy ) {
473
- $terms = get_the_terms( $this->get_id(), $taxonomy );
474
- if ( false === $terms || is_wp_error( $terms ) ) {
475
- return array();
476
- }
477
- return wp_list_pluck( $terms, 'term_id' );
478
- }
479
-
480
- /**
481
- * If set, get the default attributes for a variable product.
482
- *
483
- * @param string $attribute_name
484
- * @return string
485
- */
486
- public function get_variation_default_attribute( $attribute_name ) {
487
- if ( $this->use_wc27 && is_callable( array( $this->object, 'get_variation_default_attribute' ) ) ) {
488
- return $this->object->get_variation_default_attribute( $attribute_name );
489
- }
490
- return '';
491
- }
492
- }
493
-
494
-
495
- class WJECF_Wrap_Customer extends WJECF_Wrap {
496
-
497
  }
1
+ <?php
2
+
3
+ defined('ABSPATH') or die();
4
+
5
+ /**
6
+ * Wrapper for WC objects. Helps to maintain compatibility between both WC2 and WC3
7
+ */
8
+ class WJECF_Wrap {
9
+ protected $object = null;
10
+
11
+ public function __construct( $object ) {
12
+ $this->object = $object;
13
+ //error_log('Wrapping ' . get_class( $object ) );
14
+ }
15
+
16
+ public $use_wc27 = true;
17
+ public function get_id() {
18
+ //Since WC 2.7
19
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'get_id' ) ) ) {
20
+ return $this->object->get_id();
21
+ }
22
+ return $this->object->id;
23
+ }
24
+
25
+ public function holds( $object ) {
26
+ return $object === $this->object;
27
+ }
28
+
29
+ /**
30
+ * Get Meta Data by Key.
31
+ *
32
+ * If no value found:
33
+ * If $single is true, an empty string is returned.
34
+ * If $single is false, an empty array is returned.
35
+ *
36
+ * @since 2.4.0
37
+ * @param string $key
38
+ * @param bool $single return first found meta, or all
39
+ * @return mixed
40
+ */
41
+ public final function get_meta( $meta_key, $single = true ) {
42
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'get_meta' ) ) ) {
43
+ return $this->get_meta_wc27( $meta_key, $single );
44
+ }
45
+
46
+ return $this->get_meta_legacy( $meta_key, $single );
47
+ }
48
+
49
+ protected function get_meta_wc27( $meta_key, $single = true ) {
50
+ $values = $this->object->get_meta( $meta_key, $single );
51
+ if ($single) {
52
+ return $values; //it's just one, dispite the plural in the name!
53
+ }
54
+
55
+ if ( $values === '' ) {
56
+ return array(); //get_meta returns empty string if meta does not exist
57
+ }
58
+
59
+ return wp_list_pluck( array_values( $values ), 'value' ); //when not using array_values; the index might not start with 0
60
+ }
61
+
62
+ protected function get_meta_legacy( $meta_key, $single = true ) {
63
+ throw new Exception( sprintf( '%s::get_meta_legacy not implemented', get_class( $this ) ) );
64
+ }
65
+
66
+ /**
67
+ * Update single meta data item by meta key.
68
+ * Call save() if the values must to be persisted.
69
+ * @since 2.4.0
70
+ * @param string $meta_key
71
+ * @param mixed $value The value; use null to clear
72
+ */
73
+ public final function set_meta( $meta_key, $value ) {
74
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'update_meta_data' ) ) ) {
75
+ if ( $value === null ) {
76
+ $this->object->delete_meta_data( $meta_key );
77
+ } else {
78
+ $this->object->update_meta_data( $meta_key, $value );
79
+ }
80
+ return;
81
+ }
82
+
83
+ $this->set_meta_legacy( $meta_key, $value );
84
+ }
85
+
86
+ protected function set_meta_legacy( $meta_key, $value ) {
87
+ throw new Exception( sprintf( '%s::set_meta_legacy not implemented', get_class( $this ) ) );
88
+ }
89
+
90
+ }
91
+
92
+ /**
93
+ * Wrap a data object ( Coupons and products were converted to WC_Data since WC 2.7.0 )
94
+ */
95
+ class WJECF_Wrap_Coupon extends WJECF_Wrap {
96
+
97
+ public function exists() {
98
+ return $this->get_id() > 0;
99
+ }
100
+
101
+ public function get_code() {
102
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'get_code' ) ) ) {
103
+ return $this->object->get_code();
104
+ }
105
+
106
+ return $this->object->code;
107
+ }
108
+
109
+ public function get_description() {
110
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'get_description' ) ) ) {
111
+ return $this->object->get_description();
112
+ }
113
+
114
+ $post = get_post( $this->get_id() );
115
+ return $post->post_excerpt;
116
+ }
117
+
118
+ public function get_amount() {
119
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'get_amount' ) ) ) {
120
+ return $this->object->get_amount();
121
+ }
122
+
123
+ return $this->object->coupon_amount;
124
+ }
125
+
126
+ public function get_individual_use() {
127
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'get_individual_use' ) ) ) {
128
+ return $this->object->get_individual_use();
129
+ }
130
+
131
+ return $this->object->individual_use == 'yes';
132
+ }
133
+
134
+ public function get_limit_usage_to_x_items() {
135
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'get_limit_usage_to_x_items' ) ) ) {
136
+ return $this->object->get_limit_usage_to_x_items();
137
+ }
138
+
139
+ return $this->object->limit_usage_to_x_items;
140
+ }
141
+
142
+ public function set_limit_usage_to_x_items( $limit_usage_to_x_items ) {
143
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'set_limit_usage_to_x_items' ) ) ) {
144
+ $this->object->set_limit_usage_to_x_items( $limit_usage_to_x_items );
145
+ } else {
146
+ $this->object->limit_usage_to_x_items = $limit_usage_to_x_items;
147
+ }
148
+ }
149
+
150
+ public function get_discount_type() {
151
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'get_discount_type' ) ) ) {
152
+ return $this->object->get_discount_type();
153
+ }
154
+
155
+ return $this->object->discount_type;
156
+ }
157
+
158
+ public function set_discount_type( $discount_type ) {
159
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'set_discount_type' ) ) ) {
160
+ $this->object->set_discount_type( $discount_type );
161
+ } else {
162
+ $this->object->discount_type = $discount_type;
163
+ $this->object->type = $discount_type;
164
+ }
165
+ }
166
+
167
+
168
+ public function get_email_restrictions() {
169
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'get_email_restrictions' ) ) ) {
170
+ return $this->object->get_email_restrictions();
171
+ }
172
+
173
+ return $this->object->customer_email;
174
+ }
175
+
176
+ public function get_product_ids() {
177
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'get_product_ids' ) ) ) {
178
+ return $this->object->get_product_ids();
179
+ }
180
+
181
+ return $this->object->product_ids;
182
+ }
183
+
184
+ public function get_free_shipping() {
185
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'get_free_shipping' ) ) ) {
186
+ return $this->object->get_free_shipping();
187
+ }
188
+
189
+ return $this->object->enable_free_shipping();
190
+ }
191
+
192
+ public function get_product_categories() {
193
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'get_product_categories' ) ) ) {
194
+ return $this->object->get_product_categories();
195
+ }
196
+
197
+ return $this->object->product_categories;
198
+ }
199
+
200
+ public function get_minimum_amount() {
201
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'get_minimum_amount' ) ) ) {
202
+ return $this->object->get_minimum_amount();
203
+ }
204
+
205
+ return $this->object->minimum_amount;
206
+ }
207
+
208
+ /**
209
+ * Set the product IDs this coupon cannot be used with.
210
+ * @since 2.4.2 (For WC3.0)
211
+ * @param array $excluded_product_ids
212
+ * @throws WC_Data_Exception
213
+ */
214
+ public function set_excluded_product_ids( $excluded_product_ids ) {
215
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'set_excluded_product_ids' ) ) ) {
216
+ $this->object->set_excluded_product_ids( $excluded_product_ids );
217
+ } else {
218
+ //NOTE: Prior to WC2.7 it was called exclude_ instead of excluded_
219
+ $this->object->exclude_product_ids = $excluded_product_ids;
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Set the product category IDs this coupon cannot be used with.
225
+ * @since 2.4.2 (For WC3.0)
226
+ * @param array $excluded_product_categories
227
+ * @throws WC_Data_Exception
228
+ */
229
+ public function set_excluded_product_categories( $excluded_product_categories ) {
230
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'set_excluded_product_categories' ) ) ) {
231
+ $this->object->set_excluded_product_categories( $excluded_product_categories );
232
+ } else {
233
+ //NOTE: Prior to WC2.7 it was called exclude_ instead of excluded_
234
+ $this->object->exclude_product_categories = $excluded_product_categories;
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Set if this coupon should excluded sale items or not.
240
+ * @since 2.4.2 (For WC3.0)
241
+ * @param bool $exclude_sale_items
242
+ * @throws WC_Data_Exception
243
+ */
244
+ public function set_exclude_sale_items( $exclude_sale_items ) {
245
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'set_exclude_sale_items' ) ) ) {
246
+ $this->object->set_exclude_sale_items( $exclude_sale_items );
247
+ } else {
248
+ //NOTE: Prior to WC2.7 it was yes/no instead of boolean
249
+ $this->object->exclude_sale_items = $exclude_sale_items ? 'yes' : 'no';
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Check the type of the coupon
255
+ * @param string|array $type The type(s) we want to check for
256
+ * @return bool True if the coupon is of the type
257
+ */
258
+ public function is_type( $type ) {
259
+ //Backwards compatibility 2.2.11
260
+ if ( method_exists( $this->object, 'is_type' ) ) {
261
+ return $this->object->is_type( $type );
262
+ }
263
+
264
+ return ( $this->object->discount_type == $type || ( is_array( $type ) && in_array( $this->object->discount_type, $type ) ) ) ? true : false;
265
+ }
266
+
267
+ protected function set_meta_legacy( $meta_key, $value ) {
268
+ $this->maybe_get_custom_fields();
269
+ //WJECF()->log('...setting legacy meta ' . $meta_key );
270
+ $this->legacy_custom_fields[ $meta_key ] = array( $value );
271
+ $this->legacy_unsaved_keys[] = $meta_key;
272
+ }
273
+
274
+ /**
275
+ * Save the metadata
276
+ * @return id of this object
277
+ */
278
+ public function save() {
279
+ //WJECF()->log('Saving ' . $this->get_id() );
280
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'save' ) ) ) {
281
+ return $this->object->save();
282
+ }
283
+
284
+ //Save the unsaved...
285
+ foreach( $this->legacy_unsaved_keys as $meta_key ) {
286
+ //WJECF()->log('...saving legacy meta ' . $meta_key );
287
+ $value = reset( $this->legacy_custom_fields[ $meta_key ] );
288
+ if ( $value === null ) {
289
+ delete_post_meta( $this->get_id(), $meta_key );
290
+ } else {
291
+ update_post_meta( $this->get_id(), $meta_key, $value );
292
+ }
293
+ }
294
+ $this->legacy_unsaved_keys = array();
295
+
296
+ return $this->get_id();
297
+ }
298
+
299
+ protected $legacy_custom_fields = null; // [ 'meta_key' => [ array_of_values ] ]
300
+ protected $legacy_unsaved_keys = array();
301
+
302
+ protected function maybe_get_custom_fields() {
303
+ //Read custom fields if not yet done
304
+ if ( is_null( $this->legacy_custom_fields ) ) {
305
+ $this->legacy_custom_fields = $this->object->coupon_custom_fields;
306
+ }
307
+ }
308
+
309
+ protected function get_meta_legacy( $meta_key, $single = true ) {
310
+ //Read custom fields if not yet done
311
+ $this->maybe_get_custom_fields();
312
+
313
+ if ( isset( $this->legacy_custom_fields[ $meta_key ] ) ) {
314
+ $values = $this->legacy_custom_fields[ $meta_key ];
315
+ //WP_CLI::log( "LEGACY:" . print_r( $values, true ));
316
+ if ($single) {
317
+ return maybe_unserialize( reset( $values ) ); //reset yields the first
318
+ }
319
+ $values = array_map( 'maybe_unserialize', $values );
320
+ return $values;
321
+ }
322
+
323
+ return $single ? '' : array();
324
+ }
325
+
326
+ }
327
+
328
+ class WJECF_Wrap_Product extends WJECF_Wrap {
329
+
330
+ protected $legacy_custom_fields = null; // [ 'meta_key' => [ array_of_values ] ]
331
+ protected $legacy_unsaved_keys = array();
332
+
333
+ protected function get_meta_legacy( $meta_key, $single = true ) {
334
+ if ( isset( $this->legacy_custom_fields[ $meta_key ] ) ) {
335
+ $values = $this->legacy_custom_fields[ $meta_key ];
336
+ //WP_CLI::log( "LEGACY:" . print_r( $values, true ));
337
+ if ($single) {
338
+ return maybe_unserialize( reset( $values ) ); //reset yields the first
339
+ }
340
+ $values = array_map( 'maybe_unserialize', $values );
341
+ return $values;
342
+ }
343
+
344
+ return get_post_meta( $this->get_product_or_variation_id(), $meta_key, $single );
345
+ }
346
+
347
+ public function set_meta_legacy( $meta_key, $value ) {
348
+ $this->legacy_custom_fields[ $meta_key ] = array( 0 => $value );
349
+ $this->legacy_unsaved_keys[] = $meta_key;
350
+ }
351
+
352
+ /**
353
+ * Combines get_meta or get_prop (in legacy WC those were the same thing, in WC3.0+ there is a difference)
354
+ * @param string $field_name
355
+ * @param bool $single
356
+ * @return
357
+ */
358
+ public function get_field($field_name, $single = true) {
359
+ if ( $this->use_wc27 ) {
360
+ $values = $this->get_meta($field_name, $single);
361
+ if ( ! empty( $values ) ) return $values;
362
+
363
+ if ( is_callable( array( $this->object, 'get_prop' ) ) ) {
364
+ $value = $this->object->get_prop($field_name);
365
+ if ( ! empty( $value ) ) return $single ? $value : array( $value );
366
+ }
367
+ }
368
+
369
+ return get_post_meta( $this->get_product_or_variation_id(), $field_name, $single );
370
+ }
371
+
372
+ private function is_variation() {
373
+ return $this->object instanceof WC_Product_Variation;
374
+ }
375
+
376
+ /**
377
+ * Retrieve the id of the product or the variation id if it's a variant.
378
+ *
379
+ * (2.4.0: Moved from WJECF_Controller to WJECF_WC)
380
+ *
381
+ * @param WC_Product $product
382
+ * @return int|bool The variation or product id. False if not a valid product
383
+ */
384
+ public function get_product_or_variation_id() {
385
+ if ( $this->is_variation() ) {
386
+ return $this->get_variation_id();
387
+ } elseif ( $this->object instanceof WC_Product ) {
388
+ return $this->get_id();
389
+ } else {
390
+ return false;
391
+ }
392
+ }
393
+
394
+ /**
395
+ * Retrieve the id of the parent product if it's a variation; otherwise retrieve this products id
396
+ *
397
+ * (2.4.0: Moved from WJECF_Controller to WJECF_WC)
398
+ *
399
+ * @param WC_Product $product
400
+ * @return int|bool The product id. False if this product is not a variation
401
+ */
402
+ public function get_variable_product_id() {
403
+ if ( ! $this->is_variation() ) {
404
+ return false;
405
+ }
406
+
407
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'get_parent_id' ) ) ) {
408
+ return $this->object->get_parent_id();
409
+ } else {
410
+ return wp_get_post_parent_id( $this->object->variation_id );
411
+ }
412
+ }
413
+
414
+ /**
415
+ * Get current variation id
416
+ * @return int|bool False if this is not a variation
417
+ */
418
+ protected function get_variation_id() {
419
+ if ( ! $this->is_variation() ) {
420
+ return false;
421
+ }
422
+
423
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'get_id' ) ) ) {
424
+ return $this->object->get_id();
425
+ } elseif ( $this->use_wc27 && is_callable( array( $this->object, 'get_variation_id' ) ) ) {
426
+ return $this->object->get_variation_id();
427
+ }
428
+ return $this->object->variation_id;
429
+ }
430
+
431
+
432
+ public function get_name() {
433
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'get_name' ) ) ) {
434
+ return $this->object->get_name();
435
+ } else {
436
+ return $this->object->post->post_title;
437
+ }
438
+ }
439
+
440
+ public function get_description() {
441
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'get_description' ) ) ) {
442
+ return $this->object->get_description();
443
+ } else {
444
+ return $this->object->post->post_content;
445
+ }
446
+ }
447
+
448
+ public function get_short_description() {
449
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'get_short_description' ) ) ) {
450
+ return $this->object->get_short_description();
451
+ } else {
452
+ return $this->object->post->post_excerpt;
453
+ }
454
+ }
455
+
456
+ public function get_status() {
457
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'get_status' ) ) ) {
458
+ return $this->object->get_status();
459
+ } else {
460
+ return $this->object->post->post_status;
461
+ }
462
+ }
463
+
464
+ public function get_tag_ids() {
465
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'get_tag_ids' ) ) ) {
466
+ return $this->object->get_tag_ids();
467
+ } else {
468
+ return $this->legacy_get_term_ids( 'product_tag' );
469
+ }
470
+ }
471
+
472
+ protected function legacy_get_term_ids( $taxonomy ) {
473
+ $terms = get_the_terms( $this->get_id(), $taxonomy );
474
+ if ( false === $terms || is_wp_error( $terms ) ) {
475
+ return array();
476
+ }
477
+ return wp_list_pluck( $terms, 'term_id' );
478
+ }
479
+
480
+ /**
481
+ * If set, get the default attributes for a variable product.
482
+ *
483
+ * @param string $attribute_name
484
+ * @return string
485
+ */
486
+ public function get_variation_default_attribute( $attribute_name ) {
487
+ if ( $this->use_wc27 && is_callable( array( $this->object, 'get_variation_default_attribute' ) ) ) {
488
+ return $this->object->get_variation_default_attribute( $attribute_name );
489
+ }
490
+ return '';
491
+ }
492
+ }
493
+
494
+
495
+ class WJECF_Wrap_Customer extends WJECF_Wrap {
496
+
497
  }
includes/admin/WJECF_Admin.php CHANGED
@@ -1,457 +1,457 @@
1
- <?php
2
-
3
- defined('ABSPATH') or die();
4
-
5
- if ( ! class_exists('WJECF_Admin') ) {
6
-
7
- class WJECF_Admin extends Abstract_WJECF_Plugin {
8
-
9
- public function __construct() {
10
- $this->set_plugin_data( array(
11
- 'description' => __( 'Admin interface of WooCommerce Extended Coupon Features.', 'woocommerce-jos-autocoupon' ),
12
- 'dependencies' => array(),
13
- 'can_be_disabled' => false
14
- ) );
15
- }
16
-
17
- public function init_admin_hook() {
18
- add_action( 'admin_notices', array( $this, 'admin_notices'));
19
-
20
- if ( ! WJECF_WC()->check_woocommerce_version('2.3.0') ) {
21
- $this->enqueue_notice( '<p>' .
22
- __( '<strong>WooCommerce Extended Coupon Features:</strong> You are using an old version of WooCommerce. Updating of WooCommerce is recommended as using an outdated version might cause unexpected behaviour in combination with modern plugins.' )
23
- . '</p>', 'notice-warning' );
24
- }
25
- //Admin hooks
26
- add_filter( 'plugin_row_meta', array( $this, 'wjecf_plugin_meta' ), 10, 2 );
27
- add_action( 'admin_head', array( $this, 'on_admin_head'));
28
-
29
- add_filter( 'woocommerce_coupon_data_tabs', array( $this, 'admin_coupon_options_tabs' ), 20, 1);
30
- add_action( 'woocommerce_coupon_data_panels', array( $this, 'admin_coupon_options_panels' ), 10, 0 );
31
- add_action( 'woocommerce_process_shop_coupon_meta', array( $this, 'process_shop_coupon_meta' ), 10, 2 );
32
-
33
- add_action('woocommerce_coupon_options_usage_restriction', array( $this, 'on_woocommerce_coupon_options_usage_restriction' ), 20, 1);
34
-
35
- add_action( 'wjecf_coupon_metabox_checkout', array( $this, 'admin_coupon_metabox_checkout' ), 10, 2 );
36
- add_action( 'wjecf_coupon_metabox_customer', array( $this, 'admin_coupon_metabox_customer' ), 10, 2 );
37
- //add_action( 'wjecf_coupon_metabox_misc', array( $this, 'admin_coupon_metabox_misc' ), 10, 2 );
38
-
39
- $this->add_inline_style( '
40
- #woocommerce-coupon-data .wjecf-not-wide { width:50% }
41
- ');
42
- }
43
-
44
- // ===========================================================================
45
- // START - ADMIN NOTICES
46
- // Allows notices to be displayed on the admin pages
47
- // ===========================================================================
48
-
49
- private $notices = array();
50
-
51
- /**
52
- * Enqueue a notice to display on the admin page
53
- * @param stirng $html Please embed in <p> tags
54
- * @param string $class
55
- */
56
- public function enqueue_notice( $html, $class = 'notice-info' ) {
57
- $this->notices[] = array( 'class' => $class, 'html' => $html );
58
- }
59
-
60
- public function admin_notices() {
61
- foreach( $this->notices as $notice ) {
62
- echo '<div class="notice ' . $notice['class'] . '">';
63
- echo '<p><strong>WooCommerce Extended Coupon Features</strong> &#8211; ';
64
- echo $notice['html'];
65
- echo '</div>';
66
- }
67
- $this->notices = array();
68
- }
69
-
70
- // ===========================================================================
71
- // END - ADMIN NOTICES
72
- // ===========================================================================
73
-
74
- //2.3.6 Inline css
75
- private $admin_css = '';
76
-
77
- /**
78
- * 2.3.6
79
- * @return void
80
- */
81
- function on_admin_head() {
82
- //Output inline style for the admin pages
83
- if ( ! empty( $this->admin_css ) ) {
84
- echo '<style type="text/css">' . $this->admin_css . '</style>';
85
- $this->admin_css = '';
86
- }
87
-
88
- //Enqueue scripts
89
- wp_enqueue_script( "wjecf-admin", WJECF()->plugin_url() . "assets/js/wjecf-admin.js", array( 'jquery' ), WJECF()->plugin_version() );
90
- wp_localize_script( 'wjecf-admin', 'wjecf_admin_i18n', array(
91
- 'label_and' => __( '(AND)', 'woocommerce-jos-autocoupon' ),
92
- 'label_or' => __( '(OR)', 'woocommerce-jos-autocoupon' )
93
- ) );
94
-
95
- }
96
-
97
- //Add tabs to the coupon option page
98
- public function admin_coupon_options_tabs( $tabs ) {
99
-
100
- $tabs['extended_features_checkout'] = array(
101
- 'label' => __( 'Checkout', 'woocommerce-jos-autocoupon' ),
102
- 'target' => 'wjecf_coupondata_checkout',
103
- 'class' => 'wjecf_coupondata_checkout',
104
- );
105
-
106
- $tabs['extended_features_misc'] = array(
107
- 'label' => __( 'Miscellaneous', 'woocommerce-jos-autocoupon' ),
108
- 'target' => 'wjecf_coupondata_misc',
109
- 'class' => 'wjecf_coupondata_misc',
110
- );
111
-
112
- return $tabs;
113
- }
114
-
115
- //Add panels to the coupon option page
116
- public function admin_coupon_options_panels() {
117
- global $thepostid, $post;
118
- $thepostid = empty( $thepostid ) ? $post->ID : $thepostid;
119
- ?>
120
- <div id="wjecf_coupondata_checkout" class="panel woocommerce_options_panel">
121
- <?php
122
- do_action( 'wjecf_coupon_metabox_checkout', $thepostid, $post );
123
- do_action( 'wjecf_coupon_metabox_customer', $thepostid, $post );
124
- $this->admin_coupon_data_footer();
125
- ?>
126
- </div>
127
- <div id="wjecf_coupondata_misc" class="panel woocommerce_options_panel">
128
- <?php
129
- //Allow other classes to inject options
130
- do_action( 'wjecf_woocommerce_coupon_options_extended_features', $thepostid, $post );
131
- do_action( 'wjecf_coupon_metabox_misc', $thepostid, $post );
132
- $this->admin_coupon_data_footer();
133
- ?>
134
- </div>
135
- <?php
136
- }
137
-
138
- public function admin_coupon_data_footer() {
139
- $documentation_url = WJECF()->plugin_url( 'docs/index.html' );
140
- if ( ! WJECF()->is_pro() ) {
141
- $documentation_url = 'http://www.soft79.nl/documentation/wjecf';
142
- ?>
143
- <h3><?php _e( 'Do you find WooCommerce Extended Coupon Features useful?', 'woocommerce-jos-autocoupon'); ?></h3>
144
- <p class="form-field"><label for="wjecf_donate_button"><?php
145
- echo esc_html( __('Express your gratitude', 'woocommerce-jos-autocoupon' ) );
146
- ?></label>
147
- <a id="wjecf_donate_button" href="<?php echo $this->get_donate_url(); ?>" target="_blank" class="button button-primary">
148
- <?php
149
- echo esc_html( __('Donate to the developer', 'woocommerce-jos-autocoupon' ) );
150
- ?></a><br>
151
- Or get the PRO version at <a href="http://www.soft79.nl" target="_blank">www.soft79.nl</a>.
152
- </p>
153
- <?php
154
- }
155
- //Documentation link
156
- echo '<h3>' . __( 'Documentation', 'woocommerce-jos-autocoupon' ) . '</h3>';
157
- echo '<p><a href="' . $documentation_url . '" target="_blank">' .
158
- __( 'WooCommerce Extended Coupon Features Documentation', 'woocommerce-jos-autocoupon' ) . '</a></p>';
159
-
160
- }
161
-
162
- // //Tab 'extended features'
163
- //public function admin_coupon_metabox_products( $thepostid, $post ) {
164
-
165
- //since 2.5.0 moved to the 'Usage restriction' tab
166
- public function on_woocommerce_coupon_options_usage_restriction() {
167
- global $thepostid, $post;
168
- $thepostid = empty( $thepostid ) ? $post->ID : $thepostid;
169
-
170
- //See WooCommerce class-wc-meta-box-coupon-data.php function ouput
171
-
172
- echo "<h3>" . esc_html( __( 'Matching products', 'woocommerce-jos-autocoupon' ) ). "</h3>\n";
173
- //=============================
174
- // AND instead of OR the products
175
- WJECF_Admin_Html::render_select_with_default( array(
176
- 'id' => '_wjecf_products_and',
177
- 'label' => __( 'Products Operator', 'woocommerce-jos-autocoupon' ),
178
- 'options' => array( 'no' => __( 'OR', 'woocommerce-jos-autocoupon' ), 'yes' => __( 'AND', 'woocommerce-jos-autocoupon' ) ),
179
- 'default_value' => 'no',
180
- 'class' => 'wjecf-not-wide',
181
- /* translators: OLD TEXT: 'Check this box if ALL of the products (see tab \'usage restriction\') must be in the cart to use this coupon (instead of only one of the products).' */
182
- 'description' => __( 'Use AND if ALL of the products must be in the cart to use this coupon (instead of only one of the products).', 'woocommerce-jos-autocoupon' ),
183
- 'desc_tip' => true
184
- ) );
185
-
186
- //=============================
187
- // 2.2.3.1 AND instead of OR the categories
188
- WJECF_Admin_Html::render_select_with_default( array(
189
- 'id' => '_wjecf_categories_and',
190
- 'label' => __( 'Categories Operator', 'woocommerce-jos-autocoupon' ),
191
- 'options' => array( 'no' => __( 'OR', 'woocommerce-jos-autocoupon' ), 'yes' => __( 'AND', 'woocommerce-jos-autocoupon' ) ),
192
- 'default_value' => 'no',
193
- 'class' => 'wjecf-not-wide',
194
- /* translators: OLD TEXT: 'Check this box if products from ALL of the categories (see tab \'usage restriction\') must be in the cart to use this coupon (instead of only one from one of the categories).' */
195
- 'description' => __( 'Use AND if products from ALL of the categories must be in the cart to use this coupon (instead of only one from one of the categories).', 'woocommerce-jos-autocoupon' ),
196
- 'desc_tip' => true
197
- ) );
198
-
199
-
200
-
201
- // Minimum quantity of matching products (product/category)
202
- woocommerce_wp_text_input( array(
203
- 'id' => '_wjecf_min_matching_product_qty',
204
- 'label' => __( 'Minimum quantity of matching products', 'woocommerce-jos-autocoupon' ),
205
- 'placeholder' => __( 'No minimum', 'woocommerce' ),
206
- 'description' => __( 'Minimum quantity of the products that match the given product or category restrictions (see tab \'usage restriction\'). If no product or category restrictions are specified, the total number of products is used.', 'woocommerce-jos-autocoupon' ),
207
- 'data_type' => 'decimal',
208
- 'desc_tip' => true
209
- ) );
210
-
211
- // Maximum quantity of matching products (product/category)
212
- woocommerce_wp_text_input( array(
213
- 'id' => '_wjecf_max_matching_product_qty',
214
- 'label' => __( 'Maximum quantity of matching products', 'woocommerce-jos-autocoupon' ),
215
- 'placeholder' => __( 'No maximum', 'woocommerce' ),
216
- 'description' => __( 'Maximum quantity of the products that match the given product or category restrictions (see tab \'usage restriction\'). If no product or category restrictions are specified, the total number of products is used.', 'woocommerce-jos-autocoupon' ),
217
- 'data_type' => 'decimal',
218
- 'desc_tip' => true
219
- ) );
220
-
221
- // Minimum subtotal of matching products (product/category)
222
- woocommerce_wp_text_input( array(
223
- 'id' => '_wjecf_min_matching_product_subtotal',
224
- 'label' => __( 'Minimum subtotal of matching products', 'woocommerce-jos-autocoupon' ),
225
- 'placeholder' => __( 'No minimum', 'woocommerce' ),
226
- 'description' => __( 'Minimum price subtotal of the products that match the given product or category restrictions (see tab \'usage restriction\').', 'woocommerce-jos-autocoupon' ),
227
- 'data_type' => 'price',
228
- 'desc_tip' => true
229
- ) );
230
-
231
- // Maximum subtotal of matching products (product/category)
232
- woocommerce_wp_text_input( array(
233
- 'id' => '_wjecf_max_matching_product_subtotal',
234
- 'label' => __( 'Maximum subtotal of matching products', 'woocommerce-jos-autocoupon' ),
235
- 'placeholder' => __( 'No maximum', 'woocommerce' ),
236
- 'description' => __( 'Maximum price subtotal of the products that match the given product or category restrictions (see tab \'usage restriction\').', 'woocommerce-jos-autocoupon' ),
237
- 'data_type' => 'price',
238
- 'desc_tip' => true
239
- ) );
240
- }
241
-
242
- public function admin_coupon_metabox_checkout( $thepostid, $post ) {
243
-
244
- echo "<h3>" . esc_html( __( 'Checkout', 'woocommerce-jos-autocoupon' ) ). "</h3>\n";
245
-
246
- //=============================
247
- // Shipping methods
248
- ?>
249
- <p class="form-field"><label for="wjecf_shipping_methods"><?php _e( 'Shipping methods', 'woocommerce-jos-autocoupon' ); ?></label>
250
- <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' ); ?>">
251
- <?php
252
- $coupon_shipping_method_ids = WJECF()->get_coupon_shipping_method_ids( $thepostid );
253
- $shipping_methods = WC()->shipping->load_shipping_methods();
254
-
255
- if ( $shipping_methods ) foreach ( $shipping_methods as $shipping_method ) {
256
- echo '<option value="' . esc_attr( $shipping_method->id ) . '"' . selected( in_array( $shipping_method->id, $coupon_shipping_method_ids ), true, false ) . '>' . esc_html( $shipping_method->method_title ) . '</option>';
257
- }
258
- ?>
259
- </select><?php echo WJECF_Admin_Html::wc_help_tip( __( 'One of these shipping methods must be selected in order for this coupon to be valid.', 'woocommerce-jos-autocoupon' ) ); ?>
260
- </p>
261
- <?php
262
-
263
- //=============================
264
- // Payment methods
265
- ?>
266
- <p class="form-field"><label for="wjecf_payment_methods"><?php _e( 'Payment methods', 'woocommerce-jos-autocoupon' ); ?></label>
267
- <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' ); ?>">
268
- <?php
269
- $coupon_payment_method_ids = WJECF()->get_coupon_payment_method_ids( $thepostid );
270
- //DONT USE WC()->payment_gateways->available_payment_gateways() AS IT CAN CRASH IN UNKNOWN OCCASIONS
271
- $payment_methods = WC()->payment_gateways->payment_gateways();
272
- if ( $payment_methods ) foreach ( $payment_methods as $payment_method ) {
273
- if ('yes' === $payment_method->enabled) {
274
- echo '<option value="' . esc_attr( $payment_method->id ) . '"' . selected( in_array( $payment_method->id, $coupon_payment_method_ids ), true, false ) . '>' . esc_html( $payment_method->title ) . '</option>';
275
- }
276
- }
277
- ?>
278
- </select><?php echo WJECF_Admin_Html::wc_help_tip( __( 'One of these payment methods must be selected in order for this coupon to be valid.', 'woocommerce-jos-autocoupon' ) ); ?>
279
- </p>
280
- <?php
281
- }
282
-
283
- public function admin_coupon_metabox_customer( $thepostid, $post ) {
284
-
285
- //=============================
286
- //Title: "CUSTOMER RESTRICTIONS"
287
- echo "<h3>" . esc_html( __( 'Customer restrictions', 'woocommerce-jos-autocoupon' ) ). "</h3>\n";
288
- echo "<p><span class='description'>" . __( 'If both a customer and a role restriction are supplied, matching either one of them will suffice.' , 'woocommerce-jos-autocoupon' ) . "</span></p>\n";
289
-
290
- //=============================
291
- // User ids
292
- ?>
293
- <p class="form-field"><label><?php _e( 'Allowed Customers', 'woocommerce-jos-autocoupon' ); ?></label>
294
- <?php
295
- $coupon_customer_ids = WJECF()->get_coupon_customer_ids( $thepostid );
296
- WJECF_Admin_Html::render_admin_customer_selector( 'wjecf_customer_ids', '_wjecf_customer_ids', $coupon_customer_ids );
297
- echo WJECF_Admin_Html::wc_help_tip( __( 'Only these customers may use this coupon.', 'woocommerce-jos-autocoupon' ) );
298
- ?>
299
- </p>
300
- <?php
301
-
302
- //=============================
303
- // User roles
304
- ?>
305
- <p class="form-field"><label for="wjecf_customer_roles"><?php _e( 'Allowed User Roles', 'woocommerce-jos-autocoupon' ); ?></label>
306
- <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' ); ?>">
307
- <?php
308
- $coupon_customer_roles = WJECF()->get_coupon_customer_roles( $thepostid );
309
-
310
- $available_customer_roles = array_reverse( get_editable_roles() );
311
- foreach ( $available_customer_roles as $role_id => $role ) {
312
- $role_name = translate_user_role($role['name'] );
313
-
314
- echo '<option value="' . esc_attr( $role_id ) . '"'
315
- . selected( in_array( $role_id, $coupon_customer_roles ), true, false ) . '>'
316
- . esc_html( $role_name ) . '</option>';
317
- }
318
- ?>
319
- </select>
320
- <?php echo WJECF_Admin_Html::wc_help_tip( __( 'Only these User Roles may use this coupon.', 'woocommerce-jos-autocoupon' ) ); ?>
321
- </p>
322
- <?php
323
-
324
- //=============================
325
- // Excluded user roles
326
- ?>
327
- <p class="form-field"><label for="wjecf_excluded_customer_roles"><?php _e( 'Disallowed User Roles', 'woocommerce-jos-autocoupon' ); ?></label>
328
- <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' ); ?>">
329
- <?php
330
- $coupon_excluded_customer_roles = WJECF()->get_coupon_excluded_customer_roles( $thepostid );
331
-
332
- foreach ( $available_customer_roles as $role_id => $role ) {
333
- $role_name = translate_user_role($role['name'] );
334
-
335
- echo '<option value="' . esc_attr( $role_id ) . '"'
336
- . selected( in_array( $role_id, $coupon_excluded_customer_roles ), true, false ) . '>'
337
- . esc_html( $role_name ) . '</option>';
338
- }
339
- ?>
340
- </select>
341
- <?php echo WJECF_Admin_Html::wc_help_tip( __( 'These User Roles will be specifically excluded from using this coupon.', 'woocommerce-jos-autocoupon' ) ); ?>
342
- </p>
343
- <?php
344
- }
345
-
346
- public function admin_coupon_meta_fields( $coupon ) {
347
- $fields = array(
348
- '_wjecf_min_matching_product_qty' => 'int',
349
- '_wjecf_max_matching_product_qty' => 'int',
350
- '_wjecf_min_matching_product_subtotal' => 'decimal',
351
- '_wjecf_max_matching_product_subtotal' => 'decimal',
352
- '_wjecf_products_and' => 'yesno',
353
- '_wjecf_categories_and' => 'yesno',
354
- '_wjecf_shipping_methods' => 'clean',
355
- '_wjecf_payment_methods' => 'clean',
356
- '_wjecf_customer_ids' => 'int,',
357
- '_wjecf_customer_roles' => 'clean',
358
- '_wjecf_excluded_customer_roles' => 'clean'
359
- );
360
-
361
- //Espagueti
362
- if ( WJECF()->is_pro() ) {
363
- $fields = array_merge( $fields, WJECF()->admin_coupon_meta_fields( $coupon ) );
364
- }
365
- return $fields;
366
- }
367
-
368
- /**
369
- * Get an array with all the metafields for all the WJECF plugins
370
- *
371
- * @see Abstract_WJECF_Plugin::admin_coupon_meta_fields()
372
- *
373
- * @param type $coupon
374
- * @return type
375
- */
376
- function get_all_coupon_meta_fields( $coupon ) {
377
- //Collect the meta_fields of all the WJECF plugins
378
- $fields = array();
379
- foreach ( WJECF()->get_plugins() as $name => $plugin ) {
380
- if ( $plugin->plugin_is_enabled() ) {
381
- $fields = array_merge( $fields, $plugin->admin_coupon_meta_fields( $coupon ) );
382
- }
383
- }
384
- return $fields;
385
- }
386
-
387
- function process_shop_coupon_meta( $post_id, $post ) {
388
- $coupon = WJECF_WC()->get_coupon( $post );
389
- $wrap_coupon = WJECF_Wrap( $coupon );
390
- $sanitizer = WJECF()->sanitizer();
391
-
392
- $fields = $this->get_all_coupon_meta_fields( $coupon );
393
- foreach( $fields as $key => $rule ) {
394
- //If array contains [ 'callback' => callback, 'args' => args[] ]
395
- //Then that callback will be called with the given args (optional)
396
-
397
- if ( is_array( $rule ) && isset( $rule['callback'] ) && is_callable( $rule['callback'] ) ) {
398
- $args = array( 'key' => $key );
399
- if ( isset( $rule['args'] ) ) $args = array_merge( $args, $rule['args'] );
400
-
401
- $value = call_user_func( $rule['callback'], $args );
402
- } else {
403
- $value = $sanitizer->sanitize( isset( $_POST[$key] ) ? $_POST[$key] : null, $rule );
404
- }
405
- if ( $value === '' ) $value = null; //Don't save empty entries
406
-
407
- $wrap_coupon->set_meta( $key, $value ); //Always single
408
-
409
- //error_log(sprintf("%s => %s", $key, is_array($value) ? 'array' : $value));
410
- }
411
-
412
- $wrap_coupon->save();
413
- }
414
-
415
-
416
-
417
- /**
418
- * 2.3.6
419
- * Add inline style (css) to the admin page. Must be called BEFORE admin_head !
420
- * @param string $css
421
- * @return void
422
- */
423
- public function add_inline_style( $css ) {
424
- $this->admin_css .= $css;
425
- }
426
-
427
-
428
- /**
429
- *
430
- * 2.3.4
431
- * Parse an array or comma separated string; make sure they are valid ints and return as comma separated string
432
- * @deprecated 2.5.1 Use WJECF()->sanitizer->sanitize( ..., 'int[]' ) instead
433
- * @param array|string $int_array
434
- * @return string comma separated int array
435
- */
436
- public function comma_separated_int_array( $int_array ) {
437
- _deprecated_function( 'comma_separated_int_array', '2.5.1', 'WJECF()->sanitizer->sanitize()' );
438
- return WJECF()->sanitizer->sanitize( $int_array, 'int[]' );
439
- }
440
-
441
- /**
442
- * Add donate-link to plugin page
443
- */
444
- function wjecf_plugin_meta( $links, $file ) {
445
- if ( strpos( $file, 'woocommerce-jos-autocoupon.php' ) !== false ) {
446
- $links = array_merge( $links, array( '<a href="' . WJECF_Admin::get_donate_url() . '" title="Support the development" target="_blank">Donate</a>' ) );
447
- }
448
- return $links;
449
- }
450
-
451
-
452
- public static function get_donate_url() {
453
- 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";
454
- }
455
- }
456
-
457
  }
1
+ <?php
2
+
3
+ defined('ABSPATH') or die();
4
+
5
+ if ( ! class_exists('WJECF_Admin') ) {
6
+
7
+ class WJECF_Admin extends Abstract_WJECF_Plugin {
8
+
9
+ public function __construct() {
10
+ $this->set_plugin_data( array(
11
+ 'description' => __( 'Admin interface of WooCommerce Extended Coupon Features.', 'woocommerce-jos-autocoupon' ),
12
+ 'dependencies' => array(),
13
+ 'can_be_disabled' => false
14
+ ) );
15
+ }
16
+
17
+ public function init_admin_hook() {
18
+ add_action( 'admin_notices', array( $this, 'admin_notices'));
19
+
20
+ if ( ! WJECF_WC()->check_woocommerce_version('2.3.0') ) {
21
+ $this->enqueue_notice( '<p>' .
22
+ __( '<strong>WooCommerce Extended Coupon Features:</strong> You are using an old version of WooCommerce. Updating of WooCommerce is recommended as using an outdated version might cause unexpected behaviour in combination with modern plugins.' )
23
+ . '</p>', 'notice-warning' );
24
+ }
25
+ //Admin hooks
26
+ add_filter( 'plugin_row_meta', array( $this, 'wjecf_plugin_meta' ), 10, 2 );
27
+ add_action( 'admin_head', array( $this, 'on_admin_head'));
28
+
29
+ add_filter( 'woocommerce_coupon_data_tabs', array( $this, 'admin_coupon_options_tabs' ), 20, 1);
30
+ add_action( 'woocommerce_coupon_data_panels', array( $this, 'admin_coupon_options_panels' ), 10, 0 );
31
+ add_action( 'woocommerce_process_shop_coupon_meta', array( $this, 'process_shop_coupon_meta' ), 10, 2 );
32
+
33
+ add_action('woocommerce_coupon_options_usage_restriction', array( $this, 'on_woocommerce_coupon_options_usage_restriction' ), 20, 1);
34
+
35
+ add_action( 'wjecf_coupon_metabox_checkout', array( $this, 'admin_coupon_metabox_checkout' ), 10, 2 );
36
+ add_action( 'wjecf_coupon_metabox_customer', array( $this, 'admin_coupon_metabox_customer' ), 10, 2 );
37
+ //add_action( 'wjecf_coupon_metabox_misc', array( $this, 'admin_coupon_metabox_misc' ), 10, 2 );
38
+
39
+ $this->add_inline_style( '
40
+ #woocommerce-coupon-data .wjecf-not-wide { width:50% }
41
+ ');
42
+ }
43
+
44
+ // ===========================================================================
45
+ // START - ADMIN NOTICES
46
+ // Allows notices to be displayed on the admin pages
47
+ // ===========================================================================
48
+
49
+ private $notices = array();
50
+
51
+ /**
52
+ * Enqueue a notice to display on the admin page
53
+ * @param stirng $html Please embed in <p> tags
54
+ * @param string $class
55
+ */
56
+ public function enqueue_notice( $html, $class = 'notice-info' ) {
57
+ $this->notices[] = array( 'class' => $class, 'html' => $html );
58
+ }
59
+
60
+ public function admin_notices() {
61
+ foreach( $this->notices as $notice ) {
62
+ echo '<div class="notice ' . $notice['class'] . '">';
63
+ echo '<p><strong>WooCommerce Extended Coupon Features</strong> &#8211; ';
64
+ echo $notice['html'];
65
+ echo '</div>';
66
+ }
67
+ $this->notices = array();
68
+ }
69
+
70
+ // ===========================================================================
71
+ // END - ADMIN NOTICES
72
+ // ===========================================================================
73
+
74
+ //2.3.6 Inline css
75
+ private $admin_css = '';
76
+
77
+ /**
78
+ * 2.3.6
79
+ * @return void
80
+ */
81
+ function on_admin_head() {
82
+ //Output inline style for the admin pages
83
+ if ( ! empty( $this->admin_css ) ) {
84
+ echo '<style type="text/css">' . $this->admin_css . '</style>';
85
+ $this->admin_css = '';
86
+ }
87
+
88
+ //Enqueue scripts
89
+ wp_enqueue_script( "wjecf-admin", WJECF()->plugin_url() . "assets/js/wjecf-admin.js", array( 'jquery' ), WJECF()->plugin_version() );
90
+ wp_localize_script( 'wjecf-admin', 'wjecf_admin_i18n', array(
91
+ 'label_and' => __( '(AND)', 'woocommerce-jos-autocoupon' ),
92
+ 'label_or' => __( '(OR)', 'woocommerce-jos-autocoupon' )
93
+ ) );
94
+
95
+ }
96
+
97
+ //Add tabs to the coupon option page
98
+ public function admin_coupon_options_tabs( $tabs ) {
99
+
100
+ $tabs['extended_features_checkout'] = array(
101
+ 'label' => __( 'Checkout', 'woocommerce-jos-autocoupon' ),
102
+ 'target' => 'wjecf_coupondata_checkout',
103
+ 'class' => 'wjecf_coupondata_checkout',
104
+ );
105
+
106
+ $tabs['extended_features_misc'] = array(
107
+ 'label' => __( 'Miscellaneous', 'woocommerce-jos-autocoupon' ),
108
+ 'target' => 'wjecf_coupondata_misc',
109
+ 'class' => 'wjecf_coupondata_misc',
110
+ );
111
+
112
+ return $tabs;
113
+ }
114
+
115
+ //Add panels to the coupon option page
116
+ public function admin_coupon_options_panels() {
117
+ global $thepostid, $post;
118
+ $thepostid = empty( $thepostid ) ? $post->ID : $thepostid;
119
+ ?>
120
+ <div id="wjecf_coupondata_checkout" class="panel woocommerce_options_panel">
121
+ <?php
122
+ do_action( 'wjecf_coupon_metabox_checkout', $thepostid, $post );
123
+ do_action( 'wjecf_coupon_metabox_customer', $thepostid, $post );
124
+ $this->admin_coupon_data_footer();
125
+ ?>
126
+ </div>
127
+ <div id="wjecf_coupondata_misc" class="panel woocommerce_options_panel">
128
+ <?php
129
+ //Allow other classes to inject options
130
+ do_action( 'wjecf_woocommerce_coupon_options_extended_features', $thepostid, $post );
131
+ do_action( 'wjecf_coupon_metabox_misc', $thepostid, $post );
132
+ $this->admin_coupon_data_footer();
133
+ ?>
134
+ </div>
135
+ <?php
136
+ }
137
+
138
+ public function admin_coupon_data_footer() {
139
+ $documentation_url = WJECF()->plugin_url( 'docs/index.html' );
140
+ if ( ! WJECF()->is_pro() ) {
141
+ $documentation_url = 'http://www.soft79.nl/documentation/wjecf';
142
+ ?>
143
+ <h3><?php _e( 'Do you find WooCommerce Extended Coupon Features useful?', 'woocommerce-jos-autocoupon'); ?></h3>
144
+ <p class="form-field"><label for="wjecf_donate_button"><?php
145
+ echo esc_html( __('Express your gratitude', 'woocommerce-jos-autocoupon' ) );
146
+ ?></label>
147
+ <a id="wjecf_donate_button" href="<?php echo $this->get_donate_url(); ?>" target="_blank" class="button button-primary">
148
+ <?php
149
+ echo esc_html( __('Donate to the developer', 'woocommerce-jos-autocoupon' ) );
150
+ ?></a><br>
151
+ Or get the PRO version at <a href="http://www.soft79.nl" target="_blank">www.soft79.nl</a>.
152
+ </p>
153
+ <?php
154
+ }
155
+ //Documentation link
156
+ echo '<h3>' . __( 'Documentation', 'woocommerce-jos-autocoupon' ) . '</h3>';
157
+ echo '<p><a href="' . $documentation_url . '" target="_blank">' .
158
+ __( 'WooCommerce Extended Coupon Features Documentation', 'woocommerce-jos-autocoupon' ) . '</a></p>';
159
+
160
+ }
161
+
162
+ // //Tab 'extended features'
163
+ //public function admin_coupon_metabox_products( $thepostid, $post ) {
164
+
165
+ //since 2.5.0 moved to the 'Usage restriction' tab
166
+ public function on_woocommerce_coupon_options_usage_restriction() {
167
+ global $thepostid, $post;
168
+ $thepostid = empty( $thepostid ) ? $post->ID : $thepostid;
169
+
170
+ //See WooCommerce class-wc-meta-box-coupon-data.php function ouput
171
+
172
+ echo "<h3>" . esc_html( __( 'Matching products', 'woocommerce-jos-autocoupon' ) ). "</h3>\n";
173
+ //=============================
174
+ // AND instead of OR the products
175
+ WJECF_Admin_Html::render_select_with_default( array(
176
+ 'id' => '_wjecf_products_and',
177
+ 'label' => __( 'Products Operator', 'woocommerce-jos-autocoupon' ),
178
+ 'options' => array( 'no' => __( 'OR', 'woocommerce-jos-autocoupon' ), 'yes' => __( 'AND', 'woocommerce-jos-autocoupon' ) ),
179
+ 'default_value' => 'no',
180
+ 'class' => 'wjecf-not-wide',
181
+ /* translators: OLD TEXT: 'Check this box if ALL of the products (see tab \'usage restriction\') must be in the cart to use this coupon (instead of only one of the products).' */
182
+ 'description' => __( 'Use AND if ALL of the products must be in the cart to use this coupon (instead of only one of the products).', 'woocommerce-jos-autocoupon' ),
183
+ 'desc_tip' => true
184
+ ) );
185
+
186
+ //=============================
187
+ // 2.2.3.1 AND instead of OR the categories
188
+ WJECF_Admin_Html::render_select_with_default( array(
189
+ 'id' => '_wjecf_categories_and',
190
+ 'label' => __( 'Categories Operator', 'woocommerce-jos-autocoupon' ),
191
+ 'options' => array( 'no' => __( 'OR', 'woocommerce-jos-autocoupon' ), 'yes' => __( 'AND', 'woocommerce-jos-autocoupon' ) ),
192
+ 'default_value' => 'no',
193
+ 'class' => 'wjecf-not-wide',
194
+ /* translators: OLD TEXT: 'Check this box if products from ALL of the categories (see tab \'usage restriction\') must be in the cart to use this coupon (instead of only one from one of the categories).' */
195
+ 'description' => __( 'Use AND if products from ALL of the categories must be in the cart to use this coupon (instead of only one from one of the categories).', 'woocommerce-jos-autocoupon' ),
196
+ 'desc_tip' => true
197
+ ) );
198
+
199
+
200
+
201
+ // Minimum quantity of matching products (product/category)
202
+ woocommerce_wp_text_input( array(
203
+ 'id' => '_wjecf_min_matching_product_qty',
204
+ 'label' => __( 'Minimum quantity of matching products', 'woocommerce-jos-autocoupon' ),
205
+ 'placeholder' => __( 'No minimum', 'woocommerce' ),
206
+ 'description' => __( 'Minimum quantity of the products that match the given product or category restrictions (see tab \'usage restriction\'). If no product or category restrictions are specified, the total number of products is used.', 'woocommerce-jos-autocoupon' ),
207
+ 'data_type' => 'decimal',
208
+ 'desc_tip' => true
209
+ ) );
210
+
211
+ // Maximum quantity of matching products (product/category)
212
+ woocommerce_wp_text_input( array(
213
+ 'id' => '_wjecf_max_matching_product_qty',
214
+ 'label' => __( 'Maximum quantity of matching products', 'woocommerce-jos-autocoupon' ),
215
+ 'placeholder' => __( 'No maximum', 'woocommerce' ),
216
+ 'description' => __( 'Maximum quantity of the products that match the given product or category restrictions (see tab \'usage restriction\'). If no product or category restrictions are specified, the total number of products is used.', 'woocommerce-jos-autocoupon' ),
217
+ 'data_type' => 'decimal',
218
+ 'desc_tip' => true
219
+ ) );
220
+
221
+ // Minimum subtotal of matching products (product/category)
222
+ woocommerce_wp_text_input( array(
223
+ 'id' => '_wjecf_min_matching_product_subtotal',
224
+ 'label' => __( 'Minimum subtotal of matching products', 'woocommerce-jos-autocoupon' ),
225
+ 'placeholder' => __( 'No minimum', 'woocommerce' ),
226
+ 'description' => __( 'Minimum price subtotal of the products that match the given product or category restrictions (see tab \'usage restriction\').', 'woocommerce-jos-autocoupon' ),
227
+ 'data_type' => 'price',
228
+ 'desc_tip' => true
229
+ ) );
230
+
231
+ // Maximum subtotal of matching products (product/category)
232
+ woocommerce_wp_text_input( array(
233
+ 'id' => '_wjecf_max_matching_product_subtotal',
234
+ 'label' => __( 'Maximum subtotal of matching products', 'woocommerce-jos-autocoupon' ),
235
+ 'placeholder' => __( 'No maximum', 'woocommerce' ),
236
+ 'description' => __( 'Maximum price subtotal of the products that match the given product or category restrictions (see tab \'usage restriction\').', 'woocommerce-jos-autocoupon' ),
237
+ 'data_type' => 'price',
238
+ 'desc_tip' => true
239
+ ) );
240
+ }
241
+
242
+ public function admin_coupon_metabox_checkout( $thepostid, $post ) {
243
+
244
+ echo "<h3>" . esc_html( __( 'Checkout', 'woocommerce-jos-autocoupon' ) ). "</h3>\n";
245
+
246
+ //=============================
247
+ // Shipping methods
248
+ ?>
249
+ <p class="form-field"><label for="wjecf_shipping_methods"><?php _e( 'Shipping methods', 'woocommerce-jos-autocoupon' ); ?></label>
250
+ <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' ); ?>">
251
+ <?php
252
+ $coupon_shipping_method_ids = WJECF()->get_coupon_shipping_method_ids( $thepostid );
253
+ $shipping_methods = WC()->shipping->load_shipping_methods();
254
+
255
+ if ( $shipping_methods ) foreach ( $shipping_methods as $shipping_method ) {
256
+ echo '<option value="' . esc_attr( $shipping_method->id ) . '"' . selected( in_array( $shipping_method->id, $coupon_shipping_method_ids ), true, false ) . '>' . esc_html( $shipping_method->method_title ) . '</option>';
257
+ }
258
+ ?>
259
+ </select><?php echo WJECF_Admin_Html::wc_help_tip( __( 'One of these shipping methods must be selected in order for this coupon to be valid.', 'woocommerce-jos-autocoupon' ) ); ?>
260
+ </p>
261
+ <?php
262
+
263
+ //=============================
264
+ // Payment methods
265
+ ?>
266
+ <p class="form-field"><label for="wjecf_payment_methods"><?php _e( 'Payment methods', 'woocommerce-jos-autocoupon' ); ?></label>
267
+ <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' ); ?>">
268
+ <?php
269
+ $coupon_payment_method_ids = WJECF()->get_coupon_payment_method_ids( $thepostid );
270
+ //DONT USE WC()->payment_gateways->available_payment_gateways() AS IT CAN CRASH IN UNKNOWN OCCASIONS
271
+ $payment_methods = WC()->payment_gateways->payment_gateways();
272
+ if ( $payment_methods ) foreach ( $payment_methods as $payment_method ) {
273
+ if ('yes' === $payment_method->enabled) {
274
+ echo '<option value="' . esc_attr( $payment_method->id ) . '"' . selected( in_array( $payment_method->id, $coupon_payment_method_ids ), true, false ) . '>' . esc_html( $payment_method->title ) . '</option>';
275
+ }
276
+ }
277
+ ?>
278
+ </select><?php echo WJECF_Admin_Html::wc_help_tip( __( 'One of these payment methods must be selected in order for this coupon to be valid.', 'woocommerce-jos-autocoupon' ) ); ?>
279
+ </p>
280
+ <?php
281
+ }
282
+
283
+ public function admin_coupon_metabox_customer( $thepostid, $post ) {
284
+
285
+ //=============================
286
+ //Title: "CUSTOMER RESTRICTIONS"
287
+ echo "<h3>" . esc_html( __( 'Customer restrictions', 'woocommerce-jos-autocoupon' ) ). "</h3>\n";
288
+ echo "<p><span class='description'>" . __( 'If both a customer and a role restriction are supplied, matching either one of them will suffice.' , 'woocommerce-jos-autocoupon' ) . "</span></p>\n";
289
+
290
+ //=============================
291
+ // User ids
292
+ ?>
293
+ <p class="form-field"><label><?php _e( 'Allowed Customers', 'woocommerce-jos-autocoupon' ); ?></label>
294
+ <?php
295
+ $coupon_customer_ids = WJECF()->get_coupon_customer_ids( $thepostid );
296
+ WJECF_Admin_Html::render_admin_customer_selector( 'wjecf_customer_ids', '_wjecf_customer_ids', $coupon_customer_ids );
297
+ echo WJECF_Admin_Html::wc_help_tip( __( 'Only these customers may use this coupon.', 'woocommerce-jos-autocoupon' ) );
298
+ ?>
299
+ </p>
300
+ <?php
301
+
302
+ //=============================
303
+ // User roles
304
+ ?>
305
+ <p class="form-field"><label for="wjecf_customer_roles"><?php _e( 'Allowed User Roles', 'woocommerce-jos-autocoupon' ); ?></label>
306
+ <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' ); ?>">
307
+ <?php
308
+ $coupon_customer_roles = WJECF()->get_coupon_customer_roles( $thepostid );
309
+
310
+ $available_customer_roles = array_reverse( get_editable_roles() );
311
+ foreach ( $available_customer_roles as $role_id => $role ) {
312
+ $role_name = translate_user_role($role['name'] );
313
+
314
+ echo '<option value="' . esc_attr( $role_id ) . '"'
315
+ . selected( in_array( $role_id, $coupon_customer_roles ), true, false ) . '>'
316
+ . esc_html( $role_name ) . '</option>';
317
+ }
318
+ ?>
319
+ </select>
320
+ <?php echo WJECF_Admin_Html::wc_help_tip( __( 'Only these User Roles may use this coupon.', 'woocommerce-jos-autocoupon' ) ); ?>
321
+ </p>
322
+ <?php
323
+
324
+ //=============================
325
+ // Excluded user roles
326
+ ?>
327
+ <p class="form-field"><label for="wjecf_excluded_customer_roles"><?php _e( 'Disallowed User Roles', 'woocommerce-jos-autocoupon' ); ?></label>
328
+ <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' ); ?>">
329
+ <?php
330
+ $coupon_excluded_customer_roles = WJECF()->get_coupon_excluded_customer_roles( $thepostid );
331
+
332
+ foreach ( $available_customer_roles as $role_id => $role ) {
333
+ $role_name = translate_user_role($role['name'] );
334
+
335
+ echo '<option value="' . esc_attr( $role_id ) . '"'
336
+ . selected( in_array( $role_id, $coupon_excluded_customer_roles ), true, false ) . '>'
337
+ . esc_html( $role_name ) . '</option>';
338
+ }
339
+ ?>
340
+ </select>
341
+ <?php echo WJECF_Admin_Html::wc_help_tip( __( 'These User Roles will be specifically excluded from using this coupon.', 'woocommerce-jos-autocoupon' ) ); ?>
342
+ </p>
343
+ <?php
344
+ }
345
+
346
+ public function admin_coupon_meta_fields( $coupon ) {
347
+ $fields = array(
348
+ '_wjecf_min_matching_product_qty' => 'int',
349
+ '_wjecf_max_matching_product_qty' => 'int',
350
+ '_wjecf_min_matching_product_subtotal' => 'decimal',
351
+ '_wjecf_max_matching_product_subtotal' => 'decimal',
352
+ '_wjecf_products_and' => 'yesno',
353
+ '_wjecf_categories_and' => 'yesno',
354
+ '_wjecf_shipping_methods' => 'clean',
355
+ '_wjecf_payment_methods' => 'clean',
356
+ '_wjecf_customer_ids' => 'int,',
357
+ '_wjecf_customer_roles' => 'clean',
358
+ '_wjecf_excluded_customer_roles' => 'clean'
359
+ );
360
+
361
+ //Espagueti
362
+ if ( WJECF()->is_pro() ) {
363
+ $fields = array_merge( $fields, WJECF()->admin_coupon_meta_fields( $coupon ) );
364
+ }
365
+ return $fields;
366
+ }
367
+
368
+ /**
369
+ * Get an array with all the metafields for all the WJECF plugins
370
+ *
371
+ * @see Abstract_WJECF_Plugin::admin_coupon_meta_fields()
372
+ *
373
+ * @param type $coupon
374
+ * @return type
375
+ */
376
+ function get_all_coupon_meta_fields( $coupon ) {
377
+ //Collect the meta_fields of all the WJECF plugins
378
+ $fields = array();
379
+ foreach ( WJECF()->get_plugins() as $name => $plugin ) {
380
+ if ( $plugin->plugin_is_enabled() ) {
381
+ $fields = array_merge( $fields, $plugin->admin_coupon_meta_fields( $coupon ) );
382
+ }
383
+ }
384
+ return $fields;
385
+ }
386
+
387
+ function process_shop_coupon_meta( $post_id, $post ) {
388
+ $coupon = WJECF_WC()->get_coupon( $post );
389
+ $wrap_coupon = WJECF_Wrap( $coupon );
390
+ $sanitizer = WJECF()->sanitizer();
391
+
392
+ $fields = $this->get_all_coupon_meta_fields( $coupon );
393
+ foreach( $fields as $key => $rule ) {
394
+ //If array contains [ 'callback' => callback, 'args' => args[] ]
395
+ //Then that callback will be called with the given args (optional)
396
+
397
+ if ( is_array( $rule ) && isset( $rule['callback'] ) && is_callable( $rule['callback'] ) ) {
398
+ $args = array( 'key' => $key );
399
+ if ( isset( $rule['args'] ) ) $args = array_merge( $args, $rule['args'] );
400
+
401
+ $value = call_user_func( $rule['callback'], $args );
402
+ } else {
403
+ $value = $sanitizer->sanitize( isset( $_POST[$key] ) ? $_POST[$key] : null, $rule );
404
+ }
405
+ if ( $value === '' ) $value = null; //Don't save empty entries
406
+
407
+ $wrap_coupon->set_meta( $key, $value ); //Always single
408
+
409
+ //error_log(sprintf("%s => %s", $key, is_array($value) ? 'array' : $value));
410
+ }
411
+
412
+ $wrap_coupon->save();
413
+ }
414
+
415
+
416
+
417
+ /**
418
+ * 2.3.6
419
+ * Add inline style (css) to the admin page. Must be called BEFORE admin_head !
420
+ * @param string $css
421
+ * @return void
422
+ */
423
+ public function add_inline_style( $css ) {
424
+ $this->admin_css .= $css;
425
+ }
426
+
427
+
428
+ /**
429
+ *
430
+ * 2.3.4
431
+ * Parse an array or comma separated string; make sure they are valid ints and return as comma separated string
432
+ * @deprecated 2.5.1 Use WJECF()->sanitizer->sanitize( ..., 'int[]' ) instead
433
+ * @param array|string $int_array
434
+ * @return string comma separated int array
435
+ */
436
+ public function comma_separated_int_array( $int_array ) {
437
+ _deprecated_function( 'comma_separated_int_array', '2.5.1', 'WJECF()->sanitizer->sanitize()' );
438
+ return WJECF()->sanitizer->sanitize( $int_array, 'int[]' );
439
+ }
440
+
441
+ /**
442
+ * Add donate-link to plugin page
443
+ */
444
+ function wjecf_plugin_meta( $links, $file ) {
445
+ if ( strpos( $file, 'woocommerce-jos-autocoupon.php' ) !== false ) {
446
+ $links = array_merge( $links, array( '<a href="' . WJECF_Admin::get_donate_url() . '" title="Support the development" target="_blank">Donate</a>' ) );
447
+ }
448
+ return $links;
449
+ }
450
+
451
+
452
+ public static function get_donate_url() {
453
+ 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";
454
+ }
455
+ }
456
+
457
  }
includes/admin/WJECF_Admin_Data_Update.php CHANGED
@@ -1,83 +1,83 @@
1
- <?php
2
-
3
- class WJECF_Admin_Data_Update extends Abstract_WJECF_Plugin {
4
-
5
- public function __construct() {
6
- $this->set_plugin_data( array(
7
- 'description' => __( 'Automatically update data when a new version of this plugin is installed.', 'woocommerce-jos-autocoupon' ),
8
- 'dependencies' => array(),
9
- 'can_be_disabled' => true
10
- ) );
11
- }
12
-
13
- public function init_admin_hook() {
14
- $this->auto_data_update();
15
- }
16
-
17
- //Upgrade database on version change
18
- public function auto_data_update() {
19
- if ( ! class_exists('WC_Coupon') ) {
20
- return;
21
- }
22
-
23
- //WJECF()->set_option('db_version', 0); // Will force all upgrades
24
- global $wpdb;
25
- $prev_version = WJECF()->get_option('db_version');
26
- $current_version = $prev_version;
27
-
28
- //DB_VERSION 1: Since 2.1.0-b5
29
- if ( $current_version < 1 ) {
30
- //RENAME meta_key _wjecf_matching_product_qty TO _wjecf_min_matching_product_qty
31
- $where = array("meta_key" => "_wjecf_matching_product_qty");
32
- $set = array('meta_key' => "_wjecf_min_matching_product_qty");
33
- $wpdb->update( _get_meta_table('post'), $set, $where );
34
-
35
- //RENAME meta_key woocommerce-jos-autocoupon TO _wjecf_is_auto_coupon
36
- $where = array("meta_key" => "woocommerce-jos-autocoupon");
37
- $set = array('meta_key' => "_wjecf_is_auto_coupon");
38
- $wpdb->update( _get_meta_table('post'), $set, $where );
39
- //Now we're version 1
40
- $current_version = 1;
41
- } //DB VERSION 1
42
-
43
- //DB_VERSION 2: Since 2.3.3-b3 No changes; but used to omit message if 2.3.3-b3 has been installed before
44
- if ( $current_version < 2 ) {
45
- $current_version = 2;
46
- }
47
-
48
- if ( $current_version > 2 ) {
49
- WJECF_ADMIN()->enqueue_notice( __( 'Please note, you\'re using an older version of this plugin, while the data was upgraded to a newer version.' , 'woocommerce-jos-autocoupon' ), 'notice-warning');
50
- }
51
-
52
- //An upgrade took place?
53
- if ( $current_version != $prev_version ) {
54
- // Set version and write options to database
55
- WJECF()->set_option( 'db_version', $current_version );
56
- WJECF()->save_options();
57
-
58
- WJECF_ADMIN()->enqueue_notice( __( 'Data succesfully upgraded to the newest version.', 'woocommerce-jos-autocoupon' ), 'notice-success');
59
- }
60
- }
61
-
62
- // function admin_notice_allow_cart_excluded() {
63
-
64
- // if( ! empty( $this->admin_notice_allow_cart_excluded_couponcodes ) )
65
- // {
66
- // $html = '<div class="notice notice-warning">';
67
- // $html .= '<p>';
68
- // $html .= __( '<strong>WooCommerce Extended Coupon Features:</strong> The following coupons use the deprecated option \'Allow discount on cart with excluded items\'. Please review and save them.', 'woocommerce-jos-autocoupon' );
69
- // $html .= '<ul>';
70
- // foreach( $this->admin_notice_allow_cart_excluded_couponcodes as $post_id => $coupon_code ) {
71
- // $html .= '<li><a class="post-edit-link" href="' . esc_url( get_edit_post_link( $post_id ) ) . '">' . $coupon_code . '</a></li>';
72
- // }
73
- // $html .= '</ul>';
74
- // $html .= '</p>';
75
- // $html .= '</div>';
76
- // echo $html;
77
- // }
78
- // }
79
-
80
- }
81
-
82
-
83
  ?>
1
+ <?php
2
+
3
+ class WJECF_Admin_Data_Update extends Abstract_WJECF_Plugin {
4
+
5
+ public function __construct() {
6
+ $this->set_plugin_data( array(
7
+ 'description' => __( 'Automatically update data when a new version of this plugin is installed.', 'woocommerce-jos-autocoupon' ),
8
+ 'dependencies' => array(),
9
+ 'can_be_disabled' => true
10
+ ) );
11
+ }
12
+
13
+ public function init_admin_hook() {
14
+ $this->auto_data_update();
15
+ }
16
+
17
+ //Upgrade database on version change
18
+ public function auto_data_update() {
19
+ if ( ! class_exists('WC_Coupon') ) {
20
+ return;
21
+ }
22
+
23
+ //WJECF()->set_option('db_version', 0); // Will force all upgrades
24
+ global $wpdb;
25
+ $prev_version = WJECF()->get_option('db_version');
26
+ $current_version = $prev_version;
27
+
28
+ //DB_VERSION 1: Since 2.1.0-b5
29
+ if ( $current_version < 1 ) {
30
+ //RENAME meta_key _wjecf_matching_product_qty TO _wjecf_min_matching_product_qty
31
+ $where = array("meta_key" => "_wjecf_matching_product_qty");
32
+ $set = array('meta_key' => "_wjecf_min_matching_product_qty");
33
+ $wpdb->update( _get_meta_table('post'), $set, $where );
34
+
35
+ //RENAME meta_key woocommerce-jos-autocoupon TO _wjecf_is_auto_coupon
36
+ $where = array("meta_key" => "woocommerce-jos-autocoupon");
37
+ $set = array('meta_key' => "_wjecf_is_auto_coupon");
38
+ $wpdb->update( _get_meta_table('post'), $set, $where );
39
+ //Now we're version 1
40
+ $current_version = 1;
41
+ } //DB VERSION 1
42
+
43
+ //DB_VERSION 2: Since 2.3.3-b3 No changes; but used to omit message if 2.3.3-b3 has been installed before
44
+ if ( $current_version < 2 ) {
45
+ $current_version = 2;
46
+ }
47
+
48
+ if ( $current_version > 2 ) {
49
+ WJECF_ADMIN()->enqueue_notice( __( 'Please note, you\'re using an older version of this plugin, while the data was upgraded to a newer version.' , 'woocommerce-jos-autocoupon' ), 'notice-warning');
50
+ }
51
+
52
+ //An upgrade took place?
53
+ if ( $current_version != $prev_version ) {
54
+ // Set version and write options to database
55
+ WJECF()->set_option( 'db_version', $current_version );
56
+ WJECF()->save_options();
57
+
58
+ WJECF_ADMIN()->enqueue_notice( __( 'Data succesfully upgraded to the newest version.', 'woocommerce-jos-autocoupon' ), 'notice-success');
59
+ }
60
+ }
61
+
62
+ // function admin_notice_allow_cart_excluded() {
63
+
64
+ // if( ! empty( $this->admin_notice_allow_cart_excluded_couponcodes ) )
65
+ // {
66
+ // $html = '<div class="notice notice-warning">';
67
+ // $html .= '<p>';
68
+ // $html .= __( '<strong>WooCommerce Extended Coupon Features:</strong> The following coupons use the deprecated option \'Allow discount on cart with excluded items\'. Please review and save them.', 'woocommerce-jos-autocoupon' );
69
+ // $html .= '<ul>';
70
+ // foreach( $this->admin_notice_allow_cart_excluded_couponcodes as $post_id => $coupon_code ) {
71
+ // $html .= '<li><a class="post-edit-link" href="' . esc_url( get_edit_post_link( $post_id ) ) . '">' . $coupon_code . '</a></li>';
72
+ // }
73
+ // $html .= '</ul>';
74
+ // $html .= '</p>';
75
+ // $html .= '</div>';
76
+ // echo $html;
77
+ // }
78
+ // }
79
+
80
+ }
81
+
82
+
83
  ?>
includes/admin/WJECF_Admin_Html.php CHANGED
@@ -1,333 +1,333 @@
1
- <?php
2
-
3
- /**
4
- * HTML Output functions for admin
5
- */
6
- class WJECF_Admin_Html {
7
-
8
-
9
- /**
10
- * 2.3.6
11
- * Renders a <SELECT> that has a default value. Relies on woocommerce_wp_select
12
- *
13
- * field['default_value']: The default value (if omitted the first option will be default)
14
- * field['append_default_label']: If true or omitted the text '(DEFAULT)' will be appended to the default option caption
15
- *
16
- * @param array $field see wc-meta-box-functions.php:woocommerce_wp_select
17
- * @return void
18
- */
19
- public static function render_select_with_default( $field ) {
20
- global $thepostid, $post;
21
- $thepostid = empty( $thepostid ) ? $post->ID : $thepostid;
22
-
23
- reset( $field['options'] ); //move to first for key()
24
- $default_value = isset( $field['default_value'] ) ? $field['default_value'] : key( $field['options'] );
25
- $append_default_label = isset( $field['append_default_label'] ) ? $field['append_default_label'] : true;
26
-
27
- if ( $append_default_label ) {
28
- $field['options'][$default_value] = sprintf( __( '%s (Default)', 'woocommerce-jos-autocoupon' ), $field['options'][$default_value] );
29
- }
30
-
31
- if ( ! isset( $field['value'] ) ) {
32
- $field['value'] = get_post_meta( $thepostid, $field['id'], true );
33
- }
34
- if ( empty( $field['value'] ) ) {
35
- $field['value'] = $default_value;
36
- }
37
-
38
- woocommerce_wp_select( $field );
39
- }
40
-
41
- public static function render_admin_cat_selector( $dom_id, $field_name, $selected_ids, $placeholder = null ) {
42
- if ( $placeholder === null ) $placeholder = __( 'Search for a product…', 'woocommerce' );
43
-
44
- // Categories
45
- ?>
46
- <select id="<?php esc_attr_e( $dom_id ) ?>" name="<?php esc_attr_e( $field_name ) ?>[]" style="width: 50%;" class="wc-enhanced-select" multiple="multiple" data-placeholder="<?php esc_attr_e( $placeholder ); ?>">
47
- <?php
48
- $categories = get_terms( 'product_cat', 'orderby=name&hide_empty=0' );
49
-
50
- if ( $categories ) foreach ( $categories as $cat ) {
51
- echo '<option value="' . esc_attr( $cat->term_id ) . '"' . selected( in_array( $cat->term_id, $selected_ids ), true, false ) . '>' . esc_html( $cat->name ) . '</option>';
52
- }
53
- ?>
54
- </select>
55
- <?php
56
- }
57
-
58
-
59
- /**
60
- * Display a WooCommerce help tip
61
- * @param string $tip The tip to display
62
- * @return string
63
- */
64
- public static function wc_help_tip( $tip ) {
65
- //Since WC 2.5.0
66
- if ( function_exists( 'wc_help_tip' ) ) {
67
- return wc_help_tip( $tip );
68
- }
69
-
70
- return '<img class="help_tip" style="margin-top: 21px;" data-tip="' . esc_attr( $tip ) . '" src="' . esc_url( WC()->plugin_url() ) . '/assets/images/help.png" height="16" width="16" />';
71
- }
72
-
73
- /**
74
- * Renders a product selection <input>. Will use either select2 v4 (WC3.0+) select2 v3 (WC2.3+) or chosen (< WC2.3)
75
- * @param string $dom_id
76
- * @param string $field_name
77
- * @param array $selected_ids Array of integers
78
- * @param string|null $placeholder
79
- * @return void
80
- */
81
- public static function render_admin_product_selector( $dom_id, $field_name, $selected_ids, $placeholder = null ) {
82
- $product_key_values = array();
83
- foreach ( $selected_ids as $product_id ) {
84
- $product = wc_get_product( $product_id );
85
- if ( is_object( $product ) ) {
86
- $product_key_values[ esc_attr( $product_id ) ] = wp_kses_post( $product->get_formatted_name() );
87
- }
88
- }
89
-
90
- if ( $placeholder === null ) $placeholder = __( 'Search for a product…', 'woocommerce' );
91
-
92
- //In WooCommerce version 2.3.0 chosen was replaced by select2
93
- //In WooCommerce version 3.0 select2 v3 was replaced by select2 v4
94
- if ( WJECF_WC()->check_woocommerce_version('3.0') ) {
95
- self::render_admin_select2_v4_product_selector( $dom_id, $field_name, $product_key_values, $placeholder );
96
- } elseif ( WJECF_WC()->check_woocommerce_version('2.3.0') ) {
97
- self::render_admin_select2_product_selector( $dom_id, $field_name, $product_key_values, $placeholder );
98
- } else {
99
- self::render_admin_chosen_product_selector( $dom_id, $field_name, $product_key_values, $placeholder );
100
- }
101
- }
102
-
103
-
104
- /**
105
- * Renders a product selection <input>.
106
- * Chosen (Legacy)
107
- * @param string $dom_id
108
- * @param string $field_name
109
- * @param array $selected_ids Array of integers
110
- * @param string|null $placeholder
111
- */
112
- private static function render_admin_chosen_product_selector( $dom_id, $field_name, $selected_keys_and_values, $placeholder ) {
113
- // $selected_keys_and_values must be an array of [ id => name ]
114
-
115
- echo '<select id="' . esc_attr( $dom_id ) . '" name="' . esc_attr( $field_name ) . '[]" class="ajax_chosen_select_products_and_variations" multiple="multiple" data-placeholder="' . esc_attr( $placeholder ) . '">';
116
- foreach ( $selected_keys_and_values as $product_id => $product_name ) {
117
- echo '<option value="' . $product_id . '" selected="selected">' . $product_name . '</option>';
118
- }
119
- echo '</select>';
120
- }
121
-
122
- /**
123
- * @since 2.4.1 for WC 3.0 compatibility
124
- *
125
- * Renders a product selection <input>.
126
- * Select2 version 3 (Since WC 2.3.0)
127
- * @param string $dom_id
128
- * @param string $field_name
129
- * @param array $selected_ids Array of integers
130
- * @param string|null $placeholder
131
- */
132
- private static function render_admin_select2_product_selector( $dom_id, $field_name, $selected_keys_and_values, $placeholder ) {
133
- // $selected_keys_and_values must be an array of [ id => name ]
134
-
135
- $json_encoded = esc_attr( json_encode( $selected_keys_and_values ) );
136
- echo '<input type="hidden" class="wc-product-search" data-multiple="true" style="width: 50%;" name="'
137
- . esc_attr( $field_name ) . '" data-placeholder="'
138
- . esc_attr( $placeholder ) . '" data-action="woocommerce_json_search_products_and_variations" data-selected="'
139
- . $json_encoded . '" value="' . implode( ',', array_keys( $selected_keys_and_values ) ) . '" />';
140
-
141
- }
142
-
143
- /**
144
- * Renders a product selection <input>.
145
- * Select2 version 4 (Since WC 3.0)
146
- * @param string $dom_id
147
- * @param string $field_name
148
- * @param string $selected_keys_and_values
149
- * @param string $placeholder
150
- */
151
- private static function render_admin_select2_v4_product_selector( $dom_id, $field_name, $selected_keys_and_values, $placeholder ) {
152
- // $selected_keys_and_values must be an array of [ id => name ]
153
-
154
- $json_encoded = esc_attr( json_encode( $selected_keys_and_values ) );
155
-
156
- echo '<select id="'. esc_attr( $dom_id ) .'" class="wc-product-search" name="'
157
- . esc_attr( $field_name ) . '[]" multiple="multiple" style="width: 50%;" data-placeholder="'
158
- . esc_attr( $placeholder ) . '" data-action="woocommerce_json_search_products_and_variations">';
159
-
160
- foreach ( $selected_keys_and_values as $product_id => $product_name ) {
161
- echo '<option value="' . esc_attr( $product_id ) . '"' . selected( true, true, false ) . '>' . wp_kses_post( $product_name ) . '</option>';
162
- }
163
-
164
- echo '</select>';
165
- }
166
-
167
-
168
- /**
169
- * Renders a customer selection <input>. Will use either select2 v4 (WC3.0+) or select2 v3 (WC2.3+)
170
- * @param string $dom_id
171
- * @param string $field_name
172
- * @param array $selected_customer_ids Array of integers
173
- * @param string|null $placeholder
174
- * @return void
175
- */
176
- public static function render_admin_customer_selector( $dom_id, $field_name, $selected_customer_ids, $placeholder = null ) {
177
- $selected_keys_and_values = array();
178
- foreach ( $selected_customer_ids as $customer_id ) {
179
- $customer = get_userdata( $customer_id );
180
- if ( is_object( $customer ) ) {
181
- $selected_keys_and_values[ $customer_id ] = $customer->display_name . ' (#' . $customer->ID . ' &ndash; ' . sanitize_email( $customer->user_email ) . ')';
182
- }
183
- }
184
- if ( $placeholder === null ) $placeholder = __( 'Any customer', 'woocommerce-jos-autocoupon' );
185
-
186
- //In WooCommerce version 2.3.0 chosen was replaced by select2
187
- //In WooCommerce version 3.0 select2 v3 was replaced by select2 v4
188
- if ( WJECF_WC()->check_woocommerce_version('3.0') ) {
189
- self::render_admin_select2_v4_customer_selector( $dom_id, $field_name, $selected_keys_and_values, $placeholder );
190
- } else {
191
- self::render_admin_select2_customer_selector( $dom_id, $field_name, $selected_keys_and_values, $placeholder );
192
- }
193
- }
194
-
195
- private static function render_admin_select2_customer_selector( $dom_id, $field_name, $selected_keys_and_values, $placeholder ) {
196
- // $selected_keys_and_values must be an array of [ id => name ] .e.g. [ 12 => 'John Smith (#12 john.smith@example.com)' ]
197
-
198
- $json_encoded = esc_attr( json_encode( $selected_keys_and_values ) );
199
- echo '<input type="hidden" class="wc-customer-search" data-multiple="true" style="width: 50%;" name="'
200
- . esc_attr( $field_name ) . '" data-placeholder="'
201
- . esc_attr( $placeholder ) . '" data-action="woocommerce_json_search_customers" data-selected="'
202
- . $json_encoded . '" value="' . implode( ',', array_keys( $selected_keys_and_values ) ) . '" />';
203
- }
204
-
205
- private static function render_admin_select2_v4_customer_selector( $dom_id, $field_name, $selected_keys_and_values, $placeholder ) {
206
- // $selected_keys_and_values must be an array of [ id => name ]
207
-
208
- $json_encoded = esc_attr( json_encode( $selected_keys_and_values ) );
209
-
210
- echo '<select id="'. esc_attr( $dom_id ) .'" class="wc-customer-search" name="'
211
- . esc_attr( $field_name ) . '[]" multiple="multiple" style="width: 50%;" data-placeholder="'
212
- . esc_attr( $placeholder ) . '" data-action="woocommerce_json_search_customers">';
213
-
214
- foreach ( $selected_keys_and_values as $key => $value ) {
215
- echo '<option value="' . esc_attr( $key ) . '"' . selected( true, true, false ) . '>' . wp_kses_post( $value ) . '</option>';
216
- }
217
-
218
- echo '</select>';
219
- }
220
-
221
- // ============================================
222
- // Simple html output
223
-
224
-
225
- /**
226
- * Renders a <input type='text' />
227
- * $args should be an array in this form:
228
- * [ 'type' => 'text', name' => 'field-name', 'id' => 'dom-id', 'value' => 'value', 'class' => 'css-class' ]
229
- * @param array $args
230
- */
231
- public static function render_input( $args )
232
- {
233
- if ( ! isset( $args['type'] ) ) throw new Exception('Type field is obligatory.');
234
-
235
- $fields = array();
236
- switch ( $args['type'] ) {
237
- case 'text':
238
- $fields['type'] = 'text';
239
- if ( isset( $args['value'] ) ) $fields['value'] = esc_attr( $args['value'] );
240
- break;
241
-
242
- case 'hidden':
243
- $fields['type'] = 'hidden';
244
- if ( isset( $args['value'] ) ) $fields['value'] = esc_attr( $args['value'] );
245
- break;
246
-
247
- case 'radio':
248
- $fields['type'] = 'radio';
249
- if ( isset( $args['checked'] ) && $args['checked'] ) $fields['checked'] = esc_attr( $args['checked'] );
250
- if ( isset( $args['disabled'] ) && $args['disabled'] ) $fields['disabled'] = 'disabled';
251
-
252
- if ( isset( $args['value'] ) ) $fields['value'] = esc_attr( $args['value'] );
253
- break;
254
-
255
- case 'checkbox':
256
- $fields['type'] = 'checkbox';
257
- if ( isset( $args['disabled'] ) && $args['disabled'] ) $fields['disabled'] = 'disabled';
258
-
259
- $fields['value'] = isset( $args['cbvalue'] ) ? $args['cbvalue'] : 'yes';
260
- if ( isset( $args['value'] ) && (string) $args['value'] === (string) $fields['value'] ) {
261
- $fields['checked'] = "checked";
262
- }
263
-
264
- if ( ! empty( $field['description'] ) && false === $field['desc_tip'] ) {
265
- echo '<span class="description">' . wp_kses_post( $field['description'] ) . '</span>';
266
- }
267
- break;
268
-
269
- default:
270
- throw new Exception( sprintf( 'Unknown field type "%s" is obligatory.', $args['type'] ) );
271
- }
272
-
273
- //var_dump($args);
274
- if ( isset( $args['name'] ) ) $fields['name'] = esc_attr( $args['name'] );
275
- if ( isset( $args['id'] ) ) $fields['id'] = esc_attr( $args['id'] );
276
- if ( isset( $args['class'] ) ) $fields['class'] = esc_attr( $args['class'] );
277
-
278
- self::render_tag( 'input', $fields );
279
- }
280
-
281
- /**
282
- * Renders a html tag:
283
- * e.g. <name field="value">contents</name>
284
- *
285
- * This function does not escape the fields! Keys must be valid attribute names and values MUST be properly escaped!!!
286
- *
287
- * @param string $name
288
- * @param array $fields E.g. [ 'class' => 'clearfix' ] yields: class="clearfix"
289
- * @param string|null $contents If null the html tag will be self closing
290
- * @return void
291
- */
292
- public static function render_tag( $name, $fields = array(), $contents = null ) {
293
- echo self::tag( $name, $fields, $contents );
294
- }
295
-
296
- /**
297
- * Returns a html tag:
298
- * e.g. <name field="value">contents</name>
299
- *
300
- * This function does not escape the fields! Keys must be valid attribute names and values MUST be properly escaped!!!
301
- *
302
- * @param string $name
303
- * @param array $fields E.g. [ 'class' => 'clearfix' ] yields: class="clearfix"
304
- * @param string|null $contents If null the html tag will be self closing
305
- * @return string
306
- */
307
- protected static function tag( $name, $fields = array(), $contents = null ) {
308
- if ( is_null( $contents ) ) {
309
- return sprintf( "<%s %s/>", $name, self::extract_attributes( $fields ) );
310
- } else {
311
- return sprintf( "<%s %s>%s</%s>", $name, self::extract_attributes( $fields ), $contents, $name );
312
- }
313
- }
314
-
315
- /**
316
- * Extracts fields to attributes for html tags.
317
- * E.g. [ 'class' => 'clearfix' ] yields: class="clearfix"
318
- *
319
- * This function does not escape the fields! Keys must be valid attribute names and values MUST be properly escaped!!!
320
- *
321
- * @param array $fields
322
- * @return string
323
- */
324
- protected static function extract_attributes( $fields ) {
325
- //NOTE: attrib and value MUST be properly escaped!!!
326
- $extracted = "";
327
- foreach( $fields as $field => $value ) {
328
- $extracted .= sprintf( '%s="%s" ', $field, $value );
329
- }
330
- return $extracted;
331
- }
332
-
333
  }
1
+ <?php
2
+
3
+ /**
4
+ * HTML Output functions for admin
5
+ */
6
+ class WJECF_Admin_Html {
7
+
8
+
9
+ /**
10
+ * 2.3.6
11
+ * Renders a <SELECT> that has a default value. Relies on woocommerce_wp_select
12
+ *
13
+ * field['default_value']: The default value (if omitted the first option will be default)
14
+ * field['append_default_label']: If true or omitted the text '(DEFAULT)' will be appended to the default option caption
15
+ *
16
+ * @param array $field see wc-meta-box-functions.php:woocommerce_wp_select
17
+ * @return void
18
+ */
19
+ public static function render_select_with_default( $field ) {
20
+ global $thepostid, $post;
21
+ $thepostid = empty( $thepostid ) ? $post->ID : $thepostid;
22
+
23
+ reset( $field['options'] ); //move to first for key()
24
+ $default_value = isset( $field['default_value'] ) ? $field['default_value'] : key( $field['options'] );
25
+ $append_default_label = isset( $field['append_default_label'] ) ? $field['append_default_label'] : true;
26
+
27
+ if ( $append_default_label ) {
28
+ $field['options'][$default_value] = sprintf( __( '%s (Default)', 'woocommerce-jos-autocoupon' ), $field['options'][$default_value] );
29
+ }
30
+
31
+ if ( ! isset( $field['value'] ) ) {
32
+ $field['value'] = get_post_meta( $thepostid, $field['id'], true );
33
+ }
34
+ if ( empty( $field['value'] ) ) {
35
+ $field['value'] = $default_value;
36
+ }
37
+
38
+ woocommerce_wp_select( $field );
39
+ }
40
+
41
+ public static function render_admin_cat_selector( $dom_id, $field_name, $selected_ids, $placeholder = null ) {
42
+ if ( $placeholder === null ) $placeholder = __( 'Search for a product…', 'woocommerce' );
43
+
44
+ // Categories
45
+ ?>
46
+ <select id="<?php esc_attr_e( $dom_id ) ?>" name="<?php esc_attr_e( $field_name ) ?>[]" style="width: 50%;" class="wc-enhanced-select" multiple="multiple" data-placeholder="<?php esc_attr_e( $placeholder ); ?>">
47
+ <?php
48
+ $categories = get_terms( 'product_cat', 'orderby=name&hide_empty=0' );
49
+
50
+ if ( $categories ) foreach ( $categories as $cat ) {
51
+ echo '<option value="' . esc_attr( $cat->term_id ) . '"' . selected( in_array( $cat->term_id, $selected_ids ), true, false ) . '>' . esc_html( $cat->name ) . '</option>';
52
+ }
53
+ ?>
54
+ </select>
55
+ <?php
56
+ }
57
+
58
+
59
+ /**
60
+ * Display a WooCommerce help tip
61
+ * @param string $tip The tip to display
62
+ * @return string
63
+ */
64
+ public static function wc_help_tip( $tip ) {
65
+ //Since WC 2.5.0
66
+ if ( function_exists( 'wc_help_tip' ) ) {
67
+ return wc_help_tip( $tip );
68
+ }
69
+
70
+ return '<img class="help_tip" style="margin-top: 21px;" data-tip="' . esc_attr( $tip ) . '" src="' . esc_url( WC()->plugin_url() ) . '/assets/images/help.png" height="16" width="16" />';
71
+ }
72
+
73
+ /**
74
+ * Renders a product selection <input>. Will use either select2 v4 (WC3.0+) select2 v3 (WC2.3+) or chosen (< WC2.3)
75
+ * @param string $dom_id
76
+ * @param string $field_name
77
+ * @param array $selected_ids Array of integers
78
+ * @param string|null $placeholder
79
+ * @return void
80
+ */
81
+ public static function render_admin_product_selector( $dom_id, $field_name, $selected_ids, $placeholder = null ) {
82
+ $product_key_values = array();
83
+ foreach ( $selected_ids as $product_id ) {
84
+ $product = wc_get_product( $product_id );
85
+ if ( is_object( $product ) ) {
86
+ $product_key_values[ esc_attr( $product_id ) ] = wp_kses_post( $product->get_formatted_name() );
87
+ }
88
+ }
89
+
90
+ if ( $placeholder === null ) $placeholder = __( 'Search for a product…', 'woocommerce' );
91
+
92
+ //In WooCommerce version 2.3.0 chosen was replaced by select2
93
+ //In WooCommerce version 3.0 select2 v3 was replaced by select2 v4
94
+ if ( WJECF_WC()->check_woocommerce_version('3.0') ) {
95
+ self::render_admin_select2_v4_product_selector( $dom_id, $field_name, $product_key_values, $placeholder );
96
+ } elseif ( WJECF_WC()->check_woocommerce_version('2.3.0') ) {
97
+ self::render_admin_select2_product_selector( $dom_id, $field_name, $product_key_values, $placeholder );
98
+ } else {
99
+ self::render_admin_chosen_product_selector( $dom_id, $field_name, $product_key_values, $placeholder );
100
+ }
101
+ }
102
+
103
+
104
+ /**
105
+ * Renders a product selection <input>.
106
+ * Chosen (Legacy)
107
+ * @param string $dom_id
108
+ * @param string $field_name
109
+ * @param array $selected_ids Array of integers
110
+ * @param string|null $placeholder
111
+ */
112
+ private static function render_admin_chosen_product_selector( $dom_id, $field_name, $selected_keys_and_values, $placeholder ) {
113
+ // $selected_keys_and_values must be an array of [ id => name ]
114
+
115
+ echo '<select id="' . esc_attr( $dom_id ) . '" name="' . esc_attr( $field_name ) . '[]" class="ajax_chosen_select_products_and_variations" multiple="multiple" data-placeholder="' . esc_attr( $placeholder ) . '">';
116
+ foreach ( $selected_keys_and_values as $product_id => $product_name ) {
117
+ echo '<option value="' . $product_id . '" selected="selected">' . $product_name . '</option>';
118
+ }
119
+ echo '</select>';
120
+ }
121
+
122
+ /**
123
+ * @since 2.4.1 for WC 3.0 compatibility
124
+ *
125
+ * Renders a product selection <input>.
126
+ * Select2 version 3 (Since WC 2.3.0)
127
+ * @param string $dom_id
128
+ * @param string $field_name
129
+ * @param array $selected_ids Array of integers
130
+ * @param string|null $placeholder
131
+ */
132
+ private static function render_admin_select2_product_selector( $dom_id, $field_name, $selected_keys_and_values, $placeholder ) {
133
+ // $selected_keys_and_values must be an array of [ id => name ]
134
+
135
+ $json_encoded = esc_attr( json_encode( $selected_keys_and_values ) );
136
+ echo '<input type="hidden" class="wc-product-search" data-multiple="true" style="width: 50%;" name="'
137
+ . esc_attr( $field_name ) . '" data-placeholder="'
138
+ . esc_attr( $placeholder ) . '" data-action="woocommerce_json_search_products_and_variations" data-selected="'
139
+ . $json_encoded . '" value="' . implode( ',', array_keys( $selected_keys_and_values ) ) . '" />';
140
+
141
+ }
142
+
143
+ /**
144
+ * Renders a product selection <input>.
145
+ * Select2 version 4 (Since WC 3.0)
146
+ * @param string $dom_id
147
+ * @param string $field_name
148
+ * @param string $selected_keys_and_values
149
+ * @param string $placeholder
150
+ */
151
+ private static function render_admin_select2_v4_product_selector( $dom_id, $field_name, $selected_keys_and_values, $placeholder ) {
152
+ // $selected_keys_and_values must be an array of [ id => name ]
153
+
154
+ $json_encoded = esc_attr( json_encode( $selected_keys_and_values ) );
155
+
156
+ echo '<select id="'. esc_attr( $dom_id ) .'" class="wc-product-search" name="'
157
+ . esc_attr( $field_name ) . '[]" multiple="multiple" style="width: 50%;" data-placeholder="'
158
+ . esc_attr( $placeholder ) . '" data-action="woocommerce_json_search_products_and_variations">';
159
+
160
+ foreach ( $selected_keys_and_values as $product_id => $product_name ) {
161
+ echo '<option value="' . esc_attr( $product_id ) . '"' . selected( true, true, false ) . '>' . wp_kses_post( $product_name ) . '</option>';
162
+ }
163
+
164
+ echo '</select>';
165
+ }
166
+
167
+
168
+ /**
169
+ * Renders a customer selection <input>. Will use either select2 v4 (WC3.0+) or select2 v3 (WC2.3+)
170
+ * @param string $dom_id
171
+ * @param string $field_name
172
+ * @param array $selected_customer_ids Array of integers
173
+ * @param string|null $placeholder
174
+ * @return void
175
+ */
176
+ public static function render_admin_customer_selector( $dom_id, $field_name, $selected_customer_ids, $placeholder = null ) {
177
+ $selected_keys_and_values = array();
178
+ foreach ( $selected_customer_ids as $customer_id ) {
179
+ $customer = get_userdata( $customer_id );
180
+ if ( is_object( $customer ) ) {
181
+ $selected_keys_and_values[ $customer_id ] = $customer->display_name . ' (#' . $customer->ID . ' &ndash; ' . sanitize_email( $customer->user_email ) . ')';
182
+ }
183
+ }
184
+ if ( $placeholder === null ) $placeholder = __( 'Any customer', 'woocommerce-jos-autocoupon' );
185
+
186
+ //In WooCommerce version 2.3.0 chosen was replaced by select2
187
+ //In WooCommerce version 3.0 select2 v3 was replaced by select2 v4
188
+ if ( WJECF_WC()->check_woocommerce_version('3.0') ) {
189
+ self::render_admin_select2_v4_customer_selector( $dom_id, $field_name, $selected_keys_and_values, $placeholder );
190
+ } else {
191
+ self::render_admin_select2_customer_selector( $dom_id, $field_name, $selected_keys_and_values, $placeholder );
192
+ }
193
+ }
194
+
195
+ private static function render_admin_select2_customer_selector( $dom_id, $field_name, $selected_keys_and_values, $placeholder ) {
196
+ // $selected_keys_and_values must be an array of [ id => name ] .e.g. [ 12 => 'John Smith (#12 john.smith@example.com)' ]
197
+
198
+ $json_encoded = esc_attr( json_encode( $selected_keys_and_values ) );
199
+ echo '<input type="hidden" class="wc-customer-search" data-multiple="true" style="width: 50%;" name="'
200
+ . esc_attr( $field_name ) . '" data-placeholder="'
201
+ . esc_attr( $placeholder ) . '" data-action="woocommerce_json_search_customers" data-selected="'
202
+ . $json_encoded . '" value="' . implode( ',', array_keys( $selected_keys_and_values ) ) . '" />';
203
+ }
204
+
205
+ private static function render_admin_select2_v4_customer_selector( $dom_id, $field_name, $selected_keys_and_values, $placeholder ) {
206
+ // $selected_keys_and_values must be an array of [ id => name ]
207
+
208
+ $json_encoded = esc_attr( json_encode( $selected_keys_and_values ) );
209
+
210
+ echo '<select id="'. esc_attr( $dom_id ) .'" class="wc-customer-search" name="'
211
+ . esc_attr( $field_name ) . '[]" multiple="multiple" style="width: 50%;" data-placeholder="'
212
+ . esc_attr( $placeholder ) . '" data-action="woocommerce_json_search_customers">';
213
+
214
+ foreach ( $selected_keys_and_values as $key => $value ) {
215
+ echo '<option value="' . esc_attr( $key ) . '"' . selected( true, true, false ) . '>' . wp_kses_post( $value ) . '</option>';
216
+ }
217
+
218
+ echo '</select>';
219
+ }
220
+
221
+ // ============================================
222
+ // Simple html output
223
+
224
+
225
+ /**
226
+ * Renders a <input type='text' />
227
+ * $args should be an array in this form:
228
+ * [ 'type' => 'text', name' => 'field-name', 'id' => 'dom-id', 'value' => 'value', 'class' => 'css-class' ]
229
+ * @param array $args
230
+ */
231
+ public static function render_input( $args )
232
+ {
233
+ if ( ! isset( $args['type'] ) ) throw new Exception('Type field is obligatory.');
234
+
235
+ $fields = array();
236
+ switch ( $args['type'] ) {
237
+ case 'text':
238
+ $fields['type'] = 'text';
239
+ if ( isset( $args['value'] ) ) $fields['value'] = esc_attr( $args['value'] );
240
+ break;
241
+
242
+ case 'hidden':
243
+ $fields['type'] = 'hidden';
244
+ if ( isset( $args['value'] ) ) $fields['value'] = esc_attr( $args['value'] );
245
+ break;
246
+
247
+ case 'radio':
248
+ $fields['type'] = 'radio';
249
+ if ( isset( $args['checked'] ) && $args['checked'] ) $fields['checked'] = esc_attr( $args['checked'] );
250
+ if ( isset( $args['disabled'] ) && $args['disabled'] ) $fields['disabled'] = 'disabled';
251
+
252
+ if ( isset( $args['value'] ) ) $fields['value'] = esc_attr( $args['value'] );
253
+ break;
254
+
255
+ case 'checkbox':
256
+ $fields['type'] = 'checkbox';
257
+ if ( isset( $args['disabled'] ) && $args['disabled'] ) $fields['disabled'] = 'disabled';
258
+
259
+ $fields['value'] = isset( $args['cbvalue'] ) ? $args['cbvalue'] : 'yes';
260
+ if ( isset( $args['value'] ) && (string) $args['value'] === (string) $fields['value'] ) {
261
+ $fields['checked'] = "checked";
262
+ }
263
+
264
+ if ( ! empty( $field['description'] ) && false === $field['desc_tip'] ) {
265
+ echo '<span class="description">' . wp_kses_post( $field['description'] ) . '</span>';
266
+ }
267
+ break;
268
+
269
+ default:
270
+ throw new Exception( sprintf( 'Unknown field type "%s" is obligatory.', $args['type'] ) );
271
+ }
272
+
273
+ //var_dump($args);
274
+ if ( isset( $args['name'] ) ) $fields['name'] = esc_attr( $args['name'] );
275
+ if ( isset( $args['id'] ) ) $fields['id'] = esc_attr( $args['id'] );
276
+ if ( isset( $args['class'] ) ) $fields['class'] = esc_attr( $args['class'] );
277
+
278
+ self::render_tag( 'input', $fields );
279
+ }
280
+
281
+ /**
282
+ * Renders a html tag:
283
+ * e.g. <name field="value">contents</name>
284
+ *
285
+ * This function does not escape the fields! Keys must be valid attribute names and values MUST be properly escaped!!!
286
+ *
287
+ * @param string $name
288
+ * @param array $fields E.g. [ 'class' => 'clearfix' ] yields: class="clearfix"
289
+ * @param string|null $contents If null the html tag will be self closing
290
+ * @return void
291
+ */
292
+ public static function render_tag( $name, $fields = array(), $contents = null ) {
293
+ echo self::tag( $name, $fields, $contents );
294
+ }
295
+
296
+ /**
297
+ * Returns a html tag:
298
+ * e.g. <name field="value">contents</name>
299
+ *
300
+ * This function does not escape the fields! Keys must be valid attribute names and values MUST be properly escaped!!!
301
+ *
302
+ * @param string $name
303
+ * @param array $fields E.g. [ 'class' => 'clearfix' ] yields: class="clearfix"
304
+ * @param string|null $contents If null the html tag will be self closing
305
+ * @return string
306
+ */
307
+ protected static function tag( $name, $fields = array(), $contents = null ) {
308
+ if ( is_null( $contents ) ) {
309
+ return sprintf( "<%s %s/>", $name, self::extract_attributes( $fields ) );
310
+ } else {
311
+ return sprintf( "<%s %s>%s</%s>", $name, self::extract_attributes( $fields ), $contents, $name );
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Extracts fields to attributes for html tags.
317
+ * E.g. [ 'class' => 'clearfix' ] yields: class="clearfix"
318
+ *
319
+ * This function does not escape the fields! Keys must be valid attribute names and values MUST be properly escaped!!!
320
+ *
321
+ * @param array $fields
322
+ * @return string
323
+ */
324
+ protected static function extract_attributes( $fields ) {
325
+ //NOTE: attrib and value MUST be properly escaped!!!
326
+ $extracted = "";
327
+ foreach( $fields as $field => $value ) {
328
+ $extracted .= sprintf( '%s="%s" ', $field, $value );
329
+ }
330
+ return $extracted;
331
+ }
332
+
333
  }
includes/admin/WJECF_Admin_Settings.php CHANGED
@@ -1,269 +1,269 @@
1
- <?php
2
-
3
- defined('ABSPATH') or die();
4
-
5
- if ( class_exists('WJECF_Admin') ) {
6
- return;
7
- }
8
-
9
- class WJECF_Admin_Settings extends Abstract_WJECF_Plugin {
10
- const DOM_PREFIX = 'wjecf-';
11
- const SETTINGS_PAGE = 'wjecf_settings';
12
- const OPTION_NAME = 'wjecf_options'; // In the wp_options table
13
-
14
- public function __construct() {
15
- $this->set_plugin_data( array(
16
- 'description' => __( 'Settings page of WooCommerce Extended Coupon Features.', 'woocommerce-jos-autocoupon' ),
17
- 'dependencies' => array(),
18
- 'can_be_disabled' => false
19
- ) );
20
- }
21
-
22
- public function init_admin_hook() {
23
- //return;
24
- add_action( 'admin_menu', array( $this, 'action_admin_menu' ) );
25
- add_action( 'admin_init', array( $this, 'action_admin_init' ) );
26
- add_filter( 'plugin_action_links_' . WJECF()->plugin_basename(), array( $this, 'plugin_action_links' ) );
27
- }
28
-
29
- public function action_admin_menu() {
30
- add_options_page( __( 'WooCommerce Extended Coupon Features', 'woocommerce-jos-autocoupon' ), __( 'WooCommerce Extended Coupon Features', 'woocommerce-jos-autocoupon' ), 'manage_options', self::SETTINGS_PAGE, array( &$this, 'action_admin_config_page' ) );
31
- }
32
-
33
- public function action_admin_config_page() {
34
- ?>
35
- <h2><?php _e( 'WooCommerce Extended Coupon Features', 'woocommerce-jos-autocoupon' ); ?></h2>
36
- <form method="post" action="options.php">
37
- <?php
38
- settings_fields( self::SETTINGS_PAGE );
39
- do_settings_sections( self::SETTINGS_PAGE );
40
- submit_button();
41
- ?>
42
- </form>
43
- <?php
44
- }
45
-
46
- public function action_admin_init() {
47
- $page = self::SETTINGS_PAGE;
48
-
49
- register_setting( self::SETTINGS_PAGE, self::OPTION_NAME, array( $this, 'validate_settings' ) );
50
-
51
- do_action( 'wjecf_admin_before_settings' );
52
-
53
- // Section DEBUG
54
- add_settings_section(
55
- self::DOM_PREFIX . 'section_debug',
56
- __( 'Advanced settings', 'woocommerce-jos-autocoupon' ),
57
- array( &$this, 'render_section' ),
58
- $page
59
- );
60
-
61
- add_settings_field(
62
- self::DOM_PREFIX . 'debug_mode',
63
- __( 'Debug mode', 'woocommerce-jos-autocoupon' ),
64
- array( $this, 'render_setting_debug_mode' ),
65
- $page,
66
- self::DOM_PREFIX . 'section_debug'
67
- );
68
-
69
- if ( WJECF()->get_option( 'debug_mode' ) === true ) {
70
- foreach ( WJECF()->get_plugins() as $name => $plugin ) {
71
- if ( $plugin->get_plugin_data('hidden') ) {
72
- continue;
73
- }
74
-
75
- if ( $plugin->get_plugin_data('can_be_disabled') || WJECF()->get_option('debug_mode') ) {
76
- add_settings_field(
77
- self::DOM_PREFIX . 'plugin-' . $name,
78
- $plugin->get_plugin_class_name(),
79
- array( $this, 'render_setting_plugin' ),
80
- $page,
81
- self::DOM_PREFIX . 'section_debug', //section
82
- array(
83
- 'plugin' => $plugin,
84
- ) //arguments
85
- );
86
- }
87
- }
88
- } else {
89
- $disabled_plugins = WJECF()->get_option('disabled_plugins');
90
- if ( ! empty( $disabled_plugins ) ) {
91
- add_settings_field(
92
- self::DOM_PREFIX . 'disabled_plugins',
93
- __( 'Disabled plugins', 'woocommerce-jos-autocoupon' ),
94
- array( $this, 'render_setting_disabled_plugins' ),
95
- $page,
96
- self::DOM_PREFIX . 'section_debug' //section
97
- );
98
- }
99
- }
100
-
101
- do_action( 'wjecf_admin_after_settings' );
102
- }
103
-
104
- /**
105
- * Show action links on the plugin screen.
106
- *
107
- * @since 2.5.6
108
- * @param mixed $links Plugin Action links
109
- * @return array
110
- */
111
- public function plugin_action_links( $links ) {
112
- $action_links = array(
113
- 'settings' => sprintf(
114
- '<a href="%s" title="%s">%s</a>',
115
- $this->get_settings_page_url(),
116
- esc_attr( __( 'Settings', 'woocommerce' ) ),
117
- esc_attr( __( 'Settings', 'woocommerce' ) )
118
- )
119
- );
120
- return array_merge( $action_links, $links );
121
- }
122
-
123
- /**
124
- * The url to the WooCommerce Extended Coupon Features settings page
125
- *
126
- * @since 2.5.6
127
- * @return string
128
- */
129
- public function get_settings_page_url() {
130
- return admin_url( 'options-general.php?page=' . self::SETTINGS_PAGE );
131
- }
132
-
133
-
134
- /**
135
- * Sanitize the posted settings
136
- * @param type $input
137
- * @return type
138
- */
139
- public function validate_settings( $input ) {
140
-
141
- $options = WJECF()->get_options();
142
-
143
- //disabled_plugins
144
- if ( isset( $input['disabled_plugins'] ) && is_array( $input['disabled_plugins'] ) ) {
145
- foreach( $input['disabled_plugins'] as $class_name => $disabled ) {
146
-
147
- $plugin = WJECF()->get_plugin( $class_name );
148
- if ( $plugin === false ) {
149
- continue; //unknown / invalid plugin
150
- }
151
-
152
- //Never disable those
153
- if ( ! $plugin->get_plugin_data('can_be_disabled') ) $disabled = 'no';
154
-
155
- //false if not found; otherwise the index
156
- $index = array_search( $class_name, $options['disabled_plugins'] );
157
-
158
- if ( $disabled == 'yes' && $index === false ) {
159
- $options['disabled_plugins'][] = $class_name;
160
- }
161
- elseif ( $disabled == 'no' && $index !== false )
162
- {
163
- unset( $options['disabled_plugins'][$index] );
164
- }
165
- }
166
- }
167
-
168
- //debug_mode
169
- $options['debug_mode'] = isset( $input['debug_mode'] ) && $input['debug_mode'] === 'yes';
170
-
171
- $options = apply_filters( 'wjecf_admin_validate_settings', $options, $input );
172
- return $options;
173
-
174
- }
175
-
176
- public function render_section( $section ) {
177
- switch ( $section['id'] ) {
178
- case self::DOM_PREFIX . 'section_debug':
179
- $body = __( 'When debug mode is enabled, extensive logging will be active and WJECF Plugins can be enabled/disabled. If there are compatibility issues with WooCommerce plugins, you can try disabling WJECF Plugins. Please don\'t keep debug mode enabled on a production environment when it is not necessary. Log can be found <a href="%s">here</a>.' , 'woocommerce-jos-autocoupon' );
180
- $body = sprintf( $body, admin_url( 'admin.php?page=wc-status&tab=logs' ) );
181
- printf( '<p>%s</p>', $body );
182
- break;
183
- }
184
- }
185
-
186
- /**
187
- *
188
- * Renders the enable/disable checkbox for a plugin
189
- *
190
- * Arguments: [ 'plugin' => WJECF_Plugin object ]
191
- *
192
- * @param array $arguments
193
- * @return void
194
- */
195
- public function render_setting_plugin( $arguments ) {
196
- //var_dump($arguments);
197
-
198
- $plugin = $arguments['plugin'];
199
- $plugin_name = $plugin->get_plugin_class_name();
200
- $plugin_disabled = $plugin->get_plugin_data('can_be_disabled') && in_array( $plugin_name, WJECF()->get_option( 'disabled_plugins' ) );
201
-
202
- //This field yields the value if the checbox is not checked
203
- //Don't draw this if plugin can't be disabled; as a disabled checkbox will not return a value
204
- $args = array(
205
- 'type' => 'hidden',
206
- 'id' => self::DOM_PREFIX . 'plugin-' . $plugin_name . '-enabled',
207
- 'name' => sprintf( "%s[disabled_plugins][%s]", self::OPTION_NAME, $plugin_name ),
208
- 'value' => $plugin->get_plugin_data('can_be_disabled') ? 'yes' : 'no' //yes = disabled
209
- );
210
- WJECF_Admin_Html::render_input( $args );
211
-
212
- $args = array(
213
- 'type' => 'checkbox',
214
- 'id' => self::DOM_PREFIX . 'plugin-' . $plugin_name . '-disabled',
215
- 'name' => sprintf( "%s[disabled_plugins][%s]", self::OPTION_NAME, $plugin_name ),
216
- 'value' => $plugin_disabled ? 'yes' : 'no',
217
- 'cbvalue' => 'no' //no = enabled
218
- );
219
- if ( ! $plugin->get_plugin_data('can_be_disabled') ) $args['disabled'] = true;
220
-
221
- WJECF_Admin_Html::render_input( $args );
222
-
223
- WJECF_Admin_Html::render_tag(
224
- 'label',
225
- array( "for" => esc_attr( $args['id'] ) ),
226
- __( 'Enabled', 'woocommerce' )
227
- );
228
- echo '<br>';
229
-
230
- printf( '<p><i>%s</i></p>',
231
- $plugin->get_plugin_description()
232
- );
233
-
234
- //echo $plugin->get_plugin_description();
235
- //echo "</p>\n";
236
- //echo "</li>\n";
237
- }
238
-
239
- public function render_setting_disabled_plugins() {
240
- echo "<ul>";
241
- foreach( WJECF()->get_option( 'disabled_plugins' ) as $class_name ) {
242
- $plugin = WJECF()->get_plugin( $class_name );
243
- if ( $plugin ) {
244
- echo "<li><strong>" . $class_name . "</strong><br><i>" . $plugin->get_plugin_description() . "</i></li>";
245
- }
246
- }
247
- echo "</ul>";
248
- }
249
-
250
- public function render_setting_debug_mode() {
251
- $option_name = 'debug_mode';
252
- $args = array(
253
- 'type' => 'checkbox',
254
- 'id' => self::DOM_PREFIX . $option_name,
255
- 'name' => sprintf( "%s[%s]", self::OPTION_NAME, $option_name ),
256
- 'value' => WJECF()->get_option( $option_name ) ? 'yes' : 'no'
257
- );
258
- WJECF_Admin_Html::render_input( $args );
259
-
260
- WJECF_Admin_Html::render_tag(
261
- 'label',
262
- array( "for" => esc_attr( $args['id'] ) ),
263
- __( 'Enabled', 'woocommerce' )
264
- );
265
-
266
- }
267
-
268
-
269
  }
1
+ <?php
2
+
3
+ defined('ABSPATH') or die();
4
+
5
+ if ( class_exists('WJECF_Admin') ) {
6
+ return;
7
+ }
8
+
9
+ class WJECF_Admin_Settings extends Abstract_WJECF_Plugin {
10
+ const DOM_PREFIX = 'wjecf-';
11
+ const SETTINGS_PAGE = 'wjecf_settings';
12
+ const OPTION_NAME = 'wjecf_options'; // In the wp_options table
13
+
14
+ public function __construct() {
15
+ $this->set_plugin_data( array(
16
+ 'description' => __( 'Settings page of WooCommerce Extended Coupon Features.', 'woocommerce-jos-autocoupon' ),
17
+ 'dependencies' => array(),
18
+ 'can_be_disabled' => false
19
+ ) );
20
+ }
21
+
22
+ public function init_admin_hook() {
23
+ //return;
24
+ add_action( 'admin_menu', array( $this, 'action_admin_menu' ) );
25
+ add_action( 'admin_init', array( $this, 'action_admin_init' ) );
26
+ add_filter( 'plugin_action_links_' . WJECF()->plugin_basename(), array( $this, 'plugin_action_links' ) );
27
+ }
28
+
29
+ public function action_admin_menu() {
30
+ add_options_page( __( 'WooCommerce Extended Coupon Features', 'woocommerce-jos-autocoupon' ), __( 'WooCommerce Extended Coupon Features', 'woocommerce-jos-autocoupon' ), 'manage_options', self::SETTINGS_PAGE, array( &$this, 'action_admin_config_page' ) );
31
+ }
32
+
33
+ public function action_admin_config_page() {
34
+ ?>
35
+ <h2><?php _e( 'WooCommerce Extended Coupon Features', 'woocommerce-jos-autocoupon' ); ?></h2>
36
+ <form method="post" action="options.php">
37
+ <?php
38
+ settings_fields( self::SETTINGS_PAGE );
39
+ do_settings_sections( self::SETTINGS_PAGE );
40
+ submit_button();
41
+ ?>
42
+ </form>
43
+ <?php
44
+ }
45
+
46
+ public function action_admin_init() {
47
+ $page = self::SETTINGS_PAGE;
48
+
49
+ register_setting( self::SETTINGS_PAGE, self::OPTION_NAME, array( $this, 'validate_settings' ) );
50
+
51
+ do_action( 'wjecf_admin_before_settings' );
52
+
53
+ // Section DEBUG
54
+ add_settings_section(
55
+ self::DOM_PREFIX . 'section_debug',
56
+ __( 'Advanced settings', 'woocommerce-jos-autocoupon' ),
57
+ array( &$this, 'render_section' ),
58
+ $page
59
+ );
60
+
61
+ add_settings_field(
62
+ self::DOM_PREFIX . 'debug_mode',
63
+ __( 'Debug mode', 'woocommerce-jos-autocoupon' ),
64
+ array( $this, 'render_setting_debug_mode' ),
65
+ $page,
66
+ self::DOM_PREFIX . 'section_debug'
67
+ );
68
+
69
+ if ( WJECF()->get_option( 'debug_mode' ) === true ) {
70
+ foreach ( WJECF()->get_plugins() as $name => $plugin ) {
71
+ if ( $plugin->get_plugin_data('hidden') ) {
72
+ continue;
73
+ }
74
+
75
+ if ( $plugin->get_plugin_data('can_be_disabled') || WJECF()->get_option('debug_mode') ) {
76
+ add_settings_field(
77
+ self::DOM_PREFIX . 'plugin-' . $name,
78
+ $plugin->get_plugin_class_name(),
79
+ array( $this, 'render_setting_plugin' ),
80
+ $page,
81
+ self::DOM_PREFIX . 'section_debug', //section
82
+ array(
83
+ 'plugin' => $plugin,
84
+ ) //arguments
85
+ );
86
+ }
87
+ }
88
+ } else {
89
+ $disabled_plugins = WJECF()->get_option('disabled_plugins');
90
+ if ( ! empty( $disabled_plugins ) ) {
91
+ add_settings_field(
92
+ self::DOM_PREFIX . 'disabled_plugins',
93
+ __( 'Disabled plugins', 'woocommerce-jos-autocoupon' ),
94
+ array( $this, 'render_setting_disabled_plugins' ),
95
+ $page,
96
+ self::DOM_PREFIX . 'section_debug' //section
97
+ );
98
+ }
99
+ }
100
+
101
+ do_action( 'wjecf_admin_after_settings' );
102
+ }
103
+
104
+ /**
105
+ * Show action links on the plugin screen.
106
+ *
107
+ * @since 2.5.6
108
+ * @param mixed $links Plugin Action links
109
+ * @return array
110
+ */
111
+ public function plugin_action_links( $links ) {
112
+ $action_links = array(
113
+ 'settings' => sprintf(
114
+ '<a href="%s" title="%s">%s</a>',
115
+ $this->get_settings_page_url(),
116
+ esc_attr( __( 'Settings', 'woocommerce' ) ),
117
+ esc_attr( __( 'Settings', 'woocommerce' ) )
118
+ )
119
+ );
120
+ return array_merge( $action_links, $links );
121
+ }
122
+
123
+ /**
124
+ * The url to the WooCommerce Extended Coupon Features settings page
125
+ *
126
+ * @since 2.5.6
127
+ * @return string
128
+ */
129
+ public function get_settings_page_url() {
130
+ return admin_url( 'options-general.php?page=' . self::SETTINGS_PAGE );
131
+ }
132
+
133
+
134
+ /**
135
+ * Sanitize the posted settings
136
+ * @param type $input
137
+ * @return type
138
+ */
139
+ public function validate_settings( $input ) {
140
+
141
+ $options = WJECF()->get_options();
142
+
143
+ //disabled_plugins
144
+ if ( isset( $input['disabled_plugins'] ) && is_array( $input['disabled_plugins'] ) ) {
145
+ foreach( $input['disabled_plugins'] as $class_name => $disabled ) {
146
+
147
+ $plugin = WJECF()->get_plugin( $class_name );
148
+ if ( $plugin === false ) {
149
+ continue; //unknown / invalid plugin
150
+ }
151
+
152
+ //Never disable those
153
+ if ( ! $plugin->get_plugin_data('can_be_disabled') ) $disabled = 'no';
154
+
155
+ //false if not found; otherwise the index
156
+ $index = array_search( $class_name, $options['disabled_plugins'] );
157
+
158
+ if ( $disabled == 'yes' && $index === false ) {
159
+ $options['disabled_plugins'][] = $class_name;
160
+ }
161
+ elseif ( $disabled == 'no' && $index !== false )
162
+ {
163
+ unset( $options['disabled_plugins'][$index] );
164
+ }
165
+ }
166
+ }
167
+
168
+ //debug_mode
169
+ $options['debug_mode'] = isset( $input['debug_mode'] ) && $input['debug_mode'] === 'yes';
170
+
171
+ $options = apply_filters( 'wjecf_admin_validate_settings', $options, $input );
172
+ return $options;
173
+
174
+ }
175
+
176
+ public function render_section( $section ) {
177
+ switch ( $section['id'] ) {
178
+ case self::DOM_PREFIX . 'section_debug':
179
+ $body = __( 'When debug mode is enabled, extensive logging will be active and WJECF Plugins can be enabled/disabled. If there are compatibility issues with WooCommerce plugins, you can try disabling WJECF Plugins. Please don\'t keep debug mode enabled on a production environment when it is not necessary. Log can be found <a href="%s">here</a>.' , 'woocommerce-jos-autocoupon' );
180
+ $body = sprintf( $body, admin_url( 'admin.php?page=wc-status&tab=logs' ) );
181
+ printf( '<p>%s</p>', $body );
182
+ break;
183
+ }
184
+ }
185
+
186
+ /**
187
+ *
188
+ * Renders the enable/disable checkbox for a plugin
189
+ *
190
+ * Arguments: [ 'plugin' => WJECF_Plugin object ]
191
+ *
192
+ * @param array $arguments
193
+ * @return void
194
+ */
195
+ public function render_setting_plugin( $arguments ) {
196
+ //var_dump($arguments);
197
+
198
+ $plugin = $arguments['plugin'];
199
+ $plugin_name = $plugin->get_plugin_class_name();
200
+ $plugin_disabled = $plugin->get_plugin_data('can_be_disabled') && in_array( $plugin_name, WJECF()->get_option( 'disabled_plugins' ) );
201
+
202
+ //This field yields the value if the checbox is not checked
203
+ //Don't draw this if plugin can't be disabled; as a disabled checkbox will not return a value
204
+ $args = array(
205
+ 'type' => 'hidden',
206
+ 'id' => self::DOM_PREFIX . 'plugin-' . $plugin_name . '-enabled',
207
+ 'name' => sprintf( "%s[disabled_plugins][%s]", self::OPTION_NAME, $plugin_name ),
208
+ 'value' => $plugin->get_plugin_data('can_be_disabled') ? 'yes' : 'no' //yes = disabled
209
+ );
210
+ WJECF_Admin_Html::render_input( $args );
211
+
212
+ $args = array(
213
+ 'type' => 'checkbox',
214
+ 'id' => self::DOM_PREFIX . 'plugin-' . $plugin_name . '-disabled',
215
+ 'name' => sprintf( "%s[disabled_plugins][%s]", self::OPTION_NAME, $plugin_name ),
216
+ 'value' => $plugin_disabled ? 'yes' : 'no',
217
+ 'cbvalue' => 'no' //no = enabled
218
+ );
219
+ if ( ! $plugin->get_plugin_data('can_be_disabled') ) $args['disabled'] = true;
220
+
221
+ WJECF_Admin_Html::render_input( $args );
222
+
223
+ WJECF_Admin_Html::render_tag(
224
+ 'label',
225
+ array( "for" => esc_attr( $args['id'] ) ),
226
+ __( 'Enabled', 'woocommerce' )
227
+ );
228
+ echo '<br>';
229
+
230
+ printf( '<p><i>%s</i></p>',
231
+ $plugin->get_plugin_description()
232
+ );
233
+
234
+ //echo $plugin->get_plugin_description();
235
+ //echo "</p>\n";
236
+ //echo "</li>\n";
237
+ }
238
+
239
+ public function render_setting_disabled_plugins() {
240
+ echo "<ul>";
241
+ foreach( WJECF()->get_option( 'disabled_plugins' ) as $class_name ) {
242
+ $plugin = WJECF()->get_plugin( $class_name );
243
+ if ( $plugin ) {
244
+ echo "<li><strong>" . $class_name . "</strong><br><i>" . $plugin->get_plugin_description() . "</i></li>";
245
+ }
246
+ }
247
+ echo "</ul>";
248
+ }
249
+
250
+ public function render_setting_debug_mode() {
251
+ $option_name = 'debug_mode';
252
+ $args = array(
253
+ 'type' => 'checkbox',
254
+ 'id' => self::DOM_PREFIX . $option_name,
255
+ 'name' => sprintf( "%s[%s]", self::OPTION_NAME, $option_name ),
256
+ 'value' => WJECF()->get_option( $option_name ) ? 'yes' : 'no'
257
+ );
258
+ WJECF_Admin_Html::render_input( $args );
259
+
260
+ WJECF_Admin_Html::render_tag(
261
+ 'label',
262
+ array( "for" => esc_attr( $args['id'] ) ),
263
+ __( 'Enabled', 'woocommerce' )
264
+ );
265
+
266
+ }
267
+
268
+
269
  }
readme.txt CHANGED
@@ -4,8 +4,8 @@ Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=5T9XQ
4
  Tags: woocommerce, coupons, discount
5
  Requires at least: 4.7
6
  Requires PHP: 5.3
7
- Tested up to: 4.9.4
8
- Stable tag: 2.6.2
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
@@ -108,7 +108,15 @@ Sure! [This](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=5T9XQ
108
 
109
  == Changelog ==
110
 
 
 
 
 
 
 
 
111
  = 2.6.2 =
 
112
  * FEATURE: Auto-coupon compatibility with the 'WooCommerce Free Gift Coupons'-plugin
113
  * FIX: CATEGORIES AND in combination with variable products
114
  * FIX: Call to undefined function wc_add_notice()
4
  Tags: woocommerce, coupons, discount
5
  Requires at least: 4.7
6
  Requires PHP: 5.3
7
+ Tested up to: 4.9.6
8
+ Stable tag: 2.6.3
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
108
 
109
  == Changelog ==
110
 
111
+ = 2.6.3 =
112
+ *Release Date - 2018-06-04*
113
+ * FIX: WJECF_Controller: Don't use wp_get_current_user() for admin-orders
114
+ * FIX: WJECF_Controller: Don't use WC()->cart->subtotal for admin-orders
115
+ * FIX: Possible division by zero when calculating multiplier value
116
+ * FEATURE: Filter 'wjecf_coupon_multiplier_value' to allow overriding the coupon's multiplier value
117
+
118
  = 2.6.2 =
119
+ *Release Date - 2018-04-02*
120
  * FEATURE: Auto-coupon compatibility with the 'WooCommerce Free Gift Coupons'-plugin
121
  * FIX: CATEGORIES AND in combination with variable products
122
  * FIX: Call to undefined function wc_add_notice()
woocommerce-jos-autocoupon.php CHANGED
@@ -3,14 +3,14 @@
3
  * Plugin Name: WooCommerce Extended Coupon Features
4
  * Plugin URI: http://www.soft79.nl
5
  * Description: Additional functionality for WooCommerce Coupons: Apply certain coupons automatically, allow applying coupons via an url, etc...
6
- * Version: 2.6.2
7
  * Author: Soft79
8
  * License: GPL2
9
  * WC requires at least: 2.6.0
10
- * WC tested up to: 3.3.4
11
  */
12
 
13
- if ( ! defined('WJECF_VERSION') ) define ('WJECF_VERSION', '2.6.2');
14
 
15
  // Changelog: see readme.txt
16
 
3
  * Plugin Name: WooCommerce Extended Coupon Features
4
  * Plugin URI: http://www.soft79.nl
5
  * Description: Additional functionality for WooCommerce Coupons: Apply certain coupons automatically, allow applying coupons via an url, etc...
6
+ * Version: 2.6.3
7
  * Author: Soft79
8
  * License: GPL2
9
  * WC requires at least: 2.6.0
10
+ * WC tested up to: 3.4.1
11
  */
12
 
13
+ if ( ! defined('WJECF_VERSION') ) define ('WJECF_VERSION', '2.6.3');
14
 
15
  // Changelog: see readme.txt
16