Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4d2ee0b45 | ||
|
|
8eb4caf456 | ||
|
|
bd550841bd | ||
|
|
5709c5c856 | ||
|
|
dabdfa1354 | ||
|
|
bb532515ce | ||
|
|
d1235f2e20 | ||
|
|
cb03ecd239 | ||
|
|
80b6541e30 | ||
|
|
775ae1c95d |
@@ -34,6 +34,7 @@ class AcaiAPI
|
|||||||
// ── Cache flags (mirror CocoDB interface) ─────────────────────────
|
// ── Cache flags (mirror CocoDB interface) ─────────────────────────
|
||||||
public static $redis = null;
|
public static $redis = null;
|
||||||
public static $redis_expireTime = 0;
|
public static $redis_expireTime = 0;
|
||||||
|
public static $redis_no_disponible = false;
|
||||||
public static $force_load_cache = false;
|
public static $force_load_cache = false;
|
||||||
public static $force_redis = false;
|
public static $force_redis = false;
|
||||||
public static $force_redis_module = false; // consumed by external cache layer
|
public static $force_redis_module = false; // consumed by external cache layer
|
||||||
@@ -647,14 +648,29 @@ class AcaiAPI
|
|||||||
/** Connect to Redis (idempotent). */
|
/** Connect to Redis (idempotent). */
|
||||||
static function initCache()
|
static function initCache()
|
||||||
{
|
{
|
||||||
|
// Sin Redis (hosting propio sin el servicio o sin la extensión, o el
|
||||||
|
// servicio caído) la web sigue SIN caché en vez de morir con un fatal:
|
||||||
|
// cacheGet/cacheSet ya no hacen nada si no hay conexión. Se marca para
|
||||||
|
// no reintentar en cada consulta de la misma petición.
|
||||||
|
if (self::$redis_no_disponible) return;
|
||||||
|
if (!class_exists('Redis')) {
|
||||||
|
self::$redis_no_disponible = true;
|
||||||
|
self::$redis = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$redisHost = getenv('REDIS_HOST') ?: '127.0.0.1';
|
$redisHost = getenv('REDIS_HOST') ?: '127.0.0.1';
|
||||||
$redisPort = (int)(getenv('REDIS_PORT') ?: 6379);
|
$redisPort = (int)(getenv('REDIS_PORT') ?: 6379);
|
||||||
|
|
||||||
if (!self::$redis) {
|
try {
|
||||||
self::$redis = new Redis();
|
if (!self::$redis) self::$redis = new Redis();
|
||||||
self::$redis->connect($redisHost, $redisPort);
|
if (!self::$redis->isConnected() && !self::$redis->connect($redisHost, $redisPort)) {
|
||||||
} elseif (!self::$redis->isConnected()) {
|
throw new RuntimeException("no se pudo conectar a $redisHost:$redisPort");
|
||||||
self::$redis->connect($redisHost, $redisPort);
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
self::$redis_no_disponible = true;
|
||||||
|
self::$redis = null;
|
||||||
|
error_log('[cache] Redis no disponible, se sigue sin caché: ' . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2164,13 +2180,58 @@ class AcaiAPI
|
|||||||
$childNums[(int) $row['parentNum']][] = $row['num'];
|
$childNums[(int) $row['parentNum']][] = $row['num'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$before = $byNum;
|
||||||
|
|
||||||
self::_rebuildCategoryBranch([
|
self::_rebuildCategoryBranch([
|
||||||
'branchParent' => 0,
|
'branchParent' => 0,
|
||||||
'records' => &$byNum,
|
'records' => &$byNum,
|
||||||
'childNodes' => $childNums,
|
'childNodes' => $childNums,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Solo las filas que cambian, y todas en UNA transaccion. Antes era un
|
||||||
|
// UPDATE por fila de la tabla entera en autocommit: en InnoDB cada uno
|
||||||
|
// es un commit a disco (8.168 filas = 1,9 s; en una transaccion, 0,7 s),
|
||||||
|
// y mover un nodo deja casi siempre intacta buena parte del arbol.
|
||||||
|
// START TRANSACTION por SQL y no con PDO: `Db` no expone la conexion, y
|
||||||
|
// anadirle un metodo aqui romperia las webs con un Db.php propio (el
|
||||||
|
// sync de web-base respeta los ficheros personalizados). En MyISAM es
|
||||||
|
// inocuo.
|
||||||
|
$changed = [];
|
||||||
foreach ($byNum as $num => $cat) {
|
foreach ($byNum as $num => $cat) {
|
||||||
|
if (self::_categoryRowChanged($before[$num], $cat)) $changed[$num] = $cat;
|
||||||
|
}
|
||||||
|
if (!$changed) return;
|
||||||
|
|
||||||
|
$db->execute("START TRANSACTION");
|
||||||
|
try {
|
||||||
|
self::_writeCategoryRows($db, $fullTable, $changed);
|
||||||
|
$db->execute("COMMIT");
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$db->execute("ROLLBACK");
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ¿Cambia alguno de los campos que recalcula el arbol? PDO devuelve
|
||||||
|
* tipos nativos y el recalculo mezcla int y string: se compara como
|
||||||
|
* texto, salvo NULL, que solo es igual a NULL. */
|
||||||
|
private static function _categoryRowChanged(array $before, array $after)
|
||||||
|
{
|
||||||
|
foreach (['globalOrder', 'siblingOrder', 'depth', 'lineage', 'breadcrumb'] as $field) {
|
||||||
|
$a = isset($before[$field]) ? $before[$field] : null;
|
||||||
|
$b = isset($after[$field]) ? $after[$field] : null;
|
||||||
|
if ($a === null || $b === null) {
|
||||||
|
if ($a !== $b) return true;
|
||||||
|
} elseif ((string) $a !== (string) $b) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function _writeCategoryRows($db, $fullTable, array $rows)
|
||||||
|
{
|
||||||
|
foreach ($rows as $num => $cat) {
|
||||||
$db->execute(
|
$db->execute(
|
||||||
"UPDATE `$fullTable`
|
"UPDATE `$fullTable`
|
||||||
SET globalOrder = ?,
|
SET globalOrder = ?,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class CocoDB
|
|||||||
public static $trackData = [];
|
public static $trackData = [];
|
||||||
public static $redis = null;
|
public static $redis = null;
|
||||||
public static $redis_expireTime = 0;
|
public static $redis_expireTime = 0;
|
||||||
|
public static $redis_no_disponible = false;
|
||||||
public static $force_load_cache = false;
|
public static $force_load_cache = false;
|
||||||
public static $force_redis = false;
|
public static $force_redis = false;
|
||||||
public static $force_redis_module = false;
|
public static $force_redis_module = false;
|
||||||
@@ -1176,14 +1177,29 @@ class CocoDB
|
|||||||
*/
|
*/
|
||||||
static function initCache()
|
static function initCache()
|
||||||
{
|
{
|
||||||
|
// Sin Redis (hosting propio sin el servicio o sin la extensión, o el
|
||||||
|
// servicio caído) la web sigue SIN caché en vez de morir con un fatal:
|
||||||
|
// cacheGet/cacheSet ya no hacen nada si no hay conexión. Se marca para
|
||||||
|
// no reintentar en cada consulta de la misma petición.
|
||||||
|
if (self::$redis_no_disponible) return;
|
||||||
|
if (!class_exists('Redis')) {
|
||||||
|
self::$redis_no_disponible = true;
|
||||||
|
self::$redis = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$redisHost = getenv('REDIS_HOST') ?: '127.0.0.1';
|
$redisHost = getenv('REDIS_HOST') ?: '127.0.0.1';
|
||||||
$redisPort = (int)(getenv('REDIS_PORT') ?: 6379);
|
$redisPort = (int)(getenv('REDIS_PORT') ?: 6379);
|
||||||
|
|
||||||
if (!self::$redis) {
|
try {
|
||||||
self::$redis = new Redis();
|
if (!self::$redis) self::$redis = new Redis();
|
||||||
self::$redis->connect($redisHost, $redisPort);
|
if (!self::$redis->isConnected() && !self::$redis->connect($redisHost, $redisPort)) {
|
||||||
} else if (!self::$redis->isConnected()) {
|
throw new RuntimeException("no se pudo conectar a $redisHost:$redisPort");
|
||||||
self::$redis->connect($redisHost, $redisPort);
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
self::$redis_no_disponible = true;
|
||||||
|
self::$redis = null;
|
||||||
|
error_log('[cache] Redis no disponible, se sigue sin caché: ' . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -406,10 +406,13 @@ function _renameFile() {
|
|||||||
* (install/uninstall/enable/disable) quedaban inservibles ahi con un
|
* (install/uninstall/enable/disable) quedaban inservibles ahi con un
|
||||||
* "Token no valido" que no tenia nada que ver con el token.
|
* "Token no valido" que no tenia nada que ver con el token.
|
||||||
*
|
*
|
||||||
* No es un bypass: se compara el par token+tokenHash recibido con el que la
|
* No es un bypass: se compara un secreto recibido en el cuerpo con el que la
|
||||||
* propia web guarda en su .acai, que es exactamente el que Forge envia. Sin
|
* propia web guarda en su .acai. Sin .acai (produccion), con mode=production,
|
||||||
* .acai (produccion), con mode=production, o sin coincidencia exacta, no
|
* o sin coincidencia exacta, no autoriza nada. Dos formas de coincidir:
|
||||||
* autoriza nada.
|
* - secretoLocal (cuerpo) == secreto_local (.acai): el secreto de la copia,
|
||||||
|
* que no es una credencial de ninguna persona. Es la via buena.
|
||||||
|
* - token+tokenHash (cuerpo) == token+tokenHash (.acai): la de siempre.
|
||||||
|
* Las cadenas vacias nunca coinciden. Los secretos no se registran nunca.
|
||||||
*/
|
*/
|
||||||
function _lifecycleTokenLocalValido($datosAuth) {
|
function _lifecycleTokenLocalValido($datosAuth) {
|
||||||
$acaiPath = __DIR__ . "/../../../.acai";
|
$acaiPath = __DIR__ . "/../../../.acai";
|
||||||
@@ -420,10 +423,24 @@ function _lifecycleTokenLocalValido($datosAuth) {
|
|||||||
if (!is_array($acai) || @$acai["mode"] === "production") {
|
if (!is_array($acai) || @$acai["mode"] === "production") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$token = (string) @$datosAuth["token"];
|
if (!is_array($datosAuth)) {
|
||||||
$hash = (string) @$datosAuth["tokenHash"];
|
return false;
|
||||||
$tokenLocal = (string) @$acai["token"];
|
}
|
||||||
$hashLocal = (string) @$acai["tokenHash"];
|
|
||||||
|
// Via buena: el secreto propio de la copia local.
|
||||||
|
$secretoLocal = @$acai["secreto_local"];
|
||||||
|
$secreto = @$datosAuth["secretoLocal"];
|
||||||
|
if (is_string($secretoLocal) && $secretoLocal !== ""
|
||||||
|
&& is_scalar($secreto) && (string) $secreto !== ""
|
||||||
|
&& hash_equals($secretoLocal, (string) $secreto)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// transitorio: quitar cuando el .acai ya no lleve token (fase 1d)
|
||||||
|
$token = is_scalar(@$datosAuth["token"]) ? (string) $datosAuth["token"] : "";
|
||||||
|
$hash = is_scalar(@$datosAuth["tokenHash"]) ? (string) $datosAuth["tokenHash"] : "";
|
||||||
|
$tokenLocal = is_scalar(@$acai["token"]) ? (string) $acai["token"] : "";
|
||||||
|
$hashLocal = is_scalar(@$acai["tokenHash"]) ? (string) $acai["tokenHash"] : "";
|
||||||
if ($token === "" || $hash === "" || $tokenLocal === "" || $hashLocal === "") {
|
if ($token === "" || $hash === "" || $tokenLocal === "" || $hashLocal === "") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-1
@@ -20,7 +20,15 @@ if (file_exists($filePath)) {
|
|||||||
# <?=strtoupper($configuracionRecord["tienda_nombre_empresa"]);?>
|
# <?=strtoupper($configuracionRecord["tienda_nombre_empresa"]);?>
|
||||||
|
|
||||||
User-agent: *
|
User-agent: *
|
||||||
<? if (strpos($_SERVER["HTTP_HOST"], "plandeweb.com")) {?>
|
<?
|
||||||
|
// Cerrada mientras no este publicada: la regla es `acaiRobotsBloqueados` del
|
||||||
|
// plugin builder_saas (la misma de header.php y de la puerta de las webs
|
||||||
|
// Astro). Un robots.txt propio de la web manda sobre esto (arriba). Sin el
|
||||||
|
// plugin, como siempre: solo se cierran los dominios de pruebas.
|
||||||
|
$robotsBloqueados = strpos($_SERVER["HTTP_HOST"], "plandeweb.com")
|
||||||
|
|| (function_exists("acaiRobotsBloqueados")
|
||||||
|
&& acaiRobotsBloqueados(is_file(dirname(__DIR__) . "/.acai"), @$configuracionRecord['pagina_publicada']));
|
||||||
|
if ($robotsBloqueados) {?>
|
||||||
Disallow: /
|
Disallow: /
|
||||||
<? }?>
|
<? }?>
|
||||||
<? if (@$configuracionRecord['pagina_publicada']) {?>
|
<? if (@$configuracionRecord['pagina_publicada']) {?>
|
||||||
|
|||||||
+5
-2
@@ -7,8 +7,11 @@ $modulosCargados = array();
|
|||||||
$recursosCSS = array();
|
$recursosCSS = array();
|
||||||
// HASTA AUQI
|
// HASTA AUQI
|
||||||
|
|
||||||
if (!file_exists(__DIR__.'/'.CMS_FOLDER.'/uploads/webp/')) {
|
// Sin carrera: varias peticiones a la vez (el prerender de Astro las lanza en
|
||||||
mkdir(__DIR__.'/'.CMS_FOLDER.'/uploads/webp/');
|
// paralelo) veian la carpeta sin crear y la segunda imprimia "mkdir(): File
|
||||||
|
// exists" delante del JSON de action_ws, rompiendo el build de una web nueva.
|
||||||
|
if (!is_dir(__DIR__.'/'.CMS_FOLDER.'/uploads/webp/')) {
|
||||||
|
@mkdir(__DIR__.'/'.CMS_FOLDER.'/uploads/webp/', 0775, true);
|
||||||
}
|
}
|
||||||
global $configuracionTienda;
|
global $configuracionTienda;
|
||||||
$configuracionRecord = CocoDB::get("configuracion", "", null, 1);
|
$configuracionRecord = CocoDB::get("configuracion", "", null, 1);
|
||||||
|
|||||||
+18
-2
@@ -78,16 +78,32 @@ $config_header = array(
|
|||||||
// prod + publicada -> se ve
|
// prod + publicada -> se ve
|
||||||
// prod + sin publicar + ?pruebas -> se ve, con el cartel rojo
|
// prod + sin publicar + ?pruebas -> se ve, con el cartel rojo
|
||||||
// prod + sin publicar + visitante -> mantenimiento
|
// prod + sin publicar + visitante -> mantenimiento
|
||||||
|
//
|
||||||
|
// La tabla de arriba es UNA regla para todas las webs (PHP y Astro) y para el
|
||||||
|
// robots.txt: `acaiVisibilidadWeb` del plugin builder_saas (ws_respond.php),
|
||||||
|
// que se reparte entero a todas las webs. Se llama con function_exists por la
|
||||||
|
// misma razon que la señal va en linea: si una web no trae el plugin, se
|
||||||
|
// queda con la RESERVA de abajo, que es la regla de siempre (y el cartel con
|
||||||
|
// 200, sin las cabeceras 503 de `acaiCabecerasMantenimiento`).
|
||||||
$esEntornoLocal = is_file(dirname(__FILE__) . "/.acai");
|
$esEntornoLocal = is_file(dirname(__FILE__) . "/.acai");
|
||||||
|
|
||||||
if ($esEntornoLocal || @$configuracionRecord["pagina_publicada"] || isset($_SESSION["pruebas"])) {
|
if (function_exists("acaiVisibilidadWeb")) {
|
||||||
|
$visibilidadWeb = acaiVisibilidadWeb($esEntornoLocal, @$configuracionRecord["pagina_publicada"], isset($_SESSION["pruebas"]));
|
||||||
|
} else {
|
||||||
|
// RESERVA (sin el plugin): la misma tabla, en linea.
|
||||||
|
$visibilidadWeb = ($esEntornoLocal || @$configuracionRecord["pagina_publicada"]) ? "visible"
|
||||||
|
: (isset($_SESSION["pruebas"]) ? "pruebas" : "mantenimiento");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($visibilidadWeb !== "mantenimiento") {
|
||||||
if (@$customCode) echo modulo('custom-header',$config_header); else echo tpl('cabecera',$config_header);
|
if (@$customCode) echo modulo('custom-header',$config_header); else echo tpl('cabecera',$config_header);
|
||||||
if (!$esEntornoLocal && isset($_SESSION["pruebas"]) && @$configuracionRecord["pagina_publicada"] == 0) {
|
if ($visibilidadWeb === "pruebas") {
|
||||||
?>
|
?>
|
||||||
<div style="position:absolute;top:0px;width:150px;background-color:red;color:#fff;font-size:12px;padding:0 10px;z-index:99999999;transform:translateZ(1000px);">SITIO EN PRUEBAS</div>
|
<div style="position:absolute;top:0px;width:150px;background-color:red;color:#fff;font-size:12px;padding:0 10px;z-index:99999999;transform:translateZ(1000px);">SITIO EN PRUEBAS</div>
|
||||||
<?
|
<?
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (function_exists("acaiCabecerasMantenimiento")) acaiCabecerasMantenimiento();
|
||||||
die(tpl('mantenimiento',$config_header));
|
die(tpl('mantenimiento',$config_header));
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|||||||
Reference in New Issue
Block a user