get_request_uri(); $request_query = $this->get_request_query(); $request_host = isset( $_SERVER['HTTP_HOST'] ) ? strtolower( sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) ) : ''; // Itera su ogni regola per trovare una corrispondenza. foreach ($rules as $rule) { // Le regole con sorgente assoluta si applicano solo al dominio/protocollo/porta indicati. $rule_origin = ''; if ( preg_match( '~^https?://~i', $rule->url_from ) ) { $parsed_rule = wp_parse_url( $rule->url_from ); $rule_scheme = isset( $parsed_rule['scheme'] ) ? strtolower( $parsed_rule['scheme'] ) : 'http'; $rule_host = isset( $parsed_rule['host'] ) ? strtolower( $parsed_rule['host'] ) : ''; $rule_port = isset( $parsed_rule['port'] ) ? ':' . $parsed_rule['port'] : ''; $rule_origin = $rule_scheme . '://' . $rule_host . $rule_port; } $request_scheme = is_ssl() ? 'https' : 'http'; $request_origin = $request_scheme . '://' . $request_host; if ( $rule_origin !== '' && $rule_origin !== $request_origin ) { continue; } $match = false; $destination_url = ''; $mode = isset($rule->query_string_mode) ? $rule->query_string_mode : 'ignore'; if (!in_array($mode, array('ignore', 'passthrough', 'exact', 'exact_passthrough', 'original_passthrough'), true)) { $mode = 'ignore'; } // Separa percorso e query string della sorgente (supporta anche URL assoluti). $rule_parts = $this->split_rule_url($rule->url_from); $rule_path = $rule_parts['path']; $rule_query = $rule_parts['query']; // Se la regola è di tipo 'wildcard' (con un asterisco *). if ($rule->type === 'wildcard') { // Converte il pattern wildcard in una espressione regolare (regex) case-insensitive. $regex = str_replace('\*', '(.*)', preg_quote($rule_path, '/')); if (preg_match('/^' . $regex . '$/i', $request_path, $matches)) { // Modalità exact: la query string deve corrispondere esattamente. if ( ( $mode === 'exact' || $mode === 'exact_passthrough' ) && ! $this->query_matches( $rule_query, $request_query ) ) { continue; } // Se l'URL di destinazione non contiene segnaposto ($1, $2, ...), usa l'URL così com'è. if (strpos($rule->url_to, '$') === false) { $destination_url = $rule->url_to; } else { // Sostituisce i segnaposto $1, $2, etc., con le parti catturate dalla regex. $destination_url = preg_replace_callback( '/\$(\d+)/', function ($placeholder_matches) use ($matches) { $index = (int)$placeholder_matches[1]; return isset($matches[$index]) ? $matches[$index] : ''; }, $rule->url_to ); } $match = true; } } else { // Altrimenti, è una regola di tipo 'url' (corrispondenza esatta del percorso). // Confronto diretto e case-insensitive tra il percorso della richiesta e la regola. if (strcasecmp($request_path, $rule_path) === 0) { // Modalità exact: la query string deve corrispondere esattamente. if ( ( $mode === 'exact' || $mode === 'exact_passthrough' ) && ! $this->query_matches( $rule_query, $request_query ) ) { continue; } $destination_url = $rule->url_to; $match = true; } } // Se è stata trovata una corrispondenza, esegui il reindirizzamento e termina. if ($match) { $this->execute_redirect($rule, $destination_url, $request_path, $request_query, $mode); // Esci dal loop e dallo script. return; } } } /** * Recupera le regole di reindirizzamento attive, utilizzando la Object Cache di WordPress. * * @return array L'array delle regole di reindirizzamento attive. */ public static function get_active_redirects() { // 1. Per prima cosa, proviamo a recuperare i dati dalla cache. $cached_rules = wp_cache_get( self::CACHE_KEY, self::CACHE_GROUP ); // Se `wp_cache_get` restituisce un valore diverso da `false`, significa che abbiamo trovato i dati. if ( false !== $cached_rules ) { // Cache HIT! Restituiamo i dati direttamente dalla cache, saltando la query al database. return $cached_rules; } // 2. Cache MISS. I dati non sono in cache, quindi eseguiamo la query al database. global $wpdb; $table_name = $wpdb->prefix . 'ncb_arm_redirects'; // La query seleziona solo le regole attive ('off' escluso) e le ordina per // processare prima le wildcard e poi le regole più lunghe (più specifiche). $query = $wpdb->prepare( "SELECT id, url_from, url_to, status, type, query_string_mode FROM {$table_name} WHERE status != %s ORDER BY type DESC, LENGTH(url_from) DESC", 'off' ); $rules = $wpdb->get_results($query); // 3. Salviamo i risultati nella cache per le prossime richieste. // Impostiamo una scadenza (es. 1 ora) come sicurezza, nel caso l'invalidazione fallisca. wp_cache_set( self::CACHE_KEY, $rules, self::CACHE_GROUP, HOUR_IN_SECONDS ); return $rules; } /** * Invalida (cancella) la cache delle regole di reindirizzamento. * Questo metodo statico va chiamato ogni volta che le regole vengono aggiunte, modificate o eliminate. */ public static function invalidate_cache() { wp_cache_delete( self::CACHE_KEY, self::CACHE_GROUP ); } /** * Esegue il reindirizzamento effettivo. * * @param object $rule La regola di reindirizzamento trovata. * @param string $destination_url L'URL di destinazione finale (già processato per le wildcard). */ private function execute_redirect($rule, $destination_url, $request_path = '', $request_query = '', $mode = 'ignore') { global $wpdb; // Incrementa il contatore delle visite (hits) per questa regola. $table_name = $wpdb->prefix . 'ncb_arm_redirects'; $wpdb->query($wpdb->prepare("UPDATE {$table_name} SET count = count + 1, last_hit = %s WHERE id = %d", current_time('mysql'), $rule->id)); $status_code = (int) $rule->status; // Se lo stato è un codice di errore (4xx), invia l'header e termina. if ($status_code === 403 || $status_code === 410 || $status_code === 451) { wp_die( get_status_header_desc($status_code), '', array('response' => $status_code) ); exit(); } // La logica di reindirizzamento rimane la stessa per i codici 3xx. if (is_numeric($destination_url)) { $link = get_permalink((int) $destination_url); if ($link) $destination_url = $link; } if (substr($destination_url, 0, 1) === '/') { $destination_url = home_url($destination_url); } else if (!preg_match("~^(?:f|ht)tps?://~i", $destination_url)) { $destination_url = 'https://' . $destination_url; } if (!in_array($status_code, [301, 302, 307, 308])) { $status_code = 301; } // Gestione Query String. if ( $mode === 'passthrough' ) { $destination_url = $this->append_query_to_url( $destination_url, $request_query ); } elseif ( $mode === 'original_passthrough' || $mode === 'exact_passthrough' ) { $destination_url = $this->merge_query_to_url( $destination_url, $request_query ); } // Evita redirect circolari: blocca solo se destinazione e richiesta sono identiche. if ($request_path !== '') { $request_full = $request_path . ( $request_query !== '' ? '?' . $request_query : '' ); $parsed_dest = wp_parse_url( $destination_url ); $dest_path = isset( $parsed_dest['path'] ) ? $parsed_dest['path'] : '/'; $dest_query = isset( $parsed_dest['query'] ) ? $parsed_dest['query'] : ''; // Normalizza il path della destinazione rimuovendo il base path di WordPress. $home_path = rtrim( (string) wp_parse_url( home_url(), PHP_URL_PATH ), '/' ); if ( $home_path !== '' && strpos( $dest_path, $home_path ) === 0 ) { $dest_path = substr( $dest_path, strlen( $home_path ) ); if ( $dest_path === '' ) { $dest_path = '/'; } } $dest_full = $dest_path . ( $dest_query !== '' ? '?' . $dest_query : '' ); if ( $dest_full === $request_full ) { return; } } wp_redirect(esc_url_raw($destination_url), $status_code); exit(); } /** * Ottiene l'URI della richiesta corrente, pulito dalla query string e normalizzato. * * @return string L'URI della richiesta (es. '/mia-pagina/'). */ private function get_request_uri() { // Sanitize request_uri without decoding, to preserve the original representation. $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : ''; // Rimuove la query string (tutto ciò che viene dopo '?'). $request_uri_path = strtok( $request_uri, '?' ); // Gestisce correttamente le installazioni di WordPress in sottodirectory. $home_path = rtrim( (string) wp_parse_url( home_url(), PHP_URL_PATH ), '/' ); if ( ! empty( $home_path ) && strpos( $request_uri_path, $home_path ) === 0 ) { $request_uri_path = substr( $request_uri_path, strlen( $home_path ) ); } // Assicura che il risultato inizi sempre con uno slash, o sia solo uno slash per la home page. return ( ! empty($request_uri_path) ) ? '/' . ltrim( $request_uri_path, '/' ) : '/'; } /** * Ottiene la query string della richiesta corrente (senza il '?'). * * @return string */ private function get_request_query() { if ( isset( $_SERVER['QUERY_STRING'] ) ) { return wp_unslash( $_SERVER['QUERY_STRING'] ); } $request_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : ''; $parts = explode( '?', $request_uri, 2 ); return isset( $parts[1] ) ? $parts[1] : ''; } /** * Separa percorso e query string da una URL sorgente (supporta URL assoluti). * * @param string $url * @return array{path:string, query:string} */ private function split_rule_url( $url ) { $path = $url; $query = ''; // URL assoluto: estrae il path. if ( preg_match( '~^https?://~i', $url ) ) { $parsed = wp_parse_url( $url ); $path = isset( $parsed['path'] ) ? $parsed['path'] : '/'; $query = isset( $parsed['query'] ) ? $parsed['query'] : ''; // Se il dominio/protocollo/porta coincidono con la richiesta, normalizza il path // rimuovendo il base path di WordPress (es. /wordpress1). $rule_scheme = isset( $parsed['scheme'] ) ? strtolower( $parsed['scheme'] ) : 'http'; $rule_host = isset( $parsed['host'] ) ? strtolower( $parsed['host'] ) : ''; $rule_port = isset( $parsed['port'] ) ? ':' . $parsed['port'] : ''; $rule_origin = $rule_scheme . '://' . $rule_host . $rule_port; $request_origin = ( is_ssl() ? 'https' : 'http' ) . '://' . $request_host; if ( $rule_origin !== '' && $rule_origin === $request_origin ) { $home_path = rtrim( (string) wp_parse_url( home_url(), PHP_URL_PATH ), '/' ); if ( $home_path !== '' && strpos( $path, $home_path ) === 0 ) { $path = substr( $path, strlen( $home_path ) ); if ( $path === '' ) { $path = '/'; } } } } else { $pos = strpos( $url, '?' ); if ( $pos !== false ) { $path = substr( $url, 0, $pos ); $query = substr( $url, $pos + 1 ); } } if ( $path === '' || $path === null ) { $path = '/'; } return array( 'path' => $path, 'query' => $query, ); } /** * Confronta due query string in modo esatto (parametri, valori, ordine, duplicati). * * @param string $rule_query * @param string $request_query * @return bool */ private function query_matches( $rule_query, $request_query ) { return $this->parse_query_pairs( $rule_query ) === $this->parse_query_pairs( $request_query ); } /** * Trasforma una query string in un array ordinato di coppie [chiave, valore]. * * @param string $query * @return array */ private function parse_query_pairs( $query ) { $pairs = array(); $query = ltrim( (string) $query, '?' ); if ( $query === '' ) { return $pairs; } foreach ( explode( '&', $query ) as $part ) { if ( $part === '' ) { continue; } $kv = explode( '=', $part, 2 ); $key = $kv[0]; $val = isset( $kv[1] ) ? $kv[1] : ''; $pairs[] = array( $key, $val ); } return $pairs; } private function append_query_to_url( $url, $query ) { $query = ltrim( (string) $query, '?&' ); if ( $query === '' ) { return $url; } $fragment = ''; if ( strpos( $url, '#' ) !== false ) { $parts = explode( '#', $url, 2 ); $url = $parts[0]; $fragment = '#' . $parts[1]; } $url = rtrim( $url, '?' ); if ( strpos( $url, '?' ) === false ) { $url .= '?' . $query; } else { $url .= '&' . $query; } return $url . $fragment; } /** * Unisce la query originale alla destinazione, con precedenza della destinazione * sui parametri con lo stesso nome. * * @param string $url * @param string $original_query * @return string */ private function merge_query_to_url( $url, $original_query ) { $original_query = ltrim( (string) $original_query, '?&' ); if ( $original_query === '' ) { return $url; } $fragment = ''; if ( strpos( $url, '#' ) !== false ) { $parts = explode( '#', $url, 2 ); $url = $parts[0]; $fragment = '#' . $parts[1]; } $pos = strpos( $url, '?' ); if ( $pos === false ) { $dest_query = ''; $base = $url; } else { $base = substr( $url, 0, $pos ); $dest_query = substr( $url, $pos + 1 ); } // Parse raw segments. $dest_segments = array(); $dest_keys = array(); if ( $dest_query !== '' ) { foreach ( explode( '&', $dest_query ) as $seg ) { if ( $seg === '' ) continue; $kv = explode( '=', $seg, 2 ); $key = $kv[0]; $dest_segments[] = $seg; if ( ! in_array( $key, $dest_keys, true ) ) { $dest_keys[] = $key; } } } $orig_segments = array(); if ( $original_query !== '' ) { foreach ( explode( '&', $original_query ) as $seg ) { if ( $seg === '' ) continue; $kv = explode( '=', $seg, 2 ); $key = $kv[0]; // Only add if key not already present in destination. if ( ! in_array( $key, $dest_keys, true ) ) { $orig_segments[] = $seg; $dest_keys[] = $key; } } } $all = array_merge( $dest_segments, $orig_segments ); if ( empty( $all ) ) { return $base . $fragment; } return $base . '?' . implode( '&', $all ) . $fragment; } }