init_hooks(); } /** * Get the single instance of the Tracker class. * * @since 1.0.0 * @access public * * @return Tracker The single instance. */ public static function get_instance() { if ( is_null( self::$instance ) ) { self::$instance = new self(); } return self::$instance; } /** * Initialize WordPress hooks. * * @since 1.0.0 * @access private * * @return void */ private function init_hooks() { add_action( 'wp_ajax_acumenpopup_track_view', array( $this, 'track_view' ) ); add_action( 'wp_ajax_nopriv_acumenpopup_track_view', array( $this, 'track_view' ) ); add_action( 'wp_ajax_acumenpopup_track_conversion', array( $this, 'track_conversion' ) ); add_action( 'wp_ajax_nopriv_acumenpopup_track_conversion', array( $this, 'track_conversion' ) ); add_action( 'wp_ajax_acumenpopup_get_analytics', array( $this, 'get_analytics_data' ) ); } /** * Track popup view. * * Increments the view count for a popup. * * @since 1.0.0 * @access public * * @return void */ public function track_view() { check_ajax_referer( 'acumenpopup_tracker', 'nonce' ); $popup_id_raw = isset( $_POST['popup_id'] ) ? sanitize_text_field( wp_unslash( $_POST['popup_id'] ) ) : ''; if ( empty( $popup_id_raw ) ) { wp_send_json_error( array( 'message' => 'Invalid popup ID' ) ); } // Try to find post by acumenpopup_popup_id meta. $args = array( 'post_type' => 'acumen_popup_cmpgn', 'meta_key' => 'acumenpopup_popup_id', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'meta_value' => $popup_id_raw, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value 'fields' => 'ids', 'numberposts' => 1, 'post_status' => 'any', ); $posts = get_posts( $args ); $post_id = ! empty( $posts ) ? $posts[0] : 0; // Fallback: Check if raw ID is already a numeric post ID. if ( ! $post_id && is_numeric( $popup_id_raw ) ) { $post_id = absint( $popup_id_raw ); } if ( ! $post_id ) { wp_send_json_error( array( 'message' => 'Popup not found' ) ); } // Update total views $current_views = (int) get_post_meta( $post_id, 'acumenpopup_views', true ); update_post_meta( $post_id, 'acumenpopup_views', $current_views + 1 ); // Daily tracking $today = current_time( 'Y-m-d' ); $daily_views = (int) get_post_meta( $post_id, 'acumenpopup_views_' . $today, true ); update_post_meta( $post_id, 'acumenpopup_views_' . $today, $daily_views + 1 ); // Device tracking $device = $this->get_device_type(); $device_views = (int) get_post_meta( $post_id, 'acumenpopup_views_device_' . $device, true ); update_post_meta( $post_id, 'acumenpopup_views_device_' . $device, $device_views + 1 ); wp_send_json_success( array( 'views' => $current_views + 1 ) ); } /** * Track popup form conversion. * * Increments the conversion count for a popup. * * @since 1.0.0 * @access public * * @return void */ public function track_conversion() { // Try form submit nonce or tracker nonce depending on caller if ( ! check_ajax_referer( 'acumenpopup_tracker', 'nonce', false ) && ! check_ajax_referer( 'acumenpopup_form_submit', 'nonce', false ) ) { wp_send_json_error( array( 'message' => 'Invalid nonce' ) ); } $popup_id_raw = isset( $_POST['popup_id'] ) ? sanitize_text_field( wp_unslash( $_POST['popup_id'] ) ) : ''; if ( empty( $popup_id_raw ) ) { wp_send_json_error( array( 'message' => 'Invalid popup ID' ) ); } $args = array( 'post_type' => 'acumen_popup_cmpgn', 'meta_key' => 'acumenpopup_popup_id', // phpcs:ignore 'meta_value' => $popup_id_raw, // phpcs:ignore 'fields' => 'ids', 'numberposts' => 1, 'post_status' => 'any', ); $posts = get_posts( $args ); $post_id = ! empty( $posts ) ? $posts[0] : 0; if ( ! $post_id && is_numeric( $popup_id_raw ) ) { $post_id = absint( $popup_id_raw ); } if ( ! $post_id ) { wp_send_json_error( array( 'message' => 'Popup not found' ) ); } // Update total conversions $current_conversions = (int) get_post_meta( $post_id, 'acumenpopup_conversions', true ); update_post_meta( $post_id, 'acumenpopup_conversions', $current_conversions + 1 ); // Daily conversion tracking $today = current_time( 'Y-m-d' ); $daily_conv = (int) get_post_meta( $post_id, 'acumenpopup_conv_' . $today, true ); update_post_meta( $post_id, 'acumenpopup_conv_' . $today, $daily_conv + 1 ); // Device conversion tracking $device = $this->get_device_type(); $device_conv = (int) get_post_meta( $post_id, 'acumenpopup_conv_device_' . $device, true ); update_post_meta( $post_id, 'acumenpopup_conv_device_' . $device, $device_conv + 1 ); wp_send_json_success( array( 'conversions' => $current_conversions + 1 ) ); } /** * Detect device type from user agent. * * @return string 'mobile', 'tablet', or 'desktop' */ private function get_device_type() { $user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : ''; if ( preg_match( '/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i', $user_agent ) ) { return 'tablet'; } if ( preg_match( '/Mobile|iP(hone|od)|Android|BlackBerry|IEMobile|Kindle|Silk-Accelerated|(hpw|web)OS|Opera M(obi|ini)/', $user_agent ) ) { return 'mobile'; } return 'desktop'; } /** * Get analytics data. * * @since 1.0.0 * @access public * * @return void */ public function get_analytics_data() { check_ajax_referer( 'acumenpopup-nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'Unauthorized' ), 403 ); } // Accept date range and campaign filter from frontend $date_from = isset( $_POST['date_from'] ) ? sanitize_text_field( wp_unslash( $_POST['date_from'] ) ) : ''; $date_to = isset( $_POST['date_to'] ) ? sanitize_text_field( wp_unslash( $_POST['date_to'] ) ) : ''; $all_time = isset( $_POST['all_time'] ) ? (bool) $_POST['all_time'] : false; $campaign_id = isset( $_POST['campaign_id'] ) ? absint( $_POST['campaign_id'] ) : 0; $now = current_time( 'timestamp' ); $from_ts = $date_from ? strtotime( $date_from ) : strtotime( '-6 days', $now ); $to_ts = $date_to ? strtotime( $date_to ) : $now; if ( $from_ts > $to_ts ) { list( $from_ts, $to_ts ) = array( $to_ts, $from_ts ); } // Build date range array for chart (cap at 60 days) $chart_dates = array(); $diff_days = (int) round( ( $to_ts - $from_ts ) / DAY_IN_SECONDS ) + 1; $diff_days = min( $diff_days, 60 ); for ( $i = $diff_days - 1; $i >= 0; $i-- ) { $date = gmdate( 'Y-m-d', strtotime( "-$i days", $to_ts ) ); $chart_dates[ $date ] = array( 'views' => 0, 'conversions' => 0 ); } $query_args = array( 'post_type' => 'acumen_popup_cmpgn', 'post_status' => 'any', 'posts_per_page' => -1, ); if ( $campaign_id ) { $query_args['post__in'] = array( $campaign_id ); } $campaigns = get_posts( $query_args ); $total_views = 0; $total_conversions = 0; $campaign_list = array(); $devices = array( 'desktop' => 0, 'tablet' => 0, 'mobile' => 0 ); foreach ( $campaigns as $post ) { $campaign_views = 0; $campaign_conv = 0; foreach ( $chart_dates as $date => $stats ) { $dv = (int) get_post_meta( $post->ID, 'acumenpopup_views_' . $date, true ); $dc = (int) get_post_meta( $post->ID, 'acumenpopup_conv_' . $date, true ); $chart_dates[ $date ]['views'] += $dv; $chart_dates[ $date ]['conversions'] += $dc; $campaign_views += $dv; $campaign_conv += $dc; } // For "All Time" use stored totals; otherwise use date-range sum if ( $all_time ) { $campaign_views = (int) get_post_meta( $post->ID, 'acumenpopup_views', true ); $campaign_conv = (int) get_post_meta( $post->ID, 'acumenpopup_conversions', true ); } $total_views += $campaign_views; $total_conversions += $campaign_conv; $devices['desktop'] += (int) get_post_meta( $post->ID, 'acumenpopup_views_device_desktop', true ); $devices['tablet'] += (int) get_post_meta( $post->ID, 'acumenpopup_views_device_tablet', true ); $devices['mobile'] += (int) get_post_meta( $post->ID, 'acumenpopup_views_device_mobile', true ); $campaign_list[] = array( 'id' => $post->ID, 'popup_id' => get_post_meta( $post->ID, 'acumenpopup_popup_id', true ), 'title' => $post->post_title, 'views' => $campaign_views, 'conversions' => $campaign_conv, ); } $formatted_chart_data = array(); foreach ( $chart_dates as $date => $stats ) { $formatted_chart_data[] = array( 'date' => gmdate( 'd M', strtotime( $date ) ), 'views' => $stats['views'], 'conversions' => $stats['conversions'], ); } wp_send_json_success( array( 'total_views' => $total_views, 'total_conversions' => $total_conversions, 'devices' => $devices, 'chart_data' => $formatted_chart_data, 'campaigns' => $campaign_list, ) ); } /** * Prevent cloning of the instance. * * @since 1.0.0 * @access public * * @return void */ public function __clone() { _doing_it_wrong( __FUNCTION__, esc_html__( 'Cloning is forbidden.', 'acumen-popup' ), '1.0.0' ); } /** * Prevent unserializing of the instance. * * @since 1.0.0 * @access public * * @return void */ public function __wakeup() { _doing_it_wrong( __FUNCTION__, esc_html__( 'Unserializing is forbidden.', 'acumen-popup' ), '1.0.0' ); } }