3 Commits

Author SHA1 Message Date
FrankZamora
85f3387fd2 perf(php): add conditional debug logging to prevent gb logs
- add ROI_DEBUG constant (default false) to control debug output
- create roi_debug_log() function for conditional logging
- replace all error_log DEBUG calls with roi_debug_log
- keep ERROR logs always active for exception tracking
- to enable debug, add define('ROI_DEBUG', true) in wp-config.php

this prevents production logs from growing to gb sizes
(previous error.log was 4.8gb from constant debug output)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-07 17:52:50 -06:00
FrankZamora
ff5ba25505 feat(php): implement cache-first architecture hook
Add CacheFirstHooksRegistrar that fires roi_theme_before_page_serve
hook on template_redirect priority 0 for singular pages.

- Only fires for anonymous users (cache doesn't apply to logged in)
- Only fires for singular pages (posts, pages, CPTs)
- Provides post_id to external plugins
- Does NOT define DONOTCACHEPAGE (allows page caching)

Plan 1000.01 implementation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-07 12:11:48 -06:00
FrankZamora
eab974d14c docs(config): add cache-first-architecture specification
Define arquitectura cache-first para ROI-Theme:
- Hook roi_theme_before_page_serve en template_redirect p=0
- Solo para páginas singulares y usuarios anónimos
- Permite que plugins externos evalúen acceso antes de servir página
- NO define DONOTCACHEPAGE (permite cache)

Plan 1000.01 - Preparación para integración con IP View Limit.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-07 12:07:14 -06:00
4 changed files with 270 additions and 6 deletions

View 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);
}
}
}

View File

@@ -1,5 +1,27 @@
<?php <?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 // AUTOLOADER PARA COMPONENTES
// ============================================================================= // =============================================================================
@@ -181,7 +203,7 @@ function roi_render_component(string $componentName): string {
global $wpdb; global $wpdb;
// DEBUG: Trace component rendering // 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 { try {
// Obtener datos del componente desde BD normalizada // 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); $renderer = new \ROITheme\Public\Navbar\Infrastructure\Ui\NavbarRenderer($cssGenerator);
break; break;
case 'hero': 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); $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; break;
// Componentes sin soporte de CSS Crítico (below-the-fold) // Componentes sin soporte de CSS Crítico (below-the-fold)
@@ -339,13 +361,13 @@ function roi_render_component(string $componentName): string {
} }
if (!$renderer) { if (!$renderer) {
error_log("ROI Theme DEBUG: No renderer for {$componentName}"); roi_debug_log("ROI Theme DEBUG: No renderer for {$componentName}");
return ''; return '';
} }
error_log("ROI Theme DEBUG: Calling render() for {$componentName}"); roi_debug_log("ROI Theme DEBUG: Calling render() for {$componentName}");
$output = $renderer->render($component); $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; return $output;
} catch (\Exception $e) { } catch (\Exception $e) {

View File

@@ -174,6 +174,12 @@ try {
); );
$youtubeFacadeHooksRegistrar->register(); $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 // Log en modo debug
if (defined('WP_DEBUG') && WP_DEBUG) { if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('ROI Theme: Admin Panel initialized successfully'); error_log('ROI Theme: Admin Panel initialized successfully');

View 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