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)); // Zibll `order` parity: asc/desc. Default desc. $order = strtoupper(sanitize_key((string) ($instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC'; $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'] = $order; break; case 'comments': $query['orderby'] = 'comment_count'; $query['order'] = $order; break; case 'likes': $query['meta_key'] = '_aksrt_like_count'; $query['orderby'] = 'meta_value_num'; $query['order'] = $order; break; case 'favorites': $query['meta_key'] = '_aksrt_favorite_count'; $query['orderby'] = 'meta_value_num'; $query['order'] = $order; break; case 'downloads': $query['meta_key'] = '_aksrt_res_download_count'; $query['orderby'] = 'meta_value_num'; $query['order'] = $order; break; case 'rand': $query['orderby'] = 'rand'; break; case 'modified': $query['orderby'] = 'modified'; $query['order'] = $order; break; default: $query['orderby'] = 'date'; $query['order'] = $order; } // 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 ''; echo '
'; if ($thumb) { echo ''; } echo '
'; echo '
'; echo '
' . esc_html(get_the_title()) . '
'; if ($show_meta) { echo '
' . esc_html(get_the_date()) . ''; if ($views > 0) { echo ' ' . esc_html((string) $views) . ''; } echo '
'; } echo '
'; } else { echo ''; if ($show_number) { echo '' . esc_html((string) $i) . ''; } if ($show_thumb) { echo ''; if ($thumb) { echo ''; } echo ''; } echo ''; echo '' . esc_html(get_the_title()) . ''; if ($show_meta) { echo '' . esc_html(get_the_date()) . ''; } echo ''; } } 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')) : '

' . esc_html__('暂无文章', 'aksrt') . '

'; } $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')), 'order' => strtoupper(sanitize_key((string) ($instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC', 'cat' => (int) ($instance['cat'] ?? 0), 'limit_day' => (int) ($instance['limit_day'] ?? 0), '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, 'load_mode' => sanitize_key((string) ($instance['load_mode'] ?? 'detail')) === 'ajax' ? 'ajax' : 'detail', ], 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) : '
' . esc_html__('加载更多', 'aksrt') . '
'; } 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) : '
'; } else { $paginate_html = '
'; } 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')); if (!in_array($paginate, ['none', 'ajax', 'number'], true)) { $paginate = 'none'; } $load_mode = sanitize_key((string) ($instance['load_mode'] ?? 'detail')); if (!in_array($load_mode, ['detail', 'ajax'], true)) { $load_mode = 'detail'; } $instance['style'] = $style; $instance['paginate'] = $paginate; 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 } $count = max(1, min(30, (int) ($instance['count'] ?? 6))); $rest = rest_url('aksrt/v1/widgets/posts'); $qs = http_build_query(array_filter([ 'page' => 1, 'count' => $count, 'orderby' => sanitize_key((string) ($instance['orderby'] ?? 'date')), 'order' => strtoupper(sanitize_key((string) ($instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC', 'cat' => (int) ($instance['cat'] ?? 0), 'limit_day' => (int) ($instance['limit_day'] ?? 0), 'pay_type' => sanitize_key((string) ($instance['pay_type'] ?? '')), 'style' => $style, '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, 'load_mode' => sanitize_key((string) ($instance['load_mode'] ?? 'detail')) === 'ajax' ? 'ajax' : 'detail', ], static function ($v) { return $v !== '' && $v !== null; })); $wrap_cls = 'aksrt-main-posts style-' . $style . ' ajaxpager'; if ($style === 'mini') { $wrap_cls .= ' zib-widget posts-mini-lists'; } echo '
'; if ($load_mode === 'ajax') { // Skeleton first paint (zibll load_mode=ajax). echo '
'; for ($si = 0; $si < $count; $si++) { if ($style === 'card') { echo '
'; } else { echo '
'; } } echo '
'; } else { $payload = self::query_payload($instance, 1); echo $payload['html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped echo $payload['paginate_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } echo ''; echo '
'; echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } public function form($instance): void { ?>



sanitize_text_field((string) ($new_instance['title'] ?? '')), 'count' => max(1, min(30, (int) ($new_instance['count'] ?? 6))), 'style' => $style, 'orderby' => $orderby, 'order' => strtoupper(sanitize_key((string) ($new_instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC', 'cat' => max(0, (int) ($new_instance['cat'] ?? 0)), 'limit_day' => max(0, (int) ($new_instance['limit_day'] ?? 0)), 'pay_type' => sanitize_key((string) ($new_instance['pay_type'] ?? '')), 'paginate' => $paginate, 'load_mode' => sanitize_key((string) ($new_instance['load_mode'] ?? 'detail')) === 'ajax' ? 'ajax' : 'detail', '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, ]; } }