getData(); if (!$this->isEnabled($data)) { return ''; } if (!PageVisibilityHelper::shouldShow(self::COMPONENT_NAME)) { return ''; } $visibilityClass = $this->getVisibilityClass($data); if ($visibilityClass === null) { return ''; } global $wp_query; // Si no hay posts, mostrar mensaje if (!have_posts()) { $noPostsMessage = $data['content']['no_posts_message'] ?? 'No se encontraron publicaciones'; return $this->renderNoPostsMessage($noPostsMessage, $visibilityClass, $data); } $css = $this->generateCSS($data); $html = $this->buildHTML($data, $visibilityClass); return sprintf("\n%s", $css, $html); } public function supports(string $componentType): bool { return $componentType === self::COMPONENT_NAME; } private function isEnabled(array $data): bool { $value = $data['visibility']['is_enabled'] ?? false; return $value === true || $value === '1' || $value === 1; } private function getVisibilityClass(array $data): ?string { $showDesktop = $data['visibility']['show_on_desktop'] ?? true; $showDesktop = $showDesktop === true || $showDesktop === '1' || $showDesktop === 1; $showMobile = $data['visibility']['show_on_mobile'] ?? true; $showMobile = $showMobile === true || $showMobile === '1' || $showMobile === 1; if (!$showDesktop && !$showMobile) { return null; } if (!$showDesktop && $showMobile) { return 'd-lg-none'; } if ($showDesktop && !$showMobile) { return 'd-none d-lg-block'; } return ''; } private function renderNoPostsMessage(string $message, string $visibilityClass, array $data): string { $colors = $data['colors'] ?? []; $spacing = $data['spacing'] ?? []; $bgColor = $colors['card_bg_color'] ?? '#ffffff'; $textColor = $colors['excerpt_color'] ?? '#6b7280'; $borderColor = $colors['card_border_color'] ?? '#e5e7eb'; $padding = $spacing['card_padding'] ?? '1.25rem'; $css = $this->cssGenerator->generate('.post-grid-no-posts', [ 'background-color' => $bgColor, 'color' => $textColor, 'border' => "1px solid {$borderColor}", 'border-radius' => '0.5rem', 'padding' => '2rem', 'text-align' => 'center', ]); $containerClass = 'post-grid-no-posts'; if (!empty($visibilityClass)) { $containerClass .= ' ' . $visibilityClass; } $html = sprintf( '

%s

', esc_attr($containerClass), esc_html($message) ); return sprintf("\n%s", $css, $html); } private function generateCSS(array $data): string { $colors = $data['colors'] ?? []; $spacing = $data['spacing'] ?? []; $effects = $data['visual_effects'] ?? []; $typography = $data['typography'] ?? []; $layout = $data['layout'] ?? []; $cssRules = []; // Colores $cardBgColor = $colors['card_bg_color'] ?? '#ffffff'; $cardTitleColor = $colors['card_title_color'] ?? '#0E2337'; $cardHoverBgColor = $colors['card_hover_bg_color'] ?? '#f9fafb'; $cardBorderColor = $colors['card_border_color'] ?? '#e5e7eb'; $cardHoverBorderColor = $colors['card_hover_border_color'] ?? '#FF8600'; $excerptColor = $colors['excerpt_color'] ?? '#6b7280'; $metaColor = $colors['meta_color'] ?? '#9ca3af'; $categoryBgColor = $colors['category_bg_color'] ?? '#FFF5EB'; $categoryTextColor = $colors['category_text_color'] ?? '#FF8600'; $paginationColor = $colors['pagination_color'] ?? '#0E2337'; $paginationActiveBg = $colors['pagination_active_bg'] ?? '#FF8600'; $paginationActiveColor = $colors['pagination_active_color'] ?? '#ffffff'; // Spacing $gapHorizontal = $spacing['gap_horizontal'] ?? '24px'; $gapVertical = $spacing['gap_vertical'] ?? '24px'; $cardPadding = $spacing['card_padding'] ?? '20px'; $sectionMarginTop = $spacing['section_margin_top'] ?? '0px'; $sectionMarginBottom = $spacing['section_margin_bottom'] ?? '32px'; // Visual effects $cardBorderRadius = $effects['card_border_radius'] ?? '0.5rem'; $cardShadow = $effects['card_shadow'] ?? '0 1px 3px rgba(0,0,0,0.1)'; $cardHoverShadow = $effects['card_hover_shadow'] ?? '0 4px 12px rgba(0,0,0,0.15)'; $cardTransition = $effects['card_transition'] ?? 'all 0.3s ease'; $imageBorderRadius = $effects['image_border_radius'] ?? '0.375rem'; // Typography $cardTitleSize = $typography['card_title_size'] ?? '1.1rem'; $cardTitleWeight = $typography['card_title_weight'] ?? '600'; $excerptSize = $typography['excerpt_size'] ?? '0.9rem'; $metaSize = $typography['meta_size'] ?? '0.8rem'; // Container $cssRules[] = $this->cssGenerator->generate('.post-grid', [ 'margin-top' => $sectionMarginTop, 'margin-bottom' => $sectionMarginBottom, ]); // Row: usar display flex con gap, quitar margins/paddings de Bootstrap $cssRules[] = ".post-grid .row { display: flex; flex-wrap: wrap; column-gap: {$gapHorizontal}; row-gap: {$gapVertical}; margin: 0; padding: 0; }"; // Columnas: quitar padding de Bootstrap y margin-bottom $cssRules[] = ".post-grid .post-card-col { padding: 0; margin: 0; }"; // Card base - sin margin extra $cssRules[] = ".post-grid .card { background: {$cardBgColor}; border: 1px solid {$cardBorderColor}; border-radius: {$cardBorderRadius}; box-shadow: {$cardShadow}; transition: {$cardTransition}; height: 100%; overflow: hidden; margin: 0; }"; // Card hover $cssRules[] = ".post-grid .card:hover { background: {$cardHoverBgColor}; border-color: {$cardHoverBorderColor}; box-shadow: {$cardHoverShadow}; transform: translateY(-2px); }"; // Card body $cssRules[] = $this->cssGenerator->generate('.post-grid .card-body', [ 'padding' => $cardPadding, ]); // Card image $cssRules[] = ".post-grid .card-img-top { border-radius: {$imageBorderRadius} {$imageBorderRadius} 0 0; object-fit: cover; width: 100%; height: 200px; }"; // Card title $cssRules[] = ".post-grid .card-title { color: {$cardTitleColor}; font-size: {$cardTitleSize}; font-weight: {$cardTitleWeight}; line-height: 1.4; margin-bottom: 0.75rem; }"; // Card title hover $cssRules[] = ".post-grid a:hover .card-title { color: {$cardHoverBorderColor}; }"; // Excerpt $cssRules[] = ".post-grid .card-text { color: {$excerptColor}; font-size: {$excerptSize}; line-height: 1.6; }"; // Meta $cssRules[] = ".post-grid .post-meta { color: {$metaColor}; font-size: {$metaSize}; }"; // Categories $cssRules[] = ".post-grid .post-category { background: {$categoryBgColor}; color: {$categoryTextColor}; font-size: 0.75rem; font-weight: 600; padding: 0.25rem 0.75rem; border-radius: 9999px; text-decoration: none; display: inline-block; margin-right: 0.5rem; margin-bottom: 0.5rem; }"; $cssRules[] = ".post-grid .post-category:hover { background: {$categoryTextColor}; color: #ffffff; }"; // Pagination $cssRules[] = ".post-grid .pagination { margin-top: 2rem; }"; $cssRules[] = ".post-grid .page-link { color: {$paginationColor}; border: 1px solid {$cardBorderColor}; padding: 0.5rem 1rem; margin: 0 0.25rem; border-radius: 4px; font-weight: 500; transition: all 0.3s ease; }"; $cssRules[] = ".post-grid .page-link:hover { background-color: rgba(255, 133, 0, 0.1); border-color: {$paginationActiveBg}; color: {$paginationActiveBg}; }"; $cssRules[] = ".post-grid .page-item.active .page-link, .post-grid .nav-links .current { background-color: {$paginationActiveBg}; border-color: {$paginationActiveBg}; color: {$paginationActiveColor}; }"; // WordPress pagination classes $cssRules[] = ".post-grid .nav-links { display: flex; justify-content: center; gap: 0.5rem; margin-top: 2rem; }"; $cssRules[] = ".post-grid .nav-links a, .post-grid .nav-links span { color: {$paginationColor}; border: 1px solid {$cardBorderColor}; padding: 0.5rem 1rem; border-radius: 4px; font-weight: 500; transition: all 0.3s ease; text-decoration: none; }"; $cssRules[] = ".post-grid .nav-links a:hover { background-color: rgba(255, 133, 0, 0.1); border-color: {$paginationActiveBg}; color: {$paginationActiveBg}; }"; // Layout responsive columns $colsDesktop = $layout['columns_desktop'] ?? '3'; $colsTablet = $layout['columns_tablet'] ?? '2'; $colsMobile = $layout['columns_mobile'] ?? '1'; // Mobile (1 col = no gap needed) $mobileWidth = $this->getColumnWidth($colsMobile, $gapHorizontal); $cssRules[] = "@media (max-width: 575.98px) { .post-grid .post-card-col { flex: 0 0 {$mobileWidth}; max-width: {$mobileWidth}; } }"; // Tablet $tabletWidth = $this->getColumnWidth($colsTablet, $gapHorizontal); $cssRules[] = "@media (min-width: 576px) and (max-width: 991.98px) { .post-grid .post-card-col { flex: 0 0 {$tabletWidth}; max-width: {$tabletWidth}; } }"; // Desktop $desktopWidth = $this->getColumnWidth($colsDesktop, $gapHorizontal); $cssRules[] = "@media (min-width: 992px) { .post-grid .post-card-col { flex: 0 0 {$desktopWidth}; max-width: {$desktopWidth}; } }"; return implode("\n", $cssRules); } /** * Calcula el ancho de columna considerando el gap * * Con gap en flexbox, el ancho debe ser: * (100% - (n-1)*gap) / n * * @param string $cols Número de columnas * @param string $gap Valor del gap (ej: '1.5rem', '24px') * @return string Valor CSS con calc() si hay gap */ private function getColumnWidth(string $cols, string $gap): string { $colCount = (int)$cols; if ($colCount <= 0) { $colCount = 1; } // Si es 1 columna, no hay gap entre columnas if ($colCount === 1) { return '100%'; } // Número de gaps = columnas - 1 $gapCount = $colCount - 1; // calc((100% - (n-1)*gap) / n) return sprintf('calc((100%% - %d * %s) / %d)', $gapCount, $gap, $colCount); } private function buildHTML(array $data, string $visibilityClass): string { $content = $data['content'] ?? []; $typography = $data['typography'] ?? []; $media = $data['media'] ?? []; $layout = $data['layout'] ?? []; $showThumbnail = $this->toBool($content['show_thumbnail'] ?? true); $showExcerpt = $this->toBool($content['show_excerpt'] ?? true); $showMeta = $this->toBool($content['show_meta'] ?? true); $showCategories = $this->toBool($content['show_categories'] ?? true); $excerptLength = (int)($content['excerpt_length'] ?? 20); $readMoreText = $content['read_more_text'] ?? 'Leer mas'; $headingLevel = $typography['heading_level'] ?? 'h3'; $fallbackImage = $media['fallback_image'] ?? ''; $fallbackImageAlt = $media['fallback_image_alt'] ?? 'Imagen por defecto'; $imagePosition = $layout['image_position'] ?? 'top'; $containerClass = 'post-grid'; if (!empty($visibilityClass)) { $containerClass .= ' ' . $visibilityClass; } $html = sprintf('
', esc_attr($containerClass)); $html .= '
'; while (have_posts()) { the_post(); $html .= $this->buildCardHTML( $showThumbnail, $showExcerpt, $showMeta, $showCategories, $excerptLength, $readMoreText, $headingLevel, $fallbackImage, $fallbackImageAlt, $imagePosition ); } $html .= '
'; // Paginacion nativa de WordPress $html .= '
'; $html .= $this->buildPaginationHTML(); $html .= '
'; $html .= '
'; wp_reset_postdata(); return $html; } private function toBool(mixed $value): bool { return $value === true || $value === '1' || $value === 1; } private function buildCardHTML( bool $showThumbnail, bool $showExcerpt, bool $showMeta, bool $showCategories, int $excerptLength, string $readMoreText, string $headingLevel, string $fallbackImage, string $fallbackImageAlt, string $imagePosition ): string { $permalink = get_permalink(); $title = get_the_title(); $html = '
'; $html .= sprintf( '', esc_url($permalink) ); $cardClass = 'card h-100'; if ($imagePosition === 'left') { $cardClass .= ' flex-row'; } $html .= sprintf('
', esc_attr($cardClass)); // Imagen if ($showThumbnail && $imagePosition !== 'none') { $html .= $this->buildImageHTML($fallbackImage, $fallbackImageAlt, $imagePosition); } $html .= '
'; // Categorias if ($showCategories) { $html .= $this->buildCategoriesHTML(); } // Titulo $html .= sprintf( '<%s class="card-title">%s', esc_attr($headingLevel), esc_html($title), esc_attr($headingLevel) ); // Meta if ($showMeta) { $html .= $this->buildMetaHTML(); } // Excerpt if ($showExcerpt) { $html .= $this->buildExcerptHTML($excerptLength); } $html .= '
'; // card-body $html .= '
'; // card $html .= '
'; $html .= '
'; // col return $html; } private function buildImageHTML(string $fallbackImage, string $fallbackImageAlt, string $imagePosition): string { if (has_post_thumbnail()) { $imageClass = $imagePosition === 'left' ? 'card-img-left' : 'card-img-top'; return get_the_post_thumbnail( null, 'medium_large', ['class' => $imageClass, 'loading' => 'lazy'] ); } if (!empty($fallbackImage)) { $imageClass = $imagePosition === 'left' ? 'card-img-left' : 'card-img-top'; return sprintf( '%s', esc_url($fallbackImage), esc_attr($fallbackImageAlt), esc_attr($imageClass) ); } return ''; } private function buildCategoriesHTML(): string { $categories = get_the_category(); if (empty($categories)) { return ''; } $html = '
'; foreach (array_slice($categories, 0, 2) as $category) { $html .= sprintf( '%s', esc_html($category->name) ); } $html .= '
'; return $html; } private function buildMetaHTML(): string { $date = get_the_date(); $author = get_the_author(); return sprintf( '
%s | %s
', esc_html($date), esc_html($author) ); } private function buildExcerptHTML(int $length): string { $excerpt = get_the_excerpt(); if (empty($excerpt)) { $excerpt = wp_trim_words(get_the_content(), $length, '...'); } else { $excerpt = wp_trim_words($excerpt, $length, '...'); } return sprintf( '

%s

', esc_html($excerpt) ); } private function buildPaginationHTML(): string { global $wp_query; $totalPages = $wp_query->max_num_pages; if ($totalPages <= 1) { return ''; } $currentPage = max(1, get_query_var('paged', 1)); $html = ''; return $html; } }