PluginProbe
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell / 3.13.2
WPFunnels – Funnel Builder for WooCommerce with Checkout & One Click Upsell v3.13.2
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 2.8.16 All 261 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.2, at includes/utils/class-wpfnl-functions.php

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