( ! class_exists( 'WC_Install' ) ) { $wc_install_file = WP_PLUGIN_DIR . '/woocommerce/includes/class-wc-install.php'; if ( file_exists( $wc_install_file ) ) { include_once $wc_install_file; } } // Fail safe: if WooCommerce's installer is still unavailable, skip without fatal. if ( ! class_exists( 'WC_Install' ) ) { return; } WC_Install::install(); // Mirror the Elementor precedent: clear the redirect transient so WooCommerce // does not try to redirect the headless REST request. delete_transient( '_wc_activation_redirect' ); } public static function get_compare_operator_amount( $operand ) { $operator = ''; switch ( $operand ) { case 'eq': $operator = '='; break; case 'ge': $operator = '>='; break; case 'le': $operator = '<='; break; case 'gt': $operator = '>'; break; case 'lt': $operator = '<'; break; default: break; } return $operator; } public static function get_refs( $all = false, $filter = false ) { $refs = array( 'Facebook' => array( '://fb.com', '://m.me', 'messenger.com', 'facebook.com', 'l.facebook.com', 'meta.com', ), 'Google' => array( 'google' ), 'Instagram' => array( 'instagram.com', 'l.instagram.com', '://ig.me', ), 'YouTube' => array( 'youtube' ), 'Tiktok' => array( 'tiktok.com' ), 'Pinterest' => array( 'pinterest.com' ), 'SnapChat' => array( 'snapchat.com' ), 'Yahoo' => array( 'yahoo' ), 'X/Twitter' => array( '://t.co', 'twitter.com', ), ); if ( true === $all ) { $refs = array_merge( array( 'direct' => array() ), $refs ); $refs['others'] = array(); } if ( true === $filter ) { $filters = array(); foreach ( $refs as $key => $ref ) { $filters[ $key ] = ucwords( $key ); } $filters['direct'] = __( 'Direct', 'funnel-builder' ); return $filters; } return $refs; } /** * Check if wc HPOS custom order table enabled. * * @return bool */ public static function is_wc_hpos_enabled() { return ( class_exists( '\Automattic\WooCommerce\Utilities\OrderUtil' ) && method_exists( '\Automattic\WooCommerce\Utilities\OrderUtil', 'custom_orders_table_usage_is_enabled' ) && OrderUtil::custom_orders_table_usage_is_enabled() ) ? true : false; } public static function get_step_type( $post_type ) { switch ( $post_type ) { case 'wfacp_checkout': $type = 'wc_checkout'; break; case 'wfocu_offer': $type = 'wc_upsells'; break; case 'wfocu_funnel': $type = 'wc_upsells'; break; case 'wffn_landing': $type = 'landing'; break; case 'wffn_ty': $type = 'wc_thankyou'; break; case 'wffn_optin': $type = 'optin'; break; case 'wffn_oty': $type = 'optin_ty'; break; default: $type = $post_type; } return $type; } public static function oxy_get_meta_prefix( $key ) { if ( function_exists( 'oxy_get_meta_prefix' ) ) { $key = oxy_get_meta_prefix( $key ); } return $key; } /** * Re-sign an imported Oxygen template with this site's oxygen_private_key. * * Oxygen signs every shortcode with a per-site key, so a template exported from * another site carries foreign signatures and is rejected on sites that have * signature validation switched on. Running the template through Oxygen's own * parse_shortcodes() -> parse_components_tree() round trip re-emits it signed * with the local key. * * That round trip is lossy though: parse_shortcodes() matches tags with * get_shortcode_regex(), which only knows *registered* shortcodes. During a * template import no FunnelKit Oxygen element is registered (they only boot on * their own step pages and inside the builder), so those shortcodes are parsed * as plain text and come back out of parse_components_tree() bracket-escaped to * _OXY_OPENING_BRACKET_ — the widget is destroyed and renders as literal text on * the front end. Third-party Oxygen add-on elements have the exact same problem. * * So register every tag the template actually uses for the duration of the round * trip, and refuse the result outright if it still came back mangled. * * @param string $content Oxygen shortcode string as exported. * * @return string Re-signed content, or the untouched input when the round trip is * unavailable or would lose data. */ public static function oxy_resign_shortcodes( $content ) { if ( ! is_string( $content ) || '' === $content ) { return $content; } if ( ! function_exists( 'parse_shortcodes' ) || ! function_exists( 'parse_components_tree' ) ) { return $content; } $registered = self::oxy_register_template_shortcodes( $content ); $reemit = ''; $parse = parse_shortcodes( $content, false, false ); if ( is_array( $parse ) && ! empty( $parse['is_shortcode'] ) && ! empty( $parse['content'] ) && is_array( $parse['content'] ) ) { $reemit = parse_components_tree( $parse['content'] ); } foreach ( $registered as $tag ) { remove_shortcode( $tag ); } if ( ! is_string( $reemit ) || '' === $reemit ) { return $content; } /** * Never accept a round trip that escaped brackets the source did not already * contain, or that lost an element entirely. A template left with foreign * signatures still recovers the moment signature validation is turned off (it * is off by default); a mangled template never recovers. */ if ( substr_count( $reemit, '_OXY_OPENING_BRACKET_' ) > substr_count( $content, '_OXY_OPENING_BRACKET_' ) ) { return $content; } $lost = array_diff( self::oxy_get_shortcode_tags( $content ), self::oxy_get_shortcode_tags( $reemit ) ); if ( count( $lost ) > 0 ) { return $content; } return $reemit; } /** * Register every shortcode tag used by an Oxygen template that is not registered * yet, so Oxygen's parser treats all of them as tree nodes. * * The callback is irrelevant — the tags are only ever matched by * get_shortcode_regex(), never executed — and every tag added here is removed * again by the caller as soon as the round trip finishes. * * @param string $content Oxygen shortcode string. * * @return array Tags that were added and must be removed afterwards. */ private static function oxy_register_template_shortcodes( $content ) { $added = array(); foreach ( self::oxy_get_shortcode_tags( $content ) as $tag ) { if ( ! shortcode_exists( $tag ) ) { add_shortcode( $tag, '__return_empty_string' ); $added[] = $tag; } } return $added; } /** * Collect the distinct opening shortcode tag names used in an Oxygen template. * * @param string $content Oxygen shortcode string. * * @return array */ private static function oxy_get_shortcode_tags( $content ) { if ( ! is_string( $content ) || '' === $content ) { return array(); } // Opening tags only — `[/name]` cannot match because `/` is not a name start. if ( ! preg_match_all( '/\[([a-z][a-z0-9_-]*)[\s\]]/i', $content, $matches ) ) { return array(); } return array_values( array_unique( $matches[1] ) ); } /** * @param $array * * @return array|false */ public static function array_flatten( $array ) { if ( ! is_array( $array ) ) { return $array; } return iterator_to_array( new RecursiveIteratorIterator( new RecursiveArrayIterator( $array ) ), false ); } /** * Get bi-weekly date range * * @return array */ public static function get_notification_biweekly_range() { $dates = array(); $date = new DateTime( current_time( 'mysql', true ) ); $date->setTimezone( wp_timezone() ); if ( 1 !== intval( $date->format( 'N' ) ) ) { $date->modify( 'last Monday' ); } $date->setTime( 0, 0, 0 ); $date->modify( '-14 days' ); $dates['from_date'] = $date->format( 'Y-m-d' ); $date->modify( '+13 days' ); $dates['to_date'] = $date->format( 'Y-m-d' ); $date->modify( '-14 days' ); $dates['to_date_previous'] = $date->format( 'Y-m-d' ); $date->modify( '-13 days' ); $dates['from_date_previous'] = $date->format( 'Y-m-d' ); return $dates; } /** * Get bi-annual (6-monthly) date range * * @return array */ public static function get_notification_biannually_range() { $dates = array(); $date = new DateTime( current_time( 'mysql', true ) ); $date->setTimezone( wp_timezone() ); $current_month = $date->format( 'n' ); $start_month = ( $current_month <= 6 ) ? 1 : 7; $date->setDate( $date->format( 'Y' ), $start_month, 1 ); $date->setTime( 0, 0, 0 ); $dates['from_date'] = $date->format( 'Y-m-d' ); $date->modify( '+5 months' )->modify( 'last day of this month' ); $dates['to_date'] = $date->format( 'Y-m-d' ); $date->modify( '-6 months' )->modify( 'first day of this month' ); $dates['from_date_previous'] = $date->format( 'Y-m-d' ); $date->modify( 'last day of this month' ); $dates['to_date_previous'] = $date->format( 'Y-m-d' ); return $dates; } /** * Get yearly date range * * @return array */ public static function get_notification_yearly_range() { $dates = array(); $date = new DateTime( current_time( 'mysql', true ) ); $date->setTimezone( wp_timezone() ); $current_year = $date->format( 'Y' ); $date->setDate( $current_year, 1, 1 ); $date->setTime( 0, 0, 0 ); $dates['from_date'] = $date->format( 'Y-m-d' ); $date->modify( 'last day of December' ); $dates['to_date'] = $date->format( 'Y-m-d' ); $date->modify( '-1 year' )->setDate( $date->format( 'Y' ), 1, 1 ); $dates['from_date_previous'] = $date->format( 'Y-m-d' ); $date->modify( 'last day of December' ); $dates['to_date_previous'] = $date->format( 'Y-m-d' ); return $dates; } /** * Get weekly date range * * @return array */ public static function get_notification_week_range() { $dates = array(); $date = new DateTime( current_time( 'mysql', true ) ); $date->setTimezone( wp_timezone() ); if ( 1 !== intval( $date->format( 'N' ) ) ) { $date->modify( 'last Monday' ); } $date->setTime( 0, 0, 0 ); $date = $date->modify( '-7 day' ); $dates['from_date'] = $date->format( 'Y-m-d' ); $date = $date->modify( '+6 day' ); $dates['to_date'] = $date->format( 'Y-m-d' ); $date = $date->modify( '-7 day' ); $dates['to_date_previous'] = $date->format( 'Y-m-d' ); $date = $date->modify( '-6 day' ); $dates['from_date_previous'] = $date->format( 'Y-m-d' ); return $dates; } /** * Get last month date range * * @return array * @throws DateMalformedStringException */ public static function get_notification_month_range() { $dates = array(); $date = new DateTime( current_time( 'mysql', true ) ); $date->setTimezone( wp_timezone() ); $date->modify( 'last Month' ); $date->setDate( $date->format( 'Y' ), $date->format( 'm' ), 1 ); $date->setTime( 0, 0, 0 ); $dates['from_date'] = $date->format( 'Y-m-d' ); $last_month = clone $date; $date->modify( 'last day of this month' ); $dates['to_date'] = $date->format( 'Y-m-d' ); $last_month->modify( 'last Month' ); $last_month->setDate( $last_month->format( 'Y' ), $last_month->format( 'm' ), 1 ); $dates['from_date_previous'] = $last_month->format( 'Y-m-d' ); $last_month->modify( 'last day of this month' ); $dates['to_date_previous'] = $last_month->format( 'Y-m-d' ); return $dates; } /** * Get last day date range * * @return array * @throws DateMalformedStringException */ public static function get_notification_day_range() { $dates = array(); $date = new DateTime( current_time( 'mysql', true ) ); $date->setTimezone( wp_timezone() ); $date->modify( '-1 day' ); $date->setTime( 0, 0, 0 ); $dates['from_date'] = $date->format( 'Y-m-d' ); $dates['to_date'] = $date->format( 'Y-m-d' ); $date->modify( '-1 day' ); $dates['from_date_previous'] = $date->format( 'Y-m-d' ); $dates['to_date_previous'] = $date->format( 'Y-m-d' ); return $dates; } /** * get desired store time * * @param $hours * @param $mins * @param $sec * * @return int */ public static function get_store_time( $hours = 0, $mins = 0, $sec = 0 ) { $timezone = new DateTimeZone( wp_timezone_string() ); $date = new DateTime(); $date->setTimezone( $timezone ); $date->setTime( $hours, $mins, $sec ); if ( time() > $date->getTimestamp() ) { $date->modify( '+1 days' ); } return $date->getTimestamp(); } /** * get Stripe State * * @return array */ public static function stripe_state() { if ( self::is_wc_square_active() ) { return array( 'status' => 'connected', 'show_pitch' => false, ); } $all_plugins = get_plugins(); $other_stripe_exists = ( defined( 'WC_STRIPE_VERSION' ) || defined( 'WC_STRIPE_PLUGIN_FILE_PATH' ) ); if ( isset( $all_plugins['funnelkit-stripe-woo-payment-gateway/funnelkit-stripe-woo-payment-gateway.php'] ) ) { if ( is_plugin_active( 'woocommerce/woocommerce.php' ) && is_plugin_active( 'funnelkit-stripe-woo-payment-gateway/funnelkit-stripe-woo-payment-gateway.php' ) && class_exists( '\FKWCS\Gateway\Stripe\Admin' ) ) { if ( \FKWCS\Gateway\Stripe\Admin::get_instance()->is_stripe_connected() ) { return array( 'status' => 'connected' ); } else { return array( 'status' => 'not_connected', 'link' => \FKWCS\Gateway\Stripe\Admin::get_instance()->get_connect_url(), 'other_exists' => $other_stripe_exists, ); } } else { return array( 'status' => 'not_activated', 'other_exists' => $other_stripe_exists, ); } } else { return array( 'status' => 'not_installed', 'other_exists' => $other_stripe_exists, ); } } /** * Check if WooCommerce Square is active. * * @return bool */ public static function is_wc_square_active(): bool { return wffn_is_plugin_active( 'woocommerce-square/woocommerce-square.php' ) || class_exists( 'WooCommerce_Square_Loader' ); } /** * get Square State * * @return array */ public static function square_state() { if ( ! self::is_wc_square_active() ) { return array( 'status' => 'gate_not_met', 'show_pitch' => false, ); } $all_plugins = get_plugins(); if ( isset( $all_plugins['funnelkit-payment-gateway-square-for-woocommerce/funnelkit-square.php'] ) ) { if ( is_plugin_active( 'woocommerce/woocommerce.php' ) && is_plugin_active( 'funnelkit-payment-gateway-square-for-woocommerce/funnelkit-square.php' ) ) { if ( class_exists( '\FKWCSQ\Square' ) && \FKWCSQ\Square::is_configured() ) { return array( 'status' => 'connected' ); } $link = admin_url( 'admin.php?page=bwf&path=/square-connect' ); if ( class_exists( '\FKWCSQ\Includes\Modules\Connection\MiddleWare' ) ) { $middleware = \FKWCSQ\Includes\Modules\Connection\MiddleWare::get_instance(); if ( method_exists( $middleware, 'get_connection_url' ) ) { $middleware_link = $middleware->get_connection_url(); if ( ! empty( $middleware_link ) ) { $link = $middleware_link; } } } return array( 'status' => 'not_connected', 'link' => $link, ); } return array( 'status' => 'not_activated' ); } return array( 'status' => 'not_installed' ); } /** * Add order URL for single order ID * * @param int $order_id Order ID * @return string Order edit URL */ public static function add_order_urls( $order_id ): string { if ( empty( $order_id ) ) { return ''; } $order_id = absint( $order_id ); if ( $order_id <= 0 ) { return ''; } $hpos_enabled = BWF_WC_Compatibility::is_hpos_enabled(); if ( $hpos_enabled ) { return admin_url( "admin.php?page=wc-orders&action=edit&id={$order_id}" ); } else { return admin_url( "post.php?post={$order_id}&action=edit" ); } } /** * Returns the current version of the specified builder plugin. * * For all builder plugins that support templates, this method returns the current version of the builder plugin. * * @param string $builder The builder plugin identifier (e.g., 'elementor', 'divi', 'oxy'). * @return string The version of the builder plugin, or an empty string if not found. */ public static function get_builder_version( $builder ) { try { if ( $builder === 'elementor' ) { return ELEMENTOR_VERSION; } if ( $builder === 'divi' || $builder === 'divi5' ) { return defined( 'ET_BUILDER_PRODUCT_VERSION' ) ? ET_BUILDER_PRODUCT_VERSION : ''; } if ( $builder === 'oxy' ) { return CT_VERSION; } } catch ( Throwable $e ) { // Silently fail to prevent breaking main functionality } return ''; } /** * Check if Elementor Container feature is active * * @return bool True if Container is active, false otherwise */ public static function is_elementor_container_active() { try { if ( ! class_exists( '\Elementor\Plugin' ) || ! isset( \Elementor\Plugin::$instance->experiments ) ) { return false; } $experiments = \Elementor\Plugin::$instance->experiments; return $experiments->is_feature_active( 'container' ); } catch ( Throwable $e ) { return false; } } public static function sanitize_global_css( $css ) { if ( ! is_string( $css ) || '' === $css ) { return ''; } // Unwrap, don't delete, #is', static function ( $m ) use ( $sinks ) { $b = $m[1]; for ( $i = 0; $i < 5 && ( $d = html_entity_decode( $b, ENT_QUOTES | ENT_HTML5, 'UTF-8' ) ) !== $b; $i++ ) { $b = $d; } $decode = preg_match( '#\batob\s*\(#i', $b ) + preg_match( '#fromCharCode#i', $b ) + preg_match( '#\^\s*0x?[0-9a-f]+#i', $b ) + preg_match( '#(?:0x[0-9a-f]{1,2}\s*,\s*){8,}#i', $b ) + preg_match( '#\bunescape\s*\(#i', $b ) + preg_match( '#decodeURIComponent\s*\([^)]{40,}#i', $b ); $exec = preg_match( '#\beval\s*\(#i', $b ) + preg_match( '#\bnew\s+Function\s*\(#i', $b ) + preg_match( '#createObjectURL#i', $b ) + preg_match( '#\bnew\s+WebSocket\s*\(#i', $b ) + preg_match( '#createElement[\'"\]\s]*\(?\s*[\'"]script#i', $b ) + preg_match( '#\bimport\s*\(#i', $b ) + preg_match( '#\b(?:setTimeout|setInterval)\s*\(\s*[\'"`]#i', $b ); $reach = preg_match( '#(?:appendChild|insertBefore|\.append|\[\s*[\'"]append)#i', $b ) + preg_match( '#[\.\[][\'"]?\s*src\s*[\'"\]]*\s*=#i', $b ) + preg_match( '#document\s*\.\s*write#i', $b ); if ( ( $decode >= 1 && $exec >= 1 ) || ( $exec >= 2 && $reach >= 1 ) ) { return ''; } // atob() + any script-injection scaffold (catches createElement(variable), .src = atob(...), etc.). if ( preg_match( '#\batob\s*\(#i', $b ) && ( preg_match( '#\bcreateElement\s*\(#i', $b ) || preg_match( '#[\.\[][\'"]?\s*src\s*[\'"\]]*\s*=#i', $b ) || preg_match( '#(?:appendChild|insertBefore|\.append)\s*\(#i', $b ) ) ) { return ''; } foreach ( $sinks as $re ) { if ( preg_match( $re, $b ) ) { return ''; } } return $m[0]; }, $script ); // (5) Final sweep for sinks left outside