- Disable lazy loading by default for featured images (LCP element) - Add fetchpriority="high" attribute for faster loading - Remove fetchpriority when lazy loading is enabled 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
205 lines
6.1 KiB
PHP
205 lines
6.1 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace ROITheme\Public\FeaturedImage\Infrastructure\Ui;
|
|
|
|
use ROITheme\Shared\Domain\Contracts\RendererInterface;
|
|
use ROITheme\Shared\Domain\Contracts\CSSGeneratorInterface;
|
|
use ROITheme\Shared\Domain\Entities\Component;
|
|
|
|
/**
|
|
* FeaturedImageRenderer - Renderiza la imagen destacada del post
|
|
*
|
|
* RESPONSABILIDAD: Generar HTML y CSS de la imagen destacada
|
|
*
|
|
* CARACTERISTICAS:
|
|
* - Integracion con get_the_post_thumbnail()
|
|
* - Estilos configurables desde BD
|
|
* - Efecto hover opcional
|
|
* - Soporte responsive
|
|
*
|
|
* Cumple con:
|
|
* - DIP: Recibe CSSGeneratorInterface por constructor
|
|
* - SRP: Una responsabilidad (renderizar featured image)
|
|
* - Clean Architecture: Infrastructure puede usar WordPress
|
|
*
|
|
* @package ROITheme\Public\FeaturedImage\Infrastructure\Ui
|
|
*/
|
|
final class FeaturedImageRenderer implements RendererInterface
|
|
{
|
|
public function __construct(
|
|
private CSSGeneratorInterface $cssGenerator
|
|
) {}
|
|
|
|
public function render(Component $component): string
|
|
{
|
|
$data = $component->getData();
|
|
|
|
if (!$this->isEnabled($data)) {
|
|
return '';
|
|
}
|
|
|
|
if (!$this->shouldShowOnCurrentPage($data)) {
|
|
return '';
|
|
}
|
|
|
|
if (!$this->hasPostThumbnail()) {
|
|
return '';
|
|
}
|
|
|
|
$css = $this->generateCSS($data);
|
|
$html = $this->buildHTML($data);
|
|
|
|
return sprintf("<style>%s</style>\n%s", $css, $html);
|
|
}
|
|
|
|
public function supports(string $componentType): bool
|
|
{
|
|
return $componentType === 'featured-image';
|
|
}
|
|
|
|
private function isEnabled(array $data): bool
|
|
{
|
|
return ($data['visibility']['is_enabled'] ?? false) === true;
|
|
}
|
|
|
|
private function shouldShowOnCurrentPage(array $data): bool
|
|
{
|
|
$showOn = $data['visibility']['show_on_pages'] ?? 'posts';
|
|
|
|
switch ($showOn) {
|
|
case 'all':
|
|
return true;
|
|
case 'posts':
|
|
return is_single();
|
|
case 'pages':
|
|
return is_page();
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private function hasPostThumbnail(): bool
|
|
{
|
|
return is_singular() && has_post_thumbnail();
|
|
}
|
|
|
|
private function generateCSS(array $data): string
|
|
{
|
|
$spacing = $data['spacing'] ?? [];
|
|
$effects = $data['visual_effects'] ?? [];
|
|
$visibility = $data['visibility'] ?? [];
|
|
|
|
$marginTop = $spacing['margin_top'] ?? '1rem';
|
|
$marginBottom = $spacing['margin_bottom'] ?? '2rem';
|
|
|
|
$borderRadius = $effects['border_radius'] ?? '12px';
|
|
$boxShadow = $effects['box_shadow'] ?? '0 8px 24px rgba(0, 0, 0, 0.1)';
|
|
$hoverEffect = $effects['hover_effect'] ?? true;
|
|
$hoverScale = $effects['hover_scale'] ?? '1.02';
|
|
$transitionDuration = $effects['transition_duration'] ?? '0.3s';
|
|
|
|
$showOnDesktop = $visibility['show_on_desktop'] ?? true;
|
|
$showOnMobile = $visibility['show_on_mobile'] ?? true;
|
|
|
|
$cssRules = [];
|
|
|
|
// Container styles
|
|
$cssRules[] = $this->cssGenerator->generate('.featured-image-container', [
|
|
'border-radius' => $borderRadius,
|
|
'overflow' => 'hidden',
|
|
'box-shadow' => $boxShadow,
|
|
'margin-top' => $marginTop,
|
|
'margin-bottom' => $marginBottom,
|
|
'transition' => "transform {$transitionDuration} ease, box-shadow {$transitionDuration} ease",
|
|
]);
|
|
|
|
// Image styles
|
|
$cssRules[] = $this->cssGenerator->generate('.featured-image-container img', [
|
|
'width' => '100%',
|
|
'height' => 'auto',
|
|
'display' => 'block',
|
|
'transition' => "transform {$transitionDuration} ease",
|
|
]);
|
|
|
|
// Hover effect
|
|
if ($hoverEffect) {
|
|
$cssRules[] = $this->cssGenerator->generate('.featured-image-container:hover', [
|
|
'box-shadow' => '0 12px 32px rgba(0, 0, 0, 0.15)',
|
|
]);
|
|
|
|
$cssRules[] = $this->cssGenerator->generate('.featured-image-container:hover img', [
|
|
'transform' => "scale({$hoverScale})",
|
|
]);
|
|
}
|
|
|
|
// Link styles (remove default link styling)
|
|
$cssRules[] = $this->cssGenerator->generate('.featured-image-container a', [
|
|
'display' => 'block',
|
|
'line-height' => '0',
|
|
]);
|
|
|
|
// Responsive visibility
|
|
if (!$showOnMobile) {
|
|
$cssRules[] = "@media (max-width: 767.98px) {
|
|
.featured-image-container { display: none !important; }
|
|
}";
|
|
}
|
|
|
|
if (!$showOnDesktop) {
|
|
$cssRules[] = "@media (min-width: 768px) {
|
|
.featured-image-container { display: none !important; }
|
|
}";
|
|
}
|
|
|
|
return implode("\n", $cssRules);
|
|
}
|
|
|
|
private function buildHTML(array $data): string
|
|
{
|
|
$content = $data['content'] ?? [];
|
|
|
|
$imageSize = $content['image_size'] ?? 'roi-featured-large';
|
|
$lazyLoading = $content['lazy_loading'] ?? false; // LCP: no lazy por defecto
|
|
$linkToMedia = $content['link_to_media'] ?? false;
|
|
|
|
$imgAttr = [
|
|
'class' => 'img-fluid featured-image',
|
|
'alt' => get_the_title(),
|
|
'fetchpriority' => 'high', // LCP optimization
|
|
];
|
|
|
|
if ($lazyLoading) {
|
|
$imgAttr['loading'] = 'lazy';
|
|
unset($imgAttr['fetchpriority']); // No fetchpriority si es lazy
|
|
}
|
|
|
|
$thumbnail = get_the_post_thumbnail(null, $imageSize, $imgAttr);
|
|
|
|
if (empty($thumbnail)) {
|
|
return '';
|
|
}
|
|
|
|
$html = '<div class="featured-image-container">';
|
|
|
|
if ($linkToMedia) {
|
|
$fullImageUrl = get_the_post_thumbnail_url(null, 'full');
|
|
$html .= sprintf(
|
|
'<a href="%s" target="_blank" rel="noopener" aria-label="%s">',
|
|
esc_url($fullImageUrl),
|
|
esc_attr__('Ver imagen en tamano completo', 'roi-theme')
|
|
);
|
|
}
|
|
|
|
$html .= $thumbnail;
|
|
|
|
if ($linkToMedia) {
|
|
$html .= '</a>';
|
|
}
|
|
|
|
$html .= '</div>';
|
|
|
|
return $html;
|
|
}
|
|
}
|