From 184038b7860410637a1e9eb56898e88fc955f01f Mon Sep 17 00:00:00 2001 From: Jordan Diaz Date: Thu, 24 Sep 2026 00:17:04 +0000 Subject: [PATCH] fix(core): una fecha ausente solo se rellena si la columna es NOT NULL sin DEFAULT --- cms/lib/classes/AcaiAPI.php | 60 ++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/cms/lib/classes/AcaiAPI.php b/cms/lib/classes/AcaiAPI.php index 587a860..f6a2451 100644 --- a/cms/lib/classes/AcaiAPI.php +++ b/cms/lib/classes/AcaiAPI.php @@ -87,9 +87,13 @@ class AcaiAPI $lastId = 0; $count = 0; + // Columnas que MySQL exige en un INSERT (NOT NULL sin DEFAULT): solo a + // esas se les pone valor si no vienen. null = no se pudo saber. + $requiredCols = $ignoreSchema ? [] : self::requiredColumns($db, $prefix . $table); + foreach ($records as $record) { list($setParts, $params, $uploadFields) = self::buildSetClause( - $record, $schema, $functions, $ignoreSchema, true, $forceNum + $record, $schema, $functions, $ignoreSchema, true, $forceNum, $requiredCols ); if (empty($setParts)) continue; @@ -1366,7 +1370,8 @@ class AcaiAPI array $functions, $ignoreSchema, $isInsert, - $forceNum + $forceNum, + $requiredCols = null ) { $now = date('Y-m-d H:i:s'); $user = 1; @@ -1439,15 +1444,21 @@ class AcaiAPI $params[] = time(); } - // Campos `date` que NO vienen en el alta -> fecha actual, como el - // Admin clasico (save.php: "default date fields to current date"). - // El bloque de campos de usuario, abajo, solo cubre la fecha que - // llega VACIA; si la clave falta, una columna `datetime NOT NULL` - // sin DEFAULT (cms_vacantes.fecha_publicacion) tumbaba el INSERT. - if (!$ignoreSchema && is_array($schema)) { + // Campos `date` que NO vienen en el alta -> fecha actual, SOLO si la + // columna es NOT NULL sin DEFAULT (MySQL rechazaria el INSERT sin + // ella: cms_vacantes.fecha_publicacion). En una columna que admite + // NULL, vacio SIGNIFICA algo (notas_internas.deleted_at = no + // borrada, cms_users.fecha_caducidad = no caduca): rellenarla con + // "ahora" marcaba cada nota nueva como borrada (regresion de + // 14ddf16). Si no se sabe que columnas exige ($requiredCols null) + // no se rellena nada: mejor un error claro que un dato falso. Un + // `date` del schema sin columna real tampoco entra (no esta en la + // lista). + if (!$ignoreSchema && is_array($schema) && is_array($requiredCols)) { foreach ($schema as $field => $def) { if (array_key_exists($field, $record)) continue; if (!is_array($def) || ($def['type'] ?? '') !== 'date') continue; + if (!isset($requiredCols[$field])) continue; $setParts[] = "`$field` = ?"; $params[] = $now; } @@ -1661,6 +1672,39 @@ class AcaiAPI * reintentaba a ciegas. isset(): una web con Db.php personalizado (sin * lastError) sigue funcionando, solo que sin el detalle. */ + /** Cache por peticion: tabla completa => [columna => true]. */ + private static $requiredColsCache = []; + + /** + * Columnas de `$fullTable` que un INSERT tiene que traer: NOT NULL, sin + * DEFAULT y no auto_increment. Devuelve [columna => true], o null si no se + * pudo consultar (el llamador no rellena nada en ese caso). Solo usa + * Db::executeS, que existe en todas las versiones de Db.php. + */ + private static function requiredColumns($db, $fullTable) + { + if (array_key_exists($fullTable, self::$requiredColsCache)) { + return self::$requiredColsCache[$fullTable]; + } + try { + $rows = $db->executeS( + "SELECT COLUMN_NAME FROM information_schema.COLUMNS" + . " WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?" + . " AND IS_NULLABLE = 'NO' AND COLUMN_DEFAULT IS NULL" + . " AND EXTRA NOT LIKE '%auto_increment%'", + [$fullTable] + ); + } catch (\Throwable $e) { + return self::$requiredColsCache[$fullTable] = null; + } + $cols = []; + foreach ((array) $rows as $row) { + $name = is_array($row) ? ($row['COLUMN_NAME'] ?? reset($row)) : null; + if ($name) $cols[$name] = true; + } + return self::$requiredColsCache[$fullTable] = $cols; + } + private static function dbErrorSuffix($db) { return (is_object($db) && isset($db->lastError) && $db->lastError !== '')