Merge pull request 'Worktree ux depth batch9' (#227) from worktree-ux-depth-batch9 into main

Reviewed-on: CYL/wordpress-theme-aksrt#227
This commit is contained in:
2026-07-20 22:59:03 +08:00
14 changed files with 799 additions and 265 deletions
+96
View File
@@ -787,3 +787,99 @@
overflow: hidden;
text-overflow: ellipsis;
}
/* ---- batch9: plate info actions / mods grid / board list / term polish ---- */
.aksrt-plate-info__cover.is-icon {
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
color: var(--color-primary, #3b82f6);
background: linear-gradient(135deg, #eff6ff, #f5f3ff);
}
.aksrt-plate-info__actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
}
.aksrt-plate-info__btn {
display: inline-flex;
align-items: center;
gap: 6px;
border: 0;
border-radius: 999px;
padding: 6px 12px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
text-decoration: none;
line-height: 1.2;
}
.aksrt-plate-info__follow {
background: #ef4444;
color: #fff !important;
}
.aksrt-plate-info__follow.is-followed {
background: var(--surface-2, #e5e7eb);
color: inherit !important;
}
.aksrt-plate-info__compose {
background: var(--color-primary, #3b82f6);
color: #fff !important;
}
.aksrt-plate-mods.style-grid .aksrt-plate-mods__list {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
}
.aksrt-plate-mods.style-grid .aksrt-plate-mods__item {
flex-direction: column;
text-align: center;
gap: 4px;
}
.aksrt-plate-mods.style-grid .aksrt-plate-mods__name {
font-size: 12px;
width: 100%;
}
.aksrt-plate-mods.style-grid .aksrt-plate-mods__role {
font-size: 10px;
}
.aksrt-plate-mods__item.role-author .aksrt-plate-mods__role,
.aksrt-plate-mods__item.role-super .aksrt-plate-mods__role {
color: #b45309;
}
.aksrt-forum-widget__boards .aksrt-forum-widget__name {
flex: 1;
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.aksrt-forum-widget__boards .aksrt-forum-widget__count {
margin-left: 8px;
color: var(--muted-2-color, #999);
font-size: 12px;
flex-shrink: 0;
}
.aksrt-forum-widget__boards.style-mini .aksrt-forum-widget__board a {
gap: 8px;
}
.aksrt-forum-widget__thumb {
width: 28px;
height: 28px;
border-radius: 6px;
object-fit: cover;
flex-shrink: 0;
margin-right: 4px;
}
.aksrt-tag-cloud .c-blue-2 { background: #bfdbfe; color: #1e40af; }
.aksrt-tag-cloud .c-green-2 { background: #bbf7d0; color: #166534; }
.aksrt-tag-cloud .c-yellow-2 { background: #fde68a; color: #92400e; }
.aksrt-tag-cloud .c-purple-2 { background: #ddd6fe; color: #5b21b6; }
.aksrt-tag-cloud .c-red-2 { background: #fecaca; color: #991b1b; }
.aksrt-home-widget__ajax .ajax-pag { margin-top: 12px; text-align: center; }
+139 -11
View File
@@ -25,17 +25,24 @@ class WidgetAjax {
public function register_routes(): void {
$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',
];
'/widgets/posts' => 'rest_main_posts',
'/widgets/tab-posts' => 'rest_tab_posts',
'/widgets/paid-posts' => 'rest_paid_posts',
'/widgets/category-tabs' => 'rest_category_tabs',
'/widgets/category-block' => 'rest_category_block',
'/widgets/category-recommend' => 'rest_category_recommend',
'/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',
'/widgets/forum-boards' => 'rest_forum_boards',
'/widgets/tag-cloud' => 'rest_tag_cloud',
'/widgets/categories' => 'rest_categories',
'/widgets/term-cards' => 'rest_term_cards',
'/widgets/term-aggregation' => 'rest_term_aggregation',
];
foreach ($routes as $path => $cb) {
register_rest_route('aksrt/v1', $path, [
'methods' => 'GET',
@@ -222,6 +229,127 @@ class WidgetAjax {
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_category_block(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\CategoryBlockWidget')) {
return $this->fail();
}
$instance = [
'category_id' => max(0, (int) ($request->get_param('cat') ?? $request->get_param('category_id') ?? 0)),
'count' => max(1, min(12, (int) ($request->get_param('count') ?? 6))),
'columns' => max(1, min(4, (int) ($request->get_param('columns') ?? 3))),
'layout' => sanitize_key((string) ($request->get_param('layout') ?? 'card')),
'paginate' => sanitize_key((string) ($request->get_param('paginate') ?? 'ajax')),
];
$context = sanitize_key((string) ($request->get_param('context') ?? 'homepage'));
$page = max(1, (int) ($request->get_param('page') ?? 1));
return $this->payload(\AKSRT\Widgets\CategoryBlockWidget::query_payload($instance, $page, $context), $page);
}
public function rest_category_recommend(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\CategoryRecommendWidget')) {
return $this->fail();
}
$raw_ids = $request->get_param('category_ids');
if (is_string($raw_ids)) {
$raw_ids = array_filter(array_map('intval', preg_split('/[\s,]+/', $raw_ids) ?: []));
}
$instance = [
'category_ids' => is_array($raw_ids) ? $raw_ids : [],
'posts_per_category' => max(1, min(8, (int) ($request->get_param('posts_per_category') ?? 5))),
'columns' => max(1, min(4, (int) ($request->get_param('columns') ?? 2))),
'show_views' => (int) ($request->get_param('show_views') ?? 0),
'show_description' => $request->get_param('show_description') === null ? 1 : (int) $request->get_param('show_description'),
'show_dates' => $request->get_param('show_dates') === null ? 1 : (int) $request->get_param('show_dates'),
'show_more' => $request->get_param('show_more') === null ? 1 : (int) $request->get_param('show_more'),
];
$html = \AKSRT\Widgets\CategoryRecommendWidget::render_html($instance);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_forum_boards(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\WidgetForumBoardList')) {
return $this->fail();
}
$instance = [
'count' => max(1, min(50, (int) ($request->get_param('count') ?? 30))),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'menu_order')),
'style' => sanitize_key((string) ($request->get_param('style') ?? 'list')),
'hide_empty' => $request->get_param('hide_empty') === null ? 1 : (int) $request->get_param('hide_empty'),
];
$html = \AKSRT\Widgets\WidgetForumBoardList::render_html($instance);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_tag_cloud(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\TagCloudWidget')) {
return $this->fail();
}
$instance = [
'taxonomy' => sanitize_key((string) ($request->get_param('taxonomy') ?? 'post_tag')),
'count' => max(1, min(80, (int) ($request->get_param('count') ?? 15))),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'count')),
'color' => sanitize_html_class((string) ($request->get_param('color') ?? 'rand')),
'show_count' => (int) ($request->get_param('show_count') ?? 0),
'blank' => (int) ($request->get_param('blank') ?? 0),
'fixed_width' => (int) ($request->get_param('fixed_width') ?? 0),
'hide_empty' => (int) ($request->get_param('hide_empty') ?? 0),
];
$html = \AKSRT\Widgets\TagCloudWidget::render_html($instance);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_categories(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\CategoriesWidget')) {
return $this->fail();
}
$instance = [
'count' => max(1, min(50, (int) ($request->get_param('count') ?? 8))),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'count')),
'order' => sanitize_key((string) ($request->get_param('order') ?? 'DESC')),
'parent' => max(0, (int) ($request->get_param('parent') ?? 0)),
'include' => sanitize_text_field((string) ($request->get_param('include') ?? '')),
'exclude' => sanitize_text_field((string) ($request->get_param('exclude') ?? '')),
'show_count' => $request->get_param('show_count') === null ? 1 : (int) $request->get_param('show_count'),
'hide_empty' => $request->get_param('hide_empty') === null ? 1 : (int) $request->get_param('hide_empty'),
'hierarchical' => (int) ($request->get_param('hierarchical') ?? 0),
];
$html = \AKSRT\Widgets\CategoriesWidget::render_html($instance);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_term_cards(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\WidgetTermCards')) {
return $this->fail();
}
$instance = [
'taxonomy' => sanitize_key((string) ($request->get_param('taxonomy') ?? 'category')),
'number' => max(1, min(30, (int) ($request->get_param('number') ?? 8))),
'columns' => max(1, min(4, (int) ($request->get_param('columns') ?? 2))),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'count')),
'include' => sanitize_text_field((string) ($request->get_param('include') ?? '')),
'hide_empty' => $request->get_param('hide_empty') === null ? 1 : (int) $request->get_param('hide_empty'),
];
$html = \AKSRT\Widgets\WidgetTermCards::render_html($instance);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_term_aggregation(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\WidgetTermAggregation')) {
return $this->fail();
}
$instance = [
'taxonomy' => sanitize_key((string) ($request->get_param('taxonomy') ?? 'category')),
'term_ids' => sanitize_text_field((string) ($request->get_param('term_ids') ?? '')),
'count' => max(1, min(12, (int) ($request->get_param('count') ?? 4))),
'columns' => max(1, min(3, (int) ($request->get_param('columns') ?? 1))),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'date')),
'auto_terms' => max(1, min(4, (int) ($request->get_param('auto_terms') ?? 2))),
];
$html = \AKSRT\Widgets\WidgetTermAggregation::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);
}
+35 -22
View File
@@ -22,14 +22,22 @@ class CategoriesWidget 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'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
/** @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 {
$count = max(1, min(50, (int) ($instance['count'] ?? 8)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'count'));
if (!in_array($orderby, ['count', 'name', 'id', 'slug'], true)) {
@@ -40,8 +48,8 @@ class CategoriesWidget extends \WP_Widget {
$hierarchical = !empty($instance['hierarchical']);
$parent = max(0, (int) ($instance['parent'] ?? 0));
$show_count = !isset($instance['show_count']) || !empty($instance['show_count']);
$exclude = $this->parse_ids((string) ($instance['exclude'] ?? ''));
$include = $this->parse_ids((string) ($instance['include'] ?? ''));
$exclude = self::parse_ids_static((string) ($instance['exclude'] ?? ''));
$include = self::parse_ids_static((string) ($instance['include'] ?? ''));
$term_args = [
'taxonomy' => 'category',
@@ -65,11 +73,13 @@ class CategoriesWidget extends \WP_Widget {
$cats = get_categories($term_args);
if (!$cats) {
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return;
return function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无分类', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无分类', 'aksrt') . '</p>';
}
echo '<ul class="widget-list aksrt-cat-list">';
ob_start();
echo '<ul class="widget-list aksrt-cat-list ajax-item">';
foreach ($cats as $cat) {
echo '<li>';
echo '<a href="' . esc_url(get_category_link($cat->term_id)) . '">' . esc_html($cat->name) . '</a>';
@@ -79,23 +89,26 @@ class CategoriesWidget extends \WP_Widget {
echo '</li>';
}
echo '</ul>';
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'] ?? ''), $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/categories');
echo '<div class="ajaxpager" data-aksrt-ajaxpager data-aksrt-ajax-url="' . esc_url($rest) . '">';
echo self::render_html($instance); // 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 {
+81 -11
View File
@@ -323,33 +323,85 @@ class CategoryBlockWidget extends \WP_Widget {
);
}
public function widget($args, $instance): void {
$context = aksrt_home_widget_resolve_context($args);
$category_id = $this->resolve_category_id($instance);
$term = $category_id > 0 ? get_term($category_id, 'category') : null;
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
$title = $title !== '' ? $title : (($term && ! is_wp_error($term)) ? $term->name : __('精选分类', 'aksrt'));
/**
* @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, string $context = 'homepage'): array {
$page = max(1, $page);
$category_id = self::resolve_category_id_static($instance);
$count = max(1, min(12, (int) ($instance['count'] ?? 6)));
$columns = aksrt_home_widget_effective_columns((int) ($instance['columns'] ?? 3), $context);
$layout = aksrt_home_widget_effective_layout(
aksrt_home_widget_resolve_layout($instance['layout'] ?? 'card', 'card'),
$context
);
$show_more = ! empty($instance['show_more']);
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'none'));
if (!in_array($paginate, ['ajax', 'none'], true)) {
$paginate = 'none';
}
$query_args = [
'post_type' => aksrt_home_widget_post_types(),
'post_status' => 'publish',
'posts_per_page' => $count,
'paged' => $page,
'ignore_sticky_posts' => true,
'no_found_rows' => true,
];
if ($paginate === 'none') {
$query_args['no_found_rows'] = true;
}
if ($category_id > 0) {
$query_args['cat'] = $category_id;
}
$query = new \WP_Query($query_args);
ob_start();
aksrt_home_widget_render_post_grid($query, $layout, $columns, $context);
$html = (string) ob_get_clean();
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) $query->max_num_pages);
$total = (int) $query->found_posts;
$has_more = $paginate === 'ajax' && $page < $max_pages;
$paginate_html = '';
if ($paginate === 'ajax' && $has_more) {
$next = add_query_arg([
'page' => $page + 1,
'count' => $count,
'cat' => $category_id,
'columns' => $columns,
'layout' => $layout,
'context' => $context,
'paginate' => 'ajax',
], rest_url('aksrt/v1/widgets/category-block'));
$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 {
$context = aksrt_home_widget_resolve_context($args);
$category_id = self::resolve_category_id_static($instance);
$term = $category_id > 0 ? get_term($category_id, 'category') : null;
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
$title = $title !== '' ? $title : (($term && ! is_wp_error($term)) ? $term->name : __('精选分类', 'aksrt'));
$show_more = ! empty($instance['show_more']);
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'none'));
if (!in_array($paginate, ['ajax', 'none'], true)) {
$paginate = 'none';
}
$instance['paginate'] = $paginate;
$payload = self::query_payload($instance, 1, $context);
echo $args['before_widget'];
echo '<section class="aksrt-home-widget aksrt-home-widget--category-block">';
@@ -360,7 +412,12 @@ class CategoryBlockWidget extends \WP_Widget {
__('查看分类', 'aksrt')
);
aksrt_home_widget_render_post_grid($query, $layout, $columns, $context);
$rest = rest_url('aksrt/v1/widgets/category-block');
echo '<div class="ajaxpager aksrt-home-widget__ajax" 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 '</section>';
echo $args['after_widget'];
@@ -412,6 +469,14 @@ class CategoryBlockWidget extends \WP_Widget {
<input class="checkbox" type="checkbox" <?php checked($show_more); ?> id="<?php echo esc_attr($this->get_field_id('show_more')); ?>" name="<?php echo esc_attr($this->get_field_name('show_more')); ?>" value="1">
<label for="<?php echo esc_attr($this->get_field_id('show_more')); ?>"><?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="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>
</select>
</label>
</p>
<?php if (function_exists('aksrt_widget_render_location_help')) { aksrt_widget_render_location_help('hybrid'); } ?>
<?php
}
@@ -424,10 +489,11 @@ class CategoryBlockWidget extends \WP_Widget {
'columns' => max(1, min(4, absint($new_instance['columns'] ?? 3))),
'layout' => aksrt_home_widget_resolve_layout($new_instance['layout'] ?? 'card', 'card'),
'show_more' => ! empty($new_instance['show_more']) ? 1 : 0,
'paginate' => sanitize_key((string) ($new_instance['paginate'] ?? 'none')) === 'ajax' ? 'ajax' : 'none',
];
}
private function resolve_category_id(array $instance): int {
public static function resolve_category_id_static(array $instance): int {
$category_id = absint($instance['category_id'] ?? 0);
if ($category_id > 0) {
return $category_id;
@@ -436,4 +502,8 @@ class CategoryBlockWidget extends \WP_Widget {
$fallback = aksrt_home_widget_fallback_category_ids(1);
return $fallback[0] ?? 0;
}
private function resolve_category_id(array $instance): int {
return self::resolve_category_id_static($instance);
}
}
+20 -18
View File
@@ -24,9 +24,7 @@ class CategoryRecommendWidget extends \WP_Widget {
);
}
public function widget($args, $instance): void {
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
$title = $title !== '' ? $title : __('分类推荐', 'aksrt');
public static function render_html(array $instance): string {
$category_ids = aksrt_home_widget_normalize_category_ids($instance['category_ids'] ?? [], 4);
$posts_per_cat = max(1, min(8, (int) ($instance['posts_per_category'] ?? 5)));
$columns = max(1, min(4, (int) ($instance['columns'] ?? 2)));
@@ -39,23 +37,16 @@ class CategoryRecommendWidget extends \WP_Widget {
$category_ids = aksrt_home_widget_fallback_category_ids(4);
}
$cards = $this->build_cards($category_ids, $posts_per_cat, $show_views, $show_description);
echo $args['before_widget'];
echo '<section class="aksrt-home-widget aksrt-home-widget--recommend">';
aksrt_home_widget_render_header($title);
$cards = self::build_cards_static($category_ids, $posts_per_cat, $show_views, $show_description);
if (empty($cards)) {
echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无推荐分类', 'aksrt')) : '<p class="widget-empty">' . esc_html__('暂无推荐分类', 'aksrt') . '</p>';
echo '</section>';
echo $args['after_widget'];
return;
return function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('暂无推荐分类', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无推荐分类', 'aksrt') . '</p>';
}
$lazy = aksrt_home_widget_get_lazyload_args();
printf('<div class="aksrt-category-recommend__grid" style="--aksrt-recommend-columns:%d;">', (int) $columns);
ob_start();
printf('<div class="aksrt-category-recommend__grid ajax-item" style="--aksrt-recommend-columns:%d;">', (int) $columns);
foreach ($cards as $card) {
?>
<article class="aksrt-category-recommend__card">
@@ -108,8 +99,19 @@ class CategoryRecommendWidget extends \WP_Widget {
</article>
<?php
}
echo '</div>';
return (string) ob_get_clean();
}
public function widget($args, $instance): void {
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
$title = $title !== '' ? $title : __('分类推荐', 'aksrt');
$html = self::render_html($instance);
echo $args['before_widget'];
echo '<section class="aksrt-home-widget aksrt-home-widget--recommend">';
aksrt_home_widget_render_header($title);
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</section>';
echo $args['after_widget'];
}
@@ -190,7 +192,7 @@ class CategoryRecommendWidget extends \WP_Widget {
* @param bool $show_description Whether to fetch description.
* @return array<int, array<string, mixed>>
*/
private function build_cards(array $category_ids, int $posts_per_cat, bool $show_views, bool $show_description): array {
public static function build_cards_static(array $category_ids, int $posts_per_cat, bool $show_views, bool $show_description): array {
$cards = [];
foreach ($category_ids as $category_id) {
@@ -78,7 +78,10 @@ class RecentCommentsWidget extends \WP_Widget {
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$rest = rest_url('aksrt/v1/widgets/recent-comments');
echo '<div class="ajaxpager" data-aksrt-ajaxpager data-aksrt-ajax-url="' . esc_url($rest) . '">';
echo self::render_html($instance); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
+19 -11
View File
@@ -22,13 +22,7 @@ class TagCloudWidget 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'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
public static function render_html(array $instance): string {
$taxonomy = sanitize_key((string) ($instance['taxonomy'] ?? 'post_tag'));
if (!taxonomy_exists($taxonomy)) {
$taxonomy = 'post_tag';
@@ -46,18 +40,20 @@ class TagCloudWidget extends \WP_Widget {
$tags = get_terms([
'taxonomy' => $taxonomy,
'orderby' => $orderby,
'order' => 'DESC',
'order' => $orderby === 'name' ? 'ASC' : 'DESC',
'number' => $number,
'hide_empty' => !empty($instance['hide_empty']),
]);
if (is_wp_error($tags) || !$tags) {
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return;
return function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无标签', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无标签', 'aksrt') . '</p>';
}
$palette = ['c-blue', 'c-yellow', 'c-green', 'c-purple', 'c-red', '', 'c-blue-2', 'c-yellow-2', 'c-green-2', 'c-purple-2', 'c-red-2', ''];
$i = random_int(0, 10);
$wrap = 'tag-cloud aksrt-tag-cloud author-tag' . ($fixed_width ? ' fixed-width' : '');
$wrap = 'tag-cloud aksrt-tag-cloud author-tag ajax-item' . ($fixed_width ? ' fixed-width' : '');
ob_start();
echo '<div class="' . esc_attr($wrap) . '">';
foreach ($tags as $tag) {
$url = get_term_link($tag);
@@ -79,7 +75,19 @@ class TagCloudWidget extends \WP_Widget {
echo '<a href="' . esc_url($url) . '" class="text-ellipsis ' . esc_attr(trim($cls)) . '"' . $tgt . '>' . esc_html($tag->name) . $count . '</a>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
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'] ?? ''), $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/tag-cloud');
echo '<div class="ajaxpager" data-aksrt-ajaxpager data-aksrt-ajax-url="' . esc_url($rest) . '">';
echo self::render_html($instance); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
+164 -85
View File
@@ -1,5 +1,7 @@
<?php
/**
* Forum Board List Widget (board taxonomy + plate CPT fallback).
*
* @package AKSRT
*/
@@ -7,97 +9,174 @@ namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
/**
* Forum Board List Widget
*/
class WidgetForumBoardList extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_forum_board_list',
__('论坛 - 版块列表', 'aksrt'),
['description' => __('显示论坛版块列表及话题数量。', 'aksrt')]
);
}
public function __construct() {
parent::__construct(
'aksrt_forum_board_list',
__('论坛 - 版块列表', 'aksrt'),
['description' => __('显示论坛版块列表及话题数量。', 'aksrt')]
);
}
public function widget($args, $instance): void {
$title = !empty($instance['title']) ? $instance['title'] : __('版块列表', 'aksrt');
public static function render_html(array $instance): string {
$limit = max(1, min(50, (int) ($instance['count'] ?? $instance['showposts'] ?? 30)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'menu_order'));
$style = sanitize_key((string) ($instance['style'] ?? 'list'));
if (!in_array($style, ['list', 'mini'], true)) {
$style = 'list';
}
echo $args['before_widget'];
echo $args['before_title'] . esc_html($title) . $args['after_title'];
// Prefer plate CPT (same as plate_lists lite).
if (post_type_exists('aksrt_forum_plate')) {
$query_args = [
'post_type' => 'aksrt_forum_plate',
'post_status' => 'publish',
'posts_per_page' => $limit,
'order' => 'ASC',
'no_found_rows' => true,
];
switch ($orderby) {
case 'posts':
$query_args['meta_key'] = '_aksrt_posts_count';
$query_args['orderby'] = 'meta_value_num';
$query_args['order'] = 'DESC';
break;
case 'views':
$query_args['meta_key'] = '_aksrt_views';
$query_args['orderby'] = 'meta_value_num';
$query_args['order'] = 'DESC';
break;
case 'date':
$query_args['orderby'] = 'date';
$query_args['order'] = 'DESC';
break;
default:
$query_args['orderby'] = 'menu_order title';
}
$plates = get_posts($query_args);
if ($plates) {
ob_start();
echo '<ul class="aksrt-forum-widget__boards style-' . esc_attr($style) . ' ajax-item">';
foreach ($plates as $plate) {
$icon = (string) get_post_meta($plate->ID, '_aksrt_icon', true);
$count = (int) get_post_meta($plate->ID, '_aksrt_posts_count', true);
$thumb = get_the_post_thumbnail_url($plate->ID, 'thumbnail');
echo '<li class="aksrt-forum-widget__board">';
echo '<a href="' . esc_url(get_permalink($plate)) . '">';
if ($style === 'mini' && $thumb) {
echo '<img class="aksrt-forum-widget__thumb" src="' . esc_url($thumb) . '" alt="" width="28" height="28" loading="lazy" />';
} elseif ($icon !== '') {
echo '<i class="' . esc_attr($icon) . '"></i> ';
}
echo '<span class="aksrt-forum-widget__name">' . esc_html(get_the_title($plate)) . '</span>';
echo '<span class="aksrt-forum-widget__count">' . esc_html((string) $count) . '</span>';
echo '</a></li>';
}
echo '</ul>';
return (string) ob_get_clean();
}
}
$rendered = false;
if (post_type_exists('aksrt_forum_plate')) {
$plates = get_posts([
'post_type' => 'aksrt_forum_plate',
'post_status' => 'publish',
'posts_per_page' => 30,
'orderby' => 'menu_order title',
'order' => 'ASC',
]);
if ($plates) {
$rendered = true;
echo '<ul class="aksrt-forum-widget__boards">';
foreach ($plates as $plate) {
$icon = (string) get_post_meta($plate->ID, '_aksrt_icon', true);
$count = (int) get_post_meta($plate->ID, '_aksrt_posts_count', true);
echo '<li class="aksrt-forum-widget__board">';
echo '<a href="' . esc_url(get_permalink($plate)) . '">';
if ($icon !== '') {
echo '<i class="' . esc_attr($icon) . '"></i> ';
}
echo esc_html(get_the_title($plate));
echo '<span class="aksrt-forum-widget__count">' . $count . '</span>';
echo '</a>';
echo '</li>';
}
echo '</ul>';
}
}
if (!$rendered) {
$boards = get_terms([
'taxonomy' => 'aksrt_forum_board',
'hide_empty' => true,
'orderby' => 'meta_value_num',
'meta_key' => '_aksrt_sort_order',
'order' => 'ASC',
]);
if (!is_wp_error($boards) && !empty($boards)) {
echo '<ul class="aksrt-forum-widget__boards">';
foreach ($boards as $board) {
$icon = get_term_meta($board->term_id, '_aksrt_icon', true);
echo '<li class="aksrt-forum-widget__board">';
echo '<a href="' . esc_url(get_term_link($board)) . '">';
if ($icon) {
echo '<i class="' . esc_attr($icon) . '"></i> ';
}
echo esc_html($board->name);
echo '<span class="aksrt-forum-widget__count">' . (int) $board->count . '</span>';
echo '</a>';
echo '</li>';
}
echo '</ul>';
} else {
echo '<p class="aksrt-forum-widget__empty">' . esc_html__('暂无版块', 'aksrt') . '</p>';
}
}
// Taxonomy fallback.
$term_args = [
'taxonomy' => 'aksrt_forum_board',
'hide_empty' => !isset($instance['hide_empty']) || !empty($instance['hide_empty']),
'number' => $limit,
'order' => 'ASC',
];
if ($orderby === 'count') {
$term_args['orderby'] = 'count';
$term_args['order'] = 'DESC';
} elseif ($orderby === 'name') {
$term_args['orderby'] = 'name';
} else {
$term_args['orderby'] = 'meta_value_num';
$term_args['meta_key'] = '_aksrt_sort_order';
}
$boards = get_terms($term_args);
if (is_wp_error($boards) || empty($boards)) {
return function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无版块', 'aksrt'))
: '<p class="aksrt-forum-widget__empty ajax-item">' . esc_html__('暂无版块', 'aksrt') . '</p>';
}
ob_start();
echo '<ul class="aksrt-forum-widget__boards style-' . esc_attr($style) . ' ajax-item">';
foreach ($boards as $board) {
$icon = get_term_meta($board->term_id, '_aksrt_icon', true);
echo '<li class="aksrt-forum-widget__board">';
echo '<a href="' . esc_url(get_term_link($board)) . '">';
if ($icon) {
echo '<i class="' . esc_attr($icon) . '"></i> ';
}
echo '<span class="aksrt-forum-widget__name">' . esc_html($board->name) . '</span>';
echo '<span class="aksrt-forum-widget__count">' . esc_html((string) (int) $board->count) . '</span>';
echo '</a></li>';
}
echo '</ul>';
return (string) ob_get_clean();
}
echo $args['after_widget'];
}
public function widget($args, $instance): void {
$title = !empty($instance['title']) ? $instance['title'] : __('版块列表', 'aksrt');
$html = self::render_html($instance);
public function form($instance): void {
$title = !empty($instance['title']) ? $instance['title'] : __('版块列表', 'aksrt');
?>
<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>
<?php
}
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
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
public function update($new_instance, $old_instance): array {
$instance = [];
$instance['title'] = sanitize_text_field($new_instance['title'] ?? '');
return $instance;
}
public function form($instance): void {
$title = !empty($instance['title']) ? $instance['title'] : __('版块列表', 'aksrt');
?>
<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><?php esc_html_e('数量', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="50" name="<?php echo esc_attr($this->get_field_name('count')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['count'] ?? 30))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('排序', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
<option value="menu_order" <?php selected(($instance['orderby'] ?? 'menu_order'), 'menu_order'); ?>><?php esc_html_e('菜单顺序', 'aksrt'); ?></option>
<option value="posts" <?php selected(($instance['orderby'] ?? ''), 'posts'); ?>><?php esc_html_e('帖子数', 'aksrt'); ?></option>
<option value="views" <?php selected(($instance['orderby'] ?? ''), 'views'); ?>><?php esc_html_e('浏览量', 'aksrt'); ?></option>
<option value="date" <?php selected(($instance['orderby'] ?? ''), 'date'); ?>><?php esc_html_e('创建时间', 'aksrt'); ?></option>
<option value="name" <?php selected(($instance['orderby'] ?? ''), 'name'); ?>><?php esc_html_e('名称', 'aksrt'); ?></option>
<option value="count" <?php selected(($instance['orderby'] ?? ''), 'count'); ?>><?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('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>
</select>
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hide_empty')); ?>" value="1" <?php checked(!isset($instance['hide_empty']) || !empty($instance['hide_empty'])); ?>> <?php esc_html_e('隐藏空版块(分类模式)', 'aksrt'); ?></label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'menu_order'));
$allowed = ['menu_order', 'posts', 'views', 'date', 'name', 'count'];
if (!in_array($orderby, $allowed, true)) {
$orderby = 'menu_order';
}
return [
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'count' => max(1, min(50, (int) ($new_instance['count'] ?? 30))),
'orderby' => $orderby,
'style' => ($new_instance['style'] ?? '') === 'mini' ? 'mini' : 'list',
'hide_empty' => !empty($new_instance['hide_empty']) ? 1 : 0,
];
}
}
+95 -20
View File
@@ -22,41 +22,96 @@ class WidgetForumPlateInfo extends \WP_Widget {
);
}
public function widget($args, $instance): void {
$plate = $this->resolve_current_plate();
/**
* @return array{html:string}
*/
public static function render_payload(array $instance, ?\WP_Post $plate = null): array {
$plate = $plate ?: self::resolve_current_plate_static();
if (!$plate) {
return;
return ['html' => ''];
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$show_follow = !isset($instance['show_follow']) || !empty($instance['show_follow']);
$show_compose = !isset($instance['show_compose']) || !empty($instance['show_compose']);
$show_stats = !isset($instance['show_stats']) || !empty($instance['show_stats']);
$id = (int) $plate->ID;
$url = get_permalink($id);
$thumb = get_the_post_thumbnail_url($id, 'medium');
$posts = (int) get_post_meta($id, '_aksrt_posts_count', true);
$reply = (int) get_post_meta($id, '_aksrt_reply_count', true);
$id = (int) $plate->ID;
$url = get_permalink($id);
$thumb = get_the_post_thumbnail_url($id, 'medium');
$posts = (int) get_post_meta($id, '_aksrt_posts_count', true);
$reply = (int) get_post_meta($id, '_aksrt_reply_count', true);
$follow = (int) get_post_meta($id, '_aksrt_follow_count', true);
$desc = $plate->post_excerpt !== '' ? $plate->post_excerpt : wp_trim_words(wp_strip_all_tags($plate->post_content), 30);
$views = (int) get_post_meta($id, '_aksrt_views', true);
$desc = $plate->post_excerpt !== '' ? $plate->post_excerpt : wp_trim_words(wp_strip_all_tags($plate->post_content), 30);
$icon = (string) get_post_meta($id, '_aksrt_icon', true);
echo '<div class="aksrt-plate-info">';
$forum = class_exists('\AKSRT\Forum\Forum') ? \AKSRT\Forum\Forum::get_instance() : null;
$uid = get_current_user_id();
$is_followed = $forum && $uid > 0 ? $forum->is_user_following_plate($uid, $id) : false;
$compose_url = function_exists('aksrt_get_forum_compose_url')
? aksrt_get_forum_compose_url(['board' => $id])
: home_url('/');
ob_start();
echo '<div class="aksrt-plate-info ajax-item">';
if ($thumb) {
echo '<div class="aksrt-plate-info__cover" style="background-image:url(' . esc_url($thumb) . ')"></div>';
} elseif ($icon !== '') {
echo '<div class="aksrt-plate-info__cover is-icon"><i class="' . esc_attr($icon) . '" aria-hidden="true"></i></div>';
}
echo '<div class="aksrt-plate-info__body">';
echo '<a class="aksrt-plate-info__title" href="' . esc_url($url) . '">' . esc_html(get_the_title($plate)) . '</a>';
if ($desc !== '') {
echo '<div class="aksrt-plate-info__desc">' . esc_html($desc) . '</div>';
}
echo '<div class="aksrt-plate-info__stats">';
echo '<span><i class="fas fa-file-alt" aria-hidden="true"></i> ' . esc_html((string) $posts) . '</span>';
echo '<span><i class="fas fa-comment" aria-hidden="true"></i> ' . esc_html((string) $reply) . '</span>';
echo '<span><i class="fas fa-heart" aria-hidden="true"></i> ' . esc_html((string) $follow) . '</span>';
echo '</div></div></div>';
if ($show_stats) {
echo '<div class="aksrt-plate-info__stats">';
echo '<span title="' . esc_attr__('帖子', 'aksrt') . '"><i class="fas fa-file-alt" aria-hidden="true"></i> ' . esc_html((string) $posts) . '</span>';
echo '<span title="' . esc_attr__('回复', 'aksrt') . '"><i class="fas fa-comment" aria-hidden="true"></i> ' . esc_html((string) $reply) . '</span>';
if ($views > 0) {
echo '<span title="' . esc_attr__('浏览', 'aksrt') . '"><i class="fas fa-eye" aria-hidden="true"></i> ' . esc_html((string) $views) . '</span>';
}
echo '<span title="' . esc_attr__('关注', 'aksrt') . '"><i class="fas fa-heart" aria-hidden="true"></i> ' . esc_html((string) $follow) . '</span>';
echo '</div>';
}
$actions = [];
if ($show_follow && $uid > 0) {
$actions[] = sprintf(
'<button type="button" class="aksrt-plate-info__btn aksrt-plate-info__follow js-aksrt-follow-plate%s" data-plate-id="%d" data-followed="%s"><i class="fas fa-heart" aria-hidden="true"></i> %s</button>',
$is_followed ? ' is-followed' : '',
$id,
$is_followed ? '1' : '0',
esc_html($is_followed ? __('已关注', 'aksrt') : __('关注', 'aksrt'))
);
}
if ($show_compose) {
$actions[] = '<a class="aksrt-plate-info__btn aksrt-plate-info__compose" href="' . esc_url($compose_url) . '"><i class="fas fa-pen" aria-hidden="true"></i> ' . esc_html__('发帖', 'aksrt') . '</a>';
}
if ($actions) {
echo '<div class="aksrt-plate-info__actions">' . implode('', $actions) . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '</div></div>';
return ['html' => (string) ob_get_clean()];
}
public function widget($args, $instance): void {
$payload = self::render_payload($instance);
if ($payload['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 $payload['html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
private function resolve_current_plate(): ?\WP_Post {
public static function resolve_current_plate_static(): ?\WP_Post {
if (is_singular('aksrt_forum_plate')) {
$p = get_queried_object();
return $p instanceof \WP_Post ? $p : null;
@@ -65,7 +120,6 @@ class WidgetForumPlateInfo extends \WP_Widget {
$topic_id = (int) get_queried_object_id();
$plate_id = (int) get_post_meta($topic_id, '_aksrt_plate_id', true);
if ($plate_id <= 0) {
// Fallback: first linked board term → plate meta, or parent.
$boards = get_the_terms($topic_id, 'aksrt_forum_board');
if (!is_wp_error($boards) && !empty($boards[0])) {
$linked = (int) get_term_meta((int) $boards[0]->term_id, '_aksrt_plate_id', true);
@@ -84,11 +138,32 @@ class WidgetForumPlateInfo extends \WP_Widget {
return null;
}
private function resolve_current_plate(): ?\WP_Post {
return self::resolve_current_plate_static();
}
public function form($instance): void {
echo '<p class="description">' . esc_html__('无需配置。仅在版块页或帖子页显示对应版块信息。', 'aksrt') . '</p>';
?>
<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><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_stats')); ?>" value="1" <?php checked(!isset($instance['show_stats']) || !empty($instance['show_stats'])); ?>> <?php esc_html_e('显示统计', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_follow')); ?>" value="1" <?php checked(!isset($instance['show_follow']) || !empty($instance['show_follow'])); ?>> <?php esc_html_e('显示关注按钮', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_compose')); ?>" value="1" <?php checked(!isset($instance['show_compose']) || !empty($instance['show_compose'])); ?>> <?php esc_html_e('显示发帖按钮', 'aksrt'); ?></label>
</p>
<p class="description"><?php esc_html_e('仅在版块页或帖子页显示对应版块信息。', 'aksrt'); ?></p>
<?php
}
public function update($new_instance, $old_instance): array {
return [];
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'show_stats' => !empty($new_instance['show_stats']) ? 1 : 0,
'show_follow' => !empty($new_instance['show_follow']) ? 1 : 0,
'show_compose' => !empty($new_instance['show_compose']) ? 1 : 0,
];
}
}
+69 -44
View File
@@ -22,45 +22,65 @@ class WidgetForumPlateModerator extends \WP_Widget {
);
}
public function widget($args, $instance): void {
$plate = $this->resolve_current_plate();
/**
* @return array{html:string}
*/
public static function render_payload(array $instance, ?\WP_Post $plate = null): array {
$plate = $plate ?: WidgetForumPlateInfo::resolve_current_plate_static();
if (!$plate) {
return;
return ['html' => ''];
}
$id = (int) $plate->ID;
$id = (int) $plate->ID;
$author_id = (int) $plate->post_author;
$mods = get_post_meta($id, '_aksrt_moderators', true);
$mods = get_post_meta($id, '_aksrt_moderators', true);
if (!is_array($mods)) {
$mods = [];
}
$mods = array_values(array_unique(array_filter(array_map('intval', $mods))));
// Super-mods meta (zibll-like).
$super = get_post_meta($id, '_aksrt_super_moderators', true);
if (!is_array($super)) {
$super = [];
}
$super = array_values(array_unique(array_filter(array_map('intval', $super))));
$people = [];
if ($author_id > 0) {
$people[] = ['id' => $author_id, 'role' => 'author'];
}
foreach ($super as $sid) {
if ($sid === $author_id) {
continue;
}
$people[] = ['id' => $sid, 'role' => 'super'];
}
foreach ($mods as $mid) {
if ($mid === $author_id) {
if ($mid === $author_id || in_array($mid, $super, true)) {
continue;
}
$people[] = ['id' => $mid, 'role' => 'moderator'];
}
if (!$people) {
return;
return ['html' => ''];
}
$title_text = (string) ($instance['title_text'] ?? __('版主', 'aksrt'));
$title_text = (string) ($instance['title_text'] ?? __('版主', 'aksrt'));
$show_avatar = !isset($instance['show_avatar']) || !empty($instance['show_avatar']);
$style = sanitize_key((string) ($instance['style'] ?? 'list'));
if (!in_array($style, ['list', 'grid'], true)) {
$style = 'list';
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="aksrt-plate-mods">';
ob_start();
echo '<div class="aksrt-plate-mods style-' . esc_attr($style) . ' ajax-item">';
if ($title_text !== '') {
echo '<div class="aksrt-plate-mods__head"><div class="title-theme">' . esc_html($title_text) . '</div></div>';
}
echo '<div class="aksrt-plate-mods__list">';
foreach ($people as $row) {
$uid = (int) $row['id'];
$uid = (int) $row['id'];
$user = get_userdata($uid);
if (!$user) {
continue;
@@ -71,61 +91,66 @@ class WidgetForumPlateModerator extends \WP_Widget {
$home = function_exists('aksrt_get_user_home_url')
? aksrt_get_user_home_url($uid)
: get_author_posts_url($uid);
$role_label = $row['role'] === 'author'
? __('超级版主', 'aksrt')
: __('版主', 'aksrt');
echo '<a class="aksrt-plate-mods__item" href="' . esc_url($home) . '">';
echo '<img src="' . esc_url($avatar) . '" alt="" class="aksrt-plate-mods__avatar" loading="lazy" width="40" height="40" />';
if ($row['role'] === 'author' || $row['role'] === 'super') {
$role_label = __('超级版主', 'aksrt');
} else {
$role_label = __('版主', 'aksrt');
}
echo '<a class="aksrt-plate-mods__item role-' . esc_attr($row['role']) . '" href="' . esc_url($home) . '">';
if ($show_avatar) {
echo '<img src="' . esc_url($avatar) . '" alt="" class="aksrt-plate-mods__avatar" loading="lazy" width="40" height="40" />';
}
echo '<span class="aksrt-plate-mods__name">' . esc_html($user->display_name) . '</span>';
echo '<span class="aksrt-plate-mods__role">' . esc_html($role_label) . '</span>';
echo '</a>';
}
echo '</div></div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return ['html' => (string) ob_get_clean()];
}
private function resolve_current_plate(): ?\WP_Post {
if (is_singular('aksrt_forum_plate')) {
$p = get_queried_object();
return $p instanceof \WP_Post ? $p : null;
public function widget($args, $instance): void {
$payload = self::render_payload($instance);
if ($payload['html'] === '') {
return;
}
if (is_singular('aksrt_forum_topic')) {
$topic_id = (int) get_queried_object_id();
$plate_id = (int) get_post_meta($topic_id, '_aksrt_plate_id', true);
if ($plate_id <= 0) {
$boards = get_the_terms($topic_id, 'aksrt_forum_board');
if (!is_wp_error($boards) && !empty($boards[0])) {
$linked = (int) get_term_meta((int) $boards[0]->term_id, '_aksrt_plate_id', true);
if ($linked > 0) {
$plate_id = $linked;
}
}
}
if ($plate_id > 0) {
$p = get_post($plate_id);
if ($p instanceof \WP_Post && $p->post_type === 'aksrt_forum_plate') {
return $p;
}
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
// title_text is rendered inside payload head (zibll-like); optional WP title still allowed.
$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
}
return null;
echo $payload['html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
public function form($instance): void {
?>
<p>
<label><?php esc_html_e('标题文案', 'aksrt'); ?>
<label><?php esc_html_e('小标题文案', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title_text')); ?>" value="<?php echo esc_attr((string) ($instance['title_text'] ?? __('版主', 'aksrt'))); ?>">
</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="grid" <?php selected(($instance['style'] ?? ''), 'grid'); ?>><?php esc_html_e('头像网格', 'aksrt'); ?></option>
</select>
</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 class="description"><?php esc_html_e('仅在版块/帖子页显示。', 'aksrt'); ?></p>
<?php
}
public function update($new_instance, $old_instance): array {
return [
'title_text' => sanitize_text_field((string) ($new_instance['title_text'] ?? '')),
'title_text' => sanitize_text_field((string) ($new_instance['title_text'] ?? '')),
'style' => ($new_instance['style'] ?? '') === 'grid' ? 'grid' : 'list',
'show_avatar' => !empty($new_instance['show_avatar']) ? 1 : 0,
];
}
}
+3
View File
@@ -105,7 +105,10 @@ class WidgetOnelinePosts extends \WP_Widget {
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$rest = rest_url('aksrt/v1/widgets/oneline-posts');
echo '<div class="ajaxpager" data-aksrt-ajaxpager data-aksrt-ajax-url="' . esc_url($rest) . '">';
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
+36 -22
View File
@@ -22,18 +22,32 @@ class WidgetTermAggregation 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 {
$taxonomy = sanitize_key((string) ($instance['taxonomy'] ?? 'category'));
if (!taxonomy_exists($taxonomy)) {
$taxonomy = 'category';
}
$term_ids = $this->parse_ids((string) ($instance['term_ids'] ?? ''));
$term_ids = self::parse_ids_static((string) ($instance['term_ids'] ?? ''));
$count = max(1, min(12, (int) ($instance['count'] ?? 4)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
$columns = max(1, min(3, (int) ($instance['columns'] ?? 1)));
if (!$term_ids) {
// Fallback: top terms by count.
$terms = get_terms([
'taxonomy' => $taxonomy,
'number' => max(1, min(4, (int) ($instance['auto_terms'] ?? 2))),
@@ -42,18 +56,15 @@ class WidgetTermAggregation extends \WP_Widget {
'hide_empty' => true,
]);
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>';
}
$term_ids = array_map(static fn($t) => (int) $t->term_id, $terms);
}
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-term-agg" style="--aksrt-ta-cols:' . esc_attr((string) $columns) . '">';
ob_start();
echo '<div class="aksrt-term-agg ajax-item" style="--aksrt-ta-cols:' . esc_attr((string) $columns) . '">';
foreach ($term_ids as $term_id) {
$term = get_term($term_id, $taxonomy);
if (!$term || is_wp_error($term)) {
@@ -118,23 +129,26 @@ class WidgetTermAggregation extends \WP_Widget {
echo '</div>';
}
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
}
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 [];
}
$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 {
+36 -21
View File
@@ -22,14 +22,29 @@ class WidgetTermCards 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 {
$taxonomy = sanitize_key((string) ($instance['taxonomy'] ?? 'category'));
if (!taxonomy_exists($taxonomy)) {
$taxonomy = 'category';
}
$number = max(1, min(30, (int) ($instance['number'] ?? 8)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'count'));
$include = $this->parse_ids((string) ($instance['include'] ?? ''));
$include = self::parse_ids_static((string) ($instance['include'] ?? ''));
$hide_empty = !isset($instance['hide_empty']) || !empty($instance['hide_empty']);
$term_args = [
@@ -47,17 +62,14 @@ class WidgetTermCards extends \WP_Widget {
$terms = get_terms($term_args);
if (is_wp_error($terms) || !$terms) {
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
return function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无分类', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无分类', 'aksrt') . '</p>';
}
$cols = max(1, min(4, (int) ($instance['columns'] ?? 2)));
echo '<div class="aksrt-term-cards" style="--aksrt-tc-cols:' . esc_attr((string) $cols) . '">';
ob_start();
echo '<div class="aksrt-term-cards ajax-item" style="--aksrt-tc-cols:' . esc_attr((string) $cols) . '">';
foreach ($terms as $term) {
$link = get_term_link($term);
if (is_wp_error($link)) {
@@ -88,23 +100,26 @@ class WidgetTermCards extends \WP_Widget {
echo '</div></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
}
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 [];
}
$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 {
+3
View File
@@ -129,7 +129,10 @@ class WidgetUserLists extends \WP_Widget {
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$rest = rest_url('aksrt/v1/widgets/user-lists');
echo '<div class="ajaxpager" data-aksrt-ajaxpager data-aksrt-ajax-url="' . esc_url($rest) . '">';
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}