Files
roi-theme/Shared/Infrastructure/Di/DIContainer.php
FrankZamora 36d5cf56de 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>
2025-12-04 11:46:21 -06:00

497 lines
18 KiB
PHP

<?php
declare(strict_types=1);
namespace ROITheme\Shared\Infrastructure\Di;
use ROITheme\Shared\Domain\Contracts\ComponentRepositoryInterface;
use ROITheme\Shared\Domain\Contracts\ComponentDefaultsRepositoryInterface;
use ROITheme\Shared\Domain\Contracts\ValidationServiceInterface;
use ROITheme\Shared\Domain\Contracts\CacheServiceInterface;
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
use ROITheme\Shared\Domain\Contracts\ComponentSettingsRepositoryInterface;
use ROITheme\Shared\Domain\Contracts\CriticalCSSCollectorInterface;
use ROITheme\Shared\Infrastructure\Persistence\WordPress\WordPressComponentRepository;
use ROITheme\Shared\Infrastructure\Persistence\WordPress\WordPressDefaultsRepository;
use ROITheme\Shared\Infrastructure\Persistence\WordPress\WordPressComponentSettingsRepository;
use ROITheme\Shared\Infrastructure\Services\WordPressValidationService;
use ROITheme\Shared\Infrastructure\Services\WordPressCacheService;
use ROITheme\Shared\Infrastructure\Services\SchemaSyncService;
use ROITheme\Shared\Infrastructure\Services\CleanupService;
use ROITheme\Shared\Infrastructure\Services\CSSGeneratorService;
use ROITheme\Shared\Infrastructure\Services\CriticalCSSCollector;
use ROITheme\Shared\Application\UseCases\GetComponentSettings\GetComponentSettingsUseCase;
use ROITheme\Shared\Application\UseCases\SaveComponentSettings\SaveComponentSettingsUseCase;
use ROITheme\Public\AdsensePlacement\Infrastructure\Ui\AdsensePlacementRenderer;
use ROITheme\Shared\Domain\Contracts\PageVisibilityRepositoryInterface;
use ROITheme\Shared\Domain\Contracts\PageTypeDetectorInterface;
use ROITheme\Shared\Infrastructure\Services\WordPressPageTypeDetector;
use ROITheme\Shared\Infrastructure\Persistence\WordPress\WordPressPageVisibilityRepository;
use ROITheme\Shared\Application\UseCases\EvaluatePageVisibility\EvaluatePageVisibilityUseCase;
use ROITheme\Shared\Infrastructure\Services\MigratePageVisibilityService;
// Exclusion System (Plan 99.11)
use ROITheme\Shared\Domain\Contracts\ExclusionRepositoryInterface;
use ROITheme\Shared\Domain\Contracts\PageContextProviderInterface;
use ROITheme\Shared\Domain\Contracts\ServerRequestProviderInterface;
use ROITheme\Shared\Infrastructure\Persistence\WordPress\WordPressExclusionRepository;
use ROITheme\Shared\Infrastructure\Services\WordPressPageContextProvider;
use ROITheme\Shared\Infrastructure\Services\WordPressServerRequestProvider;
use ROITheme\Shared\Application\UseCases\EvaluateExclusions\EvaluateExclusionsUseCase;
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
*
* RESPONSABILIDAD: Crear y gestionar instancias de servicios
*
* PATRÓN: Service Locator + Lazy Initialization
* - Lazy Initialization: Crear instancias solo cuando se necesitan
* - Singleton Pattern: Una sola instancia por servicio
* - Dependency Resolution: Resolver dependencias automáticamente
*
* USO:
* ```php
* $container = new DIContainer($wpdb, '/path/to/schemas');
* $repository = $container->getComponentRepository();
* $service = $container->getValidationService();
* ```
*
* @package ROITheme\Shared\Infrastructure\DI
*/
final class DIContainer
{
private array $instances = [];
/**
* Instancia singleton del contenedor
* @var self|null
*/
private static ?self $instance = null;
/**
* Obtiene la instancia singleton del contenedor
*
* NOTA: Se debe haber creado una instancia previamente en functions.php
* El constructor registra automáticamente la instancia.
*
* @return self
* @throws \RuntimeException Si no se ha inicializado el contenedor
*/
public static function getInstance(): self
{
if (self::$instance === null) {
throw new \RuntimeException(
'DIContainer no ha sido inicializado. Asegúrate de que functions.php se haya ejecutado primero.'
);
}
return self::$instance;
}
public function __construct(
private \wpdb $wpdb,
private string $schemasPath
) {
// Registrar como instancia singleton
self::$instance = $this;
}
/**
* Obtener repositorio de componentes
*
* Lazy initialization: Crea la instancia solo en la primera llamada
*
* @return ComponentRepositoryInterface
*/
public function getComponentRepository(): ComponentRepositoryInterface
{
if (!isset($this->instances['componentRepository'])) {
$this->instances['componentRepository'] = new WordPressComponentRepository(
$this->wpdb
);
}
return $this->instances['componentRepository'];
}
/**
* Obtener repositorio de defaults
*
* Lazy initialization: Crea la instancia solo en la primera llamada
*
* @return ComponentDefaultsRepositoryInterface
*/
public function getDefaultsRepository(): ComponentDefaultsRepositoryInterface
{
if (!isset($this->instances['defaultsRepository'])) {
$this->instances['defaultsRepository'] = new WordPressDefaultsRepository(
$this->wpdb
);
}
return $this->instances['defaultsRepository'];
}
/**
* Obtener servicio de validación
*
* Lazy initialization: Crea la instancia solo en la primera llamada
* Resuelve dependencia: getDefaultsRepository()
*
* @return ValidationServiceInterface
*/
public function getValidationService(): ValidationServiceInterface
{
if (!isset($this->instances['validationService'])) {
$this->instances['validationService'] = new WordPressValidationService(
$this->getDefaultsRepository()
);
}
return $this->instances['validationService'];
}
/**
* Obtener servicio de cache
*
* Lazy initialization: Crea la instancia solo en la primera llamada
*
* @return CacheServiceInterface
*/
public function getCacheService(): CacheServiceInterface
{
if (!isset($this->instances['cacheService'])) {
$this->instances['cacheService'] = new WordPressCacheService(
$this->wpdb
);
}
return $this->instances['cacheService'];
}
/**
* Obtener servicio de sincronización de schemas
*
* Lazy initialization: Crea la instancia solo en la primera llamada
* Resuelve dependencia: getDefaultsRepository()
*
* @return SchemaSyncService
*/
public function getSchemaSyncService(): SchemaSyncService
{
if (!isset($this->instances['schemaSyncService'])) {
$this->instances['schemaSyncService'] = new SchemaSyncService(
$this->getDefaultsRepository(),
$this->schemasPath
);
}
return $this->instances['schemaSyncService'];
}
/**
* Obtener servicio de limpieza
*
* Lazy initialization: Crea la instancia solo en la primera llamada
* Resuelve dependencias: getComponentRepository(), getDefaultsRepository()
*
* @return CleanupService
*/
public function getCleanupService(): CleanupService
{
if (!isset($this->instances['cleanupService'])) {
$this->instances['cleanupService'] = new CleanupService(
$this->getComponentRepository(),
$this->getDefaultsRepository()
);
}
return $this->instances['cleanupService'];
}
/**
* Obtener servicio de generación de CSS
*
* Lazy initialization: Crea la instancia solo en la primera llamada
* Sin dependencias
*
* @return CSSGeneratorInterface
*/
public function getCSSGeneratorService(): CSSGeneratorInterface
{
if (!isset($this->instances['cssGeneratorService'])) {
$this->instances['cssGeneratorService'] = new CSSGeneratorService();
}
return $this->instances['cssGeneratorService'];
}
/**
* Obtener repositorio de configuraciones de componentes
*
* Lazy initialization: Crea la instancia solo en la primera llamada
*
* @return ComponentSettingsRepositoryInterface
*/
public function getComponentSettingsRepository(): ComponentSettingsRepositoryInterface
{
if (!isset($this->instances['componentSettingsRepository'])) {
$this->instances['componentSettingsRepository'] = new WordPressComponentSettingsRepository(
$this->wpdb
);
}
return $this->instances['componentSettingsRepository'];
}
/**
* Obtener caso de uso para obtener configuraciones de componentes
*
* Lazy initialization: Crea la instancia solo en la primera llamada
* Resuelve dependencia: getComponentSettingsRepository()
*
* @return GetComponentSettingsUseCase
*/
public function getGetComponentSettingsUseCase(): GetComponentSettingsUseCase
{
if (!isset($this->instances['getComponentSettingsUseCase'])) {
$this->instances['getComponentSettingsUseCase'] = new GetComponentSettingsUseCase(
$this->getComponentSettingsRepository()
);
}
return $this->instances['getComponentSettingsUseCase'];
}
/**
* Obtener caso de uso para guardar configuraciones de componentes
*
* Lazy initialization: Crea la instancia solo en la primera llamada
* Resuelve dependencia: getComponentSettingsRepository()
*
* @return SaveComponentSettingsUseCase
*/
public function getSaveComponentSettingsUseCase(): SaveComponentSettingsUseCase
{
if (!isset($this->instances['saveComponentSettingsUseCase'])) {
$this->instances['saveComponentSettingsUseCase'] = new SaveComponentSettingsUseCase(
$this->getComponentSettingsRepository()
);
}
return $this->instances['saveComponentSettingsUseCase'];
}
/**
* Obtener renderer de AdSense Placement
*
* Lazy initialization: Crea la instancia solo en la primera llamada
* Resuelve dependencia: getCSSGeneratorService()
*
* @return AdsensePlacementRenderer
*/
public function getAdsensePlacementRenderer(): AdsensePlacementRenderer
{
if (!isset($this->instances['adsensePlacementRenderer'])) {
$this->instances['adsensePlacementRenderer'] = new AdsensePlacementRenderer(
$this->getCSSGeneratorService()
);
}
return $this->instances['adsensePlacementRenderer'];
}
/**
* Obtener colector de CSS crítico
*
* Lazy initialization: Crea la instancia solo en la primera llamada
* IMPORTANTE: Singleton - misma instancia para todos los Renderers
*
* @return CriticalCSSCollectorInterface
*/
public function getCriticalCSSCollector(): CriticalCSSCollectorInterface
{
if (!isset($this->instances['criticalCSSCollector'])) {
$this->instances['criticalCSSCollector'] = new CriticalCSSCollector();
}
return $this->instances['criticalCSSCollector'];
}
// ===============================
// Page Visibility System
// ===============================
/**
* Obtiene el repositorio de visibilidad de página
*
* IMPORTANTE: Inyecta $wpdb para consistencia con el resto del código
* (WordPressComponentSettingsRepository también recibe $wpdb por constructor)
*/
public function getPageVisibilityRepository(): PageVisibilityRepositoryInterface
{
if (!isset($this->instances['pageVisibilityRepository'])) {
// Inyectar $wpdb siguiendo el patrón existente
$this->instances['pageVisibilityRepository'] = new WordPressPageVisibilityRepository($this->wpdb);
}
return $this->instances['pageVisibilityRepository'];
}
/**
* Obtiene el detector de tipo de página
*/
public function getPageTypeDetector(): PageTypeDetectorInterface
{
if (!isset($this->instances['pageTypeDetector'])) {
$this->instances['pageTypeDetector'] = new WordPressPageTypeDetector();
}
return $this->instances['pageTypeDetector'];
}
/**
* Obtiene el caso de uso de evaluación de visibilidad
*/
public function getEvaluatePageVisibilityUseCase(): EvaluatePageVisibilityUseCase
{
if (!isset($this->instances['evaluatePageVisibilityUseCase'])) {
$this->instances['evaluatePageVisibilityUseCase'] = new EvaluatePageVisibilityUseCase(
$this->getPageTypeDetector(),
$this->getPageVisibilityRepository()
);
}
return $this->instances['evaluatePageVisibilityUseCase'];
}
/**
* Obtiene el servicio de migración de visibilidad
*/
public function getMigratePageVisibilityService(): MigratePageVisibilityService
{
if (!isset($this->instances['migratePageVisibilityService'])) {
$this->instances['migratePageVisibilityService'] = new MigratePageVisibilityService(
$this->getPageVisibilityRepository()
);
}
return $this->instances['migratePageVisibilityService'];
}
// ===============================
// Exclusion System (Plan 99.11)
// ===============================
/**
* Obtiene el proveedor de request HTTP
*
* Encapsula acceso a $_SERVER
*/
public function getServerRequestProvider(): ServerRequestProviderInterface
{
if (!isset($this->instances['serverRequestProvider'])) {
$this->instances['serverRequestProvider'] = new WordPressServerRequestProvider();
}
return $this->instances['serverRequestProvider'];
}
/**
* Obtiene el repositorio de exclusiones
*/
public function getExclusionRepository(): ExclusionRepositoryInterface
{
if (!isset($this->instances['exclusionRepository'])) {
$this->instances['exclusionRepository'] = new WordPressExclusionRepository($this->wpdb);
}
return $this->instances['exclusionRepository'];
}
/**
* Obtiene el proveedor de contexto de página
*/
public function getPageContextProvider(): PageContextProviderInterface
{
if (!isset($this->instances['pageContextProvider'])) {
$this->instances['pageContextProvider'] = new WordPressPageContextProvider(
$this->getServerRequestProvider()
);
}
return $this->instances['pageContextProvider'];
}
/**
* Obtiene el caso de uso de evaluación de exclusiones
*/
public function getEvaluateExclusionsUseCase(): EvaluateExclusionsUseCase
{
if (!isset($this->instances['evaluateExclusionsUseCase'])) {
$this->instances['evaluateExclusionsUseCase'] = new EvaluateExclusionsUseCase(
$this->getExclusionRepository(),
$this->getPageContextProvider()
);
}
return $this->instances['evaluateExclusionsUseCase'];
}
/**
* Obtiene el caso de uso orquestador de visibilidad completa
*
* Combina visibilidad por tipo de página + exclusiones
*/
public function getEvaluateComponentVisibilityUseCase(): EvaluateComponentVisibilityUseCase
{
if (!isset($this->instances['evaluateComponentVisibilityUseCase'])) {
$this->instances['evaluateComponentVisibilityUseCase'] = new EvaluateComponentVisibilityUseCase(
$this->getEvaluatePageVisibilityUseCase(),
$this->getEvaluateExclusionsUseCase()
);
}
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'];
}
}