$length ? mb_substr($text, 0, $length) . '...' : $text; } endif; if (!function_exists('aksrt_widget_ajax_null')) : /** * Empty state for ajax-paged lists (hidden pager stub included). */ function aksrt_widget_ajax_null(string $text = ''): string { if ($text === '') { $text = __('暂无内容', 'aksrt'); } $html = '
' . aksrt_get_null($text, 60, 'null.svg', '', 280) . '
'; $html .= '
'; return $html; } endif; if (!function_exists('aksrt_widget_term_cover_url')) : /** * Category cover image URL, resolved through the theme's canonical chain: * explicit term cover → latest thumbnailed post inside the term → optional * default placeholder. The reference hides term cards without a real cover * while the aggregation card always falls back to a default image, so the * last step is opt-in via $default_fallback. */ function aksrt_widget_term_cover_url(int $term_id, bool $default_fallback = false): string { $cover_id = (int) get_term_meta($term_id, 'aksrt_category_cover_id', true); if ($cover_id > 0) { $url = wp_get_attachment_url($cover_id); if ($url !== '') { return (string) $url; } } $posts = get_posts([ 'category__in' => [$term_id], 'posts_per_page' => 1, 'post_status' => 'publish', 'orderby' => 'date', 'order' => 'DESC', 'meta_query' => [['key' => '_thumbnail_id', 'compare' => 'EXISTS']], 'fields' => 'ids', ]); if (!empty($posts)) { $url = get_the_post_thumbnail_url((int) $posts[0], 'medium_large'); if ($url) { return (string) $url; } } return $default_fallback ? get_template_directory_uri() . '/assets/img/placeholder.svg' : ''; } endif; if (!function_exists('aksrt_widget_term_views_sum')) : /** * Sum of published post views inside a term (badge on the aggregation card). */ function aksrt_widget_term_views_sum(int $term_id): int { static $cache = []; if (isset($cache[$term_id])) { return $cache[$term_id]; } $sum = 0; $term = get_term($term_id); if ($term instanceof WP_Term && class_exists('wpdb')) { global $wpdb; $view_key = class_exists('AKSRT\\Core\\DomainModel') ? \AKSRT\Core\DomainModel::POST_VIEW_COUNT_KEY : 'aksrt_post_views'; $tt_id = (int) $term->term_taxonomy_id; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- prepare() below. $sum = (int) $wpdb->get_var($wpdb->prepare( "SELECT SUM(CAST(pm.meta_value AS UNSIGNED)) FROM {$wpdb->postmeta} AS pm INNER JOIN {$wpdb->posts} AS p ON p.ID = pm.post_id INNER JOIN {$wpdb->term_relationships} AS tr ON tr.object_id = p.ID WHERE pm.meta_key = %s AND tr.term_taxonomy_id = %d AND p.post_status = 'publish' AND p.post_type = 'post'", $view_key, $tt_id )); } $cache[$term_id] = max(0, $sum); return $cache[$term_id]; } endif; if (!function_exists('aksrt_widget_hot_posts')) : /** * Hot posts ranking box (reference parity, sidebar sized). * * @param array $args {orderby, limit_day, count, target_blank}. */ function aksrt_widget_hot_posts(array $args = []): string { $instance = wp_parse_args($args, [ 'orderby' => 'views', 'limit_day' => 0, 'count' => 6, 'target_blank' => false, ]); $orderby = (string) $instance['orderby']; $target = !empty($instance['target_blank']) ? ' target="_blank" rel="noopener noreferrer"' : ''; $instance['paginate'] = ''; $query = \AKSRT\WidgetSystem\PostQuery::build($instance, 1, []); if (!$query->have_posts()) { return ''; } $posts_html = ''; $i = 0; while ($query->have_posts()) { $query->the_post(); $i++; $data = aksrt_renderer_collect_post_data(['time_source' => 'date']); if (!$data) { continue; } $title = esc_html((string) $data['title']) . ($data['subtitle'] !== '' ? '' . esc_html((string) $data['subtitle']) . '' : ''); $badge_classes = ['', 'jb-red', 'jb-yellow']; $badge_class = ($i === 1 ? 'em12 ' : '') . ($badge_classes[$i - 1] ?? 'b-gray'); $top_badge = 'TOP' . $i . ''; $meta_value = ''; if ($orderby === 'favorite') { $meta_value = aksrt_format_number((int) aksrt_get_post_favorites((int) $data['post_id'])) . __('人收藏', 'aksrt'); } elseif ($orderby === 'like') { $meta_value = aksrt_format_number((int) aksrt_get_post_likes((int) $data['post_id'])) . __('人点赞', 'aksrt'); } elseif ($orderby === 'comment_count') { $meta_value = aksrt_format_number((int) $data['comments']) . __('条讨论', 'aksrt'); } if ($meta_value === '') { $meta_value = aksrt_format_number((int) $data['views']) . __('人已阅读', 'aksrt'); } $thumb = ''; if ((string) $data['primary'] !== '') { $thumb = ''; } $time_ago = '' . esc_html((string) $data['time_ago']); $permalink = esc_url((string) $data['permalink']); if ($i === 1) { $posts_html .= '
'; $posts_html .= ''; $posts_html .= '
' . $thumb; $posts_html .= '
'; $posts_html .= '
'; $posts_html .= '
' . esc_html($meta_value) . '
'; $posts_html .= $title; $posts_html .= '
'; $posts_html .= $top_badge; $posts_html .= '
'; } else { $posts_html .= '
'; $posts_html .= '
' . $thumb . '
'; $posts_html .= '
'; $posts_html .= '
' . $title . '
'; $posts_html .= '
' . $time_ago . '' . esc_html($meta_value) . '
'; $posts_html .= '
'; $posts_html .= $top_badge; $posts_html .= '
'; } } wp_reset_postdata(); return '
' . $posts_html . '
'; } endif; if (!function_exists('aksrt_widget_term_aggregation')) : /** * Category aggregation card: cover + description + latest posts list. * * @param array $args {term_id, target_blank, orderby, order, count}. */ function aksrt_widget_term_aggregation(array $args = []): string { $args = wp_parse_args($args, [ 'term_id' => 0, 'target_blank' => false, 'orderby' => 'modified', 'order' => 'DESC', 'count' => 4, ]); $term_id = (int) $args['term_id']; $term = $term_id > 0 ? get_term($term_id, 'category') : null; if (!$term instanceof WP_Term || is_wp_error($term)) { return ''; } $target = !empty($args['target_blank']) ? ' target="_blank" rel="noopener noreferrer"' : ''; $href = get_term_link($term); if (is_wp_error($href)) { return ''; } $href = esc_url((string) $href); $icon = ''; $img = aksrt_widget_term_cover_url((int) $term->term_id, true); $img_html = aksrt_graphic_card([ 'class' => '', 'img' => $img, 'alt' => __('分类', 'aksrt') . '-' . $term->name, 'link' => ['url' => $href, 'target' => !empty($args['target_blank']) ? '_blank' : ''], 'height_scale' => 0, 'mask_opacity' => 0, 'more' => '' . aksrt_renderer_svg_icon('hot') . ' ' . esc_html(aksrt_format_number(aksrt_widget_term_views_sum((int) $term->term_id))) . '', ]); $img_html = '
' . $img_html . '
'; $name = '' . esc_html($term->name) . ''; $description = '
' . esc_html($term->description) . '
'; $query_args = [ 'cat' => (string) $term->term_id, 'orderby' => (string) $args['orderby'], 'order' => strtoupper((string) $args['order']) === 'ASC' ? 'ASC' : 'DESC', 'count' => max(1, (int) $args['count']), 'paginate' => '', ]; $query = \AKSRT\WidgetSystem\PostQuery::build($query_args, 1, []); $meta = '' . sprintf(esc_html__('%s篇文章', 'aksrt'), esc_html(aksrt_format_number(max(0, (int) $query->found_posts)))) . ''; $meta .= '' . esc_html__('更多文章', 'aksrt') . ''; $meta = '
' . $meta . '
'; $posts_html = ''; while ($query->have_posts()) { $query->the_post(); $data = aksrt_renderer_collect_post_data(['time_source' => 'date']); if (!$data) { continue; } $title = esc_html((string) $data['title']) . ($data['subtitle'] !== '' ? '' . esc_html((string) $data['subtitle']) . '' : ''); $title = ''; $post_meta = ''; switch ((string) $args['orderby']) { case 'views': $post_meta = aksrt_format_number((int) $data['views']); break; case 'favorite': $post_meta = aksrt_format_number((int) aksrt_get_post_favorites((int) $data['post_id'])); break; case 'like': $post_meta = aksrt_format_number((int) aksrt_get_post_likes((int) $data['post_id'])); break; case 'comment_count': $post_meta = aksrt_format_number((int) $data['comments']); break; } if ($post_meta === '') { $post_meta = '' . esc_html((string) $data['time_ago']); } $posts_html .= '
' . $title . '
' . $post_meta . '
'; } wp_reset_postdata(); $html = '
'; $html .= '
'; $html .= $img_html; $html .= '
' . $name . $description . $meta . '
'; $html .= '
'; $html .= $posts_html; $html .= '
'; return $html; } endif; if (!function_exists('aksrt_widget_comments_list')) : /** * Recent comments widget list (reference parity). * * @param array $args {limit, outer, outpost, style, show_source}. */ function aksrt_widget_comments_list(array $args = []): string { $args = wp_parse_args($args, [ 'limit' => 8, 'outer' => '', 'outpost' => '', 'style' => '', 'show_source' => false, ]); $comments = get_comments([ 'orderby' => 'comment_date', 'number' => max(1, (int) $args['limit']), 'status' => 'approve', 'post_type' => ['post'], 'author__not_in' => \AKSRT\WidgetSystem\RendererHelpers::ids((string) $args['outer']), 'post__not_in' => \AKSRT\WidgetSystem\RendererHelpers::ids((string) $args['outpost']), ]); if (!$comments) { return ''; } $output = ''; foreach ($comments as $comment) { $content = aksrt_widget_comment_excerpt((string) $comment->comment_content); $link = esc_url((string) get_comment_link($comment)); $time = aksrt_time_ago((string) $comment->comment_date); $user_name = function_exists('aksrt_get_comments_user_name_html') ? aksrt_get_comments_user_name_html($comment) : esc_html(get_comment_author($comment)); $like = aksrt_get_comment_like_html($comment); $avatar = aksrt_get_comment_avatar_box($comment, 'avatar-img'); $class = 'comment-widget-item' . ((string) $args['style'] === 'style_2' ? ' style-2' : ''); $source = ''; if (!empty($args['show_source'])) { $post_title = esc_html(get_the_title((int) $comment->comment_post_ID)); $source = ''; } $output .= '
'; $output .= '
' . $avatar; $output .= '
'; $output .= '

' . $user_name . '' . esc_html($time) . '' . $like . '

'; $output .= (string) $args['style'] === 'style_2' ? '' : '' . $content . ''; $output .= '
'; $output .= (string) $args['style'] === 'style_2' ? '
' . $content . '
' : ''; $output .= $source; $output .= '
'; } return $output; } endif; if (!function_exists('aksrt_widget_comment_excerpt')) : /** * Comment text reduced to a widget-safe plain snippet (images/code dropped). */ function aksrt_widget_comment_excerpt(string $content): string { $content = convert_smilies($content); $content = preg_replace('/\[img=(.*?)\]/', '[图片]', $content) ?? $content; $content = preg_replace('/\[link=(.*?) target=_blank\](.*?)\[\/link\]/', '$2', $content) ?? $content; $content = preg_replace('/\[link=(.*?)\](.*?)\[\/link\]/', '$2', $content) ?? $content; $content = preg_replace('/\[code]([\s\S]*)\[\/code]/', '[代码]', $content) ?? $content; $content = preg_replace('/\[g=(.*?)\]/', '[$1]', $content) ?? $content; // Strip any remaining anchors but keep their text. $content = preg_replace('/]*>(.*?)<\/a>/', '$1', $content) ?? $content; return wp_kses_post($content); } endif;