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;
}
}
}