fix ghost columns

This commit is contained in:
2026-06-22 20:43:52 +03:00
parent 089886cb9a
commit ff61456d91
+74 -22
View File
@@ -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']]);