From 4e46322b025b984d67fa8f867e6440c714acc614 Mon Sep 17 00:00:00 2001 From: Jordan Diaz Date: Thu, 24 Sep 2026 07:21:53 +0000 Subject: [PATCH] feat(core): regla unica de valores por defecto en los INSERT (AcaiAPI::insertDefaults) --- cms/lib/classes/AcaiAPI.php | 137 ++++++++++++++++++++++++++++++------ 1 file changed, 114 insertions(+), 23 deletions(-) diff --git a/cms/lib/classes/AcaiAPI.php b/cms/lib/classes/AcaiAPI.php index f6a2451..547697f 100644 --- a/cms/lib/classes/AcaiAPI.php +++ b/cms/lib/classes/AcaiAPI.php @@ -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 ` 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, ' 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; }