feat(frontend): replace article system with Zibll 9.0

This commit is contained in:
kate522 committed 2026-08-18 16:06:47 +08:00
1 parent 2881c69e9c
commit 0f2ac6c34a
69 files changed
+2603 -4163

No files matched your search

+177 -286
View File
@@ -1,9 +1,6 @@
<?php
/**
* Shared post renderer helpers for archive, widget, and related lists.
*
* Centralizes card/list/mini rendering to avoid duplicating DOM contracts
* across category.php, search.php, widgets, and home modules.
* Zibll 9.0 article-list configuration and image resolution.
*
* @package AKSRT
*/
@@ -11,355 +8,249 @@
defined('ABSPATH') || exit;
if (!function_exists('aksrt_renderer_defaults')) {
/**
* Base renderer config defaults.
*
* @return array<string,mixed>
*/
/** @return array<string,mixed> */
function aksrt_renderer_defaults(): array {
return [
'context' => 'archive',
'mode' => 'list',
'columns' => 1,
'style' => 'default',
'card_style' => 'standard',
'heading_bold' => true,
'show_excerpt' => true,
'excerpt_words' => 36,
'show_meta' => true,
'show_author' => true,
'show_date' => true,
'show_views' => true,
'show_comments' => true,
'show_likes' => false,
'show_badges' => true,
'show_tags' => true,
'tag_limit' => 3,
'category_limit' => 1,
'show_thumb' => true,
'thumb_ratio' => '4:3',
'thumb_fit' => 'cover',
'thumb_if_has' => false,
'target_blank' => false,
'title_html' => '',
'excerpt_html' => '',
'badge_show' => true,
'show_resource_badge' => true,
'multi_thumb_enabled' => true,
'multi_thumb_limit' => 4,
'multi_thumb_min' => 2,
'list_show_type' => 'separate',
'list_img_position' => 'left',
'number_offset' => 0,
'show_number' => false,
'extra_class' => '',
'highlight_query' => '',
'type' => 'auto',
'no_author' => false,
'no_margin' => false,
'is_mult_thumb' => false,
'is_no_thumb' => false,
'is_card' => false,
'is_category' => is_category(),
'is_search' => is_search(),
'is_home' => is_home() || is_front_page(),
'is_author' => is_author(),
'is_tag' => is_tag(),
'highlight_query' => '',
'wrapper_class' => '',
'skip_wrapper' => false,
];
}
}
if (!function_exists('aksrt_renderer_config')) {
/**
* Merge renderer config with context-aware defaults.
* Resolve the one canonical Zibll list/card decision matrix.
*
* @param array<string,mixed> $config User config.
* @param array<string,mixed> $args Caller arguments.
* @return array<string,mixed>
*/
function aksrt_renderer_config(array $config = []): array {
$cfg = wp_parse_args($config, aksrt_renderer_defaults());
function aksrt_renderer_config(array $args = []): array {
$args = wp_parse_args($args, aksrt_renderer_defaults());
$list = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_post_list_style_settings()
: [];
$thumb = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_post_thumb_settings()
: [];
$cfg['mode'] = in_array($cfg['mode'], ['list', 'card', 'mini', 'grid-cms'], true)
? $cfg['mode']
: 'list';
$cfg['columns'] = max(1, min(6, (int) $cfg['columns']));
$cfg['excerpt_words'] = max(0, (int) $cfg['excerpt_words']);
$cfg['tag_limit'] = max(0, (int) $cfg['tag_limit']);
$cfg['category_limit'] = max(0, (int) $cfg['category_limit']);
$cfg['multi_thumb_limit'] = max(1, min(6, (int) $cfg['multi_thumb_limit']));
$cfg['multi_thumb_min'] = max(1, min(6, (int) $cfg['multi_thumb_min']));
if ($cfg['mode'] === 'mini') {
$cfg['show_excerpt'] = false;
$cfg['show_badges'] = false;
$cfg['show_tags'] = false;
$cfg['show_author'] = false;
$cfg['show_comments'] = false;
$list_type = sanitize_key((string) ($list['list_type'] ?? 'thumb'));
$forced_type = sanitize_key((string) $args['type']);
$is_forced = in_array($forced_type, ['list', 'card'], true);
$is_card = $forced_type === 'card' || (!$is_forced && !empty($args['is_card']));
if (!$is_forced && !$is_card && $list_type === 'card') {
$is_card = true;
}
if (!$is_forced && !$is_card) {
$is_card = (!empty($args['is_tag']) && !empty($list['list_card_tag']))
|| (!empty($args['is_home']) && !empty($list['list_card_home']))
|| (!empty($args['is_author']) && !empty($list['list_card_author']));
}
if (!$is_forced && !$is_card) {
$term_id = (int) get_queried_object_id();
$is_card = $term_id > 0 && in_array($term_id, (array) ($list['list_card_cat'] ?? []), true);
}
if (in_array((string) $cfg['style'], ['default', 'null'], true)) {
$cfg['style'] = '';
$list_option = (array) ($list['list_list_option'] ?? []);
$card_option = (array) ($list['list_card_option'] ?? []);
$style = sanitize_key((string) (($is_card ? $card_option : $list_option)['style'] ?? 'null'));
if (!in_array($style, ['style2', 'style3'], true)) {
$style = '';
}
$cfg['list_img_position'] = in_array(sanitize_key((string) ($cfg['list_img_position'] ?? 'left')), ['left', 'right'], true)
? sanitize_key((string) ($cfg['list_img_position'] ?? 'left'))
: 'left';
$cfg['list_show_type'] = in_array(sanitize_key((string) ($cfg['list_show_type'] ?? 'separate')), ['separate', 'no_margin'], true)
? sanitize_key((string) ($cfg['list_show_type'] ?? 'separate'))
: 'separate';
return $cfg;
}
}
$args['mode'] = $is_card ? 'card' : 'list';
$args['list_type'] = $list_type;
$args['style'] = $style;
$args['img_position'] = sanitize_key((string) ($list_option['img_position'] ?? 'left')) === 'right' ? 'right' : 'left';
$args['list_scale'] = max(50, min(200, (int) ($list_option['scale'] ?? 70)));
$args['card_scale'] = max(20, min(200, (int) ($card_option['scale'] ?? 70)));
$args['no_margin'] = !empty($args['no_margin']) || (($list['list_show_type'] ?? 'separate') === 'no_margin');
$args['no_author'] = !empty($args['no_author']) || !empty($args['is_author']);
$args['target_blank'] = !empty($list['target_blank']);
$args['heading_bold'] = !empty($list['item_heading_bold']);
$args['show_author'] = !empty($list['post_list_author']);
$args['show_time'] = !empty($list['post_list_time']);
$args['time_source'] = (string) ($list['post_time_source'] ?? 'modified');
$args['list_orderby'] = (string) ($list['list_orderby'] ?? 'modified');
$args['mobile_meta'] = (string) ($list['list_meta_show'] ?? 'like');
$args['badge_types'] = (array) ($list['list_badge_show'] ?? ['pay', 'cat', 'tag']);
$args['mult_thumb_cat'] = (array) ($list['mult_thumb_cat'] ?? []);
$args['thumb'] = $thumb;
$args['allow_conditional_layout'] = aksrt_is_show_sidebar() || wp_is_mobile();
$page = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_post_page_settings()
: [];
$comments = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_comment_display_settings()
: [];
$args['show_like'] = !array_key_exists('like_enabled', $page) || !empty($page['like_enabled']);
$args['close_comments'] = !empty($comments['close_comments']);
if (!function_exists('aksrt_renderer_sanitize_inline_html')) {
/**
* Sanitize small inline HTML snippets used by render config overrides.
*
* @param string $html Raw HTML.
* @return string
*/
function aksrt_renderer_sanitize_inline_html(string $html): string {
return wp_kses($html, [
'span' => ['class' => true, 'aria-hidden' => true],
'em' => ['class' => true],
'strong' => ['class' => true],
'i' => ['class' => true, 'aria-hidden' => true],
'small'=> ['class' => true],
]);
return $args;
}
}
if (!function_exists('aksrt_renderer_highlight_text')) {
/**
* Highlight plain text query matches using a safe span wrapper.
*
* @param string $text Plain text.
* @param string $query Highlight query.
* @return string
*/
function aksrt_renderer_highlight_text(string $text, string $query): string {
$text = wp_strip_all_tags($text);
$query = trim($query);
if ($query === '' || $text === '') {
return esc_html($text);
}
$words = preg_split('/\s+/u', $query) ?: [];
$words = array_values(array_unique(array_filter(array_map('trim', $words), static function ($word) {
return $word !== '' && mb_strlen($word) >= 2;
})));
if (empty($words)) {
$words = array_values(array_unique(array_filter(preg_split('/\s+/u', $query) ?: [])));
if (!$words) {
return esc_html($text);
}
$pattern = '/' . implode('|', array_map(static function ($word) {
return preg_quote($word, '/');
}, $words)) . '/iu';
$escaped = esc_html($text);
return (string) preg_replace($pattern, '<span class="focus-color">$0</span>', $escaped);
$pattern = '/' . implode('|', array_map(static fn(string $word): string => preg_quote($word, '/'), $words)) . '/iu';
return (string) preg_replace($pattern, '<span class="focus-color">$0</span>', esc_html($text));
}
}
if (!function_exists('aksrt_renderer_title_markup')) {
/**
* Resolve title markup, supporting highlighted search terms.
*
* @param array<string,mixed> $data Collected post data.
* @param array<string,mixed> $cfg Renderer config.
* @return string
*/
function aksrt_renderer_title_markup(array $data, array $cfg): string {
if (!empty($cfg['title_html'])) {
return aksrt_renderer_sanitize_inline_html((string) $cfg['title_html']);
$title = aksrt_renderer_highlight_text((string) $data['title'], (string) ($cfg['highlight_query'] ?? ''));
if ((string) ($data['subtitle'] ?? '') !== '') {
$title .= '<span class="focus-color"> ' . esc_html((string) $data['subtitle']) . '</span>';
}
$title = aksrt_renderer_highlight_text((string) $data['title'], (string) $cfg['highlight_query']);
$subtitle = trim((string) ($data['subtitle'] ?? ''));
if ($subtitle !== '') {
$title .= '<span class="focus-color"> ' . esc_html($subtitle) . '</span>';
}
return $title;
}
}
if (!function_exists('aksrt_renderer_excerpt_markup')) {
/**
* Resolve excerpt markup, supporting highlighted search terms.
*
* @param array<string,mixed> $data Collected post data.
* @param array<string,mixed> $cfg Renderer config.
* @return string
*/
function aksrt_renderer_excerpt_markup(array $data, array $cfg): string {
if (!empty($cfg['excerpt_html'])) {
return aksrt_renderer_sanitize_inline_html((string) $cfg['excerpt_html']);
}
return aksrt_renderer_highlight_text((string) $data['excerpt'], (string) $cfg['highlight_query']);
return aksrt_renderer_highlight_text((string) $data['excerpt'], (string) ($cfg['highlight_query'] ?? ''));
}
}
if (!function_exists('aksrt_renderer_curated_images')) {
/**
* Resolve curated cover images from post meta.
*
* @param int $post_id Post ID.
* @param int $limit Maximum items.
* @return string[]
*/
function aksrt_renderer_curated_images(int $post_id, int $limit = 6): array {
/** @return string[] */
function aksrt_renderer_curated_images(int $post_id, string $size, int $limit = 8): array {
$raw = class_exists('\AKSRT\Core\PostMetaBox')
? (string) \AKSRT\Core\PostMetaBox::get_meta_value($post_id, 'featured_slide', '')
: '';
$images = [];
$limit = max(1, min(8, $limit));
if (!class_exists('\AKSRT\Core\PostMetaBox')) {
return [];
}
$cover_images = (string) \AKSRT\Core\PostMetaBox::get_meta_value($post_id, 'cover_images', '');
if ($cover_images === '') {
return [];
}
$cover_ids = array_filter(array_map('absint', preg_split('/[\s,]+/', $cover_images) ?: []));
foreach ($cover_ids as $attachment_id) {
$url = (string) wp_get_attachment_image_url($attachment_id, 'medium_large');
if ($url !== '') {
$images[] = esc_url_raw($url);
foreach (array_filter(preg_split('/[\s,]+/', $raw) ?: []) as $token) {
$image = aksrt_renderer_sized_image((string) $token, $size);
if ($image !== '') {
$images[] = $image;
}
}
return array_slice(array_values(array_unique($images)), 0, $limit);
return array_slice($images, 0, $limit);
}
}
if (!function_exists('aksrt_renderer_attachment_images')) {
/**
* Resolve gallery/content images for list cards.
*
* @param int $post_id Post ID.
* @param int $limit Maximum items.
* @return string[]
*/
function aksrt_renderer_attachment_images(int $post_id, int $limit = 6): array {
$images = [];
$limit = max(1, min(8, $limit));
if (function_exists('aksrt_get_post_gallery_images')) {
$images = aksrt_get_post_gallery_images($post_id, $limit);
}
if (function_exists('aksrt_get_first_content_image_url')) {
$first_content_image = (string) aksrt_get_first_content_image_url($post_id);
if ($first_content_image !== '') {
$images[] = esc_url_raw($first_content_image);
}
}
return array_slice(array_values(array_unique(array_filter($images))), 0, $limit);
}
}
if (!function_exists('aksrt_renderer_random_fallback_image')) {
/**
* Pick a deterministic fallback image when "random cover" is enabled.
*
* @param int $post_id Post ID.
* @return string
*/
function aksrt_renderer_random_fallback_image(int $post_id): string {
$candidates = [
get_template_directory_uri() . '/assets/img/person_background.png',
get_template_directory_uri() . '/assets/img/placeholder.svg',
];
if (class_exists('\AKSRT\Settings\Settings')) {
$thumb = \AKSRT\Settings\Settings::get_post_thumb_settings();
$spare = $thumb['spare_images'] ?? [];
if (is_array($spare) && !empty($spare)) {
foreach ($spare as $url) {
if (is_string($url) && $url !== '') {
array_unshift($candidates, $url);
}
}
}
}
$candidates = array_values(array_unique(array_filter(array_map('esc_url_raw', $candidates))));
if (empty($candidates)) {
if (!function_exists('aksrt_renderer_sized_image')) {
function aksrt_renderer_sized_image(string $image, string $size): string {
$image = trim($image);
if ($image === '') {
return '';
}
$attachment_id = ctype_digit($image) ? (int) $image : 0;
if ($attachment_id === 0) {
$lookup_url = (string) preg_replace('/\?.*$/', '', $image);
$attachment_id = attachment_url_to_postid($lookup_url);
if ($attachment_id === 0) {
$full_url = (string) preg_replace('/-\d+x\d+(?=\.[^.]+$)/', '', $lookup_url);
$attachment_id = attachment_url_to_postid($full_url);
}
}
if ($attachment_id > 0) {
$sized = wp_get_attachment_image_url($attachment_id, $size);
if (is_string($sized) && $sized !== '') {
return esc_url_raw($sized);
}
}
return esc_url_raw($image);
}
}
$index = $post_id % count($candidates);
return $candidates[$index] ?? $candidates[0];
if (!function_exists('aksrt_renderer_content_images')) {
/** @return string[] */
function aksrt_renderer_content_images(int $post_id, string $size): array {
$content = strip_shortcodes((string) get_post_field('post_content', $post_id, 'raw'));
if ($content === '' || !preg_match_all('/<img.*?(?: |\t|\r|\n)?src=[\'\"]?(.+?)[\'\"]?(?:(?: |\t|\r|\n)+.*?)?>/sim', $content, $matches)) {
return [];
}
$images = [];
foreach ((array) ($matches[1] ?? []) as $image) {
$sized = aksrt_renderer_sized_image((string) $image, $size);
if ($sized !== '') {
$images[] = $sized;
}
}
return $images;
}
}
if (!function_exists('aksrt_renderer_category_image')) {
function aksrt_renderer_category_image(int $post_id, string $size): string {
foreach ((array) get_the_category($post_id) as $category) {
$attachment_id = (int) get_term_meta((int) $category->term_id, 'aksrt_category_cover_id', true);
if ($attachment_id > 0) {
return aksrt_renderer_sized_image((string) $attachment_id, $size);
}
}
return '';
}
}
if (!function_exists('aksrt_renderer_spare_image')) {
function aksrt_renderer_spare_image(array $thumb): string {
$images = array_values(array_filter((array) ($thumb['spare_thumbnail'] ?? [])));
return $images ? (string) $images[array_rand($images)] : '';
}
}
if (!function_exists('aksrt_renderer_resolve_images')) {
/**
* Resolve primary + gallery images for a post based on card settings.
*
* @param int $post_id Post ID.
* @param array<string,mixed> $cfg Renderer config.
* @return array{primary:string,gallery:string[],has_thumb:bool,has_real:bool}
*/
/** @return array{primary:string,gallery:string[],slides:string[],has_real:bool,is_slide:bool,video_url:string} */
function aksrt_renderer_resolve_images(int $post_id, array $cfg): array {
$thumb_url = get_the_post_thumbnail_url($post_id, 'medium_large');
$curated_images = !empty($cfg['thumb_if_has']) || !empty($cfg['multi_thumb_enabled'])
? aksrt_renderer_curated_images($post_id, max(4, (int) $cfg['multi_thumb_limit']))
: [];
$content_images = !empty($cfg['multi_thumb_enabled'])
? aksrt_renderer_attachment_images($post_id, max(4, (int) $cfg['multi_thumb_limit']))
: [];
$thumb = (array) ($cfg['thumb'] ?? []);
$size = (string) ($thumb['thumb_postfirstimg_size'] ?? 'medium');
$slides = aksrt_renderer_curated_images($post_id, $size);
$gallery = aksrt_renderer_content_images($post_id, $size);
$video_url = class_exists('\AKSRT\Core\PostMetaBox')
? esc_url_raw((string) \AKSRT\Core\PostMetaBox::get_meta_value($post_id, 'featured_video', ''))
: '';
$gallery = [];
if (!empty($curated_images)) {
$gallery = array_merge($gallery, $curated_images);
$thumbnail_id = (int) get_post_thumbnail_id($post_id);
$primary = $thumbnail_id > 0 ? aksrt_renderer_sized_image((string) $thumbnail_id, $size) : '';
if ($primary === '') {
$primary = class_exists('\AKSRT\Core\PostMetaBox')
? esc_url_raw((string) \AKSRT\Core\PostMetaBox::get_meta_value($post_id, 'thumbnail_url', ''))
: '';
}
if (!empty($content_images)) {
$gallery = array_merge($gallery, $content_images);
if ($primary === '' && !empty($thumb['thumb_postfirstimg_s']) && !empty($gallery[0])) {
$primary = (string) $gallery[0];
}
if ($primary === '' && !empty($thumb['thumb_catimg_s'])) {
$primary = aksrt_renderer_category_image($post_id, $size);
}
$gallery = array_values(array_unique(array_filter($gallery)));
$primary = is_string($thumb_url) && $thumb_url !== '' ? esc_url_raw($thumb_url) : '';
$has_real = $primary !== '';
if ($primary === '' && !empty($curated_images[0])) {
$primary = $curated_images[0];
$has_real = true;
if ($primary === '') {
$primary = aksrt_renderer_spare_image($thumb);
}
if ($primary === '' && !empty($content_images[0])) {
$primary = $content_images[0];
$has_real = true;
}
$thumb_settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_post_thumb_settings() : [];
$fallback = sanitize_key((string) ($thumb_settings['fallback'] ?? 'placeholder'));
if ($primary === '' && empty($cfg['thumb_if_has'])) {
if ($fallback === 'random') {
$primary = aksrt_renderer_random_fallback_image($post_id);
} elseif ($fallback === 'placeholder') {
$primary = aksrt_get_lazy_thumb();
}
if ($primary === '') {
$primary = (string) aksrt_get_lazy_thumb();
}
return [
'primary' => $primary,
'gallery' => $gallery,
'has_thumb' => $primary !== '',
'has_real' => $has_real,
'primary' => esc_url_raw($primary),
'gallery' => $gallery,
'slides' => $slides,
'has_real' => $has_real,
'is_slide' => !empty($thumb['list_thumb_slides_s']) && count($slides) > 0,
'video_url' => $video_url,
];
}
}
if (!function_exists('aksrt_renderer_thumb_ratio_value')) {
/**
* Convert ratio tokens like 4:3 to CSS aspect ratio values.
*
* @param string $ratio Ratio token.
* @return string
*/
function aksrt_renderer_thumb_ratio_value(string $ratio): string {
$ratio = trim($ratio);
if (preg_match('/^(\d+)\s*:\s*(\d+)$/', $ratio, $matches)) {
$w = max(1, (int) $matches[1]);
$h = max(1, (int) $matches[2]);
return $w . ' / ' . $h;
}
return '4 / 3';
}
}