table_name = $wpdb->prefix . 'ncb_arm_404_log'; $this->maybe_add_ip_column(); add_action( 'admin_post_ncb_arm_save_404_logging', array( $this, 'save_logging_setting' ) ); } /** * Salva la preferenza di logging 404. */ public function save_logging_setting() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( 'Permission denied.' ); } check_admin_referer( 'ncb_arm_404_logging', 'ncb_arm_404_logging_nonce' ); $enabled = isset( $_POST['ncb_arm_404_logging_enabled'] ) ? 1 : 0; update_option( 'ncb_arm_404_logging_enabled', $enabled, false ); wp_safe_redirect( add_query_arg( array( 'page' => 'advanced-redirect-manager', 'tab' => '404_log', 'arm-message' => 'logging-saved' ), admin_url( 'options-general.php' ) ) ); exit; } /** * Ensure the IP column exists for older installs. */ private function maybe_add_ip_column() { global $wpdb; if ( get_option( 'ncb_arm_404_log_has_ip', false ) ) { return; } $table_exists = $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $this->table_name ) ) === $this->table_name; if ( ! $table_exists ) { return; } $col = $wpdb->get_results( "SHOW COLUMNS FROM {$this->table_name} LIKE 'ip'" ); if ( empty( $col ) ) { $wpdb->query( "ALTER TABLE {$this->table_name} ADD COLUMN ip VARCHAR(45) NOT NULL DEFAULT ''" ); } update_option( 'ncb_arm_404_log_has_ip', 1, false ); } /** * Controlla se la pagina corrente è un errore 404 e, in caso affermativo, la registra. * * Questa funzione si aggancia a 'template_redirect'. * * @since 1.0.0 */ public function log_404_error() { // Se non è una pagina 404, non faccio nulla. // Se il logging 404 è disattivato, non registrare. if ( ! get_option( 'ncb_arm_404_logging_enabled', 1 ) ) { return; } if ( ! is_404() ) { return; } // Escludo richieste comuni di file che non esistono (es. .map, favicon.ico) // per mantenere il log pulito da rumore inutile generato da bot e browser. if ( $this->is_common_file_request() ) { return; } // Preparo i dati da inserire nel database. $log_entry = array( 'url' => $this->get_full_request_uri(), 'timestamp' => current_time( 'mysql' ), // Uso il formato datetime di MySQL, più efficiente. 'user_agent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : 'N/A', 'referrer' => isset( $_SERVER['HTTP_REFERER'] ) ? esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : 'N/A', 'ip' => $this->get_client_ip(), ); $this->add_entry_to_log( $log_entry ); } /** * Aggiunge una nuova voce alla tabella dei log 404 e gestisce la rotazione del log. * * @since 1.0.1 (Riscritto per usare la tabella custom) * @param array $new_entry La nuova voce di log da aggiungere. */ private function add_entry_to_log( $new_entry ) { global $wpdb; // Inserisco la nuova riga. È un'operazione semplice e veloce. $wpdb->insert( $this->table_name, $new_entry ); // Ora gestisco la rotazione del log per evitare che la tabella cresca all'infinito. // Recupero il limite impostato dall'utente (con un default di 200). $limit = (int) get_option( self::LOG_LIMIT_OPTION_NAME, 200 ); $limit = apply_filters( 'ncb_arm_404_log_limit', $limit ); $limit = max(50, $limit); // Imposto un limite minimo ragionevole di 50 voci. // Per non appesantire ogni singola richiesta 404 con un controllo, // eseguo la pulizia solo occasionalmente (in media 1 volta ogni 20 errori). // Questo bilancia perfettamente performance e manutenzione del database. if ( wp_rand( 1, 20 ) === 1 ) { // Conto le righe totali nella tabella. $count = $wpdb->get_var( "SELECT COUNT(id) FROM {$this->table_name}" ); // Se il numero di righe supera il limite, cancello le più vecchie. if ( $count > $limit ) { $ids_to_delete = $wpdb->get_col( $wpdb->prepare( "SELECT id FROM {$this->table_name} ORDER BY timestamp ASC LIMIT %d", $count - $limit ) ); if ( ! empty( $ids_to_delete ) ) { $ids_to_delete_safe = array_map( 'absint', $ids_to_delete ); // 1. Crea la stringa di placeholder (es. '%d, %d, %d') $id_placeholders = implode( ', ', array_fill( 0, count( $ids_to_delete_safe ), '%d' ) ); // 2. Costruisci la query SQL completa usando la stringa di placeholder $sql = "DELETE FROM {$this->table_name} WHERE id IN ( $id_placeholders )"; // 3. Esegui la query passando la stringa SQL completa e i valori $wpdb->query( $wpdb->prepare( $sql, $ids_to_delete_safe ) ); } } } } /** * Funzione helper per ottenere l'URI completo della richiesta, incluso il dominio. * * @since 1.0.0 * @return string L'URL completo della richiesta. */ private function get_full_request_uri() { $protocol = ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http' ); // Sanitize host e request_uri $host = isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : ''; $uri = isset( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; return "{$protocol}://{$host}{$uri}"; } /** * Controlla se la richiesta è per un tipo di file comune ma spesso inesistente * che vogliamo escludere dal log 404 per evitare rumore. * * @since 1.0.0 * @return bool True se la richiesta va ignorata, false altrimenti. */ private function get_client_ip() { if ( ! empty( $_SERVER['HTTP_CF_CONNECTING_IP'] ) ) { return sanitize_text_field( wp_unslash( $_SERVER['HTTP_CF_CONNECTING_IP'] ) ); } if ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) { $ips = explode( ',', wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ); return sanitize_text_field( trim( $ips[0] ) ); } if ( ! empty( $_SERVER['REMOTE_ADDR'] ) ) { return sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ); } return 'N/A'; } private function is_common_file_request() { if ( empty( $_SERVER['REQUEST_URI'] ) ) { return false; } // Sanitize request_uri $request_uri = strtolower( esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ); $common_misses = array( '.css.map', '.js.map', 'favicon.ico', 'apple-touch-icon.png', 'browserconfig.xml', 'wlwmanifest.xml', 'xmlrpc.php', 'wp-login.php', // Spesso bersaglio di bot ); foreach ( $common_misses as $miss ) { if ( strpos( $request_uri, $miss ) !== false ) { return true; } } return false; } }