register_rewrite_rules(); flush_rewrite_rules(); } /** * Retrieve sanitized cloaked prefix string. * * @return string */ public static function get_prefix() { $prefix = apply_filters( 'affistyle_cloak_prefix', 'recommends' ); $prefix = sanitize_title( $prefix ); return ! empty( $prefix ) ? $prefix : 'recommends'; } /** * Register Cloaked URL Rewrite Rules. */ public function register_rewrite_rules() { $prefix = preg_quote( self::get_prefix(), '/' ); add_rewrite_rule( '^' . $prefix . '/([^/]+)/([^/]+)/?$', 'index.php?affistyle_cloak=$matches[1]&affistyle_store=$matches[2]', 'top' ); add_rewrite_rule( '^' . $prefix . '/([^/]+)/?$', 'index.php?affistyle_cloak=$matches[1]', 'top' ); } /** * Register Unique Query Variables (Avoiding WordPress Core collisions). * * @param array $vars Public query variables. * @return array */ public function register_query_vars( $vars ) { $vars[] = 'affistyle_cloak'; $vars[] = 'affistyle_p'; $vars[] = 'affistyle_store'; return $vars; } /** * Handle plain permalink fallback (?affistyle_cloak=slug). */ public function maybe_intercept_plain_request() { if ( is_admin() ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $cloak_param = isset( $_GET['affistyle_cloak'] ) ? sanitize_title( wp_unslash( $_GET['affistyle_cloak'] ) ) : ''; if ( ! empty( $cloak_param ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $store = isset( $_GET['affistyle_store'] ) ? sanitize_title( wp_unslash( $_GET['affistyle_store'] ) ) : ( isset( $_GET['store'] ) ? sanitize_title( wp_unslash( $_GET['store'] ) ) : '' ); $this->execute_redirect( $cloak_param, $store ); } } /** * Handle Cloaked Link Redirection on template_redirect hook. */ public function handle_cloaked_redirect() { global $wp; $slug = get_query_var( 'affistyle_cloak' ); $store = get_query_var( 'affistyle_store' ); // 1. Direct query variable match from standard WordPress rewrite rules. if ( ! empty( $slug ) ) { if ( empty( $store ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $store = isset( $_GET['affistyle_store'] ) ? sanitize_title( wp_unslash( $_GET['affistyle_store'] ) ) : ( isset( $_GET['store'] ) ? sanitize_title( wp_unslash( $_GET['store'] ) ) : '' ); } $this->execute_redirect( sanitize_title( $slug ), sanitize_title( $store ) ); return; } // 2. Direct URI/request path parsing (works on %postname% & custom permalinks even if rewrite rules weren't flushed). $prefix = self::get_prefix(); $req_path = isset( $wp->request ) ? trim( (string) $wp->request, '/' ) : ''; if ( empty( $req_path ) && isset( $_SERVER['REQUEST_URI'] ) ) { $raw_uri = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ); $raw_path = (string) wp_parse_url( $raw_uri, PHP_URL_PATH ); $home_path = (string) wp_parse_url( home_url(), PHP_URL_PATH ); $home_path = trim( (string) $home_path, '/' ); $path_only = trim( $raw_path, '/' ); if ( ! empty( $home_path ) && 0 === strpos( $path_only, $home_path ) ) { $path_only = trim( substr( $path_only, strlen( $home_path ) ), '/' ); } $req_path = $path_only; } if ( ! empty( $req_path ) ) { // Standard prefix check: e.g. /recommends/my-slug/ or /recommends/my-slug/store/. if ( preg_match( '#^' . preg_quote( $prefix, '#' ) . '/([^/]+)(?:/([^/]+))?/?$#i', $req_path, $matches ) ) { $matched_slug = sanitize_title( $matches[1] ); $matched_store = ! empty( $matches[2] ) ? sanitize_title( $matches[2] ) : ''; $this->execute_redirect( $matched_slug, $matched_store ); return; } // 3. Pretty Links / Legacy Migration 404 Interceptor: // If a URL would 404 (e.g. an imported Pretty Link like /amazon-deal or /go/deal), // check if the slug matches an imported AffiStyle Box and seamlessly redirect. if ( is_404() ) { $candidate_slug = sanitize_title( basename( $req_path ) ); if ( ! empty( $candidate_slug ) ) { $matched_post = $this->resolve_post_by_slug( $candidate_slug ); if ( $matched_post ) { $this->execute_redirect( $candidate_slug ); return; } } } } } /** * Resolve an affiliate box post from slug, ID, or custom slug postmeta. * * @param string $slug Box post slug, ID, or custom slug. * @return WP_Post|null */ public function resolve_post_by_slug( $slug ) { if ( empty( $slug ) ) { return null; } $post = null; // 1. Direct ID resolution. if ( is_numeric( $slug ) ) { $candidate = get_post( absint( $slug ) ); if ( $candidate && in_array( $candidate->post_type, array( 'affistyle_box', 'aff_box' ), true ) ) { $post = $candidate; } } // 2. Query by post_name. if ( ! $post ) { $posts = get_posts( array( 'name' => $slug, 'post_type' => array( 'affistyle_box', 'aff_box' ), 'post_status' => 'publish', 'numberposts' => 1, ) ); if ( ! empty( $posts ) ) { $post = $posts[0]; } } // 3. Query by custom slug meta (e.g. imported from Pretty Links, Lasso, etc.). if ( ! $post ) { $posts = get_posts( array( 'post_type' => array( 'affistyle_box', 'aff_box' ), 'post_status' => 'publish', 'numberposts' => 1, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query 'meta_query' => array( array( 'key' => '_affistyle_custom_slug', 'value' => $slug, ), ), ) ); if ( ! empty( $posts ) ) { $post = $posts[0]; } } $post = apply_filters( 'affistyle_resolve_cloaked_post', $post, $slug ); if ( ! $post || ! in_array( $post->post_type, array( 'affistyle_box', 'aff_box' ), true ) ) { return null; } return $post; } /** * Execute redirect for a validated box slug. * * @param string $slug Box post slug or custom slug. * @param string $store_param Optional store identifier for multi-retailer URLs. */ private function execute_redirect( $slug, $store_param = '' ) { if ( empty( $slug ) ) { return; } $post = $this->resolve_post_by_slug( $slug ); if ( ! $post ) { return; // Allow WordPress to handle 404 naturally. } $box_id = $post->ID; // Resolve product index using safe query variables. // phpcs:ignore WordPress.Security.NonceVerification.Recommended $get_p = isset( $_GET['affistyle_p'] ) ? sanitize_text_field( wp_unslash( $_GET['affistyle_p'] ) ) : ( isset( $_GET['p'] ) ? sanitize_text_field( wp_unslash( $_GET['p'] ) ) : '' ); $product_query = get_query_var( 'affistyle_p' ); $product_index = ( '2' === $get_p || '2' === (string) $product_query ) ? 2 : 1; $destination = ''; // 1. Multi-Store Retailer Lookup if store parameter is present. if ( ! empty( $store_param ) ) { $retailers = class_exists( 'AffiStyle_Pro_Multi_Retailer' ) ? AffiStyle_Pro_Multi_Retailer::get_retailers( $box_id, $product_index ) : get_post_meta( $box_id, '_affistyle_multi_retailers_' . $product_index, true ); if ( is_string( $retailers ) ) { $retailers = json_decode( $retailers, true ); } if ( ! empty( $retailers ) && is_array( $retailers ) ) { foreach ( $retailers as $idx => $r ) { $r_id = ! empty( $r['store_id'] ) ? $r['store_id'] : ( ! empty( $r['store_name'] ) ? sanitize_title( $r['store_name'] ) : 'store-' . ( $idx + 1 ) ); $r_name = ! empty( $r['store_name'] ) ? sanitize_title( $r['store_name'] ) : ''; if ( $r_id === $store_param || sanitize_title( $r_id ) === sanitize_title( $store_param ) || $r_name === $store_param || sanitize_title( $r_name ) === sanitize_title( $store_param ) ) { $target_store_url = ! empty( $r['url'] ) ? $r['url'] : ( ! empty( $r['link'] ) ? $r['link'] : '' ); if ( ! empty( $target_store_url ) && '#' !== $target_store_url ) { $destination = $target_store_url; break; } } } } } // 2. Default Destination Fallback. if ( empty( $destination ) ) { $destination = ( 2 === $product_index ) ? get_post_meta( $box_id, '_affistyle_url_2_default', true ) : get_post_meta( $box_id, '_affistyle_destination', true ); if ( empty( $destination ) && 1 === $product_index ) { $destination = get_post_meta( $box_id, '_affistyle_url_default', true ); } } $destination = apply_filters( 'affistyle_cloaked_destination', $destination, $box_id, $product_index, $store_param ); $destination = apply_filters( 'affistyle_redirect_destination_url', $destination, $box_id, $product_index, $store_param ); if ( empty( $destination ) ) { wp_safe_redirect( home_url(), 302 ); exit; } // Attribution Tracking. do_action( 'affistyle_track_redirect_click', $box_id, $product_index, $store_param ); // Anti-caching and SEO headers. $status_code = apply_filters( 'affistyle_redirect_status_code', 302, $box_id, $product_index ); $status_code = absint( $status_code ); if ( ! in_array( $status_code, array( 301, 302, 307 ), true ) ) { $status_code = 302; } nocache_headers(); header( 'Cache-Control: no-cache, no-store, must-revalidate', true ); header( 'Pragma: no-cache', true ); header( 'Expires: 0', true ); header( 'X-Robots-Tag: noindex, nofollow', true ); $affistyle_target_url = esc_url_raw( wp_unslash( $destination ) ); if ( ! empty( $affistyle_target_url ) ) { // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- External redirect is expected for cloaked affiliate URLs. wp_redirect( $affistyle_target_url, $status_code ); exit; } } /** * Log click into analytics database before executing HTTP redirect. * * @param int $box_id Affiliate Box ID. * @param int $product_index Product index (1 or 2). */ public function record_redirect_click( $box_id, $product_index = 1 ) { $box_id = absint( $box_id ); if ( ! $box_id ) { return; } if ( class_exists( 'AffiStyle_Database' ) ) { AffiStyle_Database::ensure_analytics_table_exists(); } global $wpdb; $table_name = esc_sql( $wpdb->prefix . 'affistyle_clicks' ); $country_code = class_exists( 'AffiStyle_Click_Analytics' ) ? AffiStyle_Click_Analytics::detect_country_code() : 'ZZ'; $device_type = class_exists( 'AffiStyle_Click_Analytics' ) ? AffiStyle_Click_Analytics::detect_device_type() : 'desktop'; $raw_referrer = isset( $_SERVER['HTTP_REFERER'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : ''; $referrer_url = ! empty( $raw_referrer ) ? esc_url_raw( $raw_referrer ) : ''; $ip_address = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : ''; if ( class_exists( 'AffiStyle_Click_Analytics' ) && AffiStyle_Click_Analytics::is_duplicate_click( $box_id, $ip_address, 5 ) ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $click_source = isset( $_GET['aff_src'] ) ? sanitize_key( wp_unslash( $_GET['aff_src'] ) ) : ( isset( $_GET['src'] ) ? sanitize_key( wp_unslash( $_GET['src'] ) ) : 'card_primary_button' ); if ( 'mobile_sticky_cta' === $click_source ) { $device_type = 'mobile_sticky'; } $user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : ''; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->insert( $table_name, array( 'box_id' => $box_id, 'post_id' => $box_id, 'product_index' => absint( $product_index ), 'country_code' => ! empty( $country_code ) ? $country_code : 'ZZ', 'device_type' => $device_type, 'referrer_url' => $referrer_url, 'referrer' => ! empty( $click_source ) ? $click_source : $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' ) ); } /** * Retrieve Cloaked or Raw Affiliate Destination URL. * * @param int $post_id Affiliate Box ID. * @param int $product Product index (1 or 2). * @param string $store_id Optional store identifier for multi-store. * @return string */ public static function get_cloaked_url( $post_id, $product = 1, $store_id = '' ) { $post_id = absint( $post_id ); if ( ! $post_id ) { return '#'; } $product_index = ( 2 === absint( $product ) ) ? 2 : 1; $cloaking_enabled = apply_filters( 'affistyle_enable_cloaking', true ); if ( ! $cloaking_enabled ) { if ( ! empty( $store_id ) ) { $retailers = get_post_meta( $post_id, '_affistyle_multi_retailers_' . $product_index, true ); if ( is_string( $retailers ) ) { $retailers = json_decode( $retailers, true ); } if ( is_array( $retailers ) ) { foreach ( $retailers as $idx => $r ) { $r_id = ! empty( $r['store_id'] ) ? $r['store_id'] : ( ! empty( $r['store_name'] ) ? sanitize_title( $r['store_name'] ) : 'store-' . ( $idx + 1 ) ); if ( $r_id === $store_id || sanitize_title( $r_id ) === sanitize_title( $store_id ) ) { return ! empty( $r['url'] ) ? $r['url'] : '#'; } } } } if ( 2 === $product_index ) { $raw_url = get_post_meta( $post_id, '_affistyle_url_2_default', true ); } else { $raw_url = get_post_meta( $post_id, '_affistyle_destination', true ); if ( empty( $raw_url ) ) { $raw_url = get_post_meta( $post_id, '_affistyle_url_default', true ); } } return ! empty( $raw_url ) ? $raw_url : '#'; } $custom_slug = get_post_meta( $post_id, '_affistyle_custom_slug', true ); if ( empty( $custom_slug ) ) { $post = get_post( $post_id ); $slug = ( $post && ! empty( $post->post_name ) ) ? $post->post_name : 'box-' . $post_id; } else { $slug = $custom_slug; } $prefix = self::get_prefix(); $slug = apply_filters( 'affistyle_post_cloak_slug', $slug, $post_id ); if ( ! get_option( 'permalink_structure' ) ) { $url = add_query_arg( array( 'affistyle_cloak' => $slug ), home_url( '/' ) ); } else { $url = home_url( '/' . trim( $prefix, '/' ) . '/' . trim( $slug, '/' ) . '/' ); } if ( 2 === $product_index ) { $url = add_query_arg( 'affistyle_p', '2', $url ); } if ( ! empty( $store_id ) ) { $url = add_query_arg( 'affistyle_store', sanitize_title( $store_id ), $url ); } return $url; } /** * Helper method specifically for multi-store retailer cloaked URLs. * * @param int $post_id Affiliate Box ID. * @param string $store_id Store identifier or slug. * @param int $product Product index (1 or 2). * @return string */ public static function get_cloaked_retailer_url( $post_id, $store_id = '', $product = 1 ) { return self::get_cloaked_url( $post_id, $product, $store_id ); } } new AffiStyle_Cloaking();