" data-zx-post=""> *
[]
*
*

…

*
…·…
(classic / full content) *

…

|
…
(full content) * Read more *
* (cards) * * * The skin, image position and parts come from the widget settings; the post is always passed explicitly (never * the_post()), so global state stays intact. */ final class PostCard { public const SKINS = ['classic', 'cards', 'full_content']; public const META = ['author', 'date', 'comments', 'modified']; public const TITLE_TAGS = ['h2', 'h3', 'h4', 'h5', 'h6', 'p', 'div']; public const SIZES = ['thumbnail', 'medium', 'medium_large', 'large', 'full']; public static function skin(array $settings): string { $skin = (string) ($settings['skin'] ?? 'classic'); return in_array($skin, self::SKINS, true) ? $skin : 'classic'; } /** Image position of the classic skin: top | left | right (cards and full content: top). */ public static function image_position(array $settings): string { $pos = (string) ($settings['image_position'] ?? 'top'); return self::skin($settings) === 'classic' && in_array($pos, ['left', 'right'], true) ? $pos : 'top'; } /** Classes for the widget root (skin, image position, masonry / equal height). @return string[] */ public static function root_classes(array $settings): array { $skin = self::skin($settings); $classes = ['zx-posts--' . str_replace('_', '-', $skin), 'zx-posts--img-' . self::image_position($settings)]; if (!empty($settings['masonry'])) { $classes[] = 'zx-posts--masonry'; } elseif (!array_key_exists('equal_height', $settings) || !empty($settings['equal_height'])) { $classes[] = 'zx-posts--equal'; } return $classes; } /** Loop item renderer: `$item($renderer, $settings, $post_id, $position)`. */ public static function item(Renderer $renderer, array $settings, int $post_id, int $position = 1): string { $post = function_exists('get_post') ? get_post($post_id) : null; if (!is_object($post) || !isset($post->ID)) { return ''; } $on = static fn(string $key, bool $default = true): bool => array_key_exists($key, $settings) ? !empty($settings[$key]) : $default; $skin = self::skin($settings); $url = function_exists('get_permalink') ? (string) get_permalink($post) : ''; $title = wp_strip_all_tags((string) (function_exists('get_the_title') ? get_the_title($post_id) : ($post->post_title ?? ''))); $title_text = $title !== '' ? $title : __('(no title)', 'zadex'); $media = ''; $has_thumb = false; if ($on('show_image')) { $size = in_array($settings['image_size'] ?? 'medium_large', self::SIZES, true) ? (string) $settings['image_size'] : 'medium_large'; $img = function_exists('get_the_post_thumbnail') ? (string) get_the_post_thumbnail($post, $size, ['class' => 'zx-posts__img', 'loading' => 'lazy', 'decoding' => 'async', 'alt' => '']) : ''; if ($img !== '') { $has_thumb = true; $media = ''; } } if ($skin === 'cards' && $on('show_badge')) { $media .= self::badge($post_id, (string) ($settings['badge_taxonomy'] ?? 'category')); } $body = ''; if ($on('show_title')) { $tag = in_array($settings['title_tag'] ?? 'h3', self::TITLE_TAGS, true) ? (string) $settings['title_tag'] : 'h3'; $body .= '<' . $tag . ' class="zx-posts__title">' . esc_html($title_text) . ''; } $meta = self::meta($post, $settings); if ($meta !== '' && $skin !== 'cards') { $body .= $meta; } if ($skin === 'full_content') { $content = self::content($renderer, $post_id); if ($content !== '') { $body .= '
' . $content . '
'; } } elseif ($on('show_excerpt')) { $length = isset($settings['excerpt_length']) && is_numeric($settings['excerpt_length']) ? max(0, min(500, (int) $settings['excerpt_length'])) : 25; $excerpt = (string) (new PostExcerpt())->render(['length' => $length], Context::for_post($post_id)); if ($excerpt !== '') { $body .= '

' . esc_html($excerpt) . '

'; } } $more = trim((string) ($settings['read_more_text'] ?? __('Read more', 'zadex'))); if ($skin !== 'full_content' && $on('show_read_more') && $more !== '') { $body .= '' . esc_html($more) . ' ' . esc_html($title_text) . ''; } $html = '
'; if ($media !== '') { $html .= '
' . $media . '
'; } $html .= '
' . $body . '
'; if ($skin === 'cards') { $avatar = ''; if ($on('show_avatar') && function_exists('get_avatar')) { $avatar = (string) get_avatar((int) ($post->post_author ?? 0), 32, '', '', ['class' => 'zx-posts__avatar', 'loading' => 'lazy']); } if ($avatar !== '' || $meta !== '') { $html .= ''; } } return $html . '
'; } /** Meta line: the chosen items (author, date, comments, modified) joined by the separator. */ public static function meta(object $post, array $settings): string { $items = $settings['meta'] ?? ['author', 'date']; $items = is_array($items) ? array_values(array_intersect(array_map('strval', array_filter($items, 'is_scalar')), self::META)) : []; $post_id = (int) ($post->ID ?? 0); $parts = []; foreach ($items as $m) { if ($m === 'author' && function_exists('get_the_author_meta')) { $name = (string) get_the_author_meta('display_name', (int) ($post->post_author ?? 0)); if ($name !== '') { $parts[] = '' . esc_html($name) . ''; } } elseif ($m === 'date' && function_exists('get_the_date')) { $parts[] = ''; } elseif ($m === 'modified' && function_exists('get_the_modified_date')) { $parts[] = ''; } elseif ($m === 'comments' && function_exists('get_comments_number')) { $n = (int) get_comments_number($post_id); /* translators: %s: number of comments */ $parts[] = '' . esc_html(sprintf(_n('%s comment', '%s comments', $n, 'zadex'), function_exists('number_format_i18n') ? number_format_i18n($n) : (string) $n)) . ''; } } if (!$parts) { return ''; } $sep = array_key_exists('meta_separator', $settings) ? (string) $settings['meta_separator'] : '·'; $glue = $sep !== '' ? '' : ' '; return '
' . implode($glue, $parts) . '
'; } /** Cards badge: the first term of the taxonomy, linked to its archive. */ public static function badge(int $post_id, string $taxonomy): string { $taxonomy = Query::taxonomy($taxonomy) ?: 'category'; $terms = function_exists('get_the_terms') ? get_the_terms($post_id, $taxonomy) : []; if (!is_array($terms) || !$terms || !is_object($terms[0] ?? null)) { return ''; } $term = $terms[0]; $link = function_exists('get_term_link') ? get_term_link($term) : ''; $href = is_string($link) ? $link : ''; $name = esc_html((string) ($term->name ?? '')); return $href !== '' ? '' . $name . '' : '' . $name . ''; } /** Full content through `the_content`; never the document being rendered (a page listing itself). */ public static function content(Renderer $renderer, int $post_id): string { if ($post_id === $renderer->context()->post_id) { return ''; } return (string) (new PostContent())->render([], Context::for_post($post_id)); } }