Files
wordpress-theme-aksrt/src/AKSRT/Widgets/WidgetMainPosts.php
T
kate522 ea3c987695 feat(widgets): number paginate + main/tab query depth
Add shared aksrt_get_ajax_number_paginate, MainPosts topics/pay_type/
favorites filters and number pager, TabPosts style/paginate/skeleton
payload, REST wiring, product number AJAX parity.
2026-07-21 12:16:57 +08:00

428 lines
18 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Main posts list (zibll zib_widget_ui_main_post + ajax pager).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetMainPosts extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_main_posts',
esc_html__('AKSRT 文章列表', 'aksrt'),
[
'description' => esc_html__('卡片/迷你/列表样式的文章模块(支持 AJAX)。', 'aksrt'),
'classname' => 'widget_aksrt_main_posts',
]
);
}
/**
* @param array $instance
* @param int $paged
* @return array
*/
public static function build_query_args(array $instance, int $paged = 1): array {
$count = max(1, min(30, (int) ($instance['count'] ?? 6)));
$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',
'posts_per_page' => $count,
'paged' => max(1, $paged),
'ignore_sticky_posts' => true,
];
if ($cat > 0) {
$query['cat'] = $cat;
}
if ($limit_day > 0) {
$query['date_query'] = [['after' => $limit_day . ' days ago']];
}
switch ($orderby) {
case 'views':
$query['meta_key'] = 'aksrt_post_views';
$query['orderby'] = 'meta_value_num';
$query['order'] = 'DESC';
break;
case 'comments':
$query['orderby'] = 'comment_count';
$query['order'] = 'DESC';
break;
case 'likes':
$query['meta_key'] = '_aksrt_like_count';
$query['orderby'] = 'meta_value_num';
$query['order'] = 'DESC';
break;
case 'favorites':
$query['meta_key'] = '_aksrt_favorite_count';
$query['orderby'] = 'meta_value_num';
$query['order'] = 'DESC';
break;
case 'downloads':
$query['meta_key'] = '_aksrt_res_download_count';
$query['orderby'] = 'meta_value_num';
$query['order'] = 'DESC';
break;
case 'rand':
$query['orderby'] = 'rand';
break;
case 'modified':
$query['orderby'] = 'modified';
$query['order'] = 'DESC';
break;
default:
$query['orderby'] = 'date';
$query['order'] = 'DESC';
}
// Topics taxonomy (CSV term IDs).
$topics_raw = trim((string) ($instance['topics'] ?? ''));
if ($topics_raw !== '' && taxonomy_exists('topics')) {
$topic_ids = array_values(array_filter(array_map('intval', preg_split('/[,,\s]+/', $topics_raw) ?: [])));
if ($topic_ids) {
$query['tax_query'] = [
[
'taxonomy' => 'topics',
'field' => 'term_id',
'terms' => $topic_ids,
],
];
}
}
// Pay type filter (reading/download/gallery/video/vip or any paid).
$pay_type = sanitize_key((string) ($instance['pay_type'] ?? ''));
if ($pay_type !== '' && $pay_type !== '0' && $pay_type !== 'no') {
$mq = isset($query['meta_query']) && is_array($query['meta_query']) ? $query['meta_query'] : [];
if ($pay_type === 'any' || $pay_type === 'paid') {
$mq[] = [
'relation' => 'OR',
[
'key' => '_aksrt_pay_price',
'value' => 0,
'compare' => '>',
'type' => 'NUMERIC',
],
[
'key' => '_aksrt_pay_type',
'value' => ['reading', 'download', 'gallery', 'video', 'vip'],
'compare' => 'IN',
],
[
'key' => '_aksrt_visibility',
'value' => ['pay', 'credits'],
'compare' => 'IN',
],
];
} else {
$mq[] = [
'key' => '_aksrt_pay_type',
'value' => $pay_type,
];
}
if ($mq) {
if (count($mq) > 1) {
$mq['relation'] = 'AND';
}
$query['meta_query'] = $mq;
}
}
return $query;
}
public static function render_items_html(\WP_Query $q, array $instance): string {
if (!$q->have_posts()) {
return '';
}
$style = sanitize_key((string) ($instance['style'] ?? 'list'));
if (!in_array($style, ['list', 'card', 'mini'], true)) {
$style = 'list';
}
$show_thumb = !isset($instance['show_thumb']) || !empty($instance['show_thumb']);
$show_meta = !isset($instance['show_meta']) || !empty($instance['show_meta']);
$show_number = !empty($instance['show_number']);
$start = max(0, (int) ($instance['_start_index'] ?? 0));
ob_start();
// Outer wrapper only on page 1 full render handled by caller for ajax append items.
$mode = $style === 'card' ? 'card' : ($style === 'mini' ? 'mini' : 'list');
if (function_exists('aksrt_render_post')) {
$html = '';
$i = $start;
while ($q->have_posts()) {
$q->the_post();
$i++;
$html .= aksrt_render_post([
'mode' => $mode,
'context' => 'widget',
'show_thumb' => $show_thumb,
'show_meta' => $show_meta,
'show_excerpt' => $mode === 'list',
'show_number' => $show_number && $mode === 'mini',
'number_offset' => $show_number ? $i : 0,
'extra_class' => 'ajax-item',
]);
}
wp_reset_postdata();
return $html;
}
$placeholder = get_template_directory_uri() . '/assets/img/placeholder.svg';
$i = $start;
while ($q->have_posts()) {
$q->the_post();
$i++;
$thumb = $show_thumb ? get_the_post_thumbnail_url(get_the_ID(), $style === 'card' ? 'medium' : 'thumbnail') : '';
$views = (int) get_post_meta(get_the_ID(), 'aksrt_post_views', true);
if ($style === 'card') {
echo '<a class="aksrt-main-posts__card ajax-item" href="' . esc_url(get_permalink()) . '">';
echo '<div class="aksrt-main-posts__thumb' . ($thumb ? '' : ' is-empty') . '">';
if ($thumb) {
echo '<img class="aksrt-lazy lazyload" data-src="' . esc_url($thumb) . '" src="' . esc_url($placeholder) . '" alt="" loading="lazy" />';
}
echo '</div>';
echo '<div class="aksrt-main-posts__body">';
echo '<div class="aksrt-main-posts__title">' . esc_html(get_the_title()) . '</div>';
if ($show_meta) {
echo '<div class="aksrt-main-posts__meta"><span>' . esc_html(get_the_date()) . '</span>';
if ($views > 0) {
echo '<span><i class="fas fa-eye"></i> ' . esc_html((string) $views) . '</span>';
}
echo '</div>';
}
echo '</div></a>';
} else {
echo '<a class="aksrt-main-posts__row ajax-item" href="' . esc_url(get_permalink()) . '">';
if ($show_number) {
echo '<span class="aksrt-main-posts__num">' . esc_html((string) $i) . '</span>';
}
if ($show_thumb) {
echo '<span class="aksrt-main-posts__thumb mini' . ($thumb ? '' : ' is-empty') . '">';
if ($thumb) {
echo '<img class="aksrt-lazy lazyload" data-src="' . esc_url($thumb) . '" src="' . esc_url($placeholder) . '" alt="" loading="lazy" />';
}
echo '</span>';
}
echo '<span class="aksrt-main-posts__body">';
echo '<span class="aksrt-main-posts__title">' . esc_html(get_the_title()) . '</span>';
if ($show_meta) {
echo '<span class="aksrt-main-posts__meta">' . esc_html(get_the_date()) . '</span>';
}
echo '</span></a>';
}
}
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);
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'none'));
$count = max(1, min(30, (int) ($instance['count'] ?? 6)));
$instance['_start_index'] = ($page - 1) * $count;
$q = new \WP_Query(self::build_query_args($instance, $page));
$html = self::render_items_html($q, $instance);
if ($html === '' && $page === 1) {
$html = function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无文章', 'aksrt'))
: '<p class="aksrt-main-posts__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;
$base_qs = array_filter([
'count' => $count,
'orderby' => sanitize_key((string) ($instance['orderby'] ?? 'date')),
'cat' => (int) ($instance['cat'] ?? 0),
'limit_day' => (int) ($instance['limit_day'] ?? 0),
'topics' => (string) ($instance['topics'] ?? ''),
'pay_type' => sanitize_key((string) ($instance['pay_type'] ?? '')),
'style' => sanitize_key((string) ($instance['style'] ?? 'list')),
'show_thumb' => !isset($instance['show_thumb']) || !empty($instance['show_thumb']) ? 1 : 0,
'show_meta' => !isset($instance['show_meta']) || !empty($instance['show_meta']) ? 1 : 0,
'show_number' => !empty($instance['show_number']) ? 1 : 0,
'paginate' => $paginate,
], static function ($v) {
return $v !== '' && $v !== null;
});
$rest_base = add_query_arg($base_qs, rest_url('aksrt/v1/widgets/posts'));
$paginate_html = '';
if ($paginate === 'ajax' && $has_more) {
$next = add_query_arg(['page' => $page + 1], $rest_base);
$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>';
} elseif ($paginate === 'number' && $max_pages > 1) {
$paginate_html = function_exists('aksrt_get_ajax_number_paginate')
? aksrt_get_ajax_number_paginate($total, $page, $count, $rest_base)
: '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
} else {
$paginate_html = '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
}
return [
'html' => $html,
'paginate_html' => $paginate_html,
'has_more' => $has_more,
'page' => $page,
'max_pages' => $max_pages,
'total' => $total,
];
}
public function widget($args, $instance): void {
$style = sanitize_key((string) ($instance['style'] ?? 'list'));
if (!in_array($style, ['list', 'card', 'mini'], true)) {
$style = 'list';
}
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'none'));
$instance['style'] = $style;
$instance['paginate'] = $paginate;
$payload = self::query_payload($instance, 1);
if ($payload['html'] === '' && empty($payload['total'])) {
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
}
$rest = rest_url('aksrt/v1/widgets/posts');
$wrap_cls = 'aksrt-main-posts style-' . $style . ' ajaxpager';
if ($style === 'mini') {
$wrap_cls .= ' zib-widget posts-mini-lists';
}
echo '<div class="' . esc_attr($wrap_cls) . '" 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 {
?>
<p>
<label><?php esc_html_e('标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('数量', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="30" name="<?php echo esc_attr($this->get_field_name('count')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['count'] ?? 6))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('样式', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('style')); ?>">
<option value="list" <?php selected(($instance['style'] ?? 'list'), 'list'); ?>><?php esc_html_e('列表', 'aksrt'); ?></option>
<option value="mini" <?php selected(($instance['style'] ?? ''), 'mini'); ?>><?php esc_html_e('迷你', 'aksrt'); ?></option>
<option value="card" <?php selected(($instance['style'] ?? ''), 'card'); ?>><?php esc_html_e('卡片', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('排序', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
<option value="date" <?php selected(($instance['orderby'] ?? 'date'), 'date'); ?>><?php esc_html_e('时间', 'aksrt'); ?></option>
<option value="modified" <?php selected(($instance['orderby'] ?? ''), 'modified'); ?>><?php esc_html_e('更新', 'aksrt'); ?></option>
<option value="views" <?php selected(($instance['orderby'] ?? ''), 'views'); ?>><?php esc_html_e('浏览', 'aksrt'); ?></option>
<option value="comments" <?php selected(($instance['orderby'] ?? ''), 'comments'); ?>><?php esc_html_e('评论', 'aksrt'); ?></option>
<option value="likes" <?php selected(($instance['orderby'] ?? ''), 'likes'); ?>><?php esc_html_e('点赞', 'aksrt'); ?></option>
<option value=\"favorites\" <?php selected(($instance['orderby'] ?? ''), 'favorites'); ?>><?php esc_html_e('收藏', 'aksrt'); ?></option>
<option value=\"downloads\" <?php selected(($instance['orderby'] ?? ''), 'downloads'); ?>><?php esc_html_e('下载', 'aksrt'); ?></option>
<option value="rand" <?php selected(($instance['orderby'] ?? ''), 'rand'); ?>><?php esc_html_e('随机', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('分类 ID(0=全部)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('cat')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['cat'] ?? 0))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('最近 N 天(0=不限)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('limit_day')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['limit_day'] ?? 0))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('分页', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('paginate')); ?>">
<option value="none" <?php selected(($instance['paginate'] ?? 'none'), 'none'); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
<option value="ajax" <?php selected(($instance['paginate'] ?? ''), 'ajax'); ?>><?php esc_html_e('AJAX 加载更多', 'aksrt'); ?></option>
<option value="number" <?php selected(($instance['paginate'] ?? ''), 'number'); ?>><?php esc_html_e('数字分页', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('专题 topics ID(逗号)', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('topics')); ?>" value="<?php echo esc_attr((string) ($instance['topics'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('付费类型', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('pay_type')); ?>">
<option value="" <?php selected(($instance['pay_type'] ?? ''), ''); ?>><?php esc_html_e('不限', 'aksrt'); ?></option>
<option value="paid" <?php selected(($instance['pay_type'] ?? ''), 'paid'); ?>><?php esc_html_e('任意付费', 'aksrt'); ?></option>
<option value="reading" <?php selected(($instance['pay_type'] ?? ''), 'reading'); ?>><?php esc_html_e('付费阅读', 'aksrt'); ?></option>
<option value="download" <?php selected(($instance['pay_type'] ?? ''), 'download'); ?>><?php esc_html_e('付费下载', 'aksrt'); ?></option>
<option value="gallery" <?php selected(($instance['pay_type'] ?? ''), 'gallery'); ?>><?php esc_html_e('付费图库', 'aksrt'); ?></option>
<option value="video" <?php selected(($instance['pay_type'] ?? ''), 'video'); ?>><?php esc_html_e('付费视频', 'aksrt'); ?></option>
<option value="vip" <?php selected(($instance['pay_type'] ?? ''), 'vip'); ?>><?php esc_html_e('会员专属', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_thumb')); ?>" value="1" <?php checked(!isset($instance['show_thumb']) || !empty($instance['show_thumb'])); ?>> <?php esc_html_e('显示缩略图', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_meta')); ?>" value="1" <?php checked(!isset($instance['show_meta']) || !empty($instance['show_meta'])); ?>> <?php esc_html_e('显示 meta', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_number')); ?>" value="1" <?php checked(!empty($instance['show_number'])); ?>> <?php esc_html_e('显示序号', 'aksrt'); ?></label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
$style = sanitize_key((string) ($new_instance['style'] ?? 'list'));
if (!in_array($style, ['list', 'card', 'mini'], true)) {
$style = 'list';
}
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'date'));
if (!in_array($orderby, ['date', 'modified', 'views', 'comments', 'likes', 'favorites', 'downloads', 'rand'], true)) {
$orderby = 'date';
}
$paginate = sanitize_key((string) ($new_instance['paginate'] ?? 'none'));
if (!in_array($paginate, ['none', 'ajax', 'number'], true)) {
$paginate = 'none';
}
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'count' => max(1, min(30, (int) ($new_instance['count'] ?? 6))),
'style' => $style,
'orderby' => $orderby,
'cat' => max(0, (int) ($new_instance['cat'] ?? 0)),
'limit_day' => max(0, (int) ($new_instance['limit_day'] ?? 0)),
'topics' => sanitize_text_field((string) ($new_instance['topics'] ?? '')),
'pay_type' => sanitize_key((string) ($new_instance['pay_type'] ?? '')),
'paginate' => $paginate,
'show_thumb' => !empty($new_instance['show_thumb']) ? 1 : 0,
'show_meta' => !empty($new_instance['show_meta']) ? 1 : 0,
'show_number' => !empty($new_instance['show_number']) ? 1 : 0,
];
}
}