export_to_csv(); break; case 'import_csv': // Inizializza le variabili con valori di default. $tmp_name = ''; $error = UPLOAD_ERR_NO_FILE; $type = ''; // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized // 1. Controlla e spacchetta l'array $_FILES in modo difensivo. if ( isset( $_FILES['ncb_arm_import_file'] ) && is_array( $_FILES['ncb_arm_import_file'] ) ) { $file_data = $_FILES['ncb_arm_import_file']; // Estrai i valori solo se le chiavi esistono. $tmp_name = isset( $file_data['tmp_name'] ) ? $file_data['tmp_name'] : ''; $error = isset( $file_data['error'] ) ? (int) $file_data['error'] : UPLOAD_ERR_NO_FILE; $type = isset( $file_data['type'] ) ? $file_data['type'] : ''; } // 2. Esegui la validazione di sicurezza. Se fallisce, esci. if ( UPLOAD_ERR_OK !== $error || empty( $tmp_name ) || ! is_uploaded_file( $tmp_name ) ) { break; } // 3. Se siamo qui, $tmp_name è stato validato. Procedi in sicurezza. $file_tmp_name = realpath( $tmp_name ); if ( false === $file_tmp_name ) { break; // Il percorso non è valido } // Sanitizza il tipo MIME (ora che sappiamo che i dati sono validi). $sanitized_type = sanitize_mime_type( $type ); // Sanitizza le opzioni dal POST. $import_duplicates_option = isset( $_POST['ncb_arm_import_duplicates'] ) ? sanitize_key( wp_unslash( $_POST['ncb_arm_import_duplicates'] ) ) : 'skip'; $update_duplicates = ( 'update' === $import_duplicates_option ); // Chiama la funzione di import con dati sicuri e validati. $this->import_from_csv( $file_tmp_name, $sanitized_type, $update_duplicates ); break; case 'reset_hits': $this->reset_all_hit_counts(); add_action('admin_notices', function() { echo '

' . esc_html__( 'All redirect hit counts have been reset.', 'advanced-redirect-manager' ) . '

'; }); break; case 'delete_all': $this->delete_all_rules(); add_action('admin_notices', function() { echo '

' . esc_html__( 'All redirect rules have been deleted.', 'advanced-redirect-manager' ) . '

'; }); break; } } } // Gestione nonce per la pulizia del log 404 if ( isset( $_POST['ncb_arm_clear_404_log_nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['ncb_arm_clear_404_log_nonce'] ) ), 'ncb_arm_clear_404_log_action' ) ) { if (!current_user_can('manage_options')) return; if (isset($_POST['ncb_arm_action']) && 'clear_404_log' === sanitize_key($_POST['ncb_arm_action'])) { global $wpdb; $table_name = $wpdb->prefix . 'ncb_arm_404_log'; $wpdb->query("TRUNCATE TABLE {$table_name}"); $redirect_url = add_query_arg( [ 'page' => 'advanced-redirect-manager', 'tab' => '404_log', 'arm-message' => 'log-cleared', ], admin_url( 'options-general.php' ) ); $nonce_redirect_url = wp_nonce_url( $redirect_url, 'ncb_arm_message_nonce' ); wp_safe_redirect( $nonce_redirect_url ); exit; } } } private function export_to_csv() { global $wpdb; $table_name = $wpdb->prefix . 'ncb_arm_redirects'; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching $redirects = $wpdb->get_results( "SELECT id, url_from, url_to, status, type, count, last_hit, created_at FROM {$table_name} ORDER BY id ASC", ARRAY_A ); if ( empty( $redirects ) ) { add_action( 'admin_notices', function() { echo '

' . esc_html__( 'There are no redirect rules to export.', 'advanced-redirect-manager' ) . '

'; } ); return; } $filename = 'ncb_arm_redirects_export_' . gmdate( 'Y-m-d' ) . '.csv'; // 1. Costruisci l'intero contenuto del CSV in una stringa. $csv_output = ''; // BOM per compatibilità con Excel $csv_output .= "\xEF\xBB\xBF"; // Riga degli header $csv_rows[] = $this->generate_csv_row( array_keys( $redirects[0] ) ); // Righe di dati foreach ( $redirects as $row ) { // Non applichiamo esc_attr qui perché generate_csv_row gestisce già le virgolette, // che è l'unico escaping necessario per il formato CSV. $csv_rows[] = $this->generate_csv_row( $row ); } $csv_output .= implode( '', $csv_rows ); // 2. Imposta gli header per il download. // È importante farlo subito prima dell'output. header( 'Content-Type: text/csv; charset=utf-8' ); header( 'Content-Disposition: attachment; filename="' . $filename . '";' ); header( 'Content-Length: ' . strlen( $csv_output ) ); header( 'Pragma: no-cache' ); header( 'Expires: 0' ); // 3. Pulisci qualsiasi output buffer precedente. if ( ob_get_level() > 0 ) { ob_end_clean(); } // 4. Stampa l'output e termina. // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $csv_output; exit; } /** * Helper function to generate a CSV-formatted row. * Handles enclosing fields in quotes if they contain commas or quotes. * * @param array $row The data row. * @return string The CSV-formatted row string. */ private function generate_csv_row( $row ) { $row_items = []; foreach ( $row as $item ) { // Pulisce il campo da virgolette doppie $item = str_replace( '"', '""', $item ); // Racchiude il campo tra virgolette se contiene una virgola, virgolette, o va a capo if ( preg_match( '/[",\r\n]/', $item ) ) { $item = '"' . $item . '"'; } $row_items[] = $item; } return implode( ',', $row_items ) . "\n"; } /** * Imports redirect rules from a CSV file. * * @param string $file_path The temporary path to the uploaded file. * @param string $file_type The sanitized mime type of the file. * @param bool $update_duplicates Whether to update rules if a duplicate is found. */ private function import_from_csv( $file_path, $file_type, $update_duplicates ) { global $wp_filesystem; if ( ! $this->init_filesystem() ) { add_action( 'admin_notices', function() { echo '

' . esc_html__( 'Error: Could not initialize filesystem.', 'advanced-redirect-manager' ) . '

'; } ); return; } if ( ! in_array( $file_type, [ 'text/csv', 'application/vnd.ms-excel', 'application/csv', 'text/plain' ], true ) ) { add_action( 'admin_notices', function() { echo '

' . esc_html__( 'Error: Please upload a valid CSV file.', 'advanced-redirect-manager' ) . '

'; } ); return; } $csv_content = $wp_filesystem->get_contents( $file_path ); if ( false === $csv_content ) { add_action( 'admin_notices', function() { echo '

' . esc_html__( 'Error: Could not read the uploaded file.', 'advanced-redirect-manager' ) . '

'; } ); return; } $lines = explode( "\n", trim( $csv_content ) ); if ( empty( $lines ) ) { return; } $lines[0] = preg_replace( '/^' . pack( 'H*', 'EFBBBF' ) . '/', '', $lines[0] ); $headers_line = array_shift( $lines ); $headers = str_getcsv( $headers_line, ',', '"', '\\' ); $required_headers = [ 'url_from', 'url_to' ]; if ( count( array_intersect( $required_headers, $headers ) ) !== count( $required_headers ) ) { add_action( 'admin_notices', function() { echo '

' . esc_html__( 'Error: CSV file must contain "url_from" and "url_to" columns.', 'advanced-redirect-manager' ) . '

'; } ); return; } $imported_count = 0; $updated_count = 0; $skipped_count = 0; global $wpdb; $table_name = $wpdb->prefix . 'ncb_arm_redirects'; foreach ( $lines as $line ) { if ( empty( trim( $line ) ) ) { continue; } $data = str_getcsv( $line, ',', '"', '\\' ); if ( count( $headers ) > count( $data ) ) { $data = array_pad( $data, count( $headers ), '' ); } if ( count( $headers ) !== count( $data ) ) { $skipped_count++; continue; } $row = array_combine( $headers, $data ); $url_from = isset($row['url_from']) ? sanitize_text_field(trim($row['url_from'])) : ''; $url_to = isset($row['url_to']) ? sanitize_text_field(trim($row['url_to'])) : ''; if ( empty( $url_from ) || empty( $url_to ) ) { $skipped_count++; continue; } $normalized_url_from = '/' . ltrim( $url_from, '/' ); $rule_data = [ 'url_from' => $normalized_url_from, 'url_to' => $url_to, 'status' => isset($row['status']) && in_array($row['status'], ['301', '302', '307', '308', 'off'], true) ? $row['status'] : '301', 'type' => strpos($normalized_url_from, '*') !== false ? 'wildcard' : 'url', 'count' => isset($row['count']) && is_numeric($row['count']) ? absint($row['count']) : 0, 'last_hit' => isset($row['last_hit']) && strtotime($row['last_hit']) ? gmdate('Y-m-d H:i:s', strtotime($row['last_hit'])) : '0000-00-00 00:00:00', 'created_at' => isset($row['created_at']) && strtotime($row['created_at']) ? gmdate('Y-m-d H:i:s', strtotime($row['created_at'])) : current_time('mysql'), ]; $existing_id = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$table_name} WHERE url_from = %s", $normalized_url_from ) ); if ( $existing_id ) { if ( $update_duplicates ) { $wpdb->update( $table_name, $rule_data, [ 'id' => $existing_id ] ); $updated_count++; } else { $skipped_count++; } } else { $wpdb->insert( $table_name, $rule_data ); $imported_count++; } } NCB_ARM_Redirect_Handler::invalidate_cache(); add_action('admin_notices', function() use ( $imported_count, $updated_count, $skipped_count ) { /* translators: 1: Number of rules created. 2: Number of rules updated. 3: Number of rules skipped. */ $translated_text = esc_html__( '%1$d rules created, %2$d rules updated, %3$d rules skipped.', 'advanced-redirect-manager' ); $message = sprintf( $translated_text, $imported_count, $updated_count, $skipped_count ); $class = ( $imported_count > 0 || $updated_count > 0 ) ? 'notice-success' : 'notice-warning'; echo '

' . esc_html( $message ) . '

'; }); } private function reset_all_hit_counts() { global $wpdb; $table_name = $wpdb->prefix . 'ncb_arm_redirects'; $wpdb->query("UPDATE {$table_name} SET count = 0, last_hit = '0000-00-00 00:00:00'"); NCB_ARM_Redirect_Handler::invalidate_cache(); } private function delete_all_rules() { global $wpdb; $table_name = $wpdb->prefix . 'ncb_arm_redirects'; $wpdb->query("TRUNCATE TABLE {$table_name}"); NCB_ARM_Redirect_Handler::invalidate_cache(); } private function init_filesystem() { if ( ! empty( $GLOBALS['wp_filesystem'] ) ) { return true; } require_once( ABSPATH . 'wp-admin/includes/file.php' ); // Passiamo le credenziali (se necessarie) e tentiamo l'inizializzazione. // Se fallisce, WordPress chiederà le credenziali all'utente. if ( WP_Filesystem() ) { return true; } else { // Se l'inizializzazione automatica fallisce, proviamo a forzare una richiesta di credenziali. // Questo è necessario su alcuni server mal configurati. request_filesystem_credentials( admin_url() ); return false; } } }