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:
co-authored by
Claude Fable 5.1
parent
462cba0505
commit
273c3dd781
+118
-4
@@ -75,9 +75,8 @@ function showInstall($path){
|
||||
|
||||
<div id="page-content">
|
||||
<h3>Instalando el plugin</h3>
|
||||
<? $borro = true; ?>
|
||||
<? if (installSQL($path)){ if ($borro) rename($path."/install.sql",$path."/install.sql.bck"); ?><p>Instalando tablas necesarias</p><?}else{?><p class="text-danger">Error instalando la base de datos</p><?}?>
|
||||
<? if (installSchemas($path)){ if ($borro) rename($path."/schemaPresets",$path."/schemaPresets.bck"); ?><p>Instalando Schemas</p><?}else{?><p class="text-danger">Error instalando los esquemas</p><?}?>
|
||||
<? $mensajesInstall = []; installPluginArtifacts($path, $mensajesInstall); ?>
|
||||
<? foreach ($mensajesInstall as $mensajeInstall): ?><p><?=htmlspecialchars($mensajeInstall);?></p><? endforeach; ?>
|
||||
<? if ($result==2){?>
|
||||
<p> </p>
|
||||
<a href="">Recarga para cargar el plugin</a>
|
||||
@@ -356,4 +355,119 @@ function query($sql)
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
// ---------------------------------------------------------------------------
|
||||
// Ciclo de vida de plugins (fase 0, 2026-09-12).
|
||||
//
|
||||
// Ganchos OPCIONALES en la carpeta del plugin: install.php, uninstall.php,
|
||||
// enable.php, disable.php y status.php. Los ejecuta runPluginLifecycle()
|
||||
// DENTRO de la web, instale quien instale (Forge o el CMS legacy), a traves de
|
||||
// action_ws=pluginLifecycle (cms/lib/handlers/handler_ws.php).
|
||||
//
|
||||
// Contrato de un gancho: se incluye en un ambito propio con $plugin, $pluginDir,
|
||||
// $event, $context y $dropData; debe ser IDEMPOTENTE (puede ejecutarse dos veces);
|
||||
// puede devolver ["ok" => bool, "messages" => [..], "status" => [..]] o
|
||||
// simplemente terminar. status.php devuelve semaforos:
|
||||
// [["clave" => "...", "estado" => "ok|warn|error", "texto" => "..."], ...].
|
||||
// Lo que el gancho imprima se recoge como mensaje, nunca sale por la respuesta.
|
||||
// ---------------------------------------------------------------------------
|
||||
function pluginLifecycleEvents() {
|
||||
return array("install", "uninstall", "enable", "disable", "status");
|
||||
}
|
||||
|
||||
function pluginFolderIsValid($folder) {
|
||||
return is_string($folder) && $folder !== "" && $folder !== "." && $folder !== ".."
|
||||
&& preg_match('/^[a-zA-Z0-9._-]+$/', $folder) === 1;
|
||||
}
|
||||
|
||||
function pluginDirResolved($folder) {
|
||||
// Ruta real del plugin, o false si no existe o se sale de pluginsdir.
|
||||
$pluginsDir = realpath($GLOBALS["APP"]["pluginsdir"]);
|
||||
if (!$pluginsDir || !pluginFolderIsValid($folder)) return false;
|
||||
$dir = realpath($pluginsDir . "/" . $folder);
|
||||
if (!$dir || !is_dir($dir)) return false;
|
||||
if (strpos($dir, $pluginsDir . DIRECTORY_SEPARATOR) !== 0) return false;
|
||||
return $dir;
|
||||
}
|
||||
|
||||
function installPluginArtifacts($path, &$messages) {
|
||||
// Lo que hacia showInstall() sin el HTML: install.sql y schemaPresets/ una
|
||||
// sola vez (se renombran a .bck, como siempre). false si el SQL fallo.
|
||||
$ok = true;
|
||||
if (file_exists($path . "/install.sql")) {
|
||||
if (installSQL($path)) {
|
||||
@rename($path . "/install.sql", $path . "/install.sql.bck");
|
||||
$messages[] = "install.sql ejecutado";
|
||||
} else {
|
||||
$ok = false;
|
||||
$messages[] = "install.sql ha fallado";
|
||||
}
|
||||
}
|
||||
if (file_exists($path . "/schemaPresets")) {
|
||||
installSchemas($path); // renombra schemaPresets -> schemaPresets.bck
|
||||
$messages[] = "schemaPresets instalados";
|
||||
}
|
||||
return $ok;
|
||||
}
|
||||
|
||||
function _includePluginHook($hookFile, $plugin, $pluginDir, $event, $context, $dropData) {
|
||||
// Ambito propio: el gancho no ve las variables de quien lo llama.
|
||||
ob_start();
|
||||
$ret = include $hookFile;
|
||||
$salida = trim((string) ob_get_clean());
|
||||
return array("ret" => $ret, "output" => $salida);
|
||||
}
|
||||
|
||||
function _pluginCoreStatus($folder, $pluginDir) {
|
||||
$schemas = getSchemaPlugins(true);
|
||||
$enabled = !empty($schemas[$folder]["enabled"]);
|
||||
$pendiente = file_exists($pluginDir . "/install.sql") || file_exists($pluginDir . "/schemaPresets");
|
||||
$status = array(
|
||||
array("clave" => "instalado", "estado" => "ok", "texto" => "Plugin instalado en la web"),
|
||||
array("clave" => "activado", "estado" => $enabled ? "ok" : "warn",
|
||||
"texto" => $enabled ? "Plugin activado" : "Plugin desactivado"),
|
||||
array("clave" => "core_ganchos", "estado" => "ok", "texto" => "El core de esta web soporta ganchos de plugins"),
|
||||
);
|
||||
if ($pendiente) {
|
||||
$status[] = array("clave" => "instalacion_pendiente", "estado" => "warn",
|
||||
"texto" => "install.sql o schemaPresets pendientes de ejecutar");
|
||||
}
|
||||
return $status;
|
||||
}
|
||||
|
||||
function runPluginLifecycle($folder, $event, $context = array()) {
|
||||
$res = array("ok" => false, "event" => $event, "plugin" => $folder, "ran" => false,
|
||||
"messages" => array(), "status" => array(), "error" => null);
|
||||
if (!pluginFolderIsValid($folder)) { $res["error"] = "Nombre de plugin no valido"; return $res; }
|
||||
if (!in_array($event, pluginLifecycleEvents(), true)) { $res["error"] = "Evento no valido"; return $res; }
|
||||
$pluginDir = pluginDirResolved($folder);
|
||||
if (!$pluginDir) { $res["error"] = "El plugin no existe en esta web"; return $res; }
|
||||
if (!file_exists($pluginDir . "/schema.ini.php")) { $res["error"] = "El plugin no tiene schema.ini.php"; return $res; }
|
||||
$dropData = !empty($context["dropData"]);
|
||||
try {
|
||||
if ($event === "install" && !installPluginArtifacts($pluginDir, $res["messages"])) {
|
||||
$res["error"] = "install.sql ha fallado";
|
||||
return $res;
|
||||
}
|
||||
if ($event === "status") $res["status"] = _pluginCoreStatus($folder, $pluginDir);
|
||||
$hookOk = true;
|
||||
$hookFile = $pluginDir . "/" . $event . ".php";
|
||||
if (file_exists($hookFile)) {
|
||||
$res["ran"] = true;
|
||||
$hook = _includePluginHook($hookFile, $folder, $pluginDir, $event, $context, $dropData);
|
||||
if ($hook["output"] !== "") $res["messages"][] = $hook["output"];
|
||||
if (is_array($hook["ret"])) {
|
||||
if (array_key_exists("ok", $hook["ret"]) && !$hook["ret"]["ok"]) $hookOk = false;
|
||||
foreach ((array) @$hook["ret"]["messages"] as $m) $res["messages"][] = (string) $m;
|
||||
foreach ((array) @$hook["ret"]["status"] as $st) if (is_array($st)) $res["status"][] = $st;
|
||||
if (!empty($hook["ret"]["error"])) $res["error"] = (string) $hook["ret"]["error"];
|
||||
}
|
||||
}
|
||||
$res["ok"] = $hookOk;
|
||||
if (!$hookOk && !$res["error"]) $res["error"] = "El gancho " . $event . ".php ha devuelto ok=false";
|
||||
} catch (Throwable $e) {
|
||||
$res["ok"] = false;
|
||||
$res["error"] = "Excepcion en " . $event . ".php: " . $e->getMessage();
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
?>
|
||||
|
||||
Reference in New Issue
Block a user