# Especificacion: Unificacion de Visibilidad AdSense para Compatibilidad con Cache ## Purpose Unificar la logica de visibilidad de AdSense para que TODA la evaluacion dependiente de usuario (hide_for_logged_in) se realice en el cliente via JavaScript-First, eliminando llamadas a `is_user_logged_in()` durante el render PHP. **Problema actual:** El `AdsensePlacementRenderer` usa `PageVisibilityHelper::shouldShow()` que internamente llama `is_user_logged_in()` en PHP. Esto causa que el HTML generado varie segun el estado de autenticacion, rompiendo la compatibilidad con page cache. **Solucion:** Cuando `javascript_first_mode` esta activo, el Renderer debe generar SIEMPRE el HTML de los slots, delegando la decision de `hide_for_logged_in` al endpoint REST y al JavaScript del cliente. --- ## Background ### Arquitectura JavaScript-First existente (spec: adsense-javascript-first) El sistema JavaScript-First ya implementa: 1. **Endpoint REST** `/wp-json/roi-theme/v1/adsense-placement/visibility` - Evalua `is_user_logged_in()` en tiempo real (no cacheado) - Retorna decision `show_ads: true/false` con razones - Headers anti-cache (no-store, no-cache) 2. **Cliente JavaScript** `adsense-visibility.js` - Consulta endpoint via AJAX con cookies (credentials: same-origin) - Cachea decision en localStorage - Aplica clases CSS para mostrar/ocultar slots ### El gap actual ``` FLUJO ACTUAL (PROBLEMATICO): Request → PHP Render → PageVisibilityHelper::shouldShow() ↓ is_user_logged_in() ← ROMPE CACHE ↓ HTML con/sin slots (depende de login) ↓ Page Cache ← HTML incorrecto para otros usuarios ``` ``` FLUJO DESEADO (CON ESTA SPEC): Request → PHP Render → [javascript_first_mode=true?] ↓ SI SIEMPRE genera HTML con slots ↓ Page Cache ← HTML identico para todos ↓ JS consulta endpoint REST ↓ Muestra/oculta segun respuesta ``` --- ## Requirements ### Requirement 1: Bypass de hide_for_logged_in en modo JavaScript-First The AdsensePlacementRenderer MUST skip the `is_user_logged_in()` evaluation during PHP render when `javascript_first_mode` is enabled. #### Scenario: JavaScript-First activo, usuario anonimo - **GIVEN** `javascript_first_mode` esta habilitado en settings - **AND** `hide_for_logged_in` esta habilitado - **AND** el visitante NO esta logueado - **WHEN** se renderiza un slot de AdSense - **THEN** el HTML del slot DEBE generarse (placeholders visibles) - **AND** el JavaScript DEBE consultar el endpoint - **AND** el endpoint retorna `show_ads: true` - **AND** los anuncios se muestran #### Scenario: JavaScript-First activo, usuario logueado - **GIVEN** `javascript_first_mode` esta habilitado - **AND** `hide_for_logged_in` esta habilitado - **AND** el visitante ESTA logueado - **WHEN** se renderiza un slot de AdSense - **THEN** el HTML del slot DEBE generarse (en cache seria identico a anonimo) - **AND** el JavaScript DEBE consultar el endpoint - **AND** el endpoint retorna `show_ads: false` con razon `hide_for_logged_in` - **AND** los anuncios se OCULTAN via CSS/JS #### Scenario: JavaScript-First deshabilitado (modo legacy) - **GIVEN** `javascript_first_mode` esta DESHABILITADO - **AND** `hide_for_logged_in` esta habilitado - **AND** el visitante ESTA logueado - **WHEN** se renderiza un slot de AdSense - **THEN** el HTML del slot NO se genera (comportamiento legacy) - **AND** `is_user_logged_in()` SE evalua en PHP - **BECAUSE** sin JS-First, el modo legacy es la unica opcion --- ### Requirement 2: PageVisibilityHelper debe respetar modo JavaScript-First The PageVisibilityHelper MUST provide a method that excludes user-dependent checks when JavaScript-First mode is active for a component. #### Scenario: Nuevo metodo shouldShowForCache - **GIVEN** un componente con `javascript_first_mode` habilitado - **WHEN** se llama `PageVisibilityHelper::shouldShowForCache('adsense-placement')` - **THEN** DEBE evaluar visibilidad por tipo de pagina (home, posts, pages, etc.) - **AND** DEBE evaluar exclusiones por categoria, ID, URL pattern - **AND** NO DEBE evaluar `hide_for_logged_in` - **BECAUSE** esa evaluacion la hace el cliente #### Scenario: Componente sin JavaScript-First usa metodo existente - **GIVEN** un componente sin `javascript_first_mode` - **WHEN** se llama `PageVisibilityHelper::shouldShow('otro-componente')` - **THEN** DEBE usar flujo existente incluyendo `hide_for_logged_in` - **BECAUSE** sin JS-First, PHP es la unica fuente de verdad --- ### Requirement 3: WordPressComponentVisibilityRepository debe soportar modo bypass The repository MUST support skipping `is_user_logged_in()` check when requested. #### Scenario: isNotExcluded con bypass de login check - **GIVEN** se llama `isNotExcluded('adsense-placement', skipLoginCheck: true)` - **WHEN** el metodo evalua exclusiones - **THEN** NO DEBE llamar `is_user_logged_in()` - **AND** DEBE evaluar otras exclusiones normalmente --- ### Requirement 4: El endpoint REST DEBE evaluar hide_for_logged_in The REST endpoint `/adsense-placement/visibility` MUST evaluate `hide_for_logged_in` and include it in the decision. **NOTA:** Esto YA esta implementado en `AdsenseVisibilityController.php`. Solo documentamos para claridad. #### Scenario: Endpoint evalua usuario logueado - **GIVEN** peticion al endpoint con cookies de sesion - **AND** `hide_for_logged_in` esta habilitado - **AND** el usuario ESTA logueado - **WHEN** el endpoint procesa la peticion - **THEN** retorna `show_ads: false` - **AND** `reasons` incluye `hide_for_logged_in` --- ### Requirement 5: El CSS de slots ocultos NO debe afectar el layout When JavaScript hides ad slots, they MUST collapse completely without affecting page layout. **NOTA:** Esto YA esta implementado con `:has([data-ad-status='unfilled'])`. Documentamos para verificar que se mantiene. --- ## Implementation ### Archivos a modificar #### 1. AdsensePlacementRenderer.php ```php // ANTES (linea 38-43): public function renderSlot(array $settings, string $location): string { // 0. Verificar visibilidad por tipo de pagina y exclusiones if (!PageVisibilityHelper::shouldShow('adsense-placement')) { return ''; } // ... } // DESPUES: public function renderSlot(array $settings, string $location): string { // 0. Verificar visibilidad (respetando modo JS-First para cache) $jsFirstMode = ($settings['behavior']['javascript_first_mode'] ?? false) === true; if ($jsFirstMode) { // En modo JS-First, usar evaluacion cache-friendly (sin is_user_logged_in) if (!PageVisibilityHelper::shouldShowForCache('adsense-placement')) { return ''; } } else { // Modo legacy: usar evaluacion completa if (!PageVisibilityHelper::shouldShow('adsense-placement')) { return ''; } } // ... } ``` #### 2. PageVisibilityHelper.php ```php // AGREGAR nuevo metodo: /** * Evalua visibilidad SIN checks dependientes de usuario * * Para uso cuando JavaScript manejara los checks de usuario. * Evalua: tipos de pagina, exclusiones por categoria/ID/URL * NO evalua: hide_for_logged_in * * @param string $componentName * @return bool */ public static function shouldShowForCache(string $componentName): bool { $container = DIContainer::getInstance(); $useCase = $container->getEvaluateComponentVisibilityUseCase(); return $useCase->executeForCache($componentName); } ``` #### 3. EvaluateComponentVisibilityUseCase.php ```php // AGREGAR nuevo metodo: /** * Evalua visibilidad SIN checks dependientes de usuario */ public function executeForCache(string $componentName): bool { // Paso 1: Verificar visibilidad por tipo de pagina (sin cambios) $visibleByPageType = $this->pageVisibilityUseCase->execute($componentName); if (!$visibleByPageType) { return false; } // Paso 2: Verificar exclusiones SIN hide_for_logged_in $isExcluded = $this->exclusionsUseCase->executeForCache($componentName); return !$isExcluded; } ``` #### 4. EvaluateExclusionsUseCase.php ```php // AGREGAR nuevo metodo: /** * Evalua exclusiones SIN hide_for_logged_in */ public function executeForCache(string $componentName): bool { // Evaluar exclusiones por categoria, ID, URL // NO evaluar hide_for_logged_in return $this->repository->isExcludedForCache($componentName); } ``` #### 5. WordPressComponentVisibilityRepository.php ```php // MODIFICAR isNotExcluded o agregar nuevo metodo: /** * Verifica exclusiones SIN evaluar hide_for_logged_in */ public function isNotExcludedForCache(string $componentName): bool { // OMITE: shouldHideForLoggedIn() // MANTIENE: PageVisibilityHelper::shouldShow() para otras exclusiones return PageVisibilityHelper::shouldShowByPageType($componentName); } ``` --- ## Acceptance Criteria 1. Con `javascript_first_mode=true` y `hide_for_logged_in=true`: - Usuario anonimo: HTML generado, JS muestra ads - Usuario logueado: HTML generado, JS oculta ads - Page cache sirve mismo HTML a ambos 2. Con `javascript_first_mode=false`: - Comportamiento legacy sin cambios - `is_user_logged_in()` se evalua en PHP 3. Otras exclusiones (categoria, ID, URL) funcionan igual en ambos modos 4. No hay regresion en visibilidad por tipo de pagina 5. Los slots ocultos por JS colapsan completamente (height: 0) --- ## Migration Notes - **NO hay breaking changes**: `shouldShow()` mantiene comportamiento actual - **Nuevo metodo opcional**: `shouldShowForCache()` para modo JS-First - **Backward compatible**: Si `javascript_first_mode=false`, todo funciona igual --- ## Version History | Version | Date | Changes | |---------|------|---------| | 1.0 | 2025-12-11 | Initial spec |