Files
roi-theme/Public/AdsensePlacement/Infrastructure/Services/AdsenseVisibilityChecker.php
FrankZamora 26546e1d69 feat(api): implement javascript-first architecture for cache compatibility
- Add REST endpoint GET /roi-theme/v1/adsense-placement/visibility
- Add Domain layer: UserContext, VisibilityDecision, AdsenseSettings VOs
- Add Application layer: CheckAdsenseVisibilityUseCase
- Add Infrastructure: AdsenseVisibilityChecker, Controller, Enqueuer
- Add JavaScript controller with localStorage caching
- Add test plan for production validation

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-11 13:03:14 -06:00

122 lines
3.9 KiB
PHP

<?php
declare(strict_types=1);
namespace ROITheme\Public\AdsensePlacement\Infrastructure\Services;
use ROITheme\Public\AdsensePlacement\Domain\Contracts\AdsenseVisibilityCheckerInterface;
use ROITheme\Public\AdsensePlacement\Domain\ValueObjects\UserContext;
use ROITheme\Public\AdsensePlacement\Domain\ValueObjects\VisibilityDecision;
use ROITheme\Public\AdsensePlacement\Domain\ValueObjects\AdsenseSettings;
use ROITheme\Shared\Domain\Contracts\ComponentSettingsRepositoryInterface;
/**
* Implementacion del checker de visibilidad de AdSense.
*
* Infrastructure Layer - Accede a BD via repository.
*
* @package ROITheme\Public\AdsensePlacement\Infrastructure\Services
*/
final class AdsenseVisibilityChecker implements AdsenseVisibilityCheckerInterface
{
private const COMPONENT_NAME = 'adsense-placement';
private const CACHE_SECONDS_SHOW = 300; // 5 min cuando se muestran ads
private const CACHE_SECONDS_HIDE = 600; // 10 min cuando se ocultan (menos volatil)
public function __construct(
private ComponentSettingsRepositoryInterface $settingsRepository
) {
}
public function check(int $postId, UserContext $userContext): VisibilityDecision
{
// Cargar configuracion desde BD
$rawSettings = $this->settingsRepository->getComponentSettings(self::COMPONENT_NAME);
$settings = AdsenseSettings::fromArray($rawSettings);
// Evaluar reglas de visibilidad
$reasons = [];
// 1. AdSense desactivado globalmente
if (!$settings->isEnabled()) {
return VisibilityDecision::hide(['adsense_disabled'], self::CACHE_SECONDS_HIDE);
}
// 2. JavaScript-First Mode desactivado (usar PHP legacy)
if (!$settings->isJavascriptFirstMode()) {
return VisibilityDecision::hide(['javascript_first_disabled'], self::CACHE_SECONDS_HIDE);
}
// 3. Ocultar para usuarios logueados
if ($settings->hideForLoggedIn() && $userContext->isLoggedIn()) {
$reasons[] = 'user_logged_in';
}
// 4. Verificar dispositivo
if ($userContext->isMobile() && !$settings->showOnMobile()) {
$reasons[] = 'mobile_disabled';
}
if ($userContext->isDesktop() && !$settings->showOnDesktop()) {
$reasons[] = 'desktop_disabled';
}
// 5. Verificar exclusiones de post (solo si postId > 0)
if ($postId > 0) {
if ($settings->isPostExcluded($postId)) {
$reasons[] = 'post_excluded';
}
// Verificar categorias del post
$postCategories = $this->getPostCategoryIds($postId);
foreach ($postCategories as $catId) {
if ($settings->isCategoryExcluded($catId)) {
$reasons[] = 'category_excluded';
break;
}
}
// Verificar post type
$postType = $this->getPostType($postId);
if ($postType !== '' && $settings->isPostTypeExcluded($postType)) {
$reasons[] = 'post_type_excluded';
}
}
// Decision final
if (count($reasons) > 0) {
return VisibilityDecision::hide($reasons, self::CACHE_SECONDS_HIDE);
}
return VisibilityDecision::show(self::CACHE_SECONDS_SHOW);
}
/**
* Obtiene IDs de categorias de un post.
*
* @return array<int>
*/
private function getPostCategoryIds(int $postId): array
{
$categories = get_the_category($postId);
if (!is_array($categories)) {
return [];
}
return array_map(
static fn($cat): int => (int) $cat->term_id,
$categories
);
}
/**
* Obtiene el post type de un post.
*/
private function getPostType(int $postId): string
{
$postType = get_post_type($postId);
return $postType !== false ? $postType : '';
}
}