Commit Graph
34 Commits
Author SHA1 Message Date
Acai ForgeandClaude Opus 5.5 cb03ecd239 fix(cache): sin Redis la web sigue sin caché en vez de morir
initCache() de CocoDB y AcaiAPI conectaba sin capturar errores: en un
hosting sin Redis (o sin la extensión) el plugin cache_handler tumbaba la
web entera con un RedisException. Ahora se marca como no disponible para
el resto de la petición y cacheGet/cacheSet, que ya comprobaban la
conexión, no hacen nada. Con Redis el comportamiento no cambia.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 17:08:52 +00:00
Acai ForgeandClaude Opus 5.5 49522987dd feat(core): las fotos de los registros traen dimensiones y miniaturas
AcaiAPI::batchLoadUploads (camino de cmsApi get con uploads:true, el que usan
los listados y fichas de las webs Astro) solo devolvia urlPath e info*: sin
width/height el front no reserva el hueco (CLS) y sin thumbUrlPath..4 no puede
montar un srcset. Ahora selecciona tambien width, height y URL+dimensiones de
los 4 huecos de miniatura (no las rutas de disco), y useAbsoluteUrls reescribe
tambien las URLs de las miniaturas.

Solo pide las columnas que existen en {prefix}uploads (SHOW COLUMNS una vez por
prefijo y peticion): una web antigua sin alguna no se queda sin fotos.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 18:28:21 +00:00
Jordan Diaz 15c3db882d feat(core): cambiar las fotos de un registro sella su updatedDate 2026-09-26 13:34:07 +00:00
Jordan Diaz 17f5d91c8d feat(core): el enlace es unico entre todas las tablas (AcaiAPI::uniqueEnlace) y slug.php registra duplicados 2026-09-24 09:34:22 +00:00
Jordan Diaz 4e46322b02 feat(core): regla unica de valores por defecto en los INSERT (AcaiAPI::insertDefaults) 2026-09-24 07:21:53 +00:00
Jordan Diaz 184038b786 fix(core): una fecha ausente solo se rellena si la columna es NOT NULL sin DEFAULT 2026-09-24 00:17:04 +00:00
Jordan Diaz 14ddf1608e fix(core): altas rellenan fechas que faltan y el error de insert/update trae el mensaje de MySQL 2026-09-23 07:59:03 +00:00
Jordan DiazandClaude Opus 5.5 701bc3bb8b feat(core): el motor de las tablas nuevas depende de la version del servidor
El cambio anterior (8d92d08) creaba siempre las tablas nuevas en InnoDB. Hay
un servidor de produccion con MariaDB 5.5.68, donde InnoDB no es seguro: no
admite FULLTEXT, limita los indices a 767 bytes y usa filas COMPACT. Con
«Subir core», las tablas nuevas de una web de ese servidor habrian nacido en
InnoDB sobre 5.5.

Ahora `acaiTableEngine()` decide por la version, con el mismo patron que
`mysqlStrictMode` usa para el modo SQL:

  MariaDB >= 10.2 o MySQL >= 5.7 -> InnoDB (Barracuda/DYNAMIC por defecto)
  cualquier otro o desconocido   -> MyISAM (lo de siempre)

Contempla el disfraz "5.5.5-10.x.y-MariaDB" con el que algunos clientes
antiguos ven a MariaDB 10+. La decision pura (`acaiEngineForServerVersion`)
esta separada para poder probarla sin servidor. Usada en los dos sitios que
crean tablas: createMissingSchemaTablesAndFields y la instalacion de plugins.

Comprobado: 12 versiones (Forge 10.6, webserver2 11.4, servidor antiguo 5.5,
disfraces, MySQL 5.6/5.7/8.0, vacio) y de punta a punta contra el MariaDB de
Forge a traves del shim mysql_*: InnoDB.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-22 17:44:26 +00:00
Jordan DiazandClaude Opus 5.5 3701072bb3 docs(core): el comentario del motor InnoDB daba una razon falsa
Decia que los INSERT parciales del CMS seguian colando porque el sql_mode de
Forge no es estricto. Es falso: el CMS fija su propio modo al conectar
(mysqlStrictMode(true) y TRADITIONAL en Db.php), y en un INSERT de una fila
ese modo rechaza lo mismo con MyISAM que con InnoDB. Solo cambia el
comentario.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-22 17:28:39 +00:00
Jordan DiazandClaude Opus 5 12dfdd5fed feat(core): las tablas nuevas del CMS nacen en InnoDB
Los dos sitios que crean tablas en caliente las creaban MyISAM: la funcion
`createMissingSchemaTablesAndFields()` (cada seccion o tabla nueva del CMS) y
la instalacion de plugins con tablas preset. Parecian codigo muerto porque
usan `mysql_query`, que PHP 8 elimino, pero estan vivos: hay un shim en
cms/lib/mysql.php que lo mapea a mysqli.

Sin esto, cambiar el molde de las webs nuevas no servia de nada: la primera
tabla que alguien creara desde el CMS volvia a nacer MyISAM y el molde se
degradaba solo con el uso.

InnoDB da recuperacion ante caida (MyISAM se corrompe con un OOM o un apagon y
hay que repararlo a mano) y bloqueo por fila. El sql_mode de Forge ya no es
estricto, asi que los INSERT parciales del CMS —AcaiAPI no rellena
`dragSortOrder` ni los checkboxes— siguen colando con un aviso igual que con
MyISAM, en vez de fallar con el error 1364.

No toca ninguna tabla existente: solo el motor de las que se creen a partir de
ahora. Contexto completo en docs/motor-innodb.md del repo de Forge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 16:41:02 +00:00
Jordan DiazandClaude Opus 5 a9ecad4ee1 fix(core): los ganchos de ciclo de vida de plugins funcionan en las webs locales
_pluginLifecycle validaba el token SOLO contra el WS central, que unicamente
conoce webs de produccion: en una copia local de Forge (standalone o de test)
el token nunca le valida y instalar, desinstalar, activar o desactivar un
plugin moria con "Token no valido", un mensaje que no tenia nada que ver con
el token.

Segunda via para esas copias: si el central rechaza y la web tiene .acai con
mode != production, se compara el par token+tokenHash recibido con el que la
propia web guarda en su .acai (hash_equals). No es un bypass — sin .acai, en
produccion o sin coincidencia exacta no autoriza nada —, y el secreto es el
mismo que Forge ya usa para hablar con la web.

Probado en superadmin/claude-es-mi-amigo: con el token del .acai el gancho
corre (ok:true); con un token falso o vacio, 403 como siempre.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 16:27:18 +00:00
Jordan DiazandClaude Opus 5 0bbf053703 fix(core): el borrado recursivo se dejaba los ficheros ocultos
`_deleteAll` listaba con `glob`, que NO devuelve los ficheros que empiezan por
punto. Un `.htaccess` sobrevivia al barrido, la carpeta no quedaba vacia y el
`rmdir` de despues fallaba sin decir por que.

Efecto real, encontrado desinstalando `variaciones` en produccion: se borra el
codigo del plugin, se devuelve "No se ha podido desinstalar el plugin" y la
operacion queda A MEDIAS — sin ficheros, pero con la carpeta en la web y con el
alta todavia en el CMS central. El paso que falla es el segundo de cuatro, asi
que los dos ultimos ni se intentan. En una web real son 5 de 14 los plugins con
`.htaccess`, no un caso raro.

Se cambia a `scandir`, que si los ve, saltando `.` y `..`.

Y de paso el symlink, que con `glob` tampoco se trataba: ahora se QUITA el
enlace en vez de seguirlo. Recorrerlo seria borrar lo que hay al otro lado, que
vive fuera de la carpeta que se pidio borrar. Comprobado con un enlace a un
directorio con un fichero dentro: el enlace desaparece, el fichero se queda.

Verificado dentro del container, con carpeta anidada, dos ficheros ocultos y un
symlink: la carpeta se borra entera y lo apuntado sobrevive.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HPL66q9F33YZxbLPUT7KW8
2026-09-13 18:08:02 +00:00
Jordan DiazandClaude Fable 5.1 b1c9ffa461 fix(plugins): el cargador ignora una carpeta con custom-schema pero sin schema
Tras un descarte del sync (que no mueve los custom-schema.ini.php, excluidos
del manifest) puede quedar una carpeta de plugin solo con su overlay. El
cargador hacia foreach sobre un schema inexistente y emitia 'Undefined array
key' en cada peticion, visible en el HTML con display_errors=1. Sin
schema.ini.php no hay plugin: la carpeta se ignora.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HPL66q9F33YZxbLPUT7KW8
2026-09-13 11:23:51 +00:00
Jordan DiazandClaude Fable 5.1 e198bd780a fix(plugins): _removeFile solo borra contenido, nunca el motor
La regla anterior permitia cualquier ruta dentro de la raiz de la web, y eso
incluye cms/lib, lib o los PHP del motor. Ahora en modo fichero solo se admite
algo bajo cms/uploads/, template/, hooks/ o cache/; en modo plugin, una carpeta
directa de pluginsdir. Lo demas, 403.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HPL66q9F33YZxbLPUT7KW8
2026-09-12 22:59:07 +00:00
Jordan DiazandClaude Fable 5.1 ea8eb6dbcb fix(plugins): red de seguridad ante exit() en un gancho y globales del CMS
Tras la revision de codigo de 273c3dd:
- Un exit()/die() dentro de un gancho no es una excepcion y dejaba un cuerpo
  que no era JSON. register_shutdown_function en _pluginLifecycle responde
  {ok:false, error, messages:[lo impreso]} si el gancho no llego al final.
- event/plugin/origin se castean solo si son escalares (un array producia un
  Warning delante del JSON).
- Los ganchos ven CURRENT_USER, menu, action, TABLE_PREFIX y SETTINGS como
  un checkpoint desde addPlugins(); documentado en el contrato.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HPL66q9F33YZxbLPUT7KW8
2026-09-12 22:51:56 +00:00
Jordan DiazandClaude Fable 5.1 3565abdd88 fix(plugins): endurecer pluginLifecycle y sanear _removeFile
Tras la revision de seguridad de 273c3dd:
- pluginLifecycle exige POST con cuerpo JSON (token, tokenHash, event, plugin),
  como _saveFile: el token no viaja en la query string.
- La excepcion de un gancho se loguea entera en el servidor y al cliente va
  sin rutas absolutas.
- _removeFile (preexistente): la ruta la mandaba el cliente sin sanear y
  _deleteAll borraba cualquier cosa alcanzable, ../ incluido. Ahora en modo
  plugin solo una carpeta directa de pluginsdir y en modo fichero solo dentro
  de la raiz de la web; nunca la raiz ni pluginsdir, nunca fuera por symlink.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HPL66q9F33YZxbLPUT7KW8
2026-09-12 22:50:20 +00:00
Jordan DiazandClaude Fable 5.1 273c3dd781 feat(plugins): ganchos de ciclo de vida y checkpoints boot_plugins y post_variables
Fase 0 del instalador de plugins (docs/plugins/fase0-instalador-diseno.md del
repo de Forge). Un plugin puede llevar install.php, uninstall.php, enable.php,
disable.php y status.php; los ejecuta runPluginLifecycle() dentro de la web
por action_ws=pluginLifecycle, sea Forge o el CMS legacy quien instale.
showInstall() reutiliza el mismo nucleo (installPluginArtifacts) en vez de
duplicar install.sql/schemaPresets.

Checkpoints nuevos, sin efecto hasta que un plugin los declare:
- boot_plugins: en init.php, con la BD ya conectada.
- post_variables: tras cada require de lib/variables.php (slug.php, sesion.php,
  lib/recursos.php), una vez por peticion. Un plugin llega DESPUES del bloque
  de cache de variables.php y puede pisarlo.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HPL66q9F33YZxbLPUT7KW8
2026-09-12 22:40:42 +00:00
Jordan DiazandClaude Fable 5.1 462cba0505 feat(core): cms/lib/plugins/index.php entra en el core distribuible
Es el cargador de plugins (loadPlugins lo incluye desde common.php): codigo del
motor, no un plugin. Hasta ahora solo existia en el core_client del WS, que deja
de ser una copia a mano y pasa a ser un checkout de este repo, asi que tiene que
vivir aqui para seguir siendo distribuible. Las 94 webs locales llevan
exactamente este contenido (md5 ef8e54e2), asi que el rsync no cambia nada.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HPL66q9F33YZxbLPUT7KW8
2026-09-11 12:11:49 +00:00
Jordan DiazandClaude Opus 5 e4ab0567df feat(correo): SMTP desde la configuracion del panel, y fuera el proxy
Dos cambios que van juntos porque atacan el mismo fallo: en las webs de Forge no
salia ni un correo.

1. LOS CAMPOS DEL PANEL AHORA SIRVEN PARA ALGO

   La tabla `configuracion` lleva desde siempre email_smtp, email_host,
   email_port, email_secure, email_username, email_password, email_from y
   email_from_name... y no los leia NADIE. Aparecian solo en el schema: el panel
   te dejaba rellenarlos, se guardaban en la BD, y el envio seguia usando los
   defaults de la clase —con la contrasena VACIA— o lo que hubiera a mano en
   lib/variables.php. Un formulario decorativo.

   `cargarSmtpDeConfiguracion()` los vuelca sobre la clase cuando el check
   email_smtp esta marcado y email_host tiene valor. Se llama desde send() antes
   de enviar, no solo desde send_email().

   El schema de `configuracion` es PER-WEB y no todas tienen esas columnas
   (villagrancanaria no las tiene): se lee con @ sobre el array, asi que una web
   sin ellas se comporta exactamente igual que antes.

2. FUERA EL PROXY

   send_email_coco_proxy() mandaba a cocosolution.com/?sendQuantumEmail=1. Se
   quita por dos motivos medidos hoy:

     - Ese endpoint RECHAZA TODO. Probados seis formatos (JSON crudo,
       form-urlencoded, anidado, y el payload cifrado por tres vias) y siempre
       contesta {"error":1,"message":"Need SMTP data and Params"}, incluso con
       smtp_data completo. Es decir: ninguna web de Forge ha podido enviar por
       ahi, y eso es anterior a los cambios de hoy.
     - No hace falta. El container alcanza los SMTP por su cuenta: verificado
       contra smtp.gmail.com:465 y :587 y smtp.ionos.es:465, con banner 220.
       PHPMailer conecta solo cuando $smtp = true.

   Se va con el encrypt(), que solo usaba el proxy y en una linea que ya estaba
   comentada dentro de esa misma funcion. Cero llamadas a ambas fuera de la
   clase, comprobado en los cuatro arboles.

Verificado en cdn-ecommerce: con el check marcado, la traza pasa de
"smtp=no host=smtp.gmail.com pass=VACIA" a "smtp=si host=<el del panel>", y
PHPMailer intenta la conexion. Falta solo poner credenciales de verdad.

OJO, queda pendiente: send_email() devuelve NULL siempre, tanto si entrega como
si no. Un fallo de envio es hoy indistinguible de un exito.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 20:34:05 +00:00
Jordan DiazandClaude Opus 5 3615d85bc0 Revert "fix(correo): un solo camino de envio, el directo, y fuera el proxy"
El proxy no era prescindible: el container de las webs de Forge NO tiene MTA.
`which sendmail mail postfix ssmtp msmtp exim4` no devuelve nada y
`sendmail_path` apunta a /usr/sbin/sendmail, que no existe. Con $smtp = false
PHPMailer cae a mail() de PHP y ahi no sale nada, en silencio.

Medido sobre las webs de Forge:

  26  configuran CocoEmail::$smtp = true propio  -> enviarian igual
  57  NO configuran SMTP                         -> se quedaban SIN correo

Entre esas 57 esta cdn-ecommerce, que es justo la web de pruebas. Se vuelve a
la version condicional: proxy cuando hay .acai (Forge/test) y directo cuando no
(legacy de produccion). Cubre los tres casos, porque el proxy reenvia con el
smtp_data que le llega en el payload, asi que las 26 con SMTP propio siguen
saliendo por su servidor.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 20:19:22 +00:00
Jordan DiazandClaude Opus 5 f279d7d1d3 fix(correo): un solo camino de envio, el directo, y fuera el proxy
El commit anterior dejaba el envio condicionado al entorno (proxy en local,
directo en produccion) para poder unificar el fichero entre los dos canales sin
cambiarle el comportamiento a nadie. Decision posterior: el proxy no hace falta,
asi que se adopta la version de core_client tal cual y se envia SIEMPRE directo.

Se van dos funciones con el, y ninguna deja huerfanos:

  send_email_coco_proxy()  POST a cocosolution.com/?sendQuantumEmail=1
  encrypt()                solo la usaba el proxy, y en una linea que ya estaba
                           comentada dentro de esa misma funcion

Comprobado antes de quitarlas: cero llamadas fuera de la clase en web-base, en
las 131 webs de Forge, en el CMS central y en core_client.

Con esto core_client y web-base quedan identicos: 0 ficheros de deriva fuera de
plugins/, que actualizacion.php se salta a proposito.

OJO al alcance: las webs de Forge pasan de enviar por el proxy a enviar
directo. Es el cambio que se buscaba, pero conviene confirmarlo con un envio
real desde una web local antes de darlo por bueno.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 20:14:38 +00:00
Jordan DiazandClaude Opus 5 ec203bc1e6 fix(core): converger las clases con core_client, que llevaban meses derivando
El core viaja por dos canales que deberian ser iguales y no lo eran:
core_client -> webs legacy via el CMS, y web-base -> webs de Forge. Nadie los
sincroniza, asi que el mismo arreglo se hacia dos veces, por separado y con
implementaciones distintas.

Medida la deriva: 5 ficheros de classes/ (el resto eran plugins/, que
actualizacion.php se salta a proposito).

  CocoDB.php, CocoDB_alias.php  <- de core_client
      Traen los guards `if (file_exists(...))`: si falta el alias o AcaiAPI,
      CocoDB se declara igual con la implementacion legacy. Es degradacion
      real para webs viejas y aqui no la teniamos. Se pierde la declaracion de
      CocoDB_old, que no referencia NADIE en ninguno de los cuatro arboles.

  AcaiAPI.php, CocoParser.php   -> ya iban a core_client desde aqui
      AcaiAPI resulto ser el MISMO codigo: 9252 tokens en ambos y 0
      diferencias al ordenarlos. El bloque de auditoria de updatedDate estaba
      colocado antes o despues del if($isInsert), y como ese bloque no escribe
      en $record, las dos posiciones son equivalentes.

  CocoEmail.php                 <- fusion, y la unica divergencia REAL
      web-base mandaba SIEMPRE por el proxy y core_client SIEMPRE directo, asi
      que el fichero no se podia unificar sin cambiarle el comportamiento a
      media plataforma. Ahora el camino se elige por entorno con el mismo
      criterio que header.php:81 (un .acai en la raiz = local), en linea y sin
      depender de otro fichero.

Para las webs de Forge NO cambia nada: las 131 tienen .acai salvo dos
directorios abandonados de abril sin index.php ni cms/lib/classes, que no
llegan a ser webs. Verificado en villagrancanaria: home, listado y ficha en
200 sin un solo error PHP, listado renderizando y hook en 54 ms.

Con los dos arboles ya identicos, el paso siguiente es que el CMS legacy se
nutra de web-base y dejar de mantener dos copias a mano.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 19:39:36 +00:00
Jordan Diaz 6397abfabb 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)
2026-08-25 15:06:21 +00:00
Jordan Diaz 46ecbb3f09 fix: tolerar null en $functions/$options de AcaiAPI (compat firma legacy CocoDB)
Los hooks legacy llaman CocoDB::updateRecords(..., null) y el nuevo
buildSetClause tipa $functions como array estricto: TypeError fatal en
PHP 8 (p.ej. pago por transferencia via IPNGenerica). Se normaliza en la
frontera publica (insert/update/get/delete).
2026-07-29 11:32:33 +00:00
Jordan Diaz 031ef33f9f fix: devolver valores como string en las lecturas (paridad mysql_* legacy)
PDO con prepares nativos devuelve tipos reales (depth=int 0) mientras que
mysql_* devolvia siempre strings. Los templates y codigo Acai comparan
estrictamente contra strings (is same as('0'), === '1'...) y con tipos
nativos esos filtros fallan en silencio — p.ej. el menu del header vacio
en webs importadas (impulse) porque el filtro depth is same as('0') no
matcheaba int(0). stringifyRows normaliza todos los escalares no-NULL a
string en los 5 paths de lectura que devuelven datos hacia fuera: get(),
uploads, cache de queries de relaciones, traducciones y pluginsConfig.
2026-07-22 11:14:55 +00:00
Jordan Diaz fe7e9ee2e6 fix: no inyectar campos de auditoria en tablas raw (ignoreSchema)
buildSetClause añadia siempre updatedDate/updatedByUserNum (y en inserts
createdDate/createdByUserNum), pero las tablas raw que se escriben con
ignoreSchema (aux_plg_config, uploads, traducciones...) no tienen esas
columnas y el INSERT/UPDATE moria con Unknown column — p.ej. guardar la
config de un plugin desde el dashboard fallaba con 'No se pudo guardar la
configuracion en la base de datos'. Se replica la semantica del CocoDB
legacy: prepareBaseSQL solo añadia auditoria y defaults cuando habia
schema cargado.
2026-07-22 09:40:13 +00:00
Jordan Diaz 34d1458f3a perf: self-provision the traducciones index required by batched t()
The batched translation preload requires the (tableName, recordNum,
prefix) index; without it the per-record query scans and is slower
than the legacy per-field lookup. t() now checks once per PHP process
(static) whether idx_tabla_registro_prefijo exists, creates it on the
fly when the DB user has ALTER rights (re-checking after a lost race),
and otherwise falls back to the legacy per-field query — never slower
than the original behavior on webs where the index cannot be created.

Verified live: dropped the index on villagrancanaria local, first
request recreated it automatically and subsequent renders stayed fast.
2026-07-21 18:34:10 +00:00
Jordan Diaz bcd21dd274 perf: batch per-record translation preload in t()
t() issued one SELECT per translated field (1,300+ queries on a typical
multilanguage home). It now preloads all translations of a (table,
record, language) in a single query and serves subsequent fields from
memory, preserving the previous LIMIT 1 semantics (first row per
fieldName). Measured on villagrancanaria: cold render -60% (en home)
/ -67% (es home), byte-identical output.

REQUIRES index (tableName, recordNum, prefix) on cms_traducciones —
without it the batched query scans and is SLOWER than before:
ALTER TABLE cms_traducciones ADD INDEX idx_tabla_registro_prefijo (tableName, recordNum, prefix);
2026-07-21 18:29:46 +00:00
Jordan Diaz 432130565b AcaiAPI data layer + CocoDB facade
AcaiAPI: PDO-based data access layer (prepared statements, JOIN relation
loading, batch upload fetching) developed on villagrancanaria.
CocoDB_alias declares the CocoDB facade delegating to AcaiAPI; the
legacy implementation stays available as CocoDB_old.
2026-07-21 17:29:51 +00:00
Jordan Diaz dd2657a5d3 CmsApi Libraries 2026-06-09 09:44:45 +00:00
Jordan 927888d0d9 Update CocoDB.php 2026-05-12 18:33:16 +01:00
Jordan 4e4bfde8a5 Quitando payments 2026-03-05 19:01:22 +00:00
Jordan 48fab839d0 Añadiendo los plugins base payments y cms_api 2026-03-05 18:31:21 +00:00
Jordan 03acc5b013 Initial commit: plantilla base PHP para webs Acai CMS 2026-02-21 21:13:57 +00:00