( ! 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