diff --git a/public/userarea/process_import_xls2.php b/public/userarea/process_import_xls2.php index 407ea08..25e4bd2 100644 --- a/public/userarea/process_import_xls2.php +++ b/public/userarea/process_import_xls2.php @@ -53,7 +53,44 @@ function normalizeColumnIndex($value): int return 1; } +/** + * Trim a cell value treating invisible/Unicode spaces as empty. + * PHP's native trim() strips only ASCII whitespace (" \t\n\r\0\x0B"), so a cell + * that contains only a non-breaking space (U+00A0), zero-width space (U+200B), + * BOM (U+FEFF) or another Unicode space looks blank to a human but would still + * count as "filled" — pulling a ghost column into the import or retaining a + * visually-empty row. Normalize those to a real space before trimming. + */ +function cleanCellText($value): string +{ + $raw = (string)$value; + $cleaned = preg_replace( + '/[\x{00A0}\x{200B}\x{FEFF}\x{2000}-\x{200A}\x{202F}\x{205F}\x{3000}]+/u', + ' ', + $raw + ); + + // preg_replace returns null on malformed UTF-8; fall back to the raw value. + return trim($cleaned ?? $raw); +} + try { + // Quando il body POST supera post_max_size, PHP scarta $_POST e $_FILES + // (warning "Content-Length exceeds the limit ... in Unknown on line 0") e lo + // script riceve una richiesta vuota. Lo intercettiamo per dare un messaggio + // chiaro invece di "Richiesta non valida". + if ( + $_SERVER['REQUEST_METHOD'] === 'POST' + && empty($_POST) && empty($_FILES) + && (int)($_SERVER['CONTENT_LENGTH'] ?? 0) > 0 + ) { + $postMax = ini_get('post_max_size'); + throw new Exception( + "Il file caricato supera il limite di upload del server (post_max_size = {$postMax}). " . + "Chiedi all'amministratore di aumentare post_max_size e upload_max_filesize." + ); + } + if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['excel_file'])) { $template_id = isset($_POST['template_id']) ? intval($_POST['template_id']) : 0; @@ -70,7 +107,7 @@ try { * Così non dipendiamo solo dal form e siamo sicuri di usare i dati salvati. */ $stmt = $pdo->prepare(" - SELECT + SELECT id, header_row, start_column, @@ -142,14 +179,14 @@ try { // Recupera il mapping da template_mapping $stmt = $pdo->prepare(" - SELECT - field_id AS excel_column, - field_id AS mysql_column, - data_type, - is_required, - default_value, - is_manual - FROM template_mapping + SELECT + field_id AS excel_column, + field_id AS mysql_column, + data_type, + is_required, + default_value, + is_manual + FROM template_mapping WHERE template_id = ? "); $stmt->execute([$template_id]); @@ -162,7 +199,9 @@ try { $response['error'] = "Nessun mapping trovato per il template con ID $template_id"; } else { // Carica il file rinominato con PHPSpreadsheet - $spreadsheet = IOFactory::load($destination); + $reader = IOFactory::createReaderForFile($destination); + $reader->setReadEmptyCells(false); + $spreadsheet = $reader->load($destination); $sheetCount = $spreadsheet->getSheetCount(); $sheetNames = $spreadsheet->getSheetNames(); @@ -189,8 +228,8 @@ try { error_log("Selected XLS sheet - index: {$xlsSheetIndex}, name: {$selectedSheetName}"); - $highestRow = $worksheet->getHighestRow(); - $highestColumn = $worksheet->getHighestColumn(); + $highestRow = $worksheet->getHighestDataRow(); + $highestColumn = $worksheet->getHighestDataColumn(); $highestColumnIndex = Coordinate::columnIndexFromString($highestColumn); $startRow = max(1, $header_row); @@ -199,7 +238,7 @@ try { // Advance startColumn to first non-empty cell in header row, matching JS behavior for ($sc = $startColumn; $sc <= $highestColumnIndex; $sc++) { $cl = Coordinate::stringFromColumnIndex($sc); - $cv = trim((string)($worksheet->getCell($cl . $header_row)->getCalculatedValue() ?? '')); + $cv = cleanCellText($worksheet->getCell($cl . $header_row)->getCalculatedValue() ?? ''); if ($cv !== '') { $startColumn = $sc; @@ -207,6 +246,19 @@ try { } } + $lastHeaderCol = $startColumn; + for ($hc = $startColumn; $hc <= $highestColumnIndex; $hc++) { + $hl = Coordinate::stringFromColumnIndex($hc); + $hv = cleanCellText($worksheet->getCell($hl . $header_row)->getCalculatedValue() ?? ''); + + if ($hv !== '') { + $lastHeaderCol = $hc; + } + } + + $highestColumnIndex = $lastHeaderCol; + $highestColumn = Coordinate::stringFromColumnIndex($highestColumnIndex); + // Debug dei parametri error_log( "Processing - template_id: $template_id, " . @@ -269,7 +321,7 @@ try { $columnLetter = Coordinate::stringFromColumnIndex($physCol); $cell = $worksheet->getCell($columnLetter . $header_row); - $cellValue = trim((string)($cell ? $cell->getCalculatedValue() : '')); + $cellValue = cleanCellText($cell ? $cell->getCalculatedValue() : ''); $cellValue = preg_replace('/[\r\n\t]+/', ' ', $cellValue); // Empty headers get __empty_N__ to match mapping page @@ -306,7 +358,7 @@ try { $filledCount = 0; foreach ($headerFilledIndices as $idx) { - if (isset($rowData[$idx]) && trim((string)$rowData[$idx]) !== '') { + if (isset($rowData[$idx]) && cleanCellText($rowData[$idx]) !== '') { $filledCount++; } } @@ -322,13 +374,13 @@ try { // Recupera routine dal template if ($template && !empty($template['idroutine'])) { $stmtRoutine = $pdo->prepare(" - SELECT - idroutine, - name, - filename, - headerrow, - instruction - FROM routine + SELECT + idroutine, + name, + filename, + headerrow, + instruction + FROM routine WHERE idroutine = ? "); $stmtRoutine->execute([$template['idroutine']]);