PluginProbe
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce / 3.0.1
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce v3.0.1
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.0.1, at modules/checkout/classes/class-cartflows-checkout-ajax.php

453 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 $email_address = isset( $_POST['email'] ) ? sanitize_email( wp_unslash( $_POST['email'] ) ) : false;
269 $password = isset( $_POST['password'] ) ? wp_unslash( $_POST['password'] ) : false; // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
270
271 $creds = array(
272 'user_login' => $email_address,
273 'user_password' => $password,
274 'remember' => false,
275 );
276
277 $user = wp_signon( $creds, false );
278
279 if ( ! is_wp_error( $user ) ) {
280 $response = array(
281 'success' => true,
282 );
283 } else {
284 // Mirror WC's process_login() so security plugins see the failure.
285 do_action( 'woocommerce_login_failed' );
286 // Generic error to prevent user enumeration.
287 $response['error'] = __( 'Invalid email address or password.', 'cartflows' );
288 }
289
290 wp_send_json_success( $response );
291 }
292
293 /**
294 * Handle checkout file upload via AJAX.
295 *
296 * @since 2.2.2
297 * @return void
298 */
299 public function upload_checkout_file() {
300
301 if ( ! check_ajax_referer( 'wcf-file-upload', 'security', false ) ) {
302 wp_send_json_error(
303 array( 'error' => __( 'Nonce validation failed.', 'cartflows' ) )
304 );
305 }
306
307 if ( empty( $_FILES['wcf_checkout_file']['tmp_name'] ) ) {
308 wp_send_json_error(
309 array( 'error' => __( 'No file uploaded.', 'cartflows' ) )
310 );
311 }
312
313 if ( ! function_exists( 'wp_handle_upload' ) ) {
314 require_once ABSPATH . 'wp-admin/includes/file.php';
315 }
316
317 $file = $_FILES['wcf_checkout_file']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput, WordPress.Security.NonceVerification.Missing
318 $file['name'] = sanitize_file_name( $file['name'] );
319 $file['ext'] = strtolower( pathinfo( $file['name'], PATHINFO_EXTENSION ) );
320
321 $master_allowed = Cartflows_Helper::get_allowed_file_extensions();
322
323 $restrictions = $this->get_field_restrictions();
324
325 $allowed_extensions = empty( $restrictions['extensions'] )
326 ? $master_allowed
327 : array_values( array_intersect( $master_allowed, $restrictions['extensions'] ) );
328
329 if ( ! in_array( $file['ext'], $allowed_extensions, true ) ) {
330 wp_send_json_error( array( 'error' => __( 'File type is not allowed.', 'cartflows' ) ) );
331 }
332
333 if ( (int) $file['size'] > $restrictions['max_size'] ) {
334 wp_send_json_error( array( 'error' => __( 'File size exceeds the allowed limit.', 'cartflows' ) ) );
335 }
336
337 $check = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'], wp_get_mime_types() );
338
339 if ( empty( $check['ext'] ) || $check['ext'] !== $file['ext'] ) {
340 wp_send_json_error( array( 'error' => __( 'Invalid or corrupted file.', 'cartflows' ) ) );
341 }
342
343 $result = $this->move_uploaded_file( $file );
344
345 wp_send_json_success(
346 array(
347 'success' => true,
348 'url' => esc_url_raw( $result['url'] ),
349 'filename' => sanitize_file_name( basename( $result['file'] ) ),
350 )
351 );
352 }
353
354 /**
355 * Retrieve file size and type restrictions from field settings.
356 *
357 * @since 2.2.2
358 *
359 * @return array{max_size:int, extensions:string[]} Field-level upload restrictions.
360 */
361 private function get_field_restrictions() {
362
363 $max_size = 5 * 1024 * 1024; // 5MB default
364 $extensions = array();
365
366 $field_key = empty( $_POST['field_key'] ) ? '' : sanitize_text_field( wp_unslash( $_POST['field_key'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
367 $checkout_id = empty( $_POST['checkout_id'] ) ? 0 : absint( $_POST['checkout_id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
368
369 if ( empty( $field_key ) || empty( $checkout_id ) ) {
370 return compact( 'max_size', 'extensions' );
371 }
372
373 $field_type = ( 0 === strpos( $field_key, 'shipping_' ) ) ? 'shipping' : 'billing';
374 $saved_fields = get_post_meta( $checkout_id, 'wcf_field_order_' . $field_type, true );
375
376 if ( ! is_array( $saved_fields ) || empty( $saved_fields[ $field_key ] ) || ! is_array( $saved_fields[ $field_key ] ) ) {
377 return compact( 'max_size', 'extensions' );
378 }
379
380 $custom_attributes = empty( $saved_fields[ $field_key ]['custom_attributes'] ) || ! is_array( $saved_fields[ $field_key ]['custom_attributes'] )
381 ? array()
382 : $saved_fields[ $field_key ]['custom_attributes'];
383
384 if ( empty( $custom_attributes ) ) {
385 return compact( 'max_size', 'extensions' );
386 }
387
388 if ( ! empty( $custom_attributes['file_size'] ) && is_scalar( $custom_attributes['file_size'] ) ) {
389 $max_size = max( 1, absint( $custom_attributes['file_size'] ) ) * 1024 * 1024;
390 }
391
392 if ( ! empty( $custom_attributes['accepted_file_types'] ) ) {
393 $extensions = $this->normalize_extension_list( $custom_attributes['accepted_file_types'] );
394 }
395
396 return compact( 'max_size', 'extensions' );
397 }
398
399 /**
400 * Normalize accepted file types into a lowercase extension list.
401 *
402 * @since 2.2.2
403 *
404 * @param string $raw Raw accepted file types value.
405 * @return array Normalized extensions.
406 */
407 private function normalize_extension_list( $raw ) {
408
409 if ( ! is_string( $raw ) || '' === trim( $raw ) ) {
410 return array();
411 }
412
413 $extensions = array_map( 'strtolower', array_map( 'trim', explode( ',', $raw ) ) );
414
415 return array_values( array_filter( $extensions ) );
416 }
417
418 /**
419 * Move the uploaded file into the WordPress uploads directory.
420 *
421 * @since 2.2.2
422 *
423 * @param array $file Normalized file data.
424 * @return array Upload result.
425 */
426 private function move_uploaded_file( array $file ) {
427
428 $upload_dir = wp_upload_dir();
429 $file['name'] = wp_unique_filename( $upload_dir['path'], 'wcf_' . wp_generate_uuid4() . '.' . $file['ext'] );
430
431 $result = wp_handle_upload(
432 $file,
433 array(
434 'test_form' => false,
435 'mimes' => wp_get_mime_types(),
436 )
437 );
438
439 if ( isset( $result['error'] ) ) {
440 wp_send_json_error(
441 array( 'error' => esc_html( $result['error'] ) )
442 );
443 }
444
445 return $result;
446 }
447 }
448
449 /**
450 * Kicking this off by calling 'get_instance()' method
451 */
452 Cartflows_Checkout_Ajax::get_instance();
453