$value): if (is_array($value)){ foreach($value as $kv => $vv): if (isset($customSchema[$key][$kv]) && @$customSchema[$key][$kv] != "" && @$customSchema[$key][$kv] != $vv) $schemaPlugins[$dirname][$key][$kv] = $customSchema[$key][$kv]; endforeach; }else{ if (isset($customSchema[$key]) && @$customSchema[$key] != "" && @$customSchema[$key] != $value) $schemaPlugins[$dirname][$key] = $customSchema[$key]; } endforeach; } } } return $schemaPlugins; } function installSQL($path){ if (file_exists($path."/install.sql")){ try{ sqlImport($path."/install.sql"); return 1; }catch(Exception $e){ return 0; } }else{ return 1; } } function installSchemas($path){ if (file_exists($path."/schemaPresets")){ foreach (scandir($path."/schemaPresets") as $dirname) { if (strpos($dirname,".ini")){ addTablePlugin($path."/schemaPresets",$dirname); } } rename($path."/schemaPresets/",$path."/schemaPresets.bck/"); return 0; }else{ return 1; } } function showInstall($path){ global $TABLE_PREFIX,$menu,$action,$schema; // $result = 0; showHeader(); ?>

Instalando el plugin

 

Recarga para cargar el plugin
$value): if (!@$CURRENT_USER["isAdmin"] && $value["adminOnly"]) continue; if (!@$value["enabled"]) continue; if (@$value["checkPoints"]){ foreach($allowedFields as $allowedField): $value["checkPoints"][$allowedField] = ""; endforeach; foreach($value["checkPoints"] as $checkPoint => $controller): if ($checkPoint==$type && isset($value["checkPoints"][$type])){ $enabled = true; $pluginsInserted[] = $key; // DEFINIMOS LAS VARIABLES DE OPCIONES PARA EL PLUGIN $options = array(); $options["templatePath"] = str_replace($GLOBALS["PROGRAM_DIR"]."/","",$GLOBALS["APP"]["pluginsdir"])."/".$key; $options["pluginName"] = $key; if (@$value["newFieldType"]){ $options["fieldType"] = $value["newFieldType"]; if (@$fieldtype){ foreach($options["fieldType"] as $kvar => $vvar): if (isset($var["field"][$kvar])) $options["fieldType"][$kvar] = $var["field"][$kvar]; endforeach; } } // SI EXISTEN FILTROS DE MENU if (@$value["menuFilter"][$type]){ $sepMenu = array_filter(explode(",",$value["menuFilter"][$type])); if (@$sepMenu && !in_array(@$menu,@$sepMenu)){ $enabled = false; } } // SI EXISTEN FILTROS DE ACTION if (@$value["actionFilter"][$type]){ $sepAction = array_filter(explode(",",$value["actionFilter"][$type])); if (@$sepAction && !in_array(@$action,@$sepAction)){ $enabled = false; } } // SI EXISTEN FILTROS DE TIPO DE CAMPO if (@$value["newFieldType"]["type"] && @$fieldtype && @$value["newFieldType"]["type"] != $fieldtype) { $enabled = false; } // SI EXISTE UN ARCHIVO DE INSTALACION LO EJECUTAMOS if (file_exists($GLOBALS["APP"]["pluginsdir"]."/".$key."/install.sql") || file_exists($GLOBALS["APP"]["pluginsdir"]."/".$key."/schemaPresets")){ $enabled = false; showInstall($GLOBALS["APP"]["pluginsdir"]."/".$key); } // SI NOS PIDE EL TYPE DE CAMPO Y EXISTE EN EL SCHEMA DEL PLUGIN LO AÑADIMOS if ($enabled && @$type=="db_type" && @$value["newFieldType"]["type"]){ array_push($var, array("value" => $value["newFieldType"]["type"], "name" => $value["newFieldType"]["label"])); continue; } if ($enabled && @$type=="db_type_config" && @$value["newFieldType"]){ ?>
" data-fieldtype="" class="fieldPluginContainer"> Plugin Options $vFT):?>
][]" value="">
0) { query($row); } fclose($file); } /** * Remove comments from sql * * @param string sql * @param boolean is multicomment line * @return string */ function clearSQL($sql, &$isMultiComment) { if ($isMultiComment) { if (preg_match('#\*/#sUi', $sql)) { $sql = preg_replace('#^.*\*/\s*#sUi', '', $sql); $isMultiComment = false; } else { $sql = ''; } if(trim($sql) == ''){ return $sql; } } $offset = 0; while (preg_match('{--\s|#|/\*[^!]}sUi', $sql, $matched, PREG_OFFSET_CAPTURE, $offset)) { list($comment, $foundOn) = $matched[0]; if (isQuoted($foundOn, $sql)) { $offset = $foundOn + strlen($comment); } else { if (substr($comment, 0, 2) == '/*') { $closedOn = strpos($sql, '*/', $foundOn); if ($closedOn !== false) { $sql = substr($sql, 0, $foundOn) . substr($sql, $closedOn + 2); } else { $sql = substr($sql, 0, $foundOn); $isMultiComment = true; } } else { $sql = substr($sql, 0, $foundOn); break; } } } return $sql; } /** * Check if "offset" position is quoted * * @param int $offset * @param string $text * @return boolean */ function isQuoted($offset, $text) { if ($offset > strlen($text)) $offset = strlen($text); $isQuoted = false; for ($i = 0; $i < $offset; $i++) { if ($text[$i] == "'") $isQuoted = !$isQuoted; if ($text[$i] == "\\" && $isQuoted) $i++; } return $isQuoted; } function query($sql) { //echo '#SQL CODE TO RUN:
' . htmlspecialchars($sql) . ';

'; if (!$query = mysql_query($sql)) { throw new Exception("Cannot execute request to the database {$sql}: " . mysql_error()); } } // --------------------------------------------------------------------------- // 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. // El gancho ve $CURRENT_USER, $menu, $action, $TABLE_PREFIX y $SETTINGS como un // checkpoint; cualquier otro global lo declara el. NUNCA exit()/die(): no es una // excepcion, se salta el resultado y solo la red de seguridad de handler_ws.php // evita una respuesta que no sea JSON. // --------------------------------------------------------------------------- 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, pero si // los mismos globales del CMS que ve un checkpoint desde addPlugins(). global $CURRENT_USER, $menu, $action, $TABLE_PREFIX, $SETTINGS; 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) { // El detalle completo (ruta, linea) va al log del servidor; al cliente, // el mensaje sin rutas absolutas. error_log("[pluginLifecycle] " . $folder . "/" . $event . ".php: " . $e->getMessage() . " en " . $e->getFile() . ":" . $e->getLine()); $raiz = realpath($GLOBALS["APP"]["pluginsdir"] . "/../../.."); $res["ok"] = false; $res["error"] = "Excepcion en " . $event . ".php: " . ($raiz ? str_replace($raiz, "", $e->getMessage()) : $e->getMessage()); } return $res; } ?>