getVisibilityClasses( $settings['visibility']['show_on_desktop'] ?? true, $settings['visibility']['show_on_mobile'] ?? true ); if ($visibilityClasses === null) { return ''; // Ambos false = no renderizar } // 3. Obtener configuracion de la ubicacion $locationConfig = $this->getLocationConfig($settings, $location); if (!$locationConfig['enabled']) { return ''; } // 4. Generar CSS (usando CSSGeneratorService) $css = $this->cssGenerator->generate( ".roi-ad-slot", [ 'display' => 'block', 'width' => '100%', 'min_width' => '300px', 'margin_top' => '1.5rem', 'margin_bottom' => '1.5rem', 'text_align' => 'center', ] ); // 5. Generar HTML del anuncio $html = $this->buildAdHTML( $settings, $locationConfig['format'], $location, $visibilityClasses ); return "\n{$html}"; } /** * Tabla de decision Bootstrap para visibilidad */ private function getVisibilityClasses(bool $desktop, bool $mobile): ?string { if (!$desktop && !$mobile) { return null; } if (!$desktop && $mobile) { return 'd-lg-none'; } if ($desktop && !$mobile) { return 'd-none d-lg-block'; } return ''; } /** * Obtiene configuracion de una ubicacion especifica */ private function getLocationConfig(array $settings, string $location): array { $locationKey = str_replace('-', '_', $location); // Manejar ubicaciones de in-content (post_content_1, post_content_2, etc.) if (preg_match('/^post_content_(\d+)$/', $locationKey, $matches)) { // In-content ads heredan la configuracion de post_content return [ 'enabled' => $settings['behavior']['post_content_enabled'] ?? false, 'format' => $settings['behavior']['post_content_format'] ?? 'in-article', ]; } // Mapeo de ubicaciones a grupos y campos $locationMap = [ 'post_top' => ['group' => 'behavior', 'enabled' => 'post_top_enabled', 'format' => 'post_top_format'], 'post_bottom' => ['group' => 'behavior', 'enabled' => 'post_bottom_enabled', 'format' => 'post_bottom_format'], 'after_related' => ['group' => 'behavior', 'enabled' => 'after_related_enabled', 'format' => 'after_related_format'], 'archive_top' => ['group' => 'layout', 'enabled' => 'archive_top_enabled', 'format' => 'archive_format'], 'archive_between' => ['group' => 'layout', 'enabled' => 'archive_between_enabled', 'format' => 'archive_format'], 'archive_bottom' => ['group' => 'layout', 'enabled' => 'archive_bottom_enabled', 'format' => 'archive_format'], 'header_below' => ['group' => 'layout', 'enabled' => 'header_below_enabled', 'format' => 'global_format'], 'footer_above' => ['group' => 'layout', 'enabled' => 'footer_above_enabled', 'format' => 'global_format'], ]; if (!isset($locationMap[$locationKey])) { return ['enabled' => false, 'format' => 'auto']; } $map = $locationMap[$locationKey]; $group = $settings[$map['group']] ?? []; return [ 'enabled' => $group[$map['enabled']] ?? false, 'format' => $group[$map['format']] ?? 'auto', ]; } /** * Genera HTML del bloque de anuncio */ private function buildAdHTML(array $settings, string $format, string $location, string $visClasses): string { $publisherId = esc_attr($settings['content']['publisher_id'] ?? ''); $delayEnabled = ($settings['forms']['delay_enabled'] ?? true) === true; if (empty($publisherId)) { return ''; } // Obtener slot segun formato $slotId = $this->getSlotByFormat($settings, $format); if (empty($slotId)) { return ''; } $scriptType = $delayEnabled ? 'text/plain' : 'text/javascript'; $dataAttr = $delayEnabled ? ' data-adsense-push' : ''; $locationClass = 'roi-ad-' . esc_attr(str_replace('_', '-', $location)); return $this->generateAdMarkup($format, $publisherId, $slotId, $locationClass, $visClasses, $scriptType, $dataAttr); } /** * Obtiene el slot ID segun el formato */ private function getSlotByFormat(array $settings, string $format): string { $content = $settings['content'] ?? []; return match($format) { 'display', 'display-large', 'display-square' => $content['slot_display'] ?? '', 'in-article' => $content['slot_inarticle'] ?? '', 'autorelaxed' => $content['slot_autorelaxed'] ?? '', default => $content['slot_auto'] ?? '', }; } /** * Genera el markup HTML segun formato de anuncio * * EXCEPCION DOCUMENTADA: CSS inline requerido por Google AdSense * ---------------------------------------------------------------- * Los atributos style="display:inline-block", style="display:block", * style="text-align:center", etc. son ESPECIFICACION DE GOOGLE y NO * pueden generarse via CSSGenerator. * * Documentacion oficial: * - https://support.google.com/adsense/answer/9274516 * - https://support.google.com/adsense/answer/9183460 * * Estos estilos son necesarios para que AdSense funcione correctamente * y son inyectados tal como Google los especifica en su documentacion. */ private function generateAdMarkup( string $format, string $client, string $slot, string $locationClass, string $visClasses, string $scriptType, string $dataAttr ): string { $allClasses = trim("{$locationClass} {$visClasses}"); return match($format) { 'display' => $this->adDisplay($client, $slot, 728, 90, $allClasses, $scriptType, $dataAttr), 'display-large' => $this->adDisplay($client, $slot, 970, 250, $allClasses, $scriptType, $dataAttr), 'display-square' => $this->adDisplay($client, $slot, 300, 250, $allClasses, $scriptType, $dataAttr), 'in-article' => $this->adInArticle($client, $slot, $allClasses, $scriptType, $dataAttr), 'autorelaxed' => $this->adAutorelaxed($client, $slot, $allClasses, $scriptType, $dataAttr), default => $this->adAuto($client, $slot, $allClasses, $scriptType, $dataAttr), }; } private function adDisplay(string $c, string $s, int $w, int $h, string $cl, string $t, string $a): string { return sprintf( '
', esc_attr($cl), $w, $h, esc_attr($c), esc_attr($s), $t, $a ); } private function adAuto(string $c, string $s, string $cl, string $t, string $a): string { return sprintf( '', esc_attr($cl), esc_attr($c), esc_attr($s), $t, $a ); } private function adInArticle(string $c, string $s, string $cl, string $t, string $a): string { return sprintf( '', esc_attr($cl), esc_attr($c), esc_attr($s), $t, $a ); } private function adAutorelaxed(string $c, string $s, string $cl, string $t, string $a): string { return sprintf( '', esc_attr($cl), esc_attr($c), esc_attr($s), $t, $a ); } /** * Renderiza Rail Ads (anuncios fijos en margenes laterales) * Se inyectan via wp_footer y usan position: fixed */ public function renderRailAds(array $settings): string { // Verificar si Rail Ads estan habilitados if (!($settings['visibility']['is_enabled'] ?? false)) { return ''; } if (!($settings['behavior']['rail_ads_enabled'] ?? false)) { return ''; } $publisherId = esc_attr($settings['content']['publisher_id'] ?? ''); $slotId = $settings['content']['slot_skyscraper'] ?? ''; if (empty($publisherId) || empty($slotId)) { return ''; } $leftEnabled = ($settings['behavior']['rail_left_enabled'] ?? true) === true; $rightEnabled = ($settings['behavior']['rail_right_enabled'] ?? true) === true; $format = $settings['behavior']['rail_format'] ?? 'skyscraper'; $topOffset = (int)($settings['behavior']['rail_top_offset'] ?? 300); $delayEnabled = ($settings['forms']['delay_enabled'] ?? true) === true; // Dimensiones segun formato // skyscraper: 160x600, wide-skyscraper: 160x800, half-page: 300x600, large-skyscraper: 300x1050 [$width, $height] = match($format) { 'wide-skyscraper' => [160, 800], 'half-page' => [300, 600], 'large-skyscraper' => [300, 1050], default => [160, 600], // skyscraper }; $scriptType = $delayEnabled ? 'text/plain' : 'text/javascript'; $dataAttr = $delayEnabled ? ' data-adsense-push' : ''; // === CSS via CSSGenerator (NO hardcodeado) === $cssRules = []; // Estilos base para Rail Ads $cssRules[] = $this->cssGenerator->generate('.roi-rail-ad', [ 'position' => 'fixed', 'top' => $topOffset . 'px', 'width' => $width . 'px', 'z-index' => '100', 'transition' => 'top 0.2s ease-out', ]); // Posicion rail izquierdo - usar max() para evitar valores negativos // Formula: max(10px, calc((100vw - 1320px) / 2 - (width + 20)px)) $cssRules[] = $this->cssGenerator->generate('.roi-rail-ad-left', [ 'left' => 'max(10px, calc((100vw - 1320px) / 2 - ' . ($width + 20) . 'px))', ]); // Posicion rail derecho - usar max() para consistencia $cssRules[] = $this->cssGenerator->generate('.roi-rail-ad-right', [ 'right' => 'max(10px, calc((100vw - 1320px) / 2 - ' . ($width + 20) . 'px))', ]); // Media query para ocultar en pantallas < 1600px // NOTA: Media queries se escriben directamente (patron consistente con FeaturedImageRenderer) $cssRules[] = "@media (max-width: 1599px) { .roi-rail-ad { display: none !important; } }"; $css = implode("\n", $cssRules); // JavaScript para posicionamiento inteligente de Rail Ads // Detecta dinamicamente el header/hero/featured-image y ajusta posicion $js = " "; $html = "\n{$js}\n"; /** * EXCEPCION DOCUMENTADA: CSS inline requerido por Google AdSense * Los atributos style="display:inline-block;width:Xpx;height:Xpx" son * especificacion de Google y NO pueden generarse via CSSGenerator. * Ref: https://support.google.com/adsense/answer/9274516 */ // Rail izquierdo if ($leftEnabled) { $html .= sprintf( '', $width, $height, esc_attr($publisherId), esc_attr($slotId), $scriptType, $dataAttr ); } // Rail derecho if ($rightEnabled) { $html .= sprintf( '', $width, $height, esc_attr($publisherId), esc_attr($slotId), $scriptType, $dataAttr ); } return $html; } }