PluginProbe
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce / 3.2.0
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce v3.2.0
3.3.0 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.1 trunk 1.0.4 1.1.0 1.1.0.1 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 All 162 releases
cartflows / modules / checkout / classes / class-cartflows-checkout-ajax.php

class-cartflows-checkout-ajax.php in CartFlows – Funnel Builder & Checkout Plugin for WooCommerce 3.2.0, at modules/checkout/classes/class-cartflows-checkout-ajax.php

454 lines 13.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Checkout Ajax.
4 *
5 * @package CartFlows
6 */
7
8 // Exit if accessed directly.
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit;
11 }
12 /**
13 * Global Checkout
14 *
15 * @since 1.0.0
16 */
17 class Cartflows_Checkout_Ajax {
18
19
20 /**
21 * Member Variable
22 *
23 * @var object instance
24 */
25 private static $instance;
26
27 /**
28 * Initiator
29 */
30 public static function get_instance() {
31 if ( ! isset( self::$instance ) ) {
32 self::$instance = new self();
33 }
34 return self::$instance;
35 }
36
37 /**
38 * Constructor
39 */
40 public function __construct() {
41
42 /* Ajax Endpoint */
43 add_filter( 'woocommerce_ajax_get_endpoint', array( $this, 'get_ajax_endpoint' ), 10, 2 );
44
45 add_action( 'wp_ajax_wcf_woo_apply_coupon', array( $this, 'apply_coupon' ) );
46 add_action( 'wp_ajax_nopriv_wcf_woo_apply_coupon', array( $this, 'apply_coupon' ) );
47
48 add_action( 'wp_ajax_wcf_woo_remove_coupon', array( $this, 'remove_coupon' ) );
49 add_action( 'wp_ajax_nopriv_wcf_woo_remove_coupon', array( $this, 'remove_coupon' ) );
50
51 add_action( 'wp_ajax_wcf_woo_remove_cart_product', array( $this, 'wcf_woo_remove_cart_product' ) );
52 add_action( 'wp_ajax_nopriv_wcf_woo_remove_cart_product', array( $this, 'wcf_woo_remove_cart_product' ) );
53
54 add_action( 'wp_ajax_nopriv_wcf_check_email_exists', array( $this, 'check_email_exists' ) );
55 add_action( 'wp_ajax_nopriv_wcf_woocommerce_login', array( $this, 'woocommerce_user_login' ) );
56
57 add_action( 'wp_ajax_wcf_upload_checkout_file', array( $this, 'upload_checkout_file' ) );
58 add_action( 'wp_ajax_nopriv_wcf_upload_checkout_file', array( $this, 'upload_checkout_file' ) );
59 }
60
61 /**
62 * Get ajax end points.
63 *
64 * @param string $endpoint_url end point URL.
65 * @param string $request end point request.
66 * @return string
67 */
68 public function get_ajax_endpoint( $endpoint_url, $request ) {
69 global $post;
70
71 if ( ! empty( $post ) && ! empty( $_SERVER['REQUEST_URI'] ) ) {
72
73 if ( _is_wcf_checkout_type() ) {
74
75 $query_args = array();
76 $url = $endpoint_url;
77
78 if ( mb_strpos( $endpoint_url, 'checkout', 0, 'utf-8' ) === false ) {
79
80 if ( '' === $request ) {
81 $query_args = array(
82 'wc-ajax' => '%%endpoint%%',
83 );
84 } else {
85 $query_args = array(
86 'wc-ajax' => $request,
87 );
88 }
89
90 $uri = explode( '?', esc_url_raw( $_SERVER['REQUEST_URI'] ), 2 );
91 $url = esc_url( $uri[0] );
92 }
93
94 $query_args['wcf_checkout_id'] = $post->ID;
95
96 $endpoint_url = add_query_arg( $query_args, $url );
97 }
98 }
99
100 return $endpoint_url;
101 }
102
103 /**
104 * Apply coupon on submit of custom coupon form.
105 */
106 public function apply_coupon() {
107 $response = '';
108
109 if ( ! check_ajax_referer( 'wcf-apply-coupon', 'security', false ) ) {
110 $response_data = array(
111 'status' => false,
112 'error' => __( 'Nonce validation failed', 'cartflows' ),
113 );
114 wp_send_json_error( $response_data );
115 }
116
117 // Update the billing email before adding a coupon required for coupon conditions.
118 $this->update_billing_email();
119
120 ob_start();
121
122 if ( ! empty( $_POST['coupon_code'] ) ) {
123 $result = WC()->cart->add_discount( sanitize_text_field( wp_unslash( $_POST['coupon_code'] ) ) );
124 } else {
125 wc_add_notice( WC_Coupon::get_generic_coupon_error( WC_Coupon::E_WC_COUPON_PLEASE_ENTER ), 'error' );
126 }
127
128 $response = array(
129 'status' => $result,
130 'msg' => wc_print_notices( true ),
131 );
132
133 ob_clean(); // Clearing the uncessary echo HTML.
134 wp_send_json( $response );
135
136 die();
137 }
138
139 /**
140 * Remove coupon.
141 */
142 public function remove_coupon() {
143 check_ajax_referer( 'wcf-remove-coupon', 'security' );
144 $coupon = isset( $_POST['coupon_code'] ) ? sanitize_text_field( wp_unslash( $_POST['coupon_code'] ) ) : false;
145
146 if ( empty( $coupon ) ) {
147 echo "<div class='woocommerce-error'>" . esc_html__( 'Sorry there was a problem removing this coupon.', 'cartflows' ) . '</div>';
148 } else {
149 WC()->cart->remove_coupon( $coupon );
150 echo "<div class='woocommerce-error'>" . esc_html__( 'Coupon has been removed.', 'cartflows' ) . '</div>';
151 }
152 wc_print_notices();
153 wp_die();
154 }
155
156 /**
157 * Remove cart item.
158 */
159 public function wcf_woo_remove_cart_product() {
160 check_ajax_referer( 'wcf-remove-cart-product', 'security' );
161 $product_key = isset( $_POST['p_key'] ) ? sanitize_text_field( wp_unslash( $_POST['p_key'] ) ) : false;
162 $product_id = isset( $_POST['p_id'] ) ? sanitize_text_field( wp_unslash( $_POST['p_id'] ) ) : '';
163 $product_title = get_the_title( $product_id );
164
165 $needs_shipping = false;
166 $is_order_bump = false;
167 $order_bump_id = '';
168
169 // Check if the product is an order bump before removing it.
170 if ( ! empty( $product_key ) ) {
171 $cart_item = WC()->cart->get_cart_item( $product_key );
172 if ( isset( $cart_item['cartflows_bump'] ) && $cart_item['cartflows_bump'] ) {
173 $is_order_bump = true;
174 $order_bump_id = isset( $cart_item['ob_id'] ) ? $cart_item['ob_id'] : '';
175 }
176
177 WC()->cart->remove_cart_item( $product_key );
178 $msg = "<div class='woocommerce-message'>" . $product_title . __( ' has been removed.', 'cartflows' ) . '</div>';
179 } else {
180 $msg = "<div class='woocommerce-message'>" . __( 'Sorry there was a problem removing ', 'cartflows' ) . $product_title;
181 }
182
183 foreach ( WC()->cart->get_cart() as $cart_item_key => $values ) {
184 if ( $values['data']->needs_shipping() ) {
185 $needs_shipping = true;
186 break;
187 }
188 }
189
190 $response = array(
191 'need_shipping' => $needs_shipping,
192 'msg' => $msg,
193 'is_order_bump' => $is_order_bump,
194 'order_bump_id' => $order_bump_id,
195 );
196
197 echo wp_json_encode( $response );
198 wp_die();
199 }
200
201
202 /**
203 * Check email exist.
204 */
205 public function check_email_exists() {
206
207 check_ajax_referer( 'check-email-exist', 'security' );
208
209 $email_address = isset( $_POST['email_address'] ) ? sanitize_email( wp_unslash( $_POST['email_address'] ) ) : false;
210
211 $is_login_allowed = 'yes' === get_option( 'woocommerce_enable_checkout_login_reminder' );
212
213 // Security: Only check email existence when the checkout login UX needs it.
214 // Otherwise the boolean would leak registered emails for no UX benefit.
215 $is_exist = $is_login_allowed && email_exists( $email_address );
216
217 $response = array(
218 'success' => boolval( $is_exist ),
219 'is_login_allowed' => $is_login_allowed,
220 'msg' => $is_exist ? __( 'Email Exist.', 'cartflows' ) : __( 'Email not exist', 'cartflows' ),
221 );
222
223 wp_send_json_success( $response );
224 }
225
226 /**
227 * Update billing email address before applying the coupon. This is used for coupon conditions.
228 *
229 * @return void
230 * @since 2.0.12
231 */
232 public function update_billing_email() {
233
234 if ( ! wcf()->is_woo_active ) {
235 return;
236 }
237
238 if ( ! class_exists( 'Automattic\WooCommerce\Utilities\ArrayUtil' ) ) {
239 return;
240 }
241
242 // Sanitize the billing email.
243 $billing_email = ! empty( $_POST['billing_email'] ) ? sanitize_email( wp_unslash( $_POST['billing_email'] ) ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Missing
244
245 $billing_email = \Automattic\WooCommerce\Utilities\ArrayUtil::get_value_or_default(
246 array(
247 'billing_email' => $billing_email,
248 ),
249 'billing_email'
250 );
251
252 if ( is_string( $billing_email ) && is_email( $billing_email ) ) {
253 wc()->customer->set_billing_email( $billing_email );
254 }
255 }
256
257 /**
258 * Check email exist.
259 */
260 public function woocommerce_user_login() {
261
262 check_ajax_referer( 'woocommerce-login', 'security' );
263
264 $response = array(
265 'success' => false,
266 );
267
268 // wp_signon() accepts username or email via user_login; sanitize_email() would drop usernames.
269 $user_login = isset( $_POST['email'] ) ? sanitize_text_field( wp_unslash( $_POST['email'] ) ) : '';
270 $password = isset( $_POST['password'] ) ? wp_unslash( $_POST['password'] ) : ''; // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
271
272 $creds = array(
273 'user_login' => $user_login,
274 'user_password' => $password,
275 'remember' => false,
276 );
277
278 $user = wp_signon( $creds, false );
279
280 if ( ! is_wp_error( $user ) ) {
281 $response = array(
282 'success' => true,
283 );
284 } else {
285 // Mirror WC's process_login() so security plugins see the failure.
286 do_action( 'woocommerce_login_failed' );
287 // Generic error to prevent user enumeration.
288 $response['error'] = __( 'Invalid username or password.', 'cartflows' );
289 }
290
291 wp_send_json_success( $response );
292 }
293
294 /**
295 * Handle checkout file upload via AJAX.
296 *
297 * @since 2.2.2
298 * @return void
299 */
300 public function upload_checkout_file() {
301
302 if ( ! check_ajax_referer( 'wcf-file-upload', 'security', false ) ) {
303 wp_send_json_error(
304 array( 'error' => __( 'Nonce validation failed.', 'cartflows' ) )
305 );
306 }
307
308 if ( empty( $_FILES['wcf_checkout_file']['tmp_name'] ) ) {
309 wp_send_json_error(
310 array( 'error' => __( 'No file uploaded.', 'cartflows' ) )
311 );
312 }
313
314 if ( ! function_exists( 'wp_handle_upload' ) ) {
315 require_once ABSPATH . 'wp-admin/includes/file.php';
316 }
317
318 $file = $_FILES['wcf_checkout_file']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput, WordPress.Security.NonceVerification.Missing
319 $file['name'] = sanitize_file_name( $file['name'] );
320 $file['ext'] = strtolower( pathinfo( $file['name'], PATHINFO_EXTENSION ) );
321
322 $master_allowed = Cartflows_Helper::get_allowed_file_extensions();
323
324 $restrictions = $this->get_field_restrictions();
325
326 $allowed_extensions = empty( $restrictions['extensions'] )
327 ? $master_allowed
328 : array_values( array_intersect( $master_allowed, $restrictions['extensions'] ) );
329
330 if ( ! in_array( $file['ext'], $allowed_extensions, true ) ) {
331 wp_send_json_error( array( 'error' => __( 'File type is not allowed.', 'cartflows' ) ) );
332 }
333
334 if ( (int) $file['size'] > $restrictions['max_size'] ) {
335 wp_send_json_error( array( 'error' => __( 'File size exceeds the allowed limit.', 'cartflows' ) ) );
336 }
337
338 $check = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'], wp_get_mime_types() );
339
340 if ( empty( $check['ext'] ) || $check['ext'] !== $file['ext'] ) {
341 wp_send_json_error( array( 'error' => __( 'Invalid or corrupted file.', 'cartflows' ) ) );
342 }
343
344 $result = $this->move_uploaded_file( $file );
345
346 wp_send_json_success(
347 array(
348 'success' => true,
349 'url' => esc_url_raw( $result['url'] ),
350 'filename' => sanitize_file_name( basename( $result['file'] ) ),
351 )
352 );
353 }
354
355 /**
356 * Retrieve file size and type restrictions from field settings.
357 *
358 * @since 2.2.2
359 *
360 * @return array{max_size:int, extensions:string[]} Field-level upload restrictions.
361 */
362 private function get_field_restrictions() {
363
364 $max_size = 5 * 1024 * 1024; // 5MB default
365 $extensions = array();
366
367 $field_key = empty( $_POST['field_key'] ) ? '' : sanitize_text_field( wp_unslash( $_POST['field_key'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
368 $checkout_id = empty( $_POST['checkout_id'] ) ? 0 : absint( $_POST['checkout_id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
369
370 if ( empty( $field_key ) || empty( $checkout_id ) ) {
371 return compact( 'max_size', 'extensions' );
372 }
373
374 $field_type = ( 0 === strpos( $field_key, 'shipping_' ) ) ? 'shipping' : 'billing';
375 $saved_fields = get_post_meta( $checkout_id, 'wcf_field_order_' . $field_type, true );
376
377 if ( ! is_array( $saved_fields ) || empty( $saved_fields[ $field_key ] ) || ! is_array( $saved_fields[ $field_key ] ) ) {
378 return compact( 'max_size', 'extensions' );
379 }
380
381 $custom_attributes = empty( $saved_fields[ $field_key ]['custom_attributes'] ) || ! is_array( $saved_fields[ $field_key ]['custom_attributes'] )
382 ? array()
383 : $saved_fields[ $field_key ]['custom_attributes'];
384
385 if ( empty( $custom_attributes ) ) {
386 return compact( 'max_size', 'extensions' );
387 }
388
389 if ( ! empty( $custom_attributes['file_size'] ) && is_scalar( $custom_attributes['file_size'] ) ) {
390 $max_size = max( 1, absint( $custom_attributes['file_size'] ) ) * 1024 * 1024;
391 }
392
393 if ( ! empty( $custom_attributes['accepted_file_types'] ) ) {
394 $extensions = $this->normalize_extension_list( $custom_attributes['accepted_file_types'] );
395 }
396
397 return compact( 'max_size', 'extensions' );
398 }
399
400 /**
401 * Normalize accepted file types into a lowercase extension list.
402 *
403 * @since 2.2.2
404 *
405 * @param string $raw Raw accepted file types value.
406 * @return array Normalized extensions.
407 */
408 private function normalize_extension_list( $raw ) {
409
410 if ( ! is_string( $raw ) || '' === trim( $raw ) ) {
411 return array();
412 }
413
414 $extensions = array_map( 'strtolower', array_map( 'trim', explode( ',', $raw ) ) );
415
416 return array_values( array_filter( $extensions ) );
417 }
418
419 /**
420 * Move the uploaded file into the WordPress uploads directory.
421 *
422 * @since 2.2.2
423 *
424 * @param array $file Normalized file data.
425 * @return array Upload result.
426 */
427 private function move_uploaded_file( array $file ) {
428
429 $upload_dir = wp_upload_dir();
430 $file['name'] = wp_unique_filename( $upload_dir['path'], 'wcf_' . wp_generate_uuid4() . '.' . $file['ext'] );
431
432 $result = wp_handle_upload(
433 $file,
434 array(
435 'test_form' => false,
436 'mimes' => wp_get_mime_types(),
437 )
438 );
439
440 if ( isset( $result['error'] ) ) {
441 wp_send_json_error(
442 array( 'error' => esc_html( $result['error'] ) )
443 );
444 }
445
446 return $result;
447 }
448 }
449
450 /**
451 * Kicking this off by calling 'get_instance()' method
452 */
453 Cartflows_Checkout_Ajax::get_instance();
454