fix(wrappers): eliminar wrappers vacíos y corregir exclusiones AdSense (Plan 99.15)
## Problema - Componentes deshabilitados/excluidos dejaban wrappers HTML vacíos (navbar 32px, sidebar col-lg-3 294px) - AdSense ignoraba exclusiones por URL pattern en grupo _exclusions ## Solución Plan 99.15 (Clean Architecture) ### Domain Layer - WrapperVisibilityCheckerInterface: contrato para verificar visibilidad ### Application Layer - CheckWrapperVisibilityUseCase: orquesta verificaciones de visibilidad ### Infrastructure Layer - WordPressComponentVisibilityRepository: consulta BD + PageVisibilityHelper - WrapperVisibilityService: facade estático para templates - BodyClassHooksRegistrar: agrega clases CSS failsafe al body ### Templates modificados - header.php: renderizado condicional de <nav> wrapper - page.php/single.php: lógica dinámica col-lg-9/col-lg-12 según sidebar ### CSS Failsafe - css-global-utilities.css: reglas body.roi-hide-* como respaldo ## Fix AdSense (Inc/adsense-placement.php) - Agregado PageVisibilityHelper::shouldShow() a todas las funciones: roi_render_ad_slot, roi_render_rail_ads, roi_enqueue_adsense_script, roi_inject_content_ads, roi_render_anchor_ads, roi_render_vignette_ad, roi_enqueue_anchor_vignette_scripts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -88,3 +88,43 @@
|
|||||||
.transition-none {
|
.transition-none {
|
||||||
transition: none !important;
|
transition: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ========================================
|
||||||
|
COMPONENT VISIBILITY FAILSAFE (Plan 99.15)
|
||||||
|
|
||||||
|
CSS failsafe: Oculta wrappers de componentes
|
||||||
|
cuando body tiene clases roi-hide-*
|
||||||
|
|
||||||
|
Estas clases se agregan via BodyClassHooksRegistrar
|
||||||
|
cuando los componentes están deshabilitados/excluidos.
|
||||||
|
======================================== */
|
||||||
|
|
||||||
|
/* Navbar hidden */
|
||||||
|
body.roi-hide-navbar .navbar {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table of Contents hidden */
|
||||||
|
body.roi-hide-toc .roi-toc-container {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CTA Sidebar hidden */
|
||||||
|
body.roi-hide-cta-sidebar .roi-cta-box {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Generic sidebar hidden */
|
||||||
|
body.roi-hide-sidebar .sidebar-sticky {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When ALL sidebar components are hidden, expand main column */
|
||||||
|
body.roi-sidebar-empty .col-lg-9 {
|
||||||
|
flex: 0 0 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.roi-sidebar-empty .col-lg-3 {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ if (!defined('ABSPATH')) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use ROITheme\Shared\Infrastructure\Services\PageVisibilityHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renderiza un slot de anuncio en una ubicacion
|
* Renderiza un slot de anuncio en una ubicacion
|
||||||
*
|
*
|
||||||
@@ -52,11 +54,16 @@ function roi_render_ad_slot(string $location): string
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verificar exclusiones
|
// Verificar exclusiones legacy (forms group)
|
||||||
if (roi_is_ad_excluded($settings)) {
|
if (roi_is_ad_excluded($settings)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verificar exclusiones modernas (Plan 99.11: _exclusions, _page_visibility)
|
||||||
|
if (!PageVisibilityHelper::shouldShow('adsense-placement')) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
// Obtener renderer desde DIContainer (DIP compliant)
|
// Obtener renderer desde DIContainer (DIP compliant)
|
||||||
$renderer = $container->getAdsensePlacementRenderer();
|
$renderer = $container->getAdsensePlacementRenderer();
|
||||||
|
|
||||||
@@ -143,11 +150,16 @@ function roi_render_rail_ads(): string
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verificar exclusiones
|
// Verificar exclusiones legacy (forms group)
|
||||||
if (roi_is_ad_excluded($settings)) {
|
if (roi_is_ad_excluded($settings)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verificar exclusiones modernas (Plan 99.11: _exclusions, _page_visibility)
|
||||||
|
if (!PageVisibilityHelper::shouldShow('adsense-placement')) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
// Obtener renderer desde DIContainer (DIP compliant)
|
// Obtener renderer desde DIContainer (DIP compliant)
|
||||||
$renderer = $container->getAdsensePlacementRenderer();
|
$renderer = $container->getAdsensePlacementRenderer();
|
||||||
|
|
||||||
@@ -193,6 +205,11 @@ function roi_enqueue_adsense_script(): void
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verificar exclusiones modernas (Plan 99.11: _exclusions, _page_visibility)
|
||||||
|
if (!PageVisibilityHelper::shouldShow('adsense-placement')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$publisherId = $settings['content']['publisher_id'] ?? '';
|
$publisherId = $settings['content']['publisher_id'] ?? '';
|
||||||
if (empty($publisherId)) {
|
if (empty($publisherId)) {
|
||||||
return;
|
return;
|
||||||
@@ -246,11 +263,16 @@ function roi_inject_content_ads(string $content): string
|
|||||||
return $content;
|
return $content;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verificar exclusiones
|
// Verificar exclusiones legacy (forms group)
|
||||||
if (roi_is_ad_excluded($settings)) {
|
if (roi_is_ad_excluded($settings)) {
|
||||||
return $content;
|
return $content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verificar exclusiones modernas (Plan 99.11: _exclusions, _page_visibility)
|
||||||
|
if (!PageVisibilityHelper::shouldShow('adsense-placement')) {
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
$renderer = $container->getAdsensePlacementRenderer();
|
$renderer = $container->getAdsensePlacementRenderer();
|
||||||
|
|
||||||
// Inyectar anuncio al inicio (post-top)
|
// Inyectar anuncio al inicio (post-top)
|
||||||
@@ -446,11 +468,16 @@ function roi_render_anchor_ads(): string
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verificar exclusiones
|
// Verificar exclusiones legacy (forms group)
|
||||||
if (roi_is_ad_excluded($settings)) {
|
if (roi_is_ad_excluded($settings)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verificar exclusiones modernas (Plan 99.11: _exclusions, _page_visibility)
|
||||||
|
if (!PageVisibilityHelper::shouldShow('adsense-placement')) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
// Obtener renderer desde DIContainer (DIP compliant)
|
// Obtener renderer desde DIContainer (DIP compliant)
|
||||||
$renderer = $container->getAdsensePlacementRenderer();
|
$renderer = $container->getAdsensePlacementRenderer();
|
||||||
|
|
||||||
@@ -490,11 +517,16 @@ function roi_render_vignette_ad(): string
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verificar exclusiones
|
// Verificar exclusiones legacy (forms group)
|
||||||
if (roi_is_ad_excluded($settings)) {
|
if (roi_is_ad_excluded($settings)) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verificar exclusiones modernas (Plan 99.11: _exclusions, _page_visibility)
|
||||||
|
if (!PageVisibilityHelper::shouldShow('adsense-placement')) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
// Obtener renderer desde DIContainer (DIP compliant)
|
// Obtener renderer desde DIContainer (DIP compliant)
|
||||||
$renderer = $container->getAdsensePlacementRenderer();
|
$renderer = $container->getAdsensePlacementRenderer();
|
||||||
|
|
||||||
@@ -556,6 +588,11 @@ function roi_enqueue_anchor_vignette_scripts(): void
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verificar exclusiones modernas (Plan 99.11: _exclusions, _page_visibility)
|
||||||
|
if (!PageVisibilityHelper::shouldShow('adsense-placement')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Encolar script
|
// Encolar script
|
||||||
wp_enqueue_script(
|
wp_enqueue_script(
|
||||||
'roi-anchor-vignette',
|
'roi-anchor-vignette',
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ROITheme\Shared\Application\UseCases;
|
||||||
|
|
||||||
|
use ROITheme\Shared\Domain\Contracts\WrapperVisibilityCheckerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UseCase: Verificar si un wrapper de componente debe renderizarse
|
||||||
|
*
|
||||||
|
* Responsabilidad: Orquestar la lógica de verificación de visibilidad
|
||||||
|
* combinando múltiples criterios:
|
||||||
|
* 1. Componente habilitado (is_enabled)
|
||||||
|
* 2. Visible en dispositivo actual (show_on_mobile/desktop)
|
||||||
|
* 3. No excluido por reglas (categoría, post ID, URL, page visibility)
|
||||||
|
*
|
||||||
|
* @package ROITheme\Shared\Application\UseCases
|
||||||
|
* @see Plan 99.15 - Fix Empty Layout Wrappers
|
||||||
|
*/
|
||||||
|
final class CheckWrapperVisibilityUseCase
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly WrapperVisibilityCheckerInterface $visibilityChecker
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ejecuta la verificación de visibilidad del wrapper
|
||||||
|
*
|
||||||
|
* @param string $componentName Nombre del componente (kebab-case)
|
||||||
|
* @param bool $isMobile True si es dispositivo móvil
|
||||||
|
* @return bool True si el wrapper debe renderizarse
|
||||||
|
*/
|
||||||
|
public function execute(string $componentName, bool $isMobile): bool
|
||||||
|
{
|
||||||
|
// Criterio 1: Debe estar habilitado
|
||||||
|
if (!$this->visibilityChecker->isEnabled($componentName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Criterio 2: Debe ser visible en el dispositivo actual
|
||||||
|
if (!$this->visibilityChecker->isVisibleOnDevice($componentName, $isMobile)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Criterio 3: No debe estar excluido
|
||||||
|
if (!$this->visibilityChecker->isNotExcluded($componentName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ROITheme\Shared\Domain\Contracts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface para verificar visibilidad de wrappers de componentes
|
||||||
|
*
|
||||||
|
* Responsabilidad: Definir contrato para determinar si un wrapper
|
||||||
|
* de componente debe renderizarse basándose en:
|
||||||
|
* - Estado habilitado/deshabilitado
|
||||||
|
* - Visibilidad por dispositivo
|
||||||
|
* - Reglas de exclusión (categoría, post ID, URL pattern, page visibility)
|
||||||
|
*
|
||||||
|
* @package ROITheme\Shared\Domain\Contracts
|
||||||
|
* @see Plan 99.15 - Fix Empty Layout Wrappers
|
||||||
|
*/
|
||||||
|
interface WrapperVisibilityCheckerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Verifica si el componente está habilitado globalmente
|
||||||
|
*
|
||||||
|
* @param string $componentName Nombre del componente (kebab-case)
|
||||||
|
* @return bool True si is_enabled = true en BD
|
||||||
|
*/
|
||||||
|
public function isEnabled(string $componentName): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifica si el componente es visible en el dispositivo actual
|
||||||
|
*
|
||||||
|
* @param string $componentName Nombre del componente (kebab-case)
|
||||||
|
* @param bool $isMobile True si es dispositivo móvil
|
||||||
|
* @return bool True si show_on_mobile/show_on_desktop según corresponda
|
||||||
|
*/
|
||||||
|
public function isVisibleOnDevice(string $componentName, bool $isMobile): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifica si el componente NO está excluido para la página actual
|
||||||
|
*
|
||||||
|
* Evalúa todas las reglas de exclusión:
|
||||||
|
* - Exclusión por categoría
|
||||||
|
* - Exclusión por post ID
|
||||||
|
* - Exclusión por URL pattern
|
||||||
|
* - Page visibility (home, posts, pages, archives, search)
|
||||||
|
*
|
||||||
|
* @param string $componentName Nombre del componente (kebab-case)
|
||||||
|
* @return bool True si el componente NO está excluido
|
||||||
|
*/
|
||||||
|
public function isNotExcluded(string $componentName): bool;
|
||||||
|
}
|
||||||
@@ -37,6 +37,11 @@ use ROITheme\Shared\Infrastructure\Services\WordPressPageContextProvider;
|
|||||||
use ROITheme\Shared\Infrastructure\Services\WordPressServerRequestProvider;
|
use ROITheme\Shared\Infrastructure\Services\WordPressServerRequestProvider;
|
||||||
use ROITheme\Shared\Application\UseCases\EvaluateExclusions\EvaluateExclusionsUseCase;
|
use ROITheme\Shared\Application\UseCases\EvaluateExclusions\EvaluateExclusionsUseCase;
|
||||||
use ROITheme\Shared\Application\UseCases\EvaluateComponentVisibility\EvaluateComponentVisibilityUseCase;
|
use ROITheme\Shared\Application\UseCases\EvaluateComponentVisibility\EvaluateComponentVisibilityUseCase;
|
||||||
|
// Wrapper Visibility System (Plan 99.15)
|
||||||
|
use ROITheme\Shared\Domain\Contracts\WrapperVisibilityCheckerInterface;
|
||||||
|
use ROITheme\Shared\Infrastructure\Persistence\WordPress\WordPressComponentVisibilityRepository;
|
||||||
|
use ROITheme\Shared\Application\UseCases\CheckWrapperVisibilityUseCase;
|
||||||
|
use ROITheme\Shared\Infrastructure\Wordpress\BodyClassHooksRegistrar;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DIContainer - Contenedor de Inyección de Dependencias
|
* DIContainer - Contenedor de Inyección de Dependencias
|
||||||
@@ -443,4 +448,49 @@ final class DIContainer
|
|||||||
}
|
}
|
||||||
return $this->instances['evaluateComponentVisibilityUseCase'];
|
return $this->instances['evaluateComponentVisibilityUseCase'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===============================
|
||||||
|
// Wrapper Visibility System (Plan 99.15)
|
||||||
|
// ===============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene el repositorio de visibilidad de wrappers
|
||||||
|
*
|
||||||
|
* Implementa WrapperVisibilityCheckerInterface
|
||||||
|
*/
|
||||||
|
public function getWrapperVisibilityChecker(): WrapperVisibilityCheckerInterface
|
||||||
|
{
|
||||||
|
if (!isset($this->instances['wrapperVisibilityChecker'])) {
|
||||||
|
$this->instances['wrapperVisibilityChecker'] = new WordPressComponentVisibilityRepository($this->wpdb);
|
||||||
|
}
|
||||||
|
return $this->instances['wrapperVisibilityChecker'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene el caso de uso para verificar visibilidad de wrappers
|
||||||
|
*
|
||||||
|
* Usado por WrapperVisibilityService para templates
|
||||||
|
*/
|
||||||
|
public function getCheckWrapperVisibilityUseCase(): CheckWrapperVisibilityUseCase
|
||||||
|
{
|
||||||
|
if (!isset($this->instances['checkWrapperVisibilityUseCase'])) {
|
||||||
|
$this->instances['checkWrapperVisibilityUseCase'] = new CheckWrapperVisibilityUseCase(
|
||||||
|
$this->getWrapperVisibilityChecker()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $this->instances['checkWrapperVisibilityUseCase'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene el registrador de hooks para body_class
|
||||||
|
*
|
||||||
|
* CSS failsafe: Agrega clases cuando componentes están ocultos
|
||||||
|
*/
|
||||||
|
public function getBodyClassHooksRegistrar(): BodyClassHooksRegistrar
|
||||||
|
{
|
||||||
|
if (!isset($this->instances['bodyClassHooksRegistrar'])) {
|
||||||
|
$this->instances['bodyClassHooksRegistrar'] = new BodyClassHooksRegistrar();
|
||||||
|
}
|
||||||
|
return $this->instances['bodyClassHooksRegistrar'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ROITheme\Shared\Infrastructure\Persistence\WordPress;
|
||||||
|
|
||||||
|
use ROITheme\Shared\Domain\Contracts\WrapperVisibilityCheckerInterface;
|
||||||
|
use ROITheme\Shared\Infrastructure\Services\PageVisibilityHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementación de WrapperVisibilityCheckerInterface para WordPress
|
||||||
|
*
|
||||||
|
* Responsabilidad: Consultar BD y evaluar visibilidad de wrappers de componentes
|
||||||
|
*
|
||||||
|
* - Consulta tabla wp_roi_theme_component_settings para is_enabled, show_on_mobile, show_on_desktop
|
||||||
|
* - Delega evaluación de exclusiones a PageVisibilityHelper (DRY)
|
||||||
|
*
|
||||||
|
* @package ROITheme\Shared\Infrastructure\Persistence\WordPress
|
||||||
|
* @see Plan 99.15 - Fix Empty Layout Wrappers
|
||||||
|
*/
|
||||||
|
final class WordPressComponentVisibilityRepository implements WrapperVisibilityCheckerInterface
|
||||||
|
{
|
||||||
|
private string $tableName;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private \wpdb $wpdb
|
||||||
|
) {
|
||||||
|
$this->tableName = $this->wpdb->prefix . 'roi_theme_component_settings';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function isEnabled(string $componentName): bool
|
||||||
|
{
|
||||||
|
$value = $this->getVisibilityAttribute($componentName, 'is_enabled');
|
||||||
|
|
||||||
|
// Si no existe el registro, asumir habilitado por defecto
|
||||||
|
if ($value === null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->toBool($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function isVisibleOnDevice(string $componentName, bool $isMobile): bool
|
||||||
|
{
|
||||||
|
$attribute = $isMobile ? 'show_on_mobile' : 'show_on_desktop';
|
||||||
|
$value = $this->getVisibilityAttribute($componentName, $attribute);
|
||||||
|
|
||||||
|
// Si no existe el registro, asumir visible por defecto
|
||||||
|
if ($value === null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->toBool($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* Delega a PageVisibilityHelper que ya implementa:
|
||||||
|
* - Visibilidad por tipo de página (home, posts, pages, archives, search)
|
||||||
|
* - Exclusiones por categoría, post ID, URL pattern
|
||||||
|
*/
|
||||||
|
public function isNotExcluded(string $componentName): bool
|
||||||
|
{
|
||||||
|
return PageVisibilityHelper::shouldShow($componentName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene un atributo del grupo visibility desde la BD
|
||||||
|
*
|
||||||
|
* @param string $componentName
|
||||||
|
* @param string $attributeName
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
private function getVisibilityAttribute(string $componentName, string $attributeName): ?string
|
||||||
|
{
|
||||||
|
$sql = $this->wpdb->prepare(
|
||||||
|
"SELECT attribute_value
|
||||||
|
FROM {$this->tableName}
|
||||||
|
WHERE component_name = %s
|
||||||
|
AND group_name = %s
|
||||||
|
AND attribute_name = %s
|
||||||
|
LIMIT 1",
|
||||||
|
$componentName,
|
||||||
|
'visibility',
|
||||||
|
$attributeName
|
||||||
|
);
|
||||||
|
|
||||||
|
$result = $this->wpdb->get_var($sql);
|
||||||
|
|
||||||
|
return $result !== null ? (string) $result : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convierte string a boolean
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function toBool(string $value): bool
|
||||||
|
{
|
||||||
|
return $value === '1' || strtolower($value) === 'true';
|
||||||
|
}
|
||||||
|
}
|
||||||
100
Shared/Infrastructure/Services/WrapperVisibilityService.php
Normal file
100
Shared/Infrastructure/Services/WrapperVisibilityService.php
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ROITheme\Shared\Infrastructure\Services;
|
||||||
|
|
||||||
|
use ROITheme\Shared\Application\UseCases\CheckWrapperVisibilityUseCase;
|
||||||
|
use ROITheme\Shared\Infrastructure\Di\DIContainer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Servicio facade para verificar visibilidad de wrappers desde templates
|
||||||
|
*
|
||||||
|
* Responsabilidad: Proveer acceso simplificado (singleton/static) al
|
||||||
|
* CheckWrapperVisibilityUseCase para uso en templates PHP.
|
||||||
|
*
|
||||||
|
* USO EN TEMPLATES:
|
||||||
|
* ```php
|
||||||
|
* if (WrapperVisibilityService::shouldRenderWrapper('navbar')) {
|
||||||
|
* // Renderizar wrapper y componente
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @package ROITheme\Shared\Infrastructure\Services
|
||||||
|
* @see Plan 99.15 - Fix Empty Layout Wrappers
|
||||||
|
*/
|
||||||
|
final class WrapperVisibilityService
|
||||||
|
{
|
||||||
|
private static ?CheckWrapperVisibilityUseCase $useCase = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifica si el wrapper de un componente debe renderizarse
|
||||||
|
*
|
||||||
|
* @param string $componentName Nombre del componente (kebab-case)
|
||||||
|
* @return bool True si el wrapper debe renderizarse
|
||||||
|
*/
|
||||||
|
public static function shouldRenderWrapper(string $componentName): bool
|
||||||
|
{
|
||||||
|
$useCase = self::getUseCase();
|
||||||
|
$isMobile = self::detectMobile();
|
||||||
|
|
||||||
|
return $useCase->execute($componentName, $isMobile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifica visibilidad para múltiples componentes
|
||||||
|
*
|
||||||
|
* Útil para determinar si renderizar un contenedor que agrupa varios componentes
|
||||||
|
*
|
||||||
|
* @param array<string> $componentNames Lista de nombres de componentes
|
||||||
|
* @return bool True si AL MENOS UNO de los componentes debe mostrarse
|
||||||
|
*/
|
||||||
|
public static function shouldRenderAnyWrapper(array $componentNames): bool
|
||||||
|
{
|
||||||
|
foreach ($componentNames as $componentName) {
|
||||||
|
if (self::shouldRenderWrapper($componentName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene o crea el UseCase
|
||||||
|
*
|
||||||
|
* @return CheckWrapperVisibilityUseCase
|
||||||
|
*/
|
||||||
|
private static function getUseCase(): CheckWrapperVisibilityUseCase
|
||||||
|
{
|
||||||
|
if (self::$useCase === null) {
|
||||||
|
$container = DIContainer::getInstance();
|
||||||
|
self::$useCase = $container->getCheckWrapperVisibilityUseCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$useCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detecta si el dispositivo actual es móvil
|
||||||
|
*
|
||||||
|
* Usa wp_is_mobile() de WordPress
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private static function detectMobile(): bool
|
||||||
|
{
|
||||||
|
if (function_exists('wp_is_mobile')) {
|
||||||
|
return wp_is_mobile();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limpia la instancia del UseCase (útil para tests)
|
||||||
|
*/
|
||||||
|
public static function reset(): void
|
||||||
|
{
|
||||||
|
self::$useCase = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
96
Shared/Infrastructure/Wordpress/BodyClassHooksRegistrar.php
Normal file
96
Shared/Infrastructure/Wordpress/BodyClassHooksRegistrar.php
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace ROITheme\Shared\Infrastructure\Wordpress;
|
||||||
|
|
||||||
|
use ROITheme\Shared\Infrastructure\Services\WrapperVisibilityService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registra hook body_class para agregar clases CSS de componentes ocultos
|
||||||
|
*
|
||||||
|
* RESPONSABILIDAD:
|
||||||
|
* - Registrar hook body_class
|
||||||
|
* - Agregar clases CSS cuando componentes están ocultos
|
||||||
|
*
|
||||||
|
* FLUJO:
|
||||||
|
* 1. body_class filter → addHiddenComponentClasses()
|
||||||
|
* - Verifica visibilidad de componentes clave (navbar, sidebar components)
|
||||||
|
* - Agrega clases: roi-hide-navbar, roi-hide-sidebar, etc.
|
||||||
|
*
|
||||||
|
* PROPÓSITO:
|
||||||
|
* Failsafe CSS: Si los templates no pueden ocultar wrappers completamente,
|
||||||
|
* estas clases permiten ocultarlos via CSS.
|
||||||
|
*
|
||||||
|
* PATRÓN:
|
||||||
|
* - SRP: Solo registra hooks, delega lógica a WrapperVisibilityService
|
||||||
|
*
|
||||||
|
* @package ROITheme\Shared\Infrastructure\Wordpress
|
||||||
|
* @see Plan 99.15 - Fix Empty Layout Wrappers
|
||||||
|
*/
|
||||||
|
final class BodyClassHooksRegistrar
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Componentes que afectan el layout principal
|
||||||
|
*/
|
||||||
|
private const LAYOUT_COMPONENTS = [
|
||||||
|
'navbar' => 'roi-hide-navbar',
|
||||||
|
'table-of-contents' => 'roi-hide-toc',
|
||||||
|
'cta-box-sidebar' => 'roi-hide-cta-sidebar',
|
||||||
|
'sidebar' => 'roi-hide-sidebar',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Componentes de sidebar que determinan si mostrar columna lateral
|
||||||
|
*/
|
||||||
|
private const SIDEBAR_COMPONENTS = [
|
||||||
|
'table-of-contents',
|
||||||
|
'cta-box-sidebar',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registrar hooks de WordPress
|
||||||
|
*/
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
add_filter('body_class', [$this, 'addHiddenComponentClasses']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback para body_class - agrega clases para componentes ocultos
|
||||||
|
*
|
||||||
|
* @param array<string> $classes Clases existentes
|
||||||
|
* @return array<string> Clases modificadas
|
||||||
|
*/
|
||||||
|
public function addHiddenComponentClasses(array $classes): array
|
||||||
|
{
|
||||||
|
// Agregar clase por cada componente oculto
|
||||||
|
foreach (self::LAYOUT_COMPONENTS as $componentName => $cssClass) {
|
||||||
|
if (!WrapperVisibilityService::shouldRenderWrapper($componentName)) {
|
||||||
|
$classes[] = $cssClass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verificar si TODOS los componentes de sidebar están ocultos
|
||||||
|
if ($this->allSidebarComponentsHidden()) {
|
||||||
|
$classes[] = 'roi-sidebar-empty';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $classes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifica si todos los componentes de sidebar están ocultos
|
||||||
|
*
|
||||||
|
* @return bool True si ningún componente de sidebar debe mostrarse
|
||||||
|
*/
|
||||||
|
private function allSidebarComponentsHidden(): bool
|
||||||
|
{
|
||||||
|
foreach (self::SIDEBAR_COMPONENTS as $componentName) {
|
||||||
|
if (WrapperVisibilityService::shouldRenderWrapper($componentName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -370,6 +370,11 @@ add_action('after_setup_theme', function() {
|
|||||||
$criticalCSSService = roi_get_critical_css_service();
|
$criticalCSSService = roi_get_critical_css_service();
|
||||||
$hooksRegistrar = new \ROITheme\Shared\Infrastructure\Wordpress\CriticalCSSHooksRegistrar($criticalCSSService);
|
$hooksRegistrar = new \ROITheme\Shared\Infrastructure\Wordpress\CriticalCSSHooksRegistrar($criticalCSSService);
|
||||||
$hooksRegistrar->register();
|
$hooksRegistrar->register();
|
||||||
|
|
||||||
|
// 3. Body Class Hooks (Plan 99.15) - CSS failsafe para componentes ocultos
|
||||||
|
$container = \ROITheme\Shared\Infrastructure\Di\DIContainer::getInstance();
|
||||||
|
$bodyClassHooksRegistrar = $container->getBodyClassHooksRegistrar();
|
||||||
|
$bodyClassHooksRegistrar->register();
|
||||||
});
|
});
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -378,6 +383,47 @@ add_action('after_setup_theme', function() {
|
|||||||
// NO hardcodear CSS aquí - viola la arquitectura Clean Architecture.
|
// NO hardcodear CSS aquí - viola la arquitectura Clean Architecture.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// HELPER FUNCTION: roi_should_render_wrapper() - Plan 99.15
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifica si el wrapper de un componente debe renderizarse
|
||||||
|
*
|
||||||
|
* Evalúa:
|
||||||
|
* - is_enabled
|
||||||
|
* - show_on_mobile / show_on_desktop
|
||||||
|
* - Exclusiones (categoría, post ID, URL pattern, page visibility)
|
||||||
|
*
|
||||||
|
* USO EN TEMPLATES:
|
||||||
|
* ```php
|
||||||
|
* if (roi_should_render_wrapper('navbar')) {
|
||||||
|
* echo '<nav class="navbar">';
|
||||||
|
* echo roi_render_component('navbar');
|
||||||
|
* echo '</nav>';
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param string $componentName Nombre del componente (kebab-case)
|
||||||
|
* @return bool True si el wrapper debe renderizarse
|
||||||
|
* @see Plan 99.15 - Fix Empty Layout Wrappers
|
||||||
|
*/
|
||||||
|
function roi_should_render_wrapper(string $componentName): bool {
|
||||||
|
return \ROITheme\Shared\Infrastructure\Services\WrapperVisibilityService::shouldRenderWrapper($componentName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifica si AL MENOS UN componente de una lista debe renderizarse
|
||||||
|
*
|
||||||
|
* Útil para determinar si mostrar columna sidebar
|
||||||
|
*
|
||||||
|
* @param array<string> $componentNames Lista de nombres de componentes
|
||||||
|
* @return bool True si al menos uno debe mostrarse
|
||||||
|
*/
|
||||||
|
function roi_should_render_any_wrapper(array $componentNames): bool {
|
||||||
|
return \ROITheme\Shared\Infrastructure\Services\WrapperVisibilityService::shouldRenderAnyWrapper($componentNames);
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// HELPER FUNCTION: roi_get_adsense_search_config()
|
// HELPER FUNCTION: roi_get_adsense_search_config()
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ if (function_exists('roi_render_component')) {
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<!-- Navbar (Template líneas 264-320) -->
|
<!-- Navbar (Template líneas 264-320) -->
|
||||||
|
<?php
|
||||||
|
// Plan 99.15: Solo renderizar wrapper si navbar debe mostrarse
|
||||||
|
if (function_exists('roi_should_render_wrapper') && roi_should_render_wrapper('navbar')):
|
||||||
|
?>
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark py-3" role="navigation" aria-label="<?php esc_attr_e('Primary Navigation', 'roi-theme'); ?>">
|
<nav class="navbar navbar-expand-lg navbar-dark py-3" role="navigation" aria-label="<?php esc_attr_e('Primary Navigation', 'roi-theme'); ?>">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
||||||
@@ -94,4 +98,5 @@ if (function_exists('roi_render_component')) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div><!-- .container -->
|
</div><!-- .container -->
|
||||||
</nav><!-- .navbar -->
|
</nav><!-- .navbar -->
|
||||||
|
<?php endif; // roi_should_render_wrapper('navbar') ?>
|
||||||
16
page.php
16
page.php
@@ -26,11 +26,19 @@ if (function_exists('roi_render_component')) {
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<!-- Main Content Grid -->
|
<!-- Main Content Grid -->
|
||||||
|
<?php
|
||||||
|
// Plan 99.15: Determinar si mostrar sidebar basándose en visibilidad de componentes
|
||||||
|
$sidebar_components = ['table-of-contents', 'cta-box-sidebar'];
|
||||||
|
$show_sidebar = function_exists('roi_should_render_any_wrapper')
|
||||||
|
? roi_should_render_any_wrapper($sidebar_components)
|
||||||
|
: true;
|
||||||
|
$main_col_class = $show_sidebar ? 'col-lg-9' : 'col-lg-12';
|
||||||
|
?>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<!-- Main Content Column (col-lg-9) -->
|
<!-- Main Content Column -->
|
||||||
<div class="col-lg-9">
|
<div class="<?php echo esc_attr($main_col_class); ?>">
|
||||||
|
|
||||||
<!-- Featured Image - Componente dinámico -->
|
<!-- Featured Image - Componente dinámico -->
|
||||||
<?php
|
<?php
|
||||||
@@ -79,8 +87,9 @@ if (function_exists('roi_render_component')) {
|
|||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
</div><!-- .col-lg-9 -->
|
</div><!-- .<?php echo esc_attr($main_col_class); ?> -->
|
||||||
|
|
||||||
|
<?php if ($show_sidebar): ?>
|
||||||
<!-- Sidebar Column (col-lg-3) -->
|
<!-- Sidebar Column (col-lg-3) -->
|
||||||
<div class="col-lg-3">
|
<div class="col-lg-3">
|
||||||
<div class="sidebar-sticky">
|
<div class="sidebar-sticky">
|
||||||
@@ -99,6 +108,7 @@ if (function_exists('roi_render_component')) {
|
|||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
</div><!-- .row -->
|
</div><!-- .row -->
|
||||||
</div><!-- .container -->
|
</div><!-- .container -->
|
||||||
|
|||||||
16
single.php
16
single.php
@@ -24,11 +24,19 @@ if (function_exists('roi_render_component')) {
|
|||||||
?>
|
?>
|
||||||
|
|
||||||
<!-- Main Content Grid (Template líneas 169-1020) -->
|
<!-- Main Content Grid (Template líneas 169-1020) -->
|
||||||
|
<?php
|
||||||
|
// Plan 99.15: Determinar si mostrar sidebar basándose en visibilidad de componentes
|
||||||
|
$sidebar_components = ['table-of-contents', 'cta-box-sidebar'];
|
||||||
|
$show_sidebar = function_exists('roi_should_render_any_wrapper')
|
||||||
|
? roi_should_render_any_wrapper($sidebar_components)
|
||||||
|
: true;
|
||||||
|
$main_col_class = $show_sidebar ? 'col-lg-9' : 'col-lg-12';
|
||||||
|
?>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<!-- Main Content Column (col-lg-9) -->
|
<!-- Main Content Column -->
|
||||||
<div class="col-lg-9">
|
<div class="<?php echo esc_attr($main_col_class); ?>">
|
||||||
|
|
||||||
<!-- Featured Image - Componente dinámico -->
|
<!-- Featured Image - Componente dinámico -->
|
||||||
<?php
|
<?php
|
||||||
@@ -77,8 +85,9 @@ if (function_exists('roi_render_component')) {
|
|||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
</div><!-- .col-lg-9 -->
|
</div><!-- .<?php echo esc_attr($main_col_class); ?> -->
|
||||||
|
|
||||||
|
<?php if ($show_sidebar): ?>
|
||||||
<!-- Sidebar Column (col-lg-3) -->
|
<!-- Sidebar Column (col-lg-3) -->
|
||||||
<div class="col-lg-3">
|
<div class="col-lg-3">
|
||||||
<div class="sidebar-sticky">
|
<div class="sidebar-sticky">
|
||||||
@@ -97,6 +106,7 @@ if (function_exists('roi_render_component')) {
|
|||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
</div><!-- .row -->
|
</div><!-- .row -->
|
||||||
</div><!-- .container -->
|
</div><!-- .container -->
|
||||||
|
|||||||
Reference in New Issue
Block a user