getData(); // Validar visibilidad general if (!$this->isEnabled($data)) { return ''; } // Validar visibilidad por página if (!PageVisibilityHelper::shouldShow(self::COMPONENT_NAME)) { return ''; } // Generar HTML $html = $this->buildHTML($data); // Si is_critical=true, CSS ya fue inyectado en por CriticalCSSService $isCritical = $data['visibility']['is_critical'] ?? false; if ($isCritical) { return $html; // Solo HTML, sin CSS inline } // CSS inline para componentes no críticos $css = $this->generateCSS($data); return sprintf("\n%s", $css, $html); } /** * {@inheritDoc} */ public function supports(string $componentType): bool { return $componentType === self::COMPONENT_NAME; } /** * Verificar si el componente está habilitado * * @param array $data Datos del componente * @return bool */ private function isEnabled(array $data): bool { return ($data['visibility']['is_enabled'] ?? false) === true; } /** * Verificar si el componente fue dismissed por el usuario * * @param array $data Datos del componente * @return bool */ private function isDismissed(array $data): bool { if (!$this->isDismissible($data)) { return false; } $cookieName = 'roi_notification_bar_dismissed'; return isset($_COOKIE[$cookieName]) && $_COOKIE[$cookieName] === '1'; } /** * Verificar si el componente es dismissible * * @param array $data Datos del componente * @return bool */ private function isDismissible(array $data): bool { return ($data['behavior']['is_dismissible'] ?? false) === true; } /** * Generar CSS usando CSSGeneratorService * * Este método es público para que CriticalCSSService pueda * generar CSS crítico antes de wp_head sin duplicar lógica. * * @param array $data Datos del componente * @return string CSS generado */ public function generateCSS(array $data): string { $css = ''; // Estilos base de la barra $baseStyles = [ 'background_color' => $data['styles']['background_color'] ?? '#0E2337', 'color' => $data['styles']['text_color'] ?? '#FFFFFF', 'font_size' => $data['styles']['font_size'] ?? '0.9rem', 'padding' => $data['styles']['padding'] ?? '0.5rem 0', 'width' => '100%', 'z_index' => '1050', ]; $css .= $this->cssGenerator->generate('.top-notification-bar', $baseStyles); // Estilos del ícono $iconStyles = [ 'color' => $data['styles']['icon_color'] ?? '#FF8600', ]; $css .= "\n" . $this->cssGenerator->generate('.top-notification-bar .notification-icon', $iconStyles); // Estilos de la etiqueta (label) $labelStyles = [ 'color' => $data['styles']['label_color'] ?? '#FF8600', ]; $css .= "\n" . $this->cssGenerator->generate('.top-notification-bar .notification-label', $labelStyles); // Estilos del enlace $linkStyles = [ 'color' => $data['styles']['link_color'] ?? '#FFFFFF', ]; $css .= "\n" . $this->cssGenerator->generate('.top-notification-bar .notification-link', $linkStyles); // Estilos del enlace hover $linkHoverStyles = [ 'color' => $data['styles']['link_hover_color'] ?? '#FF8600', ]; $css .= "\n" . $this->cssGenerator->generate('.top-notification-bar .notification-link:hover', $linkHoverStyles); // Estilos del ícono personalizado $customIconStyles = [ 'width' => '24px', 'height' => '24px', ]; $css .= "\n" . $this->cssGenerator->generate('.top-notification-bar .custom-icon', $customIconStyles); return $css; } /** * Generar HTML del componente * * @param array $data Datos del componente * @return string HTML generado */ private function buildHTML(array $data): string { $classes = $this->buildClasses($data); $content = $this->buildContent($data); return sprintf( '
%s
', esc_attr($classes), $content ); } /** * Construir clases CSS del componente * * @param array $data Datos del componente * @return string Clases CSS */ private function buildClasses(array $data): string { $classes = ['top-notification-bar']; // Agregar clases de visibilidad responsive $showOnDesktop = ($data['visibility']['show_on_desktop'] ?? true) === true; $showOnMobile = ($data['visibility']['show_on_mobile'] ?? true) === true; $visibilityClasses = $this->getVisibilityClasses($showOnDesktop, $showOnMobile); if ($visibilityClasses !== null) { $classes[] = $visibilityClasses; } return implode(' ', $classes); } /** * Construir atributos data para dismissible * * @param array $data Datos del componente * @return string Atributos HTML */ private function buildDismissAttributes(array $data): string { if (!$this->isDismissible($data)) { return ''; } $days = (int) ($data['behavior']['dismissible_cookie_days'] ?? 7); return sprintf(' data-dismissible-days="%d"', $days); } /** * Construir contenido del componente * * @param array $data Datos del componente * @return string HTML del contenido */ private function buildContent(array $data): string { $html = '
'; $html .= '
'; // Ícono $html .= $this->buildIcon($data); // Texto del anuncio $html .= $this->buildAnnouncementText($data); // Enlace $html .= $this->buildLink($data); $html .= '
'; $html .= '
'; return $html; } /** * Construir ícono del componente * * @param array $data Datos del componente * @return string HTML del ícono */ private function buildIcon(array $data): string { // Siempre usar Bootstrap icon desde content.icon_class $iconClass = $data['content']['icon_class'] ?? 'bi-megaphone-fill'; // Asegurar prefijo 'bi-' if (strpos($iconClass, 'bi-') !== 0) { $iconClass = 'bi-' . $iconClass; } return sprintf( '', esc_attr($iconClass) ); } /** * Construir texto del anuncio * * @param array $data Datos del componente * @return string HTML del texto */ private function buildAnnouncementText(array $data): string { $label = $data['content']['label_text'] ?? ''; $text = $data['content']['message_text'] ?? ''; if (empty($text)) { return ''; } $html = ''; if (!empty($label)) { $html .= sprintf('%s ', esc_html($label)); } $html .= esc_html($text); $html .= ''; return $html; } /** * Construir enlace de acción * * @param array $data Datos del componente * @return string HTML del enlace */ private function buildLink(array $data): string { $linkText = $data['content']['link_text'] ?? ''; $linkUrl = $data['content']['link_url'] ?? '#'; if (empty($linkText)) { return ''; } return sprintf( '%s', esc_url($linkUrl), esc_html($linkText) ); } /** * Construir botón de cerrar * * @return string HTML del botón */ private function buildDismissButton(): string { return ''; } /** * Construir estilos CSS de animaciones * * @param array $data Datos del componente * @return string CSS de animaciones */ private function buildAnimationStyles(array $data): string { $animationType = $data['visual_effects']['animation_type'] ?? 'slide-down'; $animations = [ 'slide-down' => [ 'keyframes' => '@keyframes roiSlideDown { from { transform: translateY(-100%); opacity: 0; } to { transform: translateY(0); opacity: 1; } }', 'animation' => 'roiSlideDown 0.5s ease-out', ], 'fade-in' => [ 'keyframes' => '@keyframes roiFadeIn { from { opacity: 0; } to { opacity: 1; } }', 'animation' => 'roiFadeIn 0.5s ease-out', ], ]; $anim = $animations[$animationType] ?? $animations['slide-down']; return sprintf( "%s\n.top-notification-bar.roi-animated.roi-%s { animation: %s; }", $anim['keyframes'], $animationType, $anim['animation'] ); } /** * Construir script para funcionalidad dismissible * * @param array $data Datos del componente * @return string JavaScript */ private function buildDismissScript(array $data): string { $days = (int) ($data['behavior']['dismissible_cookie_days'] ?? 7); return sprintf( '', $days ); } /** * Obtiene las clases CSS de Bootstrap para visibilidad responsive * * Implementa tabla de decisión según especificación (10.03): * - Desktop Y Mobile = null (visible en ambos) * - Solo Desktop = 'd-none d-lg-block' * - Solo Mobile = 'd-lg-none' * - Ninguno = 'd-none' (oculto) * * @param bool $desktop Mostrar en desktop * @param bool $mobile Mostrar en mobile * @return string|null Clases CSS o null si visible en ambos */ private function getVisibilityClasses(bool $desktop, bool $mobile): ?string { // Desktop Y Mobile = visible en ambos dispositivos if ($desktop && $mobile) { return null; // Sin clases = visible siempre } // Solo Desktop if ($desktop && !$mobile) { return 'd-none d-lg-block'; } // Solo Mobile if (!$desktop && $mobile) { return 'd-lg-none'; } // Ninguno = oculto completamente return 'd-none'; } }