have_posts()) { return $this->renderNoPostsMessage($settings, $options); } $css = $this->generateCSS($settings, $options); $html = $this->buildHTML($query, $settings, $options); return sprintf("\n%s", $css, $html); } private function renderNoPostsMessage(array $settings, array $options): string { $colors = $settings['colors'] ?? []; $message = 'No se encontraron publicaciones'; $bgColor = $colors['card_bg_color'] ?? '#ffffff'; $textColor = $colors['excerpt_color'] ?? '#6b7280'; $borderColor = $colors['card_border_color'] ?? '#e5e7eb'; $selector = $this->getSelector($options); $css = $this->cssGenerator->generate("{$selector} .no-posts", [ 'background-color' => $bgColor, 'color' => $textColor, 'border' => "1px solid {$borderColor}", 'border-radius' => '0.5rem', 'padding' => '2rem', 'text-align' => 'center', ]); $containerClass = $this->getContainerClass($options); return sprintf( "\n

%s

", $css, esc_attr($containerClass), esc_html($message) ); } private function getSelector(array $options): string { $id = $options['id'] ?? ''; return !empty($id) ? ".roi-post-grid-shortcode-{$id}" : '.roi-post-grid-shortcode'; } private function getContainerClass(array $options): string { $id = $options['id'] ?? ''; $customClass = $options['class'] ?? ''; $class = !empty($id) ? "roi-post-grid-shortcode roi-post-grid-shortcode-{$id}" : 'roi-post-grid-shortcode'; if (!empty($customClass)) { $class .= ' ' . sanitize_html_class($customClass); } return $class; } private function generateCSS(array $settings, array $options): string { $colors = $settings['colors'] ?? []; $spacing = $settings['spacing'] ?? []; $effects = $settings['visual_effects'] ?? []; $typography = $settings['typography'] ?? []; $selector = $this->getSelector($options); $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'; // Spacing $gridGap = $spacing['grid_gap'] ?? '1.5rem'; $cardPadding = $spacing['card_padding'] ?? '1.25rem'; // 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'; // 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($selector, [ 'margin-bottom' => '2rem', ]); // Row $cssRules[] = $this->cssGenerator->generate("{$selector} .row", [ 'row-gap' => $gridGap, ]); // Card $cssRules[] = "{$selector} .card { background: {$cardBgColor}; border: 1px solid {$cardBorderColor}; border-radius: {$cardBorderRadius}; box-shadow: {$cardShadow}; transition: {$cardTransition}; height: 100%; }"; $cssRules[] = "{$selector} .card:hover { background: {$cardHoverBgColor}; border-color: {$cardHoverBorderColor}; box-shadow: {$cardHoverShadow}; transform: translateY(-2px); }"; $cssRules[] = $this->cssGenerator->generate("{$selector} .card-body", [ 'padding' => $cardPadding, ]); $cssRules[] = "{$selector} .card-img-top { border-radius: {$cardBorderRadius} {$cardBorderRadius} 0 0; object-fit: cover; width: 100%; height: 200px; }"; $cssRules[] = "{$selector} .card-title { color: {$cardTitleColor}; font-size: {$cardTitleSize}; font-weight: {$cardTitleWeight}; line-height: 1.4; margin-bottom: 0.75rem; }"; $cssRules[] = "{$selector} a:hover .card-title { color: {$cardHoverBorderColor}; }"; $cssRules[] = "{$selector} .card-text { color: {$excerptColor}; font-size: {$excerptSize}; line-height: 1.6; }"; $cssRules[] = "{$selector} .post-meta { color: {$metaColor}; font-size: {$metaSize}; }"; $cssRules[] = "{$selector} .post-category { background: {$categoryBgColor}; color: {$categoryTextColor}; font-size: 0.75rem; font-weight: 600; padding: 0.25rem 0.75rem; border-radius: 9999px; display: inline-block; margin-right: 0.5rem; margin-bottom: 0.5rem; }"; return implode("\n", $cssRules); } private function buildHTML(\WP_Query $query, array $settings, array $options): string { $columns = (int) ($options['columns'] ?? 3); $showThumbnail = $this->toBool($options['show_thumbnail'] ?? true); $showExcerpt = $this->toBool($options['show_excerpt'] ?? true); $showMeta = $this->toBool($options['show_meta'] ?? true); $showCategories = $this->toBool($options['show_categories'] ?? true); $excerptLength = (int) ($options['excerpt_length'] ?? 20); $showPagination = $this->toBool($options['show_pagination'] ?? false); $containerClass = $this->getContainerClass($options); $colClass = $this->getColumnClass($columns); $html = sprintf('
', esc_attr($containerClass)); $html .= '
'; while ($query->have_posts()) { $query->the_post(); $html .= $this->buildCardHTML( $colClass, $showThumbnail, $showExcerpt, $showMeta, $showCategories, $excerptLength ); } $html .= '
'; if ($showPagination && $query->max_num_pages > 1) { $html .= $this->buildPaginationHTML($query, $options); } $html .= '
'; return $html; } private function getColumnClass(int $columns): string { return match ($columns) { 1 => 'col-12', 2 => 'col-12 col-md-6', 4 => 'col-12 col-md-6 col-lg-3', default => 'col-12 col-md-6 col-lg-4', }; } private function buildCardHTML( string $colClass, bool $showThumbnail, bool $showExcerpt, bool $showMeta, bool $showCategories, int $excerptLength ): string { $permalink = get_permalink(); $title = get_the_title(); $html = sprintf('
', esc_attr($colClass)); $html .= sprintf('', esc_url($permalink)); $html .= '
'; if ($showThumbnail) { $html .= $this->buildImageHTML(); } $html .= '
'; if ($showCategories) { $html .= $this->buildCategoriesHTML(); } $html .= sprintf('

%s

', esc_html($title)); if ($showMeta) { $html .= $this->buildMetaHTML(); } if ($showExcerpt) { $html .= $this->buildExcerptHTML($excerptLength); } $html .= '
'; return $html; } private function buildImageHTML(): string { if (has_post_thumbnail()) { return get_the_post_thumbnail(null, 'medium_large', [ 'class' => 'card-img-top', 'loading' => 'lazy' ]); } 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 { return sprintf( '
%s | %s
', esc_html(get_the_date()), esc_html(get_the_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(\WP_Query $query, array $options): string { $id = $options['id'] ?? ''; $queryVar = !empty($id) ? "paged_{$id}" : 'paged'; $currentPage = max(1, (int) get_query_var($queryVar, 1)); $totalPages = $query->max_num_pages; $html = ''; return $html; } private function toBool(mixed $value): bool { if (is_bool($value)) { return $value; } return $value === 'true' || $value === '1' || $value === 1; } }