Compare commits
3 Commits
b509b1a2b4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85f3387fd2 | ||
|
|
ff5ba25505 | ||
|
|
eab974d14c |
82
Shared/Infrastructure/Hooks/CacheFirstHooksRegistrar.php
Normal file
82
Shared/Infrastructure/Hooks/CacheFirstHooksRegistrar.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Shared\Infrastructure\Hooks;
|
||||
|
||||
/**
|
||||
* Registra hooks para arquitectura cache-first.
|
||||
*
|
||||
* Permite que plugins externos evalúen condiciones ANTES de servir páginas,
|
||||
* sin bloquear el cache de WordPress.
|
||||
*
|
||||
* @see openspec/specs/cache-first-architecture/spec.md
|
||||
* @package ROITheme\Shared\Infrastructure\Hooks
|
||||
*/
|
||||
final class CacheFirstHooksRegistrar
|
||||
{
|
||||
/**
|
||||
* Registra los hooks de cache-first.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
add_action('template_redirect', [$this, 'fireBeforePageServe'], 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispara hook para que plugins externos evalúen acceso.
|
||||
*
|
||||
* Solo se dispara para:
|
||||
* - Páginas singulares (posts, pages, CPTs)
|
||||
* - Visitantes NO logueados (cache no aplica a usuarios logueados)
|
||||
*
|
||||
* Los plugins pueden llamar wp_safe_redirect() + exit para bloquear.
|
||||
* Si no hacen nada, la página se sirve normalmente (con cache si disponible).
|
||||
*/
|
||||
public function fireBeforePageServe(): void
|
||||
{
|
||||
// No para usuarios logueados (cache no aplica, no tiene sentido evaluar)
|
||||
if (is_user_logged_in()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Solo páginas singulares
|
||||
if (!is_singular()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// No en admin/ajax/cron/REST
|
||||
if (is_admin() || wp_doing_ajax() || wp_doing_cron()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (defined('REST_REQUEST') && REST_REQUEST) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post_id = get_queried_object_id();
|
||||
|
||||
if ($post_id > 0) {
|
||||
/**
|
||||
* Hook: roi_theme_before_page_serve
|
||||
*
|
||||
* Permite que plugins externos evalúen condiciones antes de servir página.
|
||||
*
|
||||
* Uso típico:
|
||||
* - Rate limiters (límite de vistas por IP)
|
||||
* - Membership plugins (verificar acceso)
|
||||
* - Geolocation restrictions
|
||||
*
|
||||
* Para bloquear acceso:
|
||||
* wp_safe_redirect('/pagina-destino/', 302);
|
||||
* exit;
|
||||
*
|
||||
* Para permitir acceso:
|
||||
* return; // La página se servirá (con cache si disponible)
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param int $post_id ID del post/page que se va a servir
|
||||
*/
|
||||
do_action('roi_theme_before_page_serve', $post_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,27 @@
|
||||
<?php
|
||||
|
||||
// =============================================================================
|
||||
// ROI THEME DEBUG MODE
|
||||
// =============================================================================
|
||||
// Para activar el modo debug, agregar en wp-config.php:
|
||||
// define('ROI_DEBUG', true);
|
||||
//
|
||||
// IMPORTANTE: Mantener desactivado en producción para evitar logs de GB.
|
||||
// =============================================================================
|
||||
if (!defined('ROI_DEBUG')) {
|
||||
define('ROI_DEBUG', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log de debug condicional para ROI Theme.
|
||||
* Solo escribe al log si ROI_DEBUG está activado.
|
||||
*/
|
||||
function roi_debug_log(string $message): void {
|
||||
if (ROI_DEBUG) {
|
||||
error_log($message);
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// AUTOLOADER PARA COMPONENTES
|
||||
// =============================================================================
|
||||
@@ -181,7 +203,7 @@ function roi_render_component(string $componentName): string {
|
||||
global $wpdb;
|
||||
|
||||
// DEBUG: Trace component rendering
|
||||
error_log("ROI Theme DEBUG: roi_render_component called with: {$componentName}");
|
||||
roi_debug_log("ROI Theme DEBUG: roi_render_component called with: {$componentName}");
|
||||
|
||||
try {
|
||||
// Obtener datos del componente desde BD normalizada
|
||||
@@ -297,9 +319,9 @@ function roi_render_component(string $componentName): string {
|
||||
$renderer = new \ROITheme\Public\Navbar\Infrastructure\Ui\NavbarRenderer($cssGenerator);
|
||||
break;
|
||||
case 'hero':
|
||||
error_log("ROI Theme DEBUG: Creating HeroRenderer");
|
||||
roi_debug_log("ROI Theme DEBUG: Creating HeroRenderer");
|
||||
$renderer = new \ROITheme\Public\Hero\Infrastructure\Ui\HeroRenderer($cssGenerator);
|
||||
error_log("ROI Theme DEBUG: HeroRenderer created successfully");
|
||||
roi_debug_log("ROI Theme DEBUG: HeroRenderer created successfully");
|
||||
break;
|
||||
|
||||
// Componentes sin soporte de CSS Crítico (below-the-fold)
|
||||
@@ -339,13 +361,13 @@ function roi_render_component(string $componentName): string {
|
||||
}
|
||||
|
||||
if (!$renderer) {
|
||||
error_log("ROI Theme DEBUG: No renderer for {$componentName}");
|
||||
roi_debug_log("ROI Theme DEBUG: No renderer for {$componentName}");
|
||||
return '';
|
||||
}
|
||||
|
||||
error_log("ROI Theme DEBUG: Calling render() for {$componentName}");
|
||||
roi_debug_log("ROI Theme DEBUG: Calling render() for {$componentName}");
|
||||
$output = $renderer->render($component);
|
||||
error_log("ROI Theme DEBUG: render() returned " . strlen($output) . " chars for {$componentName}");
|
||||
roi_debug_log("ROI Theme DEBUG: render() returned " . strlen($output) . " chars for {$componentName}");
|
||||
return $output;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
|
||||
@@ -174,6 +174,12 @@ try {
|
||||
);
|
||||
$youtubeFacadeHooksRegistrar->register();
|
||||
|
||||
// === CACHE-FIRST ARCHITECTURE (Plan 1000.01) ===
|
||||
// Hook para plugins externos que necesitan evaluar acceso antes de servir página
|
||||
// @see openspec/specs/cache-first-architecture/spec.md
|
||||
$cacheFirstHooksRegistrar = new \ROITheme\Shared\Infrastructure\Hooks\CacheFirstHooksRegistrar();
|
||||
$cacheFirstHooksRegistrar->register();
|
||||
|
||||
// Log en modo debug
|
||||
if (defined('WP_DEBUG') && WP_DEBUG) {
|
||||
error_log('ROI Theme: Admin Panel initialized successfully');
|
||||
|
||||
154
openspec/specs/cache-first-architecture/spec.md
Normal file
154
openspec/specs/cache-first-architecture/spec.md
Normal file
@@ -0,0 +1,154 @@
|
||||
# Especificacion de Arquitectura Cache-First
|
||||
|
||||
## Purpose
|
||||
|
||||
Define la arquitectura cache-first para ROITheme que permite que plugins de cache (W3TC, Redis, etc.) funcionen correctamente mientras plugins externos pueden evaluar condiciones ANTES de servir paginas.
|
||||
|
||||
Esta arquitectura:
|
||||
- Permite que las paginas se sirvan desde cache siempre que sea posible
|
||||
- Provee hooks para que plugins externos (rate limiters, access control, etc.) evaluen condiciones
|
||||
- Desacopla el tema de plugins especificos de restriccion de acceso
|
||||
- Es portable: cualquier sitio con roi-theme tiene esta capacidad
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: Hook de Pre-Evaluacion de Pagina
|
||||
|
||||
The system MUST provide a hook that fires BEFORE WordPress serves a page, allowing external plugins to evaluate conditions and potentially redirect.
|
||||
|
||||
#### Scenario: Plugin externo necesita evaluar acceso antes de servir pagina
|
||||
- **GIVEN** un plugin de control de acceso (rate limiter, membership, etc.)
|
||||
- **WHEN** un visitante solicita una pagina singular (post, page, CPT)
|
||||
- **THEN** el tema DEBE disparar `do_action('roi_theme_before_page_serve', $post_id)`
|
||||
- **AND** el hook DEBE ejecutarse en `template_redirect` con priority 0
|
||||
- **AND** si el plugin llama `wp_safe_redirect()` y `exit`, la pagina NO se sirve
|
||||
|
||||
#### Scenario: Ningn plugin enganchado al hook
|
||||
- **GIVEN** ningun plugin esta escuchando `roi_theme_before_page_serve`
|
||||
- **WHEN** un visitante solicita una pagina
|
||||
- **THEN** la pagina se sirve normalmente (con cache si disponible)
|
||||
- **AND** no hay impacto en rendimiento
|
||||
|
||||
#### Scenario: Hook solo dispara en paginas singulares
|
||||
- **GIVEN** el hook `roi_theme_before_page_serve`
|
||||
- **WHEN** la solicitud es para archivo, home, search, feed, o admin
|
||||
- **THEN** el hook NO DEBE dispararse
|
||||
- **AND** la pagina se sirve sin evaluacion adicional
|
||||
|
||||
#### Scenario: Hook no dispara para usuarios logueados
|
||||
- **GIVEN** un usuario autenticado (logged in)
|
||||
- **WHEN** solicita cualquier pagina
|
||||
- **THEN** el hook NO DEBE dispararse
|
||||
- **AND** la pagina se sirve directamente sin evaluacion
|
||||
- **BECAUSE** WordPress no cachea paginas para usuarios logueados (cookies de sesion)
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Contexto Rico para Plugins Externos
|
||||
|
||||
The system MUST provide sufficient context for external plugins to make access decisions.
|
||||
|
||||
#### Scenario: Plugin necesita informacion del post
|
||||
- **GIVEN** un plugin enganchado a `roi_theme_before_page_serve`
|
||||
- **WHEN** el hook se dispara
|
||||
- **THEN** el plugin recibe `$post_id` como parametro
|
||||
- **AND** `get_queried_object()` retorna el objeto WP_Post completo
|
||||
- **AND** `is_singular()`, `is_single()`, `is_page()` funcionan correctamente
|
||||
|
||||
#### Scenario: Plugin necesita informacion del visitante
|
||||
- **GIVEN** un plugin enganchado a `roi_theme_before_page_serve`
|
||||
- **WHEN** el hook se dispara
|
||||
- **THEN** `is_user_logged_in()` esta disponible
|
||||
- **AND** `$_SERVER['REMOTE_ADDR']` esta disponible
|
||||
- **AND** headers HTTP estan disponibles via `$_SERVER`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: No Bloquear Cache
|
||||
|
||||
The system MUST NOT define DONOTCACHEPAGE or similar constants that prevent caching.
|
||||
|
||||
#### Scenario: Tema no interfiere con plugins de cache
|
||||
- **GIVEN** el tema roi-theme instalado
|
||||
- **WHEN** W3TC, WP Super Cache, o Redis Object Cache estan activos
|
||||
- **THEN** el tema NO DEBE definir `DONOTCACHEPAGE`
|
||||
- **AND** el tema NO DEBE definir `DONOTCACHEOBJECT`
|
||||
- **AND** el tema NO DEBE enviar headers `Cache-Control: no-cache`
|
||||
|
||||
#### Scenario: Plugin externo decide bloquear cache
|
||||
- **GIVEN** un plugin enganchado a `roi_theme_before_page_serve`
|
||||
- **WHEN** el plugin necesita bloquear cache para una pagina especifica
|
||||
- **THEN** es responsabilidad del PLUGIN definir `DONOTCACHEPAGE`
|
||||
- **AND** el tema NO participa en esa decision
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Ubicacion en Clean Architecture
|
||||
|
||||
The hook registration MUST follow ROITheme's Clean Architecture patterns.
|
||||
|
||||
#### Scenario: Hook registrado en Infrastructure
|
||||
- **GIVEN** el sistema de hooks del tema
|
||||
- **WHEN** el hook `roi_theme_before_page_serve` es registrado
|
||||
- **THEN** el registro DEBE estar en `Shared/Infrastructure/Hooks/`
|
||||
- **AND** DEBE seguir el patron de HooksRegistrar existente
|
||||
|
||||
#### Scenario: Servicio como punto de extension
|
||||
- **GIVEN** la arquitectura del tema
|
||||
- **WHEN** se necesita extender funcionalidad de pre-evaluacion
|
||||
- **THEN** DEBE existir una interfaz en `Shared/Domain/Contracts/`
|
||||
- **AND** la implementacion DEBE estar en `Shared/Infrastructure/Services/`
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### Hook Priority
|
||||
|
||||
```
|
||||
template_redirect priority order:
|
||||
├─ Priority 0: roi_theme_before_page_serve (tema dispara hook)
|
||||
│ └─ Plugins externos se enganchan aqui
|
||||
├─ Priority 1+: Otros plugins
|
||||
└─ Priority 10 (default): WordPress template loading
|
||||
```
|
||||
|
||||
### Ejemplo de Plugin Enganchado
|
||||
|
||||
```php
|
||||
// En un plugin externo (ej: ip-rate-limiter)
|
||||
add_action('roi_theme_before_page_serve', function(int $post_id) {
|
||||
// Evaluar condicion (ej: limite de IP)
|
||||
if ($this->is_limit_exceeded()) {
|
||||
wp_safe_redirect('/suscripcion-vip/?reason=limit', 302);
|
||||
exit;
|
||||
}
|
||||
// Si no hay problema, simplemente retornar
|
||||
// La pagina se servira (con cache si disponible)
|
||||
}, 10);
|
||||
```
|
||||
|
||||
### Archivos a Crear
|
||||
|
||||
```
|
||||
Shared/
|
||||
├── Domain/
|
||||
│ └── Contracts/
|
||||
│ └── PageServeHookInterface.php # Interface del hook
|
||||
└── Infrastructure/
|
||||
└── Hooks/
|
||||
└── CacheFirstHooksRegistrar.php # Registro del hook
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
1. El hook `roi_theme_before_page_serve` se dispara en `template_redirect` priority 0
|
||||
2. Solo se dispara para `is_singular() === true`
|
||||
3. NO se dispara para usuarios logueados (`is_user_logged_in() === true`)
|
||||
4. Pasa `$post_id` como parametro al hook
|
||||
5. No define DONOTCACHEPAGE ni headers anti-cache
|
||||
6. Plugins externos pueden enganchar y hacer redirect/exit
|
||||
7. Si ningun plugin engancha, no hay impacto en rendimiento
|
||||
8. Sigue patrones de Clean Architecture del tema
|
||||
Reference in New Issue
Block a user