Files
wordpress-theme-aksrt/src/Widgets/WidgetPaidPosts.php
T
kate522 8c5b1555c9 refactor: flatten src/AKSRT → src (drop theme-identifier dir)
Move all domain folders from src/AKSRT/{Domain} to src/{Domain}; remap
Composer PSR-4 "AKSRT\\": "src/"; update audit script + README + ADR
paths; remove dead tools/build-psr4-map.php (scanned deleted inc/classes).
2026-08-02 22:06:06 +08:00

274 lines
12 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
/**
* Paid posts list (zibll zib_widget_ui_posts_pay + ajax).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
use AKSRT\Widgets\Traits\CommonWidgetFields;
defined('ABSPATH') || exit;
class WidgetPaidPosts extends \WP_Widget {
use CommonWidgetFields;
public function __construct() {
parent::__construct(
'aksrt_paid_posts',
esc_html__('AKSRT 付费内容列表', 'aksrt'),
[
'description' => esc_html__('展示开启付费/积分可见的文章(支持 AJAX)。', 'aksrt'),
'classname' => 'widget_aksrt_paid_posts',
]
);
}
public static function build_query_args(array $instance, int $paged = 1): array {
$count = max(1, min(20, (int) ($instance['count'] ?? 6)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
$query = [
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => $count,
'paged' => max(1, $paged),
'order' => 'DESC',
'meta_query' => [
'relation' => 'OR',
[
'key' => '_aksrt_pay_price',
'value' => 0,
'compare' => '>',
'type' => 'NUMERIC',
],
[
'key' => '_aksrt_credits_cost',
'value' => 0,
'compare' => '>',
'type' => 'NUMERIC',
],
[
'key' => '_aksrt_visibility',
'value' => ['pay', 'credits'],
'compare' => 'IN',
],
],
];
if ($orderby === 'views') {
$query['meta_key'] = 'aksrt_post_views';
$query['orderby'] = 'meta_value_num';
} elseif ($orderby === 'price') {
$query['meta_key'] = '_aksrt_pay_price';
$query['orderby'] = 'meta_value_num';
} else {
$query['orderby'] = 'date';
}
$cat = max(0, (int) ($instance['cat'] ?? 0));
if ($cat > 0) {
$query['cat'] = $cat;
}
$limit_day = max(0, (int) ($instance['limit_day'] ?? 0));
if ($limit_day > 0) {
$query['date_query'] = [['after' => $limit_day . ' days ago']];
}
return $query;
}
public static function render_items_html(\WP_Query $q, string $style = 'mini'): string {
if (!$q->have_posts()) {
return '';
}
$placeholder = get_template_directory_uri() . '/assets/img/placeholder.svg';
ob_start();
while ($q->have_posts()) {
$q->the_post();
$pid = get_the_ID();
$price = (float) get_post_meta($pid, '_aksrt_pay_price', true);
$credits = (int) get_post_meta($pid, '_aksrt_credits_cost', true);
$thumb = get_the_post_thumbnail_url($pid, 'thumbnail');
if ($price > 0) {
$badge = '¥' . number_format($price, 2, '.', '');
} elseif ($credits > 0) {
$badge = sprintf(__('%d 积分', 'aksrt'), $credits);
} else {
$badge = __('付费', 'aksrt');
}
$mode = in_array($style, ['list', 'card', 'mini'], true) ? $style : 'mini';
if (function_exists('aksrt_render_post')) {
$item = aksrt_render_post([
'mode' => $mode,
'context' => 'widget',
'show_thumb' => true,
'show_meta' => true,
'show_excerpt' => $mode === 'list',
'extra_class' => 'ajax-item aksrt-paid-posts__' . $mode,
]);
if ($item !== '') {
echo '<div class="aksrt-paid-posts__item is-' . esc_attr($mode) . ' ajax-item">';
echo $item; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<span class="aksrt-paid-posts__price badge">' . esc_html($badge) . '</span>';
echo '</div>';
continue;
}
}
echo '<li class="aksrt-paid-posts__item ajax-item">';
if ($thumb) {
echo '<a class="aksrt-paid-posts__thumb" href="' . esc_url(get_permalink()) . '"><img class="aksrt-lazy lazyload" data-src="' . esc_url($thumb) . '" src="' . esc_url($placeholder) . '" alt="" loading="lazy" /></a>';
}
echo '<div class="aksrt-paid-posts__body">';
echo '<a class="aksrt-paid-posts__title" href="' . esc_url(get_permalink()) . '">' . esc_html(get_the_title()) . '</a>';
echo '<div class="aksrt-paid-posts__meta"><span class="aksrt-paid-posts__price">' . esc_html($badge) . '</span></div>';
echo '</div></li>';
}
wp_reset_postdata();
return (string) ob_get_clean();
}
public static function query_payload(array $instance, int $page = 1): array {
$page = max(1, $page);
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'ajax'));
$count = max(1, min(20, (int) ($instance['count'] ?? 6)));
$q = new \WP_Query(self::build_query_args($instance, $page));
$style = sanitize_key((string) ($instance['style'] ?? 'mini'));
if (!in_array($style, ['mini', 'list', 'card'], true)) {
$style = 'mini';
}
$items = self::render_items_html($q, $style);
$wrap_tag = (strpos($items, 'posts-mini') !== false || strpos($items, 'aksrt-paid-posts__mini') !== false) ? 'div' : 'ul';
$html = $items !== '' ? '<' . $wrap_tag . ' class="aksrt-paid-posts style-' . esc_attr($style) . '">' . $items . '</' . $wrap_tag . '>' : '';
if ($html === '' && $page === 1) {
$html = function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_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;
$base_qs = array_filter([
'count' => $count,
'orderby' => sanitize_key((string) ($instance['orderby'] ?? 'date')),
'cat' => max(0, (int) ($instance['cat'] ?? 0)),
'limit_day' => max(0, (int) ($instance['limit_day'] ?? 0)),
'style' => $style,
'paginate' => $paginate,
], static function ($v) {
return $v !== '' && $v !== null;
});
$rest_base = add_query_arg($base_qs, rest_url('aksrt/v1/widgets/paid-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 {
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'ajax'));
$instance['paginate'] = $paginate;
$payload = self::query_payload($instance, 1);
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
}
$affix = function_exists('aksrt_widget_affix_attrs') ? aksrt_widget_affix_attrs($instance) : ['class' => '', 'attr' => ''];
$rest = rest_url('aksrt/v1/widgets/paid-posts');
$wrap = 'aksrt-paid-posts-wrap ajaxpager' . $affix['class'];
echo '<div class="' . esc_attr($wrap) . '" data-aksrt-ajaxpager data-aksrt-ajax-url="' . esc_url($rest) . '"' . $affix['attr'] . '>';
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'] ?? __('付费精选', 'aksrt'))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('数量', 'aksrt'); ?>
<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'] ?? 6))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('样式', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('style')); ?>">
<option value="mini" <?php selected(($instance['style'] ?? 'mini'), 'mini'); ?>><?php esc_html_e('迷你', 'aksrt'); ?></option>
<option value="list" <?php selected(($instance['style'] ?? ''), 'list'); ?>><?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="views" <?php selected(($instance['orderby'] ?? ''), 'views'); ?>><?php esc_html_e('浏览', 'aksrt'); ?></option>
<option value="price" <?php selected(($instance['orderby'] ?? ''), 'price'); ?>><?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('paginate')); ?>">
<option value="ajax" <?php selected(($instance['paginate'] ?? 'ajax'), 'ajax'); ?>><?php esc_html_e('AJAX', 'aksrt'); ?></option>
<option value="number" <?php selected(($instance['paginate'] ?? ''), 'number'); ?>><?php esc_html_e('数字分页', 'aksrt'); ?></option>
<option value="none" <?php selected(($instance['paginate'] ?? ''), 'none'); ?>><?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>
<?php $this->render_common_form_section($instance); ?>
<?php
}
public function update($new_instance, $old_instance): array {
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'date'));
if (!in_array($orderby, ['date', 'views', 'price'], true)) {
$orderby = 'date';
}
return $this->update_common_fields($new_instance, $old_instance, [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'count' => max(1, min(20, (int) ($new_instance['count'] ?? 6))),
'orderby' => $orderby,
'style' => in_array(sanitize_key((string) ($new_instance['style'] ?? 'mini')), ['mini', 'list', 'card'], true) ? sanitize_key((string) ($new_instance['style'] ?? 'mini')) : 'mini',
'cat' => max(0, (int) ($new_instance['cat'] ?? 0)),
'limit_day' => max(0, (int) ($new_instance['limit_day'] ?? 0)),
'paginate' => in_array(sanitize_key((string) ($new_instance['paginate'] ?? 'ajax')), ['none', 'ajax', 'number'], true) ? sanitize_key((string) ($new_instance['paginate'] ?? 'ajax')) : 'ajax',
]);
}
}