fix(AcaiAPI): fecha vacia, sellado de auditoria y escapado de ids

1) Un campo `date` con valor vacio rompia TODA insercion en la tabla.

   MySQL rechaza la cadena vacia en una columna datetime ("Incorrect datetime
   value") tanto si es NOT NULL como si es nullable, y bajo el sql_mode
   TRADITIONAL que fuerza Db.php no hay ningun valor "vacio" valido: '' falla,
   NULL falla si la columna es NOT NULL, '0000-00-00' falla por NO_ZERO_DATE y
   omitir la columna falla por no tener default. Bastaba UN campo fecha para que
   dejara de entrar cualquier registro, con un 400 "Error al insertar" sin
   detalle.

   El Admin clasico nunca se topa con esto porque ya lo resuelve:
   menus/default/save.php:287 pone la fecha actual cuando el campo no viene
   ("default date fields to current date"). AcaiAPI nunca replico esa regla —
   coerceValue tiene casos para checkbox, list y multitext, pero no para date.
   Ahora en INSERT hace lo mismo que el Admin; en UPDATE omite la columna, para
   no pisar con "hoy" un valor ya guardado solo porque venga la clave vacia.

   Se descarto la alternativa de crear la columna como NULL DEFAULT NULL: no
   arregla ninguna de las tablas ya existentes (el sync copia ficheros, no
   ejecuta ALTER) y ademas '' seguiria fallando igual.

2) Auditoria: `updatedDate`/`updatedByUserNum` pasan a respetar el valor que
   traiga el caller y generarse solo si no viene. Antes se pisaban siempre
   ("not overridable by the caller"). Alineado con la intencion del cambio de
   Anael en core_client, pero con los guards FUERA del if ($isInsert): dentro,
   un UPDATE se quedaba sin sellar la fecha porque no actuaba ninguna rama.
   No hay doble escritura: cuando el caller lo trae, lo escribe el bucle de
   campos y el guard evita que el bloque de auditoria lo repita en el SET.

3) Seguridad: los ids de las relaciones se escapan antes de interpolarlos en el
   IN (...). Salen del valor guardado de un campo `list`, que es un mediumtext
   sin validacion, asi que era una inyeccion SQL de segundo orden. Ya estaba
   corregido en core_client y faltaba aqui.

Ref: docs/escalados/estado.md 3.1 (repo acai-vscode-plugin)
This commit is contained in:
Jordan Diaz
2026-08-25 15:06:21 +00:00
parent 5861bac1da
commit 6397abfabb
+29 -2
View File
@@ -1018,7 +1018,11 @@ class AcaiAPI
foreach ($pools as &$pool) { foreach ($pools as &$pool) {
$ids = array_keys($pool['ids']); $ids = array_keys($pool['ids']);
if (!$ids) continue; if (!$ids) continue;
$related = self::get($pool['table'], $pool['valueField'] . ' IN (' . implode(',', $ids) . ')', null, null, [ // Los ids salen del valor guardado del campo `list` (mediumtext), asi
// que se escapan antes de interpolarlos: sin esto es una inyeccion SQL
// de segundo orden. Alineado con core_client.
$ids = array_map(function ($id) { return mysql_real_escape_string((string) $id); }, $ids);
$related = self::get($pool['table'], $pool['valueField'] . " IN ('" . implode("','", $ids) . "')", null, null, [
'relationsDepth' => $subDepth, 'relationsDepth' => $subDepth,
'translates' => $options['translates'], 'translates' => $options['translates'],
'uploads' => $options['uploads'], 'uploads' => $options['uploads'],
@@ -1381,8 +1385,14 @@ class AcaiAPI
// traducciones, aux_*...) no tienen columnas de auditoria y el // traducciones, aux_*...) no tienen columnas de auditoria y el
// INSERT/UPDATE reventaria con "Unknown column". Misma semantica que // INSERT/UPDATE reventaria con "Unknown column". Misma semantica que
// CocoDB legacy (prepareBaseSQL solo añadia estos campos con schema). // CocoDB legacy (prepareBaseSQL solo añadia estos campos con schema).
if (!$ignoreSchema) { // El caller manda si trae el valor; si no, se genera. Aplica a INSERT y a
// UPDATE: un update sin updatedDate explicita tiene que sellar la fecha.
// (El bucle de campos de abajo escribe el valor del caller cuando viene,
// asi que el guard evita ponerlo dos veces en el SET.)
if (!$ignoreSchema && !isset($record['updatedDate'])) {
$setParts[] = '`updatedDate` = ?'; $params[] = $now; $setParts[] = '`updatedDate` = ?'; $params[] = $now;
}
if (!$ignoreSchema && !isset($record['updatedByUserNum'])) {
$setParts[] = '`updatedByUserNum` = ?'; $params[] = $user; $setParts[] = '`updatedByUserNum` = ?'; $params[] = $user;
} }
@@ -1456,6 +1466,23 @@ class AcaiAPI
$value = self::coerceValue($value, $schemaField); $value = self::coerceValue($value, $schemaField);
} }
// Un campo `date` NUNCA acepta cadena vacia: MySQL la rechaza con
// "Incorrect datetime value" tanto si la columna es NOT NULL como si
// es nullable. El Admin clasico ya lo resuelve poniendo la fecha
// actual cuando el campo no viene (menus/default/save.php:287,
// "default date fields to current date"), pero AcaiAPI nunca replico
// esa regla: por eso un alta con la fecha vacia moria con un 400
// "Error al insertar" sin ningun detalle, y bastaba UN campo fecha
// para que dejara de entrar cualquier registro en la tabla.
// - INSERT: mismo comportamiento que el Admin -> fecha actual.
// - UPDATE: se omite la columna, para no pisar con "hoy" un valor
// ya guardado solo porque el caller mando la clave vacia.
if ($schemaField && isset($schemaField['type']) && $schemaField['type'] === 'date'
&& ($value === null || (is_string($value) && trim($value) === ''))) {
if (!$isInsert) continue;
$value = $now;
}
if ($value === null) continue; if ($value === null) continue;
if (is_array($value)) $value = json_encode($value); if (is_array($value)) $value = json_encode($value);