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'] ) ) : ''; 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, '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' ) ); if ( false === $inserted ) { wp_send_json_error( array( 'message' => __( 'Database insertion failed', 'affistyle' ) ), 500 ); } 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 ) ); } } } // Initialize handler. AffiStyle_Click_Analytics::init();