Categories gain hierarchical children, optional thumb/desc; hot posts show orderby-aware metrics with lazy thumbs; tag cloud can scale size.
231 lines
11 KiB
PHP
231 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* Hot posts ranking (zibll hot posts + ajax).
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
|
|
namespace AKSRT\Widgets;
|
|
|
|
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 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;
|
|
}
|
|
|
|
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']);
|
|
$order_by = sanitize_key((string) ($instance['order_by'] ?? 'views'));
|
|
$placeholder = get_template_directory_uri() . '/assets/img/placeholder.svg';
|
|
ob_start();
|
|
$index = $start;
|
|
while ($query->have_posts()) {
|
|
$query->the_post();
|
|
$index++;
|
|
$rank_class = $index <= 3 ? ' rank-top' : '';
|
|
$pid = get_the_ID();
|
|
if ($order_by === 'comments') {
|
|
$metric = (int) get_comments_number($pid);
|
|
$metric_icon = 'fa-comment-o';
|
|
} elseif ($order_by === 'likes') {
|
|
$metric = function_exists('aksrt_get_post_likes') ? (int) aksrt_get_post_likes($pid) : (int) get_post_meta($pid, '_aksrt_like_count', true);
|
|
$metric_icon = 'fa-heart';
|
|
} elseif ($order_by === 'favorites') {
|
|
$metric = (int) get_post_meta($pid, '_aksrt_favorite_count', true);
|
|
$metric_icon = 'fa-star';
|
|
} elseif ($order_by === 'downloads') {
|
|
$metric = (int) get_post_meta($pid, '_aksrt_res_download_count', true);
|
|
$metric_icon = 'fa-download';
|
|
} else {
|
|
$metric = (int) get_post_meta($pid, 'aksrt_post_views', true);
|
|
$metric_icon = 'fa-eye';
|
|
}
|
|
$metric_txt = function_exists('aksrt_format_number') ? aksrt_format_number($metric) : (string) $metric;
|
|
?>
|
|
<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($pid, 'thumbnail');
|
|
?>
|
|
<a class="hot-post-item__thumb<?php echo $thumb ? '' : ' is-empty'; ?>" href="<?php the_permalink(); ?>">
|
|
<?php if ($thumb) : ?><img class="aksrt-lazy lazyload" data-src="<?php echo esc_url($thumb); ?>" src="<?php echo esc_url($placeholder); ?>" 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="fa <?php echo esc_attr($metric_icon); ?>" aria-hidden="true"></i> <?php echo esc_html((string) $metric_txt); ?></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();
|
|
}
|
|
|
|
/**
|
|
* @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');
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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',
|
|
];
|
|
}
|
|
}
|