feat(core): regla unica de valores por defecto en los INSERT (AcaiAPI::insertDefaults)
This commit is contained in:
+114
-23
@@ -1444,25 +1444,6 @@ class AcaiAPI
|
||||
$params[] = time();
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── User-supplied fields ─────────────────────────────────────────
|
||||
@@ -1502,9 +1483,14 @@ class AcaiAPI
|
||||
// - 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.
|
||||
// - Pero SOLO si la columna la exige (NOT NULL sin DEFAULT). En una
|
||||
// que admite NULL, vacio significa "sin fecha": se omite y queda
|
||||
// NULL (antes quedaba "ahora": una nota con deleted_at vacio
|
||||
// nacia borrada). Si no se sabe ($requiredCols null), como antes.
|
||||
if ($schemaField && isset($schemaField['type']) && $schemaField['type'] === 'date'
|
||||
&& ($value === null || (is_string($value) && trim($value) === ''))) {
|
||||
if (!$isInsert) continue;
|
||||
if (is_array($requiredCols) && !isset($requiredCols[$key])) continue;
|
||||
$value = $now;
|
||||
}
|
||||
|
||||
@@ -1515,9 +1501,111 @@ class AcaiAPI
|
||||
$params[] = $value;
|
||||
}
|
||||
|
||||
// ── Valores por defecto del INSERT (regla UNICA) ──────────────────
|
||||
if ($isInsert && !$ignoreSchema && $schema) {
|
||||
list($defParts, $defParams) = self::insertDefaults(
|
||||
$setParts, $schema, $requiredCols, $now);
|
||||
$setParts = array_merge($setParts, $defParts);
|
||||
$params = array_merge($params, $defParams);
|
||||
}
|
||||
|
||||
return [$setParts, $params, $uploadFields];
|
||||
}
|
||||
|
||||
/**
|
||||
* Valores de las columnas que un INSERT no trae. UNICA fuente de la regla
|
||||
* (antes repartida entre el Admin clasico, el server Python y aqui):
|
||||
*
|
||||
* 1. Default EXPLICITO del schema, siempre que el campo falte: checkbox
|
||||
* marcado por defecto -> checkedValue; defaultContent (textbox,
|
||||
* wysiwyg, codigo) o defaultValue. Nunca se evalua como PHP: un
|
||||
* default con `<?` se ignora.
|
||||
* 2. Sin default explicito, SOLO si la columna es NOT NULL sin DEFAULT
|
||||
* (MySQL rechazaria el INSERT): checkbox -> uncheckedValue; si no, por
|
||||
* tipo de columna (fecha -> ahora, numero -> 0, enum -> primer valor,
|
||||
* resto -> ''), como el Admin clasico. Incluye columnas obligatorias
|
||||
* que el schema no declara (p.ej. flags de `accounts`).
|
||||
* 3. Nada mas: una columna que admite NULL o tiene DEFAULT se deja a MySQL
|
||||
* (vacio puede significar algo: deleted_at = no borrada).
|
||||
*
|
||||
* $requiredCols null = no se pudo consultar la tabla: solo se aplica (1).
|
||||
*/
|
||||
private static function insertDefaults(array $setParts, array $schema, $requiredCols, $now)
|
||||
{
|
||||
$present = [];
|
||||
foreach ($setParts as $part) {
|
||||
if (preg_match('/^`([^`]+)`/', $part, $m)) $present[$m[1]] = true;
|
||||
}
|
||||
$parts = [];
|
||||
$params = [];
|
||||
$add = function ($col, $val) use (&$parts, &$params, &$present) {
|
||||
$parts[] = "`$col` = ?";
|
||||
$params[] = $val;
|
||||
$present[$col] = true;
|
||||
};
|
||||
|
||||
foreach ($schema as $field => $def) {
|
||||
if (!is_array($def) || isset($present[$field])) continue;
|
||||
$type = isset($def['type']) ? $def['type'] : '';
|
||||
if (in_array($type, ['upload', 'separator', 'none'], true)) continue;
|
||||
if (!empty($def['isSystemField'])) continue;
|
||||
|
||||
$explicit = self::schemaDefault($def);
|
||||
if ($explicit !== null) {
|
||||
$add($field, $explicit);
|
||||
} elseif (is_array($requiredCols) && isset($requiredCols[$field])) {
|
||||
$add($field, $type === 'checkbox'
|
||||
? (string) (isset($def['uncheckedValue']) && $def['uncheckedValue'] !== '' ? $def['uncheckedValue'] : '0')
|
||||
: self::columnFallback($requiredCols[$field], $now));
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($requiredCols)) {
|
||||
foreach ($requiredCols as $col => $meta) {
|
||||
if (isset($present[$col]) || $col === 'num') continue;
|
||||
$add($col, self::columnFallback($meta, $now));
|
||||
}
|
||||
}
|
||||
return [$parts, $params];
|
||||
}
|
||||
|
||||
/** Default explicito del schema para un campo, o null si no declara uno. */
|
||||
private static function schemaDefault(array $def)
|
||||
{
|
||||
$type = isset($def['type']) ? $def['type'] : '';
|
||||
if ($type === 'checkbox') {
|
||||
$checked = isset($def['checkedByDefault']) ? $def['checkedByDefault'] : 0;
|
||||
if (!in_array($checked, [1, '1', true], true)) return null;
|
||||
return (string) (isset($def['checkedValue']) && $def['checkedValue'] !== '' ? $def['checkedValue'] : '1');
|
||||
}
|
||||
$keys = in_array($type, ['textbox', 'wysiwyg', 'codigo'], true)
|
||||
? ['defaultContent', 'defaultValue'] : ['defaultValue'];
|
||||
foreach ($keys as $k) {
|
||||
if (!isset($def[$k]) || !is_scalar($def[$k])) continue;
|
||||
$v = (string) $def[$k];
|
||||
if ($v === '' || strpos($v, '<?') !== false) continue;
|
||||
return $v;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Valor por tipo de columna para una columna NOT NULL sin DEFAULT. */
|
||||
private static function columnFallback(array $meta, $now)
|
||||
{
|
||||
$t = isset($meta['data_type']) ? $meta['data_type'] : '';
|
||||
if (in_array($t, ['datetime', 'timestamp'], true)) return $now;
|
||||
if ($t === 'date') return substr($now, 0, 10);
|
||||
if ($t === 'time') return '00:00:00';
|
||||
if ($t === 'year') return substr($now, 0, 4);
|
||||
if (in_array($t, ['tinyint', 'smallint', 'mediumint', 'int', 'integer', 'bigint',
|
||||
'decimal', 'numeric', 'float', 'double', 'real', 'bit'], true)) return 0;
|
||||
if ($t === 'enum' && preg_match("/^enum\('((?:[^'\\\\]|\\\\.|'')*)'/i",
|
||||
isset($meta['column_type']) ? $meta['column_type'] : '', $m)) {
|
||||
return str_replace("''", "'", $m[1]);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Coerces a value to its correct database representation
|
||||
* based on the schema field definition.
|
||||
@@ -1677,7 +1765,7 @@ class AcaiAPI
|
||||
|
||||
/**
|
||||
* 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
|
||||
* DEFAULT y no auto_increment. Devuelve [columna => [data_type, column_type]], 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.
|
||||
*/
|
||||
@@ -1688,7 +1776,7 @@ class AcaiAPI
|
||||
}
|
||||
try {
|
||||
$rows = $db->executeS(
|
||||
"SELECT COLUMN_NAME FROM information_schema.COLUMNS"
|
||||
"SELECT COLUMN_NAME, DATA_TYPE, COLUMN_TYPE 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%'",
|
||||
@@ -1699,8 +1787,11 @@ class AcaiAPI
|
||||
}
|
||||
$cols = [];
|
||||
foreach ((array) $rows as $row) {
|
||||
$name = is_array($row) ? ($row['COLUMN_NAME'] ?? reset($row)) : null;
|
||||
if ($name) $cols[$name] = true;
|
||||
if (!is_array($row) || empty($row['COLUMN_NAME'])) continue;
|
||||
$cols[$row['COLUMN_NAME']] = [
|
||||
'data_type' => strtolower((string) ($row['DATA_TYPE'] ?? '')),
|
||||
'column_type' => (string) ($row['COLUMN_TYPE'] ?? ''),
|
||||
];
|
||||
}
|
||||
return self::$requiredColsCache[$fullTable] = $cols;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user