fix(theme): align reference layouts and restore passing contracts

Align homepage and category discovery, modern widgets, admin controls, quotes, and comments with the reference theme. Remove superseded paths and add rendering and behavior coverage. Update stale asset and finance contracts; all 760 Node tests and 139 widget assertions pass.
This commit is contained in:
kate522 committed 2026-10-29 19:16:03 +08:00
1 parent b520d29368
commit 8aa116ec43
63 files changed
+1985 -2304

No files matched your search

+122 -176
View File
@@ -10,58 +10,43 @@
defined('ABSPATH') || exit;
if (!function_exists('aksrt_archive_render_term_header')) :
/**
* Shared term archive header for category/tag pages.
*
* @param array<string,mixed> $args Header args.
*/
/** Reference term cover, including first-page count and term search. */
function aksrt_archive_render_term_header(array $args = []): void {
$args = wp_parse_args($args, [
'header_style' => 'simple',
'show_cover' => false,
'cover_url' => '',
'eyebrow' => '',
'title' => '',
'description' => '',
'admin_path' => '',
'admin_label' => '',
'header_style' => 'simple', 'show_cover' => false, 'cover_url' => '',
'title' => '', 'description' => '', 'post_count' => false,
]);
$header_style = (string) $args['header_style'];
if ($header_style === 'none') {
if ($args['header_style'] === 'none') {
return;
}
$show_cover = !empty($args['show_cover']) && (string) $args['cover_url'] !== '';
$cover_url = (string) $args['cover_url'];
?>
<header class="page-header term-page-header term-page-header--<?php echo esc_attr($header_style); ?><?php echo ($show_cover && $header_style === 'cover') ? ' has-cover-bg' : ''; ?>">
<?php if ($show_cover && $header_style === 'cover') : ?>
<div class="term-page-header__cover-bg" style="background-image:url('<?php echo esc_url($cover_url); ?>')">
<div class="term-page-header__cover-overlay"></div>
</div>
<?php endif; ?>
<div class="term-page-header__body<?php echo $header_style === 'cover' ? ' term-page-header__body--overlay' : ''; ?>">
<div>
<?php if ((string) $args['eyebrow'] !== '') : ?>
<p class="term-page-header__eyebrow"><?php echo esc_html((string) $args['eyebrow']); ?></p>
<?php endif; ?>
<h1 class="entry-title"><?php echo esc_html((string) $args['title']); ?></h1>
<?php if ((string) $args['description'] !== '') : ?>
<p class="term-page-header__desc"><?php echo $args['description']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></p>
<?php endif; ?>
</div>
<?php
if ((string) $args['admin_path'] !== '' && function_exists('aksrt_render_admin_quick_link')) {
aksrt_render_admin_quick_link((string) $args['admin_path'], (string) $args['admin_label']);
}
?>
</div>
<?php if ($show_cover && $header_style !== 'cover') : ?>
<div class="term-page-header__cover" style="background-image:url('<?php echo esc_url($cover_url); ?>')"></div>
<?php endif; ?>
</header>
<?php
$term = get_queried_object();
$is_tag = $term instanceof WP_Term && $term->taxonomy === 'post_tag';
$paged = max(1, (int) get_query_var('paged', 1));
$title = '<i class="fa fas ' . ($is_tag ? 'fa-tags' : 'fa-folder-open') . ' em12 mr10 ml6" aria-hidden="true"></i>' . esc_html((string) $args['title']);
global $wp_query;
if (!empty($args['post_count']) && $paged === 1) {
$count = $wp_query instanceof WP_Query ? (int) $wp_query->found_posts : 0;
$title .= '<span class="icon-spot">' . sprintf(esc_html__('共%s篇', 'aksrt'), (string) $count) . '</span>';
}
$description = esc_html(trim(wp_strip_all_tags((string) $args['description'])));
$more = '';
if ($term instanceof WP_Term) {
$more = '<div class="page-cover-more-btns flex ac"><a href="javascript:;" class="main-search-btn" search-type="post" search-trem="' . (int) $term->term_id . '" trem-name="' . esc_attr($term->name) . '" search-placeholder="' . esc_attr(sprintf(__('在%s中搜索', 'aksrt'), $term->name)) . '" title="' . esc_attr(sprintf(__('在%s中搜索', 'aksrt'), $term->name)) . '"><i class="fa fas fa-search" aria-hidden="true"></i></a></div>';
}
if (empty($args['show_cover'])) {
echo '<div class="aksrt-panel relative"><h4 class="title-h-left">' . $title . '</h4><div class="muted-2-color">' . $description . '</div>' . $more . '</div>';
return;
}
if ($paged > 1) {
$title .= ' <small class="icon-spot">' . sprintf(esc_html__('第%s页', 'aksrt'), $paged) . '</small>';
}
$cover = (string) $args['cover_url'];
if ($cover === '') {
$cover = get_template_directory_uri() . '/assets/img/user_t.jpg';
}
echo '<div' . ($paged === 1 ? ' win-ajax-replace="page-cover"' : '') . ' class="page-cover aksrt-panel">';
echo '<img' . aksrt_get_lazy_attr('lazy_cover', $cover, 'fit-cover') . ' alt="' . esc_attr((string) $args['title']) . '">';
echo '<div class="absolute linear-mask"></div><div class="list-inline box-body page-cover-con"><div class="title-h-left"><b>' . $title . '</b></div><div class="em09 page-desc">' . $description . '</div></div>' . $more . '</div>';
}
endif;
@@ -76,46 +61,43 @@ if (!function_exists('aksrt_archive_render_filter_chip')) :
'label' => '',
'url' => '',
'active' => false,
'attrs' => '',
]);
if ((string) $chip['label'] === '' || (string) $chip['url'] === '') {
return;
}
?>
<a class="archive-filter-bar__chip<?php echo !empty($chip['active']) ? ' is-active' : ''; ?>" href="<?php echo esc_url((string) $chip['url']); ?>"<?php echo $chip['attrs']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>><?php echo esc_html((string) $chip['label']); ?></a>
<a rel="nofollow" ajax-replace="true" class="ajax-next<?php echo !empty($chip['active']) ? ' focus-color' : ''; ?>" href="<?php echo esc_url((string) $chip['url']); ?>"><?php echo esc_html((string) $chip['label']); ?></a>
<?php
}
endif;
if (!function_exists('aksrt_archive_render_filter_group')) :
/**
* One labeled chip group.
*
* @param array<string,mixed> $group Group args.
*/
/** One reference option row; dropdown entries stay separate from inline entries. */
function aksrt_archive_render_filter_group(array $group): void {
$group = wp_parse_args($group, [
'label' => '',
'chips' => [],
'class' => '',
]);
$chips = is_array($group['chips'] ?? null) ? $group['chips'] : [];
if (empty($chips)) {
$group = wp_parse_args($group, ['label' => '', 'chips' => [], 'dropdown' => []]);
if (empty($group['chips']) && empty($group['dropdown'])) {
return;
}
$class = trim('archive-filter-bar__main ' . (string) $group['class']);
?>
<div class="<?php echo esc_attr($class); ?>">
<?php if ((string) $group['label'] !== '') : ?>
<span class="archive-filter-bar__label"><?php echo esc_html((string) $group['label']); ?></span>
<?php endif; ?>
<div class="archive-filter-bar__chips">
<?php foreach ($chips as $chip) : ?>
<?php if (is_array($chip)) { aksrt_archive_render_filter_chip($chip); } ?>
<?php endforeach; ?>
</div>
</div>
<?php
echo '<div class="flex ac"><div class="option-dropdown splitters-this-r dropdown flex0">';
if (!empty($group['dropdown'])) {
echo '<a href="#" data-toggle="dropdown" aria-haspopup="true"><span>' . esc_html((string) $group['label']) . '</span><i class="fa fas fa-fw fa-sort opacity5" aria-hidden="true"></i></a><ul class="dropdown-menu">';
foreach ($group['dropdown'] as $chip) {
echo '<li>';
aksrt_archive_render_filter_chip($chip);
echo '</li>';
}
echo '</ul>';
} else {
echo esc_html((string) $group['label']);
}
echo '</div><ul class="list-inline scroll-x mini-scrollbar option-items">';
foreach ((array) $group['chips'] as $chip) {
aksrt_archive_render_filter_chip($chip);
}
echo '</ul></div>';
foreach ((array) ($group['children'] ?? []) as $child_group) {
aksrt_archive_render_filter_group($child_group);
}
}
endif;
@@ -126,10 +108,9 @@ if (!function_exists('aksrt_archive_build_term_filter_group')) :
* @param string $taxonomy category|post_tag.
* @param array<string,mixed> $option lists/dropdown/dropdown_lists fieldset.
* @param string $label Visible group label.
* @param bool $ajax Whether to attach archive AJAX attributes.
* @return array<string,mixed>
*/
function aksrt_archive_build_term_filter_group(string $taxonomy, array $option, string $label, bool $ajax): array {
function aksrt_archive_build_term_filter_group(string $taxonomy, array $option, string $label): array {
if (!in_array($taxonomy, ['category', 'post_tag'], true)) {
return [];
}
@@ -153,113 +134,81 @@ if (!function_exists('aksrt_archive_build_term_filter_group')) :
return [];
}
$query_key = $taxonomy === 'category' ? 'aksrt_filter_cat' : 'aksrt_filter_tag';
$data_key = $taxonomy === 'category' ? 'data-filter-cat' : 'data-filter-tag';
$current = isset($_GET[$query_key]) ? absint(wp_unslash($_GET[$query_key])) : 0;
$chips = [[
'label' => __('全部', 'aksrt'),
'url' => remove_query_arg([$query_key, 'paged']),
'active' => $current === 0,
'attrs' => $ajax ? ' ' . $data_key . '=""' : '',
]];
$inline_ids = array_map('absint', (array) ($option['lists'] ?? []));
$dropdown_ids = !empty($option['dropdown']) ? array_map('absint', (array) ($option['dropdown_lists'] ?? [])) : [];
$current = get_queried_object();
$active_ids = $current instanceof WP_Term
? array_merge([(int) $current->term_id], get_ancestors($current->term_id, $current->taxonomy, 'taxonomy')) : [];
$group = ['label' => $label, 'chips' => [], 'dropdown' => [], 'children' => []];
$child_ids = [];
foreach ($terms as $term) {
$term_id = (int) $term->term_id;
$chips[] = [
'label' => $term->name,
'url' => add_query_arg($query_key, $term_id, remove_query_arg('paged')),
'active' => $current === $term_id,
'attrs' => $ajax ? ' ' . $data_key . '="' . $term_id . '"' : '',
];
$link = get_term_link($term);
if (is_wp_error($link)) {
continue;
}
$chip = ['label' => aksrt_widget_str_cut((string) $term->name, 8), 'url' => $link, 'active' => in_array((int) $term->term_id, $active_ids, true)];
if (in_array((int) $term->term_id, $inline_ids, true)) {
$group['chips'][] = $chip;
if ($chip['active']) {
$children = get_terms(['taxonomy' => $taxonomy, 'parent' => (int) $term->term_id, 'fields' => 'ids']);
if (!is_wp_error($children)) {
$child_ids = array_merge($child_ids, $children);
}
}
}
if (in_array((int) $term->term_id, $dropdown_ids, true)) {
$group['dropdown'][] = $chip;
}
}
return [
'label' => $label,
'chips' => $chips,
];
if ($child_ids) {
$child_group = aksrt_archive_build_term_filter_group($taxonomy, ['lists' => array_unique($child_ids)], $taxonomy === 'category' ? __('子分类', 'aksrt') : __('子标签', 'aksrt'));
if ($child_group) {
$group['children'][] = $child_group;
}
}
return $group;
}
endif;
if (!function_exists('aksrt_archive_render_filter_bar')) :
/**
* Shared filter bar shell for category/tag archives.
*
* @param array<string,mixed> $args Filter bar args.
*/
/** Category/tag filter rows share the homepage option menu presentation. */
function aksrt_archive_render_filter_bar(array $args = []): void {
// legacy zib_ajax_option_menu:nofilter 请求(首页更多栏目 tab 加载)不输出筛选菜单。
if (!empty($_GET['nofilter'])) {
return;
}
$args = wp_parse_args($args, [
'ajax' => false,
'ajax_archive' => '',
'term_id' => 0,
'aria_label' => '',
'meta_text' => '',
'groups' => [],
'sort_enabled' => false,
'sort_current' => 'date',
'sort_links' => [],
'sort_labels' => [],
'sort_as_main' => false,
'aria_label' => '', 'groups' => [], 'sort_enabled' => false,
'sort_current' => '', 'sort_links' => [], 'sort_labels' => [], 'sort_option' => [],
]);
$groups = is_array($args['groups'] ?? null) ? $args['groups'] : [];
$sort_links = is_array($args['sort_links'] ?? null) ? $args['sort_links'] : [];
$sort_labels = is_array($args['sort_labels'] ?? null) ? $args['sort_labels'] : [];
$has_sort = !empty($args['sort_enabled']) && !empty($sort_links);
if (empty($groups) && !$has_sort && (string) $args['meta_text'] === '') {
$groups = (array) $args['groups'];
if (!empty($args['sort_enabled'])) {
$sort = ['label' => __('排序', 'aksrt'), 'chips' => [], 'dropdown' => []];
$option = (array) $args['sort_option'];
foreach ((array) $args['sort_links'] as $key => $url) {
$chip = [
'label' => (string) ($args['sort_labels'][$key] ?? $key), 'url' => $url,
'active' => (string) $args['sort_current'] === (string) $key,
];
if (in_array($key, (array) ($option['lists'] ?? []), true)) {
$sort['chips'][] = $chip;
}
if (!empty($option['dropdown'])) {
$sort['dropdown'][] = $chip;
}
}
if ($sort['chips'] || $sort['dropdown']) {
$groups[] = $sort;
}
}
if (!$groups) {
return;
}
$ajax = !empty($args['ajax']);
?>
<div
class="archive-filter-bar<?php echo $ajax ? ' archive-filter-bar--ajax' : ''; ?>"
aria-label="<?php echo esc_attr((string) ($args['aria_label'] !== '' ? $args['aria_label'] : __('筛选', 'aksrt'))); ?>"
<?php if ($ajax) : ?>
data-ajax-archive="<?php echo esc_attr((string) $args['ajax_archive']); ?>"
data-term-id="<?php echo (int) $args['term_id']; ?>"
data-query-context="archive"
<?php endif; ?>
>
<?php foreach ($groups as $group) : ?>
<?php if (is_array($group)) { aksrt_archive_render_filter_group($group); } ?>
<?php endforeach; ?>
<?php if ((string) $args['meta_text'] !== '') : ?>
<div class="archive-filter-bar__meta"><?php echo esc_html((string) $args['meta_text']); ?></div>
<?php endif; ?>
<?php if ($has_sort) : ?>
<?php
$sort_chips = [];
foreach ($sort_links as $sort_key => $sort_url) {
$sort_chips[] = [
'label' => (string) ($sort_labels[$sort_key] ?? $sort_key),
'url' => (string) $sort_url,
'active' => (string) $args['sort_current'] === (string) $sort_key,
'attrs' => $ajax ? ' data-filter-sort="' . esc_attr((string) $sort_key) . '"' : '',
];
}
if (!empty($args['sort_as_main'])) {
aksrt_archive_render_filter_group([
'label' => __('排序', 'aksrt'),
'chips' => $sort_chips,
]);
} else {
?>
<div class="archive-filter-bar__sort" aria-label="<?php esc_attr_e('排序方式', 'aksrt'); ?>">
<?php foreach ($sort_chips as $chip) { aksrt_archive_render_filter_chip($chip); } ?>
</div>
<?php
}
?>
<?php endif; ?>
</div>
<?php
echo '<div class="ajax-option ajax-replace" win-ajax-replace="filter" aria-label="' . esc_attr((string) $args['aria_label']) . '"';
echo '>';
foreach ($groups as $group) {
aksrt_archive_render_filter_group($group);
}
echo '</div><div></div>';
}
endif;
@@ -272,10 +221,9 @@ if (!function_exists('aksrt_archive_build_custom_filter_groups')) :
*
* @param array<int,array<string,mixed>> $dimensions Dimension definitions (custom_filter settings).
* @param mixed $keys 'all' or list of enabled dimension keys.
* @param bool $ajax Attach AJAX attributes.
* @return array<int,array<string,mixed>>
*/
function aksrt_archive_build_custom_filter_groups(array $dimensions, $keys, bool $ajax): array {
function aksrt_archive_build_custom_filter_groups(array $dimensions, $keys): array {
$groups = [];
foreach ($dimensions as $dimension) {
$dim_key = sanitize_key((string) ($dimension['key'] ?? ''));
@@ -301,10 +249,9 @@ if (!function_exists('aksrt_archive_build_custom_filter_groups')) :
$chips[] = [
'label' => $value_name,
'url' => $is_active
? remove_query_arg([$dim_key, 'paged'])
: add_query_arg([$dim_key => $value_key], remove_query_arg('paged')),
? remove_query_arg($dim_key, aksrt_homepage_url_del_paged(aksrt_homepage_get_current_url()))
: add_query_arg([$dim_key => $value_key], aksrt_homepage_url_del_paged(aksrt_homepage_get_current_url())),
'active' => $is_active,
'attrs' => $ajax ? ' data-filter-dim="' . esc_attr($dim_key) . '" data-filter-dim-value="' . esc_attr($value_key) . '"' : '',
];
if ($is_active) {
$has_focus = true;
@@ -316,9 +263,8 @@ if (!function_exists('aksrt_archive_build_custom_filter_groups')) :
if ($has_focus) {
array_unshift($chips, [
'label' => __('全部', 'aksrt'),
'url' => remove_query_arg([$dim_key, 'paged']),
'url' => remove_query_arg($dim_key, aksrt_homepage_url_del_paged(aksrt_homepage_get_current_url())),
'active' => false,
'attrs' => $ajax ? ' data-filter-dim="' . esc_attr($dim_key) . '" data-filter-dim-value=""' : '',
]);
}
+9 -17
View File
@@ -100,12 +100,12 @@ if (!function_exists('aksrt_archive_get_sort_state')) :
$lists = array_values(array_unique(array_merge($lists, $all)));
}
$current = isset($_GET['orderby']) ? sanitize_key(wp_unslash((string) $_GET['orderby'])) : 'date';
if (!in_array($current, $all, true)) {
$current = isset($_GET['orderby']) ? sanitize_key(wp_unslash((string) $_GET['orderby'])) : '';
if ($current !== '' && !in_array($current, $all, true)) {
$current = 'date';
}
$base_url = remove_query_arg(['orderby', 'order', 'paged']);
$base_url = remove_query_arg(['orderby', 'order'], aksrt_homepage_url_del_paged(aksrt_homepage_get_current_url()));
$links = [];
foreach ($lists as $orderby) {
$links[$orderby] = add_query_arg('orderby', $orderby, $base_url);
@@ -176,12 +176,12 @@ if (!function_exists('aksrt_archive_render_stream')) :
'query_context' => 'archive',
'empty_text' => __('暂无文章', 'aksrt'),
'stream_class' => '',
'ajax_enabled' => false,
'ajax_attrs' => '',
'filter_html' => '',
]);
$classes = [
'content-stream',
'content-stream', 'posts-row', 'ajaxpager',
'content-stream--' . sanitize_html_class((string) $args['page_kind']),
];
if (!empty($args['stream_class'])) {
@@ -192,9 +192,6 @@ if (!function_exists('aksrt_archive_render_stream')) :
}
}
}
if (!empty($args['ajax_enabled'])) {
$classes[] = 'is-ajax-enabled';
}
$mode = (string) ($render_config['mode'] ?? 'list');
$has_posts = $query instanceof WP_Query ? $query->have_posts() : have_posts();
@@ -204,12 +201,9 @@ if (!function_exists('aksrt_archive_render_stream')) :
data-page-kind="<?php echo esc_attr((string) $args['page_kind']); ?>"
data-render-mode="<?php echo esc_attr($mode); ?>"
data-list-type="<?php echo esc_attr((string) ($render_config['list_type'] ?? 'thumb')); ?>"
<?php if (!empty($args['ajax_enabled'])) : ?>
data-ajax-wrap
data-query-context="<?php echo esc_attr((string) $args['query_context']); ?>"
<?php endif; ?>
<?php echo $args['ajax_attrs']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
>
<?php echo $args['filter_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php if ($has_posts) : ?>
<?php
// legacy 契约:分页渲染在 .ajaxpager 内部(zib_paging 位置),
@@ -222,7 +216,7 @@ if (!function_exists('aksrt_archive_render_stream')) :
$pagination_html = (string) ob_get_clean();
// 归档面与首页共用同一套骨架契约:.post_ajax_loader + .is-loading。
// 挂在分页之后(列表末尾),archive.js 靠它在 wrap 内定位加载态。
// 挂在分页之后(列表末尾),通用列表运行时在 pager 内定位加载态。
$loader_html = '<div class="post_ajax_loader" role="status" aria-label="'
. esc_attr__('正在加载', 'aksrt') . '">'
. aksrt_render_placeholder(
@@ -232,14 +226,12 @@ if (!function_exists('aksrt_archive_render_stream')) :
. '</div>';
aksrt_render_post_list(array_merge($render_config, [
'wrapper_class' => 'content-stream__list',
'skip_wrapper' => true,
'append_inner_html' => $pagination_html . $loader_html,
]), $query);
?>
<?php else : ?>
<div class="no-results content-stream__empty">
<p><?php echo esc_html((string) $args['empty_text']); ?></p>
</div>
<div class="ajax-item"><?php echo aksrt_get_null((string) $args['empty_text'], 100, 'null-post.svg'); ?></div>
<?php endif; ?>
</div>
<?php
+26 -97
View File
@@ -20,21 +20,15 @@ if (!function_exists('aksrt_comments_template_query_args')) :
$order = isset($_GET['aksrt_comment_order'])
? sanitize_key((string) wp_unslash($_GET['aksrt_comment_order']))
: '';
if ($order === '') {
$settings = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_comment_display_settings()
: [];
$order = (($settings['order'] ?? 'desc') === 'asc') ? 'oldest' : 'newest';
}
if (!in_array($order, ['newest', 'oldest', 'hottest'], true)) {
if (!in_array($order, ['newest', 'hottest'], true)) {
$order = 'newest';
}
if ($order === 'oldest') {
$args['order'] = 'ASC';
if ($order === 'hottest') {
$args['order'] = 'DESC';
$args['orderby'] = 'comment_date_gmt';
$args['aksrt_hottest_order'] = true;
} else {
// newest + hottest: base DESC; hottest reorders via comments_clauses.
$args['order'] = 'DESC';
$args['orderby'] = 'comment_date_gmt';
}
@@ -63,6 +57,17 @@ add_filter('comments_template_query_args', static function ($args) {
: $args;
});
add_filter('comments_clauses', static function (array $clauses, WP_Comment_Query $query): array {
if (empty($query->query_vars['aksrt_hottest_order'])) {
return $clauses;
}
global $wpdb;
$clauses['join'] .= " LEFT JOIN {$wpdb->commentmeta} AS aksrt_cm_hot ON ({$wpdb->comments}.comment_ID = aksrt_cm_hot.comment_id AND aksrt_cm_hot.meta_key = 'aksrt_comment_likes')";
$clauses['orderby'] = 'CAST(COALESCE(aksrt_cm_hot.meta_value, 0) AS UNSIGNED) DESC, ' . $wpdb->comments . '.comment_date_gmt DESC';
return $clauses;
}, 20, 2);
if (!function_exists('aksrt_comment_box_visible')) :
/**
* Whether the comment module may render for the current visitor.
@@ -103,14 +108,12 @@ if (!function_exists('aksrt_comments_build_context')) :
'nested_depth' => 3,
'require_login' => false,
'avatar_size' => 48,
'empty_text' => __('暂无评论,来做第一个评论的人吧~', 'aksrt'),
];
$default_order = (($settings['order'] ?? 'desc') === 'asc') ? 'oldest' : 'newest';
$order = isset($_GET['aksrt_comment_order'])
? sanitize_key((string) wp_unslash($_GET['aksrt_comment_order']))
: $default_order;
if (!in_array($order, ['newest', 'oldest', 'hottest'], true)) {
: 'newest';
if (!in_array($order, ['newest', 'hottest'], true)) {
$order = 'newest';
}
@@ -126,13 +129,10 @@ if (!function_exists('aksrt_comments_build_context')) :
'current_user' => wp_get_current_user(),
'require_login' => !empty($settings['require_login']),
'order' => $order,
'reverse_comments' => $order === 'newest',
'is_hottest' => $order === 'hottest',
'author_only' => isset($_GET['aksrt_author_only']) && (int) $_GET['aksrt_author_only'] === 1,
'post_author_id' => (int) get_the_author_meta('ID'),
'avatar_size' => $avatar_size,
'max_depth' => max(1, (int) ($settings['nested_depth'] ?? 3)),
'empty_text' => (string) ($settings['empty_text'] ?? __('暂无评论,来做第一个评论的人吧~', 'aksrt')),
'toolbar' => $toolbar,
'upload_enabled' => !empty($toolbar['upload_img']),
'link_enabled' => !empty($toolbar['link']),
@@ -140,7 +140,7 @@ if (!function_exists('aksrt_comments_build_context')) :
'box_placeholder' => (string) ($settings['box_placeholder'] ?? __('说点什么吧...', 'aksrt')),
'submit_text' => (string) ($settings['submit_text'] ?? __('提交评论', 'aksrt')),
'title_text' => (string) ($settings['title'] ?? __('评论', 'aksrt')),
'corderby_enabled' => !empty($settings['corderby']),
'corderby_enabled' => !empty($settings['corderby']) && !empty($settings['like_enabled']),
'close_comments' => !empty($settings['close_comments']),
'box_hidden' => !aksrt_comment_box_visible(),
'oauth_providers' => class_exists('\AKSRT\Auth\OAuth')
@@ -150,26 +150,6 @@ if (!function_exists('aksrt_comments_build_context')) :
}
endif;
if (!function_exists('aksrt_comments_apply_hottest_order')) :
/**
* Apply hottest-order SQL once for the current comments query.
*/
function aksrt_comments_apply_hottest_order(): void {
static $applied = false;
if ($applied) {
return;
}
$applied = true;
add_filter('comments_clauses', static function ($clauses) {
global $wpdb;
$clauses['join'] .= " LEFT JOIN {$wpdb->commentmeta} AS aksrt_cm ON ({$wpdb->comments}.comment_ID = aksrt_cm.comment_id AND aksrt_cm.meta_key = 'aksrt_comment_likes')";
$clauses['orderby'] = 'CAST(aksrt_cm.meta_value AS UNSIGNED) DESC, ' . $clauses['orderby'];
return $clauses;
});
}
endif;
if (!function_exists('aksrt_comments_normalize_order')) :
/**
@@ -177,13 +157,7 @@ if (!function_exists('aksrt_comments_normalize_order')) :
*/
function aksrt_comments_normalize_order(string $order = ''): string {
$order = sanitize_key($order);
if (!in_array($order, ['newest', 'oldest', 'hottest'], true)) {
$settings = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_comment_display_settings()
: [];
$order = (($settings['order'] ?? 'desc') === 'asc') ? 'oldest' : 'newest';
}
if (!in_array($order, ['newest', 'oldest', 'hottest'], true)) {
if (!in_array($order, ['newest', 'hottest'], true)) {
$order = 'newest';
}
return $order;
@@ -212,7 +186,7 @@ if (!function_exists('aksrt_comments_query_for_post')) :
'number' => $per_page,
'offset' => ($page - 1) * $per_page,
'orderby' => 'comment_date_gmt',
'order' => $order === 'oldest' ? 'ASC' : 'DESC',
'order' => 'DESC',
'hierarchical' => 'threaded',
];
@@ -228,46 +202,13 @@ if (!function_exists('aksrt_comments_query_for_post')) :
// WP_Comment_Query does not support mixed status easily; keep approve only for list fragment.
}
// Hottest uses meta_value_num when meta exists; fall back to date.
$hottest_filter = null;
if ($order === 'hottest') {
$query_args['orderby'] = 'meta_value_num';
$query_args['meta_key'] = 'aksrt_comment_likes';
$query_args['order'] = 'DESC';
// Ensure comments without like meta still appear.
$query_args['meta_query'] = [
'relation' => 'OR',
[
'key' => 'aksrt_comment_likes',
'compare' => 'EXISTS',
],
[
'key' => 'aksrt_comment_likes',
'compare' => 'NOT EXISTS',
],
];
// Prefer clauses filter for stable hottest ordering.
$hottest_filter = static function ($clauses) {
global $wpdb;
if (strpos($clauses['join'], 'aksrt_cm_hot') === false) {
$clauses['join'] .= " LEFT JOIN {$wpdb->commentmeta} AS aksrt_cm_hot ON ({$wpdb->comments}.comment_ID = aksrt_cm_hot.comment_id AND aksrt_cm_hot.meta_key = 'aksrt_comment_likes')";
}
$clauses['orderby'] = 'CAST(COALESCE(aksrt_cm_hot.meta_value, 0) AS UNSIGNED) DESC, ' . $wpdb->comments . '.comment_date_gmt DESC';
return $clauses;
};
add_filter('comments_clauses', $hottest_filter, 20);
// Prefer date orderby with clauses override.
$query_args['orderby'] = 'comment_date_gmt';
unset($query_args['meta_key'], $query_args['meta_query']);
$query_args['aksrt_hottest_order'] = true;
}
$comment_query = new WP_Comment_Query($query_args);
$comments = is_array($comment_query->comments) ? $comment_query->comments : [];
if ($hottest_filter) {
remove_filter('comments_clauses', $hottest_filter, 20);
}
// Pin reorder for page 1 when not author-only.
if ($page === 1 && !$author_only && function_exists('aksrt_reorder_pinned_comments')) {
$comments = aksrt_reorder_pinned_comments($comments, $post_id);
@@ -325,8 +266,6 @@ if (!function_exists('aksrt_comments_render_items_html')) :
'style' => 'ol',
'avatar_size' => $avatar,
'max_depth' => $depth,
'reverse_top_level' => $reverse,
'reverse_children' => $reverse,
], $comments);
return (string) ob_get_clean();
}
@@ -370,9 +309,8 @@ if (!function_exists('aksrt_comments_render_list_fragment')) :
'order' => $order,
'author_only' => $author_only,
'post_author_id' => (int) get_post_field('post_author', $post_id),
'corderby_enabled' => !empty($settings['corderby']),
'corderby_enabled' => !empty($settings['corderby']) && !empty($settings['like_enabled']),
'comment_count' => $total,
'empty_text' => (string) ($settings['empty_text'] ?? __('暂无评论,来做第一个评论的人吧~', 'aksrt')),
];
ob_start();
@@ -623,7 +561,7 @@ if (!function_exists('aksrt_comments_render_filter')) :
</li>
</ul>
<?php if ($post_author_id > 0) : ?>
<a href="javascript:;" class="but comment-orderby btn-only-author p2-10<?php echo $author_only ? ' active' : ''; ?>" data-author-only="<?php echo $author_only ? '0' : '1'; ?>" rel="nofollow">
<a href="javascript:;" class="but comment-orderby btn-only-author p2-10" data-author-only="<?php echo $author_only ? '0' : '1'; ?>" rel="nofollow">
<?php echo $author_only ? esc_html__('查看全部', 'aksrt') : esc_html__('只看作者', 'aksrt'); ?>
</a>
<?php endif; ?>
@@ -652,12 +590,9 @@ if (!function_exists('aksrt_comments_render_empty')) :
* @param array<string,mixed> $ctx Comments context.
*/
function aksrt_comments_render_empty(array $ctx): void {
$text = (string) ($ctx['empty_text'] ?? __('暂无评论内容', 'aksrt'));
if (!empty($ctx['author_only'])) {
$text = __('暂无作者评论', 'aksrt');
}
$text = __('暂无评论内容', 'aksrt');
if (function_exists('aksrt_get_null')) {
echo aksrt_get_null($text, 40, 'null.svg', 'comment comment-null', 280); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo aksrt_get_null($text, 40, 'comment-null.svg', 'comment comment-null', 280); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return;
}
echo '<div class="text-center comment comment-null" style="padding:40px 0;"><p class="em09 muted-3-color separator">' . esc_html($text) . '</p></div>';
@@ -678,10 +613,6 @@ if (!function_exists('aksrt_comments_render')) :
return;
}
if (!empty($ctx['is_hottest'])) {
aksrt_comments_apply_hottest_order();
}
set_query_var('aksrt_comment_order', (string) $ctx['order']);
set_query_var('aksrt_comment_settings', $ctx['settings']);
@@ -707,7 +638,7 @@ if (!function_exists('aksrt_comments_render')) :
<div id="postcomments">
<ol class="commentlist list-unstyled">
<?php
$show_filter = !empty($ctx['corderby_enabled']) && ($count > 0 || !empty($ctx['author_only']) || have_comments());
$show_filter = !empty($ctx['corderby_enabled']) && have_comments();
if ($show_filter) {
aksrt_comments_render_filter($ctx);
}
@@ -740,8 +671,6 @@ if (!function_exists('aksrt_comments_render_list_items')) :
'avatar_size' => (int) $ctx['avatar_size'],
'max_depth' => (int) $ctx['max_depth'],
'callback' => 'aksrt_comment_callback',
'reverse_top_level' => !empty($ctx['reverse_comments']),
'reverse_children' => !empty($ctx['reverse_comments']),
]);
echo aksrt_get_comment_ajax_loader(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if (function_exists('aksrt_render_comment_pagination')) {
+2
View File
@@ -135,6 +135,8 @@ if (!function_exists('aksrt_graphic_card')) :
$more_safe = $more !== '' ? wp_kses($more, array_merge($allow, [
'div' => ['class' => true, 'style' => true],
'badge' => ['class' => true],
'svg' => ['class' => true, 'viewbox' => true, 'aria-hidden' => true],
'use' => ['href' => true, 'xlink:href' => true],
])) : '';
$text = '';
+2 -6
View File
@@ -65,22 +65,18 @@ if (!function_exists('aksrt_homepage_get_tab_configs')) {
return [];
}
$excluded_cat_ids = array_flip(\AKSRT\Settings\Settings::home_exclude_category_ids());
$configs = [];
foreach ($structured_tabs as $tab) {
if ($tab['term_id'] <= 0) {
continue;
}
if (isset($excluded_cat_ids[$tab['term_id']])) {
continue;
}
$term = get_term($tab['term_id']);
if (!$term || is_wp_error($term)) {
continue;
}
if ($term->taxonomy !== 'category') {
if ($term->taxonomy !== 'category' || (int) $term->count === 0) {
continue;
}
@@ -106,7 +102,7 @@ if (!function_exists('aksrt_homepage_get_sort_mode')) {
: ['modified', 'date', 'views', 'like', 'comment_count', 'favorite', 'rand'];
$default_sort = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::homepage_default_sort()
: 'modified';
: 'date';
$override_sort = get_query_var('aksrt_homepage_sort_override');
$sort = is_string($override_sort) && $override_sort !== ''
? sanitize_key($override_sort)
+17 -9
View File
@@ -214,7 +214,6 @@ if (!function_exists('aksrt_widget_term_aggregation')) :
}
$href = esc_url((string) $href);
$icon = '<i class="fa fas fa-folder-open mr6" aria-hidden="true"></i>';
$img = aksrt_widget_term_cover_url((int) $term->term_id, true);
$img_html = aksrt_graphic_card([
'class' => '',
@@ -228,7 +227,12 @@ if (!function_exists('aksrt_widget_term_aggregation')) :
$img_html = '<div class="term-img flex0 em09-sm">' . $img_html . '</div>';
$name = '<a class="em14 key-color text-ellipsis"' . $target . ' href="' . $href . '">' . esc_html($term->name) . '</a>';
$description = '<div class="text-ellipsis-2 muted-color">' . esc_html($term->description) . '</div>';
$description = wp_kses_post((string) $term->description);
if ($description === '' && current_user_can('manage_categories')) {
$description = esc_html__('请在WordPress后台-文章-文章分类中添加描述!', 'aksrt')
. '<a href="' . esc_url(get_edit_term_link($term_id, 'category')) . '">' . esc_html__('立即编辑', 'aksrt') . '</a>';
}
$description = '<div class="text-ellipsis-2 muted-color">' . $description . '</div>';
$query_args = [
'cat' => (string) $term->term_id,
@@ -241,14 +245,18 @@ if (!function_exists('aksrt_widget_term_aggregation')) :
];
$query = \AKSRT\WidgetSystem\PostQuery::build($query_args, 1, []);
$meta = '<span class="px12 muted-3-color text-ellipsis"><i class="fa fas fa-file-alt fa-fw" aria-hidden="true"></i>' . sprintf(esc_html__('%s篇文章', 'aksrt'), esc_html(aksrt_format_number(max(0, (int) $query->found_posts)))) . '</span>';
if (!$query->have_posts()) {
wp_reset_postdata();
return '';
}
$meta = '<span class="px12 muted-3-color text-ellipsis"><i class="fa far fa-file-alt fa-fw" aria-hidden="true"></i>' . sprintf(esc_html__('%s篇文章', 'aksrt'), esc_html((string) max(0, (int) $query->found_posts))) . '</span>';
$meta .= '<a class="but p2-10 px12 c-blue hide-sm shrink0"' . $target . ' href="' . $href . '"><i class="fa fas fa-angle-right" aria-hidden="true"></i>' . esc_html__('更多文章', 'aksrt') . '</a>';
$meta = '<div class="term-meta flex jsb ac">' . $meta . '</div>';
$posts_html = '';
while ($query->have_posts()) {
$query->the_post();
$data = aksrt_renderer_collect_post_data(['time_source' => 'date']);
$data = aksrt_renderer_collect_post_data(['time_source' => $args['orderby'] === 'date' ? 'date' : 'modified']);
if (!$data) {
continue;
}
@@ -258,20 +266,20 @@ if (!function_exists('aksrt_widget_term_aggregation')) :
$post_meta = '';
switch ((string) $args['orderby']) {
case 'views':
$post_meta = aksrt_format_number((int) $data['views']);
$post_meta = esc_html__('阅读[', 'aksrt') . aksrt_format_number((int) $data['views']) . ']';
break;
case 'favorite':
$post_meta = aksrt_format_number((int) aksrt_get_post_favorites((int) $data['post_id']));
$post_meta = esc_html__('收藏[', 'aksrt') . 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']));
$post_meta = esc_html__('点赞[', 'aksrt') . aksrt_format_number((int) aksrt_get_post_likes((int) $data['post_id'])) . ']';
break;
case 'comment_count':
$post_meta = aksrt_format_number((int) $data['comments']);
$post_meta = esc_html__('评论[', 'aksrt') . aksrt_format_number((int) $data['comments']) . ']';
break;
}
if ($post_meta === '') {
$post_meta = '<i class="fa fas fa-clock mr3" aria-hidden="true"></i>' . esc_html((string) $data['time_ago']);
$post_meta = '<i class="fa far fa-clock mr3" aria-hidden="true"></i>' . esc_html((string) $data['time_ago']);
}
$posts_html .= '<div class="mt10 flex jsb ac">' . $title . '<div class="em09 muted-3-color flex0 ml10">' . $post_meta . '</div></div>';
}
+47 -27
View File
@@ -9,47 +9,67 @@
defined('ABSPATH') || exit;
add_action('wp_ajax_aksrt_yiyan', 'aksrt_ajax_yiyan');
add_action('wp_ajax_nopriv_aksrt_yiyan', 'aksrt_ajax_yiyan');
if (!function_exists('aksrt_ajax_yiyan')) :
/** Return one random Chinese/English pair from the theme quote file. */
function aksrt_ajax_yiyan(): void {
$custom_file = WP_CONTENT_DIR . '/yiyan.txt';
$theme_file = get_template_directory() . '/yiyan/qv-yiyan.txt';
$file = is_readable($custom_file) ? $custom_file : $theme_file;
$lines = is_readable($file) ? file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) : false;
$pairs = [];
if (is_array($lines)) {
// The first line is the reference file's title; the rest alternate EN/CN.
array_shift($lines);
$lines = array_values(array_map('trim', $lines));
for ($i = 0, $length = count($lines); $i + 1 < $length; $i += 2) {
if ($lines[$i] !== '' && $lines[$i + 1] !== '') {
$pairs[] = ['en' => $lines[$i], 'cn' => $lines[$i + 1]];
}
}
}
if (!$pairs) {
wp_send_json_error(['message' => __('一言内容暂不可用', 'aksrt')], 503);
}
wp_send_json_success($pairs[array_rand($pairs)]);
}
endif;
if (!function_exists('aksrt_render_yiyan_block')) :
/**
* Shared 一言 shell for single-post placements (and reusable markup).
*
* Hitokoto category `c` must be a letter a–l, or empty for mixed random.
* Client loads quotes via parallel fetches when count > 1.
* Reads bilingual pairs from the bundled reference file and supports the
* same optional wp-content/yiyan.txt override used by the reference theme.
*
* @param array<string,mixed> $args {
* @type string $class Extra CSS classes on the root.
* @type string $type Category letter a–l, or empty for random.
* @type int $count Number of quotes to load (1–10).
* @type int $interval Rotation interval in seconds.
* @type string $icon Optional FA icon class; empty = no icon.
* }
*/
function aksrt_render_yiyan_block(array $args = []): void {
echo aksrt_get_yiyan_markup($args); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped by the markup helper.
}
function aksrt_get_yiyan_markup(array $args = []): string {
$class = trim((string) ($args['class'] ?? 'aksrt-yiyan'));
$type_raw = strtolower(trim((string) ($args['type'] ?? '')));
$type = preg_match('/^[a-l]$/', $type_raw) ? $type_raw : '';
$count = max(1, min(10, (int) ($args['count'] ?? 5)));
$interval = max(3, min(30, (int) ($args['interval'] ?? 8)));
$icon = sanitize_text_field((string) ($args['icon'] ?? ''));
if (strpos($class, 'aksrt-yiyan') === false) {
$class = trim($class . ' aksrt-yiyan');
}
?>
<div class="<?php echo esc_attr($class); ?>"
data-aksrt-yiyan
data-count="<?php echo esc_attr((string) $count); ?>"
data-interval="<?php echo esc_attr((string) $interval); ?>"
<?php echo $type !== '' ? ' data-type="' . esc_attr($type) . '"' : ''; ?>>
<div class="aksrt-yiyan__content">
<?php if ($icon !== '') : ?>
<i class="<?php echo esc_attr($icon); ?> aksrt-yiyan__icon" aria-hidden="true"></i>
<?php endif; ?>
<p class="aksrt-yiyan__text"><?php esc_html_e('加载中…', 'aksrt'); ?></p>
<p class="aksrt-yiyan__from"></p>
</div>
<div class="aksrt-yiyan__dots" aria-hidden="true"></div>
</div>
<?php
$html = '<div class="' . esc_attr($class) . '"'
. ' data-aksrt-yiyan'
. ' data-endpoint="' . esc_url(admin_url('admin-ajax.php')) . '"'
. ' data-toggle="tooltip"'
. ' title="' . esc_attr__('点击切换一言', 'aksrt') . '"'
. ' role="button" tabindex="0"'
. ' aria-label="' . esc_attr__('点击切换一言', 'aksrt') . '"'
. ' aria-live="polite">'
. '<div class="yiyan"><div class="en"></div><div class="cn">' . esc_html__('加载中…', 'aksrt') . '</div></div>'
. '</div>';
return $html;
}
endif;
+3 -12
View File
@@ -120,20 +120,11 @@ if (!function_exists('aksrt_homepage_get_tab_panes_html')) {
return '';
}
$is_card = class_exists('\AKSRT\Settings\PostDisplay')
&& \AKSRT\Settings\PostDisplay::get_archive_layout() === 'card';
$is_mobel = wp_is_mobile();
if ($is_card) {
$placeholder_type = 'card';
$placeholder_num = $is_mobel ? 4 : 12;
} else {
$placeholder_type = 'list';
$placeholder_num = $is_mobel ? 4 : 6;
}
$placeholder = aksrt_render_placeholder($placeholder_type, $placeholder_num);
$html = '';
foreach ($tab_configs as $index => $tab_cfg) {
$config = aksrt_archive_get_render_config(['context' => 'category', 'term_id' => (int) $tab_cfg['term_id']]);
$is_card = ($config['mode'] ?? 'list') === 'card';
$placeholder = aksrt_render_placeholder($is_card ? 'card' : 'list', $is_card ? (wp_is_mobile() ? 4 : 12) : (wp_is_mobile() ? 4 : 6));
$html .= '<div class="ajaxpager tab-pane fade" id="index-tab-' . (int) $index . '">';
$html .= '<span class="post_ajax_trigger"><a class="ajax_load ajax-next ajax-open" href="' . esc_url(aksrt_homepage_get_tab_link($tab_cfg)) . '"></a></span>';
$html .= '<div class="post_ajax_loader">' . $placeholder . '</div>';