Merge pull request 'Worktree ux depth batch8' (#226) from worktree-ux-depth-batch8 into main

Reviewed-on: CYL/wordpress-theme-aksrt#226
This commit is contained in:
2026-07-20 22:17:26 +08:00
10 changed files with 726 additions and 503 deletions
+11
View File
@@ -776,3 +776,14 @@
flex-direction: column;
align-items: flex-start;
}
/* User ranking extras (batch8) */
.aksrt-user-ranking__item { align-items: center; }
.aksrt-user-ranking__meta { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.aksrt-user-ranking__desc {
font-size: 11px;
color: var(--muted-2-color, #999);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
+131 -49
View File
@@ -1,6 +1,6 @@
<?php
/**
* Public widget list REST endpoints (posts / tab-posts).
* Public widget list REST endpoints.
*
* @package AKSRT
*/
@@ -24,31 +24,30 @@ class WidgetAjax {
}
public function register_routes(): void {
register_rest_route('aksrt/v1', '/widgets/posts', [
'methods' => 'GET',
'callback' => [$this, 'rest_main_posts'],
'permission_callback' => '__return_true',
]);
register_rest_route('aksrt/v1', '/widgets/tab-posts', [
'methods' => 'GET',
'callback' => [$this, 'rest_tab_posts'],
'permission_callback' => '__return_true',
]);
register_rest_route('aksrt/v1', '/widgets/paid-posts', [
'methods' => 'GET',
'callback' => [$this, 'rest_paid_posts'],
'permission_callback' => '__return_true',
]);
register_rest_route('aksrt/v1', '/widgets/category-tabs', [
'methods' => 'GET',
'callback' => [$this, 'rest_category_tabs'],
'permission_callback' => '__return_true',
]);
$routes = [
'/widgets/posts' => 'rest_main_posts',
'/widgets/tab-posts' => 'rest_tab_posts',
'/widgets/paid-posts' => 'rest_paid_posts',
'/widgets/category-tabs' => 'rest_category_tabs',
'/widgets/hot-posts' => 'rest_hot_posts',
'/widgets/oneline-posts' => 'rest_oneline_posts',
'/widgets/recent-comments'=> 'rest_recent_comments',
'/widgets/user-lists' => 'rest_user_lists',
'/widgets/forum-topics' => 'rest_forum_topics',
'/widgets/forum-terms' => 'rest_forum_terms',
];
foreach ($routes as $path => $cb) {
register_rest_route('aksrt/v1', $path, [
'methods' => 'GET',
'callback' => [$this, $cb],
'permission_callback' => '__return_true',
]);
}
}
public function rest_main_posts(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\WidgetMainPosts')) {
return new \WP_REST_Response(['success' => false, 'message' => 'unavailable'], 500);
return $this->fail();
}
$instance = [
'count' => max(1, min(30, (int) ($request->get_param('count') ?? 6))),
@@ -62,21 +61,12 @@ class WidgetAjax {
'paginate' => sanitize_key((string) ($request->get_param('paginate') ?? 'ajax')),
];
$page = max(1, (int) ($request->get_param('page') ?? $request->get_param('paged') ?? 1));
$payload = \AKSRT\Widgets\WidgetMainPosts::query_payload($instance, $page);
return new \WP_REST_Response([
'success' => true,
'html' => (string) ($payload['html'] ?? ''),
'paginate_html' => (string) ($payload['paginate_html'] ?? ''),
'has_more' => !empty($payload['has_more']),
'page' => (int) ($payload['page'] ?? $page),
'max_pages' => (int) ($payload['max_pages'] ?? 1),
'total' => (int) ($payload['total'] ?? 0),
], 200);
return $this->payload(\AKSRT\Widgets\WidgetMainPosts::query_payload($instance, $page), $page);
}
public function rest_tab_posts(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\TabPostsWidget')) {
return new \WP_REST_Response(['success' => false, 'message' => 'unavailable'], 500);
return $this->fail();
}
$default_count = max(1, min(20, (int) ($request->get_param('count') ?? 5)));
$tab = [
@@ -94,15 +84,12 @@ class WidgetAjax {
} else {
$html = '<div class="widget-tab-posts__list widget-tab-posts__list--mini aksrt-main-posts style-mini">' . $html . '</div>';
}
return new \WP_REST_Response([
'success' => true,
'html' => $html,
], 200);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_paid_posts(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\WidgetPaidPosts')) {
return new \WP_REST_Response(['success' => false, 'message' => 'unavailable'], 500);
return $this->fail();
}
$instance = [
'count' => max(1, min(20, (int) ($request->get_param('count') ?? 6))),
@@ -110,16 +97,7 @@ class WidgetAjax {
'paginate' => sanitize_key((string) ($request->get_param('paginate') ?? 'ajax')),
];
$page = max(1, (int) ($request->get_param('page') ?? $request->get_param('paged') ?? 1));
$payload = \AKSRT\Widgets\WidgetPaidPosts::query_payload($instance, $page);
return new \WP_REST_Response([
'success' => true,
'html' => (string) ($payload['html'] ?? ''),
'paginate_html' => (string) ($payload['paginate_html'] ?? ''),
'has_more' => !empty($payload['has_more']),
'page' => (int) ($payload['page'] ?? $page),
'max_pages' => (int) ($payload['max_pages'] ?? 1),
'total' => (int) ($payload['total'] ?? 0),
], 200);
return $this->payload(\AKSRT\Widgets\WidgetPaidPosts::query_payload($instance, $page), $page);
}
public function rest_category_tabs(\WP_REST_Request $request) {
@@ -152,7 +130,111 @@ class WidgetAjax {
echo '</ul>';
}
wp_reset_postdata();
$html = (string) ob_get_clean();
return new \WP_REST_Response(['success' => true, 'html' => (string) ob_get_clean()], 200);
}
public function rest_hot_posts(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\HotPostsWidget')) {
return $this->fail();
}
$instance = [
'count' => max(1, min(20, (int) ($request->get_param('count') ?? 5))),
'order_by' => sanitize_key((string) ($request->get_param('order_by') ?? 'views')),
'days' => max(0, (int) ($request->get_param('days') ?? 30)),
'show_thumb' => $request->get_param('show_thumb') === null ? 1 : (int) $request->get_param('show_thumb'),
'paginate' => sanitize_key((string) ($request->get_param('paginate') ?? 'ajax')),
];
$page = max(1, (int) ($request->get_param('page') ?? 1));
return $this->payload(\AKSRT\Widgets\HotPostsWidget::query_payload($instance, $page), $page);
}
public function rest_oneline_posts(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\WidgetOnelinePosts')) {
return $this->fail();
}
$instance = [
'count' => max(1, min(20, (int) ($request->get_param('count') ?? 8))),
'style' => sanitize_key((string) ($request->get_param('style') ?? 'card')),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'date')),
'cat' => max(0, (int) ($request->get_param('cat') ?? 0)),
'limit_day' => max(0, (int) ($request->get_param('limit_day') ?? 0)),
];
$html = \AKSRT\Widgets\WidgetOnelinePosts::render_html($instance);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_recent_comments(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\RecentCommentsWidget')) {
return $this->fail();
}
$instance = [
'count' => max(1, min(20, (int) ($request->get_param('count') ?? 5))),
'show_avatar' => $request->get_param('show_avatar') === null ? 1 : (int) $request->get_param('show_avatar'),
'outpost' => max(0, (int) ($request->get_param('outpost') ?? 0)),
'outer' => sanitize_text_field((string) ($request->get_param('outer') ?? '')),
];
$html = \AKSRT\Widgets\RecentCommentsWidget::render_html($instance);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_user_lists(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\WidgetUserLists')) {
return $this->fail();
}
$instance = [
'number' => max(1, min(50, (int) ($request->get_param('number') ?? 8))),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'user_registered')),
'order' => sanitize_key((string) ($request->get_param('order') ?? 'DESC')),
'include' => sanitize_text_field((string) ($request->get_param('include') ?? '')),
'exclude' => sanitize_text_field((string) ($request->get_param('exclude') ?? '')),
'hide_box' => (int) ($request->get_param('hide_box') ?? 0),
];
$html = \AKSRT\Widgets\WidgetUserLists::render_html($instance);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_forum_topics(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\WidgetForumHotTopics')) {
return $this->fail();
}
$instance = [
'count' => max(1, min(20, (int) ($request->get_param('count') ?? 5))),
'period' => sanitize_key((string) ($request->get_param('period') ?? 'week')),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'views')),
'paginate' => sanitize_key((string) ($request->get_param('paginate') ?? 'ajax')),
];
$page = max(1, (int) ($request->get_param('page') ?? 1));
return $this->payload(\AKSRT\Widgets\WidgetForumHotTopics::query_payload($instance, $page), $page);
}
public function rest_forum_terms(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\WidgetForumTopicTerms')) {
return $this->fail();
}
$instance = [
'number' => max(1, min(40, (int) ($request->get_param('number') ?? 12))),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'count')),
'hide_empty' => $request->get_param('hide_empty') === null ? 1 : (int) $request->get_param('hide_empty'),
'include' => sanitize_text_field((string) ($request->get_param('include') ?? '')),
'style' => sanitize_key((string) ($request->get_param('style') ?? 'mini')),
];
$html = \AKSRT\Widgets\WidgetForumTopicTerms::render_html($instance);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
private function fail(): \WP_REST_Response {
return new \WP_REST_Response(['success' => false, 'message' => 'unavailable'], 500);
}
private function payload(array $payload, int $page): \WP_REST_Response {
return new \WP_REST_Response([
'success' => true,
'html' => (string) ($payload['html'] ?? ''),
'paginate_html' => (string) ($payload['paginate_html'] ?? ''),
'has_more' => !empty($payload['has_more']),
'page' => (int) ($payload['page'] ?? $page),
'max_pages' => (int) ($payload['max_pages'] ?? 1),
'total' => (int) ($payload['total'] ?? 0),
], 200);
}
}
+193 -141
View File
@@ -1,5 +1,7 @@
<?php
/**
* Hot posts ranking (zibll hot posts + ajax).
*
* @package AKSRT
*/
@@ -9,151 +11,201 @@ defined('ABSPATH') || exit;
class HotPostsWidget extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_hot_posts',
esc_html__('AKSRT 热榜文章', 'aksrt'),
[
'description' => esc_html__('按浏览量/评论数/点赞数排序的热门文章列表', 'aksrt'),
'customize_selective_refresh' => true,
]
);
}
public function __construct() {
parent::__construct(
'aksrt_hot_posts',
esc_html__('AKSRT 热榜文章', 'aksrt'),
[
'description' => esc_html__('按浏览量/评论数/点赞数排序的热门文章列表', 'aksrt'),
'customize_selective_refresh' => true,
]
);
}
public function widget($args, $instance): void {
echo $args['before_widget'];
public static function build_query_args(array $instance, int $paged = 1): array {
$count = max(1, min(20, (int) ($instance['count'] ?? 5)));
$order_by = sanitize_key((string) ($instance['order_by'] ?? 'views'));
$days = max(0, (int) ($instance['days'] ?? 30));
$query_args = [
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => $count,
'paged' => max(1, $paged),
'ignore_sticky_posts' => true,
];
if ($days > 0 && $days < 365) {
$query_args['date_query'] = [['after' => $days . ' days ago']];
}
switch ($order_by) {
case 'comments':
$query_args['orderby'] = 'comment_count';
$query_args['order'] = 'DESC';
break;
case 'likes':
$query_args['meta_key'] = '_aksrt_like_count';
$query_args['orderby'] = 'meta_value_num';
$query_args['order'] = 'DESC';
break;
case 'favorites':
$query_args['meta_key'] = '_aksrt_favorite_count';
$query_args['orderby'] = 'meta_value_num';
$query_args['order'] = 'DESC';
break;
case 'downloads':
$query_args['meta_key'] = '_aksrt_res_download_count';
$query_args['orderby'] = 'meta_value_num';
$query_args['order'] = 'DESC';
break;
case 'views':
default:
$query_args['meta_key'] = 'aksrt_post_views';
$query_args['orderby'] = 'meta_value_num';
$query_args['order'] = 'DESC';
break;
}
return $query_args;
}
$title = apply_filters('widget_title', $instance['title'] ?? __('热门文章', 'aksrt'), $instance, $this->id_base);
$count = absint($instance['count'] ?? 5);
$order_by = $instance['order_by'] ?? 'views';
$days = absint($instance['days'] ?? 30);
$show_thumb = !empty($instance['show_thumb']);
public static function render_items_html(\WP_Query $query, array $instance, int $start = 0): string {
if (!$query->have_posts()) {
return '';
}
$show_thumb = !empty($instance['show_thumb']);
ob_start();
$index = $start;
while ($query->have_posts()) {
$query->the_post();
$index++;
$rank_class = $index <= 3 ? ' rank-top' : '';
$views = (int) get_post_meta(get_the_ID(), 'aksrt_post_views', true);
?>
<div class="hot-post-item ajax-item <?php echo esc_attr($rank_class); ?>">
<span class="hot-post-item__rank"><?php echo esc_html((string) $index); ?></span>
<?php if ($show_thumb) :
$thumb = get_the_post_thumbnail_url(get_the_ID(), 'thumbnail');
?>
<a class="hot-post-item__thumb<?php echo $thumb ? '' : ' is-empty'; ?>" href="<?php the_permalink(); ?>">
<?php if ($thumb) : ?><img src="<?php echo esc_url($thumb); ?>" alt="" loading="lazy" /><?php endif; ?>
</a>
<?php endif; ?>
<div class="hot-post-item__info">
<a href="<?php the_permalink(); ?>" class="hot-post-item__title"><?php the_title(); ?></a>
<div class="hot-post-item__meta">
<span><i class="fas fa-eye" aria-hidden="true"></i> <?php echo esc_html(function_exists('aksrt_format_number') ? aksrt_format_number($views) : (string) $views); ?></span>
<span class="hot-post-item__time"><?php echo esc_html(human_time_diff(get_the_time('U'), current_time('timestamp')) . __('前', 'aksrt')); ?></span>
</div>
</div>
</div>
<?php
}
wp_reset_postdata();
return (string) ob_get_clean();
}
if ($title) {
echo $args['before_title'] . esc_html($title) . $args['after_title'];
}
/**
* @return array{html:string,paginate_html:string,has_more:bool,page:int,max_pages:int,total:int}
*/
public static function query_payload(array $instance, int $page = 1): array {
$page = max(1, $page);
$count = max(1, min(20, (int) ($instance['count'] ?? 5)));
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'ajax'));
$q = new \WP_Query(self::build_query_args($instance, $page));
$items = self::render_items_html($q, $instance, ($page - 1) * $count);
$html = $items !== '' ? '<div class="widget-hot-posts widget-hot-posts--mini">' . $items . '</div>' : '';
if ($html === '' && $page === 1) {
$html = function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('暂无热门文章', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无热门文章', 'aksrt') . '</p>';
}
$max_pages = max(1, (int) $q->max_num_pages);
$total = (int) $q->found_posts;
$has_more = $page < $max_pages;
$paginate_html = '';
if ($paginate === 'ajax' && $has_more) {
$next = add_query_arg([
'page' => $page + 1,
'count' => $count,
'order_by' => sanitize_key((string) ($instance['order_by'] ?? 'views')),
'days' => max(0, (int) ($instance['days'] ?? 30)),
'show_thumb' => !empty($instance['show_thumb']) ? 1 : 0,
'paginate' => 'ajax',
], rest_url('aksrt/v1/widgets/hot-posts'));
$paginate_html = function_exists('aksrt_get_ajax_next_paginate')
? aksrt_get_ajax_next_paginate($total, $page, $count, $next)
: '<div class="text-center theme-pagination ajax-pag"><div class="next-page ajax-next"><a href="' . esc_url($next) . '">' . esc_html__('加载更多', 'aksrt') . '</a></div></div>';
} else {
$paginate_html = '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
}
return compact('html', 'paginate_html', 'has_more', 'page', 'max_pages', 'total');
}
$query_args = [
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => $count,
'ignore_sticky_posts' => true,
'no_found_rows' => true,
];
public function widget($args, $instance): void {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', $instance['title'] ?? __('热门文章', 'aksrt'), $instance, $this->id_base);
if ($title) {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$instance['paginate'] = sanitize_key((string) ($instance['paginate'] ?? 'ajax'));
$payload = self::query_payload($instance, 1);
$rest = rest_url('aksrt/v1/widgets/hot-posts');
echo '<div class="ajaxpager" data-aksrt-ajaxpager data-aksrt-ajax-url="' . esc_url($rest) . '">';
echo $payload['html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $payload['paginate_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="post_ajax_loader" data-aksrt-ajax-loader data-aksrt-ajax-loader-hide hidden></div>';
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
if ($days > 0 && $days < 365) {
$query_args['date_query'] = [['after' => $days . ' days ago']];
}
public function form($instance): void {
$title = $instance['title'] ?? __('热门文章', 'aksrt');
$count = (int) ($instance['count'] ?? 5);
$order_by = $instance['order_by'] ?? 'views';
$days = (int) ($instance['days'] ?? 30);
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr((string) $title); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('显示数量:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" min="1" max="20" value="<?php echo (int) $count; ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('order_by')); ?>"><?php esc_html_e('排序方式:', 'aksrt'); ?></label>
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('order_by')); ?>" name="<?php echo esc_attr($this->get_field_name('order_by')); ?>">
<option value="views" <?php selected($order_by, 'views'); ?>><?php esc_html_e('浏览量', 'aksrt'); ?></option>
<option value="comments" <?php selected($order_by, 'comments'); ?>><?php esc_html_e('评论数', 'aksrt'); ?></option>
<option value="likes" <?php selected($order_by, 'likes'); ?>><?php esc_html_e('点赞数', 'aksrt'); ?></option>
<option value="favorites" <?php selected($order_by, 'favorites'); ?>><?php esc_html_e('收藏数', 'aksrt'); ?></option>
<option value="downloads" <?php selected($order_by, 'downloads'); ?>><?php esc_html_e('下载数', 'aksrt'); ?></option>
</select>
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('days')); ?>"><?php esc_html_e('时间范围(天,0=不限):', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('days')); ?>" name="<?php echo esc_attr($this->get_field_name('days')); ?>" type="number" min="0" max="365" value="<?php echo (int) $days; ?>">
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_thumb')); ?>" value="1" <?php checked(!empty($instance['show_thumb'])); ?>> <?php esc_html_e('显示缩略图', 'aksrt'); ?></label>
</p>
<p>
<label><?php esc_html_e('分页', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('paginate')); ?>">
<option value="ajax" <?php selected(($instance['paginate'] ?? 'ajax'), 'ajax'); ?>><?php esc_html_e('AJAX', 'aksrt'); ?></option>
<option value="none" <?php selected(($instance['paginate'] ?? ''), 'none'); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
</select>
</label>
</p>
<?php
}
switch ($order_by) {
case 'comments':
$query_args['orderby'] = 'comment_count';
$query_args['order'] = 'DESC';
break;
case 'likes':
$query_args['meta_key'] = '_aksrt_like_count';
$query_args['orderby'] = 'meta_value_num';
$query_args['order'] = 'DESC';
break;
case 'favorites':
$query_args['meta_key'] = '_aksrt_favorite_count';
$query_args['orderby'] = 'meta_value_num';
$query_args['order'] = 'DESC';
break;
case 'downloads':
$query_args['meta_key'] = '_aksrt_res_download_count';
$query_args['orderby'] = 'meta_value_num';
$query_args['order'] = 'DESC';
break;
case 'views':
default:
$query_args['meta_key'] = 'aksrt_post_views';
$query_args['orderby'] = 'meta_value_num';
$query_args['order'] = 'DESC';
break;
}
$query = new \WP_Query($query_args);
if ($query->have_posts()) {
echo '<div class="widget-hot-posts widget-hot-posts--mini">';
$index = 0;
while ($query->have_posts()) {
$query->the_post();
$index++;
$rank_class = $index <= 3 ? ' rank-top' : '';
$views = (int) get_post_meta(get_the_ID(), 'aksrt_post_views', true);
?>
<div class="hot-post-item <?php echo esc_attr($rank_class); ?>">
<span class="hot-post-item__rank"><?php echo esc_html($index); ?></span>
<?php if ($show_thumb) :
$thumb = get_the_post_thumbnail_url(get_the_ID(), 'thumbnail');
?>
<a class="hot-post-item__thumb<?php echo $thumb ? '' : ' is-empty'; ?>" href="<?php the_permalink(); ?>">
<?php if ($thumb) : ?><img src="<?php echo esc_url($thumb); ?>" alt="" loading="lazy" /><?php endif; ?>
</a>
<?php endif; ?>
<div class="hot-post-item__info">
<a href="<?php the_permalink(); ?>" class="hot-post-item__title"><?php the_title(); ?></a>
<div class="hot-post-item__meta">
<span><i class="fas fa-eye" aria-hidden="true"></i> <?php echo esc_html(function_exists('aksrt_format_number') ? aksrt_format_number($views) : (string) $views); ?></span>
<span class="hot-post-item__time"><?php echo esc_html(human_time_diff(get_the_time('U'), current_time('timestamp')) . __('前', 'aksrt')); ?></span>
</div>
</div>
</div>
<?php
}
echo '</div>';
wp_reset_postdata();
} else {
echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无热门文章', 'aksrt')) : '<p class="widget-empty">' . esc_html__('暂无热门文章', 'aksrt') . '</p>';
}
echo $args['after_widget'];
}
public function form($instance): void {
$title = $instance['title'] ?? __('热门文章', 'aksrt');
$count = (int) ($instance['count'] ?? 5);
$order_by = $instance['order_by'] ?? 'views';
$days = (int) ($instance['days'] ?? 30);
?>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo $this->get_field_id('count'); ?>"><?php esc_html_e('显示数量:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo $this->get_field_id('count'); ?>" name="<?php echo $this->get_field_name('count'); ?>" type="number" min="1" max="20" value="<?php echo $count; ?>">
</p>
<p>
<label for="<?php echo $this->get_field_id('order_by'); ?>"><?php esc_html_e('排序方式:', 'aksrt'); ?></label>
<select class="widefat" id="<?php echo $this->get_field_id('order_by'); ?>" name="<?php echo $this->get_field_name('order_by'); ?>">
<option value="views" <?php selected($order_by, 'views'); ?>><?php esc_html_e('浏览量', 'aksrt'); ?></option>
<option value="comments" <?php selected($order_by, 'comments'); ?>><?php esc_html_e('评论数', 'aksrt'); ?></option>
<option value="likes" <?php selected($order_by, 'likes'); ?>><?php esc_html_e('点赞数', 'aksrt'); ?></option>
<option value="favorites" <?php selected($order_by, 'favorites'); ?>><?php esc_html_e('收藏数', 'aksrt'); ?></option>
<option value="downloads" <?php selected($order_by, 'downloads'); ?>><?php esc_html_e('下载数', 'aksrt'); ?></option>
</select>
</p>
<p>
<label for="<?php echo $this->get_field_id('days'); ?>"><?php esc_html_e('时间范围(天,0=不限):', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo $this->get_field_id('days'); ?>" name="<?php echo $this->get_field_name('days'); ?>" type="number" min="0" max="365" value="<?php echo $days; ?>">
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_thumb')); ?>" value="1" <?php checked(!empty($instance['show_thumb'])); ?>> <?php esc_html_e('显示缩略图', 'aksrt'); ?></label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'count' => absint($new_instance['count'] ?? 5),
'order_by' => in_array($new_instance['order_by'] ?? '', ['views', 'comments', 'likes', 'favorites', 'downloads'], true) ? $new_instance['order_by'] : 'views',
'days' => absint($new_instance['days'] ?? 30),
'show_thumb' => !empty($new_instance['show_thumb']) ? 1 : 0,
];
}
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'count' => absint($new_instance['count'] ?? 5),
'order_by' => in_array($new_instance['order_by'] ?? '', ['views', 'comments', 'likes', 'favorites', 'downloads'], true) ? $new_instance['order_by'] : 'views',
'days' => absint($new_instance['days'] ?? 30),
'show_thumb' => !empty($new_instance['show_thumb']) ? 1 : 0,
'paginate' => sanitize_key((string) ($new_instance['paginate'] ?? 'ajax')) === 'none' ? 'none' : 'ajax',
];
}
}
+38 -41
View File
@@ -1,6 +1,6 @@
<?php
/**
* Recent comments mini list (zibll widget_ui_new_comment lite).
* Recent comments mini list (zibll widget_ui_new_comment + remote HTML).
*
* @package AKSRT
*/
@@ -17,18 +17,11 @@ class RecentCommentsWidget extends \WP_Widget {
]);
}
public function widget($args, $instance): void {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? __('最新评论', 'aksrt')), $instance, $this->id_base);
public static function render_html(array $instance): string {
$count = max(1, min(20, (int) ($instance['count'] ?? $instance['limit'] ?? 5)));
$show_avatar = !isset($instance['show_avatar']) || !empty($instance['show_avatar']);
$outpost = max(0, (int) ($instance['outpost'] ?? 0));
$outer = sanitize_text_field((string) ($instance['outer'] ?? ''));
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$query = [
'number' => $count,
'status' => 'approve',
@@ -37,10 +30,6 @@ class RecentCommentsWidget extends \WP_Widget {
if ($outpost > 0) {
$query['post__not_in'] = [$outpost];
}
if ($outer !== '') {
// Exclude comments containing outer keywords (comma separated).
// Implemented post-filter below.
}
$comments = get_comments($query);
if ($outer !== '' && $comments) {
$keys = array_filter(array_map('trim', preg_split('/[,,]/', $outer) ?: []));
@@ -56,32 +45,40 @@ class RecentCommentsWidget extends \WP_Widget {
}));
}
}
if ($comments) {
echo '<div class="widget-recent-comments comment-mini-lists">';
foreach ($comments as $comment) {
$post = get_post($comment->comment_post_ID);
if (!$post) {
continue;
}
echo '<div class="widget-recent-comments__item">';
if ($show_avatar) {
echo '<div class="widget-recent-comments__avatar">' . get_avatar($comment, 32) . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '<div class="widget-recent-comments__body">';
echo '<div class="widget-recent-comments__meta">';
echo '<strong class="widget-recent-comments__author">' . esc_html($comment->comment_author) . '</strong>';
echo '<span class="widget-recent-comments__time">' . esc_html(human_time_diff(strtotime($comment->comment_date), current_time('timestamp'))) . '</span>';
echo '</div>';
echo '<p class="widget-recent-comments__text"><a href="' . esc_url(get_comment_link($comment)) . '">' . esc_html(wp_trim_words($comment->comment_content, 15, '...')) . '</a></p>';
echo '<div class="widget-recent-comments__post muted-2-color em09"><a href="' . esc_url(get_permalink($post)) . '">' . esc_html(get_the_title($post)) . '</a></div>';
echo '</div></div>';
}
echo '</div>';
} else {
echo '<p class="widget-empty">' . esc_html__('暂无评论', 'aksrt') . '</p>';
if (!$comments) {
return '<p class="widget-empty ajax-item">' . esc_html__('暂无评论', 'aksrt') . '</p>';
}
ob_start();
echo '<div class="widget-recent-comments comment-mini-lists ajax-item">';
foreach ($comments as $comment) {
$post = get_post($comment->comment_post_ID);
if (!$post) {
continue;
}
echo '<div class="widget-recent-comments__item">';
if ($show_avatar) {
echo '<div class="widget-recent-comments__avatar">' . get_avatar($comment, 32) . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '<div class="widget-recent-comments__body">';
echo '<div class="widget-recent-comments__meta">';
echo '<strong class="widget-recent-comments__author">' . esc_html($comment->comment_author) . '</strong>';
echo '<span class="widget-recent-comments__time">' . esc_html(human_time_diff(strtotime($comment->comment_date), current_time('timestamp'))) . '</span>';
echo '</div>';
echo '<p class="widget-recent-comments__text"><a href="' . esc_url(get_comment_link($comment)) . '">' . esc_html(wp_trim_words($comment->comment_content, 15, '...')) . '</a></p>';
echo '<div class="widget-recent-comments__post muted-2-color em09"><a href="' . esc_url(get_permalink($post)) . '">' . esc_html(get_the_title($post)) . '</a></div>';
echo '</div></div>';
}
echo '</div>';
return (string) ob_get_clean();
}
public function widget($args, $instance): void {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? __('最新评论', 'aksrt')), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo self::render_html($instance); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
@@ -97,6 +94,9 @@ class RecentCommentsWidget extends \WP_Widget {
<input class="tiny-text" type="number" min="1" max="20" name="<?php echo esc_attr($this->get_field_name('count')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['count'] ?? 5))); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_avatar')); ?>" value="1" <?php checked(!isset($instance['show_avatar']) || !empty($instance['show_avatar'])); ?>> <?php esc_html_e('显示头像', 'aksrt'); ?></label>
</p>
<p>
<label><?php esc_html_e('排除文章 ID', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('outpost')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['outpost'] ?? 0))); ?>">
@@ -107,9 +107,6 @@ class RecentCommentsWidget extends \WP_Widget {
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('outer')); ?>" value="<?php echo esc_attr((string) ($instance['outer'] ?? '')); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_avatar')); ?>" value="1" <?php checked(!isset($instance['show_avatar']) || !empty($instance['show_avatar'])); ?>> <?php esc_html_e('显示头像', 'aksrt'); ?></label>
</p>
<?php
}
@@ -117,9 +114,9 @@ class RecentCommentsWidget extends \WP_Widget {
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'count' => max(1, min(20, (int) ($new_instance['count'] ?? 5))),
'show_avatar' => !empty($new_instance['show_avatar']) ? 1 : 0,
'outpost' => max(0, (int) ($new_instance['outpost'] ?? 0)),
'outer' => sanitize_text_field((string) ($new_instance['outer'] ?? '')),
'show_avatar' => !empty($new_instance['show_avatar']) ? 1 : 0,
];
}
}
+146 -92
View File
@@ -1,5 +1,7 @@
<?php
/**
* Forum hot / recent topic widgets with shared AJAX payload.
*
* @package AKSRT
*/
@@ -7,105 +9,157 @@ namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
/**
* Forum-specific widgets.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
/**
* Forum Hot Topics Widget
*/
class WidgetForumHotTopics extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_forum_hot_topics',
__('论坛 - 热门话题', 'aksrt'),
['description' => __('显示论坛热门话题列表。', 'aksrt')]
);
}
public function __construct() {
parent::__construct(
'aksrt_forum_hot_topics',
__('论坛 - 热门话题', 'aksrt'),
['description' => __('显示论坛热门话题列表。', 'aksrt')]
);
}
public function widget($args, $instance): void {
$title = !empty($instance['title']) ? $instance['title'] : __('热门话题', 'aksrt');
$count = !empty($instance['count']) ? max(1, min(20, (int) $instance['count'])) : 5;
$period = !empty($instance['period']) ? sanitize_key($instance['period']) : 'week';
public static function build_query_args(array $instance, int $paged = 1): array {
$count = max(1, min(20, (int) ($instance['count'] ?? 5)));
$period = sanitize_key((string) ($instance['period'] ?? 'week'));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'views'));
$date_query = [];
if ($period === 'week') {
$date_query = [['after' => '7 days ago']];
} elseif ($period === 'month') {
$date_query = [['after' => '30 days ago']];
}
$query = [
'post_type' => 'aksrt_forum_topic',
'posts_per_page' => $count,
'paged' => max(1, $paged),
'post_status' => 'publish',
'date_query' => $date_query,
];
if ($orderby === 'comments') {
$query['orderby'] = 'comment_count';
$query['order'] = 'DESC';
} elseif ($orderby === 'date') {
$query['orderby'] = 'date';
$query['order'] = 'DESC';
} else {
$query['meta_key'] = '_aksrt_views';
$query['orderby'] = 'meta_value_num';
$query['order'] = 'DESC';
}
return $query;
}
echo $args['before_widget'];
echo $args['before_title'] . esc_html($title) . $args['after_title'];
public static function render_items_html(\WP_Query $query, int $start = 0): string {
if (!$query->have_posts()) {
return '';
}
ob_start();
$i = $start;
while ($query->have_posts()) {
$query->the_post();
$i++;
$views = (int) get_post_meta(get_the_ID(), '_aksrt_views', true);
echo '<li class="aksrt-forum-widget__item ajax-item">';
echo '<span class="aksrt-forum-widget__rank">' . esc_html((string) $i) . '</span>';
echo '<a href="' . esc_url(get_permalink()) . '" class="aksrt-forum-widget__link">';
echo esc_html(get_the_title());
echo '</a>';
echo '<span class="aksrt-forum-widget__views"><i class="fas fa-eye"></i> ' . esc_html((string) $views) . '</span>';
echo '</li>';
}
wp_reset_postdata();
return (string) ob_get_clean();
}
$date_query = [];
if ($period === 'week') {
$date_query = [['after' => '7 days ago']];
} elseif ($period === 'month') {
$date_query = [['after' => '30 days ago']];
}
/**
* @return array{html:string,paginate_html:string,has_more:bool,page:int,max_pages:int,total:int}
*/
public static function query_payload(array $instance, int $page = 1): array {
$page = max(1, $page);
$count = max(1, min(20, (int) ($instance['count'] ?? 5)));
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'ajax'));
$q = new \WP_Query(self::build_query_args($instance, $page));
$items = self::render_items_html($q, ($page - 1) * $count);
$html = $items !== '' ? '<ul class="aksrt-forum-widget__list">' . $items . '</ul>' : '';
if ($html === '' && $page === 1) {
$html = '<p class="aksrt-forum-widget__empty ajax-item">' . esc_html__('暂无话题', 'aksrt') . '</p>';
}
$max_pages = max(1, (int) $q->max_num_pages);
$total = (int) $q->found_posts;
$has_more = $page < $max_pages;
$paginate_html = '';
if ($paginate === 'ajax' && $has_more) {
$next = add_query_arg([
'page' => $page + 1,
'count' => $count,
'period' => sanitize_key((string) ($instance['period'] ?? 'week')),
'orderby' => sanitize_key((string) ($instance['orderby'] ?? 'views')),
'paginate' => 'ajax',
], rest_url('aksrt/v1/widgets/forum-topics'));
$paginate_html = function_exists('aksrt_get_ajax_next_paginate')
? aksrt_get_ajax_next_paginate($total, $page, $count, $next)
: '<div class="text-center theme-pagination ajax-pag"><div class="next-page ajax-next"><a href="' . esc_url($next) . '">' . esc_html__('加载更多', 'aksrt') . '</a></div></div>';
} else {
$paginate_html = '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
}
return compact('html', 'paginate_html', 'has_more', 'page', 'max_pages', 'total');
}
$query = new \WP_Query([
'post_type' => 'aksrt_forum_topic',
'posts_per_page' => $count,
'post_status' => 'publish',
'meta_key' => '_aksrt_views',
'orderby' => 'meta_value_num',
'order' => 'DESC',
'date_query' => $date_query,
]);
public function widget($args, $instance): void {
$title = !empty($instance['title']) ? $instance['title'] : __('热门话题', 'aksrt');
$instance['paginate'] = sanitize_key((string) ($instance['paginate'] ?? 'ajax'));
$payload = self::query_payload($instance, 1);
if ($query->have_posts()) {
echo '<ul class="aksrt-forum-widget__list">';
$i = 0;
while ($query->have_posts()) {
$query->the_post();
$i++;
$views = (int) get_post_meta(get_the_ID(), '_aksrt_views', true);
echo '<li class="aksrt-forum-widget__item">';
echo '<span class="aksrt-forum-widget__rank">' . $i . '</span>';
echo '<a href="' . esc_url(get_permalink()) . '" class="aksrt-forum-widget__link">';
echo esc_html(get_the_title());
echo '</a>';
echo '<span class="aksrt-forum-widget__views"><i class="fas fa-eye"></i> ' . $views . '</span>';
echo '</li>';
}
echo '</ul>';
wp_reset_postdata();
} else {
echo '<p class="aksrt-forum-widget__empty">' . esc_html__('暂无话题', 'aksrt') . '</p>';
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$rest = rest_url('aksrt/v1/widgets/forum-topics');
echo '<div class="ajaxpager" data-aksrt-ajaxpager data-aksrt-ajax-url="' . esc_url($rest) . '">';
echo $payload['html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $payload['paginate_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="post_ajax_loader" data-aksrt-ajax-loader data-aksrt-ajax-loader-hide hidden></div>';
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo $args['after_widget'];
}
public function form($instance): void {
$title = !empty($instance['title']) ? $instance['title'] : __('热门话题', 'aksrt');
$count = !empty($instance['count']) ? (int) $instance['count'] : 5;
$period = !empty($instance['period']) ? $instance['period'] : 'week';
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('数量:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" value="<?php echo esc_attr((string) $count); ?>" min="1" max="20">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('period')); ?>"><?php esc_html_e('时间范围:', 'aksrt'); ?></label>
<select id="<?php echo esc_attr($this->get_field_id('period')); ?>" name="<?php echo esc_attr($this->get_field_name('period')); ?>">
<option value="all" <?php selected($period, 'all'); ?>><?php esc_html_e('全部', 'aksrt'); ?></option>
<option value="week" <?php selected($period, 'week'); ?>><?php esc_html_e('最近一周', 'aksrt'); ?></option>
<option value="month" <?php selected($period, 'month'); ?>><?php esc_html_e('最近一月', 'aksrt'); ?></option>
</select>
</p>
<p>
<label><?php esc_html_e('分页', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('paginate')); ?>">
<option value="ajax" <?php selected(($instance['paginate'] ?? 'ajax'), 'ajax'); ?>><?php esc_html_e('AJAX', 'aksrt'); ?></option>
<option value="none" <?php selected(($instance['paginate'] ?? ''), 'none'); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
</select>
</label>
</p>
<?php
}
public function form($instance): void {
$title = !empty($instance['title']) ? $instance['title'] : __('热门话题', 'aksrt');
$count = !empty($instance['count']) ? (int) $instance['count'] : 5;
$period = !empty($instance['period']) ? $instance['period'] : 'week';
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('数量:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" value="<?php echo esc_attr($count); ?>" min="1" max="20">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('period')); ?>"><?php esc_html_e('时间范围:', 'aksrt'); ?></label>
<select id="<?php echo esc_attr($this->get_field_id('period')); ?>" name="<?php echo esc_attr($this->get_field_name('period')); ?>">
<option value="all" <?php selected($period, 'all'); ?>><?php esc_html_e('全部', 'aksrt'); ?></option>
<option value="week" <?php selected($period, 'week'); ?>><?php esc_html_e('最近一周', 'aksrt'); ?></option>
<option value="month" <?php selected($period, 'month'); ?>><?php esc_html_e('最近一月', 'aksrt'); ?></option>
</select>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
$instance = [];
$instance['title'] = sanitize_text_field($new_instance['title'] ?? '');
$instance['count'] = max(1, min(20, (int) ($new_instance['count'] ?? 5)));
$instance['period'] = in_array($new_instance['period'] ?? '', ['all', 'week', 'month'], true) ? $new_instance['period'] : 'week';
return $instance;
}
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'count' => max(1, min(20, (int) ($new_instance['count'] ?? 5))),
'period' => in_array($new_instance['period'] ?? '', ['all', 'week', 'month'], true) ? $new_instance['period'] : 'week',
'paginate' => sanitize_key((string) ($new_instance['paginate'] ?? 'ajax')) === 'none' ? 'none' : 'ajax',
];
}
}
+54 -64
View File
@@ -1,5 +1,7 @@
<?php
/**
* Forum recent topics — thin wrapper over hot-topics payload with date order.
*
* @package AKSRT
*/
@@ -7,74 +9,62 @@ namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
/**
* Forum Recent Topics Widget
*/
class WidgetForumRecentTopics extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_forum_recent_topics',
__('论坛 - 最新话题', 'aksrt'),
['description' => __('显示论坛最新话题列表。', 'aksrt')]
);
}
public function __construct() {
parent::__construct(
'aksrt_forum_recent_topics',
__('论坛 - 最新话题', 'aksrt'),
['description' => __('显示论坛最新话题列表。', 'aksrt')]
);
}
public function widget($args, $instance): void {
$title = !empty($instance['title']) ? $instance['title'] : __('最新话题', 'aksrt');
$count = !empty($instance['count']) ? max(1, min(20, (int) $instance['count'])) : 5;
public function widget($args, $instance): void {
$title = !empty($instance['title']) ? $instance['title'] : __('最新话题', 'aksrt');
$instance['orderby'] = 'date';
$instance['period'] = 'all';
$instance['paginate'] = sanitize_key((string) ($instance['paginate'] ?? 'ajax'));
$payload = WidgetForumHotTopics::query_payload($instance, 1);
echo $args['before_widget'];
echo $args['before_title'] . esc_html($title) . $args['after_title'];
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$rest = rest_url('aksrt/v1/widgets/forum-topics');
echo '<div class="ajaxpager" data-aksrt-ajaxpager data-aksrt-ajax-url="' . esc_url($rest) . '">';
echo $payload['html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $payload['paginate_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="post_ajax_loader" data-aksrt-ajax-loader data-aksrt-ajax-loader-hide hidden></div>';
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$query = new \WP_Query([
'post_type' => 'aksrt_forum_topic',
'posts_per_page' => $count,
'post_status' => 'publish',
'orderby' => 'date',
'order' => 'DESC',
]);
public function form($instance): void {
$title = !empty($instance['title']) ? $instance['title'] : __('最新话题', 'aksrt');
$count = !empty($instance['count']) ? (int) $instance['count'] : 5;
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('数量:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" value="<?php echo esc_attr((string) $count); ?>" min="1" max="20">
</p>
<p>
<label><?php esc_html_e('分页', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('paginate')); ?>">
<option value="ajax" <?php selected(($instance['paginate'] ?? 'ajax'), 'ajax'); ?>><?php esc_html_e('AJAX', 'aksrt'); ?></option>
<option value="none" <?php selected(($instance['paginate'] ?? ''), 'none'); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
</select>
</label>
</p>
<?php
}
if ($query->have_posts()) {
echo '<ul class="aksrt-forum-widget__list">';
while ($query->have_posts()) {
$query->the_post();
$author = get_the_author_meta('display_name');
echo '<li class="aksrt-forum-widget__item">';
echo '<a href="' . esc_url(get_permalink()) . '" class="aksrt-forum-widget__link">';
echo esc_html(get_the_title());
echo '</a>';
echo '<span class="aksrt-forum-widget__author">' . esc_html($author) . '</span>';
echo '</li>';
}
echo '</ul>';
wp_reset_postdata();
} else {
echo '<p class="aksrt-forum-widget__empty">' . esc_html__('暂无话题', 'aksrt') . '</p>';
}
echo $args['after_widget'];
}
public function form($instance): void {
$title = !empty($instance['title']) ? $instance['title'] : __('最新话题', 'aksrt');
$count = !empty($instance['count']) ? (int) $instance['count'] : 5;
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('数量:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" value="<?php echo esc_attr($count); ?>" min="1" max="20">
</p>
<?php
}
public function update($new_instance, $old_instance): array {
$instance = [];
$instance['title'] = sanitize_text_field($new_instance['title'] ?? '');
$instance['count'] = max(1, min(20, (int) ($new_instance['count'] ?? 5)));
return $instance;
}
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'count' => max(1, min(20, (int) ($new_instance['count'] ?? 5))),
'paginate' => sanitize_key((string) ($new_instance['paginate'] ?? 'ajax')) === 'none' ? 'none' : 'ajax',
];
}
}
+51 -38
View File
@@ -1,6 +1,6 @@
<?php
/**
* Forum topic tags / topic taxonomy list (zibll zib_bbs_widget_ui_topic_lists).
* Forum topic tags list (zibll topic_lists + remote HTML).
*
* @package AKSRT
*/
@@ -22,52 +22,60 @@ class WidgetForumTopicTerms extends \WP_Widget {
);
}
public function widget($args, $instance): void {
/** @return array<int,int> */
public static function parse_ids_static(string $raw): array {
if ($raw === '') {
return [];
}
$ids = [];
foreach (preg_split('/[\s,]+/', $raw) ?: [] as $p) {
$id = (int) $p;
if ($id > 0) {
$ids[] = $id;
}
}
return array_values(array_unique($ids));
}
public static function render_html(array $instance): string {
$tax = taxonomy_exists('aksrt_forum_tag') ? 'aksrt_forum_tag' : (taxonomy_exists('aksrt_forum_topic_cat') ? 'aksrt_forum_topic_cat' : '');
if ($tax === '') {
return;
return '';
}
$number = max(1, min(40, (int) ($instance['number'] ?? 12)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'count'));
$hide_empty = !isset($instance['hide_empty']) || !empty($instance['hide_empty']);
$include = $this->parse_ids((string) ($instance['include'] ?? ''));
$include = self::parse_ids_static((string) ($instance['include'] ?? ''));
$style = sanitize_key((string) ($instance['style'] ?? 'mini'));
$term_args = [
'taxonomy' => $tax,
'number' => $number,
'hide_empty' => $hide_empty,
'hide_empty' => (bool) $hide_empty,
];
if ($include) {
$term_args['include'] = $include;
$term_args['orderby'] = 'include';
} elseif ($orderby === 'name') {
$term_args['orderby'] = 'name';
$term_args['order'] = 'ASC';
} elseif ($orderby === 'views') {
$term_args['meta_key'] = '_aksrt_views';
$term_args['orderby'] = 'meta_value_num';
$term_args['order'] = 'DESC';
} else {
if ($orderby === 'name') {
$term_args['orderby'] = 'name';
$term_args['order'] = 'ASC';
} elseif ($orderby === 'views') {
$term_args['meta_key'] = '_aksrt_views';
$term_args['orderby'] = 'meta_value_num';
$term_args['order'] = 'DESC';
} else {
$term_args['orderby'] = 'count';
$term_args['order'] = 'DESC';
}
$term_args['orderby'] = 'count';
$term_args['order'] = 'DESC';
}
$terms = get_terms($term_args);
if (is_wp_error($terms) || !$terms) {
return;
return function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无话题', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无话题', 'aksrt') . '</p>';
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$list_cls = $style === 'card' ? 'aksrt-ftag-list is-card' : 'aksrt-ftag-list is-mini';
ob_start();
$list_cls = $style === 'card' ? 'aksrt-ftag-list is-card ajax-item' : 'aksrt-ftag-list is-mini ajax-item';
echo '<div class="' . esc_attr($list_cls) . '">';
foreach ($terms as $term) {
$link = get_term_link($term);
@@ -84,23 +92,28 @@ class WidgetForumTopicTerms extends \WP_Widget {
echo '</a>';
}
echo '</div>';
return (string) ob_get_clean();
}
public function widget($args, $instance): void {
$html = self::render_html($instance);
if ($html === '') {
return;
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
// IAS-style wrap for remote refresh.
echo '<div class="zib-widget padding-6 ajaxpager" data-aksrt-ajaxpager>';
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/** @return array<int,int> */
private function parse_ids(string $raw): array {
if ($raw === '') {
return [];
}
$ids = [];
foreach (preg_split('/[\s,]+/', $raw) ?: [] as $p) {
$id = (int) $p;
if ($id > 0) {
$ids[] = $id;
}
}
return array_values(array_unique($ids));
return self::parse_ids_static($raw);
}
public function form($instance): void {
+28 -17
View File
@@ -1,6 +1,6 @@
<?php
/**
* Horizontal one-line posts scroller (zibll widget_ui_oneline_posts lite).
* Horizontal one-line posts scroller (zibll oneline + remote HTML).
*
* @package AKSRT
*/
@@ -22,16 +22,11 @@ class WidgetOnelinePosts extends \WP_Widget {
);
}
public function widget($args, $instance): void {
public static function build_query_args(array $instance): array {
$count = max(1, min(20, (int) ($instance['count'] ?? 8)));
$style = sanitize_key((string) ($instance['style'] ?? 'card'));
if (!in_array($style, ['card', 'mini', 'list'], true)) {
$style = 'card';
}
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
$cat = (int) ($instance['cat'] ?? 0);
$limit_day = max(0, (int) ($instance['limit_day'] ?? 0));
$query = [
'post_type' => 'post',
'post_status' => 'publish',
@@ -62,20 +57,23 @@ class WidgetOnelinePosts extends \WP_Widget {
$query['orderby'] = 'date';
$query['order'] = 'DESC';
}
return $query;
}
$q = new \WP_Query($query);
public static function render_html(array $instance): string {
$style = sanitize_key((string) ($instance['style'] ?? 'card'));
if (!in_array($style, ['card', 'mini', 'list'], true)) {
$style = 'card';
}
$q = new \WP_Query(self::build_query_args($instance));
if (!$q->have_posts()) {
wp_reset_postdata();
return;
return function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无文章', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无文章', 'aksrt') . '</p>';
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '<div class="aksrt-oneline style-' . esc_attr($style) . '"><div class="aksrt-oneline__track">';
ob_start();
echo '<div class="aksrt-oneline style-' . esc_attr($style) . ' ajax-item"><div class="aksrt-oneline__track">';
while ($q->have_posts()) {
$q->the_post();
$thumb = get_the_post_thumbnail_url(get_the_ID(), $style === 'card' ? 'medium' : 'thumbnail');
@@ -94,7 +92,20 @@ class WidgetOnelinePosts extends \WP_Widget {
}
echo '</div></div>';
wp_reset_postdata();
return (string) ob_get_clean();
}
public function widget($args, $instance): void {
$html = self::render_html($instance);
if ($html === '') {
return;
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
+27 -18
View File
@@ -1,5 +1,7 @@
<?php
/**
* Horizontal swiper product widget — uses shared product list payload.
*
* @package AKSRT
*/
@@ -7,26 +9,33 @@ namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
/**
* Horizontal swiper product widget — equivalent to zibll `zib_shop_widget_ui_oneline_product_lists`.
*/
class WidgetProductOneline extends WidgetProductList {
public function __construct() {
\WP_Widget::__construct(
'aksrt_product_oneline',
__('横滑商品', 'aksrt'),
['description' => __('AKSRT 单行横滑商品 Swiper 组件。', 'aksrt')]
);
}
public function __construct() {
\WP_Widget::__construct(
'aksrt_product_oneline',
__('横滑商品', 'aksrt'),
['description' => __('AKSRT 单行横滑商品列表(AJAX 同源)。', 'aksrt')]
);
}
public function widget($args, $instance): void {
$instance['style'] = 'oneline';
parent::widget($args, $instance);
}
public function widget($args, $instance): void {
$instance['style'] = 'oneline';
// Prefer no paginate for horizontal scroller (zibll loads fixed count).
if (!isset($instance['paginate'])) {
$instance['paginate'] = 'none';
}
parent::widget($args, $instance);
}
public function form($instance): void {
$instance = wp_parse_args($instance, ['style' => 'oneline']);
parent::form($instance);
}
public function form($instance): void {
$instance = wp_parse_args($instance, ['style' => 'oneline', 'paginate' => 'none']);
parent::form($instance);
}
public function update($new_instance, $old_instance): array {
$out = parent::update($new_instance, $old_instance);
$out['style'] = 'oneline';
return $out;
}
}
+47 -43
View File
@@ -1,6 +1,6 @@
<?php
/**
* Registered user list (zibll widget_ui_user_lists).
* Registered user list (zibll widget_ui_user_lists + remote HTML).
*
* @package AKSRT
*/
@@ -22,20 +22,34 @@ class WidgetUserLists extends \WP_Widget {
);
}
public function widget($args, $instance): void {
/** @return array<int,int> */
public static function parse_ids_static(string $raw): array {
if ($raw === '') {
return [];
}
$ids = [];
foreach (preg_split('/[\s,]+/', $raw) ?: [] as $p) {
$id = (int) $p;
if ($id > 0) {
$ids[] = $id;
}
}
return array_values(array_unique($ids));
}
public static function render_html(array $instance): string {
$number = max(1, min(50, (int) ($instance['number'] ?? 8)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'user_registered'));
$order = strtoupper((string) ($instance['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC';
$include = $this->parse_ids((string) ($instance['include'] ?? ''));
$exclude = $this->parse_ids((string) ($instance['exclude'] ?? ''));
$include = self::parse_ids_static((string) ($instance['include'] ?? ''));
$exclude = self::parse_ids_static((string) ($instance['exclude'] ?? ''));
$hide_box = !empty($instance['hide_box']);
$query = [
'number' => $number,
'order' => $order,
'fields' => ['ID', 'display_name', 'user_nicename'],
'number' => $number,
'order' => $order,
'fields' => ['ID', 'display_name', 'user_nicename'],
];
if ($include) {
$query['include'] = $include;
$query['orderby'] = 'include';
@@ -52,7 +66,6 @@ class WidgetUserLists extends \WP_Widget {
$query['orderby'] = 'meta_value';
break;
case 'followed-user-count':
// Fallback: registered date; fan ranking needs denormalized meta when available.
$query['meta_key'] = 'aksrt_follower_count';
$query['orderby'] = 'meta_value_num';
break;
@@ -66,11 +79,12 @@ class WidgetUserLists extends \WP_Widget {
$users = get_users($query);
if (!$users) {
return;
return function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无用户', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无用户', 'aksrt') . '</p>';
}
// If follower meta empty for all, re-query by registered when fan order requested.
if ($orderby === 'followed-user-count' && !$include) {
if ($orderby === 'followed-user-count' && !$include && class_exists('\AKSRT\User\Follow')) {
$has_meta = false;
foreach ($users as $u) {
if ((int) get_user_meta($u->ID, 'aksrt_follower_count', true) > 0) {
@@ -78,8 +92,7 @@ class WidgetUserLists extends \WP_Widget {
break;
}
}
if (!$has_meta && class_exists('\AKSRT\User\Follow')) {
// Rank by live follower counts (small N only).
if (!$has_meta) {
usort($users, static function ($a, $b) {
$fa = \AKSRT\User\Follow::get_instance()->get_follower_count((int) $a->ID);
$fb = \AKSRT\User\Follow::get_instance()->get_follower_count((int) $b->ID);
@@ -88,13 +101,8 @@ class WidgetUserLists extends \WP_Widget {
}
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$wrap = $hide_box ? 'aksrt-user-lists is-bare' : 'aksrt-user-lists';
ob_start();
$wrap = $hide_box ? 'aksrt-user-lists is-bare ajax-item' : 'aksrt-user-lists ajax-item';
echo '<div class="' . esc_attr($wrap) . '">';
foreach ($users as $user) {
$uid = (int) $user->ID;
@@ -111,26 +119,22 @@ class WidgetUserLists extends \WP_Widget {
echo '</a>';
}
echo '</div>';
return (string) ob_get_clean();
}
public function widget($args, $instance): void {
$html = self::render_html($instance);
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @return array<int, int>
*/
private function parse_ids(string $raw): array {
if ($raw === '') {
return [];
}
$parts = preg_split('/[\s,]+/', $raw) ?: [];
$ids = [];
foreach ($parts as $p) {
$id = (int) $p;
if ($id > 0) {
$ids[] = $id;
}
}
return array_values(array_unique($ids));
return self::parse_ids_static($raw);
}
public function form($instance): void {
@@ -187,13 +191,13 @@ class WidgetUserLists extends \WP_Widget {
$orderby = 'user_registered';
}
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'number' => max(1, min(50, (int) ($new_instance['number'] ?? 8))),
'include' => sanitize_text_field((string) ($new_instance['include'] ?? '')),
'exclude' => sanitize_text_field((string) ($new_instance['exclude'] ?? '')),
'orderby' => $orderby,
'order' => strtoupper((string) ($new_instance['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC',
'hide_box'=> !empty($new_instance['hide_box']) ? 1 : 0,
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'number' => max(1, min(50, (int) ($new_instance['number'] ?? 8))),
'include' => sanitize_text_field((string) ($new_instance['include'] ?? '')),
'exclude' => sanitize_text_field((string) ($new_instance['exclude'] ?? '')),
'orderby' => $orderby,
'order' => strtoupper((string) ($new_instance['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC',
'hide_box' => !empty($new_instance['hide_box']) ? 1 : 0,
];
}
}