prefix . 'affistyle_clicks' ); $box_id = absint( $box_id ); if ( ! $box_id ) { return false; } if ( empty( $ip_address ) && isset( $_SERVER['REMOTE_ADDR'] ) ) { $ip_address = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ); } $ip_address = self::standardize_ip( $ip_address ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $last_created = $wpdb->get_var( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter "SELECT created_at FROM {$table_name} WHERE box_id = %d AND (ip_address = %s OR ip_address = '127.0.0.1' OR ip_address = '') ORDER BY id DESC LIMIT 1", $box_id, $ip_address ) ); if ( ! empty( $last_created ) ) { $last_ts = strtotime( $last_created ); $now_ts = time(); $diff = $now_ts - $last_ts; if ( $diff >= 0 && $diff <= absint( $time_window ) ) { return true; } } return false; } /** * High-Performance AJAX Endpoint to Track Clicks Securely. */ public static function track_click_ajax() { // Verifies nonce passed either as 'nonce' or default '_ajax_nonce'. if ( ! check_ajax_referer( 'affistyle_track_click_nonce', 'nonce', false ) && ! check_ajax_referer( 'affistyle_track_click_nonce', '_ajax_nonce', false ) ) { wp_send_json_error( array( 'message' => __( 'Security verification failed.', 'affistyle' ) ), 403 ); } // phpcs:disable WordPress.Security.NonceVerification.Missing $box_id = isset( $_POST['box_id'] ) ? absint( $_POST['box_id'] ) : ( isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0 ); $post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : $box_id; $product_index = isset( $_POST['product_index'] ) ? absint( $_POST['product_index'] ) : ( isset( $_POST['product'] ) ? absint( $_POST['product'] ) : 1 ); $raw_referrer = isset( $_SERVER['HTTP_REFERER'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : ''; if ( ! $box_id && ! $post_id ) { wp_send_json_error( array( 'message' => __( 'Invalid Box/Post ID', 'affistyle' ) ), 400 ); } $target_id = $post_id ? $post_id : $box_id; $target_post_type = get_post_type( $target_id ); if ( 'affistyle_box' !== $target_post_type && 'aff_box' !== $target_post_type ) { wp_send_json_error( array( 'message' => __( 'Invalid Affiliate Box Post Type', 'affistyle' ) ), 400 ); } $raw_ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : ''; $ip_address = self::standardize_ip( $raw_ip ); // Deduplication: if click recorded in last 5 seconds, return success without duplicate insert. if ( self::is_duplicate_click( $target_id, $ip_address, 5 ) ) { wp_send_json_success( array( 'message' => __( 'Click already recorded recently.', 'affistyle' ) ) ); } $passed_country = isset( $_POST['client_country'] ) ? sanitize_text_field( wp_unslash( $_POST['client_country'] ) ) : ( isset( $_POST['country_code'] ) ? sanitize_text_field( wp_unslash( $_POST['country_code'] ) ) : '' ); $passed_country = strtoupper( trim( $passed_country ) ); if ( ! empty( $passed_country ) && preg_match( '/^[A-Z]{2,3}$/', $passed_country ) && 'XX' !== $passed_country ) { $country_code = $passed_country; } else { $country_code = self::detect_country_code(); } // phpcs:enable WordPress.Security.NonceVerification.Missing $device_type = self::detect_device_type(); // phpcs:disable WordPress.Security.NonceVerification.Missing $click_source = isset( $_POST['click_source'] ) ? sanitize_key( wp_unslash( $_POST['click_source'] ) ) : ''; // phpcs:enable WordPress.Security.NonceVerification.Missing if ( 'mobile_sticky_cta' === $click_source ) { $device_type = 'mobile_sticky'; } $referrer_url = ! empty( $raw_referrer ) ? esc_url_raw( $raw_referrer ) : ''; $user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : ''; // phpcs:disable WordPress.Security.NonceVerification.Missing $variant = isset( $_POST['variant'] ) ? strtoupper( sanitize_key( wp_unslash( $_POST['variant'] ) ) ) : 'A'; // phpcs:enable WordPress.Security.NonceVerification.Missing if ( 'B' !== $variant ) { $variant = 'A'; } if ( class_exists( 'AffiStyle_Database' ) ) { AffiStyle_Database::ensure_analytics_table_exists(); } global $wpdb; $table_name = esc_sql( $wpdb->prefix . 'affistyle_clicks' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $inserted = $wpdb->insert( $table_name, array( 'box_id' => $target_id, 'post_id' => $target_id, 'product_index' => $product_index, 'variant' => $variant, 'country_code' => ! empty( $country_code ) ? $country_code : 'ZZ', 'device_type' => $device_type, 'referrer_url' => $referrer_url, 'referrer' => $referrer_url, 'ip_address' => $ip_address, 'user_agent' => $user_agent, 'created_at' => current_time( 'mysql' ), ), array( '%d', '%d', '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ); if ( false === $inserted ) { wp_send_json_error( array( 'message' => __( 'Database insertion failed', 'affistyle' ) ), 500 ); } // Update A/B test click counters if A/B split testing is active. $ab_enabled = get_post_meta( $target_id, '_affistyle_ab_enabled', true ); if ( '1' === (string) $ab_enabled ) { $ab_clicks_meta = ( 'B' === $variant ) ? '_affistyle_ab_clicks_b' : '_affistyle_ab_clicks_a'; $cur_ab_clicks = (int) get_post_meta( $target_id, $ab_clicks_meta, true ); update_post_meta( $target_id, $ab_clicks_meta, $cur_ab_clicks + 1 ); } wp_send_json_success( array( 'message' => __( 'Click recorded successfully.', 'affistyle' ) ) ); } /** * Add Analytics Stats Column to the AffiStyle Post List Table. * * @param array $columns Existing post list columns. * @return array */ public static function add_clicks_column( $columns ) { $columns['affistyle_clicks'] = __( 'Total Clicks', 'affistyle' ); return $columns; } /** * Render Click Stats column content. * * @param string $column Column key. * @param int $post_id Post ID. */ public static function show_clicks_column_content( $column, $post_id ) { if ( 'affistyle_clicks' === $column ) { global $wpdb; $table_name = esc_sql( $wpdb->prefix . 'affistyle_clicks' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $total_clicks = $wpdb->get_var( $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter "SELECT COUNT(id) FROM {$table_name} WHERE post_id = %d", absint( $post_id ) ) ); $total_clicks = $total_clicks ? absint( $total_clicks ) : 0; printf( /* translators: %d: Total number of clicks */ wp_kses_post( __( '%d clicks', 'affistyle' ) ), absint( $total_clicks ) ); } } /** * Retrieve financial intelligence settings. * * @return array */ public static function get_financial_settings() { $options = get_option( 'affistyle_financial_settings', array() ); $defaults = array( 'default_epc' => 0.00, 'default_aov' => 0.00, 'commission_rate' => 0.0, 'currency_symbol' => '$', ); return wp_parse_args( is_array( $options ) ? $options : array(), $defaults ); } /** * Compute card effective Earnings Per Click (EPC). * * Returns 0.0 if the user has not configured earning parameters for this card. * * @param int $card_id Post ID of the affiliate card/box. * @param int $product_index Product index (1 or 2). * @return float Effective EPC value. */ public static function get_card_effective_epc( $card_id, $product_index = 1 ) { $card_id = absint( $card_id ); if ( 0 === $card_id ) { return 0.0; } $prefix = ( 2 === (int) $product_index ) ? '_affistyle_p2_' : '_affistyle_'; $earning_type = get_post_meta( $card_id, $prefix . 'earning_type', true ); $fixed_epc = get_post_meta( $card_id, $prefix . 'estimated_epc', true ); $comm_rate = get_post_meta( $card_id, $prefix . 'commission_rate', true ); $conv_rate = get_post_meta( $card_id, $prefix . 'conversion_rate', true ); // Fallback to Product 1 settings if Product 2 is requested but not uniquely configured. if ( 2 === (int) $product_index && empty( $earning_type ) && '' === $fixed_epc && '' === $comm_rate ) { return self::get_card_effective_epc( $card_id, 1 ); } // Auto-detect mode if not explicitly set. if ( empty( $earning_type ) ) { if ( '' !== $fixed_epc && floatval( $fixed_epc ) > 0 ) { $earning_type = 'epc'; } elseif ( '' !== $comm_rate && floatval( $comm_rate ) > 0 ) { $earning_type = 'commission'; } else { $earning_type = 'none'; } } if ( 'epc' === $earning_type ) { $val = floatval( $fixed_epc ); return ( $val > 0 ) ? round( $val, 4 ) : 0.0; } if ( 'commission' === $earning_type ) { $price_meta = ( 2 === (int) $product_index ) ? get_post_meta( $card_id, '_affistyle_price_2', true ) : ( get_post_meta( $card_id, '_affistyle_price_1', true ) ? get_post_meta( $card_id, '_affistyle_price_1', true ) : get_post_meta( $card_id, '_affistyle_price', true ) ); // Extract numeric value from price string (e.g. "$199.99" -> 199.99). $clean_price = preg_replace( '/[^0-9\.]/', '', (string) $price_meta ); $price_val = floatval( $clean_price ); $comm_val = floatval( $comm_rate ); $conv_val = ( '' !== $conv_rate && floatval( $conv_rate ) > 0 ) ? floatval( $conv_rate ) : 2.0; if ( $price_val > 0 && $comm_val > 0 ) { // Effective EPC = Price * (Commission % / 100) * (Conversion % / 100). return round( $price_val * ( $comm_val / 100.0 ) * ( $conv_val / 100.0 ), 4 ); } } return 0.0; } /** * Check if any card has earning parameters configured. * * @return bool True if at least one card has earning configuration. */ public static function has_revenue_configured() { global $wpdb; // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $found = $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key IN (%s, %s, %s, %s) AND CAST(meta_value AS DECIMAL(10,4)) > 0 LIMIT 1", '_affistyle_estimated_epc', '_affistyle_commission_rate', '_affistyle_p2_estimated_epc', '_affistyle_p2_commission_rate' ) ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching return ! empty( $found ); } /** * Compute comprehensive financial performance metrics for the active date range. * * @param string $date_range Range preset ('7', '30', '60', '90', 'all', 'custom'). * @param string $start_date YYYY-MM-DD. * @param string $end_date YYYY-MM-DD. * @return array */ public static function get_financial_metrics( $date_range = '7', $start_date = '', $end_date = '' ) { global $wpdb; $table_name = esc_sql( $wpdb->prefix . 'affistyle_clicks' ); $settings = self::get_financial_settings(); $currency = (string) $settings['currency_symbol']; $has_revenue_configured = self::has_revenue_configured(); $summary = class_exists( 'AffiStyle_Database' ) ? AffiStyle_Database::get_analytics_summary( $date_range, $start_date, $end_date ) : array(); $total_clicks = isset( $summary['total_last_7_days'] ) ? absint( $summary['total_last_7_days'] ) : 0; if ( 'all' === $date_range && isset( $summary['total_all_time'] ) ) { $total_clicks = absint( $summary['total_all_time'] ); } if ( ! $has_revenue_configured ) { return array( 'has_revenue_configured' => false, 'epc' => 0.00, 'currency' => $currency, 'total_clicks' => $total_clicks, 'prev_clicks' => 0, 'estimated_revenue' => 0.0, 'growth_pct' => 0.0, 'is_positive' => true, 'top_card_id' => 0, 'top_card_title' => __( 'None', 'affistyle' ), 'top_card_clicks' => 0, 'top_card_revenue' => 0.0, ); } // Build WHERE clause matching date range. $where_clause = ''; if ( in_array( $date_range, array( '7', '30', '60', '90' ), true ) ) { // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $where_clause = $wpdb->prepare( 'WHERE created_at >= DATE_SUB(NOW(), INTERVAL %d DAY)', absint( $date_range ) ); } elseif ( 'custom' === $date_range && ! empty( $start_date ) && ! empty( $end_date ) ) { // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $where_clause = $wpdb->prepare( 'WHERE created_at >= %s AND created_at <= %s', $start_date . ' 00:00:00', $end_date . ' 23:59:59' ); } elseif ( 'custom' === $date_range && ! empty( $start_date ) ) { // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $where_clause = $wpdb->prepare( 'WHERE created_at >= %s', $start_date . ' 00:00:00' ); } elseif ( 'custom' === $date_range && ! empty( $end_date ) ) { // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $where_clause = $wpdb->prepare( 'WHERE created_at <= %s', $end_date . ' 23:59:59' ); } elseif ( 'all' !== $date_range ) { $where_clause = 'WHERE created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)'; } // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $card_clicks_rows = $wpdb->get_results( "SELECT COALESCE(NULLIF(box_id, 0), post_id) as card_id, product_index, COUNT(id) as click_count FROM {$table_name} {$where_clause} GROUP BY COALESCE(NULLIF(box_id, 0), post_id), product_index", ARRAY_A ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $total_estimated_revenue = 0.0; $card_rev_map = array(); if ( ! empty( $card_clicks_rows ) ) { foreach ( $card_clicks_rows as $row ) { $c_id = absint( $row['card_id'] ); $p_idx = absint( $row['product_index'] ); $c_count = absint( $row['click_count'] ); $c_epc = self::get_card_effective_epc( $c_id, $p_idx ); $c_rev = round( $c_count * $c_epc, 2 ); $total_estimated_revenue += $c_rev; if ( ! isset( $card_rev_map[ $c_id ] ) ) { $card_rev_map[ $c_id ] = array( 'revenue' => 0.0, 'clicks' => 0, ); } $card_rev_map[ $c_id ]['revenue'] += $c_rev; $card_rev_map[ $c_id ]['clicks'] += $c_count; } } // Calculate previous period clicks & revenue for growth rate. $prev_clicks = 0; $prev_revenue = 0.0; if ( in_array( $date_range, array( '7', '30', '60', '90' ), true ) ) { $days = absint( $date_range ); // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $prev_rows = $wpdb->get_results( $wpdb->prepare( "SELECT COALESCE(NULLIF(box_id, 0), post_id) as card_id, product_index, COUNT(id) as click_count FROM {$table_name} WHERE created_at >= DATE_SUB(NOW(), INTERVAL %d DAY) AND created_at < DATE_SUB(NOW(), INTERVAL %d DAY) GROUP BY COALESCE(NULLIF(box_id, 0), post_id), product_index", $days * 2, $days ), ARRAY_A ); // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter if ( ! empty( $prev_rows ) ) { foreach ( $prev_rows as $p_row ) { $p_id = absint( $p_row['card_id'] ); $p_idx = absint( $p_row['product_index'] ); $p_cnt = absint( $p_row['click_count'] ); $p_epc = self::get_card_effective_epc( $p_id, $p_idx ); $prev_revenue += round( $p_cnt * $p_epc, 2 ); $prev_clicks += $p_cnt; } } } $growth_pct = 0.0; $is_positive = true; if ( $prev_revenue > 0 ) { $growth_pct = round( ( ( $total_estimated_revenue - $prev_revenue ) / $prev_revenue ) * 100, 1 ); $is_positive = $growth_pct >= 0; } elseif ( $total_estimated_revenue > 0 ) { $growth_pct = 100.0; $is_positive = true; } // Top earning card info. $top_card_id = 0; $top_card_title = __( 'None', 'affistyle' ); $top_card_revenue = 0.0; $top_card_clicks = 0; foreach ( $card_rev_map as $c_id => $data_item ) { if ( $data_item['revenue'] > $top_card_revenue ) { $top_card_id = $c_id; $top_card_revenue = $data_item['revenue']; $top_card_clicks = $data_item['clicks']; } } if ( $top_card_id > 0 ) { $top_title = get_the_title( $top_card_id ); if ( ! empty( $top_title ) ) { $top_card_title = $top_title; } } $avg_epc = ( $total_clicks > 0 && $total_estimated_revenue > 0 ) ? round( $total_estimated_revenue / $total_clicks, 2 ) : 0.00; return array( 'has_revenue_configured' => true, 'epc' => $avg_epc, 'currency' => $currency, 'total_clicks' => $total_clicks, 'prev_clicks' => $prev_clicks, 'estimated_revenue' => $total_estimated_revenue, 'growth_pct' => $growth_pct, 'is_positive' => $is_positive, 'top_card_id' => $top_card_id, 'top_card_title' => $top_card_title, 'top_card_clicks' => $top_card_clicks, 'top_card_revenue' => $top_card_revenue, ); } /** * AJAX endpoint to update Financial Intelligence Settings. */ public static function ajax_save_financial_settings() { check_ajax_referer( 'affistyle_save_financial_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => __( 'Permission denied.', 'affistyle' ) ), 403 ); } $epc = isset( $_POST['default_epc'] ) ? floatval( $_POST['default_epc'] ) : 0.50; $aov = isset( $_POST['default_aov'] ) ? floatval( $_POST['default_aov'] ) : 50.00; $comm = isset( $_POST['commission_rate'] ) ? floatval( $_POST['commission_rate'] ) : 4.0; $currency = isset( $_POST['currency_symbol'] ) ? sanitize_text_field( wp_unslash( $_POST['currency_symbol'] ) ) : '$'; if ( $epc < 0 ) { $epc = 0.50; } $updated = array( 'default_epc' => $epc, 'default_aov' => $aov, 'commission_rate' => $comm, 'currency_symbol' => $currency, ); update_option( 'affistyle_financial_settings', $updated ); wp_send_json_success( array( 'message' => __( 'Financial model settings updated.', 'affistyle' ), 'settings' => $updated, ) ); } } // Initialize handler. AffiStyle_Click_Analytics::init();