PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.13.4
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.13.4
3.13.4 3.13.3 3.13.2 3.13.1 3.13.0 3.12.13 3.12.12 3.12.11 3.12.10 3.12.9 3.12.8 3.12.7 3.12.6 3.12.5 3.12.4 3.12.3 3.12.1 3.12.2 3.12.0 3.11.1 3.11.0 3.10.9 3.10.8 3.10.7 3.10.6 All 262 releases
wpfunnels / includes / utils / class-wpfnl-functions.php

class-wpfnl-functions.php in WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell 3.13.4, at includes/utils/class-wpfnl-functions.php

7,584 lines 264.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * This class is responsible for all the helper functions
4 *
5 * @package WPFunnels
6 */
7 namespace WPFunnels;
8
9 use ElementorPro\Modules\WpCli\Update;
10 use WPFunnels\Metas\Wpfnl_Step_Meta_keys;
11 use WPFunnels\Discount\WpfnlDiscount;
12
13 class Wpfnl_functions {
14
15 public static $installed_plugins;
16
17 /**
18 * Canvas layout used when nothing has been stored yet.
19 *
20 * Installs that predate this default are pinned to `horizontal` once, by
21 * Wpfnl_Activator::maybe_pin_legacy_builder_mode(), so flipping this value
22 * only affects sites that never ran on the old default.
23 *
24 * @since 3.12.13
25 */
26 const DEFAULT_FUNNEL_BUILDER_MODE = 'vertical';
27
28 /**
29 * User meta key holding a single admin's preferred canvas layout.
30 *
31 * Overrides the site-wide default for that user only, so switching views on
32 * the canvas survives a reload without changing anything for their teammates.
33 *
34 * @since 3.12.13
35 */
36 const CANVAS_VIEW_MODE_META_KEY = '_wpfnl_canvas_view_mode';
37
38 /**
39 * Session key holding the ids of the orders this session actually placed.
40 *
41 * Ownership has to be recorded per order. A marker that is not bound to a
42 * specific order authorises every order id a caller cares to submit.
43 *
44 * @since 3.12.14
45 */
46 const OWNED_ORDERS_SESSION_KEY = 'wpfnl_owned_orders';
47
48 /**
49 * Upper bound on the owned-order list carried in a single session.
50 *
51 * @since 3.12.14
52 */
53 const MAX_OWNED_ORDERS = 50;
54
55 /**
56 * Action Scheduler hook that sends the store revenue report email.
57 *
58 * @since 3.14.0
59 */
60 const REVENUE_REPORT_HOOK = 'wpfunnels_send_revenue_report_email';
61
62 /**
63 * Post meta keys that must not travel between sites on funnel export/import.
64 *
65 * These hold per-site render caches or bookkeeping that is only meaningful on the site
66 * that produced them. Carrying them into a new site makes the imported step inherit the
67 * source site's cached state:
68 *
69 * - `_elementor_css` records that a `post-{ID}.css` file was already generated. Elementor
70 * regenerates only when the recorded status is empty (`Base::enqueue()`), and `Post_CSS`
71 * does not override `is_update_required()`, so a stale record makes it enqueue a CSS file
72 * that was never written here — the step renders with no per-post styles at all.
73 * - `_elementor_element_cache` holds pre-rendered element HTML from the source site.
74 * - `_elementor_page_assets` holds the cached widget style handles to enqueue.
75 * - `_edit_lock` / `_edit_last` / `_wp_old_slug` / `_eael_post_view_count` reference users,
76 * locks, and slugs that do not exist on the destination.
77 *
78 * Elementor's own `files_manager->clear_cache()` clears the first three, but only when the
79 * builder post-import pass runs. Excluding them on both sides keeps an import correct even
80 * when Elementor is absent or `allow_url_fopen` is disabled.
81 *
82 * @since 2.5.15
83 */
84 const VOLATILE_TRANSFER_META_KEYS = [
85 '_elementor_css',
86 '_elementor_element_cache',
87 '_elementor_page_assets',
88 '_edit_lock',
89 '_edit_last',
90 '_wp_old_slug',
91 '_eael_post_view_count',
92 ];
93
94 /**
95 * Get all the steps of the funnel
96 *
97 * @param $funnel_id
98 *
99 * @return array|mixed
100 */
101 public static function get_steps($funnel_id) {
102 if (!is_int($funnel_id) && !is_string($funnel_id)) {
103 // Throw an error or handle this case as appropriate for your application
104 throw new \InvalidArgumentException('Funnel ID must be an integer or a string');
105 }
106
107 $steps = get_post_meta($funnel_id, '_steps_order', true);
108 if (!is_array($steps)) {
109 $steps = array();
110 }
111 return $steps;
112 }
113
114
115 /**
116 * Check if the associated order is from a funnel.
117 *
118 * This function checks if the given order is associated with a funnel.
119 * It returns true if the order is from a funnel, and false otherwise.
120 * Previously 'get_funnel_id_from_order' function parameter was order id but from 2.8.0 version it changed to order object
121 *
122 * @param \WC_Order $order The WooCommerce order object.
123 * @return bool True if the order is from a funnel, false otherwise.
124 * @since 2.8.0
125 */
126 public static function check_if_funnel_order( $order ) {
127 if ( false === is_a( $order, 'WC_Order' ) ) {
128 return false;
129 }
130
131 $funnel_id = self::get_funnel_id_from_order( $order );
132 if ( ! $funnel_id ) {
133 return false;
134 }
135 return true;
136 }
137
138
139 /**
140 * Get the associated funnel ID from the order ID.
141 *
142 * This function retrieves the ID of the associated funnel for a given order.
143 * Previously data was retrieved by post meta table but from 2.8.0 version user get_meta function to retrieve meta
144 *
145 * @param \WC_Order|Int $order The WooCommerce order object or Order ID of WooCommerce order.
146 * @return int|false The ID of the associated funnel, or false if not found.
147 * @since 2.8.0
148 */
149 public static function get_funnel_id_from_order( $order ) {
150 if ( ! $order ) {
151 return false;
152 }
153
154 if ( false === is_a( $order, 'WC_Order' ) ) {
155 $order_id = intval( $order );
156 $order = wc_get_order( $order_id );
157 if ( false === is_a( $order, 'WC_Order' ) ) {
158 return false;
159 }
160 }
161
162 $funnel_id = $order->get_meta( '_wpfunnels_funnel_id' ) ? $order->get_meta( '_wpfunnels_funnel_id' ) : $order->get_meta( '_wpfunnels_parent_funnel_id' );
163 if ( ! $funnel_id ) {
164 return false;
165 }
166 return intval( $funnel_id );
167 }
168
169 /**
170 * Check if the given string is a date or not
171 *
172 * @param $date
173 *
174 * @return bool
175 * @since 1.0.0
176 */
177 public static function validate_date( $date ) {
178 return (bool) strtotime( $date );
179 }
180
181 /**
182 * Define constant if it is not set yet
183 *
184 * @param $name
185 * @param $value
186 *
187 * @since 2.0.3
188 */
189 public static function maybe_define_constant( $name, $value ) {
190 if ( ! defined( $name ) ) {
191 define( $name, $value );
192 }
193 }
194
195 /**
196 * Set do not cache constants
197 */
198 public static function do_not_cache() {
199 global $post;
200
201 if ( ! apply_filters( 'wpfunnels/do_not_cache', true, $post->ID ) ) {
202 return;
203 }
204 self::maybe_define_constant( 'DONOTCACHEPAGE', true );
205 self::maybe_define_constant( 'DONOTCACHEOBJECT', true );
206 self::maybe_define_constant( 'DONOTCACHEDB', true );
207 nocache_headers();
208 }
209
210 /**
211 * Return formatted date from the
212 * given date object
213 *
214 * @param $date
215 *
216 * @return false|string
217 * @since 1.0.0
218 */
219 public static function get_formatted_date( $date ) {
220 return date( 'Y-m-d h:i A', strtotime( $date ) );
221 }
222
223
224 /**
225 * Get checkout ID
226 *
227 * @return bool|int
228 */
229 public static function get_checkout_id_from_post_data() {
230 $checkout_id = ! empty( $_POST['_wpfunnels_checkout_id'] ) ? filter_var( wp_unslash( $_POST['_wpfunnels_checkout_id'] ), FILTER_SANITIZE_NUMBER_INT ) : false;
231 return $checkout_id ? intval( $checkout_id ) : false;
232 }
233
234
235 /**
236 * Get checkout id from post
237 *
238 * @return bool|int
239 */
240 public static function get_checkout_id_from_post( $post = null ) {
241 if ( null === $post && ! empty( $_POST ) ) {
242 $post = $_POST;
243 }
244
245 if ( is_array( $post ) ) {
246 if ( ! empty( $post['_wpfunnels_checkout_id'] ) ) {
247 $checkout_id = filter_var( wp_unslash( $post['_wpfunnels_checkout_id'] ), FILTER_SANITIZE_NUMBER_INT );
248 if ( $checkout_id ) {
249 return intval( $checkout_id );
250 }
251 }
252 if ( ! empty( $post['wpfnl_step_id'] ) ) {
253 $checkout_id = self::get_posted_non_offer_step_id( wp_unslash( $post['wpfnl_step_id'] ) );
254 if ( $checkout_id ) {
255 return $checkout_id;
256 }
257 }
258 if ( ! empty( $post['step_id'] ) ) {
259 $checkout_id = self::get_posted_non_offer_step_id( wp_unslash( $post['step_id'] ) );
260 if ( $checkout_id ) {
261 return $checkout_id;
262 }
263 }
264
265 if ( isset( $post['post_data'] ) ) {
266 $post_data = array();
267 wp_parse_str( wp_unslash( $post['post_data'] ), $post_data );
268 if ( ! empty( $post_data['_wpfunnels_checkout_id'] ) ) {
269 $checkout_id = filter_var( $post_data['_wpfunnels_checkout_id'], FILTER_SANITIZE_NUMBER_INT );
270 if ( $checkout_id ) {
271 return intval( $checkout_id );
272 }
273 }
274 if ( ! empty( $post_data['wpfnl_step_id'] ) ) {
275 $checkout_id = self::get_posted_non_offer_step_id( $post_data['wpfnl_step_id'] );
276 if ( $checkout_id ) {
277 return $checkout_id;
278 }
279 }
280 if ( ! empty( $post_data['step_id'] ) ) {
281 $checkout_id = self::get_posted_non_offer_step_id( $post_data['step_id'] );
282 if ( $checkout_id ) {
283 return $checkout_id;
284 }
285 }
286 }
287 }
288
289 return false;
290 }
291
292
293 /**
294 * Get a posted generic step id, unless it belongs to an offer step.
295 *
296 * Upsell/downsell requests post `step_id` too (accept/reject, gateway calls,
297 * the offer fallback payment form), but they are not checkout requests.
298 * Resolving them marks the request as a funnel step page, and released Pro
299 * versions then reject it with "Your session is expired".
300 *
301 * @param mixed $value Posted step id.
302 *
303 * @return bool|int
304 * @since 3.13.2
305 */
306 private static function get_posted_non_offer_step_id( $value ) {
307 $step_id = intval( filter_var( $value, FILTER_SANITIZE_NUMBER_INT ) );
308 if ( $step_id <= 0 ) {
309 return false;
310 }
311
312 $step_type = get_post_meta( $step_id, '_step_type', true );
313 if ( in_array( $step_type, array( 'upsell', 'downsell' ), true ) ) {
314 return false;
315 }
316
317 return $step_id;
318 }
319
320
321 /**
322 * Check order bump clicked or not from post data
323 *
324 * @return bool True|False
325 * @since 2.8.0
326 */
327 public static function is_orderbump_clicked_from_post_data() {
328 if ( ! empty( $_POST['post_data'] ) ) {
329 wp_parse_str( $_POST['post_data'], $post_data );
330 return ! empty( $post_data['_wpfunnels_order_bump_clicked'] ) && 'yes' === $post_data['_wpfunnels_order_bump_clicked'];
331 }
332 return ! empty( $_POST['_wpfunnels_order_bump_clicked'] ) && 'yes' === $_POST['_wpfunnels_order_bump_clicked'];
333 }
334
335
336 /**
337 * Check checkoutify order bump clicked or not from post data
338 *
339 * @return bool True|False
340 * @since 2.8.0
341 */
342 public static function is_checkoutify_orderbump_clicked_from_post_data() {
343 if ( ! empty( $_POST['post_data'] ) ) {
344 wp_parse_str( $_POST['post_data'], $post_data );
345 return ! empty( $post_data['checkoutify_orderbump_clicked'] ) && 'yes' === $post_data['checkoutify_orderbump_clicked'];
346 }
347 return ! empty( $_POST['checkoutify_orderbump_clicked'] ) && 'yes' === $_POST['checkoutify_orderbump_clicked'];
348 }
349
350
351 /**
352 * Check qunatity select or not from post data
353 *
354 * @return bool True|False
355 * @since 2.8.0
356 */
357 public static function maybe_select_quantity_from_post_data() {
358 if ( ! empty( $_POST['post_data'] ) ) {
359 $post_data = array();
360 wp_parse_str( $_POST['post_data'], $post_data );
361 return ! empty( $post_data['_wpfunnels_select_quantity'] ) && 'yes' === $post_data['_wpfunnels_select_quantity'];
362 }
363 return ! empty( $_POST['_wpfunnels_select_quantity'] ) && 'yes' === $_POST['_wpfunnels_select_quantity'];
364 }
365
366
367
368 /**
369 * Check variation is selected or not from post data
370 *
371 * @return bool True|False
372 * @since 2.8.0
373 */
374 public static function is_variation_selected_from_post_data() {
375 if ( ! empty( $_POST['post_data'] ) ) {
376 wp_parse_str( $_POST['post_data'], $post_data );
377 return ! empty( $post_data['_wpfunnels_variable_product'] ) && 'selected' === $post_data['_wpfunnels_variable_product'];
378 }
379 return ! empty( $_POST['_wpfunnels_variable_product'] ) && 'selected' === $_POST['_wpfunnels_variable_product'];
380 }
381
382
383 /**
384 * Check if the customer has already accepted an order bump this session,
385 * via the `wpfnl_order_bump` cookie set on accept/reject. Used to keep a
386 * plain page reload from wiping an accepted bump back out of the cart.
387 *
388 * @return bool
389 * @since 3.13.1
390 */
391 public static function is_order_bump_already_accepted_from_cookie() {
392 if ( empty( $_COOKIE['wpfnl_order_bump'] ) ) {
393 return false;
394 }
395 $cookie = json_decode( wp_unslash( $_COOKIE['wpfnl_order_bump'] ), true );
396 return ! empty( $cookie['order_bump_accepted'] ) && 'yes' === $cookie['order_bump_accepted'];
397 }
398
399
400 /**
401 * Check product option is selected or not from post data
402 *
403 * @return bool True|False
404 * @since 2.8.0
405 */
406 public static function is_product_option_from_post_data() {
407 if ( ! empty( $_POST['post_data'] ) ) {
408 wp_parse_str( $_POST['post_data'], $post_data );
409 return ! empty( $post_data['_wpfunnels_product_option'] ) && 'selected' === $post_data['_wpfunnels_product_option'];
410 }
411 return ! empty( $_POST['_wpfunnels_product_option'] ) && 'selected' === $_POST['_wpfunnels_product_option'];
412 }
413
414
415 /**
416 * Check variation is selected or not from post data
417 *
418 * @return bool True|False
419 * @since 2.8.0
420 */
421 public static function is_coupon_applied_from_post_data() {
422 if ( ! empty( $_POST['post_data'] ) ) {
423 wp_parse_str( $_POST['post_data'], $post_data );
424 return ! empty( $post_data['_wpfunnels_auto_coupon'] ) && 'yes' === $post_data['_wpfunnels_auto_coupon'];
425 }
426 return ! empty( $_POST['_wpfunnels_auto_coupon'] ) && 'yes' === $_POST['_wpfunnels_auto_coupon'];
427 }
428
429
430 /**
431 * Get funnel id
432 *
433 * @param $step_id
434 *
435 * @return mixed
436 */
437 public static function get_funnel_id_from_step( $step_id ) {
438 $funnel_id = get_post_meta( $step_id, '_funnel_id', true );
439 return intval( $funnel_id );
440 }
441
442 /**
443 * Get order id from funnel post data
444 *
445 * @return false|int
446 * @since 2.8.2
447 */
448 public static function get_order_id_from_post_data() {
449 if ( ! empty( $_POST['_wpfunnels_order_unique_identifier'] ) ) {
450 global $wpdb;
451 $identifier = wp_unslash( $_POST['_wpfunnels_order_unique_identifier'] );
452 $prepare_guery = $wpdb->prepare( "SELECT `post_id` FROM {$wpdb->postmeta} WHERE meta_key ='_wpfunnels_order_unique_identifier' and meta_value like '%s'", $identifier );
453 $get_value = $wpdb->get_row( $prepare_guery );
454 return $get_value ? intval( $get_value->post_id ) : false;
455 }
456 return false;
457 }
458
459
460 /**
461 * Get checkout id from order
462 *
463 * @param $order_id
464 *
465 * @return int
466 */
467 public static function get_checkout_id_from_order( $order_id ) {
468 if ( !self::is_wc_active() ) {
469 return false;
470 }
471
472 $order = wc_get_order( $order_id );
473
474 if ( false === is_a( $order, 'WC_Order' ) ) {
475 return false;
476 }
477
478 $checkout_id = $order->get_meta( '_wpfunnels_checkout_id' );
479 return $checkout_id ? intval( $checkout_id ) : false;
480 }
481
482
483 /**
484 * Get funnel from post data
485 *
486 * @return bool|int
487 */
488 public static function get_funnel_id_from_post_data() {
489 if ( ! empty( $_POST['_wpfunnels_funnel_id'] ) ) {
490 $funnel_id = filter_var( wp_unslash( $_POST['_wpfunnels_funnel_id'] ), FILTER_SANITIZE_NUMBER_INT );
491 return $funnel_id ? intval( $funnel_id ) : false;
492 }
493 return false;
494 }
495
496
497 /**
498 * Get funnel id from step page
499 *
500 * @return false|mixed
501 */
502 public static function get_funnel_id() {
503 global $post;
504 if ( $post ) {
505 $funnel_id = get_post_meta( $post->ID, '_funnel_id', true );
506 return $funnel_id ? intval( $funnel_id ) : false;
507 }
508 return false;
509 }
510
511 /**
512 * Unserialize data
513 *
514 * @param $data
515 *
516 * @return mixed
517 * @since 1.0.0
518 */
519 public static function unserialize_array_data( $data ) {
520 return maybe_unserialize( $data );
521 }
522
523
524 /**
525 * Get formatted string with phrase
526 * e.g: 1 item if singular or
527 * 2 items if plural
528 *
529 * @param Int $number
530 * @param String $singular
531 * @param String $plural
532 *
533 * @return String
534 * @since 1.0.0
535 */
536 public static function get_formatted_data_with_phrase( $number, $singular = '', $plural = 's' ) {
537 return ( is_numeric( $number ) && 0 <= $number ) ? ( 1 < $number ? $plural : $singular ) : null;
538 }
539
540
541 /**
542 * Get formatted string from funnel status
543 *
544 * @param $status
545 *
546 * @return string
547 * @since 1.0.0
548 */
549 public static function get_formatted_status( $status ) {
550 switch ( $status ) {
551 case 'publish':
552 return 'Published';
553 case 'draft':
554 return 'Draft';
555 default:
556 return $status;
557 }
558 }
559
560
561 /**
562 * Generate active class for funnel menus
563 *
564 * @param $key
565 *
566 * @return bool
567 * @since 1.0.0
568 */
569 public static function define_active_class($key)
570 {
571 $page = filter_input(INPUT_GET, 'page', FILTER_SANITIZE_SPECIAL_CHARS);
572 if ($page === WPFNL_FUNNEL_PAGE_SLUG && $key === 'overview') {
573 return true;
574 }
575
576 if ( $page === 'create_funnel' && $key === 'create_funnel' ) {
577 return true;
578 }
579
580 if ( $page === 'wpfnl_automations' && $key === 'automations' ) {
581 return true;
582 }
583
584 if ( $page === 'wpfnl_settings' && $key === 'settings' ) {
585 return true;
586 }
587
588 if ( $page === WPFNL_ADDONS_SLUG && $key === 'addons' ) {
589 return true;
590 }
591
592 // The legacy page; the React `#/integrations` route never renders this nav.
593 if ( $page === WPFNL_INTEGRATIONS_MAIN_PAGE_SLUG && $key === 'integrations' ) {
594 return true;
595 }
596
597 if ( $page === WPFNL_TEMPLATE_PAGE_SLUG && $key === 'templates' ) {
598 return true;
599 }
600
601 if ( $page === WPFNL_STORE_CHECKOUT_SLUG && $key === 'store_checkout' ) {
602 return true;
603 }
604
605 if ( defined( 'WPFNL_AI_FUNNEL_PAGE_SLUG' ) && $page === WPFNL_AI_FUNNEL_PAGE_SLUG && $key === 'ai_assistant' ) {
606 return true;
607 }
608
609 return false;
610 }
611
612
613 /**
614 * Check If module exists or not
615 *
616 * @param $id
617 *
618 * @return bool
619 * @since 1.0.0
620 */
621 public static function check_if_module_exists( $id ) {
622 return 'publish' === get_post_status( $id ) || 'draft' === get_post_status( $id ) || 'trash' === get_post_status( $id );
623 }
624
625
626 /**
627 * Check if the cpt is step or not
628 *
629 * @param $step_id
630 * @param string $type
631 *
632 * @return bool
633 */
634 public static function check_if_this_is_step_type_by_id( $step_id, $type = 'landing' ) {
635 $post_type = get_post_type( $step_id );
636 if ( WPFNL_STEPS_POST_TYPE === $post_type ) {
637 if ( $type === get_post_meta( $step_id, '_step_type', true ) ) {
638 return true;
639 }
640 }
641 return false;
642 }
643
644
645 /**
646 * Get thankyou page id
647 *
648 * @param $funnel_id
649 *
650 * @return bool|mixed
651 *
652 * @since 2.2.6
653 */
654 public static function get_thankyou_page_id( $funnel_id ) {
655 $steps = self::get_steps( $funnel_id );
656 if ( is_array( $steps ) ) {
657 foreach ( $steps as $step ) {
658 if ( 'thankyou' === $step['step_type'] ) {
659 return $step['id'];
660 }
661 }
662 }
663 return false;
664 }
665
666
667 /**
668 * Check if the current post type is step or not
669 *
670 * @return bool
671 * @since 2.0.3
672 */
673 public static function is_funnel_step_page( $post_type = '' ) {
674 if ( self::get_current_post_type( $post_type ) === WPFNL_STEPS_POST_TYPE ) {
675 return true;
676 }
677
678 $post_id = null;
679 if ( wp_doing_ajax() || isset( $_GET['wc-ajax'] ) ) {
680 $post_id = self::get_checkout_id_from_post( $_POST );
681 }
682 global $post;
683
684 if ( ! $post_id ) {
685 $post_id = get_the_ID();
686 }
687 $post = $post_id ? get_post( $post_id ) : $post;
688
689 $post_type = $post->post_type ?? '';
690 if ( self::get_current_post_type( $post_type ) === WPFNL_STEPS_POST_TYPE ) {
691 return true;
692 }
693
694 return false;
695 }
696
697
698 /**
699 * Get current post type
700 *
701 * @param $post_type
702 *
703 * @return string
704 *
705 * @since 2.3.5
706 */
707 public static function get_current_post_type( $post_type ) {
708 global $post;
709 if ( '' === $post_type && is_object( $post ) ) {
710 $post_type = $post->post_type;
711 } else {
712 $post_id = '';
713 if ( wp_doing_ajax() || isset( $_GET['wc-ajax'] ) ) {
714 $post_id = self::get_checkout_id_from_post( $_POST );
715 }
716
717 if ( $post_id ) {
718 $post = get_post( $post_id );
719 $post_type = $post->post_type;
720 }
721 }
722 return $post_type;
723 }
724
725
726 /**
727 * Check if funnel checkout page
728 *
729 * @param $funnel_id
730 *
731 * @return bool
732 */
733 public static function is_funnel_checkout_page() {
734 if ( isset( $_POST['post_data'] ) ) {
735 parse_str( $_POST['post_data'], $post_data );
736 if ( isset( $post_data['_wpfunnels_checkout_id'] ) ) {
737 return array(
738 'status' => true,
739 'id' => $post_data['_wpfunnels_checkout_id'],
740 );
741 }
742 }
743
744 // Some ajax actions (e.g. the order bump accept/reject handler) post the
745 // checkout step id directly as `step_id` instead of a serialized `post_data`
746 // form. Fall back to that so quantity/checkout-id dependent filters still
747 // resolve the correct step during those requests.
748 if ( ! empty( $_POST['step_id'] ) ) {
749 $step_id = filter_var( wp_unslash( $_POST['step_id'] ), FILTER_SANITIZE_NUMBER_INT );
750 if ( $step_id && self::check_if_this_is_step_type_by_id( intval( $step_id ), 'checkout' ) ) {
751 return array(
752 'status' => true,
753 'id' => intval( $step_id ),
754 );
755 }
756 }
757
758 return array(
759 'status' => false,
760 'id' => '',
761 );
762 }
763
764
765 /**
766 * Check if funnel exists
767 *
768 * @param $funnel_id
769 *
770 * @return bool
771 */
772 public static function is_funnel_exists( $funnel_id ) {
773 if (!is_int($funnel_id) && !is_string($funnel_id)) {
774 // Throw an error or handle this case as appropriate for your application
775 throw new \InvalidArgumentException('Funnel ID must be an integer or a string');
776 }
777
778 if ( ! $funnel_id ) {
779 return false;
780 }
781
782 if ( false === get_post_status( $funnel_id ) ) {
783 return true;
784 }
785 return false;
786 }
787
788
789 /**
790 * Function to check if the current page is a post edit page
791 *
792 * @return bool
793 *
794 * @since 2.0.3
795 */
796 public static function is_step_edit_page() {
797
798 $step_id = -1;
799 if ( is_admin() && isset( $_REQUEST['action'] ) ) {
800 if ( 'edit' === $_REQUEST['action'] && isset( $_GET['post'] ) ) {
801 $step_id = isset( $_GET['post'] ) ? $_GET['post'] : - 1;
802 } elseif ( isset( $_REQUEST['wpfunnels_gb'] ) && isset( $_POST['post_id'] ) ){ //phpcs:ignore
803 $step_id = intval( $_POST['post_id'] ); //phpcs:ignore
804 }
805 if ( $step_id === - 1 ) {
806
807 return false;
808 }
809 $get_post_type = get_post_type( $step_id );
810 if ( WPFNL_STEPS_POST_TYPE === $get_post_type ) {
811 return true;
812 }
813 }
814 return false;
815 }
816
817
818 /**
819 * Check if the cpt is step or not
820 *
821 * @param string $type
822 *
823 * @return bool
824 * @since 1.0.0
825 */
826 public static function check_if_this_is_step_type( $type = 'landing' ) {
827 $post_type = get_post_type();
828
829 if ( WPFNL_STEPS_POST_TYPE === $post_type ) {
830 global $post;
831 if ( $type === get_post_meta( $post->ID, '_step_type', true ) ) {
832 return true;
833 }
834 }
835 return false;
836 }
837
838
839 /**
840 * Check if the cpt is step or not
841 *
842 * @param string $type
843 *
844 * @return bool
845 * @since 1.0.0
846 */
847 public static function check_if_this_is_lms_step() {
848 global $post;
849 if( ! $post ) {
850 return false;
851 }
852 $funnel_id = get_post_meta( $post->ID, '_funnel_id', true );
853
854 if (! $funnel_id ) {
855 return false;
856 }
857
858 $funnel_type = get_post_meta( $funnel_id, '_wpfnl_funnel_type', true );
859
860 return $funnel_type === 'lms';
861 }
862
863
864 /**
865 * Hooks for start and end the journey
866 * of a funnel
867 */
868 public static function start_journey() {
869 $post_type = get_post_type();
870 if ( WPFNL_STEPS_POST_TYPE === $post_type ) {
871 global $post;
872 $step_id = $post->ID;
873 $funnel_id = self::get_funnel_id_from_step( $step_id );
874 $steps = self::get_steps( $funnel_id );
875 $funnel_data = self::get_funnel_data( $funnel_id );
876 // start the journey
877 if ( $steps && is_array( $steps ) ) {
878 if ( $steps[0]['id'] === $step_id ) {
879 do_action( 'wpfunnels/funnel_journey_starts', $step_id, $funnel_id );
880 }
881 }
882
883 // end the journey
884 if ( $funnel_data && isset( $funnel_data['drawflow']['Home']['data'] ) ) {
885 $funnel_data = $funnel_data['drawflow']['Home']['data'];
886 foreach ( $funnel_data as $data ) {
887 $info = $data['data'];
888 $step_type = $info['step_type'];
889
890 if ( 'conditional' !== $step_type ) {
891 $_step_id = isset( $info['step_id'] ) ? $info['step_id'] : '';
892 $output_con = $data['outputs'];
893
894 if ( empty( $output_con ) ) {
895 if ( $_step_id === $step_id ) {
896 do_action( 'wpfunnels/funnel_journey_end', $step_id, $funnel_id );
897 delete_option( 'optin_data' );
898 break;
899 }
900 }
901 }
902 }
903 }
904 }
905 }
906
907
908 /**
909 * Conditional node logic check
910 *
911 * @param $funnel_id
912 * @param $order
913 * @param $condition_identifier
914 * @param $current_page_id
915 * @param $checker
916 *
917 * @return bool
918 *
919 * @since 2.0.2
920 */
921 public static function check_condition( $funnel_id, $order, $condition_identifier, $current_page_id, $checker = 'accept' ) {
922 $group_conditions = get_post_meta( $funnel_id, $condition_identifier, true );
923
924 if ( $group_conditions ) {
925 // Loop through group condition.
926 foreach ( $group_conditions as $group ) {
927 if ( empty( $group ) ) {
928 continue;
929 }
930
931 $match_group = true;
932 // Loop over rules and determine if all rules match.
933 foreach ( $group as $rule ) {
934 if ( ! self::match_rule( $funnel_id, $rule, $order, $current_page_id, $checker ) ) {
935 $match_group = false;
936 break;
937 }
938 }
939
940 // If this group matches, show the field group.
941 if ( $match_group ) {
942 return true;
943 } else {
944 return false;
945 }
946 }
947 }
948 if ( $checker == 'accept' ) {
949 return true;
950 }
951 // Return default.
952 return false;
953 }
954
955
956 /**
957 * Check if rule is matched
958 *
959 * @param $rule
960 * @param $order
961 * @param $current_page_id
962 * @param $checker
963 *
964 * @return mixed
965 *
966 * @since 2.0.2
967 */
968 public static function match_rule( $funnel_id, $rule, $order, $current_page_id, $checker ) {
969 if ( isset( $rule['field'] ) && $rule['field'] == 'downsell' ) {
970 $rule['field'] = 'upsell';
971 }
972 $condition = $rule['field'];
973 if ( strpos( $rule['field'], 'optin' ) ) {
974 $condition = 'optin';
975 }
976 $checker_function = $condition . '_condition_checker';
977 return self::$checker_function( $funnel_id, $rule, $order, $current_page_id, $checker );
978 }
979
980
981 /**
982 * Upsell condition checker for conditional step
983 *
984 * @param Integer $funnel_id
985 * @param Array $data
986 * @param Object $order
987 * @param Integer $current_page_id
988 * @param String $checker
989 *
990 * @return Boolean
991 *
992 * @since 2.5.7
993 */
994 public static function upsell_condition_checker( $funnel_id, $data, $order, $current_page_id, $checker ) {
995
996 if ( $data['value'] == 'yes' ) {
997 if ( $checker == 'accept' ) {
998 return true;
999 } else {
1000 return false;
1001 }
1002 } elseif ( $data['value'] == 'no' ) {
1003
1004 if ( $checker == 'reject' ) {
1005 return true;
1006 } else {
1007 return false;
1008 }
1009 }
1010 return false;
1011 }
1012
1013
1014 /**
1015 * Optin condition checker for conditional step
1016 *
1017 * @param Integer $funnel_id
1018 * @param Array $data
1019 * @param Object $order
1020 * @param Integer $current_page_id
1021 * @param String $checker
1022 *
1023 * @return Boolean
1024 *
1025 * @since 2.5.7
1026 */
1027 public function optin_condition_checker( $funnel_id, $rule, $data, $current_page_id, $checker = 'accept' ) {
1028
1029 if ( isset( $data['step_id'] ) ) {
1030 $step_id = str_replace( 'optin_', '', $data['step_id'] );
1031 $optin_steps = get_option( 'optin_data' );
1032 if ( isset( $optin_steps['optin_data'] ) ) {
1033 $key = array_search( $step_id, array_column( $optin_steps['optin_data'], 'step_id' ) );
1034 if ( false !== $key ) {
1035 unset( $optin_steps[ $key ] );
1036 update_option( 'optin_data', $optin_steps );
1037 return true;
1038 }
1039 }
1040 } elseif ( ! empty( $data['cta'] ) ) {
1041 return false;
1042 }
1043
1044 if ( isset( $rule['field'] ) ) {
1045 $step_id = str_replace( 'optin_', '', $rule['field'] );
1046 $optin_steps = get_option( 'optin_data' );
1047 if ( isset( $optin_steps['optin_data'] ) ) {
1048 $key = array_search( $step_id, array_column( $optin_steps['optin_data'], 'step_id' ) );
1049 if ( false !== $key ) {
1050 unset( $optin_steps[ $key ] );
1051 update_option( 'optin_data', $optin_steps );
1052 return true;
1053 }
1054 }
1055 }
1056
1057 return false;
1058 }
1059
1060
1061 /**
1062 * Order bump condition checker
1063 *
1064 * @param $data
1065 * @param $order
1066 * @param $current_page_id
1067 *
1068 * @return bool
1069 */
1070 public static function orderbump_condition_checker( $funnel_id, $data, $order, $current_page_id, $checker = 'accept' ) {
1071 $order_bump_accepted = '';
1072 if ( self::is_wc_active() ) {
1073 $order_bump_accepted = WC()->session->get( 'order_bump_accepted' );
1074 }
1075 return $data['value'] == $order_bump_accepted;
1076 }
1077
1078
1079 /**
1080 * Check cart total condition in conditional node
1081 *
1082 * @param $data
1083 * @param $order
1084 * @param $current_page_id
1085 *
1086 * @return bool
1087 */
1088 public static function carttotal_condition_checker( $funnel_id, $data, $order, $current_page_id, $checker = 'accept' ) {
1089 $cart_total = $order->get_total();
1090
1091 $checker = false;
1092 if ( $data['condition'] == 'greater' ) {
1093 if ( $cart_total > $data['value'] ) {
1094 $checker = true;
1095 }
1096 } elseif ( $data['condition'] == 'equal' ) {
1097 if ( $cart_total == $data['value'] ) {
1098 $checker = true;
1099 }
1100 } elseif ( $data['condition'] == 'less' ) {
1101 if ( $cart_total < $data['value'] ) {
1102 $checker = true;
1103 }
1104 }
1105 return $checker;
1106 }
1107
1108
1109 /**
1110 * Billing country condition checker
1111 *
1112 * @param Array
1113 * @param Object
1114 * @param String
1115 * @param String
1116 *
1117 * @return Bool
1118 * @since 2.4.18
1119 */
1120 public static function billing_condition_checker( $funnel_id, $data, $order, $current_page_id, $checker ) {
1121 if ( $order ) {
1122 $billing_country = $order->get_billing_country();
1123 if ( isset( $data['field'] ) && 'billing' === $data['field'] && isset( $data['value'] ) && $billing_country === $data['value'] ) {
1124 return true;
1125 }
1126 }
1127 return false;
1128 }
1129
1130
1131 /**
1132 * Shipping country condition checker
1133 *
1134 * @param Array
1135 * @param Object
1136 * @param String
1137 * @param String
1138 *
1139 * @return Bool
1140 * @since 2.4.18
1141 */
1142 public static function shipping_condition_checker( $funnel_id, $data, $order, $current_page_id, $checker ) {
1143 if ( $order ) {
1144 $shipping_country = $order->get_shipping_country();
1145 if ( isset( $data['field'] ) && 'shipping' === $data['field'] && isset( $data['value'] ) && $shipping_country === isset( $data['value'] ) ) {
1146 return true;
1147 }
1148 }
1149 return false;
1150 }
1151
1152
1153 /**
1154 * Get next step of the
1155 * funnel
1156 *
1157 * @param $funnel_id
1158 * @param $step_id
1159 * @param bool $condition
1160 *
1161 * @return Mix
1162 *
1163 * @since 1.0.0
1164 */
1165 public static function get_next_step( $funnel_id, $step_id, $condition = true ) {
1166 if ( ! $funnel_id || ! $step_id ) {
1167 return false;
1168 }
1169 $parent_step_id = get_post_meta( $step_id, '_parent_step_id', true );
1170 if ( $parent_step_id ) {
1171 $step_id = $parent_step_id;
1172 }
1173
1174 $funnel_data = self::get_funnel_data( $funnel_id );
1175
1176 if ( ! $funnel_data || ! isset( $funnel_data['drawflow']['Home']['data'] ) ) {
1177 return false;
1178 }
1179
1180 $node_id = self::get_node_id( $funnel_id, $step_id );
1181 $node_data = $funnel_data['drawflow']['Home']['data'];
1182
1183 if ( ! is_array( $node_data ) || empty( $node_data ) ) {
1184 return false;
1185 }
1186
1187 $next_node_id = '';
1188
1189 foreach ( $node_data as $node_key => $node_value ) {
1190 if ( $node_value['id'] != $node_id ) {
1191 continue;
1192 }
1193
1194 if ( $condition ) {
1195 $next_node_id = isset( $node_value['outputs']['output_1']['connections'][0]['node'] ) ? $node_value['outputs']['output_1']['connections'][0]['node'] : '';
1196 } else {
1197 $next_node_id = isset( $node_value['outputs']['output_2']['connections'][0]['node'] ) ? $node_value['outputs']['output_2']['connections'][0]['node'] : '';
1198 }
1199
1200 if ( ! $next_node_id ) {
1201 self::redirect_to_deafult_thankyou();
1202 }
1203
1204 $next_step_id = self::get_step_by_node( $funnel_id, $next_node_id );
1205 $next_step_type = self::get_node_type( $node_data, $next_node_id );
1206
1207 return apply_filters(
1208 'wpfunnels/next_step_data',
1209 array(
1210 'step_id' => $next_step_id,
1211 'step_type' => $next_step_type,
1212 )
1213 );
1214
1215 }
1216
1217 return false;
1218 }
1219
1220
1221 /**
1222 * Redirect deafult thank you page
1223 */
1224 public static function redirect_to_deafult_thankyou() {
1225
1226 if ( isset( $_POST['order_id'] ) && $_POST['order_id'] ) {
1227 $url = home_url() . '/checkout/order-received/' . $_POST['order_id'] . '/?key=' . $_POST['order_key'];
1228 return $url;
1229 } else {
1230 add_action(
1231 'template_redirect',
1232 function( $order_id ) {
1233 if ( isset( $_GET['order-received'] ) && $_GET['order-received'] ) {
1234 $url = '';
1235 $url = add_query_arg( 'id', $_GET['order-received'], $url );
1236 wp_safe_redirect( wp_sanitize_redirect( esc_url_raw( $url ) ) );
1237 exit;
1238 } elseif ( isset( $_POST['order_id'] ) && $_POST['order_id'] ) {
1239 $url = '';
1240 $url = add_query_arg( 'id', $_POST['order_id'], $url );
1241 wp_safe_redirect( wp_sanitize_redirect( esc_url_raw( $url ) ) );
1242 exit;
1243 }
1244 }
1245 );
1246 }
1247 }
1248
1249
1250 /**
1251 * Get previous step of the
1252 * funnel
1253 *
1254 * @param $funnel_id
1255 * @param $step_id
1256 * @param bool $condition
1257 *
1258 * @return Mix
1259 *
1260 * @since 1.0.0
1261 */
1262 public static function get_prev_step( $funnel_id, $step_id, $condition = true ) {
1263 if ( $funnel_id && ! $step_id ) {
1264 return false;
1265 }
1266 $funnel_data = self::get_funnel_data( $funnel_id );
1267 if ( $funnel_data ) {
1268 $node_id = self::get_node_id( $funnel_id, $step_id );
1269 $node_data = $funnel_data['drawflow']['Home']['data'];
1270
1271 foreach ( $node_data as $node_key => $node_value ) {
1272 if ( $node_value['id'] == $node_id ) {
1273 if ( $condition ) {
1274 if ( ! empty( $node_value['inputs'] ) ) {
1275 $prev_node_id = $node_value['inputs']['input_1']['connections'][0]['node'];
1276 } else {
1277 $prev_node_id = '';
1278 }
1279 } else {
1280 if ( ! empty( $node_value['inputs'] ) ) {
1281 $prev_node_id = $node_value['inputs']['input_2']['connections'][0]['node'];
1282 } else {
1283 $prev_node_id = '';
1284 }
1285 }
1286 $prev_step_id = self::get_step_by_node( $funnel_id, $prev_node_id );
1287 $prev_step_type = self::get_node_type( $node_data, $prev_node_id );
1288 if ( $prev_step_type == 'conditional' ) {
1289 return self::get_prev_step( $funnel_id, $prev_step_id );
1290
1291 } else {
1292
1293 return array(
1294 'step_id' => $prev_step_id,
1295 'step_type' => $prev_step_type,
1296 );
1297 }
1298 }
1299 }
1300 }
1301 return false;
1302 }
1303
1304 /**
1305 * Get node type
1306 */
1307 public static function get_node_type( $node_data, $node_id ) {
1308 foreach ( $node_data as $node_key => $node_value ) {
1309 if ( $node_value['id'] == $node_id ) {
1310 return $node_value['data']['step_type'];
1311 }
1312 }
1313 }
1314
1315 /**
1316 * Get step by node
1317 */
1318 public static function get_step_by_node( $funnel_id, $node_id ) {
1319 if ( ! $funnel_id && ! $node_id ) {
1320 return false;
1321 }
1322
1323 $funnel_data = self::get_funnel_data( $funnel_id );
1324
1325 if ( ! $funnel_data || ! isset( $funnel_data['drawflow']['Home']['data'] ) || ! is_array( $funnel_data['drawflow']['Home']['data'] ) ) {
1326 return false;
1327 }
1328
1329 foreach ( $funnel_data['drawflow']['Home']['data'] as $key => $data ) {
1330 if ( isset( $data['id'] ) && isset( $data['data']['step_id'] ) && $node_id == $data['id'] ) {
1331 return $data['data']['step_id'];
1332 }
1333 }
1334 return false;
1335 }
1336
1337
1338 /**
1339 * Get node by step
1340 *
1341 * @param $funnel_id
1342 * @param $step_id
1343 *
1344 * @return bool|int|string
1345 */
1346 public static function get_node_id( $funnel_id, $step_id ) {
1347 if ( ! $funnel_id && ! $step_id ) {
1348 return false;
1349 }
1350
1351 $funnel_data = self::get_funnel_data( $funnel_id );
1352
1353 if ( ! $funnel_data || ! isset( $funnel_data['drawflow']['Home']['data'] ) || ! is_array( $funnel_data['drawflow']['Home']['data'] ) ) {
1354 return false;
1355 }
1356
1357 foreach ( $funnel_data['drawflow']['Home']['data'] as $key => $data ) {
1358 if ( isset( $data['id'] ) && isset( $data['data']['step_id'] ) && $step_id == $data['data']['step_id'] ) {
1359 return $data['id'];
1360 }
1361 }
1362 return false;
1363 }
1364
1365
1366 /**
1367 * Get funnel data
1368 *
1369 * @param $funnel_id
1370 *
1371 * @return mixed
1372 *
1373 * @since 2.0.5
1374 */
1375 public static function get_funnel_data( $funnel_id ) {
1376 static $cache = [];
1377 if ( ! array_key_exists( $funnel_id, $cache ) ) {
1378 $cache[ $funnel_id ] = get_post_meta( $funnel_id, '_funnel_data', true );
1379 }
1380 return $cache[ $funnel_id ];
1381 }
1382
1383
1384 /**
1385 * Update settings option
1386 *
1387 * @param $key
1388 * @param $value
1389 * @param bool $network
1390 *
1391 * @since 1.0.0
1392 */
1393 public static function update_admin_settings( $key, $value, $network = false ) {
1394 if ( $network && is_multisite() ) {
1395 update_site_option( $key, $value );
1396 } else {
1397 update_option( $key, $value );
1398 }
1399 }
1400
1401
1402 /**
1403 * Get admin settings option
1404 * by key
1405 *
1406 * @param $key
1407 * @param bool $default
1408 * @param bool $network
1409 *
1410 * @return mixed|void
1411 * @since 1.0.0
1412 */
1413 public static function get_admin_settings( $key, $default = false, $network = false ) {
1414 if ( $network && is_multisite() ) {
1415 $value = get_site_option( $key, $default );
1416 } else {
1417 $value = get_option( $key, $default );
1418 }
1419 return $value;
1420 }
1421
1422
1423 /**
1424 * Get general settings data
1425 *
1426 * @return array
1427 * @since 1.0.0
1428 */
1429 public static function get_general_settings() {
1430 $default_settings = apply_filters(
1431 'wpfunnels_general_settings',
1432 array(
1433 'builder' => 'gutenberg',
1434 'paypal_reference' => 'off',
1435 'order_bump' => 'off',
1436 'ab_testing' => 'off',
1437 'allow_funnels' => array( 'administrator' => 'true' ),
1438 'create_child_order' => 'off',
1439 'disable_theme_style' => 'off',
1440 'enable_log_status' => 'off',
1441 'enable_skip_cart' => 'off',
1442 'skip_cart_for' => 'whole',
1443 'uninstall_cleanup' => 'off',
1444 'funnel_builder_mode' => self::DEFAULT_FUNNEL_BUILDER_MODE,
1445 )
1446 );
1447 $saved_settings = self::get_admin_settings( '_wpfunnels_general_settings', $default_settings );
1448 return wp_parse_args( $saved_settings, $default_settings );
1449 }
1450
1451
1452 /**
1453 * Valid canvas layouts.
1454 *
1455 * @return array
1456 * @since 3.12.13
1457 */
1458 public static function get_funnel_builder_modes() {
1459 return array( 'horizontal', 'vertical' );
1460 }
1461
1462
1463 /**
1464 * Site-wide default canvas layout, as chosen in Settings.
1465 *
1466 * @return string 'horizontal' or 'vertical'.
1467 * @since 3.12.13
1468 */
1469 public static function get_default_funnel_builder_mode() {
1470 $general_settings = self::get_general_settings();
1471 $mode = isset( $general_settings['funnel_builder_mode'] ) ? $general_settings['funnel_builder_mode'] : self::DEFAULT_FUNNEL_BUILDER_MODE;
1472
1473 return in_array( $mode, self::get_funnel_builder_modes(), true ) ? $mode : self::DEFAULT_FUNNEL_BUILDER_MODE;
1474 }
1475
1476
1477 /**
1478 * Canvas layout to open the builder in for a given user.
1479 *
1480 * A user who has switched views on the canvas keeps that view on their next
1481 * visit; everyone else gets the site-wide default from Settings.
1482 *
1483 * @param int $user_id User to resolve for. Defaults to the current user.
1484 *
1485 * @return string 'horizontal' or 'vertical'.
1486 * @since 3.12.13
1487 */
1488 public static function get_canvas_view_mode( $user_id = 0 ) {
1489 $user_id = $user_id ? $user_id : get_current_user_id();
1490
1491 if ( $user_id ) {
1492 $preference = get_user_meta( $user_id, self::CANVAS_VIEW_MODE_META_KEY, true );
1493 if ( in_array( $preference, self::get_funnel_builder_modes(), true ) ) {
1494 return $preference;
1495 }
1496 }
1497
1498 return self::get_default_funnel_builder_mode();
1499 }
1500
1501
1502 /**
1503 * Get permalink settings data
1504 *
1505 * @return array
1506 * @since 1.0.0
1507 */
1508 public static function get_permalink_settings() {
1509 $default_settings = apply_filters(
1510 'wpfunnels_permalink_settings',
1511 array(
1512 'structure' => 'default',
1513 'step_base' => WPFNL_STEPS_POST_TYPE,
1514 'funnel_base' => WPFNL_FUNNELS_POST_TYPE,
1515 )
1516 );
1517 $saved_settings = self::get_admin_settings( '_wpfunnels_permalink_settings' );
1518 return wp_parse_args( $saved_settings, $default_settings );
1519 }
1520
1521 /**
1522 * Get optin settings data
1523 *
1524 * @return array|string
1525 * @since 1.0.0
1526 */
1527 public static function get_optin_settings( $handler = '' ) {
1528 $default_settings = apply_filters(
1529 'wpfunnels_optin_settings',
1530 array(
1531 'sender_name' => get_bloginfo('name'),
1532 'sender_email' => wp_get_current_user()->user_email,
1533 'email_subject' => 'New Form Submission',
1534 )
1535 );
1536 $saved_settings = self::get_admin_settings( '_wpfunnels_optin_settings' );
1537 $settings = wp_parse_args( $saved_settings, $default_settings );
1538 return $handler && isset($settings[$handler]) ? $settings[$handler] : $settings;
1539 }
1540
1541
1542 /**
1543 * Get offer settings data
1544 *
1545 * @return array
1546 * @since 1.0.0
1547 */
1548 public static function get_offer_settings() {
1549 $default_settings = apply_filters(
1550 'wpfunnels/get_offer_settings',
1551 array(
1552 'offer_orders' => 'main-order',
1553 'show_supported_payment_gateway' => 'off',
1554 'skip_offer_step' => 'off',
1555 'skip_offer_step_for_free' => 'off',
1556 'enable_global_thankyou' => 'off',
1557 )
1558 );
1559 $saved_settings = self::get_admin_settings( '_wpfunnels_offer_settings' );
1560 return wp_parse_args( $saved_settings, $default_settings );
1561 }
1562
1563 /**
1564 * Derive the shipping rate that applies to an offer (upsell/downsell) product.
1565 *
1566 * Two sources are consulted, in order:
1567 *
1568 * 1. The shipping method already chosen on the parent order, re-priced against
1569 * the offer product's own shipping class when the store runs flat rate and
1570 * the offer is merged into the main order.
1571 * 2. The shipping zone matching the order's destination. This is the only
1572 * source available when the parent order never shipped — a virtual-only
1573 * checkout carries no shipping method to clone.
1574 *
1575 * An offer product that does not need shipping never derives a rate, so a
1576 * virtual upsell can never add shipping to a physical parent order.
1577 *
1578 * @param \WC_Order $order Parent order.
1579 * @param int $product_id Offer product id.
1580 * @param float $total_price Offer line total, used as the shipping package cost.
1581 * @param int $qty Offer quantity.
1582 *
1583 * @return array Shipping data, or an empty array when no shipping applies.
1584 * @since 1.0.0
1585 */
1586 public static function get_offer_shipping( $order, $product_id, $total_price = 0, $qty = 1 ) {
1587 if ( ! $product_id || ! $order instanceof \WC_Order ) {
1588 return array();
1589 }
1590
1591 $product = wc_get_product( $product_id );
1592
1593 if ( ! $product || ! $product->needs_shipping() ) {
1594 return array();
1595 }
1596
1597 $shipping = self::get_offer_shipping_from_parent_order( $order, $product );
1598
1599 if ( empty( $shipping ) ) {
1600 $shipping = self::get_offer_shipping_from_zones( $order, $product, $total_price, $qty );
1601 }
1602
1603 /**
1604 * Filter the shipping data derived for an offer product.
1605 *
1606 * @param array $shipping Derived shipping data, empty when none applies.
1607 * @param \WC_Order $order Parent order.
1608 * @param \WC_Product $product Offer product.
1609 *
1610 * @since 1.0.0
1611 */
1612 return apply_filters( 'wpfunnels/offer_shipping', $shipping, $order, $product );
1613 }
1614
1615
1616 /**
1617 * Clone the parent order's shipping method for the offer product.
1618 *
1619 * When the store runs flat rate and offers are merged into the main order the
1620 * cloned cost is re-priced against the offer product's shipping class, which is
1621 * the behaviour this plugin has always described.
1622 *
1623 * @param \WC_Order $order Parent order.
1624 * @param \WC_Product $product Offer product.
1625 *
1626 * @return array
1627 * @since 1.0.0
1628 */
1629 private static function get_offer_shipping_from_parent_order( $order, $product ) {
1630 $shipping = array();
1631 $shipping_methods = $order->get_shipping_methods();
1632
1633 if ( empty( $shipping_methods ) ) {
1634 return $shipping;
1635 }
1636
1637 foreach ( $shipping_methods as $method ) {
1638 if ( ! $method ) {
1639 continue;
1640 }
1641
1642 $shipping = array(
1643 'name' => $method->get_name(),
1644 'method_title' => $method->get_method_title(),
1645 'method_id' => $method->get_method_id(),
1646 'instance_id' => $method->get_instance_id(),
1647 'total' => (float) $method->get_total(),
1648 'total_tax' => (float) $method->get_total_tax(),
1649 'taxes' => $method->get_taxes(),
1650 );
1651 }
1652
1653 if ( empty( $shipping ) ) {
1654 return $shipping;
1655 }
1656
1657 $offer_settings = self::get_offer_settings();
1658 $offer_orders = isset( $offer_settings['offer_orders'] ) ? $offer_settings['offer_orders'] : '';
1659
1660 if ( 'main-order' !== $offer_orders || 'flat_rate' !== $shipping['method_id'] ) {
1661 return $shipping;
1662 }
1663
1664 $tax_percentage = 0.0 !== (float) $shipping['total'] ? ( $shipping['total_tax'] * 100 ) / $shipping['total'] : 0;
1665
1666 foreach ( \WC_Shipping_Zones::get_zones() as $zone ) {
1667 if ( empty( $zone['shipping_methods'] ) ) {
1668 continue;
1669 }
1670
1671 foreach ( $zone['shipping_methods'] as $method ) {
1672 if ( ! $method instanceof \WC_Shipping_Flat_Rate ) {
1673 continue;
1674 }
1675
1676 if ( ! isset( $method->instance_id ) || $method->instance_id != $shipping['instance_id'] ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
1677 continue;
1678 }
1679
1680 $instance_settings = isset( $method->instance_settings ) ? $method->instance_settings : array();
1681 $class_cost_key = 'class_cost_' . $product->get_shipping_class_id();
1682
1683 if ( isset( $instance_settings[ $class_cost_key ] ) ) {
1684 $shipping_cost = $instance_settings[ $class_cost_key ];
1685 } elseif ( isset( $instance_settings['no_class_cost'] ) ) {
1686 $shipping_cost = $instance_settings['no_class_cost'];
1687 } else {
1688 continue;
1689 }
1690
1691 $shipping_cost = $shipping_cost ? (float) $shipping_cost : 0;
1692 $total_tax = ( $shipping_cost * $tax_percentage ) / 100;
1693 $shipping['total'] = $shipping_cost;
1694 $shipping['total_tax'] = $total_tax;
1695 $shipping['taxes'] = array( 'total' => array( $total_tax ) );
1696 }
1697 }
1698
1699 return $shipping;
1700 }
1701
1702
1703 /**
1704 * Derive a shipping rate for the offer product from the matching shipping zone.
1705 *
1706 * Used when the parent order carries no shipping method to clone — a physical
1707 * offer accepted on top of a virtual-only order.
1708 *
1709 * @param \WC_Order $order Parent order.
1710 * @param \WC_Product $product Offer product.
1711 * @param float $total_price Offer line total.
1712 * @param int $qty Offer quantity.
1713 *
1714 * @return array
1715 * @since 1.0.0
1716 */
1717 private static function get_offer_shipping_from_zones( $order, $product, $total_price, $qty ) {
1718 if ( ! class_exists( '\WC_Shipping_Zones' ) ) {
1719 return array();
1720 }
1721
1722 $package = self::get_offer_shipping_package( $order, $product, $total_price, $qty );
1723
1724 if ( empty( $package['destination']['country'] ) ) {
1725 return array();
1726 }
1727
1728 $zone = \WC_Shipping_Zones::get_zone_matching_package( $package );
1729
1730 if ( ! $zone ) {
1731 return array();
1732 }
1733
1734 foreach ( $zone->get_shipping_methods( true ) as $method ) {
1735 $rates = $method->get_rates_for_package( $package );
1736
1737 if ( empty( $rates ) ) {
1738 continue;
1739 }
1740
1741 $rate = reset( $rates );
1742
1743 return array(
1744 'name' => $rate->get_label(),
1745 'method_title' => $rate->get_label(),
1746 'method_id' => $rate->get_method_id(),
1747 'instance_id' => $rate->get_instance_id(),
1748 'total' => (float) $rate->get_cost(),
1749 'total_tax' => (float) $rate->get_shipping_tax(),
1750 'taxes' => $rate->get_taxes(),
1751 );
1752 }
1753
1754 return array();
1755 }
1756
1757
1758 /**
1759 * Build the shipping package for a single offer product.
1760 *
1761 * The destination falls back to the billing address because a virtual-only
1762 * parent order has no shipping address — WooCommerce never collects one when
1763 * the cart does not need shipping.
1764 *
1765 * @param \WC_Order $order Parent order.
1766 * @param \WC_Product $product Offer product.
1767 * @param float $total_price Offer line total.
1768 * @param int $qty Offer quantity.
1769 *
1770 * @return array
1771 * @since 1.0.0
1772 */
1773 private static function get_offer_shipping_package( $order, $product, $total_price, $qty ) {
1774 $qty = max( 1, (int) $qty );
1775 $total_price = (float) $total_price;
1776
1777 $destination = array(
1778 'country' => $order->get_shipping_country() ? $order->get_shipping_country() : $order->get_billing_country(),
1779 'state' => $order->get_shipping_state() ? $order->get_shipping_state() : $order->get_billing_state(),
1780 'postcode' => $order->get_shipping_postcode() ? $order->get_shipping_postcode() : $order->get_billing_postcode(),
1781 'city' => $order->get_shipping_city() ? $order->get_shipping_city() : $order->get_billing_city(),
1782 'address' => $order->get_shipping_address_1() ? $order->get_shipping_address_1() : $order->get_billing_address_1(),
1783 'address_1' => $order->get_shipping_address_1() ? $order->get_shipping_address_1() : $order->get_billing_address_1(),
1784 'address_2' => $order->get_shipping_address_2() ? $order->get_shipping_address_2() : $order->get_billing_address_2(),
1785 );
1786
1787 return array(
1788 'contents' => array(
1789 $product->get_id() => array(
1790 'key' => $product->get_id(),
1791 'product_id' => $product->get_id(),
1792 'variation_id' => 0,
1793 'variation' => array(),
1794 'quantity' => $qty,
1795 'data' => $product,
1796 'line_total' => $total_price,
1797 'line_subtotal' => $total_price,
1798 'line_tax' => 0,
1799 'line_subtotal_tax' => 0,
1800 ),
1801 ),
1802 'contents_cost' => $total_price,
1803 'applied_coupons' => array(),
1804 'user' => array( 'ID' => $order->get_customer_id() ),
1805 'destination' => $destination,
1806 'cart_subtotal' => $total_price,
1807 );
1808 }
1809
1810
1811 /**
1812 * Record derived offer shipping on an order as a real shipping line item.
1813 *
1814 * Passing empty shipping data is a no-op, so an offer that needs no shipping
1815 * leaves the order — including any shipping line it already carries — alone.
1816 *
1817 * @param \WC_Order $order Order to add the shipping line to.
1818 * @param array $shipping Shipping data from get_offer_shipping().
1819 * @param string $offer_name Offer product name, recorded against the line.
1820 * @param int $qty Offer quantity.
1821 *
1822 * @return \WC_Order
1823 * @since 1.0.0
1824 */
1825 public static function add_offer_shipping_to_order( $order, $shipping, $offer_name = '', $qty = 1 ) {
1826 if ( ! $order instanceof \WC_Order || empty( $shipping ) || ! isset( $shipping['total'] ) ) {
1827 return $order;
1828 }
1829
1830 $item = new \WC_Order_Item_Shipping();
1831 $item->set_props(
1832 array(
1833 'method_title' => ! empty( $shipping['method_title'] ) ? $shipping['method_title'] : __( 'Shipping', 'wpfnl' ),
1834 'method_id' => isset( $shipping['method_id'] ) ? $shipping['method_id'] : '',
1835 'instance_id' => isset( $shipping['instance_id'] ) ? $shipping['instance_id'] : '',
1836 'total' => wc_format_decimal( $shipping['total'] ),
1837 'taxes' => isset( $shipping['taxes'] ) && is_array( $shipping['taxes'] ) ? $shipping['taxes'] : array(),
1838 )
1839 );
1840
1841 if ( $offer_name ) {
1842 $item->add_meta_data( 'Items', $offer_name . ' &times; ' . max( 1, (int) $qty ), true );
1843 }
1844
1845 $item->save();
1846 $order->add_item( $item );
1847
1848 self::maybe_backfill_shipping_address( $order );
1849
1850 $order->calculate_shipping();
1851 $order->calculate_totals();
1852 $order->save();
1853
1854 return $order;
1855 }
1856
1857
1858 /**
1859 * Copy the billing address onto the shipping address when the order has none.
1860 *
1861 * WooCommerce skips the billing-to-shipping copy at checkout whenever the cart
1862 * does not need a shipping address, so an order that only becomes shippable
1863 * through an offer would otherwise reveal an empty address block in the admin
1864 * order summary.
1865 *
1866 * @param \WC_Order $order Order to back-fill.
1867 *
1868 * @return \WC_Order
1869 * @since 1.0.0
1870 */
1871 public static function maybe_backfill_shipping_address( $order ) {
1872 if ( ! $order instanceof \WC_Order || $order->has_shipping_address() ) {
1873 return $order;
1874 }
1875
1876 $fields = array( 'first_name', 'last_name', 'company', 'address_1', 'address_2', 'city', 'state', 'postcode', 'country', 'phone' );
1877
1878 foreach ( $fields as $field ) {
1879 $getter = 'get_billing_' . $field;
1880 $setter = 'set_shipping_' . $field;
1881
1882 if ( ! is_callable( array( $order, $getter ) ) || ! is_callable( array( $order, $setter ) ) ) {
1883 continue;
1884 }
1885
1886 $value = $order->{$getter}();
1887
1888 if ( '' !== $value && null !== $value ) {
1889 $order->{$setter}( $value );
1890 }
1891 }
1892
1893 return $order;
1894 }
1895
1896
1897 /**
1898 * Get GTM events
1899 *
1900 * @return array
1901 */
1902 public static function get_gtm_events() {
1903 $default_gtm_events = array(
1904 'add_to_cart' => 'Add to cart',
1905 'begin_checkout' => 'Begin checkout',
1906 'add_payment_info' => 'Add Payment Info',
1907 'add_shipping_info' => 'Add Shipping Info',
1908 'purchase' => 'Purchase',
1909 'orderbump_accept' => 'Order Bump',
1910 'upsell' => 'Upsell',
1911 'downsell' => 'Downsell',
1912 );
1913 return $default_gtm_events;
1914 }
1915
1916 public static function get_gtm_settings() {
1917 $default_enable_settings = array(
1918 'gtm_enable' => 'off',
1919 'gtm_container_id' => '',
1920 'gtm_events' => array(
1921 'add_to_cart' => 'true',
1922 'begin_checkout' => 'true',
1923 'add_payment_info' => 'true',
1924 'add_shipping_info' => 'true',
1925 'purchase' => 'true',
1926 'orderbump_accept' => 'true',
1927 'upsell' => 'true',
1928 'downsell' => 'true',
1929 ),
1930 );
1931 $gtm_settings = self::get_admin_settings( '_wpfunnels_gtm' );
1932 return wp_parse_args( $gtm_settings, $default_enable_settings );
1933 }
1934 /**
1935 * Get facebook pixel events
1936 *
1937 * @return array
1938 */
1939 public static function get_facebook_events() {
1940 $default_fb_events = array(
1941 'AddPaymentInfo' => 'Add payment info',
1942 'AddToCart' => 'Add to cart',
1943 'InitiateCheckout' => 'Initiate checkout',
1944 // 'Lead' => 'Lead',
1945 'Purchase' => 'Purchase',
1946 'ViewContent' => 'View content',
1947 );
1948 return $default_fb_events;
1949 }
1950
1951 public static function get_facebook_pixel_settings() {
1952 $default_enable_settings = array(
1953 'enable_fb_pixel' => 'off',
1954 'facebook_pixel_id' => '',
1955 'facebook_tracking_events' => array(
1956 'AddPaymentInfo' => 'true',
1957 'AddToCart' => 'true',
1958 'InitiateCheckout' => 'true',
1959 'Lead' => 'true',
1960 'Purchase' => 'true',
1961 'ViewContent' => 'true',
1962 ),
1963 );
1964 $facebook_pixel_setting = self::get_admin_settings( '_wpfunnels_facebook_pixel' );
1965 return wp_parse_args( $facebook_pixel_setting, $default_enable_settings );
1966 }
1967
1968
1969 public static function get_recaptcha_settings() {
1970 $default_enable_settings = array(
1971 'enable_recaptcha' => 'no',
1972 'recaptcha_site_key' => '',
1973 'recaptcha_site_secret' => '',
1974 );
1975 $recaptcha_setting = self::get_admin_settings( '_wpfunnels_recaptcha_setting' );
1976 return wp_parse_args( $recaptcha_setting, $default_enable_settings );
1977 }
1978
1979
1980 /**
1981 * Get user role management settings
1982 *
1983 * @return array|mixed|void
1984 *
1985 * @since 3.1.3
1986 */
1987 public static function get_user_role_settings() {
1988 global $wp_roles;
1989
1990 $user_role_names = array_keys( $wp_roles->get_names() );
1991 $user_role_names = array_diff( $user_role_names, array( 'administrator' ) );
1992
1993 $default_settings = array();
1994
1995 foreach ( $user_role_names as $user_role_name ) {
1996 if( 'subscriber' === $user_role_name || 'customer' === $user_role_name ) {
1997 continue;
1998 }
1999 $default_settings[ $user_role_name ] = 'no';
2000 }
2001
2002 $user_role_manager = self::get_admin_settings( '_wpfunnels_user_roles' );
2003 $user_role_manager = wp_parse_args( $user_role_manager, $default_settings );
2004 return $user_role_manager;
2005 }
2006
2007
2008 /*
2009 * Get google place API key
2010 *
2011 * @return mixed|void
2012 * @since 3.1.3
2013 */
2014 public static function get_google_map_api_key() {
2015 return self::get_admin_settings( '_wpfunnels_google_map_api_key', '' );
2016 }
2017
2018
2019 /**
2020 * Get notification settings
2021 *
2022 * @return array
2023 * @since 3.2.0
2024 */
2025 public static function get_notification_settings() {
2026 $default_notification_settings = array(
2027 'enable_revenue_report' => 'no',
2028 'revenue_report_frequency' => 'weekly',
2029 'revenue_report_recipient' => get_option('admin_email'),
2030 'revenue_report_subject' => 'Store Revenue Report - {period}',
2031 'send_time' => '10:00',
2032 );
2033
2034 $notification_settings = self::get_admin_settings( '_wpfunnels_notification_settings' );
2035 return wp_parse_args( $notification_settings, $default_notification_settings );
2036 }
2037
2038
2039 /**
2040 * Notification settings seeded into the database on a brand-new install.
2041 *
2042 * The store revenue report ships on for sites installed from 3.14.0 onward.
2043 * It is written in once, by Wpfnl_Activator, so the decision lives in the
2044 * database rather than in the fallback used by get_notification_settings():
2045 * a site that upgrades has no stored value, keeps the disabled fallback, and
2046 * never starts mailing on its own.
2047 *
2048 * @return array
2049 * @since 3.14.0
2050 */
2051 public static function get_new_install_notification_settings() {
2052 return array(
2053 'enable_revenue_report' => 'yes',
2054 'revenue_report_frequency' => 'weekly',
2055 'revenue_report_recipient' => get_option( 'admin_email' ),
2056 'revenue_report_subject' => 'Store Revenue Report - {period}',
2057 'send_time' => '10:00',
2058 );
2059 }
2060
2061
2062 /**
2063 * Schedule the recurring revenue report email through Action Scheduler.
2064 *
2065 * Callers are responsible for clearing any existing schedule first; use
2066 * is_revenue_report_scheduled() when a duplicate must be avoided.
2067 *
2068 * @param array $settings Notification settings.
2069 *
2070 * @return void
2071 * @since 3.14.0
2072 */
2073 public static function schedule_revenue_report_email( $settings ) {
2074 if ( ! function_exists( 'as_schedule_recurring_action' ) ) {
2075 return;
2076 }
2077
2078 // Calculate next run time based on send_time.
2079 $send_time = isset( $settings['send_time'] ) ? $settings['send_time'] : '10:00';
2080 $frequency = isset( $settings['revenue_report_frequency'] ) ? $settings['revenue_report_frequency'] : 'weekly';
2081
2082 // Parse time (format: HH:MM).
2083 list( $hour, $minute ) = array_pad( explode( ':', $send_time ), 2, '0' );
2084
2085 // Get current time.
2086 $now = current_time( 'timestamp' );
2087 $today = strtotime( date( 'Y-m-d', $now ) );
2088
2089 // Calculate first run time (today at specified time).
2090 $first_run = $today + ( $hour * HOUR_IN_SECONDS ) + ( $minute * MINUTE_IN_SECONDS );
2091
2092 // If the time has already passed today, schedule for tomorrow.
2093 if ( $first_run <= $now ) {
2094 $first_run += DAY_IN_SECONDS;
2095 }
2096
2097 // Calculate interval based on frequency.
2098 $interval = ( 'monthly' === $frequency ) ? MONTH_IN_SECONDS : WEEK_IN_SECONDS;
2099
2100 as_schedule_recurring_action(
2101 $first_run,
2102 $interval,
2103 self::REVENUE_REPORT_HOOK,
2104 array(),
2105 'wpfunnels'
2106 );
2107 }
2108
2109
2110 /**
2111 * Remove the recurring revenue report email schedule.
2112 *
2113 * @return void
2114 * @since 3.14.0
2115 */
2116 public static function unschedule_revenue_report_email() {
2117 if ( ! function_exists( 'as_unschedule_all_actions' ) ) {
2118 return;
2119 }
2120
2121 as_unschedule_all_actions( self::REVENUE_REPORT_HOOK, array(), 'wpfunnels' );
2122 }
2123
2124
2125 /**
2126 * Whether a revenue report email is already queued.
2127 *
2128 * @return bool
2129 * @since 3.14.0
2130 */
2131 public static function is_revenue_report_scheduled() {
2132 if ( ! function_exists( 'as_next_scheduled_action' ) ) {
2133 return false;
2134 }
2135
2136 return (bool) as_next_scheduled_action( self::REVENUE_REPORT_HOOK, array(), 'wpfunnels' );
2137 }
2138
2139
2140 /**
2141 * Get advanced settings
2142 */
2143 public static function get_advanced_settings() {
2144 $default_enable_settings = array(
2145 'show_supported_payment_gateway' => 'off',
2146 );
2147
2148 $advanced_settings = self::get_admin_settings( '_wpfunnels_advanced_settings' );
2149 return wp_parse_args( $advanced_settings, $default_enable_settings );
2150 }
2151
2152 /**
2153 * Get UTM Parameters
2154 *
2155 * @return array
2156 */
2157 public static function get_utm_params() {
2158 $default_utm_params = array(
2159 'utm_source' => 'UTM Source',
2160 'utm_medium' => 'UTM Medium',
2161 'utm_campaign' => 'UTM Campaign',
2162 'utm_content' => 'UTM Content',
2163 );
2164 return $default_utm_params;
2165 }
2166
2167 public static function get_utm_settings() {
2168 $default_enable_settings = array(
2169 'utm_enable' => 'off',
2170 'utm_source' => '',
2171 'utm_medium' => '',
2172 'utm_campaign' => '',
2173 'utm_content' => '',
2174 );
2175 $utm_settings = self::get_admin_settings( '_wpfunnels_utm_params' );
2176 return wp_parse_args( $utm_settings, $default_enable_settings );
2177 }
2178
2179 /**
2180 * Get user roles
2181 *
2182 * @return array
2183 * @since 2.1.7
2184 */
2185 public static function get_user_roles() {
2186 global $wp_roles;
2187 $all_roles = $wp_roles->roles;
2188 return array_keys( $all_roles );
2189 }
2190
2191
2192 /**
2193 * Get the saved builder type
2194 *
2195 * @return mixed|void
2196 * @since 1.0.0
2197 */
2198 public static function get_builder_type() {
2199 $general_settings = self::get_general_settings();
2200 return $general_settings['builder'];
2201 }
2202
2203
2204 /**
2205 * Check if wc is installed
2206 *
2207 * @return bool
2208 * @since 1.0.0
2209 */
2210 public static function is_wc_active() {
2211 if ( in_array( 'woocommerce/woocommerce.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) {
2212 return true;
2213 } elseif ( function_exists( 'is_plugin_active' ) ) {
2214 if ( is_plugin_active( 'woocommerce/woocommerce.php' ) ) {
2215 return true;
2216 }
2217 }
2218 return false;
2219 }
2220
2221 /**
2222 * Check if woocommerce payment is activated or not
2223 *
2224 * @return bool
2225 * @since 1.9.0
2226 */
2227 public static function is_wc_payment_active() {
2228 if ( defined( 'WCPAY_PLUGIN_FILE' ) ) {
2229 return true;
2230 }
2231 return false;
2232 }
2233
2234
2235 /**
2236 * Check if elementor is installed
2237 *
2238 * @return bool
2239 * @since 1.0.0
2240 */
2241 public static function is_elementor_active() {
2242 if ( in_array( 'elementor/elementor.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) {
2243 return true;
2244 } elseif ( function_exists( 'is_plugin_active' ) ) {
2245 if ( is_plugin_active( 'elementor/elementor.php' ) ) {
2246 return true;
2247 }
2248 }
2249 return false;
2250 }
2251
2252
2253 /**
2254 * Check if funnel step or not
2255 *
2256 * @return bool
2257 * @since 3.1.0
2258 */
2259 public static function maybe_funnel_step( $step_id = null ) {
2260 if ( ! $step_id ) {
2261 $step_id = get_the_ID();
2262 }
2263 $post_type = get_post_type( $step_id );
2264 if ( WPFNL_STEPS_POST_TYPE === $post_type ) {
2265 return true;
2266 }
2267 return false;
2268 }
2269
2270
2271 /**
2272 * Check if saved builder is activated
2273 * or not
2274 *
2275 * @param $builder
2276 *
2277 * @return bool
2278 * @since 1.0.0
2279 */
2280 public static function is_builder_active( $builder ) {
2281 switch ( $builder ) {
2282 case 'elementor':
2283 return self::is_elementor_active();
2284 break;
2285 default:
2286 return false;
2287 break;
2288 }
2289 }
2290
2291
2292 /**
2293 * Check if the global funnel addon is activated or not
2294 *
2295 * @return bool
2296 * @since 2.0.4
2297 */
2298 public static function is_global_funnel_activated() {
2299 return apply_filters( 'wpfunnels/is_global_funnel_activated', false );
2300 }
2301
2302
2303 /**
2304 * Check if the funnel is global funnel
2305 *
2306 * @param $funnel_id
2307 *
2308 * @return bool
2309 */
2310 public static function is_global_funnel( $funnel_id ) {
2311 if ( ! $funnel_id ) {
2312 return false;
2313 }
2314 return apply_filters( 'wpfunnels/is_global_funnel', false, $funnel_id );
2315 }
2316
2317
2318 /**
2319 * Check if pro is activated/deactivated
2320 *
2321 * @return bool
2322 * @since 1.0.0
2323 */
2324 public static function is_wpfnl_pro_activated() {
2325 return apply_filters( 'wpfunnels/is_wpfnl_pro_active', false ) || apply_filters( 'is_wpfnl_pro_active', false );
2326 }
2327
2328
2329 /**
2330 * Check if Divi 5 is active.
2331 *
2332 * This function checks if Divi 5 is enabled using multiple detection methods
2333 * to ensure compatibility regardless of when the check is performed.
2334 *
2335 * @return bool
2336 * @since 2.9.1
2337 */
2338 public static function is_divi5_active(): bool {
2339 // Primary: Check if the Divi 5 function exists and call it
2340 if ( function_exists( 'et_builder_d5_enabled' ) ) {
2341 return et_builder_d5_enabled();
2342 }
2343
2344 // Fallback 1: Check if Divi 5 classes/interfaces exist
2345 if ( interface_exists( 'ET\Builder\Framework\DependencyManagement\Interfaces\DependencyInterface' ) ) {
2346 return true;
2347 }
2348
2349 // Fallback 2: Check if Divi theme is active and ET_BUILDER_VERSION is 5.x
2350 if ( get_template() === 'Divi' && defined( 'ET_BUILDER_VERSION' ) ) {
2351 return version_compare( ET_BUILDER_VERSION, '5.0', '>=' );
2352 }
2353
2354 // Fallback 3: Check theme version from style.css
2355 if ( get_template() === 'Divi' ) {
2356 $theme = wp_get_theme( 'Divi' );
2357 if ( $theme->exists() ) {
2358 $version = $theme->get( 'Version' );
2359 return version_compare( $version, '5.0', '>=' );
2360 }
2361 }
2362
2363 return false;
2364 }
2365
2366
2367 /**
2368 * Where "edit this funnel" links go.
2369 *
2370 * The React canvas (`admin.php?page=wpfunnels#/funnels/{id}/canvas`) by
2371 * default, the classic `edit_funnel` screen on a site that turned the
2372 * redirect off. Every link generator should use this rather than spelling
2373 * out `page=edit_funnel`.
2374 *
2375 * @param int $funnel_id Funnel (post) id.
2376 * @param array $link Optional deep link into a step's drawer:
2377 * `step`, `open` (`settings`|`automation`), `tab`.
2378 *
2379 * @return string Absolute admin URL.
2380 * @since 3.13.8
2381 */
2382 public static function get_funnel_edit_url( $funnel_id, $link = array() ) {
2383 return \WPFunnels\Menu\Wpfnl_Menus::funnel_edit_url( $funnel_id, $link );
2384 }
2385
2386
2387 /**
2388 * Check if pro is activated/deactivated
2389 *
2390 * @return bool
2391 * @since 1.0.0
2392 */
2393 public static function is_pro_license_activated() {
2394 return apply_filters( 'wpfunnels/is_pro_license_activated', false );
2395 }
2396
2397
2398 /**
2399 * Count funnels that are NOT store checkout type.
2400 *
2401 * Used to enforce the free-plan 3-funnel limit without counting
2402 * store checkout funnels against that quota.
2403 *
2404 * @param array $statuses Post statuses to include. Defaults to publish + draft.
2405 * @return int
2406 * @since 3.6.0
2407 */
2408 public static function count_non_store_checkout_funnels( $statuses = array( 'publish', 'draft' ) ) {
2409 $args = array(
2410 'post_type' => WPFNL_FUNNELS_POST_TYPE,
2411 'post_status' => $statuses,
2412 'posts_per_page' => -1,
2413 'suppress_filters' => true,
2414 'fields' => 'ids',
2415 'meta_query' => array(
2416 'relation' => 'OR',
2417 array(
2418 'key' => '_wpfnl_funnel_type',
2419 'compare' => 'NOT EXISTS',
2420 ),
2421 array(
2422 'key' => '_wpfnl_funnel_type',
2423 'value' => 'store_checkout',
2424 'compare' => '!=',
2425 ),
2426 ),
2427 );
2428 return count( get_posts( $args ) );
2429 }
2430
2431
2432 /**
2433 * Count store checkout funnels.
2434 *
2435 * Used to enforce the free-plan 3-store-checkout limit.
2436 *
2437 * @param array $statuses Post statuses to include. Defaults to publish + draft.
2438 * @return int
2439 * @since 3.6.0
2440 */
2441 public static function count_store_checkout_funnels( $statuses = array( 'publish', 'draft' ) ) {
2442 $args = array(
2443 'post_type' => WPFNL_FUNNELS_POST_TYPE,
2444 'post_status' => $statuses,
2445 'posts_per_page' => -1,
2446 'suppress_filters' => true,
2447 'fields' => 'ids',
2448 'meta_query' => array(
2449 array(
2450 'key' => '_wpfnl_funnel_type',
2451 'value' => 'store_checkout',
2452 'compare' => '=',
2453 ),
2454 ),
2455 );
2456 return count( get_posts( $args ) );
2457 }
2458
2459 /**
2460 * Check if the module is pro or
2461 * not
2462 *
2463 * @param $module
2464 *
2465 * @return bool
2466 * @since 1.0.0
2467 */
2468 public static function is_pro_module( $module ) {
2469 $pro_modules = apply_filters( 'wpfnl_pro_modules', array() );
2470 return in_array( $module, $pro_modules );
2471 }
2472
2473
2474 /**
2475 * Check if the module is exists or not
2476 *
2477 * @param $module_name
2478 * @param string $type
2479 * @param bool $step
2480 * @param bool $pro
2481 *
2482 * @return bool
2483 * @since 1.0.0
2484 */
2485 public static function is_module_registered( $module_name, $type = 'admin', $step = false, $pro = false ) {
2486 if ( empty( $module_name ) ) {
2487 return false;
2488 }
2489 $class_name = str_replace( '-', ' ', $module_name );
2490 $class_name = str_replace( ' ', '', ucwords( $class_name ) );
2491 if ( $pro ) {
2492 if ( $type === 'steps' ) {
2493 $class_name = 'WPFunnelsPro\\Admin\\Modules\\Steps\\' . $class_name . '\Module';
2494 }
2495 } else {
2496 if ( $type === 'admin' ) {
2497 $class_name = 'WPFunnels\\Admin\\Modules\\' . $class_name . '\Module';
2498 } elseif ( $type === 'steps' ) {
2499 $class_name = 'WPFunnels\\Admin\\Modules\\Steps\\' . $class_name . '\Module';
2500 } else {
2501 $class_name = 'WPFunnels\\Modules\\Frontend\\' . $class_name . '\Module';
2502 }
2503 }
2504 return class_exists( $class_name );
2505 }
2506
2507
2508 /**
2509 * Check manager permissions on REST API.
2510 *
2511 * @param string $object Object.
2512 * @param string $context Request context.
2513 *
2514 * @return bool
2515 * @since 2.6.0
2516 */
2517 public static function wpfnl_rest_check_manager_permissions( $object, $context = 'read' ) {
2518
2519 $objects = array(
2520 'funnels' => 'wpf_manage_funnels',
2521 'settings' => 'wpf_manage_funnels',
2522 'templates' => 'wpf_manage_funnels',
2523 'steps' => 'wpf_manage_funnels',
2524 'products' => 'wpf_manage_funnels',
2525 );
2526 if ( ! isset( $objects[ $object ] ) ) {
2527 return false;
2528 }
2529 return current_user_can( $objects[ $object ] );
2530 }
2531
2532
2533 /**
2534 * Check if the provided plugin ($path) is installed or not
2535 *
2536 * @param $path
2537 *
2538 * @return bool
2539 * @since 2.0.0
2540 */
2541 public static function is_plugin_installed( $path ) {
2542 $plugins = get_plugins();
2543 return isset( $plugins[ $path ] );
2544 }
2545
2546
2547
2548 /**
2549 * Check if the provided plugin ($path) is installed or not
2550 *
2551 * @param $path
2552 *
2553 * @return bool
2554 * @since 2.0.0
2555 */
2556 public static function is_plugin_activated( $path ) {
2557 include_once ABSPATH . 'wp-admin/includes/plugin.php';
2558 if ( is_plugin_active( $path ) ) {
2559 return true;
2560 }
2561 return false;
2562 }
2563
2564
2565
2566 /**
2567 * Check plugin status by path
2568 *
2569 * @param $path
2570 * @param $slug
2571 *
2572 * @return string
2573 *
2574 * @since 2.0.1
2575 */
2576 public static function get_plugin_action( $path, $slug ) {
2577 if ( 'divi-builder' === $slug ) {
2578 $is_divi_theme_active = self::wpfnl_is_theme_active( 'Divi' );
2579 if ( $is_divi_theme_active ) {
2580 return 'nothing';
2581 }
2582 }
2583
2584 if ( 'bricks' === $slug ) {
2585 $maybe_bricks_theme = self::maybe_bricks_theme();
2586 if ( $maybe_bricks_theme ) {
2587 return 'nothing';
2588 } else {
2589 return 'install';
2590 }
2591 }
2592
2593 if ( null === self::$installed_plugins ) {
2594 self::$installed_plugins = get_plugins();
2595 }
2596
2597 if ( ! isset( self::$installed_plugins[ $path ] ) ) {
2598 return 'install';
2599 } elseif ( ! is_plugin_active( $path ) ) {
2600 return 'activate';
2601 } else {
2602 return 'nothing';
2603 }
2604 }
2605
2606 /**
2607 * Check theme is active or not
2608 *
2609 * @param string $theme_name
2610 *
2611 * @return Bool
2612 */
2613 public static function wpfnl_is_theme_active( $theme_name ) {
2614 $theme = wp_get_theme(); // gets the current theme
2615 if ( $theme_name === $theme->name || $theme_name === $theme->parent_theme ) {
2616 return true;
2617 }
2618 return false;
2619 }
2620
2621
2622 /**
2623 * Check plugin is installed or not
2624 *
2625 * @param $plugin_slug
2626 *
2627 * @return Bolean
2628 */
2629 public static function wpfnl_check_is_plugin_installed( $plugin ) {
2630 $installed_plugins = get_plugins();
2631 return array_key_exists( $plugin, $installed_plugins ) || in_array( $plugin, $installed_plugins, true );
2632 }
2633
2634
2635 /**
2636 * Get depenedency plugins status
2637 *
2638 * @return mixed|void
2639 *
2640 * @since 2.0.1
2641 */
2642 public static function get_dependency_plugins_status() {
2643 return apply_filters(
2644 'wpfunnels/dependency_plugin_list',
2645 array(
2646 'elementor' => array(
2647 'name' => 'Elementor',
2648 'plugin_file' => 'elementor/elementor.php',
2649 'slug' => 'elementor',
2650 'action' => self::get_plugin_action( 'elementor/elementor.php', 'elementor' ),
2651 ),
2652 'divi-builder' => array(
2653 'name' => 'Divi',
2654 'plugin_file' => 'divi-builder/divi-builder.php',
2655 'slug' => 'divi-builder',
2656 'action' => self::get_plugin_action( 'divi-builder/divi-builder.php', 'divi-builder' ),
2657 ),
2658 'oxygen' => array(
2659 'name' => 'Oxygen',
2660 'plugin_file' => 'oxygen/functions.php',
2661 'slug' => 'oxygen',
2662 'action' => self::get_plugin_action( 'oxygen/functions.php', 'oxygen' ),
2663 ),
2664 'bricks' => array(
2665 'name' => 'Bricks',
2666 'plugin_file' => 'bricks',
2667 'slug' => 'bricks',
2668 'action' => self::get_plugin_action( 'bricks', 'bricks' ),
2669 ),
2670 )
2671 );
2672 }
2673
2674
2675 /**
2676 * Is there any missing plugin for wpfunnels
2677 *
2678 * @return string
2679 *
2680 * @since 2.0.1
2681 */
2682 public static function is_any_plugin_missing() {
2683 if ( null === self::$installed_plugins ) {
2684 self::$installed_plugins = get_plugins();
2685 }
2686 $builder = self::get_builder_type();
2687 $dependency_plugins = self::get_dependency_plugins_status();
2688 $is_missing = 'no';
2689
2690 if ( isset( $dependency_plugins[ $builder ] ) ) {
2691 $plugin_data = $dependency_plugins[ $builder ];
2692 if ( 'activate' === $plugin_data['action'] || 'install' === $plugin_data['action'] ) {
2693 $is_missing = 'yes';
2694 }
2695 }
2696
2697 /**
2698 * For support extra theme with divi
2699 */
2700 $theme = wp_get_theme();
2701
2702 if ( 'divi-builder' === $builder && ( 'Extra' === $theme->name || 'Extra' === $theme->parent_theme ) ) {
2703 $is_missing = 'no';
2704 }
2705
2706 return $is_missing;
2707 }
2708
2709
2710 /**
2711 * Recursively traverses a multidimensional array in search of a specific value and returns the
2712 * array containing the value, or an
2713 * null on failure.
2714 *
2715 * @param $search_value
2716 * @param $array
2717 *
2718 * @return array
2719 * @since 2.0.0
2720 */
2721 public static function recursive_multidimensional_ob_array_search_by_value( $search_value, $array, $keys = array() ) {
2722 if ( is_array( $array ) && count( $array ) > 0 ) {
2723 foreach ( $array as $key => $value ) {
2724 $temp_keys = $keys;
2725
2726 // Adding current key to search path
2727 array_push( $temp_keys, $key );
2728
2729 // Check if this value is an array
2730 // with atleast one element
2731 if ( is_array( $value ) && count( $value ) > 0 ) {
2732 $widget_type = isset( $value['widgetType'] ) ? $value['widgetType'] : false;
2733 if ( $widget_type ) {
2734 if ( $widget_type === $search_value ) {
2735 $value['path'] = $temp_keys;
2736 return $value;
2737 } else {
2738 $res_path = self::recursive_multidimensional_ob_array_search_by_value(
2739 $search_value,
2740 $value['elements'],
2741 $temp_keys
2742 );
2743 }
2744 if ( $res_path != null ) {
2745 return $res_path;
2746 }
2747 } else {
2748 $res_path = self::recursive_multidimensional_ob_array_search_by_value(
2749 $search_value,
2750 $value['elements'],
2751 $temp_keys
2752 );
2753 }
2754 if ( $res_path != null ) {
2755 return $res_path;
2756 }
2757 }
2758 }
2759 }
2760
2761 return null;
2762 }
2763
2764
2765 /**
2766 * Check if checkout ajax or not
2767 *
2768 * @return bool
2769 */
2770 public static function is_checkout_ajax() {
2771 if ( wp_doing_ajax() || isset( $_GET['wc-ajax'] ) ) {
2772 if ( isset( $_GET['wc-ajax'] ) && //phpcs:ignore
2773 isset( $_POST['_wcf_checkout_id'] ) //phpcs:ignore
2774 ) {
2775 return true;
2776 }
2777 }
2778
2779 return false;
2780 }
2781
2782
2783 /**
2784 * Calculate discount price
2785 *
2786 * @param $discount_type
2787 * @param $discount_value
2788 * @param $product_price
2789 *
2790 * @deprecated deprecated since 2.7.6
2791 *
2792 * @return string
2793 */
2794 public static function calculate_discount_price( $discount_type, $discount_value, $product_price ) {
2795
2796 $custom_price = $product_price;
2797 if ( ! empty( $discount_type ) ) {
2798 if ( 'discount-percentage' === $discount_type ) {
2799 if ( $discount_value > 0 && $discount_value <= 100 ) {
2800 $custom_price = $product_price - ( ( $product_price * $discount_value ) / 100 );
2801 $custom_price = number_format( (float) $custom_price, 2, '.', '' );
2802 }
2803 } elseif ( 'discount-price' === $discount_type ) {
2804 if ( $discount_value > 0 ) {
2805 $custom_price = $product_price - $discount_value;
2806 }
2807 }
2808 }
2809 return $custom_price;
2810 }
2811
2812
2813 /**
2814 * Calculate discount price
2815 *
2816 * @param $discount_type
2817 * @param $discount_value
2818 * @param $product_price
2819 *
2820 * @return string
2821 */
2822 public static function calculate_discount_amount( $discount_type, $discount_value, $product_price ) {
2823
2824 if ( $discount_type !== 'original' && ! empty( $discount_type ) ) {
2825 $discount = '';
2826 if ( 'discount-percentage' === $discount_type ) {
2827 if ( $discount_value > 0 && $discount_value <= 100 ) {
2828 $discount = ( ( $product_price * $discount_value ) / 100 );
2829 }
2830 } elseif ( 'discount-price' === $discount_type ) {
2831 if ( $discount_value > 0 ) {
2832 $discount = $discount_value;
2833
2834 }
2835 }
2836 return number_format( $discount, 2 );
2837 }
2838
2839 return false;
2840 }
2841
2842
2843 /**
2844 * Get attributes for wpfunnels body wrapper
2845 *
2846 * @param string $template
2847 *
2848 * @return string
2849 */
2850 public static function get_template_container_atts( $template = '' ) {
2851 $attributes = apply_filters( 'wpfunnels/page_container_atts', array() );
2852 $atts_string = '';
2853 foreach ( $attributes as $key => $value ) {
2854 if ( ! $value ) {
2855 continue;
2856 }
2857 if ( true === $value ) {
2858 $atts_string .= esc_html( $key ) . ' ';
2859 } else {
2860 $atts_string .= sprintf( '%s="%s" ', esc_html( $key ), esc_attr( $value ) );
2861 }
2862 }
2863 return $atts_string;
2864 }
2865
2866
2867 /**
2868 * Get funnel link
2869 *
2870 * @param $funnel_id
2871 *
2872 * @return Mix
2873 */
2874 public static function get_funnel_link( $funnel_id ) {
2875 if ( ! $funnel_id ) {
2876 return;
2877 }
2878 $steps = self::get_steps( $funnel_id );
2879 if ( $steps && is_array( $steps ) ) {
2880 $first_step = reset( $steps );
2881 $first_step_id = $first_step['id'];
2882 return get_the_permalink( $first_step_id );
2883 }
2884 return home_url();
2885 }
2886
2887
2888 /**
2889 * Get wc fragment
2890 *
2891 * @param array $data
2892 *
2893 * @return array
2894 */
2895 public static function get_wc_fragments( $data = array() ) {
2896 ob_start();
2897 woocommerce_order_review();
2898 $woocommerce_order_review = ob_get_clean();
2899
2900 ob_start();
2901 woocommerce_checkout_payment();
2902 $woocommerce_checkout_payment = ob_get_clean();
2903
2904 $response = array(
2905 'cart_total' => WC()->cart->total,
2906 'cart_total_currency' => WC()->cart->get_cart_total(),
2907 'wc_custom_fragments' => 'yes',
2908 'fragments' => apply_filters(
2909 'woocommerce_update_order_review_fragments',
2910 array(
2911 '.woocommerce-checkout-review-order-table' => $woocommerce_order_review,
2912 /*'.woocommerce-checkout-payment' => $woocommerce_checkout_payment,*/
2913 )
2914 ),
2915 );
2916
2917 if ( ! empty( $data ) ) {
2918 $response['wpfunnels_data'] = $data;
2919 }
2920
2921 return $response;
2922 }
2923
2924
2925 /**
2926 * Get supported builders
2927 *
2928 * @return array
2929 */
2930 public static function get_supported_builders() {
2931 $builders = array(
2932 'gutenberg' => 'Gutenberg',
2933 'elementor' => 'Elementor',
2934 'divi-builder' => 'Divi builder',
2935 'oxygen' => 'Oxygen',
2936 'bricks' => 'Bricks',
2937 'other' => 'Other',
2938 );
2939 return apply_filters( 'wpfunnels/supported_builders', $builders );
2940 }
2941
2942 /**
2943 * Get custom url instead of deafult tahnkyou page
2944 *
2945 * @param mixed $step_id
2946 *
2947 * @return [type]
2948 */
2949 public static function custom_url_for_thankyou_page( $step_id ) {
2950
2951 $isThankyou = self::check_if_this_is_step_type_by_id( $step_id, 'thankyou' );
2952 $isCustomUrl = get_post_meta( $step_id, '_wpfnl_thankyou_is_custom_redirect', true );
2953 $isDirect = get_post_meta( $step_id, '_wpfnl_thankyou_is_direct_redirect', true );
2954
2955 if ( $isThankyou && $isCustomUrl === 'on' ) {
2956 $url = get_post_meta( $step_id, '_wpfnl_thankyou_custom_redirect_url', true );
2957 if ( $url ) {
2958
2959 if ( $isDirect === 'off' ) {
2960 $redirectAfterSec = get_post_meta( $step_id, '_wpfnl_thankyou_set_time', true ) ? get_post_meta( $step_id, '_wpfnl_thankyou_set_time', true ) : 5;
2961 header( 'refresh:' . $redirectAfterSec . ';url=' . $url );
2962 } else {
2963 // unsell 'wpfunnels_automation_data' cookie and trigger 'wpfunnels/trigger_automation' hook
2964 self::unset_site_cookie( $step_id, 'wpfunnels_automation_data', 'wpfunnels/trigger_automation' );
2965 return $url;
2966 }
2967 }
2968 return false;
2969 }
2970 return false;
2971 }
2972
2973 /**
2974 * Check product has perfect variation or not
2975 *
2976 * @param $variation_id
2977 *
2978 * @return Boolean
2979 */
2980 public static function is_perfect_variations( $variation_id ) {
2981
2982 $blank_attr = array();
2983 $response = array(
2984 'status' => true,
2985 'data' => array(),
2986 );
2987 $product = wc_get_product( $variation_id );
2988 if ( $product ) {
2989 $parent_id = $product->get_parent_id();
2990 $_product = wc_get_product( $parent_id );
2991 if ( $_product ) {
2992 $attrs = self::get_product_attr( $_product );
2993 $attributes = $product->get_attributes();
2994 if ( ! empty( $attributes ) ) {
2995 foreach ( $attributes as $attribute_key => $attribute_value ) {
2996
2997 if ( ! $attribute_value ) {
2998 $blank_attr[ $attribute_key ] = $attrs[ $attribute_key ];
2999 $response['status'] = false;
3000 }
3001 }
3002 }
3003 }
3004 }
3005 $response['data'] = $blank_attr;
3006 return $response;
3007 }
3008
3009
3010 /**
3011 * Get attributes of product
3012 *
3013 * @param Object $produt
3014 */
3015 public static function get_product_attr( $product ) {
3016 $attributes = $product->get_attributes();
3017 $attr_array = array();
3018 foreach ( $attributes as $attribute_key => $attribute_value ) {
3019
3020 $attribute_name = str_replace( 'attribute_', '', $attribute_key );
3021 $attr_value = $product->get_attribute( $attribute_name );
3022 $attr_value = strtolower( $attr_value );
3023
3024 if ( strpos( $attr_value, '|' ) ) {
3025 $attr_array[ $attribute_key ] = explode( '|', $attr_value );
3026 } else {
3027 $attr_array[ $attribute_key ] = explode( ',', $attr_value );
3028 }
3029 }
3030 return $attr_array;
3031
3032 }
3033
3034
3035
3036 /**
3037 * Remove site cookie
3038 *
3039 * @param $step_id, $cookie_name, $trigger_hook, $funnel_id
3040 */
3041 public static function unset_site_cookie( $step_id, $cookie_name, $trigger_hook = '', $funnel_id = '' ) {
3042
3043 if ( ! $funnel_id ) {
3044 $funnel_id = self::get_funnel_id_from_step( $step_id );
3045 }
3046
3047 if ( ! $funnel_id ) {
3048 return false;
3049 }
3050
3051 $cookie = isset( $_COOKIE[ $cookie_name ] ) ? json_decode( wp_unslash( $_COOKIE[ $cookie_name ] ), true ) : array();
3052 if ( ! isset( $cookie['funnel_id'] ) ) {
3053 $cookie['funnel_id'] = $funnel_id;
3054 }
3055 $cookie['funnel_status'] = 'successful';
3056
3057 if ( isset( $_COOKIE[ $cookie_name ] ) ) {
3058 if ( $trigger_hook ) {
3059 do_action( $trigger_hook, $cookie );
3060 }
3061 }
3062 // unset cookie
3063 ob_start();
3064 if ( isset( $_COOKIE[ $cookie_name ] ) ) {
3065 setcookie( $cookie_name, null, strtotime( '-1 days' ), '/', COOKIE_DOMAIN );
3066 }
3067 ob_end_flush();
3068 }
3069
3070
3071 /**
3072 * Get formated product name
3073 *
3074 * @param Object $product
3075 *
3076 * @return String
3077 */
3078 public static function get_formated_product_name( $product, $formatted_attr = array() ) {
3079 $_product = wc_get_product( $product );
3080 $each_child_attr = array();
3081 $_title = '';
3082 if ( $_product ) {
3083 if ( ! $formatted_attr ) {
3084 // get_attribute_summary() only exists on variation products, not variable products
3085 if ( 'variation' === $_product->get_type() || 'subscription_variation' === $_product->get_type() ) {
3086 $attr_summary = $_product->get_attribute_summary();
3087 $attr_array = explode( ',', $attr_summary );
3088
3089 foreach ( $attr_array as $ata ) {
3090 $attr = strpbrk( $ata, ':' );
3091 $each_child_attr[] = $attr;
3092 }
3093 }
3094 } else {
3095 foreach ( $formatted_attr as $attr ) {
3096 $each_child_attr[] = ucfirst( $attr );
3097 }
3098 }
3099 if ( $each_child_attr ) {
3100 $each_child_attr_two = array();
3101 foreach ( $each_child_attr as $eca ) {
3102 $each_child_attr_two[] = str_replace( ': ', ' ', $eca );
3103 }
3104 $_title = $_product->get_title() . ' - ';
3105 $_title = $_title . implode( ', ', $each_child_attr_two );
3106 } else {
3107 $_title = $_product->get_title();
3108 }
3109 }
3110
3111 return $_title;
3112 }
3113
3114
3115 /**
3116 * Get page builder of a specific funnel by step Id from postmeta
3117 *
3118 * @param $funnel_id
3119 *
3120 * @return String $builder_name
3121 *
3122 * @since 2.0.5
3123 */
3124 public static function get_page_builder_by_step_id( $funnel_id ) {
3125 $steps = self::get_steps( $funnel_id );
3126 $builder_name = '';
3127 if ( isset( $steps[0] ) ) {
3128 $first_step_id = $steps[0]['id'];
3129 // check builder is elementor or not
3130 $elementor_page = get_post_meta( $first_step_id, '_elementor_edit_mode', true );
3131
3132 // check builder is divi or not
3133 $divi_page = get_post_meta( $first_step_id, '_et_pb_use_builder', true );
3134
3135 // check Oxygen builder is not
3136 $oxygen_page = get_post_meta( $first_step_id, 'ct_builder_shortcodes', true );
3137
3138 // check builder is bricks or not
3139 $bricks_page = get_post_meta( $first_step_id, '_bricks_editor_mode', true );
3140
3141 if ( $elementor_page ) {
3142 $builder_name = 'elementor';
3143 } elseif ( 'on' === $divi_page ) {
3144 $builder_name = 'divi-builder';
3145 } elseif ( 'bricks' === $bricks_page ) {
3146 $builder_name = 'bricks';
3147 } elseif ( ! empty( $oxygen_page ) ) {
3148 $builder_name = 'oxygen';
3149 } else {
3150 $builder_name = 'gutenberg';
3151 }
3152
3153 if ( $builder_name ) {
3154 return $builder_name;
3155 }
3156 }
3157 return $builder_name;
3158 }
3159
3160
3161 /**
3162 * Get checkout section heading settings
3163 */
3164 public static function get_checkout_section_heading_settings( $type = '', $step_id = '' ) {
3165
3166 if ( self::is_pro_license_activated() ) {
3167 if ( $step_id ) {
3168 $get_settings = get_post_meta( $step_id, '_wpfunnels_edit_field_additional_settings', true );
3169 if ( ! empty( $get_settings ) ) {
3170 if ( $type === 'billing' ) {
3171
3172 $settings = array(
3173 'custom_heading' => $get_settings['custom_billing_heading'],
3174 );
3175 return $settings;
3176 }
3177 if ( $type === 'shipping' ) {
3178 $settings = array(
3179 'custom_heading' => $get_settings['custom_shipping_heading'],
3180 );
3181 return $settings;
3182 }
3183
3184 if ( $type === 'order' ) {
3185 $settings = array(
3186 'custom_heading' => $get_settings['custom_order_detail_heading'],
3187 );
3188 return $settings;
3189 }
3190 }
3191 }
3192 }
3193 return false;
3194 }
3195
3196 /**
3197 * Check webhook addon activated or not
3198 *
3199 * @return Boolean
3200 */
3201 public static function is_webhook_activated() {
3202 if ( is_plugin_active( 'wpfunnels-pro-webhook/wpfunnels-pro-webhook.php' ) ) {
3203 return true;
3204 }
3205 return false;
3206 }
3207
3208
3209 /**
3210 * Check webhook addon license activated or not
3211 *
3212 * @return Boolean
3213 */
3214 public static function is_webhook_license_activated() {
3215
3216 if ( is_plugin_active( 'wpfunnels-pro/wpfnl-pro.php' ) ) {
3217 return true;
3218 }
3219 return false;
3220 }
3221
3222
3223 /**
3224 * Get gbf supported addons
3225 */
3226 public static function get_supported_addons() {
3227
3228 $addons = array(
3229 'global_funnel' => array(
3230 'features' => array(
3231 'trigger_options' => array(
3232 '' => __( 'Please select type..', 'wpfnl' ),
3233 'category' => __( 'Product category is ..', 'wpfnl' ),
3234 'product' => __( 'Product is ..', 'wpfnl' ),
3235 ),
3236 ),
3237 ),
3238 );
3239
3240 // 'all_product' trigger is available when GBF is active (built into Pro) or via legacy addon >= 1.0.7.
3241 if ( self::is_global_funnel_activated() || ( defined( 'WPFNL_PRO_GB_VERSION' ) && version_compare( WPFNL_PRO_GB_VERSION, '1.0.7', '>=' ) ) ) {
3242 $addons['global_funnel']['features']['trigger_options']['all_product'] = __( 'Any product is selected', 'wpfnl' );
3243 }
3244 return apply_filters( 'wpfunnels/supported-addons', $addons );
3245 }
3246
3247 /**
3248 * Get supported GBF offer condition
3249 */
3250 public static function get_supported_gbf_offer_condition() {
3251 $conditions = array(
3252 'specificProduct' => 'Specific Product',
3253 );
3254 return apply_filters( 'wpfunnels/supported-gbf-offer-condition', $conditions );
3255 }
3256
3257
3258 /**
3259 * Get product categories for GBF offer condition
3260 */
3261 public static function get_categories_gbf_offer_condition() {
3262 $categories = array();
3263 if ( self::is_wc_active() ) {
3264 $orderby = 'name';
3265 $order = 'asc';
3266 $hide_empty = false;
3267 $cat_args = array(
3268 'orderby' => $orderby,
3269 'order' => $order,
3270 'hide_empty' => $hide_empty,
3271 );
3272 $product_categories = get_terms( 'product_cat', $cat_args );
3273 if ( ! empty( $product_categories ) ) {
3274 foreach ( $product_categories as $category ) {
3275
3276 $categories[ $category->slug ] = $category->name;
3277 }
3278 }
3279 }
3280
3281 return $categories;
3282 }
3283
3284
3285 public static function oxygen_builder_version_capability() {
3286 if ( defined( 'CT_VERSION' ) && version_compare( CT_VERSION, '3.2', '>=' ) ) {
3287 return true;
3288 }
3289 return false;
3290 }
3291
3292 public static function get_funnel_utm_settings( $funnel_id = null ) {
3293 if ( $funnel_id ) {
3294 $default_enable_settings = array(
3295 'utm_enable' => 'off',
3296 'utm_source' => '',
3297 'utm_medium' => '',
3298 'utm_campaign' => '',
3299 'utm_content' => '',
3300 );
3301
3302 $utm_settings = get_post_meta( $funnel_id, '_wpfunnels_utm_params', true );
3303 return wp_parse_args( $utm_settings, $default_enable_settings );
3304 }
3305 return false;
3306 }
3307
3308 public static function get_funnel_settings( $funnel_id = null ) {
3309
3310 if ( $funnel_id ) {
3311
3312 $utm_settings = self::get_funnel_utm_settings( $funnel_id );
3313 $is_fb_pixel = get_post_meta( $funnel_id, '_wpfunnels_disabled_fb_pixel', true ) ? get_post_meta( $funnel_id, '_wpfunnels_disabled_fb_pixel', true ) : 'no';
3314 $is_gtm = get_post_meta( $funnel_id, '_wpfunnels_disabled_gtm', true ) ? get_post_meta( $funnel_id, '_wpfunnels_disabled_gtm', true ) : 'no';
3315
3316 return array(
3317 'utm_settings' => $utm_settings,
3318 'is_fb_pixel' => $is_fb_pixel,
3319 'is_gtm' => $is_gtm,
3320 );
3321 }
3322 return false;
3323
3324 }
3325
3326
3327 /**
3328 * Check discount is applicable or not in checkout page
3329 */
3330 public static function discount_on_checkout( $step_id = null ) {
3331 if ( $step_id ) {
3332 $discount = get_post_meta( $step_id, '_wpfnl_checkout_discount_main_product', true );
3333 if ( $discount ) {
3334 return $discount;
3335 }
3336 }
3337 return false;
3338 }
3339
3340
3341 /**
3342 * Calculate main product total amount in checkout page
3343 *
3344 * @param Array $checkout_product
3345 * @param Object $cart_content
3346 * @param String $step_id
3347 *
3348 * @return Mix
3349 */
3350 public static function calculate_main_product_total( $checkout_product = null, $cart_content = null, $step_id = '' ) {
3351
3352 if ( $checkout_product && $cart_content ) {
3353 $total = 0;
3354
3355 foreach ( $cart_content as $key => $content ) {
3356 if ( isset( $content['wpfnl_order_bump'] ) && $content['wpfnl_order_bump'] ) {
3357 continue;
3358 }
3359 $product_id = '';
3360 if ( ! empty( $content['variation_id'] ) ) {
3361 $product_id = $content['variation_id'];
3362 }
3363
3364 if ( ! $product_id ) {
3365 $product_id = isset( $content['product_id'] ) ? $content['product_id'] : '';
3366 }
3367
3368 $checkout_product = maybe_unserialize( $checkout_product );
3369 $key = array_search( $product_id, array_column( $checkout_product, 'id' ) );
3370 if ( $key !== false ) {
3371 if ( wc_tax_enabled() ) {
3372 if ( ! wc_prices_include_tax() ) {
3373 $content['line_total'] = number_format( $content['line_total'], 2 );
3374 $content['line_total'] = filter_var( $content['line_total'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION );
3375 $total = $total + $content['line_total'];
3376 } else {
3377
3378 $content['line_total'] = number_format( $content['line_total'], 2 );
3379 $content['line_total'] = filter_var( $content['line_total'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION );
3380 $total = $total + $content['line_total'] + $content['line_tax'];
3381
3382 }
3383 } else {
3384 $content['line_total'] = number_format( $content['line_total'], 2 );
3385 $content['line_total'] = filter_var( $content['line_total'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION );
3386 $total = $total + $content['line_total'] + $content['line_tax'];
3387 }
3388 }
3389 }
3390
3391 $total = number_format( $total, 2 );
3392 $total = filter_var( $total, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION );
3393 return $total;
3394 }
3395 return false;
3396 }
3397
3398
3399
3400 /**
3401 * Calculate cart total amount in checkout page
3402 *
3403 * @param Array $checkout_product
3404 * @param Object $cart_content
3405 * @param String $step_id
3406 *
3407 * @return bool|int|float
3408 */
3409 public static function calculate_cart_total( $checkout_product = null, $cart_content = null, $step_id = '' ) {
3410
3411 if ( $checkout_product && $cart_content ) {
3412 $total = 0;
3413
3414 foreach ( $cart_content as $key => $content ) {
3415 $product_id = $content['variation_id'] ? $content['variation_id'] : $content['product_id'];
3416
3417 if ( wc_tax_enabled() ) {
3418 if ( ! wc_prices_include_tax() ) {
3419 $content['line_total'] = number_format( $content['line_total'], 2 );
3420 $content['line_total'] = filter_var( $content['line_total'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION );
3421 $total = $total + $content['line_total'];
3422 } else {
3423 $content['line_total'] = number_format( $content['line_total'], 2 );
3424 $content['line_total'] = filter_var( $content['line_total'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION );
3425 $total = $total + $content['line_total'] + $content['line_tax'];
3426
3427 }
3428 } else {
3429 $content['line_total'] = number_format( $content['line_total'], 2 );
3430 $content['line_total'] = filter_var( $content['line_total'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION );
3431 $total = $total + $content['line_total'] + $content['line_tax'];
3432
3433 }
3434 }
3435 $total = number_format( $total, 2 );
3436 $total = filter_var( $total, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION );
3437 return $total;
3438 }
3439 return false;
3440 }
3441
3442
3443
3444 /**
3445 * Check array type ( multi-dimentional or one dimentional )
3446 *
3447 * @param Array
3448 *
3449 * @return Bool
3450 */
3451 public static function check_array_is_multidimentional( $multi_array = null ) {
3452
3453 if ( $multi_array ) {
3454 foreach ( $multi_array as $array ) {
3455 if ( is_array( $array ) ) {
3456 return true;
3457 } else {
3458 return false;
3459 }
3460 }
3461 return true;
3462 }
3463 return false;
3464 }
3465
3466
3467 /**
3468 * Migrate old Order bump settings with multiple order bump
3469 */
3470 public static function migrate_order_bump( $prev_settings = null, $step_id = null ) {
3471 if ( $prev_settings && $step_id ) {
3472 $multi_settings = array();
3473 if ( ! self::check_array_is_multidimentional( $prev_settings ) ) {
3474 $multi_settings = array(
3475 $prev_settings,
3476 );
3477 update_post_meta( $step_id, 'order-bump-settings', $multi_settings );
3478 return $multi_settings;
3479 }
3480 }
3481 return false;
3482 }
3483
3484
3485 /**
3486 * N:B test case should be implemented
3487 * Get order bump settings by step id
3488 */
3489 public static function get_ob_settings( $step_id ) {
3490
3491 $all_settings = get_post_meta( $step_id, 'order-bump-settings', true ) ? get_post_meta( $step_id, 'order-bump-settings', true ) : array();
3492 $is_multiple = self::check_array_is_multidimentional( $all_settings );
3493
3494 if ( ! $is_multiple && $all_settings ) {
3495 $all_settings['name'] = 'Order bump';
3496 $all_settings = self::migrate_order_bump( $all_settings, $step_id );
3497 }
3498
3499 if ( is_array( $all_settings ) && count( $all_settings ) > 0 ) {
3500 foreach ( $all_settings as $key => $settings ) {
3501 if ( ! isset( $all_settings[ $key ]['discountOption'] ) && 'original' !== $all_settings[ $key ]['discountOption'] ) {
3502 $product = wc_get_product( $all_settings[ $key ]['product'] );
3503 if ( $product ) {
3504 if ( $product->is_on_sale() ) {
3505 $price = wc_format_sale_price( $product->get_regular_price() * $all_settings[ $key ]['quantity'], $product->get_sale_price() ? $product->get_sale_price() * $all_settings[ $key ]['quantity'] : $product->get_regular_price() * $all_settings[ $key ]['quantity'] );
3506 } else {
3507 $price = $product->get_regular_price();
3508 if ( is_plugin_active( 'woocommerce-subscriptions/woocommerce-subscriptions.php' ) ) {
3509 $signUpFee = \WC_Subscriptions_Product::get_sign_up_fee( $product );
3510 $price = $price + $signUpFee;
3511 }
3512 $price = wc_price( $price * $all_settings[ $key ]['quantity'] );
3513 }
3514 $all_settings[ $key ]['htmlPrice'] = $price ? $price : $all_settings[ $key ]['htmlPrice'];
3515 }
3516 }
3517 }
3518 return $all_settings;
3519 }
3520
3521 return array();
3522 }
3523
3524
3525 /**
3526 * Check if funnel canvas or not
3527 *
3528 * @return bool
3529 * @since 2.3.8
3530 */
3531 public static function is_funnel_canvas_window() {
3532 if ( is_admin() && isset( $_GET['page'] ) && 'edit_funnel' === $_GET['page'] ) {
3533 return true;
3534 }
3535 return false;
3536 }
3537
3538
3539 /**
3540 * Get all funnel from post
3541 *
3542 * @return $funnels
3543 * @since 2.4.4
3544 */
3545 public static function get_all_funnels( $status = 'publish' ) {
3546
3547 if ( 'all' == $status ) {
3548 $arg = array(
3549 'post_type' => WPFNL_FUNNELS_POST_TYPE,
3550 'fields' => 'ID',
3551 'orderby' => 'date',
3552 'order' => 'ASC',
3553 'numberposts' => -1,
3554 );
3555 } else {
3556 $arg = array(
3557 'post_type' => WPFNL_FUNNELS_POST_TYPE,
3558 'post_status' => $status,
3559 'fields' => 'ID',
3560 'orderby' => 'date',
3561 'order' => 'ASC',
3562 'numberposts' => -1,
3563 );
3564 }
3565
3566 $funnels = get_posts( $arg );
3567 return $funnels;
3568 }
3569
3570
3571 /**
3572 * Get all funnel from post
3573 *
3574 * @return $funnels
3575 * @since 2.4.4
3576 */
3577 public static function get_all_steps() {
3578
3579 $steps = get_posts(
3580 array(
3581 'post_type' => 'wpfunnel_steps',
3582 'post_status' => 'publish',
3583 'fields' => 'ID',
3584 'orderby' => 'date',
3585 'order' => 'ASC',
3586 )
3587 );
3588 return $steps;
3589 }
3590
3591
3592 /**
3593 * Add type meta for each funnel
3594 *
3595 * @since 2.4.4
3596 */
3597 public static function add_type_meta() {
3598
3599 $is_added = get_option( '_wpfnl_added_type_meta' );
3600 if ( ! $is_added ) {
3601 update_option( '_wpfnl_added_type_meta', 'yes' );
3602 $funnels = self::get_all_funnels();
3603 foreach ( $funnels as $funnel ) {
3604 $type = get_post_meta( $funnel->ID, '_wpfnl_funnel_type', true );
3605 if ( ! $type ) {
3606 update_post_meta( $funnel->ID, '_wpfnl_funnel_type', 'wc' );
3607 }
3608 }
3609 }
3610
3611 }
3612
3613
3614 /**
3615 * Enable lms settings
3616 *
3617 * @return Bool
3618 * @since 2.4.5
3619 */
3620 public static function is_enable_lms_settings() {
3621 $status = get_option( 'wpfunnels_pro_lms_license_status' );
3622 if ( $status === 'active' ) {
3623 return apply_filters( 'wpfunnels/is_enable_lms_settings', false );
3624 }
3625 return false;
3626 }
3627
3628 /**
3629 * Check if lms addon activated or not
3630 *
3631 * @return Bool
3632 * @since 2.4.5
3633 */
3634 public static function is_lms_addon_active() {
3635
3636 if ( in_array( 'wpfunnels-pro-lms/wpfunnels-pro-lms.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) {
3637 return true;
3638 } elseif ( function_exists( 'is_plugin_active' ) ) {
3639 if ( is_plugin_active( 'wpfunnels-pro-lms/wpfunnels-pro-lms.php' ) ) {
3640 return true;
3641 }
3642 }
3643 return false;
3644 }
3645
3646 /**
3647 * Check if any LMS plugin is active (LearnDash or CreatorLMS)
3648 *
3649 * @return Bool
3650 * @since 2.0.0
3651 */
3652 public static function is_any_lms_plugin_active() {
3653 // Check LearnDash
3654 $is_learndash = in_array( 'sfwd-lms/sfwd_lms.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) );
3655
3656 // Check CreatorLMS
3657 $is_creatorlms = in_array( 'creatorlms/creatorlms.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) );
3658
3659 // For multisite
3660 if ( function_exists( 'is_plugin_active' ) && ! $is_learndash && ! $is_creatorlms ) {
3661 $is_learndash = is_plugin_active( 'sfwd-lms/sfwd_lms.php' );
3662 $is_creatorlms = is_plugin_active( 'creatorlms/creatorlms.php' );
3663 }
3664
3665 return $is_learndash || $is_creatorlms;
3666 }
3667
3668
3669 /**
3670 * Get checkout tabs column name
3671 *
3672 * @return Array
3673 * @since 2.4.5
3674 */
3675 public static function get_checkout_columns( $step_id ) {
3676
3677 $funnel_id = get_post_meta( $step_id, '_funnel_id', true );
3678 $columns = array(
3679 'product-name' => 'Product',
3680 'product-price' => 'Price',
3681 'product-quantity' => 'Quantity',
3682 'total-price' => 'Total Price',
3683 'product-action' => 'Actions',
3684 );
3685
3686 return apply_filters( 'wpfunnels/checkout_columns', $columns, $funnel_id );
3687 }
3688
3689
3690 /**
3691 * Supported order bump position
3692 *
3693 * @return Array
3694 * @since 2.4.5
3695 */
3696 public static function supported_orderbump_position( $step_id ) {
3697 $funnel_id = get_post_meta( $step_id, '_funnel_id', true );
3698
3699 $positions = array(
3700 array(
3701 'name' => 'Before Order Details',
3702 'value' => 'before-order',
3703 ),
3704 array(
3705 'name' => 'After Order Details',
3706 'value' => 'after-order',
3707 ),
3708 array(
3709 'name' => 'Before Checkout Details',
3710 'value' => 'before-checkout',
3711 ),
3712 array(
3713 'name' => 'After Customer Details',
3714 'value' => 'after-customer-details',
3715 ),
3716 array(
3717 'name' => 'Before Payment Options',
3718 'value' => 'before-payment',
3719 ),
3720 array(
3721 'name' => 'After Payment Options',
3722 'value' => 'after-payment',
3723 ),
3724 array(
3725 'name' => 'Pop-up offer',
3726 'value' => 'popup',
3727 ),
3728 );
3729 if ( $funnel_id ) {
3730 return apply_filters( 'wpfunnels/ob_positions', $positions, $funnel_id );
3731 }
3732
3733 return $positions;
3734
3735 }
3736
3737 /**
3738 * Get time zone in string
3739 *
3740 * @return String
3741 * @since 2.4.6
3742 */
3743 public static function wpfnl_timezone_string() {
3744
3745 // If site timezone string exists, return it.
3746 $timezone = get_option( 'timezone_string' );
3747 if ( $timezone ) {
3748 return $timezone;
3749 }
3750
3751 // Get UTC offset, if it isn't set then return UTC.
3752 $utc_offset = floatval( get_option( 'gmt_offset', 0 ) );
3753 if ( ! is_numeric( $utc_offset ) || 0.0 === $utc_offset ) {
3754 return 'UTC';
3755 }
3756
3757 // Adjust UTC offset from hours to seconds.
3758 $utc_offset = (int) ( $utc_offset * 3600 );
3759
3760 // Attempt to guess the timezone string from the UTC offset.
3761 $timezone = timezone_name_from_abbr( '', $utc_offset );
3762 if ( $timezone ) {
3763 return $timezone;
3764 }
3765
3766 // Last try, guess timezone string manually.
3767 foreach ( timezone_abbreviations_list() as $abbr ) {
3768 foreach ( $abbr as $city ) {
3769 // WordPress restrict the use of date(), since it's affected by timezone settings, but in this case is just what we need to guess the correct timezone.
3770 if ( (bool) date( 'I' ) === (bool) $city['dst'] && $city['timezone_id'] && intval( $city['offset'] ) === $utc_offset ) { // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
3771 return $city['timezone_id'];
3772 }
3773 }
3774 }
3775
3776 // Fallback to UTC.
3777 return 'UTC';
3778 }
3779
3780
3781
3782 /**
3783 * Get timezone offset in seconds.
3784 *
3785 * @since 2.4.6
3786 * @return float
3787 */
3788 public static function wpfnl_timezone_offset() {
3789 $timezone = get_option( 'timezone_string' );
3790 if ( $timezone ) {
3791 $timezone_object = new DateTimeZone( $timezone );
3792 return $timezone_object->getOffset( new DateTime( 'now' ) );
3793 } else {
3794 return floatval( get_option( 'gmt_offset', 0 ) ) * HOUR_IN_SECONDS;
3795 }
3796 }
3797
3798
3799 /**
3800 * Check if integrations addon activated or not
3801 *
3802 * @return Bool
3803 * @since 2.4.7
3804 */
3805 public static function is_integrations_addon_active() {
3806 if ( in_array( 'wpfunnels-pro-integrations/wpfunnels-pro-integrations.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) {
3807 return true;
3808 } elseif ( function_exists( 'is_plugin_active' ) ) {
3809 if ( is_plugin_active( 'wpfunnels-pro-integrations/wpfunnels-pro-integrations.php' ) ) {
3810 return true;
3811 }
3812 }
3813 return false;
3814 }
3815
3816
3817 /**
3818 * Get template types
3819 *
3820 * @return array
3821 * @since 2.4.11
3822 */
3823 public static function get_template_types() {
3824 $general_settings = self::get_general_settings();
3825 $types = array();
3826
3827 // Add WooCommerce templates if WooCommerce is active
3828 $types[] = array(
3829 'slug' => 'wc',
3830 'label' => 'Sales Templates',
3831 );
3832
3833 // Add LMS templates if LMS add-on and at least one LMS plugin (LearnDash or CreatorLMS) is active
3834 if ( self::is_lms_addon_active() && self::is_any_lms_plugin_active() ) {
3835 $types[] = array(
3836 'slug' => 'lms',
3837 'label' => 'LMS Templates',
3838 );
3839 }
3840
3841 // Lead generation templates are always available
3842 $types[] = array(
3843 'slug' => 'lead',
3844 'label' => 'Lead Gen Templates',
3845 );
3846
3847 return apply_filters( 'wpfunnels/modify_template_type', $types );
3848
3849 }
3850
3851
3852 /**
3853 * May be allow to create sales funnel
3854 *
3855 * @return Bool
3856 */
3857 public static function maybe_allow_sales_funnel() {
3858 if ( self::is_wc_active() || self::is_lms_addon_active() ) {
3859 return true;
3860 }
3861 return false;
3862 }
3863
3864 /**
3865 * Retrieve all user role
3866 *
3867 * @return Array
3868 */
3869 public static function get_all_user_roles() {
3870 global $wp_roles;
3871
3872 $all_roles = isset( $wp_roles->roles ) ? $wp_roles->roles : array();
3873 $editable_roles = apply_filters( 'editable_roles', $all_roles );
3874
3875 return $editable_roles;
3876 }
3877
3878
3879 /**
3880 * Retrive all the product tags
3881 *
3882 * @return array
3883 * @since 2.4.11
3884 */
3885 public static function get_all_tags() {
3886 $term_array = array();
3887 if ( self::is_wc_active() ) {
3888 $terms = get_terms( 'product_tag' );
3889 if ( ! empty( $terms ) && ! is_wp_error( $terms ) ) {
3890 foreach ( $terms as $term ) {
3891
3892 $term_array[] = array(
3893 'id' => isset( $term->term_id ) ? $term->term_id : '',
3894 'name' => isset( $term->name ) ? $term->name : '',
3895 );
3896 }
3897 }
3898 }
3899 return $term_array;
3900 }
3901
3902 /**
3903 * Get all shiiping classes
3904 *
3905 * @return array
3906 * @since 2.4.10
3907 */
3908 public static function get_shipping_classes() {
3909
3910 if ( self::is_wc_active() ) {
3911 $shipping_classes = WC()->shipping()->get_shipping_classes();
3912 if ( $shipping_classes ) {
3913 return $shipping_classes;
3914 }
3915 }
3916 return array();
3917 }
3918
3919
3920 /**
3921 * Maybe global funnel
3922 *
3923 * @param String
3924 *
3925 * @return String
3926 *
3927 * @since 2.4.14
3928 */
3929 public static function maybe_global_funnel( $step_id = '' ) {
3930 if ( $step_id ) {
3931 $funnel_id = get_post_meta( $step_id, '_funnel_id', true );
3932 $is_gbf = get_post_meta( $funnel_id, 'is_global_funnel', true );
3933 if ( $is_gbf ) {
3934 return $is_gbf;
3935 }
3936 }
3937 return 'no';
3938 }
3939
3940
3941 /**
3942 * Get Woocommerce billing/shipping countries
3943 *
3944 * @return array
3945 * @since 2.4.18
3946 */
3947 public static function get_countries() {
3948 if ( self::is_wc_active() ) {
3949 $wc_countries = new \WC_Countries();
3950 $countries = $wc_countries->get_countries();
3951 if ( $countries ) {
3952 return $countries;
3953 }
3954 }
3955 return array();
3956 }
3957
3958
3959 /**
3960 * Resolve a checkout step setting that builders expose under two different keys.
3961 *
3962 * Every builder persists these settings to the underscore-prefixed post meta
3963 * (`_wpfnl_checkout_layout`, `_wpfnl_floating_label`). The Gutenberg checkout
3964 * block instead publishes its live attributes through the
3965 * `wpfunnels/checkout_meta_{$key}` filter, keyed without the underscore, so that
3966 * the editor preview reflects unsaved changes.
3967 *
3968 * Read the filtered value first so the block editor keeps winning, then fall back
3969 * to the stored meta so Elementor, Bricks, Divi, Oxygen and shortcode steps —
3970 * which register no such filter — resolve to their real setting instead of the
3971 * default.
3972 *
3973 * @param int $checkout_id Checkout step ID.
3974 * @param string $key Unprefixed meta key, e.g. `wpfnl_checkout_layout`.
3975 * @param string $default Value to use when nothing is set anywhere.
3976 *
3977 * @return string
3978 * @since 3.13.2
3979 */
3980 public static function get_checkout_step_setting( $checkout_id, $key, $default = '' ) {
3981 if ( ! $checkout_id ) {
3982 return $default;
3983 }
3984
3985 // Passing false as the default keeps get_checkout_meta_value() from
3986 // substituting one, so an empty result really means "no builder filter".
3987 $value = Wpfnl::get_instance()->meta->get_checkout_meta_value( $checkout_id, $key, false );
3988
3989 if ( ! $value ) {
3990 $value = get_post_meta( $checkout_id, '_' . $key, true );
3991 }
3992
3993 return $value ? $value : $default;
3994 }
3995
3996
3997 /**
3998 * Get the checkout layout for a step.
3999 *
4000 * @param int $checkout_id Checkout step ID.
4001 * @param string $default Layout to use when nothing is set.
4002 *
4003 * @return string
4004 * @since 3.13.2
4005 */
4006 public static function get_checkout_layout( $checkout_id, $default = 'wpfnl-col-2' ) {
4007 return self::get_checkout_step_setting( $checkout_id, 'wpfnl_checkout_layout', $default );
4008 }
4009
4010
4011 /**
4012 * Check the checkout layout is express or not
4013 *
4014 * @return Bool
4015 * @since 2.4.18
4016 */
4017 public static function maybe_express_checkout( $checkout_id = '' ) {
4018 if ( ! $checkout_id ) {
4019 global $post;
4020 if ( ! $post ) {
4021 return false;
4022 }
4023 if ( wp_doing_ajax() || isset( $_GET['wc-ajax'] ) ) {
4024 $checkout_id = self::get_checkout_id_from_post( $_POST );
4025 } else {
4026 $checkout_id = $post->ID;
4027 }
4028 if ( ! $checkout_id ) {
4029 $checkout_id = get_the_ID();
4030 }
4031 }
4032
4033 $layout = get_post_meta( $checkout_id, '_wpfnl_checkout_layout', true );
4034
4035 return 'wpfnl-express-checkout' === $layout;
4036 }
4037
4038
4039
4040 /**
4041 * Sanitize request data
4042 *
4043 * @param $data
4044 *
4045 * @return Mix
4046 */
4047 public static function get_sanitized_get_post( $data = array() ) {
4048 if ( is_array( $data ) && ! empty( $data ) ) {
4049 return filter_var_array( $data, FILTER_SANITIZE_FULL_SPECIAL_CHARS );
4050 }
4051 return array(
4052 'get' => filter_input_array( INPUT_GET, FILTER_SANITIZE_FULL_SPECIAL_CHARS ),
4053 'post' => filter_input_array( INPUT_POST, FILTER_SANITIZE_FULL_SPECIAL_CHARS ),
4054 'request' => filter_var_array( $_REQUEST, FILTER_SANITIZE_FULL_SPECIAL_CHARS ),
4055 );
4056 }
4057
4058
4059
4060 /**
4061 * Delete template transients
4062 *
4063 * @since 2.5.1
4064 */
4065 public static function wpfnl_delete_transient() {
4066 delete_option( WPFNL_TEMPLATES_OPTION_KEY . '_wc' );
4067 delete_option( WPFNL_TEMPLATES_OPTION_KEY . '_lms' );
4068 delete_option( WPFNL_TEMPLATES_OPTION_KEY . '_lead' );
4069
4070 delete_transient( 'wpfunnels_remote_template_data_wc_' . WPFNL_VERSION );
4071 delete_transient( 'wpfunnels_remote_template_data_lms_' . WPFNL_VERSION );
4072 delete_transient( 'wpfunnels_remote_template_data_lead_' . WPFNL_VERSION );
4073 delete_transient( 'wpfunnels_remote_template_data_store_checkout_' . WPFNL_VERSION );
4074 }
4075
4076
4077 /**
4078 * Get all the published funnel lists
4079 *
4080 * @return Array $funnels
4081 * @since 2.5.6
4082 */
4083 public static function get_funnel_list() {
4084 $funnels = get_posts(
4085 array(
4086 'post_type' => WPFNL_FUNNELS_POST_TYPE,
4087 'post_status' => 'publish',
4088 'numberposts' => -1,
4089 'fields' => 'ID',
4090 'orderby' => 'date',
4091 'order' => 'ASC',
4092 )
4093 );
4094 $formatted_funnel_data[' '] = __( 'Select funnel', 'wpfnl' );
4095 if ( is_array( $funnels ) ) {
4096 foreach ( $funnels as $funnel ) {
4097 $is_gbf = get_post_meta( $funnel->ID, 'is_global_funnel', true );
4098 if ( ! $is_gbf || 'no' === $is_gbf ) {
4099 $formatted_funnel_data[ self::get_funnel_link( $funnel->ID ) ] = $funnel->post_title;
4100 }
4101 }
4102 }
4103 return $formatted_funnel_data;
4104 }
4105
4106 /**
4107 * Check the log status is enabled or not
4108 *
4109 * @return Bool
4110 * @since 2.5.9
4111 */
4112 public static function maybe_logger_enabled() {
4113 $general_settings = get_option( '_wpfunnels_general_settings' );
4114 if ( $general_settings && isset( $general_settings['enable_log_status'] ) ) {
4115 return 'on' === $general_settings['enable_log_status'];
4116 }
4117 return false;
4118 }
4119
4120
4121
4122 /**
4123 * Check user permission to allow WPFunnels
4124 *
4125 * @param Array $permitted_role
4126 *
4127 * @return String $role
4128 * @since 2.6.2
4129 */
4130 public static function role_permission_to_allow_wpfunnel( $permitted_role = array() ) {
4131 return 'wpf_manage_funnels';
4132 }
4133
4134
4135 /**
4136 * Get supported step type
4137 * Landing, Checkout, Thankyou steps are supported for free
4138 *
4139 * @return Array $types
4140 * @since 2.5.7
4141 */
4142 public static function get_supported_step_type() {
4143 $types = array(
4144 array(
4145 'type' => 'landing',
4146 'name' => 'Landing',
4147 ),
4148 array(
4149 'type' => 'checkout',
4150 'name' => 'Checkout',
4151 ),
4152 array(
4153 'type' => 'upsell',
4154 'name' => 'Upsell',
4155 ),
4156 array(
4157 'type' => 'thankyou',
4158 'name' => 'Thankyou',
4159 ),
4160 );
4161
4162 return apply_filters( 'wpfunnels/supported_step_type', $types );
4163 }
4164
4165
4166 /**
4167 * Check if mint crm is installed
4168 *
4169 * @return bool
4170 * @since 1.0.0
4171 */
4172 public static function is_mint_mrm_active() {
4173 return apply_filters( 'is_mail_mint_pro_active', false );
4174 }
4175
4176 public static function is_mail_mint_free_active() {
4177 if ( is_plugin_active( 'mail-mint/mail-mint.php' ) ) {
4178 return true;
4179 }
4180 return false;
4181 }
4182
4183 public static function is_mail_mint_pro_license_active() {
4184 return apply_filters( 'is_mail_mint_pro_license_active', false );
4185 }
4186
4187 /**
4188 * Check if mint automation exist for a funnel by funnel id.
4189 *
4190 * @param int $funnel_id
4191 * @return bool
4192 * @since 1.0.0
4193 */
4194 public static function maybe_automation_exist_for_a_funnel( $funnel_id ) {
4195 return apply_filters( 'wpfunnels/maybe_automation_exist_for_a_funnel', false, $funnel_id );
4196 }
4197
4198 /**
4199 * Retrieve all automations
4200 *
4201 * @param int $page Current page number
4202 * @param int $per_page Number of items per page
4203 * @return array Array of automation data with pagination info
4204 * @since 3.7.0
4205 */
4206 public static function retrieve_all_automations( $page = 1, $per_page = 10 ){
4207 return apply_filters( 'wpfunnels/retrieve_all_automations', array(
4208 'data' => array(),
4209 'total' => 0,
4210 'total_pages' => 0,
4211 'current_page' => $page,
4212 'per_page' => $per_page,
4213 ), $page, $per_page );
4214 }
4215
4216 /**
4217 * Supported steps for allow settings
4218 *
4219 * @return array
4220 * @since 2.6.6
4221 */
4222 public static function get_settings_steps() {
4223 $settings_steps = array(
4224 'upsell',
4225 'downsell',
4226 'checkout',
4227 'thankyou',
4228 'delay',
4229 'sendMail',
4230 'createUser',
4231 'addTag',
4232 'addList',
4233 'removeTag',
4234 'removeList',
4235 'twilioSendMessage',
4236 'wpf_optin_submit',
4237 'wpf_order_placed',
4238 'wpf_cta_triggered',
4239 'wpf_order_bump_accepted',
4240 'wpf_offer_trigger',
4241 );
4242
4243 return apply_filters( 'wpfunnels/supported_settings_steps', $settings_steps );
4244 }
4245
4246
4247 /**
4248 * Supported steps for allow only settings
4249 *
4250 * @return array
4251 * @since 2.6.6
4252 */
4253 public static function get_only_settings_steps() {
4254 $settings_steps = array(
4255 'delay',
4256 'sendMail',
4257 'createUser',
4258 'addTag',
4259 'addList',
4260 'removeTag',
4261 'removeList',
4262 'twilioSendMessage',
4263 'wpf_optin_submit',
4264 'wpf_order_placed',
4265 'wpf_cta_triggered',
4266 'wpf_order_bump_accepted',
4267 'wpf_offer_trigger',
4268 );
4269
4270 return apply_filters( 'wpfunnels/supported_only_settings_steps', $settings_steps );
4271 }
4272
4273
4274 /**
4275 *
4276 */
4277 public static function get_default_automation() {
4278 $automation_data = array(
4279 'name' => 'automation-1',
4280 'status' => 'active',
4281 'author' => 1,
4282 'trigger_name' => 'wpf-offer-trigger',
4283 'steps' => array(
4284 array(
4285 'step_id' => 'mg3e1i',
4286 'key' => 'wp_user_login',
4287 'type' => 'trigger',
4288 'settings' => array(),
4289 'next_step_id' => 'zytv4',
4290 ),
4291 array(
4292 'step_id' => 'zytv4',
4293 'key' => 'createUser',
4294 'type' => 'action',
4295 'settings' => array(),
4296 'next_step_id' => '9w8vjh',
4297 ),
4298 array(
4299 'step_id' => '9w8vjh',
4300 'key' => 'addTag',
4301 'type' => 'action',
4302 'settings' => array(
4303 'tag_settings' => array(
4304 'tags' => array(
4305 array(
4306 'id' => 3,
4307 'title' => 'demo',
4308 ),
4309 ),
4310 ),
4311 ),
4312 'next_step_id' => '',
4313 ),
4314 ),
4315 'created_ago' => '3 seconds',
4316 );
4317
4318 return $automation_data;
4319 }
4320
4321
4322 /**
4323 * Get mint triggers
4324 *
4325 * @return array
4326 */
4327 public static function get_mint_triggers() {
4328 $triggers = array(
4329 'wpf_optin_submit',
4330 'wpf_order_placed',
4331 'wpf_cta_triggered',
4332 'wpf_order_bump_accepted',
4333 'wpf_offer_trigger',
4334 );
4335
4336 return apply_filters( 'wpfunnels/mint_triggers', $triggers );
4337 }
4338
4339
4340 /**
4341 * Get mint actions
4342 *
4343 * @return array
4344 */
4345 public static function get_mint_actions() {
4346 $actions = array(
4347 'delay',
4348 'sendMail',
4349 'createUser',
4350 'addTag',
4351 'addList',
4352 'removeTag',
4353 'removeList',
4354 'twilioSendMessage',
4355 );
4356
4357 return apply_filters( 'wpfunnels/mint_actions', $actions );
4358 }
4359
4360
4361 /**
4362 * Get all tags/list from mint
4363 *
4364 * @return array
4365 */
4366 public static function get_mint_contact_groups( $type = 'tags' ) {
4367 $class_name = 'Mint\\MRM\\DataBase\\Models\\ContactGroupModel';
4368 if ( class_exists( $class_name ) ) {
4369
4370 $groups = $class_name::get_all_lists_or_tags( $type );
4371 if ( $groups && is_array( $groups ) ) {
4372 $formatted_groups = array();
4373 $i = 1;
4374 foreach ( $groups as $group ) {
4375 $data = array(
4376 'index' => $i,
4377 'title' => $group['title'],
4378 'id' => $group['id'],
4379 'checked' => false,
4380 );
4381 array_push( $formatted_groups, $data );
4382 $i++;
4383 }
4384
4385 return $formatted_groups;
4386 }
4387 }
4388 return array();
4389 }
4390
4391
4392 /**
4393 * Get selected steps
4394 *
4395 * @param string $type
4396 * @param int $funnel_id
4397 *
4398 * @return array
4399 */
4400 public static function get_selected_steps( $type, $funnel_id ) {
4401 if ( $funnel_id ) {
4402 $steps = get_post_meta( $funnel_id, '_steps', true );
4403 if ( $steps && is_array( $steps ) ) {
4404 $i = 1;
4405 $formatted_steps = array();
4406 $step_types = array( $type );
4407 if ( 'landing' == $type ) {
4408 $step_types = array( $type, 'custom' );
4409 }
4410 foreach ( $steps as $step ) {
4411 if ( in_array( $step['step_type'], $step_types ) ) {
4412 $data = array(
4413 'id' => $i,
4414 'title' => $step['name'],
4415 'value' => $step['id'],
4416 );
4417 array_push( $formatted_steps, $data );
4418 $i++;
4419 }
4420 }
4421
4422 return $formatted_steps;
4423 }
4424 }
4425 return array();
4426 }
4427
4428
4429 /**
4430 * Get all order bump of a funnel
4431 *
4432 * @param int $funnel_id
4433 *
4434 * @return array
4435 */
4436 public static function get_all_ob( $funnel_id ) {
4437 if ( $funnel_id ) {
4438 $steps = self::get_steps( $funnel_id );
4439 if ( $steps && is_array( $steps ) ) {
4440 $key = array_search( 'checkout', array_column( $steps, 'step_type' ) );
4441 if ( false !== $key ) {
4442 if ( isset( $steps[ $key ]['id'] ) ) {
4443 $checkout_step_id = $steps[ $key ]['id'];
4444 $order_bumps = get_post_meta( $checkout_step_id, 'order-bump-settings', true );
4445 if ( $order_bumps && is_array( $order_bumps ) ) {
4446 $i = 1;
4447 $formatted_obs = array();
4448 foreach ( $order_bumps as $index => $ob ) {
4449 if ( is_array( $ob ) && isset( $ob['name'] ) ) {
4450 $data = array(
4451 'id' => $i,
4452 'title' => $ob['name'],
4453 'value' => $index,
4454 );
4455 array_push( $formatted_obs, $data );
4456 $i++;
4457 }
4458 }
4459 return $formatted_obs;
4460 }
4461 }
4462 }
4463 }
4464 }
4465 return array();
4466 }
4467
4468
4469
4470 /**
4471 * Get sequences from campaign table
4472 *
4473 * @return array
4474 * @since 1.0.0
4475 */
4476 public static function get_sequences() {
4477 if ( self::is_mint_mrm_active() && class_exists( 'Mint\\MRM\\DataBase\\Tables\\CampaignSchema' ) ) {
4478
4479 global $wpdb;
4480 $class_name = 'Mint\\MRM\\DataBase\\Tables\\CampaignSchema';
4481 $campaign_table = $wpdb->prefix . $class_name::$campaign_table;
4482 $results = $wpdb->get_results( $wpdb->prepare( "SELECT id as value, title as label FROM $campaign_table WHERE type = %s AND status = %s ", 'automation', 'created' ), ARRAY_A ); // phpcs:ignore.
4483 $default = array(
4484 array(
4485 'value' => '',
4486 'label' => 'Select Sequence',
4487 ),
4488 );
4489 if ( ! empty( $results ) ) {
4490 return array_merge( $default, $results );
4491 } else {
4492 return $default;
4493 }
4494 }
4495 return array();
4496 }
4497
4498
4499 /**
4500 * Get CFE deafult fields
4501 *
4502 * @return array
4503 * @since 2.7.0
4504 */
4505 public static function get_cfe_default_fields() {
4506 if ( self::is_wc_active() ) {
4507 $fields = array();
4508 $billing_default_field = WC()->countries->get_address_fields();
4509 foreach ( $billing_default_field as $key => $sf ) {
4510 $billing_default_field[ $key ]['enable'] = 1;
4511 $billing_default_field[ $key ]['show'] = 1;
4512 $billing_default_field[ $key ]['delete'] = 0;
4513 if ( ! ( isset( $sf['type'] ) ) ) {
4514 $billing_default_field[ $key ]['type'] = null;
4515 }
4516 if ( ! ( isset( $sf['placeholder'] ) ) ) {
4517 $billing_default_field[ $key ]['placeholder'] = null;
4518 }
4519 if ( ! ( isset( $sf['label'] ) ) ) {
4520 $billing_default_field[ $key ]['label'] = null;
4521 }
4522 if ( ! ( isset( $sf['validate'] ) ) ) {
4523 $billing_default_field[ $key ]['validate'] = null;
4524 }
4525 if ( ! ( isset( $sf['default'] ) ) ) {
4526 $billing_default_field[ $key ]['default'] = null;
4527 }
4528 }
4529
4530 $countries = new \WC_Countries();
4531 $shipping_default_fields = $countries->get_address_fields( $countries->get_base_country(), 'shipping_' );
4532 foreach ( $shipping_default_fields as $key => $sf ) {
4533 $shipping_default_fields[ $key ]['enable'] = 1;
4534 $shipping_default_fields[ $key ]['show'] = 1;
4535 $shipping_default_fields[ $key ]['delete'] = 0;
4536 if ( ! ( isset( $sf['type'] ) ) ) {
4537 $shipping_default_fields[ $key ]['type'] = null;
4538 }
4539 if ( ! ( isset( $sf['placeholder'] ) ) ) {
4540 $shipping_default_fields[ $key ]['placeholder'] = null;
4541 }
4542 if ( ! ( isset( $sf['label'] ) ) ) {
4543 $shipping_default_fields[ $key ]['label'] = null;
4544 }
4545 if ( ! ( isset( $sf['validate'] ) ) ) {
4546 $shipping_default_fields[ $key ]['validate'] = null;
4547 }
4548 if ( ! ( isset( $sf['default'] ) ) ) {
4549 $shipping_default_fields[ $key ]['default'] = null;
4550 }
4551 }
4552
4553 $order_comment = array(
4554 'type' => 'textarea',
4555 'class' => array( 'notes' ),
4556 'label' => __( 'Order notes', 'woocommerce' ),
4557 'placeholder' => esc_attr__( 'Notes about your order', 'wpfnl' ),
4558 'name' => 'order_comments',
4559 'required' => false,
4560 'enable' => 1,
4561 'show' => 1,
4562 'default' => null,
4563 'validate' => null,
4564 'delete' => 0,
4565 );
4566
4567 $additional_default_fields = array(
4568 'order_comments' => $order_comment,
4569 );
4570
4571 $fields = array(
4572 'billing' => $billing_default_field,
4573 'shipping' => $shipping_default_fields,
4574 'additional' => $additional_default_fields,
4575 );
4576 return $fields;
4577 }
4578 }
4579
4580 /**
4581 * Generates and updates the first step id of the funnel
4582 *
4583 * @param int|string $funnel_id Funnel id.
4584 * @since 2.8.0
4585 */
4586 public static function generate_first_step( $funnel_id, $steps = array() ) {
4587 if ( $funnel_id ) {
4588 $steps = ! empty( $steps ) ? $steps : get_post_meta( $funnel_id, '_steps', true ); // Get step order
4589
4590 if ( empty( $steps ) ) {
4591 $steps = get_post_meta( $funnel_id, '_steps_order', true ); // Get step order
4592 }
4593 $priority_order = array( 'landing', 'custom', 'checkout', 'upsell', 'downsell', 'thankyou' );
4594 $first_step = array(
4595 'id' => null,
4596 'step_type' => '',
4597 );
4598
4599 if ( is_array( $steps ) && ! empty( $steps ) ) {
4600 foreach ( $steps as $step ) {
4601 if (
4602 isset( $step['step_type'], $step['id'] )
4603 && ( ! $first_step['id'] || ( array_search( $step['step_type'], $priority_order ) < array_search( $first_step['step_type'], $priority_order ) ) )
4604 ) {
4605 $first_step['id'] = $step['id'];
4606 $first_step['step_type'] = $step['step_type'];
4607 }
4608 }
4609 self::update_funnel_first_step( $funnel_id, $first_step['id'] );
4610 }
4611 }
4612 }
4613
4614 /**
4615 * Get the first step of a funnel
4616 *
4617 * @param int|string $funnel_id Funnel id.
4618 * @return string|int
4619 * @since 2.8.0
4620 */
4621 public static function get_first_step( $funnel_id ) {
4622 return get_post_meta( $funnel_id, '_first_step', true );
4623 }
4624
4625 /**
4626 * Updates the first step id of the funnel
4627 *
4628 * @param int|string $funnel_id Funnel id.
4629 * @param int|string $first_step_id First step id of the funnel.
4630 * @return void
4631 */
4632 public static function update_funnel_first_step( $funnel_id, $first_step_id ) {
4633 update_post_meta( $funnel_id, '_first_step', $first_step_id );
4634 }
4635
4636
4637 /**
4638 * Mail mint email
4639 *
4640 * @return mix
4641 */
4642 public static function get_mailmint_email() {
4643 if ( self::is_mint_mrm_active() ) {
4644 $email_settings = get_option( '_mrm_email_settings' );
4645 if ( isset( $email_settings['from_email'] ) ) {
4646 return $email_settings['from_email'];
4647 }
4648 }
4649 return false;
4650 }
4651
4652
4653 /**
4654 * Mail mint email
4655 *
4656 * @return mix
4657 */
4658 public static function get_mailmint_email_settings() {
4659 if ( self::is_mint_mrm_active() ) {
4660 return get_option( '_mrm_email_settings', \Mint\MRM\Utilites\Helper\Email::default_email_settings() );
4661 }
4662 return false;
4663 }
4664
4665
4666 /**
4667 * Mail mint email
4668 *
4669 * @return mix
4670 */
4671 public static function get_mailmint_twillo_settings() {
4672 if ( self::is_mint_mrm_active() ) {
4673 $settings = get_option( '_mint_integration_settings', array() );
4674 $twilio = isset( $settings['twilio'] ) ? $settings['twilio'] : array();
4675 return is_array( $twilio ) && ! empty( $twilio ) ? $twilio : array(
4676 'account_sid' => '',
4677 'auth_token' => '',
4678 'from_number' => '',
4679 'is_integrated' => false,
4680 );
4681 }
4682 return false;
4683 }
4684
4685
4686 /**
4687 * Maybe duplicate step
4688 *
4689 * @param $step_id Step ID
4690 *
4691 * @since 2.7.7
4692 * @return bool
4693 */
4694 public static function maybe_duplicate_step( $step_id ) {
4695
4696 if ( $step_id ) {
4697 $is_duplicate = get_post_meta( $step_id, '_is_duplicate', true );
4698 if ( 'yes' === $is_duplicate ) {
4699 return true;
4700 }
4701 }
4702 return false;
4703 }
4704
4705
4706 /**
4707 * Retrieves all created coupons from WooCommerce.
4708 * This function fetches all coupon posts of the 'shop_coupon' post type that are currently published in WooCommerce.
4709 *
4710 * @since 2.7.9
4711 * @return array An array of coupon codes and their corresponding names.
4712 */
4713 public static function get_all_coupons() {
4714 global $wpdb;
4715 $table_name = $wpdb->prefix . 'posts';
4716 $coupon_type = 'shop_coupon';
4717 $post_status = 'publish';
4718 $query = $wpdb->prepare(
4719 "SELECT post_title FROM $table_name WHERE post_type = %s AND post_status = %s ORDER BY post_name ASC LIMIT 100",
4720 $coupon_type,
4721 $post_status
4722 );
4723 $coupon_codes = array();
4724 $coupons = $wpdb->get_col( $query );
4725 if ( is_array( $coupons ) ) {
4726 foreach ( $coupons as $coupon ) {
4727 $coupon_codes[] = array(
4728 'name' => $coupon,
4729 );
4730 }
4731 }
4732 return $coupon_codes;
4733 }
4734
4735
4736 /**
4737 * Retrieves the applied coupon for a given step ID.
4738 * This function checks if a step ID is provided and retrieves the applied coupon associated with that step.
4739 * If no step ID is provided or the applied coupon is not found, it returns false.
4740 *
4741 * @param int $step_id The ID of the step for which to retrieve the applied coupon.
4742 *
4743 * @since 2.7.9
4744 * @return string|false The selected coupon code if found and auto-coupon feature is
4745 */
4746 public static function get_applied_coupon( $step_id ) {
4747 if ( ! $step_id ) {
4748 return false;
4749 }
4750 $auto_coupon = get_post_meta( $step_id, '_wpfnl_checkout_auto_coupon', true );
4751 if ( isset( $auto_coupon['enableAutoCoupon'], $auto_coupon['selectedCoupon'] ) && 'yes' === $auto_coupon['enableAutoCoupon'] && $auto_coupon['selectedCoupon'] ) {
4752 return $auto_coupon['selectedCoupon'];
4753 }
4754 return false;
4755 }
4756
4757 /**
4758 * Get step IDs for a given funnel.
4759 *
4760 * Retrieves an array of step IDs associated with a specific funnel, including A/B testing variations.
4761 *
4762 * @param int $funnel_id The ID of the funnel.
4763 * @return array An array of step IDs.
4764 * @since 2.7.10
4765 */
4766 public static function get_step_ids( $funnel_id ) {
4767 $steps = self::get_steps( $funnel_id );
4768
4769 $all_steps_including_ab = array();
4770 $all_steps_including_ab_ids = array();
4771 if ( is_array( $steps ) ) {
4772 foreach ( $steps as $step ) {
4773 if ( isset( $step['id'] ) ) {
4774 $all_steps_including_ab = get_post_meta( $step['id'], '_wpfnl_ab_testing_start_settings', true );
4775 if ( isset( $all_steps_including_ab['variations'] ) ) {
4776 $ids = array_column( $all_steps_including_ab['variations'], 'id' );
4777 $all_steps_including_ab_ids = array_merge( $all_steps_including_ab_ids, $ids );
4778 }
4779 }
4780 }
4781 }
4782
4783 return $all_steps_including_ab_ids;
4784 }
4785
4786 /**
4787 * Records that the current session placed the given order.
4788 *
4789 * Call this immediately after a funnel order is created, so later offer
4790 * requests can prove ownership of that specific order.
4791 *
4792 * @param int $order_id The order the session just placed.
4793 * @return void
4794 * @since 3.12.14
4795 */
4796 public static function mark_order_owned_by_session( $order_id ) {
4797 $order_id = absint( $order_id );
4798
4799 if ( ! $order_id || ! self::is_wc_active() || ! WC()->session ) {
4800 return;
4801 }
4802
4803 $owned = WC()->session->get( self::OWNED_ORDERS_SESSION_KEY, array() );
4804 $owned = is_array( $owned ) ? array_map( 'absint', $owned ) : array();
4805
4806 if ( in_array( $order_id, $owned, true ) ) {
4807 return;
4808 }
4809
4810 $owned[] = $order_id;
4811
4812 // Keep the list bounded. This is written on every funnel checkout and a
4813 // session has no business owning an unbounded number of orders.
4814 if ( count( $owned ) > self::MAX_OWNED_ORDERS ) {
4815 $owned = array_slice( $owned, - self::MAX_OWNED_ORDERS );
4816 }
4817
4818 WC()->session->set( self::OWNED_ORDERS_SESSION_KEY, $owned );
4819 }
4820
4821 /**
4822 * Whether the current session placed the given order.
4823 *
4824 * @param int $order_id The order to check.
4825 * @return bool
4826 * @since 3.12.14
4827 */
4828 public static function session_owns_order( $order_id ) {
4829 $order_id = absint( $order_id );
4830
4831 if ( ! $order_id || ! self::is_wc_active() || ! WC()->session ) {
4832 return false;
4833 }
4834
4835 $owned = WC()->session->get( self::OWNED_ORDERS_SESSION_KEY, array() );
4836
4837 if ( ! is_array( $owned ) ) {
4838 return false;
4839 }
4840
4841 return in_array( $order_id, array_map( 'absint', $owned ), true );
4842 }
4843
4844 /**
4845 * Signs the privileged settings an opt-in form was rendered with.
4846 *
4847 * The notification recipient, its subject and the "register this visitor"
4848 * switch are rendered as hidden inputs, so they arrive back as request data
4849 * and cannot be trusted on their own. Signing them at render time lets the
4850 * submission handler tell its own configuration apart from a caller-supplied
4851 * one, without having to re-parse the page the form was placed on.
4852 *
4853 * @param string $admin_email Notification recipient list as configured.
4854 * @param string $subject Notification subject as configured.
4855 * @param string $allow_registration 'yes' when the form may create a user.
4856 * @return string
4857 * @since 3.12.14
4858 */
4859 public static function get_optin_settings_token( $admin_email, $subject, $allow_registration = '' ) {
4860 return wp_hash( 'wpfnl_optin_settings|' . $admin_email . '|' . $subject . '|' . $allow_registration );
4861 }
4862
4863 /**
4864 * Whether the opt-in settings submitted with a form are the ones it was rendered with.
4865 *
4866 * @param string $token Token supplied by the request.
4867 * @param string $admin_email Notification recipient list from the request.
4868 * @param string $subject Notification subject from the request.
4869 * @param string $allow_registration Registration switch from the request.
4870 * @return bool
4871 * @since 3.12.14
4872 */
4873 public static function is_valid_optin_settings_token( $token, $admin_email, $subject, $allow_registration = '' ) {
4874 if ( ! is_string( $token ) || '' === $token ) {
4875 return false;
4876 }
4877
4878 return hash_equals(
4879 self::get_optin_settings_token( $admin_email, $subject, $allow_registration ),
4880 $token
4881 );
4882 }
4883
4884 /**
4885 * Resolves what an order bump is actually configured to sell.
4886 *
4887 * The bump handler runs for unauthenticated callers, so the discount must be
4888 * pinned to the product it was configured for. Without this, a bump's discount
4889 * can be borrowed from any step on the site and applied to any product.
4890 *
4891 * Settings are read through the same filters the checkout renderer uses, so a
4892 * dynamically resolved (auto-match) bump validates against the product that was
4893 * actually offered rather than the one stored in the builder.
4894 *
4895 * @param int $step_id Checkout step holding the bump settings.
4896 * @param mixed $key Which bump on that step.
4897 * @param int $product_id Product the request asked for.
4898 * @param int $variation_id Variation the request asked for, if any.
4899 * @param array $variation_data Attribute values the customer picked (attribute_* => value). Only used to fill in "Any" attributes.
4900 * @param bool $require_variation Whether a variable product must come with a purchasable, fully specified variation (true when adding the bump to the cart).
4901 * @return array|WP_Error product_id, variation_id, cart_product_id, quantity, variation_data and settings, or an error.
4902 * @since 3.12.14
4903 */
4904 public static function resolve_order_bump_item( $step_id, $key, $product_id, $variation_id = 0, $variation_data = array(), $require_variation = false ) {
4905 $step_id = absint( $step_id );
4906 $product_id = absint( $product_id );
4907 $variation_id = absint( $variation_id );
4908
4909 if ( ! self::is_wc_active() ) {
4910 return new \WP_Error( 'wpfnl_wc_inactive', __( 'WooCommerce is not active.', 'wpfnl' ) );
4911 }
4912
4913 // The bump must live on a real checkout step, not any post id the caller names.
4914 if ( ! $step_id || 'checkout' !== get_post_meta( $step_id, '_step_type', true ) ) {
4915 return new \WP_Error( 'wpfnl_invalid_step', __( 'Invalid checkout step.', 'wpfnl' ) );
4916 }
4917
4918 $funnel_id = self::get_funnel_id_from_step( $step_id );
4919 $ob_settings = get_post_meta( $step_id, 'order-bump-settings', true );
4920 $ob_settings = apply_filters( 'wpfunnels/order_bump_settings', $ob_settings, $funnel_id, $step_id );
4921 $ob_settings = apply_filters( 'wpfunnels/order_bump_settings_on_frontend', $ob_settings, $funnel_id, $step_id );
4922
4923 if ( ! is_array( $ob_settings ) || ! isset( $ob_settings[ $key ] ) || ! is_array( $ob_settings[ $key ] ) ) {
4924 return new \WP_Error( 'wpfnl_invalid_bump', __( 'Invalid order bump.', 'wpfnl' ) );
4925 }
4926
4927 $settings = $ob_settings[ $key ];
4928
4929 if ( empty( $settings['product'] ) ) {
4930 return new \WP_Error( 'wpfnl_invalid_bump', __( 'This order bump has no product configured.', 'wpfnl' ) );
4931 }
4932
4933 $configured_id = absint( $settings['product'] );
4934
4935 $configured_product = wc_get_product( $configured_id );
4936
4937 if ( ! $configured_product ) {
4938 return new \WP_Error( 'wpfnl_invalid_product', __( 'The configured order bump product no longer exists.', 'wpfnl' ) );
4939 }
4940
4941 // The discount belongs to the configured product. Anything else is a
4942 // request to price one product using another product's offer.
4943 if ( $product_id !== $configured_id ) {
4944 return new \WP_Error( 'wpfnl_product_mismatch', __( 'This product is not part of the selected offer.', 'wpfnl' ) );
4945 }
4946
4947 if ( $variation_id ) {
4948 $variation = wc_get_product( $variation_id );
4949
4950 if ( ! $variation || $variation->get_parent_id() !== $configured_id ) {
4951 return new \WP_Error( 'wpfnl_invalid_variation', __( 'Please choose a valid variation for this offer.', 'wpfnl' ) );
4952 }
4953 }
4954
4955 // Quantity is the merchant's. Letting the request set it widens the
4956 // flat-discount cap, which is compared against price multiplied by quantity.
4957 $quantity = isset( $settings['quantity'] ) ? absint( $settings['quantity'] ) : 0;
4958 $quantity = max( 1, $quantity );
4959
4960 $resolved_attributes = array();
4961
4962 if ( $require_variation ) {
4963 $resolved = self::resolve_order_bump_variation( $configured_product, $variation_id, $variation_data, $quantity );
4964
4965 if ( is_wp_error( $resolved ) ) {
4966 return $resolved;
4967 }
4968
4969 $resolved_attributes = $resolved;
4970 }
4971
4972 return array(
4973 'product_id' => $configured_id,
4974 'variation_id' => $variation_id,
4975 'cart_product_id' => $variation_id ? $variation_id : $configured_id,
4976 'quantity' => $quantity,
4977 'variation_data' => $resolved_attributes,
4978 );
4979 }
4980
4981 /**
4982 * Whether an order bump offers a variable product the customer has to pick a variation of.
4983 *
4984 * Read from the product itself rather than a flag saved with the bump, so a
4985 * product that was changed to or from variable after the bump was saved is
4986 * still handled correctly.
4987 *
4988 * @param array $settings Order bump settings.
4989 * @return bool
4990 * @since 3.13.5
4991 */
4992 public static function is_variable_order_bump( $settings ) {
4993 if ( empty( $settings['product'] ) || ! self::is_wc_active() ) {
4994 return false;
4995 }
4996
4997 $product = wc_get_product( absint( $settings['product'] ) );
4998
4999 return $product && $product->is_type( array( 'variable', 'variable-subscription' ) );
5000 }
5001
5002 /**
5003 * Checks that a variable order bump is added as a purchasable, fully specified variation.
5004 *
5005 * A variable parent cannot be added to the cart on its own, so the customer has to have
5006 * picked a variation. A variation that leaves an attribute on "Any" also needs the value
5007 * the customer chose for it, which WooCommerce validates again when adding to the cart.
5008 *
5009 * @param \WC_Product $configured_product The product configured on the bump.
5010 * @param int $variation_id Variation the request asked for, if any.
5011 * @param array $variation_data Attribute values the customer picked (attribute_* => value).
5012 * @param int $quantity Quantity the merchant configured.
5013 * @return array|\WP_Error Attributes to add the variation with (empty for a simple product), or an error.
5014 * @since 3.13.5
5015 */
5016 public static function resolve_order_bump_variation( $configured_product, $variation_id, $variation_data, $quantity = 1 ) {
5017 if ( $configured_product->is_type( 'variation' ) ) {
5018 $variation = $configured_product;
5019 } elseif ( $configured_product->is_type( array( 'variable', 'variable-subscription' ) ) ) {
5020 if ( ! $variation_id ) {
5021 return new \WP_Error( 'wpfnl_variation_required', __( 'Please choose an option for this offer.', 'wpfnl' ) );
5022 }
5023
5024 $variation = wc_get_product( $variation_id );
5025 } else {
5026 return array();
5027 }
5028
5029 if ( ! $variation || ! $variation->is_purchasable() || ! $variation->is_in_stock() || ! $variation->has_enough_stock( $quantity ) ) {
5030 return new \WP_Error( 'wpfnl_variation_unavailable', __( 'This option is not available right now.', 'wpfnl' ) );
5031 }
5032
5033 $attributes = $variation->get_variation_attributes();
5034 $submitted = is_array( $variation_data ) ? $variation_data : array();
5035
5036 foreach ( $attributes as $attribute_key => $attribute_value ) {
5037 // The variation fixes this attribute itself; only "Any" attributes take the customer's value.
5038 if ( '' !== $attribute_value ) {
5039 continue;
5040 }
5041
5042 $chosen = isset( $submitted[ $attribute_key ] ) ? wc_clean( $submitted[ $attribute_key ] ) : '';
5043
5044 if ( '' === $chosen ) {
5045 return new \WP_Error( 'wpfnl_variation_attributes', __( 'Please choose an option for this offer.', 'wpfnl' ) );
5046 }
5047
5048 $attributes[ $attribute_key ] = $chosen;
5049 }
5050
5051 return $attributes;
5052 }
5053
5054 /**
5055 * Resolves what an offer step is actually configured to sell.
5056 *
5057 * The offer cart handler runs for unauthenticated callers, so nothing about
5058 * the item may be taken from the request: the product and the quantity come
5059 * from the step's own configuration. The only choice the request gets is
5060 * which variation of that configured product to buy.
5061 *
5062 * @param int $step_id The offer step being purchased.
5063 * @param int $candidate_variation_id Variation the request selected, if any.
5064 * @return array|WP_Error product_id, variation_id and quantity, or an error.
5065 * @since 3.12.14
5066 */
5067 public static function resolve_offer_cart_item( $step_id, $candidate_variation_id = 0 ) {
5068 $step_id = absint( $step_id );
5069
5070 if ( ! self::is_wc_active() ) {
5071 return new \WP_Error( 'wpfnl_wc_inactive', __( 'WooCommerce is not active.', 'wpfnl' ) );
5072 }
5073
5074 if ( ! $step_id ) {
5075 return new \WP_Error( 'wpfnl_invalid_step', __( 'Invalid offer step.', 'wpfnl' ) );
5076 }
5077
5078 // Only a real upsell/downsell step may drive this. Without it, any post id
5079 // could be used to borrow another step's discount configuration.
5080 $step_type = get_post_meta( $step_id, '_step_type', true );
5081
5082 if ( ! in_array( $step_type, array( 'upsell', 'downsell' ), true ) ) {
5083 return new \WP_Error( 'wpfnl_invalid_step', __( 'Invalid offer step.', 'wpfnl' ) );
5084 }
5085
5086 $configured = get_post_meta( $step_id, '_wpfnl_' . $step_type . '_products', true );
5087
5088 if ( ! is_array( $configured ) || empty( $configured[0]['id'] ) ) {
5089 return new \WP_Error( 'wpfnl_no_offer_product', __( 'This offer has no product configured.', 'wpfnl' ) );
5090 }
5091
5092 $product_id = absint( $configured[0]['id'] );
5093 $product = wc_get_product( $product_id );
5094
5095 if ( ! $product ) {
5096 return new \WP_Error( 'wpfnl_invalid_product', __( 'The configured offer product no longer exists.', 'wpfnl' ) );
5097 }
5098
5099 // Quantity is the merchant's, not the caller's. Offer pages expose no
5100 // quantity control, and letting the request set it turns the discount
5101 // guard (which compares against the line total) into a lever.
5102 $quantity = isset( $configured[0]['quantity'] ) ? absint( $configured[0]['quantity'] ) : 0;
5103 $quantity = max( 1, $quantity );
5104
5105 $variation_id = absint( $candidate_variation_id );
5106
5107 if ( $variation_id ) {
5108 $variation = wc_get_product( $variation_id );
5109
5110 if ( ! $variation || $variation->get_parent_id() !== $product_id ) {
5111 return new \WP_Error( 'wpfnl_invalid_variation', __( 'Please choose a valid variation for this offer.', 'wpfnl' ) );
5112 }
5113 }
5114
5115 return array(
5116 'product_id' => $product_id,
5117 'variation_id' => $variation_id,
5118 'quantity' => $quantity,
5119 );
5120 }
5121
5122 /**
5123 * Validates the order owner's authorization.
5124 *
5125 * Every caller that resolves an order from a request-supplied id must pass
5126 * through here before acting on that order.
5127 *
5128 * @param WC_Order $order The order object to validate.
5129 * @param string $order_key Order key supplied by the request, if any.
5130 * @return bool
5131 * @since 2.7.13
5132 */
5133 public static function is_valid_order_owner( $order, $order_key = '' ) {
5134
5135 if ( ! self::is_wc_active() || false === is_a( $order, 'WC_Order' ) ) {
5136 return false;
5137 }
5138
5139 // Validate via order_key when provided. The key is cryptographically random
5140 // and unpredictable, so possession of it proves the requester is the legitimate
5141 // order owner. This covers guest checkout and Express Checkout (Google Pay /
5142 // Apple Pay) in incognito mode where no WP user session or WC session exists.
5143 if ( ! empty( $order_key ) && $order->key_is_valid( $order_key ) ) {
5144 return true;
5145 }
5146
5147 // A logged-in customer may act on their own order; so may a shop manager.
5148 if ( ! empty( $order->get_customer_id() ) ) {
5149 if ( $order->get_customer_id() === get_current_user_id() || current_user_can( 'manage_woocommerce' ) ) {
5150 return true;
5151 }
5152 }
5153
5154 // Guest flows: this session must have placed this specific order. Note the
5155 // check is against the order id, not a bare "has checked out" marker, so a
5156 // session cannot authorise an order it did not create.
5157 return self::session_owns_order( $order->get_id() );
5158 }
5159
5160
5161 /**
5162 * Capture a screenshot of a webpage given its URL and save it locally.
5163 *
5164 * @param string $url The URL of the webpage to capture the screenshot.
5165 * @param int $step_id The ID of the step associated with the screenshot.
5166 *
5167 * @return string The full URL of the captured screenshot if successful, an empty string otherwise.
5168 *
5169 * @since 2.9.0
5170 */
5171 public static function get_screenshot_from_url( $url, $step_id ) {
5172 try {
5173 $file_name = uniqid();
5174 $path = wp_upload_dir();
5175 $baseurl = $path['baseurl'] . '/wpfunnels';
5176 $path = $path['basedir'] . '/wpfunnels';
5177 $default_file = $path . '/index.php';
5178 if ( ! file_exists( $path ) ) {
5179 wp_mkdir_p( $path );
5180 }
5181
5182 if ( ! file_exists( $default_file ) ) {
5183 $file_handle = @fopen( trailingslashit( $path ) . 'index.php', 'wb' );
5184 if ( $file_handle ) {
5185 fwrite( $file_handle, $file['content'] );
5186 fclose( $file_handle );
5187 }
5188 }
5189
5190 \Spatie\Browsershot\Browsershot::url( $url )
5191 ->waitUntilNetworkIdle()
5192 ->windowSize( 1280, 800 )
5193 ->ignoreHttpsErrors()
5194 ->save( $screenshotPath );
5195
5196 // Update the post meta with the screenshot's base URL.
5197 update_post_meta( $step_id, 'wpfnl_step_screenshot_baseurl', $path . '/' . $file_name . '.png' );
5198
5199 // Return the full URL of the captured screenshot.
5200 return $baseurl . '/' . $file_name . '.png';
5201 } catch ( Exception $e ) {
5202 return '';
5203 }
5204 }
5205
5206
5207 /**
5208 * Update the screenshot URL for a step by its ID.
5209 *
5210 * @param int $step_id The ID of the step.
5211 * @param string $url The new screenshot URL to be set.
5212 *
5213 * @return bool True if the update is successful, false otherwise.
5214 *
5215 * @since 2.9.0
5216 */
5217 public static function maybe_update_screenshot_url_by_step_id( $step_id, $url ) {
5218 if ( ! $step_id || ! $url ) {
5219 return false;
5220 }
5221
5222 // Update the post meta for the step with the new screenshot URL.
5223 update_post_meta( $step_id, 'wpfnl_step_screenshot_url', $url );
5224
5225 return true;
5226 }
5227
5228
5229 /**
5230 * Update the modified date of a step's screenshot identified by its ID.
5231 *
5232 * @param int $step_id The ID of the step.
5233 *
5234 * @return bool True on success, false on failure.
5235 *
5236 * @since 2.9.0
5237 */
5238 public static function maybe_update_screenshot_modified_date_by_step_id( $step_id ) {
5239 if ( ! $step_id ) {
5240 return false;
5241 }
5242 if ( ! self::get_screenshot_url_by_step_id( $step_id ) ) {
5243 return false;
5244 }
5245 // Update the post meta for the step with the new screenshot modified date.
5246 update_post_meta( $step_id, 'wpfnl_step_screenshot_modified_date', date( 'Y-m-d H:i:s' ) );
5247
5248 return true;
5249 }
5250
5251
5252 /**
5253 * Get the screenshot URL for a step by its ID.
5254 *
5255 * @param int $step_id The ID of the step.
5256 *
5257 * @return string|false The screenshot URL if found, false if not found.
5258 *
5259 * @since 2.9.0
5260 */
5261 public static function get_screenshot_url_by_step_id( $step_id ) {
5262 if ( ! $step_id ) {
5263 return false;
5264 }
5265
5266 // Get the post meta for the step's screenshot URL.
5267 $url = get_post_meta( $step_id, 'wpfnl_step_screenshot_url', true );
5268
5269 if ( ! $url ) {
5270 return false;
5271 }
5272
5273 return $url;
5274 }
5275
5276
5277 /**
5278 * Get the modified date of a step identified by its ID.
5279 *
5280 * @param int $step_id The ID of the step.
5281 *
5282 * @return string|bool The modified date in 'Y-m-d H:i:s' format on success, false on failure.
5283 *
5284 * @since 2.9.0
5285 */
5286 public static function get_screenshot_modified_date_by_step_id( $step_id ) {
5287 if ( ! $step_id ) {
5288 return false;
5289 }
5290
5291 // Get the post meta for the step's screenshot modified date.
5292 $date = get_post_meta( $step_id, 'wpfnl_step_screenshot_modified_date', true );
5293
5294 if ( ! $date ) {
5295 return false;
5296 }
5297
5298 return $date;
5299 }
5300
5301
5302 /**
5303 * Get the modified date of a step by its ID.
5304 *
5305 * @param int $step_id The ID of the step.
5306 *
5307 * @return string|false The modified date if found, false if not found.
5308 *
5309 * @since 2.9.0
5310 */
5311 public static function get_the_modified_date_of_step( $step_id ) {
5312 if ( ! $step_id ) {
5313 return false;
5314 }
5315
5316 // Get the modified date of the step.
5317 $date = get_the_modified_date( 'Y-m-d H:i:s', $step_id );
5318
5319 if ( ! $date ) {
5320 return false;
5321 }
5322
5323 return $date;
5324 }
5325
5326
5327 /**
5328 * Delete the screenshot file associated with a step identified by its ID.
5329 *
5330 * @param int $step_id The ID of the step.
5331 *
5332 * @return bool True if the screenshot file is deleted successfully, false otherwise.
5333 */
5334 public static function maybe_delete_screenshot_by_step_id( $step_id ) {
5335
5336 if ( ! $step_id ) {
5337 return false;
5338 }
5339
5340 // Update the screenshot URL to get the file path.
5341 $path = get_post_meta( $step_id, 'wpfnl_step_screenshot_baseurl', true );
5342
5343 // Check if the file exists and is readable.
5344 if ( ! file_exists( $path ) ) {
5345 return false;
5346 }
5347
5348 // Try to delete the file.
5349 unlink( $path );
5350 return true;
5351 }
5352
5353
5354 /**
5355 * Get the URL of a Step with Optional UTM Parameters.
5356 *
5357 * This function retrieves the URL of a specified step within a funnel. It takes a funnel ID
5358 * and a step ID as input and returns the URL of the step. If UTM tracking is enabled for the
5359 * funnel, this function appends the UTM parameters to the URL as query parameters.
5360 *
5361 * @since 1.6.0
5362 *
5363 * @param int $funnel_id The ID of the funnel.
5364 * @param int $step_id The ID of the step whose URL needs to be retrieved.
5365 *
5366 * @return string|false The URL of the step with optional UTM parameters or false if the URL is not found.
5367 */
5368 public static function get_step_url( $funnel_id, $step_id ) {
5369 if ( ! $funnel_id || ! $step_id ) {
5370 return false;
5371 }
5372
5373 $url = get_the_permalink( $step_id );
5374
5375 if ( ! $url ) {
5376 return false;
5377 }
5378
5379 // Get UTM settings for the funnel.
5380 $utm_settings = self::get_funnel_utm_settings( $funnel_id );
5381
5382 // Check if UTM parameters are enabled.
5383 if ( ! is_array( $utm_settings ) || ! isset( $utm_settings['utm_enable'] ) || 'on' !== $utm_settings['utm_enable'] ) {
5384 return $url;
5385 }
5386
5387 unset( $utm_settings['utm_enable'] );
5388 $url = add_query_arg( $utm_settings, $url );
5389 $url = strtolower( $url );
5390
5391 return $url;
5392 }
5393
5394
5395 /**
5396 * Check if conditional logic is enabled for a specific step.
5397 *
5398 * This function checks whether the conditional logic feature is enabled for a given step
5399 * by checking the '_wpfnl_maybe_enable_condition' post meta value associated with the step.
5400 * If the value is 'yes', then the conditional logic is considered enabled; otherwise, it is not.
5401 *
5402 * @param int $step_id The ID of the step to check for conditional logic.
5403 * @return bool True if conditional logic is enabled, false otherwise.
5404 *
5405 * @since 2.9.0
5406 */
5407 public static function maybe_enable_condition( $step_id ) {
5408 if ( ! $step_id ) {
5409 return false;
5410 }
5411
5412 $is_enabled = get_post_meta( $step_id, '_wpfnl_maybe_enable_condition', true );
5413 if ( 'yes' !== $is_enabled ) {
5414 return false;
5415 }
5416
5417 return true;
5418 }
5419
5420
5421 /**
5422 * Get the conditions associated with a specific step.
5423 *
5424 * This function retrieves the conditions linked to a given step by querying the '_wpfnl_step_conditions'
5425 * post meta associated with that step. If conditions are found and are in an array format, they are returned;
5426 * otherwise, false is returned to indicate that no conditions or invalid conditions are available.
5427 *
5428 * @param int $step_id The ID of the step to retrieve conditions for.
5429 * @return array|false An array of conditions if available and valid, false otherwise.
5430 *
5431 * @since 2.9.0
5432 */
5433 public static function get_conditions( $step_id ) {
5434 if ( ! $step_id ) {
5435 return false;
5436 }
5437
5438 $conditions = get_post_meta( $step_id, '_wpfnl_step_conditions', true );
5439 if ( empty( $conditions ) || ! is_array( $conditions ) ) {
5440 return false;
5441 }
5442
5443 return $conditions;
5444
5445 }
5446
5447
5448 /**
5449 * Get the next step to be executed conditionally after the current step.
5450 *
5451 * This function retrieves the next step that should be executed after the current step
5452 * if certain conditions are met. The next step is retrieved by querying the '_wpfnl_next_step_after_condition'
5453 * post meta associated with the given step. If a valid array containing the next step information is found,
5454 * it is returned; otherwise, false is returned to indicate that no next step or invalid data is available.
5455 *
5456 * @param int $step_id The ID of the step to retrieve the conditional next step for.
5457 * @return array|false An array containing information about the next step if available and valid, false otherwise.
5458 *
5459 * @since 2.9.0
5460 */
5461 public static function get_conditional_next_step( $step_id ) {
5462 if ( ! $step_id ) {
5463 return false;
5464 }
5465
5466 $next_step = get_post_meta( $step_id, '_wpfnl_next_step_after_condition', true );
5467 if ( empty( $next_step ) || ! is_array( $next_step ) ) {
5468 return false;
5469 }
5470
5471 return $next_step;
5472
5473 }
5474
5475
5476 /**
5477 * Get the next step to be executed based on conditional criteria.
5478 *
5479 * This function determines the next step to be executed in a funnel based on conditional criteria.
5480 * If conditional criteria are not enabled for the step, it retrieves the next step using the
5481 * Wpfnl_functions::get_next_step method. If conditional criteria are enabled, it checks the conditions
5482 * using the WPFunnels\Conditions\Wpfnl_Condition_Checker class. If the conditions match, the true branch's
5483 * next step is returned; otherwise, the false branch's next step is returned.
5484 *
5485 * @param int $funnel_id The ID of the funnel to which the step belongs.
5486 * @param int $step_id The ID of the current step.
5487 * @param string $order Optional. The order in which the step is executed. Default is empty.
5488 * @param string $checker Optional. The checker to be used for condition checking. Default is 'accept'.
5489 * @return bool|array An array containing information about the next step and its type. if funnel id or step id is missing then it return false;
5490 *
5491 * @since 2.9.0
5492 */
5493 public static function get_next_conditional_step( $funnel_id, $step_id, $order = '', $checker = 'accept' ) {
5494
5495 if ( ! $funnel_id || ! $step_id ) {
5496 return false;
5497 }
5498
5499 $is_condition_enable = self::maybe_enable_condition( $step_id );
5500 if ( ! $is_condition_enable ) {
5501 return self::get_next_step( $funnel_id, $step_id );
5502 }
5503 $condition = \WPFunnels\Conditions\Wpfnl_Condition_Checker::getInstance();
5504 $condition_matched = $condition->check_condition( $funnel_id, $order, $step_id, $step_id, $checker );
5505 $conditional_next_step = self::get_conditional_next_step( $step_id );
5506
5507 if ( ! is_array( $conditional_next_step ) || empty( $conditional_next_step ) ) {
5508 return self::get_next_step( $funnel_id, $step_id, $condition_matched );
5509 }
5510
5511 if ( $condition_matched && empty( $conditional_next_step['true'] ) ) {
5512 return self::get_next_step( $funnel_id, $step_id, $condition_matched );
5513 } elseif ( ! $condition_matched && empty( $conditional_next_step['false'] ) ) {
5514 return self::get_next_step( $funnel_id, $step_id, $condition_matched );
5515 }
5516
5517 $next_node = array(
5518 'step_id' => $condition_matched ? $conditional_next_step['true'] : $conditional_next_step['false'],
5519 'step_type' => $condition_matched ? get_post_meta( $conditional_next_step['true'], '_step_type', true ) : get_post_meta( $conditional_next_step['false'], '_step_type', true ),
5520 );
5521
5522 return $next_node;
5523 }
5524
5525
5526
5527 /**
5528 * Update a meta key with a new value for a given step.
5529 *
5530 * @param int $step_id The ID of the step for which to update the meta.
5531 * @param string $meta_key The key of the meta data to update.
5532 * @param mixed $meta_value The new value to set for the meta key.
5533 *
5534 * @return bool True on success, false on failure.
5535 *
5536 * @since 3.0.0
5537 */
5538 public static function update_meta( $step_id, $meta_key, $meta_value ) {
5539 if ( ! $step_id || ! $meta_key ) {
5540 return false;
5541 }
5542 update_post_meta( $step_id, $meta_key, $meta_value );
5543 return true;
5544 }
5545
5546
5547 /**
5548 * Delete a meta key and its associated value for a given step.
5549 *
5550 * @param int $step_id The ID of the step from which to delete the meta.
5551 * @param string $meta_key The key of the meta data to delete.
5552 *
5553 * @return bool True on success, false on failure.
5554 * @since 3.0.0
5555 */
5556 public static function delete_meta( $step_id, $meta_key ) {
5557 if ( ! $step_id || ! $meta_key ) {
5558 return false;
5559 }
5560 delete_post_meta( $step_id, $meta_key );
5561 return true;
5562 }
5563 /**
5564 * Check if the current page is using the Gutenberg editor.
5565 *
5566 * This function checks whether the current page in the WordPress admin area is using the Gutenberg editor. It does so by checking the availability of a specific function associated with Gutenberg. If this function exists, it is likely that the Gutenberg editor is in use.
5567 *
5568 * @return bool True if the current page is using the Gutenberg editor, false otherwise.
5569 * @since 2.8.12
5570 */
5571 private static function is_gutenberg_edit_page() {
5572 if ( function_exists( 'wp_enqueue_script' ) && function_exists( 'wp_enqueue_style' ) ) {
5573 // Check if Gutenberg scripts and styles are enqueued
5574 return wp_script_is( 'wp-blocks' ) && wp_script_is( 'wp-element' ) && wp_style_is( 'wp-edit-blocks' );
5575 }
5576 return false;
5577 }
5578
5579 /**
5580 * Check if the current page is an Elementor edit page.
5581 *
5582 * This function checks whether the current page in the WordPress admin area is an edit page created with the Elementor page builder plugin. It does so by inspecting the 'action' query parameter in the URL. If the 'action' is set to 'elementor', it is likely an edit page created with Elementor.
5583 *
5584 * @return bool True if the current page is an Elementor edit page, false otherwise.
5585 * @since 2.8.12
5586 */
5587 private static function is_elementor_edit_page() {
5588 // Check if the 'action' query parameter is set to 'elementor'.
5589 if ( defined( 'ELEMENTOR_VERSION' ) && \Elementor\Plugin::$instance->editor->is_edit_mode() ) {
5590 return true;
5591 }
5592 return false;
5593 }
5594
5595 /**
5596 * Check if the current page is an edit page created with the Divi Builder.
5597 *
5598 * This function checks whether the current page in the WordPress admin area is an edit page created with the Divi Builder plugin. It does so by checking the availability of specific functions associated with Divi Builder. If these functions exist, it is likely an edit page created with Divi Builder.
5599 *
5600 * @return bool True if the current page is an edit page created with Divi Builder, false otherwise.
5601 * @since 2.8.12
5602 */
5603 private static function is_divi_edit_page() {
5604 // Check if the Divi Builder's assets are enqueued by verifying the presence of specific functions.
5605 return is_callable( 'et_pb_enqueue_main_scripts' ) && is_callable( 'et_theme_builder_overrides' );
5606 }
5607
5608 /**
5609 * Check if the current page is an Oxygen Builder edit page.
5610 *
5611 * This function checks whether the current page in the WordPress admin area is an Oxygen Builder edit page. It does so by verifying if specific Oxygen assets (scripts and styles) are enqueued. If the assets are enqueued, it is likely that the current page is an Oxygen edit page.
5612 *
5613 * @return bool True if the current page is an Oxygen edit page, false otherwise.
5614 * @since 2.8.12
5615 */
5616 private static function is_oxygen_edit_page() {
5617 // Check if Oxygen's assets are enqueued
5618 return wp_script_is( 'ct-automation' ) || wp_style_is( 'ct-select2' ) || 'ct_render_shortcode' === filter_input( INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS ) || filter_input( INPUT_GET, 'ct_builder', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) || filter_input( INPUT_GET, 'ct_inner', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
5619 }
5620
5621 /**
5622 * Check if the current page is being edited using a specific page builder or any builder if not specified.
5623 *
5624 * This function checks whether the current page in the WordPress admin area is being edited using a specific page builder, such as Elementor, Divi, Oxygen, or Gutenberg. If no specific builder is provided, it checks for any of the supported page builders.
5625 *
5626 * @param string|null $builder (Optional) The name of the page builder to check (e.g., 'elementor', 'divi', 'oxygen', 'gutenberg'). If not specified, checks for any supported page builder.
5627 * @return bool True if the current page is being edited with the specified page builder or any supported builder, false otherwise.
5628 * @since 2.8.12
5629 */
5630 public static function is_builder_edit_page( $builder = null ) {
5631 switch ( $builder ) {
5632 case 'elementor':
5633 return self::is_elementor_edit_page();
5634 case 'divi':
5635 return self::is_divi_edit_page();
5636 case 'oxygen':
5637 return self::is_oxygen_edit_page();
5638 case 'gutenberg':
5639 return self::is_gutenberg_edit_page();
5640 default:
5641 // Check for any supported page builder.
5642 return self::is_elementor_edit_page() || self::is_gutenberg_edit_page() || self::is_divi_edit_page() || self::is_oxygen_edit_page() || 'edit' === filter_input( INPUT_GET, 'action', FILTER_SANITIZE_SPECIAL_CHARS );
5643 }
5644 }
5645
5646
5647 /**
5648 * Maybe remote funnel
5649 *
5650 * @return bool
5651 * @since 3.0.6
5652 */
5653 public static function maybe_remote_funnel() {
5654 if ( defined( 'REMOTE_FUNNEL_VERSION' ) ) {
5655 return 'yes';
5656 }
5657 return 'no';
5658 }
5659
5660
5661 /**
5662 * Removes a disconnected addstep node from the funnel data.
5663 *
5664 * @param array $funnel_data The funnel data.
5665 * @param int $funnel_id The ID of the funnel.
5666 * @return array The updated funnel data.
5667 */
5668 public static function remove_disconnected_addstep_node( $funnel_data, $funnel_id ) {
5669 if ( ! $funnel_id || ! isset( $funnel_data['drawflow']['Home']['data'] ) || empty( $funnel_data['drawflow']['Home']['data'] ) ) {
5670 return $funnel_data;
5671 }
5672
5673 $count = count( $funnel_data['drawflow']['Home']['data'] );
5674
5675 if ( $count <= 1 ) {
5676 return $funnel_data;
5677 }
5678
5679 // Loop through each step in funnel
5680 foreach ( $funnel_data['drawflow']['Home']['data'] as $key => &$step ) {
5681 // Find corresponding step in step id data
5682 if ( 'addstep' === $step['data']['step_type'] ) {
5683 $maybeEmptyInput = false;
5684 $maybeEmptyOutput = false;
5685 if ( empty( $step['inputs']['input_1']['connections'] ) ) {
5686 $maybeEmptyInput = true;
5687 }
5688
5689 if ( empty( $step['inputs'] ) ) {
5690 $maybeEmptyInput = true;
5691 }
5692
5693 if ( empty( $step['outputs'] ) ) {
5694 $maybeEmptyOutput = true;
5695 }
5696
5697 if ( empty( $step['outputs']['output_1']['connections'] ) ) {
5698 $maybeEmptyOutput = true;
5699 }
5700 if ( $maybeEmptyInput && $maybeEmptyOutput ) {
5701 unset( $funnel_data['drawflow']['Home']['data'][ $key ] );
5702 update_post_meta( $funnel_id, '_funnel_data', $funnel_data );
5703 }
5704 }
5705 }
5706
5707 return $funnel_data;
5708 }
5709
5710
5711 /**
5712 * Get all static text
5713 *
5714 * @return array
5715 * @since 3.0.13
5716 */
5717 public static function get_text() {
5718 return array(
5719 'error_occurred' => __( 'Error Occurred!', 'wpfnl' ),
5720
5721 // Funnel import progress. Read by admin/assets/js/wpfnl-admin.js, which predates
5722 // the Vue bundles and has no wp.i18n of its own.
5723 'import_preparing' => __( 'Preparing import…', 'wpfnl' ),
5724 'import_in_progress' => __( 'Importing funnels…', 'wpfnl' ),
5725 'import_completed' => __( 'Import complete.', 'wpfnl' ),
5726 'import_failed' => __( 'The import could not be completed.', 'wpfnl' ),
5727 'import_failed_item' => __( 'failed', 'wpfnl' ),
5728 'import_partial' => __( 'some steps failed', 'wpfnl' ),
5729 'import_funnels_word' => __( 'funnels', 'wpfnl' ),
5730 'import_steps_word' => __( 'steps', 'wpfnl' ),
5731 'import_stalled' => __( 'The import seems to have stopped responding. Cancel it to try again.', 'wpfnl' ),
5732 'import_taking_long' => __( 'This import is taking longer than expected. Refresh the page to check on it.', 'wpfnl' ),
5733 'import_status_unavailable' => __( 'Lost contact with the import. Refresh the page to check on it.', 'wpfnl' ),
5734 'import_cancelled' => __( 'The import has been cancelled.', 'wpfnl' ),
5735 'import_cancel_failed' => __( 'Could not cancel the import.', 'wpfnl' ),
5736 'stats' => __( 'Stats', 'wpfnl' ),
5737 'stats_tooltip_txt' => __( 'Toggle to get a quick view of visits & conversion of steps right on the canvas.', 'wpfnl' ),
5738 'find_your_templates' => __( 'Find your templates', 'wpfnl' ),
5739 'enable_global_funnel' => __( 'Enable Global Funnel', 'wpfnl' ),
5740 'enable_gbf_tooltip_txt' => __( 'Enabling this will convert this funnel into a conditional global funnel and you will have define conditions to trigger the funnel right from your WooCommerce store.', 'wpfnl' ),
5741 'save' => __( 'Save', 'wpfnl' ),
5742 'webhook' => __( 'Webhook', 'wpfnl' ),
5743 'analytics' => __( 'Analytics', 'wpfnl' ),
5744 'integrations' => __( 'Integrations', 'wpfnl' ),
5745 'publish' => __( 'Publish', 'wpfnl' ),
5746 'draft' => __( 'Draft', 'wpfnl' ),
5747 'enable' => __( 'Enable', 'wpfnl' ),
5748 'disable' => __( 'Disable', 'wpfnl' ),
5749 'delete' => __( 'Delete', 'wpfnl' ),
5750 'trash' => __( 'Trash', 'wpfnl' ),
5751 'deleting' => __( 'Deleting...', 'wpfnl' ),
5752 'settings' => __( 'Settings', 'wpfnl' ),
5753 'youtube_video' => __( 'Videos', 'wpfnl' ),
5754 'documentation' => __( 'Documentation', 'wpfnl' ),
5755 'blog' => __( 'Blog', 'wpfnl' ),
5756 'select_template_industries' => __( 'Select Template Industries', 'wpfnl' ),
5757 'select_template_builder' => __( 'Select Template Builder', 'wpfnl' ),
5758 'upload_image' => __( 'Upload image', 'wpfnl' ),
5759 'upload_image_tooltip' => __( 'Uploade Funnel Featured Image', 'wpfnl' ),
5760 'remove_image' => __( 'Remove image', 'wpfnl' ),
5761 'enter_seo_title' => __( 'Enter seo title', 'wpfnl' ),
5762 'enter_seo_desc' => __( 'Enter seo description', 'wpfnl' ),
5763 'save_data' => __( 'Save Data', 'wpfnl' ),
5764 'want_add_new_step' => __( 'Do you want to add a new step?', 'wpfnl' ),
5765 'what_change_you_need' => __( 'What Changes Do You Need in this Position?', 'wpfnl' ),
5766 'step_has_dependency' => __( 'The step you are deleting has a dependency. You can delete the step and connect the condition to the previous step or you can add a new step here.', 'wpfnl' ),
5767 'are_you_sure_to_delete_step' => __( 'Are you sure to delete this step?', 'wpfnl' ),
5768 'cancel' => __( 'Cancel', 'wpfnl' ),
5769 'no' => __( 'No', 'wpfnl' ),
5770 'connect_to_previous_step' => __( 'Connect to previous step', 'wpfnl' ),
5771 'add_new_step' => __( 'Add a new step', 'wpfnl' ),
5772 'yes' => __( 'Yes', 'wpfnl' ),
5773 'are_you_sure_delete_product' => __( 'Are you sure you want to delete this product?', 'wpfnl' ),
5774 'thankyou' => __( 'Thank you', 'wpfnl' ),
5775 'add_condition' => __( 'Set Entry Rules', 'wpfnl' ),
5776 'conditional_drawer_header' => __( 'Conditions', 'wpfnl' ),
5777 'add_featured_image' => __( 'Add Featured Image', 'wpfnl' ),
5778 'title' => __( 'Title', 'wpfnl' ),
5779 'url_slug' => __( 'URL Slug', 'wpfnl' ),
5780 'click_to_copy' => __( 'Click to Copy', 'wpfnl' ),
5781 'copied' => __( 'Copied!', 'wpfnl' ),
5782 'update' => __( 'Update', 'wpfnl' ),
5783 'enable_condition' => __( 'Enable Condition', 'wpfnl' ),
5784 'successfully_saved' => __( 'Succesfully saved', 'wpfnl' ),
5785 'no_condition_found' => __( 'No condition found', 'wpfnl' ),
5786 'add_new_condition' => __( 'Add New Condition', 'wpfnl' ),
5787 'and' => __( 'And', 'wpfnl' ),
5788 'or' => __( 'Or', 'wpfnl' ),
5789 'choose_next_steps' => __( 'Choose next steps', 'wpfnl' ),
5790 'false' => __( 'False', 'wpfnl' ),
5791 'please_select' => __( 'Please select', 'wpfnl' ),
5792 'select_steps' => __( 'Select Steps', 'wpfnl' ),
5793 'select_step' => __( 'Select Step', 'wpfnl' ),
5794 'add_step' => __( 'Add Step', 'wpfnl' ),
5795 'no_steps_found' => __( 'No steps found', 'wpfnl' ),
5796 'reconfigure_conditions' => __( 'Reconfigure Conditions', 'wpfnl' ),
5797 'reconfigure_conditions_helper' => __( 'Seems like you\'ve set a condition for the step earlier. Would you like to reconfigure?', 'wpfnl' ),
5798 'no_keep_the_same' => __( 'No, keep the same', 'wpfnl' ),
5799 'yes_reconfigure' => __( 'Yes, reconfigure', 'wpfnl' ),
5800 'zoom_in' => __( 'Zoom In', 'wpfnl' ),
5801 'zoom_level' => __( 'Zoom Level', 'wpfnl' ),
5802 'zoom_out' => __( 'Zoom Out', 'wpfnl' ),
5803
5804 // ------analytics------
5805 'weekly' => __( 'Weekly', 'wpfnl' ),
5806 'monthly' => __( 'Monthly', 'wpfnl' ),
5807 'yearly' => __( 'Yearly', 'wpfnl' ),
5808 'custom' => __( 'Custom', 'wpfnl' ),
5809 'start_date' => __( 'Start Date', 'wpfnl' ),
5810 'end_date' => __( 'End Date', 'wpfnl' ),
5811 'filter' => __( 'Filter', 'wpfnl' ),
5812 'conversion_overview' => __( 'Conversion Overview', 'wpfnl' ),
5813 'previous' => __( 'Previous', 'wpfnl' ),
5814 'next' => __( 'Next', 'wpfnl' ),
5815 'total_visitors' => __( 'Total visitors:', 'wpfnl' ),
5816 'unique_visitors' => __( 'unique visitors:', 'wpfnl' ),
5817 'conversion_rates' => __( 'conversion rates:', 'wpfnl' ),
5818 'bounce_rates' => __( 'Bounce Rates:', 'wpfnl' ),
5819 'no_data_found' => __( 'No data Found', 'wpfnl' ),
5820 'from' => __( 'From', 'wpfnl' ),
5821 'to' => __( 'To', 'wpfnl' ),
5822 'revenue' => __( 'Revenue', 'wpfnl' ),
5823 'are_you_sure_delete_analytics' => __( 'Are you sure to delete all analytics data? This won\'t affect other funnels.', 'wpfnl' ),
5824
5825 // ---webhook---
5826 'webhooks' => __( 'Webhooks', 'wpfnl' ),
5827 'search_by_name' => __( 'Search by Name', 'wpfnl' ),
5828 'add_webhook' => __( 'Add Webhook', 'wpfnl' ),
5829 'name' => __( 'Name', 'wpfnl' ),
5830 'request_url' => __( 'Request URL', 'wpfnl' ),
5831 'status' => __( 'Status', 'wpfnl' ),
5832 'actions' => __( 'Actions', 'wpfnl' ),
5833 'field_required' => __( 'Field Required', 'wpfnl' ),
5834 'key' => __( 'Key', 'wpfnl' ),
5835 'value' => __( 'Value', 'wpfnl' ),
5836 'select_value' => __( 'Select value', 'wpfnl' ),
5837 'select_name' => __( 'Select name', 'wpfnl' ),
5838 'add' => __( 'Add', 'wpfnl' ),
5839 'remove' => __( 'Remove', 'wpfnl' ),
5840 'delete_webhook' => __( 'Delete Webhook', 'wpfnl' ),
5841 'want_to_delete_webhook_list' => __( 'Are you sure you want to delete this List?', 'wpfnl' ),
5842 'edit_field' => __( 'Edit Field', 'wpfnl' ),
5843 'delete_field' => __( 'Delete Field', 'wpfnl' ),
5844
5845 // ----webhook settings------
5846 'give_name_or_id' => __( 'Give a name or ID to this webhook.', 'wpfnl' ),
5847 'allow_connect_3rd_party_services' => __( 'Allow to connect with 3rd party services.', 'wpfnl' ),
5848 'request_method' => __( 'Request Method', 'wpfnl' ),
5849 'select_http_request_method' => __( 'Select HTTP request sending method.', 'wpfnl' ),
5850 'select_method' => __( 'Select method', 'wpfnl' ),
5851 'request_format' => __( 'Request Format', 'wpfnl' ),
5852 'select_http_request_formate' => __( 'Select HTTP request sending format.', 'wpfnl' ),
5853 'select_format' => __( 'Select format', 'wpfnl' ),
5854 'webhook_events' => __( 'Webhook Events', 'wpfnl' ),
5855 'webhook_events_tooltip' => __( 'Select the WPFunnels event when the request would be sent.', 'wpfnl' ),
5856 'select_an_event' => __( 'Select an event', 'wpfnl' ),
5857 'request_body' => __( 'Request Body', 'wpfnl' ),
5858 'request_body_tooltip' => __( 'Select which values would be sent.', 'wpfnl' ),
5859 'all_fields' => __( 'All Fields', 'wpfnl' ),
5860 'select_fields' => __( 'Select Fields', 'wpfnl' ),
5861 'select_fields' => __( 'Add New Condition', 'wpfnl' ),
5862 'new_condition' => __( 'New Condition', 'wpfnl' ),
5863 'saved_successfully' => __( 'Saved Successfully', 'wpfnl' ),
5864 'please_select_an_event_first' => __( 'Please select an event first', 'wpfnl' ),
5865
5866 // ----funnels settings drawer.vue------
5867 'funnel_settings' => __( 'Funnel Settings', 'wpfnl' ),
5868 'utm_settings' => __( 'UTM Settings', 'wpfnl' ),
5869 'guide_on_utm_integration' => __( 'Guide On UTM Integration', 'wpfnl' ),
5870 'enable_utm_parameters_for_this_funnel' => __( 'Enable UTM Parameters For This Funnel', 'wpfnl' ),
5871 'pro' => __( 'pro', 'wpfnl' ),
5872 'referrer_utm_source' => __( 'Referrer (utm_source)', 'wpfnl' ),
5873 'medium_utm_medium' => __( 'Medium (utm_medium)', 'wpfnl' ),
5874 'campaign_utm_campaign' => __( 'Campaign (utm_campaign)', 'wpfnl' ),
5875 'content_utm_content' => __( 'Content (utm_content)', 'wpfnl' ),
5876 'event_tracking' => __( 'Event Tracking', 'wpfnl' ),
5877 'exclude_this_funnel_from_fb_pixel_tracking' => __( 'Exclude this funnel from FB Pixel tracking', 'wpfnl' ),
5878 'fb_pixel_tracking_hints' => __( 'Please connect from WPFunnels > Settings > Event Tracking to use it in this funnel.', 'wpfnl' ),
5879 'exclude_this_funnel_from_gtm_tracking' => __( 'Exclude this funnel from GTM tracking', 'wpfnl' ),
5880 'gtm_tracking_hints' => __( 'Please connect from WPFunnels > Settings > Event Tracking to use it in this funnel.', 'wpfnl' ),
5881 'more_settings' => __( 'More Settings', 'wpfnl' ),
5882 'skip_offers_for_identical_products_in_the_cart' => __( 'Skip offers for identical products in the cart', 'wpfnl' ),
5883 'skip_offers_cart_tooltip' => __( 'Enable this if you want to skip upsell or downsell offers (for Global Funnels only) when the offer product is the same as main product in the cart.', 'wpfnl' ),
5884 'dont_skip_offer_for_higher_quantity' => __( 'Don\'t skip if the offer is for a higher quantity', 'wpfnl' ),
5885 'dont_skip_offer_for_higher_quantity_tooltip' => __( 'Enabling this will mean that if the offer (for Global Funnels only) product is same as the product in the cart but with higher quantity, then it will not be skipped.', 'wpfnl' ),
5886 'skip_upsell_downsell_for_recurring_buyers' => __( 'Skip upsell/downsell for recurring buyers', 'wpfnl' ),
5887 'skip_upsell_downsell_for_recurring_buyers_tooltip' => __( 'Enabling this will mean if the upsell/downsell offer product was purchased by a buyer in the last 365 days, the offer will be skipped.', 'wpfnl' ),
5888 'threshold_max_365' => __( 'Threshold (max 365)', 'wpfnl' ),
5889 'threshold_max_365_tooltip' => __( 'You may specify the number of days (less than 365 days) within which the product has to be purchased for the offer to be skipped. For example, skip the offer if the product was purchased within the last 100 days.', 'wpfnl' ),
5890 'lms_sales_sunnel_support' => __( 'LMS Sales Funnel Support', 'wpfnl' ),
5891 'use_this_funnel_for_lms_only' => __( 'Use this funnel for LMS only', 'wpfnl' ),
5892 'use_this_funnel_for_lms_only_tooltip' => __( 'Enable this option to turn this funnel into a sales funnel for LMS. You can use it when you have a supported LMS activated such as LearnDash.', 'wpfnl' ),
5893 'save_changes' => __( 'Save Changes', 'wpfnl' ),
5894 'custom_css_desc' => __( 'Add custom CSS code here and it will be automatically added to the head section of this page. Do not include &lt;style&gt; tags.', 'wpfnl' ),
5895 'field_validation' => __( 'Field Validation', 'wpfnl' ),
5896 'enable_field_validation' => __( 'Enable Field validation error message', 'wpfnl' ),
5897 'enable_field_validation_desc' => __( 'Show custom validation error messages for required checkout fields.', 'wpfnl' ),
5898 'validation_message_label' => __( 'Validation error message', 'wpfnl' ),
5899
5900 // ----Global Funnel Offer Mode------
5901 'offer_selection_mode' => __( 'How Should the Offer be Selected?', 'wpfnl' ),
5902 'manual' => __( 'Specific Product', 'wpfnl' ),
5903 'auto_match' => __( 'Auto-match from Store', 'wpfnl' ),
5904 'recommended' => __( 'Recommended', 'wpfnl' ),
5905 'add_existing_product' => __( 'Add Existing Product', 'wpfnl' ),
5906 'create_product' => __( 'Create Product', 'wpfnl' ),
5907 'product' => __( 'PRODUCT', 'wpfnl' ),
5908 'total_price' => __( 'TOTAL PRICE', 'wpfnl' ),
5909 'no_product_yet' => __( 'No Product Yet', 'wpfnl' ),
5910 'fallback_behavior' => __( 'If No product Matches', 'wpfnl' ),
5911 'fallback_description' => __( 'What should happen when no rules match the main product?', 'wpfnl' ),
5912 'dont_show_upsell' => __( "Don’t show this offer", 'wpfnl' ),
5913 'skip_upsell_description' => __( 'Skip the offer if no rules match.', 'wpfnl' ),
5914 'show_default_product' => __( 'Show a default product', 'wpfnl' ),
5915 'default_product_description' => __( 'Offer a specific product when no rules match.', 'wpfnl' ),
5916
5917 // ----AB testing------
5918 'running' => __( 'Running', 'wpfnl' ),
5919 'draft' => __( 'Draft', 'wpfnl' ),
5920 'archived_variant' => __( 'Archived Variant', 'wpfnl' ),
5921 'ab_test' => __( 'A/B Testing', 'wpfnl' ),
5922 'click_to_copy_ab_url' => __( 'Click to Copy A/B URL', 'wpfnl' ),
5923 'copy_link' => __( 'Copy Link', 'wpfnl' ),
5924 'stats' => __( 'Stats', 'wpfnl' ),
5925 'traffic_distribution' => __( 'Traffic Distribution', 'wpfnl' ),
5926 'start' => __( 'Start', 'wpfnl' ),
5927 'pause' => __( 'Pause', 'wpfnl' ),
5928 'archive_list' => __( 'Archive List', 'wpfnl' ),
5929 'templates' => __( 'Templates', 'wpfnl' ),
5930 'duplicate' => __( 'Duplicate', 'wpfnl' ),
5931 'duplicating' => __( 'Duplicating..', 'wpfnl' ),
5932 'delete_variant' => __( 'Delete Variant', 'wpfnl' ),
5933 'are_you_sure_to_delete_this_variant' => __( 'Are you sure to delete this Variant?', 'wpfnl' ),
5934 'visits' => __( 'Visits', 'wpfnl' ),
5935 'conversion_rate' => __( 'Conversion Rate', 'wpfnl' ),
5936 'action' => __( 'Action', 'wpfnl' ),
5937 'retrieve' => __( 'Retrieve', 'wpfnl' ),
5938 'no_variation_found' => __( 'No Variation Found', 'wpfnl' ),
5939 'confirm_retrieve' => __( 'Confirm Retrieve', 'wpfnl' ),
5940 'are_you_sure_to_retrieve' => __( 'Are you sure to retrieve?', 'wpfnl' ),
5941 'confirm' => __( 'Confirm', 'wpfnl' ),
5942 'retrieving' => __( 'Retrieving...', 'wpfnl' ),
5943 'delete_this_variant' => __( 'Delete this Variant', 'wpfnl' ),
5944 'landing' => __( 'Landing', 'wpfnl' ),
5945 'checkout' => __( 'Checkout', 'wpfnl' ),
5946 'buy_now' => __( 'Buy Now', 'wpfnl' ),
5947 'upsell' => __( 'Upsell', 'wpfnl' ),
5948 'downsell' => __( 'Downsell', 'wpfnl' ),
5949 'conversion' => __( 'Conversion', 'wpfnl' ),
5950 'edit' => __( 'Edit', 'wpfnl' ),
5951 'view_step' => __( 'View Step', 'wpfnl' ),
5952 'more_option' => __( 'More Option', 'wpfnl' ),
5953 'declare_as_winner' => __( 'Declare as Winner', 'wpfnl' ),
5954 'make_archive' => __( 'Make Archive', 'wpfnl' ),
5955 'archiving' => __( 'Archiving...', 'wpfnl' ),
5956 'make_archive' => __( 'Make Archive', 'wpfnl' ),
5957 'traffic_distribution_tooltip' => __( 'Traffic distribution should be 100%', 'wpfnl' ),
5958 'close' => __( 'Close', 'wpfnl' ),
5959 'unlock_to_enter_value' => __( 'Unlock to Enter Value', 'wpfnl' ),
5960 'want_to_set_original' => __( 'Want to set “Original', 'wpfnl' ),
5961 'page_as_winner' => __( 'page” as a winner', 'wpfnl' ),
5962 'want_to_set' => __( 'Want to set', 'wpfnl' ),
5963 'as_a_winner' => __( 'as a winner', 'wpfnl' ),
5964 'set_variation_as_winner' => __( 'If you set this variation as a winner', 'wpfnl' ),
5965 'winner_check_list1' => __( 'Winner variant will be live on your site', 'wpfnl' ),
5966 'winner_check_list2' => __( 'Other variants will no longer available for the site visitors', 'wpfnl' ),
5967 'winner_check_list3' => __( 'Other variants will be on draft', 'wpfnl' ),
5968 'archive_all_except_winner' => __( 'Archive All Variants Except Winner', 'wpfnl' ),
5969 'declare_a_winner' => __( 'Declare a Winner', 'wpfnl' ),
5970
5971 // -----mail mint automation-----
5972 'automation' => __( 'Automation', 'wpfnl' ),
5973 'select_start_point' => __( 'Select A Start Point', 'wpfnl' ),
5974 'remove_this_trigger' => __( 'Remove this Trigger', 'wpfnl' ),
5975 'send_an_email' => __( 'Send An Email', 'wpfnl' ),
5976 'email_sequence' => __( 'Email Sequence', 'wpfnl' ),
5977 'assign_tags' => __( 'Assign Tag(s)', 'wpfnl' ),
5978 'add_to_lists' => __( 'Add To List(s)', 'wpfnl' ),
5979 'remove_tags' => __( 'Remove Tag(s)', 'wpfnl' ),
5980 'remove_lists' => __( 'Remove List(s)', 'wpfnl' ),
5981 'create_contact' => __( 'Create Contact', 'wpfnl' ),
5982 'outgoing_webhook' => __( 'Outgoing Webhook', 'wpfnl' ),
5983 'stop_automation' => __( 'Stop Automation', 'wpfnl' ),
5984 'delay' => __( 'Delay', 'wpfnl' ),
5985 'twilioSendMessage' => __( 'Send Message', 'wpfnl' ),
5986 'if_else' => __( 'If/Else', 'wpfnl' ),
5987 'set_hours_or_days' => __( 'Set a number of hours or days to wait before the next step of the journey is triggered.', 'wpfnl' ),
5988 'prepare_custom_email_template' => __( 'Prepare a custom email template to send to contacts that enter this automation flow.', 'wpfnl' ),
5989 'choose_an_email_sequence' => __( 'Choose an email sequence to send at this point of your automation flow.', 'wpfnl' ),
5990 'choose_tags_to_assign' => __( 'Choose Tag(s) to assign to contacts.', 'wpfnl' ),
5991 'choose_list_to_assign' => __( 'Choose List(s) to add contacts on.', 'wpfnl' ),
5992 'choose_tag_to_remove' => __( 'Choose certain tag(s) to remove from your contacts.', 'wpfnl' ),
5993 'choose_list_to_remove' => __( 'Remove contacts from selected lists(s).', 'wpfnl' ),
5994 'contact_meets_if_else' => __( 'Contact meets If/Else conditions.', 'wpfnl' ),
5995 'create_user_hints' => __( 'If the contact is not on the contacts list already, a new contact will be created.', 'wpfnl' ),
5996 'stop_automation_hints' => __( 'Use this action to stop your automation workflow at this point.', 'wpfnl' ),
5997 'webhook_outgoing_hints' => __( 'Send Data to any external server via GET or POST Methods at any point of the Automation workflow', 'wpfnl' ),
5998 'twillo_hints' => __( 'Send a message to the contact.', 'wpfnl' ),
5999 'not_set_up_yet' => __( 'Not set up yet', 'wpfnl' ),
6000 'assigned_tags' => __( 'Assigned Tags', 'wpfnl' ),
6001 'removed_tags' => __( 'Removed Tags', 'wpfnl' ),
6002 'assigned_lists' => __( 'Assigned Lists', 'wpfnl' ),
6003 'removed_lists' => __( 'Removed Lists', 'wpfnl' ),
6004 'select_status' => __( 'Select Status :', 'wpfnl' ),
6005 'welcome_email_from_wpfunnels' => __( 'Welcome email from WPFunnels', 'wpfnl' ),
6006 'stop_this_automation_workflow' => __( 'Stop This Automation Workflow', 'wpfnl' ),
6007 'send_data_via_get_post_method' => __( 'Send Data to external server via GET or POST Method', 'wpfnl' ),
6008 'delay_not_set_yet' => __( 'Delay Not Set Yet', 'wpfnl' ),
6009 'twilloBodyText' => __( 'Dispatch text message through Twilio SMS marketing.', 'wpfnl' ),
6010 'wait_for' => __( 'Wait for', 'wpfnl' ),
6011 'delete_this_step' => __( 'Delete this Step', 'wpfnl' ),
6012 'exit' => __( 'Exit', 'wpfnl' ),
6013 'choose_templates' => __( 'Choose Templates', 'wpfnl' ),
6014 'ready_email_templates' => __( 'Ready Email Templates', 'wpfnl' ),
6015 'my_saved_templates' => __( 'My Saved Templates', 'wpfnl' ),
6016 'edit_template' => __( 'Edit Template', 'wpfnl' ),
6017 'import' => __( 'Import', 'wpfnl' ),
6018 'do_you_want_to_save_automation' => __( 'Do you want to save the automation?', 'wpfnl' ),
6019 'no_back' => __( 'No & Back', 'wpfnl' ),
6020 'yes_back' => __( 'Yes & Back', 'wpfnl' ),
6021 'saving' => __( 'Saving...', 'wpfnl' ),
6022 'unsaved_changes' => __( 'Unsaved changes', 'wpfnl' ),
6023 'automation_save_failed' => __( 'Could not save your changes', 'wpfnl' ),
6024 'remove_automation_step_confirm' => __( 'Remove this step from the automation?', 'wpfnl' ),
6025
6026 'choose_list' => __( 'Choose List', 'wpfnl' ),
6027 'select_lists' => __( 'Select Lists', 'wpfnl' ),
6028 'search_list' => __( 'Search List', 'wpfnl' ),
6029 'add_list' => __( 'Add List', 'wpfnl' ),
6030 'choose_tags' => __( 'Choose Tags', 'wpfnl' ),
6031 'select_tags' => __( 'Select Tags', 'wpfnl' ),
6032 'search_tag' => __( 'Search Tag', 'wpfnl' ),
6033 'add_tag' => __( 'Add Tag', 'wpfnl' ),
6034 'contact_status' => __( 'Contact Status', 'wpfnl' ),
6035 'select_status' => __( 'Select Status', 'wpfnl' ),
6036 'set_waiting_time' => __( 'Set waiting time', 'wpfnl' ),
6037 'select_time' => __( 'Select Time', 'wpfnl' ),
6038 'minutes' => __( 'Minutes', 'wpfnl' ),
6039 'hours' => __( 'Hours', 'wpfnl' ),
6040 'days' => __( 'Days', 'wpfnl' ),
6041 'weeks' => __( 'Weeks', 'wpfnl' ),
6042 'month' => __( 'Month', 'wpfnl' ),
6043 'year' => __( 'Year', 'wpfnl' ),
6044 'sender_email' => __( 'Sender Email', 'wpfnl' ),
6045 'sender_name' => __( 'Sender Name', 'wpfnl' ),
6046 'reply_email' => __( 'Reply Email', 'wpfnl' ),
6047 'reply_name' => __( 'Reply Name', 'wpfnl' ),
6048 'email_subject_line' => __( 'Email Subject Line', 'wpfnl' ),
6049 'email_preview_text' => __( 'Email Preview Text', 'wpfnl' ),
6050 'email_preview_placeholder' => __( 'Be Specific and concise to spark interest', 'wpfnl' ),
6051 'design' => __( 'Design', 'wpfnl' ),
6052 'select_a_template' => __( 'Select a Template', 'wpfnl' ),
6053 'choose_a_sequence' => __( 'Choose a sequence', 'wpfnl' ),
6054 'data_send_method' => __( 'Data Send Method', 'wpfnl' ),
6055 'remote_url' => __( 'Remote URL', 'wpfnl' ),
6056 'request_format' => __( 'Request Format', 'wpfnl' ),
6057 'request_body_data' => __( 'Request Body Data', 'wpfnl' ),
6058 'body_key' => __( 'Body Key', 'wpfnl' ),
6059 'body_value' => __( 'Body Value', 'wpfnl' ),
6060 'enter_key' => __( 'Enter key', 'wpfnl' ),
6061 'add_new_item' => __( 'Add new item', 'wpfnl' ),
6062 'add_new' => __( 'Add New', 'wpfnl' ),
6063 'request_header' => __( 'Request Header', 'wpfnl' ),
6064 'header_key' => __( 'Header Key', 'wpfnl' ),
6065 'header_value' => __( 'Header Value', 'wpfnl' ),
6066 'new_rule' => __( 'New Rule', 'wpfnl' ),
6067 'enter_a_value' => __( 'Enter a value', 'wpfnl' ),
6068 'select_date' => __( 'Select Date', 'wpfnl' ),
6069 'mark_this_email_as_transactional' => __( 'Mark this email as transactional', 'wpfnl' ),
6070 'mark_this_email_as_transactional_tooltip' => __( 'Unsubscribed contacts will still receive transactional emails like Order Summaries, Renewal Reminders, and Admin notifications.', 'wpfnl' ),
6071
6072 // -----step importer------
6073 'enter_step_name' => __( 'Enter A Step Name', 'wpfnl' ),
6074 'find_your_templates' => __( 'Find your templates', 'wpfnl' ),
6075 'all' => __( 'all', 'wpfnl' ),
6076 'free' => __( 'free', 'wpfnl' ),
6077 'premium' => __( 'Premium', 'wpfnl' ),
6078 'freemium' => __( 'Freemium', 'wpfnl' ),
6079 'custom' => __( 'Custom', 'wpfnl' ),
6080 'pages' => __( 'Pages', 'wpfnl' ),
6081 'start_from_scratch' => __( 'Start From Scratch', 'wpfnl' ),
6082 'oops_it_looks_like' => __( 'Oops! It looks like', 'wpfnl' ),
6083 'is_inactive' => __( 'is inactive.', 'wpfnl' ),
6084 'it_seems_like_you_have_selected' => __( 'It seems like you have selected', 'wpfnl' ),
6085 'preferred_page_builder' => __( 'as your preferred page builder, but you do not have', 'wpfnl' ),
6086 'activated_your_site' => __( 'activated on your site.', 'wpfnl' ),
6087 'you_see_we_create_funnel_templates_for_gutenberg' => __( 'You see, we create funnel templates for Gutenberg using', 'wpfnl' ),
6088 'please_install_and_activate' => __( 'Please install and activate', 'wpfnl' ),
6089 'to_import_funnel_page_templates_for_gutenberg' => __( 'to import funnel page templates for Gutenberg.', 'wpfnl' ),
6090 'click_here_to_install_and_activate' => __( 'Click here to install & activate', 'wpfnl' ),
6091 'if_you_want_to_create_funnel_steps_without' => __( 'If you want to create funnel steps without using', 'wpfnl' ),
6092 'then_dont_worry' => __( 'then don"t worry.', 'wpfnl' ),
6093 'create_from_scratch' => __( 'You can go ahead and create this step from scratch and design it with any page builder/editor. It will work just fine.', 'wpfnl' ),
6094 'selected_builder_inactive' => __( 'Oops! It looks like the page builder you selected is inactive.', 'wpfnl' ),
6095 'elementor_didnot_active' => __( 'as your preferred page builder, but you do not have the plugin activated on your site.', 'wpfnl' ),
6096 'elementor_template_import' => __( 'to import funnel templates.', 'wpfnl' ),
6097 'if_you_want_to_design_funnel_steps_without' => __( 'If you want to design the funnel steps without using', 'wpfnl' ),
6098 'divi_notice_body1' => __( 'It seems like you have selected Divi builder as your preferred page builder, but you do not have the plugin activated on your site. Please install and activate Divi builder to import funnel templates.', 'wpfnl' ),
6099 'divi_notice_body2' => __( 'If you want to design the funnel steps without using Divi builder, then don"t worry.', 'wpfnl' ),
6100
6101 'oxygen_notice_body1' => __( 'It seems like you have selected Oxygen builder as your preferred page builder, but you do not have the plugin Oxygen builder activated on your site.', 'wpfnl' ),
6102 'oxygen_notice_body2' => __( 'Please install and activate Oxygen builder to import ready funnel templates.', 'wpfnl' ),
6103 'oxygen_notice_body3' => __( 'If you want to create & design funnel pages without using Oxygen builder, then don"t worry.', 'wpfnl' ),
6104 'oxygen_notice_body4' => __( 'You can go ahead and create funnels from scratch using any page builder/editor. It will work just fine.', 'wpfnl' ),
6105
6106 'importing_step' => __( 'Importing Step', 'wpfnl' ),
6107 'preview' => __( 'Preview', 'wpfnl' ),
6108
6109 // -----integration-------
6110 'funnel_integration' => __( 'Funnel Integration', 'wpfnl' ),
6111 'add_new_integration' => __( 'Add New Integration', 'wpfnl' ),
6112 'delete_selected' => __( 'Delete Selected', 'wpfnl' ),
6113 'crm' => __( 'CRM', 'wpfnl' ),
6114 'trigger' => __( 'Trigger', 'wpfnl' ),
6115 'options' => __( 'Options', 'wpfnl' ),
6116 'are_you_sure_delete_automation' => __( 'Are you sure you want to delete this Automation?', 'wpfnl' ),
6117
6118 'integration_name' => __( 'Integration Name:', 'wpfnl' ),
6119 'edit_name' => __( 'Edit Name', 'wpfnl' ),
6120 'save_name' => __( 'Save Name', 'wpfnl' ),
6121 'cancel_changes' => __( 'Cancel Changes', 'wpfnl' ),
6122 'connect_crm' => __( 'Connect CRM', 'wpfnl' ),
6123 'select_crm' => __( 'Select CRM', 'wpfnl' ),
6124 'see_documantation' => __( 'See Documantation', 'wpfnl' ),
6125 'test_zapier_trigger' => __( 'Test Zapier Trigger', 'wpfnl' ),
6126 'choose_crm' => __( 'Choose A CRM To Start Setting Up Your Integration', 'wpfnl' ),
6127 'no_crm_notice' => __( 'It appears no CRMs or automation tools are connected or activated. Please activate or connect a supported automation tool to use this feature.', 'wpfnl' ),
6128 'are_you_sure_dummy_data' => __( 'Are you sure want to send dummy data?', 'wpfnl' ),
6129 'field_sent_successfully' => __( 'Field Sent Successfully', 'wpfnl' ),
6130 'automation_name_required' => __( 'Automation name is required', 'wpfnl' ),
6131 'when' => __( 'When', 'wpfnl' ),
6132 'user_event' => __( 'User Event', 'wpfnl' ),
6133 'select_event' => __( 'Select event', 'wpfnl' ),
6134 'then' => __( 'Then', 'wpfnl' ),
6135 'add_to_audience' => __( 'Add To Audience', 'wpfnl' ),
6136 'add_to_group' => __( 'Add To Group', 'wpfnl' ),
6137 'add_to_static_list' => __( 'Add To Static List', 'wpfnl' ),
6138 'add_to_list' => __( 'Add To List', 'wpfnl' ),
6139 'assign_tag' => __( 'Assign Tag', 'wpfnl' ),
6140 'enter_zapier_webhook_url' => __( 'Enter Zapier Webhook URL', 'wpfnl' ),
6141 'enter_pabbly_webhook_url' => __( 'Enter Pabbly Webhook URL', 'wpfnl' ),
6142 'select_tag' => __( 'Please select', 'wpfnl' ),
6143
6144 // -----coupon.vue----
6145 'select_coupon' => __( 'Select Coupon', 'wpfnl' ),
6146 'search_for_coupon' => __( 'Search for coupon', 'wpfnl' ),
6147
6148 // -----discount.vue----
6149 'discount_type' => __( 'Discount Type', 'wpfnl' ),
6150 'discount_type_tooltip' => __( 'Choose if you want to give a unique percentage/flat discount on your', 'wpfnl' ),
6151 'discount_type_tooltip2' => __( 'Choose if you want to give a unique percentage/flat discount on your offer product in the funnel.', 'wpfnl' ),
6152 'discount_type_tooltip3' => __( 'Choose if you want to give a unique percentage/flat discount on your main product.', 'wpfnl' ),
6153 'offer' => __( 'offer.', 'wpfnl' ),
6154 'discount_apply_to' => __( 'Discount Apply To', 'wpfnl' ),
6155 'discount_apply_to_tooltip' => __( 'Choose if you want the discount to be applied on the Regular Price or the Sale Price of the', 'wpfnl' ),
6156 'discount_apply_to_tooltip2' => __( 'Chose if you want the discount to be applied on the Regular Price or the Sale Price of the offer product.', 'wpfnl' ),
6157 'discount_apply_to_tooltip3' => __( 'Choose if you want the discount to be applied on the Regular Price or the Sale Price of the main product.', 'wpfnl' ),
6158 'offer_product' => __( 'offer product.', 'wpfnl' ),
6159 'discount_value' => __( 'Discount Value', 'wpfnl' ),
6160 'discount_value_tooltip' => __( '(when discount type is percentage) Assign a suitable percentage discount.', 'wpfnl' ),
6161 'discount_value_tooltip2' => __( '(when discount type is flat amount) Choose a certain amount to waive off.', 'wpfnl' ),
6162 'original_price' => __( 'Original Price', 'wpfnl' ),
6163 'sale_price' => __( 'Sale Price', 'wpfnl' ),
6164 'discounted_offer_price' => __( 'Discounted Offer Price', 'wpfnl' ),
6165 'discounted_offer_price_tooltip1' => __( 'This will be the', 'wpfnl' ),
6166 'discounted_offer_price_tooltip2' => __( 'offer price your buyer will pay.', 'wpfnl' ),
6167 'discounted_offer_price_tooltip3' => __( 'This will be the offer price your buyer will pay.', 'wpfnl' ),
6168 'discounted_offer_price_tooltip4' => __( 'This will be the main product price your buyer will pay.', 'wpfnl' ),
6169 'replace_first_product' => __( 'Replace First Product', 'wpfnl' ),
6170 'replace_first_product_hints' => __( 'It will replace the first selected product (from checkout products) with the order bump product.', 'wpfnl' ),
6171 'replace_product' => __( 'Replace Product', 'wpfnl' ),
6172 'pre_purchase_upsell_text' => __( 'Set as pre-purchase upsell', 'wpfnl' ),
6173 'pre_purchase_upsell_tooltip_text' => __( 'Enable this option to display a special offer to customers before they complete their purchase. The upsell will appear after they click the Place Order button, giving them a final opportunity to add the promoted product to their order.', 'wpfnl' ),
6174 'replace_product_tooltip' => __( 'Enabling this will mean that if a buyer accepts the order bump offer, then the order bump product will replace the main products in the checkout and the buyer will just have to pay for the order bump product.', 'wpfnl' ),
6175 'replace_product_hints' => __( 'It will replace all products (from checkout products) with the order bump product.', 'wpfnl' ),
6176
6177 'search_for_categories' => __( 'Search for categories', 'wpfnl' ),
6178 'search_for_tag' => __( 'Search for tag', 'wpfnl' ),
6179 'product' => __( 'Product', 'wpfnl' ),
6180
6181 'enter_funnel_if' => __( 'Enter Funnel If', 'wpfnl' ),
6182 'add_category' => __( 'Add Category', 'wpfnl' ),
6183 'add_product' => __( 'Add Existing Product', 'wpfnl' ),
6184 'amount' => __( 'Amount', 'wpfnl' ),
6185 'ammount_must_be_greater_than_1' => __( 'Amount must be greater than 1', 'wpfnl' ),
6186 'ammount_must_be_greater_than_0' => __( 'Amount must be greater than 0', 'wpfnl' ),
6187 'quantity' => __( 'Quantity', 'wpfnl' ),
6188 'quantity_must_be_greater_than_1' => __( 'Quantity must be greater than 1', 'wpfnl' ),
6189 'day_must_be_greater_than_or_euqal_1' => __( 'Day must be greater than or euqal 1', 'wpfnl' ),
6190 'role' => __( 'Role', 'wpfnl' ),
6191 'select_role' => __( 'Select Role', 'wpfnl' ),
6192 'class' => __( 'Class', 'wpfnl' ),
6193 'slect_shippin_class' => __( 'Select Shipping Class', 'wpfnl' ),
6194 'date' => __( 'Date :', 'wpfnl' ),
6195
6196 // -----image upload.vue------
6197 'product_image' => __( 'Product Image', 'wpfnl' ),
6198 'course_image' => __( 'Course Image', 'wpfnl' ),
6199 'product_image_tooltip' => __( 'You may change this image and use a custom image just for this order bump.', 'wpfnl' ),
6200 'click_to_change_image' => __( 'Click to Change Image', 'wpfnl' ),
6201 'click_to_update_image' => __( 'Drop an Image or ', 'wpfnl' ),
6202 'recommended_image_size' => __( 'Recommended image size 160px x 160px', 'wpfnl' ),
6203
6204 // -----orderbump.vue------
6205 'order_bump' => __( 'Order Bump', 'wpfnl' ),
6206 'add_order_bump' => __( 'Add Order Bump', 'wpfnl' ),
6207 'no_order_bump' => __( 'No Order Bump', 'wpfnl' ),
6208 'order_bump_hints1' => __( 'You can only add 1 order bump offer in the Free version.', 'wpfnl' ),
6209 'order_bump_hints2' => __( 'Upgrade to Pro now to create multiple order bump offers.', 'wpfnl' ),
6210 'upgrade_to_wpfunnels_pro' => __( 'Upgrade to WPFunnels Pro', 'wpfnl' ),
6211 'product_must_be_added' => __( 'Product must be added', 'wpfnl' ),
6212 'offer_must_be_selected' => __( 'Offer must be selected', 'wpfnl' ),
6213 'ob_default_product_name' => __( '6D Screen Protector (20% OFF)', 'wpfnl' ),
6214 'ob_highLight' => __( 'Special one time offer', 'wpfnl' ),
6215 'ob_checkbox_label' => __( 'Grab this offer with one click!', 'wpfnl' ),
6216 'ob_product_details' => __( 'Get this scratch proof 6D Tempered Glass Screen Protector for your iPhone. Keep your phone safe and sound just like a new one.', 'wpfnl' ),
6217 'ob_preview_tooltip' => __( 'The width of the order bump may vary depending on the page/container size on the Checkout Page or the device screen size.', 'wpfnl' ),
6218 'choose_offers' => __( 'Which Products to Pick from', 'wpfnl' ),
6219 'choose_offers_condition' => __( 'Select the rule that determines which product(s) will be automatically recommended as the upsell.', 'wpfnl' ),
6220 'select_product' => __( 'Select Product', 'wpfnl' ),
6221 'select_product_tooltip' => __( 'Choose the product that you want to offer as order bump at the checkout.', 'wpfnl' ),
6222 'search_for_product' => __( 'Search for product', 'wpfnl' ),
6223 'search_for_course' => __( 'Search for course', 'wpfnl' ),
6224 'select_course' => __( 'Select Course', 'wpfnl' ),
6225 'choose_the' => __( 'Choose the', 'wpfnl' ),
6226 'course' => __( 'course', 'wpfnl' ),
6227 'select_course_tooltip' => __( 'that you want to offer as order bump at the checkout.', 'wpfnl' ),
6228 'course_title' => __( 'Course Title', 'wpfnl' ),
6229 'product_title' => __( 'Product Title', 'wpfnl' ),
6230 'select_color_for_title' => __( 'Select Color for Title', 'wpfnl' ),
6231 'course_quantity' => __( 'Course Quantity', 'wpfnl' ),
6232 'product_quantity' => __( 'Product Quantity', 'wpfnl' ),
6233 'highlight_text' => __( 'Highlight Text', 'wpfnl' ),
6234 'hilight_text_color' => __( 'Select Color for Highlight Text', 'wpfnl' ),
6235 'checkbox_label' => __( 'Checkbox Label', 'wpfnl' ),
6236 'checkbox_title_color' => __( 'Select Color for Checkbox Title', 'wpfnl' ),
6237 'course_description' => __( 'Course Description', 'wpfnl' ),
6238 'product_description' => __( 'Product Description', 'wpfnl' ),
6239 'product_description_color' => __( 'Select Color for Product Description', 'wpfnl' ),
6240 'ob_delete_confirmation' => __( 'Are you sure you want to Delete the', 'wpfnl' ),
6241 'enable_order_bump' => __( 'Enable Order Bump', 'wpfnl' ),
6242 'delete_order_bump' => __( 'Delete Order Bump', 'wpfnl' ),
6243 'ob_name' => __( 'Order Bump Name', 'wpfnl' ),
6244 'select_template' => __( 'Template Style', 'wpfnl' ),
6245 'select_template_tooltip' => __( 'Choose a suitable template for your Order bump design.', 'wpfnl' ),
6246 'ob_position' => __( 'Display Position', 'wpfnl' ),
6247 'ob_position_tooltip' => __( 'Decide where the order bump offer should be placed in the checkout page.', 'wpfnl' ),
6248 'ob_arrow_color' => __( 'Arrow Color', 'wpfnl' ),
6249 'ob_remove_arrow_color' => __( 'Remove arrow color', 'wpfnl' ),
6250 'ob_bg_color' => __( 'Background Color', 'wpfnl' ),
6251 'ob_bg_color_tooltip' => __( 'Change the color of the Checkbox bar if you want to.', 'wpfnl' ),
6252 'ob_remove_bg_color' => __( 'Remove background color', 'wpfnl' ),
6253 'ob_main_color' => __( 'Main Color', 'wpfnl' ),
6254 'ob_main_color_tooltip' => __( 'Change the color of the Checkbox bar if you want to.', 'wpfnl' ),
6255 'ob_price_color' => __( 'Price Color', 'wpfnl' ),
6256 'ob_choose_variant_text' => __( 'Order Bump Choose Variant Text', 'wpfnl' ),
6257 'select_color_for_variant_text' => __( 'Select Color for Variant Text', 'wpfnl' ),
6258 'price' => __( 'Price', 'wpfnl' ),
6259 'regular_price' => __( 'Regular Price', 'wpfnl' ),
6260 'ob_template10_title' => __( 'to your order for only', 'wpfnl' ),
6261 'offer_price' => __( 'Offer Price', 'wpfnl' ),
6262
6263 // -----mint automation------
6264 'select_offer' => __( 'Select Offer', 'wpfnl' ),
6265 'select_order_bump' => __( 'Select Order Bump', 'wpfnl' ),
6266
6267 // -----step body------
6268 'new_step' => __( 'New Step', 'wpfnl' ),
6269 'new_step_hints' => __( 'Use this button to create the first step of the funnel.', 'wpfnl' ),
6270 'delete_this_step' => __( 'Delete this step', 'wpfnl' ),
6271 'ab_testing_running' => __( 'A/B Test Running', 'wpfnl' ),
6272 'view_automation' => __( 'View Automation', 'wpfnl' ),
6273 'add_automation_action' => __( 'Add Automation Action', 'wpfnl' ),
6274 'automation_running' => __( 'Automation running', 'wpfnl' ),
6275 'add_automation' => __( 'Add Automation', 'wpfnl' ),
6276 'upload_funnel_feature_image' => __( 'Uploade Funnel Featured Image', 'wpfnl' ),
6277 'oxygen_step_url' => __( 'Oxygen Builder Step URL', 'wpfnl' ),
6278
6279 'no_product_added' => __( 'No Product Added', 'wpfnl' ),
6280 'no_course_added' => __( 'No Course Added', 'wpfnl' ),
6281 'run_ab_split_test' => __( 'Run A/B Split Test', 'wpfnl' ),
6282 'to_funnel_update_msg' => __( 'Please update to WPFunnels Pro 1.7.3 or higher to use this feature.', 'wpfnl' ),
6283 'conditions' => __( 'Conditions', 'wpfnl' ),
6284
6285 // -----vertical flow canvas------
6286 'order_bumps' => __( 'Order Bumps', 'wpfnl' ),
6287 'checkout_products' => __( 'Products', 'wpfnl' ),
6288 'store_conditions' => __( 'Store Conditions', 'wpfnl' ),
6289 'add_featured_image' => __( 'Add Featured Image', 'wpfnl' ),
6290 'automation' => __( 'Automation', 'wpfnl' ),
6291 'add_conditional_next_step' => __( 'Add Conditional Next Step', 'wpfnl' ),
6292 'add_step_here' => __( 'Add step here', 'wpfnl' ),
6293 'paste_here' => __( 'Paste here', 'wpfnl' ),
6294 'conditional' => __( 'Conditional', 'wpfnl' ),
6295 'click_to_configure' => __( 'Click to configure', 'wpfnl' ),
6296 'journey_ends_here' => __( 'Journey ends here', 'wpfnl' ),
6297 'activated_conditional_flow' => __( 'Activated Conditional Flow', 'wpfnl' ),
6298 'need_reconfigure_condition' => __( 'Need reconfigure condition', 'wpfnl' ),
6299
6300 // -----step settings drawer------
6301 'assign_the_main_funnel_product' => __( 'Assign the main funnel product here.', 'wpfnl' ),
6302 'courses' => __( 'Courses', 'wpfnl' ),
6303 'products' => __( 'Products', 'wpfnl' ),
6304 'set_ob_tooltip' => __( 'Set up an order bump offer at the checkout.', 'wpfnl' ),
6305 'edit_field_tooltip' => __( 'Checkout Field Editor for this funnel.', 'wpfnl' ),
6306 'edit_fields' => __( 'Edit Fields', 'wpfnl' ),
6307
6308 'add_new_field' => __( 'Add New Field', 'wpfnl' ),
6309 'select_section' => __( 'Select Section', 'wpfnl' ),
6310 'billing' => __( 'Billing', 'wpfnl' ),
6311 'shipping' => __( 'Shipping', 'wpfnl' ),
6312 'additional' => __( 'Additional', 'wpfnl' ),
6313 'type' => __( 'Type', 'wpfnl' ),
6314 'add_more' => __( 'Add More', 'wpfnl' ),
6315 'option_text' => __( 'Option text', 'wpfnl' ),
6316 'option_value' => __( 'Option value', 'wpfnl' ),
6317 'label' => __( 'Label', 'wpfnl' ),
6318 'placeholder' => __( 'Placeholder', 'wpfnl' ),
6319 'default_value' => __( 'Default Value', 'wpfnl' ),
6320 'validation' => __( 'Validation', 'wpfnl' ),
6321 'validations' => __( 'Validations', 'wpfnl' ),
6322 'required' => __( 'Required', 'wpfnl' ),
6323 'enabled' => __( 'Enabled', 'wpfnl' ),
6324 'display_in_order_detail' => __( 'Display in Order Detail Pages', 'wpfnl' ),
6325 'select_section_type' => __( 'Please select section type', 'wpfnl' ),
6326 'select_field_type' => __( 'Please select field type', 'wpfnl' ),
6327 'name_should_not_be_empty' => __( 'Name should not be empty', 'wpfnl' ),
6328 'label_should_not_be_empty' => __( 'Label should not be empty', 'wpfnl' ),
6329 'are_you_sure_delete_this_field' => __( 'Are you sure you want to delete this field?', 'wpfnl' ),
6330
6331 'billing_fields' => __( 'Billing Fields', 'wpfnl' ),
6332 'shipping_fields' => __( 'Shipping Fields', 'wpfnl' ),
6333 'additional_fields' => __( 'Additional Fields', 'wpfnl' ),
6334 'restore_to_default' => __( 'Restore to default', 'wpfnl' ),
6335 'add_field' => __( 'Add Field', 'wpfnl' ),
6336 'additional_options' => __( 'Additional Options', 'wpfnl' ),
6337 'custom_order_button_text' => __( 'Custom order button text', 'wpfnl' ),
6338 'custom_order_button_text_tooltip' => __( 'Set a custom text for the place order button.', 'wpfnl' ),
6339 'custom_billing_section_heading' => __( 'Custom Billing Section Heading', 'wpfnl' ),
6340 'custom_billing_section_heading_tooltip' => __( 'Set a custom heading text for the Billing Section.', 'wpfnl' ),
6341 'custom_shipping_section_heading' => __( 'Custom Shipping Section Heading', 'wpfnl' ),
6342 'custom_shipping_section_heading_tooltip' => __( 'Set a custom heading text for the Shipping Section.', 'wpfnl' ),
6343 'custom_order_detail_section_heading' => __( 'Custom Order Detail Section Heading', 'wpfnl' ),
6344 'custom_order_detail_section_heading_tooltip' => __( 'Set a custom heading text for the Order Detail Section.', 'wpfnl' ),
6345 'save_additional_changes' => __( 'Save Addtional Changes', 'wpfnl' ),
6346 'billint_details' => __( 'Billing details', 'wpfnl' ),
6347 'shipto_different_address' => __( 'Ship to a different address?', 'wpfnl' ),
6348 'your_order' => __( 'Your order', 'wpfnl' ),
6349 'place_order' => __( 'Place Order', 'wpfnl' ),
6350
6351 'this_product_in_trash' => __( 'This product is in the Trash. Please restore it', 'wpfnl' ),
6352 'recurring' => __( 'Recurring', 'wpfnl' ),
6353 'subtext' => __( 'Subtext', 'wpfnl' ),
6354 'enable_highlight' => __( 'Enable Highlight', 'wpfnl' ),
6355 'permanently_deleted' => __( 'This Product is permanently deleted', 'wpfnl' ),
6356 'edit_product' => __( 'Edit product', 'wpfnl' ),
6357 'delete_product' => __( 'Delete product', 'wpfnl' ),
6358
6359 // -------product tab content----
6360 'funnel_products' => __( 'Funnel Products', 'wpfnl' ),
6361 'funnel_products_tooltip' => __( 'This will be the main offer product of the funnel. Search for a product, select it, and then click on the "+ Add Product" button.', 'wpfnl' ),
6362 'add_course' => __( 'Add course', 'wpfnl' ),
6363 'total' => __( 'Total', 'wpfnl' ),
6364 'allow_use_of_coupon' => __( 'Allow Use Of Coupon', 'wpfnl' ),
6365 'allow_use_of_coupon_tooltip' => __( 'Enable this if you want to allow buyers to use coupons during checkout in the funnel.', 'wpfnl' ),
6366 'collapsible_coupon_field' => __( 'Collapsible Coupon Field', 'wpfnl' ),
6367 'collapsible_coupon_field_tooltip' => __( 'Enable this to show the coupon field in a collapsible section on the checkout page.', 'wpfnl' ),
6368 'collapsible' => __( 'Collapsible', 'wpfnl' ),
6369 'coupon_auto_applied' => __( 'Coupon (auto-applied)', 'wpfnl' ),
6370 'coupon_auto_applied_tooltip' => __( 'Enable this to allow Auto-apply Coupon during the checkout on this funnel.', 'wpfnl' ),
6371 'quantity_limit' => __( 'Enable Quantity Limit', 'wpfnl' ),
6372 'quantity_limit_tooltip' => __( 'Enable this settings to set the quantity limit for a single user', 'wpfnl' ),
6373 'set_quantity_limit' => __( 'Set the quantity limit for a single user', 'wpfnl' ),
6374 'set_quantity_limit_tooltip' => __( 'Set the quantity limit for a single user', 'wpfnl' ),
6375 'choose_coupon' => __( 'Choose coupon', 'wpfnl' ),
6376 'choose_coupon_tooltip' => __( 'Choose any existing WooCommerce coupon from the dropdown.', 'wpfnl' ),
6377 'enable_quantity_selection' => __( 'Enable Quantity Selection', 'wpfnl' ),
6378 'enable_quantity_selection_tooltip' => __( 'Enable this if you want to allow buyers to change the quantity of the product they want to buy.', 'wpfnl' ),
6379
6380 // ------downsell-------
6381 'upsell_course' => __( 'Upsell Course', 'wpfnl' ),
6382 'downsell_course' => __( 'Downsell Course', 'wpfnl' ),
6383 'downsell_product' => __( 'Downsell Product', 'wpfnl' ),
6384 'select_an_offer' => __( 'Please select an offer', 'wpfnl' ),
6385 'select_category' => __( 'Select Category', 'wpfnl' ),
6386 'choose_offer_category' => __( 'Choose offer category', 'wpfnl' ),
6387 'choose_offer_tag' => __( 'Choose offer tag', 'wpfnl' ),
6388 'selected_price_is_less_than1' => __( 'Selected price is less than 1', 'wpfnl' ),
6389 'gbf_product_quantity_tooltip' => __( 'The number of this upsell product to add to the cart when the customer accepts the offer.', 'wpfnl' ),
6390 'selected_quantity_is_less_than1' => __( 'Selected quantity is less than 1', 'wpfnl' ),
6391 'enable_replace_settings' => __( 'Enable Replace Settings', 'wpfnl' ),
6392 'enable_replace_settings_tooltip' => __( 'Enabling this will mean that if a buyer accepts the downsell offer, then the downsell offer product will replace the main product in the checkout and the buyer will just have to pay for the downsell offer product.', 'wpfnl' ),
6393 'replace_main_order_including_order_bump' => __( 'Replace Main Order Including Order Bump', 'wpfnl' ),
6394 'replace_main_order_excluding_order_bump' => __( 'Replace Main Order Excluding Order bump', 'wpfnl' ),
6395 'replace_all_prior_orders' => __( 'Replace All Prior Orders', 'wpfnl' ),
6396 'replace_order_in_previous_step' => __( 'Replace Order in Previous Step', 'wpfnl' ),
6397 'enable_replace_with_main_product' => __( 'Enable replace with the main product.', 'wpfnl' ),
6398 'enable_replace_with_main_product_tooltip1' => __( 'You must enable child orders for offers to use this feature.', 'wpfnl' ),
6399 'enable_replace_with_main_product_tooltip2' => __( 'Go to WPFunnels > Settings > Offer Settings and enable "Create a new child order."', 'wpfnl' ),
6400 'product_must_be_added' => __( 'Product must be added', 'wpfnl' ),
6401 'offer_must_be_selected' => __( 'Offer must be selected', 'wpfnl' ),
6402
6403 // ------thank you settings-------
6404 'show_order_overview' => __( 'Show Order Overview', 'wpfnl' ),
6405 'show_order_details' => __( 'Show Order Details', 'wpfnl' ),
6406 'show_billing_details' => __( 'Show Billing Details', 'wpfnl' ),
6407 'show_shipping_details' => __( 'Show Shipping Details', 'wpfnl' ),
6408 'thankyou_redicrection_page' => __( 'Thank You Page Redirection', 'wpfnl' ),
6409 'thankyou_redicrection_page_tooltip' => __( 'Redirect buyers to an external link after viewing the thank you page for a few seconds or take the buyer to another page instead of the thank you page.', 'wpfnl' ),
6410 'redirect_url' => __( 'Redirect URL', 'wpfnl' ),
6411 'redirect_without_viewing_thankyou' => __( 'Redirect without viewing Thank You Page', 'wpfnl' ),
6412 'redirect_without_viewing_thankyou_tooltip' => __( 'The buyer will be taken to the redirect URL directly instead of the thank you page.', 'wpfnl' ),
6413 'redirect_after_viewing_thankyou' => __( 'Redirect after viewing Thank You Page', 'wpfnl' ),
6414 'redirect_after_viewing_thankyou_tooltip' => __( 'The buyer be taken to thank you page, and after the set time, will be redirected to the given redirect URL.', 'wpfnl' ),
6415 'redirect_after_in_second' => __( 'Redirect after [ in second ]', 'wpfnl' ),
6416
6417 // -----upsell settings-----
6418 'upsell_product' => __( 'Upsell Product', 'wpfnl' ),
6419 'please_select_a_category' => __( 'Please select a category', 'wpfnl' ),
6420 'please_select_a_tag' => __( 'Please select a tag', 'wpfnl' ),
6421 'upsell_product_quantity_tooltip' => __( 'The value you input here will be added to the quantity of the product that triggered the funnel, which will be offered to the buyer as the upsell offer. For example, if someone purchases 2 t-shirts, and you input 3 here, the buyer will be offered 2+3 = 5 t-shirts as the upsell offer.', 'wpfnl' ),
6422 'upsell_enable_replace_settings_tooltip' => __( 'Enabling this will mean that if a buyer accepts the upsell offer, then the upsell offer product will replace the main product in the checkout and the buyer will just have to pay for the upsell offer product.', 'wpfnl' ),
6423
6424 // -------funnels template-------
6425 'steps' => __( 'Steps', 'wpfnl' ),
6426 'step' => __( 'Step', 'wpfnl' ),
6427 'getting_ready_to_import' => __( 'Getting ready to import', 'wpfnl' ),
6428 'step_creating' => __( 'Creating Step...', 'wpfnl' ),
6429 'click_to_upgrade' => __( 'Click to Upgrade Pro', 'wpfnl' ),
6430 'template_builder_tooltip1' => __( 'as your preferred page builder, but you do not have the plugin activated on your site. Please install and activate', 'wpfnl' ),
6431 'if_you_want_to_create_funnels_without_using' => __( 'If you want to create funnels without using', 'wpfnl' ),
6432 'create_funnel_from_scratch' => __( 'You can go ahead and create funnels from scratch with any page builder/editor. It will work just fine.', 'wpfnl' ),
6433 'we_have_ready_templates_for' => __( 'As of now, we have ready templates for', 'wpfnl' ),
6434 'more_template_coming_soon' => __( 'only, but we are planning to create templates for more builders soon.', 'wpfnl' ),
6435 'create_a_funnel' => __( 'Create A Funnel', 'wpfnl' ),
6436 'how_to_use_this_funnel' => __( 'How to use this funnel', 'wpfnl' ),
6437 'creating_funnel' => __( 'Creating Funnel...', 'wpfnl' ),
6438 'creating_store_checkout' => __( 'Creating Store Checkout...', 'wpfnl' ),
6439 'write_funnel_name' => __( 'Write Funnel Name', 'wpfnl' ),
6440 'templates' => __( 'Templates', 'wpfnl' ),
6441 'template' => __( 'Template', 'wpfnl' ),
6442 'please_wait' => __( 'Please Wait...', 'wpfnl' ),
6443
6444 // -------Available payment gateways-------
6445 'disable_payment_gateways' => __( 'Disable payment gateways for this checkout', 'wpfnl' ),
6446 'disable_payment_gateways_tooltip' => __( 'Disable payment gateways for this checkout to restrict payment options. This setting is useful for special cases where you want to manage payments manually or through alternative methods.', 'wpfnl' ),
6447
6448 // -------Time bound discount-------
6449 'startDateText' => __( 'Start Date', 'wpfnl' ),
6450 'endDateText' => __( 'End Date', 'wpfnl' ),
6451 'enableTimeBoundDiscountText' => __( 'Enable time bound discount', 'wpfnl' ),
6452 'enableTimeBoundDiscountTooltipText' => __( 'This allows you to set a date range for offering discounts. Simply select the start and end dates, and the discount will automatically apply to purchases made during this period.', 'wpfnl' ),
6453 'undo' => __('Undo', 'wpfnl'),
6454 'stepCopied' => __('Step copied', 'wpfnl'),
6455 'copy' => __('Copy', 'wpfnl'),
6456
6457 // -------Automation Analytics-------
6458 'report' => __('Report', 'wpfnl'),
6459 'back' => __('Back', 'wpfnl'),
6460 'automation' => __('Automation', 'wpfnl'),
6461 'performance' => __('Performance', 'wpfnl'),
6462 'open_rate' => __('Open Rate', 'wpfnl'),
6463 'click_rate' => __('Click Rate', 'wpfnl'),
6464 'email_sent' => __('Email Sent', 'wpfnl'),
6465 'open' => __('Open', 'wpfnl'),
6466 'click' => __('Click', 'wpfnl'),
6467 'overall_report' => __('Overall Report', 'wpfnl'),
6468 'entrance' => __('Entrance', 'wpfnl'),
6469 'subscribers_completed' => __('Subscribers Completed', 'wpfnl'),
6470 'welcome_to_wpfunnels' => __('Welcome to WPFunnels', 'wpfnl'),
6471 'setup_wizard' => __('Setup Wizard', 'wpfnl'),
6472 'support' => __('Support', 'wpfnl'),
6473 'video' => __('Video', 'wpfnl'),
6474
6475 'total_customers' => __('Total Customers', 'wpfnl'),
6476 'total_orders' => __('Total Orders', 'wpfnl'),
6477 'total_revenue' => __('Total Revenue', 'wpfnl'),
6478 'order_bump_revenue' => __('Order Bump Revenue', 'wpfnl'),
6479 'upsell_downsell_evenue' => __('Upsell/Downsell Revenue', 'wpfnl'),
6480 'growth_comparison' => __('Growth Comparison', 'wpfnl'),
6481 'previous_year' => __('Previous Year', 'wpfnl'),
6482 'current_year' => __('Current Year', 'wpfnl'),
6483 'top_performing_funnels' => __('Top Performing Funnels', 'wpfnl'),
6484 'funnel_name' => __('Funnel Name', 'wpfnl'),
6485 'views' => __('Views', 'wpfnl'),
6486 'conversion' => __('Conversion', 'wpfnl'),
6487 'conversion_rate' => __('Conversion Rate', 'wpfnl'),
6488 'no_top_performing_funnels' => __('You have no top performing funnels', 'wpfnl'),
6489 'view_all' => __('View all', 'wpfnl'),
6490 'today' => __('Today', 'wpfnl'),
6491 'yesterday' => __('Yesterday', 'wpfnl'),
6492 'this_week' => __('This Week', 'wpfnl'),
6493 'last_week' => __('Last Week', 'wpfnl'),
6494 'this_month' => __('This Month', 'wpfnl'),
6495 'last_month' => __('Last Month', 'wpfnl'),
6496 'previous_month' => __('Previous month', 'wpfnl'),
6497 'next_month' => __('Next month', 'wpfnl'),
6498 'cancel' => __('Cancel', 'wpfnl'),
6499 'apply' => __('Apply', 'wpfnl'),
6500 'week_to_date' => __('Week to date', 'wpfnl'),
6501 'month_to_date' => __('Month to date', 'wpfnl'),
6502 'year_to_date' => __('Year to date', 'wpfnl'),
6503 'previous_period' => __('Previous period', 'wpfnl'),
6504 'previous_year' => __('Previous year', 'wpfnl'),
6505
6506 // -------Funnel dashboard-------
6507
6508 // tour text
6509 'add_product_on_canvas' => __('Add Product', 'wpfnl'),
6510 'add_product_on_canvas_des' => __('To add a product to this funnel, click the cart icon. You can select an existing product from your store or create a new one. This will add the product to your funnel and allow you to adjust its quantity, price, and other details.', 'wpfnl'),
6511 'update_funnel_name' => __('Update Funnel Name', 'wpfnl'),
6512 'update_funnel_name_des' => __('To give your funnel a unique and recognizable name, simply click the Funnel Name field at the top. Enter your new name and make sure to Save your changes to update the funnel details.', 'wpfnl'),
6513 'view_funnel_stats' => __('View Funnel Stats', 'wpfnl'),
6514 'view_funnel_stats_des' => __("To monitor the performance of your funnel, click on the Stats button or navigate to the Analytics section. Here, you'll see key metrics like total leads, conversions, and revenue, helping you track and optimize your funnel's success.", 'wpfnl'),
6515 'preview_funnel' => __("Preview Funnel", 'wpfnl'),
6516 'preview_funnel_des' => __("Click the Preview button to see how your funnel looks in real-time. This allows you to review each step and ensure everything is set up as you want before going live.", 'wpfnl'),
6517 'ab_testing_on_canvas' => __("Enable A/B Testing", 'wpfnl'),
6518 'ab_testing_on_canvas_des' => __("Activate A/B Testing to compare different versions of your funnel steps. This feature allows you to test variations in content, design, and calls to action to determine which version performs better. Use the insights gained to optimize your funnel for maximum conversions.", 'wpfnl'),
6519 'condition_on_canvas' => __("Enable Conditional Funnel", 'wpfnl'),
6520 'condition_on_canvas_des' => __("To create a tailored experience for your users, enable the Conditional Funnel feature. This allows you to set specific conditions that determine which steps users will see based on their actions or selections. Customize your funnel flow to increase engagement and conversion rates.", 'wpfnl'),
6521 'add_new_step' => __("Add New Step", 'wpfnl'),
6522 'add_new_step_des' => __("To enhance your funnel, click the + Add New Step button. You can create various types of steps, such as landing pages, product pages, or thank-you pages. Each step allows you to customize content and settings to guide your customers effectively through the funnel.", 'wpfnl'),
6523 'search_ob' => __("Search and Select Order Bump Product", 'wpfnl'),
6524 'search_ob_des' => __("Use the search bar to find an existing product to offer as an order bump. Once you've found the product, select it to include it as an optional add-on during checkout. This will help increase your average order value.", 'wpfnl'),
6525 'modify_ob_title' => __("Modify Product Title", 'wpfnl'),
6526 'modify_ob_title_des' => __("To edit the product title, click on the product name field and enter a new title that accurately describes the product. A clear and concise title helps customers quickly understand what you're offering.", 'wpfnl'),
6527 'choose_ob_style' => __("Choose Order Bump Style", 'wpfnl'),
6528 'choose_ob_style_des' => __("Customize the appearance of your order bump by selecting a style that fits your funnel’s design. You can choose from various layout options and adjust colors, fonts, and other visual elements to make your order bump stand out during checkout.", 'wpfnl'),
6529 'select_ob_position' => __("Select Order Bump Position", 'wpfnl'),
6530 'select_ob_position_des' => __("Select where you want the order bump to appear during checkout. You can position it above or below the payment details to ensure maximum visibility and encourage customers to add the extra product.", 'wpfnl'),
6531 'save_ob' => __("Save Your Order Bump", 'wpfnl'),
6532 'save_ob_des' => __("Make sure to click the Save button to keep your order bump settings. This ensures that your changes are applied and your order bump is active for your funnel. Don’t forget to review all details before saving!", 'wpfnl'),
6533 );
6534 }
6535
6536 /**
6537 * Maybe current theme is bricks
6538 *
6539 * @return bool
6540 * @since 3.1.0
6541 */
6542 public static function maybe_bricks_theme() {
6543 $current_theme = wp_get_theme();
6544 $parent_theme = $current_theme->parent();
6545
6546 if ('Bricks' === $current_theme->get('Name')) {
6547 return true;
6548 }
6549
6550 if ($parent_theme && 'Bricks' === $parent_theme->get('Name')) {
6551 return true;
6552 }
6553
6554 return false;
6555 }
6556
6557
6558 /**
6559 * Maybe current theme is WoodMart
6560 *
6561 * @return bool
6562 * @since 3.1.0
6563 */
6564 public static function maybe_woodmart_theme() {
6565 $current_theme = wp_get_theme();
6566 $parent_theme = $current_theme->parent();
6567
6568 if ('Woodmart' === $current_theme->get('Name')) {
6569 return true;
6570 }
6571
6572 if ($parent_theme && 'Woodmart' === $parent_theme->get('Name')) {
6573 return true;
6574 }
6575
6576 return false;
6577 }
6578
6579
6580 /**
6581 * Get WooCommerce price & currency configuration
6582 *
6583 * @return array
6584 * @since 3.0.13
6585 */
6586 public static function get_wc_price_config() {
6587 $config = array(
6588 'currency_symbol' => '$',
6589 'currency_position' => 'left',
6590 'thousand_separator' => ',',
6591 'decimal_separator' => '.',
6592 'decimal_places' => 2,
6593 'price_format' => '%1$s%2$s',
6594 );
6595
6596 if ( self::is_wc_active() ) {
6597 $config = array(
6598 'currency_symbol' => get_woocommerce_currency_symbol(),
6599 'currency_position' => get_option( 'woocommerce_currency_pos' ),
6600 'thousand_separator' => wc_get_price_thousand_separator(),
6601 'decimal_separator' => wc_get_price_decimal_separator(),
6602 'decimal_places' => wc_get_price_decimals(),
6603 'price_format' => get_woocommerce_price_format(),
6604 );
6605 }
6606 return $config;
6607 }
6608
6609
6610 /**
6611 * Get default meta for each steps
6612 *
6613 * @param $step_type
6614 * @return array|array[]
6615 *
6616 * @since 3.1.0
6617 */
6618 public static function get_step_default_meta( $step_type ) {
6619 $default_meta = array();
6620 switch ( $step_type ) {
6621 case 'landing':
6622 $default_meta = Wpfnl_Step_Meta_keys::get_landing_meta();
6623 break;
6624 case 'checkout':
6625 $default_meta = Wpfnl_Step_Meta_keys::get_checkout_meta();
6626 break;
6627 case 'thankyou':
6628 $default_meta = Wpfnl_Step_Meta_keys::get_thankyou_meta();
6629 break;
6630 case 'upsell':
6631 case 'downsell':
6632 $default_meta = Wpfnl_Step_Meta_keys::get_offer_meta();
6633 break;
6634 case 'custom':
6635 $default_meta = Wpfnl_Step_Meta_keys::get_custom_meta();
6636 break;
6637 default:
6638 break;
6639 }
6640 return $default_meta;
6641 }
6642
6643
6644 /**
6645 * Get step type from step id
6646 *
6647 * @param $step_id
6648 * @return mixed
6649 * @since 3.1.0
6650 */
6651 public static function get_step_type( $step_id ) {
6652 return get_post_meta( $step_id, '_step_type', true );
6653 }
6654
6655 /**
6656 * Check the current page is funnel page
6657 *
6658 * @return bool
6659 * @since 3.1.4
6660 */
6661 public static function maybe_funnel_page() {
6662 $supported_pages = array( 'wpfnl_settings', 'wpf-license', 'edit_funnel', 'wp_funnels' );
6663 if ( isset( $_GET['page'] ) && in_array( $_GET['page'], $supported_pages ) ) {
6664 return true;
6665 }
6666 return false;
6667 }
6668
6669
6670
6671 /**
6672 * Retrieves the list of enabled payment gateways.
6673 *
6674 * @return array The list of enabled payment gateways.
6675 *
6676 * @since 3.1.8
6677 */
6678 public static function get_enabled_payment_gateways() {
6679 if ( ! self::is_wc_active() ) {
6680 return array();
6681 }
6682 $gateways = WC()->payment_gateways->get_available_payment_gateways();
6683 $enabled_gateways = array();
6684 if ( is_array( $gateways ) && ! empty( $gateways ) ) {
6685 foreach ( $gateways as $gateway ) {
6686 if ( $gateway->enabled == 'yes' ) {
6687 $enabled_gateways[ $gateway->id ] = $gateway->title;
6688 }
6689 }
6690 }
6691 return $enabled_gateways;
6692 }
6693
6694
6695 /**
6696 * Check the slug is already available or not
6697 *
6698 * @param string $slug
6699 * @param int $post_id
6700 * @param string $title
6701 * @return bool
6702 *
6703 * @since 3.1.8
6704 */
6705 public static function is_slug_available( $slug, $post_id = 0, $title = '' ) {
6706 global $wpdb;
6707
6708 // If no title is provided, use the slug itself
6709 if ( empty( $title ) ) {
6710 $title = $slug;
6711 }
6712
6713 // Check if the generated slug exists in the database
6714 $query = $wpdb->prepare( "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND ID != %d", $slug, $post_id );
6715 $result = $wpdb->get_var( $query );
6716
6717 // If the result is null, the slug is available
6718 if ( is_null( $result ) ) {
6719 return true;
6720 }
6721
6722 return false;
6723 }
6724
6725
6726 // Function to get the total price of a composite product
6727 public static function get_composite_product_price( $composite_product_id, $use_regular_price = false ) {
6728 // Get the composite product object
6729 $composite_product = wc_get_product( $composite_product_id );
6730
6731 // Check if the product is a composite product
6732 if ( ! $composite_product || 'composite' !== $composite_product->get_type() ) {
6733 return 'The product is not a composite product.';
6734 }
6735
6736 // Initialize total price
6737 $total_price = $use_regular_price ? $composite_product->get_regular_price() : $composite_product->get_price();
6738
6739 // Get components
6740 $components = $composite_product->get_components();
6741
6742 // Loop through each component
6743 foreach ( $components as $component_id => $component ) {
6744 // Get the selected option (or default option if no selection)
6745 $selected_option = $component->get_default_option();
6746
6747 // Check if component is optional and no selection made
6748 if ( $component->is_optional() && ! $selected_option ) {
6749 continue;
6750 }
6751
6752 // Get the product object for the selected option
6753 $product = wc_get_product( $selected_option );
6754 $configuration = array();
6755 // Calculate price based on selected option and quantity
6756 if ( $product ) {
6757 $component_quantity = $component->get_quantity( $configuration );
6758 $product_price = $use_regular_price ? $product->get_regular_price() : $product->get_price();
6759
6760 // Add the price of the selected option multiplied by the quantity
6761 $total_price += $product_price * $component_quantity;
6762 }
6763 }
6764
6765 // Apply any composite product specific pricing rules or discounts
6766 // Note: This step may vary based on how your site applies discounts or custom pricing rules
6767 // $total_price = apply_composite_product_discounts($composite_product, $total_price);
6768
6769 // Format and return the total price
6770 return $total_price;
6771 }
6772
6773
6774 public static function add_composite_product_to_cart_with_defaults( $composite_product_id, $quantity, $use_regular_price = false ) {
6775
6776 // Get the composite product object
6777 $composite_product = wc_get_product( $composite_product_id );
6778
6779 if ( ! defined( 'WC_CP_VERSION' ) ) {
6780 return $use_regular_price ? $composite_product->get_regular_price() : $composite_product->get_price();
6781 }
6782
6783 // Check if the product is a composite product
6784 if ( ! $composite_product || 'composite' !== $composite_product->get_type() ) {
6785 return 'The product is not a composite product.';
6786 }
6787
6788 // Prepare the components array for the cart
6789 $cart_item_data = array(
6790 'composite_data' => array(),
6791 );
6792
6793 $components = $composite_product->get_components();
6794
6795 foreach ( $components as $component_id => $component ) {
6796 // Get the default option for the component
6797 $selected_option_id = $component->get_default_option();
6798
6799 if ( ! $selected_option_id && ! $component->is_optional() ) {
6800 return 'Missing required component option for component ID ' . $component_id;
6801 }
6802
6803 // Get the product object for the selected option
6804 $product = wc_get_product( $selected_option_id );
6805
6806 // Assuming configuration array as an empty array for this example
6807 // Ideally, this should contain the current state of the component configuration
6808 $configuration = array(); // You should replace this with actual configuration if available
6809
6810 // Calculate quantity
6811 $component_quantity = $component->get_quantity( $configuration );
6812
6813 // Add selected option and quantity to cart item data if the option is not null
6814 if ( $selected_option_id ) {
6815 $cart_item_data['composite_data'][ $component_id ] = array(
6816 'product_id' => $selected_option_id,
6817 'quantity' => $component_quantity,
6818 );
6819 }
6820 }
6821
6822 // Add the composite product to the cart
6823 $added = WC()->cart->add_to_cart( $composite_product_id, $quantity, 0, array(), $cart_item_data );
6824
6825 if ( $added ) {
6826 return 'Composite product added to cart successfully.';
6827 } else {
6828 return 'Failed to add composite product to cart.';
6829 }
6830 }
6831
6832
6833
6834 /**
6835 * Checks if a theme is installed.
6836 *
6837 * @param string $theme_slug The slug of the theme to check.
6838 * @return bool True if the theme is installed, false otherwise.
6839 *
6840 * @since 3.3.1
6841 */
6842 public static function maybe_theme_installed( $theme_slug ) {
6843 $theme = wp_get_theme( $theme_slug );
6844 // Check if the theme exists and is not an empty theme
6845 if ( $theme->exists() ) {
6846 return true;
6847 } else {
6848 return false;
6849 }
6850 }
6851
6852
6853 /**
6854 * Validate google autocomplete API key
6855 *
6856 * @param string $api_key
6857 * @return bool
6858 * @since 3.4.8
6859 */
6860 public static function validate_google_places_api_key($api_key) {
6861 if (!is_string($api_key)) {
6862 // Throw an error or handle this case as appropriate for your application
6863 throw new InvalidArgumentException('API key must be a string');
6864 }
6865
6866 $url = 'https://maps.googleapis.com/maps/api/place/textsearch/json?key=' . $api_key;
6867
6868 $response = wp_remote_get($url);
6869
6870 if (is_wp_error($response)) {
6871 return false;
6872 }
6873
6874 $body = wp_remote_retrieve_body($response);
6875 $data = json_decode($body, true);
6876
6877 if (isset($data['status']) && 'REQUEST_DENIED' === $data['status'] ) {
6878 return false;
6879 }
6880
6881 return true;
6882 }
6883
6884
6885 /**
6886 * Get Guided tour
6887 *
6888 * @return false|mixed|null
6889 * @since 3.4.16
6890 */
6891
6892 public static function get_guided_tour() {
6893 $guided_tour_default = apply_filters('wpfnl_guided_tour_default', [
6894 'checkout',
6895 'upsell',
6896 'downsell',
6897 'orderbump',
6898 'condition',
6899 'add-step',
6900 'a/b-testing',
6901 'mm-automation',
6902 'canvas-step',
6903 'view-step',
6904 'edit-step',
6905 ]);
6906 $guided_tour = get_option('wpfnl_guided_tour');
6907 // Check if the option is not found and return the default
6908 if (false === $guided_tour) {
6909 update_option('wpfnl_guided_tour',$guided_tour_default);
6910 return $guided_tour_default;
6911 }
6912 return $guided_tour;
6913 }
6914
6915 /**
6916 * Check if the Global Thank You Page feature is enabled.
6917 *
6918 * This method retrieves the `_wpfunnels_offer_settings` option from the database
6919 * and checks whether the `enable_global_thankyou` setting is set to `'on'`.
6920 *
6921 * @since 3.5.31
6922 *
6923 * @return bool True if the Global Thank You Page is enabled, false otherwise.
6924 */
6925 public static function is_global_thank_you_page_enabled() {
6926 $offer_settings = get_option('_wpfunnels_offer_settings');
6927 if( isset($offer_settings['enable_global_thankyou']) && $offer_settings['enable_global_thankyou'] ){
6928 return 'on' == $offer_settings['enable_global_thankyou'] ? true : false;
6929 }
6930 return false;
6931 }
6932
6933
6934 /**
6935 * Retrieves the product type of an offer.
6936 *
6937 * @param string $step_id The ID of the step.
6938 * @return string The product type of the offer.
6939 */
6940 public static function get_offer_product_type( $step_id = '' ) {
6941
6942 if ( $step_id ) {
6943 $step_type = get_post_meta( $step_id, '_step_type', true );
6944 if ( $step_type == 'upsell' || $step_type == 'downsell' ) {
6945 $offer_product = self::get_offer_product( $step_id, $step_type );
6946 if ( is_array( $offer_product ) ) {
6947 $product_type = '';
6948 foreach ( $offer_product as $pr_index => $pr_data ) {
6949 $product_id = $pr_data['id'];
6950 $product = wc_get_product( $product_id );
6951 if ( $product ) {
6952 $product_type = $product->get_type();
6953 break;
6954 }
6955 }
6956 return $product_type;
6957 }
6958 }
6959 }
6960 return false;
6961 }
6962
6963 /**
6964 * Retrieves the offer product for a given step ID.
6965 *
6966 * @param int $step_id The ID of the funnel step.
6967 * @param string $type The type of offer product (default is 'upsell').
6968 * @param mixed $default The default value to return if no offer product is found.
6969 *
6970 * @return mixed The offer product data or the default value if not found.
6971 *
6972 * @since 1.0.0
6973 */
6974 public static function get_offer_product( $step_id, $type = 'upsell', $default = false ) {
6975 if ( $default ) {
6976 return $default;
6977 }
6978 $value = get_post_meta( $step_id, "_wpfnl_{$type}_products", true );
6979 if ( ! $value ) {
6980 return array();
6981 }
6982 return $value;
6983 }
6984
6985
6986 /**
6987 * Get product data for widget
6988 *
6989 * @param String
6990 *
6991 * @return Array
6992 * @since 1.6.8
6993 */
6994 public static function get_product_data_for_widget( $step_id = '' ) {
6995
6996 if ( $step_id && Wpfnl_functions::is_wc_active() ) {
6997 $funnel_id = get_post_meta( $step_id, '_funnel_id', true );
6998 $step_type = get_post_meta( $step_id, '_step_type', true );
6999 $is_gbf = get_post_meta( $funnel_id, 'is_global_funnel', true );
7000 $offer_product = '';
7001 if ( 'yes' === $is_gbf && ( 'upsell' === $step_type || 'downsell' === $step_type ) ) {
7002 if ( self::is_global_funnel_activated() ) {
7003 $instance = new \WPFunnelsPro\GBF\Wpfnl_Pro_GBF_Functions();
7004 $offer_rules = get_post_meta( $step_id, 'global_funnel_' . $step_type . '_rules', true );
7005 $quantity = isset( $offer_rules['quantity'] ) ? $offer_rules['quantity'] : 1;
7006 $rules_type = isset( $offer_rules['type'] ) ? $offer_rules['type'] : '';
7007 if ( 'moreQuantity' == $rules_type ) {
7008 $rand_product_id = self::get_random_product();
7009 $offer_product = wc_get_product( $rand_product_id );
7010 $get_product_type = $offer_product ? $offer_product->get_type() : '';
7011 $quantity = isset( $gbf_product[0]['quantity'] ) ? $quantity + $gbf_product[0]['quantity'] : $quantity;
7012
7013 } elseif ( 'specificProduct' == $rules_type ) {
7014 $rules = get_post_meta( $step_id, 'global_funnel_' . $step_type . '_rules', true );
7015 $offer_product = '';
7016 if ( is_array( $rules ) ) {
7017 if ( isset( $rules['show'] ) && $rules['show'] ) {
7018 $offer_product = wc_get_product( $rules['show'] );
7019 }
7020 }
7021 $get_product_type = $offer_product ? $offer_product->get_type() : '';
7022
7023 } elseif ( 'randomProduct' == $rules_type ) {
7024 $offer_mappings = get_post_meta( $step_id, 'global_funnel_' . $step_type . '_rules', true );
7025 if ( $offer_mappings ) {
7026 $category = isset( $offer_mappings['category'] ) ? $offer_mappings['category'] : null;
7027 $function_exist = is_callable( array( $instance, 'get_random_product_in_category_for_offer' ) );
7028 if ( $function_exist ) {
7029 $id = '';
7030 if ( is_admin() ) {
7031 $dynamic_product = Wpfnl_Pro_GBF_Functions::get_random_product_in_shop_for_widget( 1 );
7032 if ( isset( $dynamic_product[0]->ID ) ) {
7033 $id = $dynamic_product[0]->ID;
7034 }
7035 } else {
7036 $dynamic_product = json_decode( wp_unslash( get_option( 'wpfunnels_dynamic_offer_data' ) ), true );
7037 if ( is_array( $dynamic_product ) && isset( $dynamic_product[0]['ID'] ) ) {
7038 $id = $dynamic_product[0]['ID'];
7039 }
7040 }
7041
7042 if ( $id ) {
7043 $offer_product = wc_get_product( $id );
7044 $get_product_type = $offer_product ? $offer_product->get_type() : '';
7045 }
7046 }
7047 }
7048 } elseif ( 'randomProductInsideCategoryWithinPrice' == $rules_type ) {
7049 $offer_mappings = get_post_meta( $step_id, 'global_funnel_' . $step_type . '_rules', true );
7050 if ( $offer_mappings ) {
7051 $category = isset( $offer_mappings['category'] ) ? $offer_mappings['category'] : null;
7052 $function_exist = is_callable( array( $instance, 'get_random_product_in_category_within_price_for_offer_for_widget' ) );
7053
7054 if ( $function_exist ) {
7055 $id = '';
7056 if ( is_admin() ) {
7057 $dynamic_product = Wpfnl_Pro_GBF_Functions::get_random_product_in_category_within_price_for_offer_for_widget( 1 );
7058 if ( isset( $dynamic_product[0]->ID ) ) {
7059 $id = $dynamic_product[0]->ID;
7060 }
7061 } else {
7062 $dynamic_product = json_decode( wp_unslash( get_option( 'wpfunnels_dynamic_offer_data' ) ), true );
7063 if ( is_array( $dynamic_product ) && isset( $dynamic_product[0]['ID'] ) ) {
7064 $id = $dynamic_product[0]['ID'];
7065 }
7066 }
7067
7068 if ( $id ) {
7069 $offer_product = wc_get_product( $id );
7070 $get_product_type = $offer_product ? $offer_product->get_type() : '';
7071 }
7072 }
7073 }
7074 } elseif ( 'highestSoldInsideCategory' == $rules_type ) {
7075 $offer_mappings = get_post_meta( $step_id, 'global_funnel_' . $step_type . '_rules', true );
7076 if ( $offer_mappings ) {
7077 $category = isset( $offer_mappings['category'] ) ? $offer_mappings['category'] : null;
7078 $function_exist = is_callable( array( $instance, 'get_highest_sold_product_in_category_for_offer' ) );
7079 if ( $function_exist ) {
7080 $highestSoldInsideCategory = Wpfnl_Pro_GBF_Functions::get_highest_sold_product_in_category_for_offer( 1, $category );
7081 if ( is_array( $highestSoldInsideCategory ) && isset( $highestSoldInsideCategory['posts'][0]->ID ) ) {
7082 $id = $highestSoldInsideCategory['posts'][0]->ID;
7083 $offer_product = wc_get_product( $id );
7084 $get_product_type = $offer_product ? $offer_product->get_type() : '';
7085 }
7086 }
7087 }
7088 } elseif ( 'highestSoldInsideTag' == $rules_type ) {
7089 $offer_mappings = get_post_meta( $step_id, 'global_funnel_' . $step_type . '_rules', true );
7090 if ( $offer_mappings ) {
7091 $tag = isset( $offer_mappings['tag'] ) ? $offer_mappings['tag'] : null;
7092 $function_exist = is_callable( array( $instance, 'get_highest_sold_product_in_tag_for_offer' ) );
7093 if ( $function_exist ) {
7094 $highestSoldInsideTag = Wpfnl_Pro_GBF_Functions::get_highest_sold_product_in_tag_for_offer( $tag, 1 );
7095 if ( is_array( $highestSoldInsideTag ) && isset( $highestSoldInsideTag['posts'][0]->ID ) ) {
7096 $id = $highestSoldInsideTag['posts'][0]->ID;
7097 $offer_product = wc_get_product( $id );
7098 $get_product_type = $offer_product ? $offer_product->get_type() : '';
7099 }
7100 }
7101 }
7102 } elseif ( 'highestSold' == $rules_type ) {
7103 $function_exist = is_callable( array( $instance, 'get_highest_sold_product_for_offer' ) );
7104 if ( $function_exist ) {
7105 $highestSold = Wpfnl_Pro_GBF_Functions::get_highest_sold_product_for_offer( 1 );
7106 if ( is_array( $highestSold ) && isset( $highestSold['posts'][0]->ID ) ) {
7107 $id = $highestSold['posts'][0]->ID;
7108 $offer_product = wc_get_product( $id );
7109 $get_product_type = $offer_product ? $offer_product->get_type() : '';
7110 }
7111 }
7112 } elseif ( 'randomProductInTag' == $rules_type ) {
7113 $function_exist = is_callable( array( $instance, 'get_random_product_in_tag_for_offer_for_widget' ) );
7114 if ( $function_exist ) {
7115 $id = '';
7116 if ( is_admin() ) {
7117 $dynamic_product = Wpfnl_Pro_GBF_Functions::get_random_product_in_tag_for_offer_for_widget( 1 );
7118 if ( isset( $dynamic_product[0]->ID ) ) {
7119 $id = $dynamic_product[0]->ID;
7120 }
7121 } else {
7122 $dynamic_product = json_decode( wp_unslash( get_option( 'wpfunnels_dynamic_offer_data' ) ), true );
7123 if ( is_array( $dynamic_product ) && isset( $dynamic_product[0]['ID'] ) ) {
7124 $id = $dynamic_product[0]['ID'];
7125 }
7126 }
7127 if ( $id ) {
7128 $offer_product = wc_get_product( $id );
7129 $get_product_type = $offer_product ? $offer_product->get_type() : '';
7130 }
7131 }
7132 } elseif ( 'randomProductInsideTagWithinPrice' == $rules_type ) {
7133 $function_exist = is_callable( array( $instance, 'get_random_product_in_tag_within_price_for_offer_for_widget' ) );
7134 if ( $function_exist ) {
7135 $id = '';
7136 if ( is_admin() ) {
7137 $dynamic_product = Wpfnl_Pro_GBF_Functions::get_random_product_in_tag_within_price_for_offer_for_widget( 1 );
7138
7139 if ( isset( $dynamic_product[0]->ID ) ) {
7140 $id = $dynamic_product[0]->ID;
7141 }
7142 } else {
7143 $dynamic_product = json_decode( wp_unslash( get_option( 'wpfunnels_dynamic_offer_data' ) ), true );
7144 if ( is_array( $dynamic_product ) && isset( $dynamic_product[0]['ID'] ) ) {
7145 $id = $dynamic_product[0]['ID'];
7146 }
7147 }
7148 if ( $id ) {
7149 $offer_product = wc_get_product( $id );
7150 $get_product_type = $offer_product ? $offer_product->get_type() : '';
7151 }
7152 }
7153 } elseif ( 'randomInShop' == $rules_type ) {
7154 $id = '';
7155 if ( is_admin() ) {
7156 $dynamic_product = Wpfnl_Pro_GBF_Functions::get_random_product_in_shop_for_widget( 1 );
7157 if ( isset( $dynamic_product[0]->ID ) ) {
7158 $id = $dynamic_product[0]->ID;
7159 }
7160 } else {
7161 $dynamic_product = json_decode( wp_unslash( get_option( 'wpfunnels_dynamic_offer_data' ) ), true );
7162 if ( is_array( $dynamic_product ) && isset( $dynamic_product[0]['ID'] ) ) {
7163 $id = $dynamic_product[0]['ID'];
7164 }
7165 }
7166 if ( $id ) {
7167 $offer_product = wc_get_product( $id );
7168 $get_product_type = $offer_product ? $offer_product->get_type() : '';
7169 }
7170 }
7171 }
7172 } else {
7173 $offer_product_data = self::get_offer_product( $step_id, $step_type );
7174 $quantity = isset( $offer_product_data['quantity'] ) ? $offer_product_data['quantity'] : 1;
7175 $product = null;
7176 if ( is_array( $offer_product_data ) ) {
7177 foreach ( $offer_product_data as $pr_index => $pr_data ) {
7178 $product_id = $pr_data['id'];
7179 $product = wc_get_product( $product_id );
7180 break;
7181 }
7182 }
7183
7184 $offer_product = $product;
7185 $get_product_type = $offer_product ? $offer_product->get_type() : '';
7186 }
7187
7188 if ( $offer_product && $get_product_type ) {
7189
7190 return array(
7191 'offer_product' => $offer_product,
7192 'get_product_type' => $get_product_type,
7193 'is_gbf' => $is_gbf,
7194 'quantity' => $quantity,
7195 );
7196 }
7197 }
7198
7199 return array(
7200 'offer_product' => '',
7201 'get_product_type' => '',
7202 'is_gbf' => 'no',
7203 'quantity' => 1,
7204 );
7205 }
7206
7207
7208 /**
7209 * Checks if a payment gateway is potentially unsupported.
7210 *
7211 * @return bool
7212 * @since 2.0.5
7213 */
7214 public static function maybe_unsupported_payment_gateway() {
7215
7216 if ( ! Wpfnl_functions::is_wc_active() || ! isset( $_GET['wpfnl-order'] ) ) {
7217 return false;
7218 }
7219
7220 $order_id = ( isset( $_GET['wpfnl-order'] ) ) ? intval( $_GET['wpfnl-order'] ) : 0;
7221 $order = wc_get_order( $order_id );
7222 if ( false === is_a( $order, 'WC_Order' ) ) {
7223 return false;
7224 }
7225 $payment_method = $order->get_payment_method();
7226
7227 if ( $payment_method ) {
7228 // First check if Pro version's factory exists (for backward compatibility)
7229 if ( class_exists( '\WPFunnelsPro\Frontend\Modules\Gateways\Payment_Gateways_Factory' ) ) {
7230 $payment_gateways = \WPFunnelsPro\Frontend\Modules\Gateways\Payment_Gateways_Factory::getInstance()->get_supported_payment_gateways();
7231 }
7232 // Otherwise use the free version's factory
7233 elseif ( class_exists( '\WPFunnels\Gateway\Payment_Gateways_Factory' ) ) {
7234 $payment_gateways = \WPFunnels\Gateway\Payment_Gateways_Factory::getInstance()->get_supported_payment_gateways();
7235 } else {
7236 // If neither exists, assume unsupported
7237 return true;
7238 }
7239
7240 if ( ! isset( $payment_gateways[ $payment_method ] ) ) {
7241 return true;
7242 } else {
7243 return false;
7244 }
7245 }
7246 return true;
7247 }
7248
7249
7250 /**
7251 * Checks if an offer exists in a funnel.
7252 *
7253 * @param int $funnel_id The ID of the funnel to check.
7254 *
7255 * @return bool True if an offer exists, false otherwise.
7256 *
7257 * @since 2.0.5
7258 */
7259 public static function is_offer_exists_in_funnel( $funnel_id ) {
7260 $is_offer_exists = false;
7261 $steps = Wpfnl_functions::get_steps( $funnel_id );
7262 if ( is_array( $steps ) ) {
7263 foreach ( $steps as $index => $step ) {
7264 $step_type = $step['step_type'];
7265 if ( in_array( $step_type, array( 'upsell', 'downsell' ) ) ) {
7266 $is_offer_exists = true;
7267 break;
7268 }
7269 }
7270 }
7271 return $is_offer_exists;
7272 }
7273
7274
7275 /**
7276 * Get upsell/downsell offer product information
7277 *
7278 * @param $step_id
7279 * @param string $selected_product_id
7280 * @param string $input_qty
7281 * @param int $order_id
7282 * @return array
7283 *
7284 * @since 1.0.0
7285 */
7286 public static function get_offer_product_data( $step_id, $selected_product_id = '', $input_qty = '', $order_id = 0 ) {
7287 $funnel_id = get_post_meta( $step_id, '_funnel_id', true );
7288
7289 return apply_filters( 'wpfunnels/offer_product_data', self::get_offer_data( $step_id, $selected_product_id, $input_qty, $order_id = 0 ), $funnel_id, $step_id );
7290 }
7291
7292
7293 /**
7294 * Retrieves offer data based on the provided parameters.
7295 *
7296 * @param int $step_id The ID of the step.
7297 * @param string $selected_product_id The ID of the selected product.
7298 * @param string $input_qty The quantity input.
7299 * @param int $order_id The ID of the order.
7300 *
7301 * @return mixed The offer data.
7302 *
7303 * @since 1.0.0
7304 */
7305 public static function get_offer_data( $step_id, $selected_product_id = '', $input_qty = '', $order_id = 0 ) {
7306 $data = array();
7307 $amount_diff = 0;
7308 $cancel_main_order = false;
7309 $product_id = 0;
7310 $product_qty = 0;
7311
7312 $step_type = get_post_meta( $step_id, '_step_type', true );
7313 $offer_product = get_post_meta( $step_id, '_wpfnl_' . $step_type . '_products', true );
7314 $discount = get_post_meta( $step_id, '_wpfnl_' . $step_type . '_discount', true );
7315 if ( isset( $offer_product[0] ) && ! $selected_product_id ) {
7316 $product_id = $offer_product[0]['id'];
7317 $product_qty = $offer_product[0]['quantity'];
7318 } else {
7319 $product_id = $selected_product_id;
7320 $product_qty = ! empty( $input_qty ) ? intval( $input_qty ) : ( isset( $offer_product[0]['quantity'] ) ? $offer_product[0]['quantity'] : 1 );
7321 }
7322
7323 if ( $product_id ) {
7324
7325 $product = wc_get_product( $product_id );
7326 if ( $product ) {
7327 $main_qty = $product->get_stock_quantity();
7328 if ( $main_qty <= 0 && $product->get_manage_stock() == true ) {
7329 $product_qty = 0;
7330 }
7331 $product_qty = intval( $product_qty );
7332 $order = wc_get_order( $order_id );
7333
7334 $product_type = $product->get_type();
7335 $original_price = $product->get_price( 'edit' );
7336 $custom_price = $original_price;
7337
7338 if ( is_a( $product, 'WC_Product_Bundle' ) ) {
7339 $custom_price = $product->get_bundle_price( 'min' );
7340 }
7341 if ( 'composite' === $product->get_type() ) {
7342 $custom_price = Wpfnl_functions::get_composite_product_price( $product->get_id(), false );
7343 }
7344
7345 $custom_price = apply_filters( 'wpfunnels/modify_offer_product_price_data_without_discount', $custom_price );
7346
7347 if ( is_plugin_active( 'woocommerce-subscriptions/woocommerce-subscriptions.php' ) ) {
7348 if ( 'subscription_variation' === $product->get_type() || 'subscription' === $product->get_type() ) {
7349 $signUpFee = \WC_Subscriptions_Product::get_sign_up_fee( $product );
7350 $custom_price = $custom_price + $signUpFee;
7351 }
7352 }
7353
7354 $unit_price = $custom_price;
7355 $unit_price_tax = $custom_price;
7356 $product_price = floatval( $custom_price ) * intval( $product_qty );
7357
7358 /** tax calculation */
7359 $tax_enabled = get_option( 'woocommerce_calc_taxes' );
7360 $shipping_fee = 0;
7361 $shipping_incl_tax = 0;
7362 $shipping_excl_tax = 0;
7363 $shipping_method_name = 0;
7364 if ( $order ) {
7365 $shipping_fee = $order->get_shipping_total();
7366 $shipping_method_name = $order->get_shipping_method();
7367 }
7368
7369 if ( 'yes' === $tax_enabled ) {
7370 if ( ! wc_prices_include_tax() ) {
7371 $product_price = wc_get_price_including_tax( $product, array( 'price' => $custom_price ) ) * floatval( $product_qty );
7372 $shipping_excl_tax = wc_get_price_including_tax( $product, array( 'price' => $shipping_fee ) );
7373 } else {
7374 $custom_price = wc_get_price_excluding_tax( $product, array( 'price' => $custom_price ) ) * floatval( $product_qty );
7375 $shipping_incl_tax = wc_get_price_excluding_tax( $product, array( 'price' => $shipping_fee ) );
7376 }
7377 $unit_price_tax = $custom_price;
7378 }
7379
7380 $shipping_incl_tax = $shipping_incl_tax ? $shipping_incl_tax : $shipping_fee;
7381 $shipping_excl_tax = $shipping_excl_tax ? $shipping_excl_tax : $shipping_fee;
7382
7383 /** if offer product has discount */
7384 if ( is_array( $discount ) ) {
7385 $discount_instance = new WpfnlDiscount();
7386
7387 if ( ! $discount_instance->maybe_time_bound_discount( $step_id ) || ( $discount_instance->maybe_time_bound_discount( $step_id ) && $discount_instance->maybe_validate_discount_time( $step_id ) ) ) {
7388
7389 $discount_type = $discount['discountType'];
7390 $discount_apply_to = $discount['discountApplyTo'];
7391 $discount_value = $discount['discountValue'];
7392 if ( 'discount-percentage' === $discount_type || 'discount-price' === $discount_type ) {
7393 $regular_price = $product->get_regular_price();
7394 if ( is_plugin_active( 'woocommerce-subscriptions/woocommerce-subscriptions.php' ) ) {
7395 if ( 'subscription_variation' === $product->get_type() || 'subscription' === $product->get_type() ) {
7396 $signUpFee = \WC_Subscriptions_Product::get_sign_up_fee( $product );
7397 $regular_price = $regular_price + $signUpFee;
7398 }
7399 }
7400
7401 $product_price = $discount_apply_to === 'sale' ? $product->get_sale_price() : $regular_price;
7402 $product_price = $product_price ? $product_price : $product->get_price();
7403
7404 if ( is_a( $product, 'WC_Product_Bundle' ) ) {
7405 $custom_price = $discount_apply_to === 'sale' ? $product->get_bundle_price( 'min' ) : $product->get_bundle_regular_price( 'max' );
7406 }
7407 if ( 'composite' === $product->get_type() ) {
7408 $custom_price = Wpfnl_functions::get_composite_product_price( $product->get_id(), false );
7409 }
7410
7411 $product_price = self::calculate_discount_price_for_widget( $discount_type, $discount_value, floatval( $product_price ) * floatval( $product_qty ) );
7412 $product_price = apply_filters( 'wpfunnels/modify_offer_product_price_data_with_discount', $product_price );
7413
7414 $custom_price = $product_price;
7415 $unit_price = $product_price;
7416 $unit_price_tax = $product_price;
7417 }
7418 }
7419 }
7420
7421 $data = array(
7422 'step_id' => $step_id,
7423 'id' => $product_id,
7424 'name' => $product->get_title(),
7425 'desc' => self::get_item_description( $product ),
7426 'qty' => $product_qty,
7427 'original_price' => $original_price,
7428 'regular_price' => $product->get_regular_price(),
7429 'sale_price' => $product->get_sale_price(),
7430 'unit_price' => $unit_price,
7431 'unit_price_tax' => $unit_price_tax,
7432 'args' => array(
7433 'subtotal' => $custom_price,
7434 'total' => $custom_price,
7435 ),
7436 'shipping_fee' => $shipping_excl_tax,
7437 'shipping_fee_incl_tax' => $shipping_incl_tax,
7438 'shipping_method_name' => $shipping_method_name,
7439 'price' => wc_prices_include_tax() ? $custom_price : $product_price,
7440 'url' => $product->get_permalink(),
7441 'total_unit_price_amount' => floatval( preg_replace( '/[^\d.]/', '', $unit_price_tax ) ) * floatval( $product_qty ),
7442 'total' => wc_prices_include_tax() ? $custom_price : $product_price,
7443 'cancel_main_order' => $cancel_main_order,
7444 'amount_diff' => $amount_diff,
7445 'discount' => $discount ? true : false,
7446 'discount_type' => isset( $discount['discountType'] ) ? $discount['discountType'] : '',
7447 'discount_apply_to' => isset( $discount['discountApplyTo'] ) ? $discount['discountApplyTo'] : '',
7448 'discount_value' => isset( $discount['discountValue'] ) ? $discount['discountValue'] : '',
7449 );
7450 }
7451 }
7452 return $data;
7453 }
7454
7455
7456 /**
7457 * Calculate discount price
7458 *
7459 * @param $discount_type
7460 * @param $discount_value
7461 * @param $product_price
7462 *
7463 * @return string
7464 */
7465 public static function calculate_discount_price_for_widget( $discount_type, $discount_value, $product_price ) {
7466
7467 $custom_price = $product_price;
7468 if ( ! empty( $discount_type ) ) {
7469 if ( 'discount-percentage' === $discount_type ) {
7470 if ( $discount_value > 0 && $discount_value <= 100 ) {
7471 $custom_price = (float) $product_price - ( (float) ( $product_price * $discount_value ) / 100 );
7472 }
7473 } elseif ( 'discount-price' === $discount_type ) {
7474 if ( $discount_value > 0 ) {
7475 $custom_price = $product_price - $discount_value;
7476 }
7477 }
7478 }
7479 return $custom_price;
7480 }
7481
7482 /**
7483 * Enqueue the stylesheet a production webpack build extracted for a bundle.
7484 *
7485 * A production build (`npm run build`) extracts every entry's CSS to
7486 * admin/assets/dist/styles/<entry>.<contenthash>.css instead of injecting it
7487 * from JS, so each bundle needs its stylesheet enqueued alongside its script
7488 * or the screen renders unstyled. The filename carries a content hash, hence
7489 * the glob.
7490 *
7491 * A development build (`npm run start`) injects styles at runtime and emits
7492 * no stylesheet at all — nothing matches the glob and this is a no-op, which
7493 * is exactly what a dev build needs.
7494 *
7495 * @param string $entry Webpack entry name, e.g. `automation-canvas`.
7496 * @param string $handle Style handle to register it under.
7497 * @param array $deps Style handles this stylesheet must load after. Only
7498 * pass handles guaranteed to be registered on the same
7499 * screen — WordPress silently drops a style whose
7500 * dependency is missing.
7501 *
7502 * @return void
7503 * @since 3.13.0
7504 */
7505 public static function enqueue_bundle_style( $entry, $handle, $deps = array() ) {
7506 $url = self::get_bundle_style_url( $entry );
7507
7508 if ( ! $url ) {
7509 return;
7510 }
7511
7512 wp_enqueue_style( $handle, $url, $deps, WPFNL_VERSION );
7513 }
7514
7515
7516 /**
7517 * URL of a webpack entry's extracted stylesheet, if there is one.
7518 *
7519 * A development build injects its styles from JavaScript and emits no
7520 * stylesheet at all, so an empty string here means "nothing to load", not
7521 * "something went wrong".
7522 *
7523 * @param string $entry Webpack entry name.
7524 *
7525 * @return string Stylesheet URL, or '' when the build emitted none.
7526 * @since 3.13.8
7527 */
7528 public static function get_bundle_style_url( $entry ) {
7529 $styles_dir = WPFNL_DIR . '/admin/assets/dist/styles/';
7530
7531 if ( ! is_dir( $styles_dir ) ) {
7532 return '';
7533 }
7534
7535 $files = glob( $styles_dir . $entry . '.*.css' );
7536
7537 if ( empty( $files ) ) {
7538 return '';
7539 }
7540
7541 // Newest wins, so a stale hash left behind by an interrupted build never
7542 // shadows the current one.
7543 usort(
7544 $files,
7545 function ( $a, $b ) {
7546 return filemtime( $b ) - filemtime( $a );
7547 }
7548 );
7549
7550 return WPFNL_URL . 'admin/assets/dist/styles/' . basename( $files[0] );
7551 }
7552
7553 /**
7554 * Helper method to return the item description, which is composed of item
7555 * meta flattened into a comma-separated string, if available. Otherwise the
7556 * product SKU is included.
7557 *
7558 * The description is automatically truncated to the 127 char limit.
7559 *
7560 * @param array $item cart or order item
7561 * @param \WC_Product $product product data
7562 *
7563 * @return string
7564 * @since 1.0.0
7565 */
7566 public static function get_item_description( $product ) {
7567
7568 if ( is_string( $product ) ) {
7569 $str = $product;
7570 } else {
7571 $str = $product->get_short_description();
7572 }
7573 $item_desc = wp_strip_all_tags( wp_specialchars_decode( wp_staticize_emoji( $str ) ) );
7574 $item_desc = preg_replace( '/[\x00-\x1F\x80-\xFF]/', '', $item_desc );
7575 $item_desc = str_replace( "\n", ', ', rtrim( $item_desc ) );
7576 if ( strlen( $item_desc ) > 127 ) {
7577 $item_desc = substr( $item_desc, 0, 124 ) . '...';
7578 }
7579
7580 return html_entity_decode( $item_desc, ENT_NOQUOTES, 'UTF-8' );
7581 }
7582 }
7583
7584