From 5a900d7385e7b17e496f52c7eb149cd2232c8f6f Mon Sep 17 00:00:00 2001
From: unknown
Date: Mon, 20 Jul 2026 22:57:10 +0800
Subject: [PATCH] feat(widgets): category block AJAX + recommend/term
render_html
Expose static HTML/query payloads for homepage category widgets,
tag cloud, categories, and term card/aggregation remote refresh.
---
src/AKSRT/Widgets/CategoriesWidget.php | 57 +++++++-----
src/AKSRT/Widgets/CategoryBlockWidget.php | 92 ++++++++++++++++---
src/AKSRT/Widgets/CategoryRecommendWidget.php | 38 ++++----
src/AKSRT/Widgets/TagCloudWidget.php | 30 +++---
src/AKSRT/Widgets/WidgetTermAggregation.php | 58 +++++++-----
src/AKSRT/Widgets/WidgetTermCards.php | 57 +++++++-----
6 files changed, 227 insertions(+), 105 deletions(-)
diff --git a/src/AKSRT/Widgets/CategoriesWidget.php b/src/AKSRT/Widgets/CategoriesWidget.php
index fe2362ce..5e412231 100644
--- a/src/AKSRT/Widgets/CategoriesWidget.php
+++ b/src/AKSRT/Widgets/CategoriesWidget.php
@@ -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 */
+ 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'))
+ : '' . esc_html__('暂无分类', 'aksrt') . '
';
}
- echo '
+
+
+
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);
+ }
}
diff --git a/src/AKSRT/Widgets/CategoryRecommendWidget.php b/src/AKSRT/Widgets/CategoryRecommendWidget.php
index 49db1d85..7d002742 100644
--- a/src/AKSRT/Widgets/CategoryRecommendWidget.php
+++ b/src/AKSRT/Widgets/CategoryRecommendWidget.php
@@ -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 '';
- echo $args['after_widget'];
- return;
+ return function_exists('aksrt_get_widget_null')
+ ? aksrt_get_widget_null(__('暂无推荐分类', 'aksrt'))
+ : '' . esc_html__('暂无推荐分类', 'aksrt') . '
';
}
$lazy = aksrt_home_widget_get_lazyload_args();
- printf('', (int) $columns);
-
+ ob_start();
+ printf('
', (int) $columns);
foreach ($cards as $card) {
?>
@@ -108,8 +99,19 @@ class CategoryRecommendWidget extends \WP_Widget {
';
+ 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 '
';
echo $args['after_widget'];
}
@@ -190,7 +192,7 @@ class CategoryRecommendWidget extends \WP_Widget {
* @param bool $show_description Whether to fetch description.
* @return array
>
*/
- 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) {
diff --git a/src/AKSRT/Widgets/TagCloudWidget.php b/src/AKSRT/Widgets/TagCloudWidget.php
index 53044419..466439f7 100644
--- a/src/AKSRT/Widgets/TagCloudWidget.php
+++ b/src/AKSRT/Widgets/TagCloudWidget.php
@@ -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'))
+ : '' . esc_html__('暂无标签', 'aksrt') . '
';
}
$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 '';
foreach ($tags as $tag) {
$url = get_term_link($tag);
@@ -79,7 +75,19 @@ class TagCloudWidget extends \WP_Widget {
echo '
' . esc_html($tag->name) . $count . ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '
';
+ 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 '';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
diff --git a/src/AKSRT/Widgets/WidgetTermAggregation.php b/src/AKSRT/Widgets/WidgetTermAggregation.php
index be75dcff..fed1a4f1 100644
--- a/src/AKSRT/Widgets/WidgetTermAggregation.php
+++ b/src/AKSRT/Widgets/WidgetTermAggregation.php
@@ -22,18 +22,32 @@ class WidgetTermAggregation extends \WP_Widget {
);
}
- public function widget($args, $instance): void {
+ /** @return array */
+ 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'))
+ : '' . esc_html__('暂无分类', 'aksrt') . '
';
}
$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 '';
+ ob_start();
+ echo '
';
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 '
';
}
echo '
';
+ 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 */
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 {
diff --git a/src/AKSRT/Widgets/WidgetTermCards.php b/src/AKSRT/Widgets/WidgetTermCards.php
index 3fd1cf40..dd881738 100644
--- a/src/AKSRT/Widgets/WidgetTermCards.php
+++ b/src/AKSRT/Widgets/WidgetTermCards.php
@@ -22,14 +22,29 @@ class WidgetTermCards extends \WP_Widget {
);
}
- public function widget($args, $instance): void {
+ /** @return array */
+ 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'))
+ : '' . esc_html__('暂无分类', 'aksrt') . '
';
}
$cols = max(1, min(4, (int) ($instance['columns'] ?? 2)));
- echo '';
+ ob_start();
+ echo '
';
foreach ($terms as $term) {
$link = get_term_link($term);
if (is_wp_error($link)) {
@@ -88,23 +100,26 @@ class WidgetTermCards extends \WP_Widget {
echo '
';
}
echo '
';
+ 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 */
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 {