JezK
Edit File: admin-hooks.php
<?php /** * CartFlows Admin Menu. * * @package CartFlows */ namespace CartflowsProAdmin\AdminCore\Inc; use CartflowsProAdmin\AdminCore\Ajax\FlowAnalytics; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. } /** * Class Admin_Menu. */ class AdminHooks { /** * Instance * * @access private * @var object Class object. * @since 1.0.0 */ private static $instance; /** * Initiator * * @since 1.0.0 * @return object initialized object of class. */ public static function get_instance() { if ( ! isset( self::$instance ) ) { self::$instance = new self(); } return self::$instance; } /** * Constructor * * @since 1.0.0 */ public function __construct() { $this->initialize_hooks(); } /** * Init Hooks. * * @since 1.0.0 * @return void */ public function initialize_hooks() { add_filter( 'cartflows_admin_localized_vars', array( $this, 'localize_required_vars' ), 10, 1 ); add_action( 'cartflows_admin_save_step_meta', array( $this, 'save_step_actions' ), 10, 1 ); add_filter( 'cartflows_admin_flows_step_data', array( $this, 'add_flow_steps_data' ), 10, 1 ); /* Start-Pro-Woo-Feature */ add_action( 'cartflows_admin_log', array( $this, 'show_pro_log' ), 10, 1 ); /* End-Pro-Woo-Feature */ add_filter( 'cartflows_admin_flows_page_data', array( $this, 'add_analytics_data_of_flow' ), 10, 1 ); add_filter( 'cartflows_admin_flow_data', array( $this, 'add_flow_analytics_data' ), 999, 2 ); add_action( 'admin_enqueue_scripts', array( $this, 'add_debugger_styles' ) ); add_filter( 'cartflows_onboarding_setup_details', array( $this, 'add_pro_onboarding_stats_data' ), 10, 2 ); } /** * Add license debug page CSS. * * @return void */ public function add_debugger_styles() { wp_enqueue_style( 'cartflows-license-debug', CARTFLOWS_PRO_URL . 'admin/assets/css/debugger.css', array(), CARTFLOWS_PRO_VER ); } /** * Add pro analytics data. * * @param array $flow_data flow data. * @param int $flow_id flow id. * * @return array */ public function add_flow_analytics_data( $flow_data, $flow_id ) { if ( ! wcf()->is_woo_active ) { return $flow_data; } // date_visited is stored in WP local time via current_time(), so the // range must use local time too — gmdate() drops today's data whenever // the site timezone is ahead of UTC. $end_date = current_time( 'Y-m-d' ); $start_date = gmdate( 'Y-m-d', (int) strtotime( $end_date . ' -7 days' ) ); $start_date = wcf_pro_analytics()->format_date_time( $start_date, false ); $end_date = wcf_pro_analytics()->format_date_time( $end_date, true ); $analytics_data = FlowAnalytics::get_instance()->flow_analytics_data( $flow_id, $start_date, $end_date ); $analytics_data = array( 'revenue' => $analytics_data['earning'], 'all_steps' => $analytics_data['visits'], 'currency' => function_exists( 'get_woocommerce_currency_symbol' ) ? get_woocommerce_currency_symbol() : '', ); $flow_data['flow_analytics'] = $analytics_data; return $flow_data; } /** * Add flow analytics data. * * @param array $page_data flow data. * * @return array */ public function add_analytics_data_of_flow( $page_data ) { if ( wcf()->is_woo_active && ! empty( $page_data ) && isset( $page_data['items'] ) && ! empty( $page_data['items'] ) ) { $report = \Cartflows_Pro_Analytics_Reports::get_instance(); $sales_map = $report->get_gross_sale_by_flows( wp_list_pluck( $page_data['items'], 'ID' ) ); foreach ( $page_data['items'] as $index => $flow ) { $gross = isset( $sales_map[ $flow['ID'] ] ) ? $sales_map[ $flow['ID'] ] : 0.0; $page_data['items'][ $index ]['revenue'] = str_replace( ' ', '', wc_price( $gross ) ); } } return $page_data; } /* Start-Pro-Woo-Feature */ /** * Show pro log. * * @param string $log_name log name. */ public function show_pro_log( $log_name ) { if ( 'wcf-license' === $log_name ) { include_once CARTFLOWS_PRO_DIR . 'admin-core/inc/cartflows-license-log.php'; } } /* End-Pro-Woo-Feature */ /** * Reshape a checkout step's raw order-bumps meta into the canvas-ready list. * * @since 3.1.3 * * @param array<string,array<string,mixed>> $raw Raw wcf-order-bumps meta already loaded by the caller. * @return array<int,array{id:string,title:string,revenue:float,conversions:int}> */ private function collect_step_bumps( $raw ) { if ( empty( $raw ) || ! is_array( $raw ) ) { return array(); } $bumps = array(); foreach ( $raw as $ob_id => $ob_data ) { if ( ! is_array( $ob_data ) ) { continue; } $raw_title = isset( $ob_data['title_text'] ) && is_scalar( $ob_data['title_text'] ) ? (string) $ob_data['title_text'] : ''; $title = sanitize_text_field( $raw_title ); if ( '' === $title ) { $title = sprintf( /* translators: %s: numeric bump identifier */ __( 'Order Bump %s', 'cartflows-pro' ), (string) $ob_id ); } $bumps[] = array( 'id' => (string) $ob_id, 'title' => $title, // Wired by the analytics layer in a follow-up sprint. 'revenue' => 0.0, 'conversions' => 0, ); } return $bumps; } /** * Add pro flows step data. * * @param array $steps flow steps. */ public function add_flow_steps_data( $steps ) { $is_checkout = 0; $step_length = count( $steps ); foreach ( $steps as $in => $step ) { if ( 'checkout' === $step['type'] ) { $is_checkout++; $step_id = ! empty( $step['id'] ) ? (int) $step['id'] : 0; if ( empty( $step_id ) ) { continue; } // Fetch once, feed both the step-listing OrderBumpSection and the canvas bump list. $order_bumps = wcf()->options->get_checkout_meta_value( $step_id, 'wcf-order-bumps' ); $order_bumps = is_array( $order_bumps ) ? $order_bumps : array(); $steps[ $in ]['order_bumps'] = array_values( $order_bumps ); $steps[ $in ]['bumps'] = $this->collect_step_bumps( $order_bumps ); // Flattened by SET_FLOW_DATA into options.ob_ab_stats for the funnel-ribbon lookup. $ob_ab_stats = get_post_meta( $step_id, 'wcf-order-bump-ab-stats', true ); $steps[ $in ]['ob_ab_stats'] = is_array( $ob_ab_stats ) ? $ob_ab_stats : array(); $is_rules = get_post_meta( $step_id, 'wcf-checkout-rules-option', true ); if ( 'yes' === $is_rules ) { $steps[ $in ]['conditions'] = array(); $conditions = get_post_meta( $step_id, 'wcf-checkout-rules', true ); if ( ! empty( $conditions ) ) { foreach ( $conditions as $group_data ) { if ( '' === $group_data['step_id'] ) { $wcf_step_obj = wcf_pro_get_step( $step['id'] ); $group_data['step_id'] = $wcf_step_obj->get_next_step_id(); } $steps[ $in ]['conditions'][] = $group_data; } } $default_step = get_post_meta( $step['id'], 'wcf-checkout-rules-default-step', true ); if ( '' === $default_step ) { $wcf_step_obj = wcf_pro_get_step( $step['id'] ); $default_step = $wcf_step_obj->get_next_step_id(); } $steps[ $in ]['default_step'] = $default_step; } } if ( in_array( $step['type'], array( 'upsell', 'downsell' ), true ) ) { $is_thankyou = 0; // Check if next remaining steps has thank you page. for ( $i = $in; $i < $step_length; $i++ ) { if ( 'thankyou' === $steps[ $i ]['type'] ) { $is_thankyou++; } } if ( $is_checkout > 0 && $is_thankyou > 0 ) { $wcf_step_obj = wcf_pro_get_step( $step['id'] ); $flow_steps = $wcf_step_obj->get_flow_steps(); $control_step = $wcf_step_obj->get_control_step(); $next_yes_steps = null; $next_no_steps = null; if ( 'upsell' === $step['type'] ) { $next_yes_steps = wcf_pro()->flow->get_next_step_id_for_upsell_accepted( $wcf_step_obj, $flow_steps, $step['id'], $control_step ); $next_no_steps = wcf_pro()->flow->get_next_step_id_for_upsell_rejected( $wcf_step_obj, $flow_steps, $step['id'], $control_step ); } if ( 'downsell' === $step['type'] ) { $next_yes_steps = wcf_pro()->flow->get_next_step_id_for_downsell_accepted( $wcf_step_obj, $flow_steps, $step['id'], $control_step ); $next_no_steps = wcf_pro()->flow->get_next_step_id_for_downsell_rejected( $wcf_step_obj, $flow_steps, $step['id'], $control_step ); } if ( ! empty( $next_yes_steps ) && false !== get_post_status( $next_yes_steps ) ) { $yes_label = __( 'YES : ', 'cartflows-pro' ) . get_the_title( $next_yes_steps ); } else { $yes_label = __( 'YES : Step not Found', 'cartflows-pro' ); } if ( ! empty( $next_no_steps ) && false !== get_post_status( $next_no_steps ) ) { $no_label = __( 'No : ', 'cartflows-pro' ) . get_the_title( $next_no_steps ); } else { $no_label = __( 'No : Step not Found', 'cartflows-pro' ); } $steps[ $in ]['offer_yes_next_step'] = $yes_label; $steps[ $in ]['offer_no_next_step'] = $no_label; $steps[ $in ]['offer_yes_step_id'] = intval( $next_yes_steps ); $steps[ $in ]['offer_no_step_id'] = intval( $next_no_steps ); // Add the AB Conditional Redirection data as well. if ( ! empty( $steps[ $in ]['ab-test-variations'] ) ) { $ab_test_variations = $steps[ $in ]['ab-test-variations']; foreach ( $ab_test_variations as $variation_in => $variation ) { $variation_id = (int) $ab_test_variations[ $variation_in ]['id']; if ( (int) $step['id'] === $variation_id ) { $ab_test_variations[ $variation_in ]['offer_yes_next_step'] = $yes_label; $ab_test_variations[ $variation_in ]['offer_no_next_step'] = $no_label; $ab_test_variations[ $variation_in ]['offer_yes_step_id'] = intval( $next_yes_steps ); $ab_test_variations[ $variation_in ]['offer_no_step_id'] = intval( $next_no_steps ); continue; } // Skip stale or deleted variations — the post may be gone // while the id is still referenced in ab-test-variations // meta, which would leave the factory with empty data and // produce confusing labels downstream. if ( false === get_post_status( $variation_id ) ) { continue; } $ab_test_wcf_step_obj = wcf_pro_get_step( $variation_id ); $ab_test_flow_steps = $ab_test_wcf_step_obj->get_flow_steps(); $ab_test_control_step = $ab_test_wcf_step_obj->get_control_step(); $ab_test_next_yes_steps = null; $ab_test_next_no_steps = null; if ( 'upsell' === $step['type'] ) { $ab_test_next_yes_steps = wcf_pro()->flow->get_next_step_id_for_upsell_accepted( $ab_test_wcf_step_obj, $ab_test_flow_steps, $variation_id, $ab_test_control_step ); $ab_test_next_no_steps = wcf_pro()->flow->get_next_step_id_for_upsell_rejected( $ab_test_wcf_step_obj, $ab_test_flow_steps, $variation_id, $ab_test_control_step ); } if ( 'downsell' === $step['type'] ) { $ab_test_next_yes_steps = wcf_pro()->flow->get_next_step_id_for_downsell_accepted( $ab_test_wcf_step_obj, $ab_test_flow_steps, $variation_id, $ab_test_control_step ); $ab_test_next_no_steps = wcf_pro()->flow->get_next_step_id_for_downsell_rejected( $ab_test_wcf_step_obj, $ab_test_flow_steps, $variation_id, $ab_test_control_step ); } if ( ! empty( $ab_test_next_yes_steps ) && false !== get_post_status( $ab_test_next_yes_steps ) ) { $ab_test_yes_label = __( 'YES : ', 'cartflows-pro' ) . get_the_title( $ab_test_next_yes_steps ); } else { $ab_test_yes_label = __( 'YES : Step not Found', 'cartflows-pro' ); } if ( ! empty( $ab_test_next_no_steps ) && false !== get_post_status( $ab_test_next_no_steps ) ) { $ab_test_no_label = __( 'No : ', 'cartflows-pro' ) . get_the_title( $ab_test_next_no_steps ); } else { $ab_test_no_label = __( 'No : Step not Found', 'cartflows-pro' ); } $ab_test_variations[ $variation_in ]['offer_yes_next_step'] = $ab_test_yes_label; $ab_test_variations[ $variation_in ]['offer_no_next_step'] = $ab_test_no_label; $ab_test_variations[ $variation_in ]['offer_yes_step_id'] = intval( $ab_test_next_yes_steps ); $ab_test_variations[ $variation_in ]['offer_no_step_id'] = intval( $ab_test_next_no_steps ); } $steps[ $in ]['ab-test-variations'] = $ab_test_variations; } } } } return $steps; } /** * Save step pro action. * * @param int $step_id step id. */ public function save_step_actions( $step_id ) { delete_post_meta( $step_id, 'wcf-pro-dynamic-css' ); } /** * Get payment gateways. * * @param array $localize localized variables. */ public function localize_required_vars( $localize ) { $product_id = \CartFlows_Pro_Licence::get_instance()->product_id; $localize['cf_pro_type'] = CARTFLOWS_PRO_PLUGIN_TYPE; $localize['license_status'] = get_option( 'wc_am_client_' . $product_id . '_activated', '' ); $localize['license_activation_nonce'] = wp_create_nonce( 'cartflows_license_activation_nonce' ); $localize['license_deactivation_nonce'] = wp_create_nonce( 'cartflows_license_deactivation_nonce' ); $localize['is_order_bump_migrated'] = get_option( 'wcf_order_bump_migrated', false ); return $localize; } /** * Add PRO onboarding statistics data. * * This function adds PRO specific onboarding statistics data to the given stats data. * It checks for the presence of order bumps and upsells in the checkout steps and updates * the stats data accordingly. * * @param array $stats_data The statistics data to be updated. * @param array $checkout_steps The checkout steps to check for order bumps and upsells. * * @return array The updated statistics data. */ public function add_pro_onboarding_stats_data( $stats_data, $checkout_steps ) { // Return if the stats_data OR checkout steps are empty. if ( ! is_array( $stats_data ) || ! is_array( $checkout_steps ) ) { return $stats_data; } // Loop through the array of checkout step Ids. foreach ( $checkout_steps as $step_id ) { // Get the funnel ID from the checkout Step ID. $funnel_id = (int) wcf()->utils->get_flow_id_from_step_id( $step_id ); // Check is the offers steps are available or not. $is_offer_step_exists = wcf_pro()->flow->is_upsell_exist_in_flow( $funnel_id, $step_id ); // Check for order bumps. $order_bumps = wcf()->options->get_checkout_meta_value( $step_id, 'wcf-order-bumps' ); // Check if the order bump is added or not. if ( ! empty( $order_bumps ) ) { $stats_data['flow_have_order_bump'] = true; } // Check if the offer steps are available in the funnel. if ( ! empty( $is_offer_step_exists ) && false !== $is_offer_step_exists ) { $stats_data['flow_have_upsell'] = true; } // Break the loop if both options are found and true. if ( isset( $stats_data['flow_have_order_bump'] ) && $stats_data['flow_have_order_bump'] && isset( $stats_data['flow_have_upsell'] ) && $stats_data['flow_have_upsell'] ) { break; } } return $stats_data; } } AdminHooks::get_instance();