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
This commit is contained in:
Jordan Diaz
2026-09-12 22:40:42 +00:00
co-authored by Claude Fable 5.1
parent 462cba0505
commit 273c3dd781
6 changed files with 177 additions and 4 deletions
+27
View File
@@ -30,6 +30,7 @@ if (isset($_REQUEST["action_ws"])){
case "coreUpdate": _coreUpdate(realpath($GLOBALS["APP"]["pluginsdir"]."/../../lib")); break;
case "pluginsUpdate": _coreUpdate(realpath($GLOBALS["APP"]["pluginsdir"]),true); break;
case "removeFile": _removeFile(); break;
case "pluginLifecycle": _pluginLifecycle(); break;
case "clearCache": _clearCache(); break;
case "renameFile": _renameFile();
default : die(json_encode(["result" => 0,"error" => "Método no permitido"]));
@@ -386,3 +387,29 @@ function _renameFile() {
die(json_encode(['success' => file_exists($newFile)]));
}
function _pluginLifecycle() {
// Fase 0 (2026-09-12): ejecuta los ganchos de ciclo de vida de un plugin
// (install/uninstall/enable/disable/status) DENTRO de la web. Unico punto
// de entrada para Forge y para el CMS legacy. Misma autenticacion que coreUpdate.
CocoWS::setToken(@$_REQUEST["token"]);
if (!CocoWS::validateUploadToken(@$_REQUEST["tokenHash"])) {
die(json_encode(['error' => ['message' => 'Token no válido', 'code' => 403]]));
}
header('Content-Type: application/json');
if (!function_exists("runPluginLifecycle")) {
// El cargador de plugins de esta web es anterior a los ganchos: el sync
// respeta ficheros personalizados y puede haber entregado este y no aquel.
die(json_encode(["ok" => false, "error" => "El cargador de plugins de esta web no soporta ganchos (cms/lib/plugins/index.php desactualizado)"]));
}
$event = (string) @$_REQUEST["event"];
$plugin = (string) @$_REQUEST["plugin"];
$context = array(
"origin" => in_array(@$_REQUEST["origin"], array("forge", "legacy"), true) ? $_REQUEST["origin"] : "desconocido",
"dropData" => filter_var(@$_REQUEST["dropData"], FILTER_VALIDATE_BOOLEAN),
// Misma señal de entorno que header.php: el .acai solo existe en las copias locales.
"mode" => file_exists(__DIR__ . "/../../../.acai") ? "local" : "production",
);
die(json_encode(runPluginLifecycle($plugin, $event, $context)));
}