feat(widgets): user minicards + term graphic cards/aggregation depth

User lists render author-minicard rows; term cards/aggregation consume
aksrt_graphic_card with style-1..4, meta CTA, and orderby-aware post rows.
This commit is contained in:
2026-07-21 08:54:47 +08:00
parent 1fdd2301fd
commit d31b3c9f91
4 changed files with 644 additions and 109 deletions
+166 -42
View File
@@ -1,6 +1,6 @@
<?php
/**
* Term aggregation cards (zibll zib_widget_ui_term_lists_card lite).
* Term aggregation cards (zibll zib_widget_ui_term_lists_card → zib_term_aggregation).
*
* @package AKSRT
*/
@@ -16,7 +16,7 @@ class WidgetTermAggregation extends \WP_Widget {
'aksrt_term_aggregation',
esc_html__('AKSRT 分类聚合', 'aksrt'),
[
'description' => esc_html__('分类封面 + 最新文章列表聚合卡。', 'aksrt'),
'description' => esc_html__('分类封面 + 描述 + 更多文章 CTA + 文章列表聚合卡。', 'aksrt'),
'classname' => 'widget_aksrt_term_aggregation',
]
);
@@ -37,15 +37,68 @@ class WidgetTermAggregation extends \WP_Widget {
return array_values(array_unique($ids));
}
/**
* @param \WP_Term $term
*/
private static function term_cover_url(\WP_Term $term): string {
if (class_exists(WidgetTermCards::class) && method_exists(WidgetTermCards::class, 'term_cover_url')) {
return WidgetTermCards::term_cover_url($term);
}
$img_id = (int) get_term_meta($term->term_id, 'thumbnail_id', true);
if (!$img_id) {
$img_id = (int) get_term_meta($term->term_id, '_aksrt_cover', true);
}
if ($img_id) {
$url = wp_get_attachment_image_url($img_id, 'medium');
if ($url) {
return (string) $url;
}
}
$url = (string) get_term_meta($term->term_id, '_aksrt_cover_url', true);
return $url !== '' ? $url : get_template_directory_uri() . '/assets/img/placeholder.svg';
}
/**
* Sum of post views under a term (best-effort).
*/
private static function term_views_sum(int $term_id, string $taxonomy): int {
$q = new \WP_Query([
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => 50,
'fields' => 'ids',
'no_found_rows' => true,
'update_post_meta_cache' => true,
'update_post_term_cache' => false,
'tax_query' => [[
'taxonomy' => $taxonomy,
'field' => 'term_id',
'terms' => $term_id,
]],
]);
$sum = 0;
foreach ($q->posts as $pid) {
$sum += function_exists('aksrt_get_post_views')
? (int) aksrt_get_post_views((int) $pid)
: (int) get_post_meta((int) $pid, 'aksrt_post_views', true);
}
return $sum;
}
/**
* @param array<string,mixed> $instance
*/
public static function render_html(array $instance): string {
$taxonomy = sanitize_key((string) ($instance['taxonomy'] ?? 'category'));
if (!taxonomy_exists($taxonomy)) {
$taxonomy = 'category';
}
$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)));
$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)));
$target_blank = !empty($instance['target_blank']);
$tgt = $target_blank ? ' target="_blank" rel="noopener noreferrer"' : '';
if (!$term_ids) {
$terms = get_terms([
@@ -74,16 +127,38 @@ class WidgetTermAggregation extends \WP_Widget {
if (is_wp_error($link)) {
continue;
}
$cover = '';
$img_id = (int) get_term_meta($term_id, 'thumbnail_id', true);
if (!$img_id) {
$img_id = (int) get_term_meta($term_id, '_aksrt_cover', true);
$cover = self::term_cover_url($term);
$views = self::term_views_sum((int) $term->term_id, $taxonomy);
$more_badge = '<badge class="img-badge px12"><i class="fa fa-fire" aria-hidden="true"></i> ' . esc_html((string) $views) . '</badge>';
$img_html = '';
if (function_exists('aksrt_graphic_card')) {
$img_html = aksrt_graphic_card([
'type' => '',
'class' => '',
'img' => $cover,
'alt' => $term->name,
'link' => [
'url' => $link,
'target' => $target_blank ? '_blank' : '',
],
'lazy' => true,
'height_scale' => 70,
'mask_opacity' => 0,
'more' => $more_badge,
]);
}
if ($img_id) {
$cover = wp_get_attachment_image_url($img_id, 'medium') ?: '';
}
if ($cover === '') {
$cover = (string) get_term_meta($term_id, '_aksrt_cover_url', true);
$description = trim((string) $term->description);
if ($description === '' && current_user_can('manage_categories')) {
$edit = get_edit_term_link((int) $term->term_id, $taxonomy);
$description = esc_html__('请在后台为该分类添加描述。', 'aksrt');
if ($edit) {
$description .= ' <a href="' . esc_url($edit) . '">' . esc_html__('立即编辑', 'aksrt') . '</a>';
}
} else {
$description = esc_html($description);
}
$posts_q = [
@@ -91,39 +166,75 @@ class WidgetTermAggregation extends \WP_Widget {
'post_status' => 'publish',
'posts_per_page' => $count,
'ignore_sticky_posts' => true,
'no_found_rows' => true,
'no_found_rows' => false,
'tax_query' => [[
'taxonomy' => $taxonomy,
'field' => 'term_id',
'terms' => $term_id,
'terms' => (int) $term->term_id,
]],
];
if ($orderby === 'views') {
$posts_q['meta_key'] = 'aksrt_post_views';
$posts_q['orderby'] = 'meta_value_num';
$posts_q['order'] = 'DESC';
$posts_q['orderby'] = 'meta_value_num';
$posts_q['order'] = 'DESC';
} elseif ($orderby === 'like') {
$posts_q['meta_key'] = 'aksrt_post_likes';
$posts_q['orderby'] = 'meta_value_num';
$posts_q['order'] = 'DESC';
} elseif ($orderby === 'comment_count') {
$posts_q['orderby'] = 'comment_count';
$posts_q['order'] = 'DESC';
} elseif ($orderby === 'modified') {
$posts_q['orderby'] = 'modified';
$posts_q['order'] = 'DESC';
$posts_q['order'] = 'DESC';
} else {
$posts_q['orderby'] = 'date';
$posts_q['order'] = 'DESC';
$posts_q['order'] = 'DESC';
}
$q = new \WP_Query($posts_q);
$found = (int) $q->found_posts;
echo '<div class="zib-widget term-aggregation mb10 aksrt-term-agg__card">';
echo '<div class="mb20 hover-zoom-img flex px12-sm">';
echo '<div class="term-img flex0 em09-sm">' . $img_html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="term-title ml10 flex xx flex1 jsb">';
echo '<a class="em14 key-color text-ellipsis"' . $tgt . ' href="' . esc_url($link) . '">' . esc_html($term->name) . '</a>';
echo '<div class="text-ellipsis-2 muted-color">' . $description . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="term-meta flex jsb ac">';
echo '<span class="px12 muted-3-color text-ellipsis"><i class="fa fa-file-text-o fa-fw" aria-hidden="true"></i>'
. esc_html(sprintf(
/* translators: %s: article count */
__('%s篇文章', 'aksrt'),
(string) $found
))
. '</span>';
echo '<a class="but p2-10 px12 c-blue hide-sm shrink0"' . $tgt . ' href="' . esc_url($link) . '"><i class="fa fa-angle-right" aria-hidden="true"></i>' . esc_html__('更多文章', 'aksrt') . '</a>';
echo '</div></div></div>';
echo '<div class="aksrt-term-agg__card">';
echo '<a class="aksrt-term-agg__head" href="' . esc_url($link) . '">';
echo '<div class="aksrt-term-agg__cover' . ($cover ? '' : ' is-empty') . '"' . ($cover ? ' style="background-image:url(' . esc_url($cover) . ')"' : '') . '></div>';
echo '<div class="aksrt-term-agg__name">' . esc_html($term->name) . '</div>';
echo '<div class="aksrt-term-agg__count">' . esc_html(sprintf(_n('%d 篇', '%d 篇', (int) $term->count, 'aksrt'), (int) $term->count)) . '</div>';
echo '</a>';
if ($q->have_posts()) {
echo '<ul class="aksrt-term-agg__posts">';
while ($q->have_posts()) {
$q->the_post();
echo '<li><a href="' . esc_url(get_permalink()) . '">' . esc_html(get_the_title()) . '</a></li>';
$pid = get_the_ID();
$title = get_the_title($pid);
$meta = '';
if ($orderby === 'views') {
$v = function_exists('aksrt_get_post_views') ? (int) aksrt_get_post_views($pid) : (int) get_post_meta($pid, 'aksrt_post_views', true);
$meta = '<i class="fa fa-eye mr3" aria-hidden="true"></i>' . esc_html((string) $v);
} elseif ($orderby === 'like') {
$v = function_exists('aksrt_get_post_likes') ? (int) aksrt_get_post_likes($pid) : (int) get_post_meta($pid, 'aksrt_post_likes', true);
$meta = '<i class="fa fa-heart mr3" aria-hidden="true"></i>' . esc_html((string) $v);
} elseif ($orderby === 'comment_count') {
$meta = '<i class="fa fa-comment-o mr3" aria-hidden="true"></i>' . esc_html((string) get_comments_number($pid));
} else {
$ts = $orderby === 'modified' ? get_the_modified_time('U', $pid) : get_the_time('U', $pid);
$ago = human_time_diff((int) $ts, current_time('timestamp'));
$meta = '<i class="fa fa-clock-o mr3" aria-hidden="true"></i>' . esc_html($ago);
}
echo '<div class="mt10 flex jsb ac">';
echo '<div class="text-ellipsis"><a class="icon-circle mln3"' . $tgt . ' href="' . esc_url(get_permalink($pid)) . '">' . esc_html($title) . '</a></div>';
echo '<div class="em09 muted-3-color flex0 ml10">' . $meta . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
}
echo '</ul>';
wp_reset_postdata();
}
echo '</div>';
@@ -132,6 +243,10 @@ class WidgetTermAggregation extends \WP_Widget {
return (string) ob_get_clean();
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$html = self::render_html($instance);
if ($html === '') {
@@ -146,11 +261,9 @@ class WidgetTermAggregation extends \WP_Widget {
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/** @return array<int,int> */
private function parse_ids(string $raw): array {
return self::parse_ids_static($raw);
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
$taxs = get_taxonomies(['public' => true], 'objects');
?>
@@ -187,25 +300,36 @@ class WidgetTermAggregation extends \WP_Widget {
<option value="date" <?php selected(($instance['orderby'] ?? 'date'), 'date'); ?>><?php esc_html_e('时间', 'aksrt'); ?></option>
<option value="modified" <?php selected(($instance['orderby'] ?? ''), 'modified'); ?>><?php esc_html_e('更新', 'aksrt'); ?></option>
<option value="views" <?php selected(($instance['orderby'] ?? ''), 'views'); ?>><?php esc_html_e('浏览', 'aksrt'); ?></option>
<option value="like" <?php selected(($instance['orderby'] ?? ''), 'like'); ?>><?php esc_html_e('点赞', 'aksrt'); ?></option>
<option value="comment_count" <?php selected(($instance['orderby'] ?? ''), 'comment_count'); ?>><?php esc_html_e('评论', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('target_blank')); ?>" value="1" <?php checked(!empty($instance['target_blank'])); ?>> <?php esc_html_e('新窗口打开', 'aksrt'); ?></label>
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'date'));
if (!in_array($orderby, ['date', 'modified', 'views'], true)) {
if (!in_array($orderby, ['date', 'modified', 'views', 'like', 'comment_count'], true)) {
$orderby = 'date';
}
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'taxonomy' => sanitize_key((string) ($new_instance['taxonomy'] ?? 'category')),
'term_ids' => sanitize_text_field((string) ($new_instance['term_ids'] ?? '')),
'count' => max(1, min(12, (int) ($new_instance['count'] ?? 4))),
'columns' => max(1, min(3, (int) ($new_instance['columns'] ?? 1))),
'orderby' => $orderby,
'auto_terms' => max(1, min(4, (int) ($new_instance['auto_terms'] ?? 2))),
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'taxonomy' => sanitize_key((string) ($new_instance['taxonomy'] ?? 'category')),
'term_ids' => sanitize_text_field((string) ($new_instance['term_ids'] ?? '')),
'count' => max(1, min(12, (int) ($new_instance['count'] ?? 4))),
'columns' => max(1, min(3, (int) ($new_instance['columns'] ?? 1))),
'orderby' => $orderby,
'auto_terms' => max(1, min(4, (int) ($new_instance['auto_terms'] ?? 2))),
'target_blank' => !empty($new_instance['target_blank']) ? 1 : 0,
];
}
}
+162 -41
View File
@@ -1,6 +1,6 @@
<?php
/**
* Taxonomy term cards (zibll zib_widget_ui_term_card lite).
* Taxonomy term graphic cards (zibll zib_widget_ui_term_card → zib_graphic_card).
*
* @package AKSRT
*/
@@ -16,7 +16,7 @@ class WidgetTermCards extends \WP_Widget {
'aksrt_term_cards',
esc_html__('AKSRT 分类/标签卡片', 'aksrt'),
[
'description' => esc_html__('以卡片展示分类或标签。', 'aksrt'),
'description' => esc_html__('以图文封面卡片展示分类或标签(style-1..4)。', 'aksrt'),
'classname' => 'widget_aksrt_term_cards',
]
);
@@ -37,15 +37,49 @@ class WidgetTermCards extends \WP_Widget {
return array_values(array_unique($ids));
}
/**
* Resolve term cover URL.
*/
public static function term_cover_url(\WP_Term $term): string {
$thumb = '';
$img_id = (int) get_term_meta($term->term_id, 'thumbnail_id', true);
if (!$img_id) {
$img_id = (int) get_term_meta($term->term_id, '_aksrt_cover', true);
}
if ($img_id) {
$thumb = (string) (wp_get_attachment_image_url($img_id, 'medium_large') ?: wp_get_attachment_image_url($img_id, 'medium') ?: '');
}
if ($thumb === '') {
$thumb = (string) get_term_meta($term->term_id, '_aksrt_cover_url', true);
}
if ($thumb === '') {
// Lightweight default gradient placeholder as data URI is heavy — use theme placeholder.
$thumb = get_template_directory_uri() . '/assets/img/placeholder.svg';
}
return $thumb;
}
/**
* @param array<string,mixed> $instance
*/
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 = self::parse_ids_static((string) ($instance['include'] ?? ''));
$number = max(1, min(30, (int) ($instance['number'] ?? 8)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'count'));
$include = self::parse_ids_static((string) ($instance['include'] ?? ''));
$hide_empty = !isset($instance['hide_empty']) || !empty($instance['hide_empty']);
$type = sanitize_key((string) ($instance['type'] ?? 'style-1'));
if (!in_array($type, ['style-1', 'style-2', 'style-3', 'style-4'], true)) {
$type = 'style-1';
}
$height_scale = max(30, min(120, (int) ($instance['height_scale'] ?? 60)));
$mask_opacity = max(0, min(80, (int) ($instance['mask_opacity'] ?? 10)));
$pc_row = max(1, min(6, (int) ($instance['pc_row'] ?? $instance['columns'] ?? 2)));
$m_row = max(1, min(3, (int) ($instance['m_row'] ?? 1)));
$target_blank = !empty($instance['target_blank']);
$term_args = [
'taxonomy' => $taxonomy,
@@ -67,42 +101,94 @@ class WidgetTermCards extends \WP_Widget {
: '<p class="widget-empty ajax-item">' . esc_html__('暂无分类', 'aksrt') . '</p>';
}
$cols = max(1, min(4, (int) ($instance['columns'] ?? 2)));
$icon = $taxonomy === 'category'
? '<i class="fa fa-folder-open-o mr6" aria-hidden="true"></i>'
: '<i class="fa fa-cube mr6" aria-hidden="true"></i>';
$is_row = count($terms) > 1;
// Bootstrap-like col classes already used elsewhere; keep numeric CSS vars as fallback.
$pc_span = max(1, (int) floor(12 / $pc_row));
$m_span = max(1, (int) floor(12 / $m_row));
$row_class = 'col-sm-' . $pc_span . ($m_row > 1 ? ' col-xs-' . $m_span : '');
ob_start();
echo '<div class="aksrt-term-cards ajax-item" style="--aksrt-tc-cols:' . esc_attr((string) $cols) . '">';
echo '<div class="aksrt-term-cards ajax-item' . ($is_row ? ' mb10' : ' mb20') . '">';
if ($is_row) {
echo '<div class="row gutters-5">';
}
foreach ($terms as $term) {
if (!($term instanceof \WP_Term)) {
continue;
}
$link = get_term_link($term);
if (is_wp_error($link)) {
continue;
}
$thumb = '';
if (function_exists('get_term_meta')) {
$img_id = (int) get_term_meta($term->term_id, 'thumbnail_id', true);
if (!$img_id) {
$img_id = (int) get_term_meta($term->term_id, '_aksrt_cover', true);
}
if ($img_id) {
$thumb = wp_get_attachment_image_url($img_id, 'medium') ?: '';
}
if ($thumb === '') {
$thumb = (string) get_term_meta($term->term_id, '_aksrt_cover_url', true);
$name = mb_strlen($term->name) > 16 ? mb_substr($term->name, 0, 16) . '…' : $term->name;
$desc = trim((string) $term->description);
if (mb_strlen($desc) > 24) {
$desc = mb_substr($desc, 0, 24) . '…';
}
$count = (int) $term->count;
$count_label = sprintf(
/* translators: %d: post count */
_n('%d篇文章', '%d篇文章', $count, 'aksrt'),
$count
);
$text1 = $name;
$text2 = $desc;
$text3 = $icon . esc_html($count_label);
if ($type === 'style-2' || $type === 'style-4') {
$text3 = '<item data-toggle="tooltip" title="' . esc_attr(sprintf(__('共%d篇文章', 'aksrt'), $count)) . '">' . $icon . $count . '</item>';
}
if ($type === 'style-3' || $type === 'style-4') {
$text1 = $icon . esc_html($name);
if ($type === 'style-3') {
$text3 = '<i class="fa mr6 fa-file-text-o" aria-hidden="true"></i>' . esc_html($count_label);
}
}
echo '<a class="aksrt-term-card" href="' . esc_url($link) . '">';
if ($thumb !== '') {
echo '<div class="aksrt-term-card__cover" style="background-image:url(' . esc_url($thumb) . ')"></div>';
$card_args = [
'type' => $type,
'class' => 'mb10',
'img' => self::term_cover_url($term),
'alt' => $name . ($desc !== '' ? '-' . $desc : ''),
'link' => [
'url' => $link,
'target' => $target_blank ? '_blank' : '',
],
'text1' => $text1,
'text2' => $text2,
'text3' => $text3,
'lazy' => true,
'height_scale' => $height_scale,
'mask_opacity' => $mask_opacity,
];
if ($is_row) {
echo '<div class="' . esc_attr($row_class) . '">';
}
if (function_exists('aksrt_graphic_card')) {
echo aksrt_graphic_card($card_args); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} else {
echo '<div class="aksrt-term-card__cover is-empty"></div>';
echo '<a class="aksrt-term-card" href="' . esc_url($link) . '"><div class="aksrt-term-card__name">' . esc_html($name) . '</div></a>';
}
echo '<div class="aksrt-term-card__body">';
echo '<div class="aksrt-term-card__name">' . esc_html($term->name) . '</div>';
echo '<div class="aksrt-term-card__count">' . esc_html(sprintf(_n('%d 篇', '%d 篇', (int) $term->count, 'aksrt'), (int) $term->count)) . '</div>';
echo '</div></a>';
if ($is_row) {
echo '</div>';
}
}
if ($is_row) {
echo '</div>';
}
echo '</div>';
return (string) ob_get_clean();
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$html = self::render_html($instance);
if ($html === '') {
@@ -117,11 +203,9 @@ class WidgetTermCards extends \WP_Widget {
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/** @return array<int,int> */
private function parse_ids(string $raw): array {
return self::parse_ids_static($raw);
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
$taxs = get_taxonomies(['public' => true], 'objects');
?>
@@ -143,8 +227,29 @@ class WidgetTermCards extends \WP_Widget {
<label><?php esc_html_e('数量', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="30" name="<?php echo esc_attr($this->get_field_name('number')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['number'] ?? 8))); ?>">
</label>
<label><?php esc_html_e('列数', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="4" name="<?php echo esc_attr($this->get_field_name('columns')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['columns'] ?? 2))); ?>">
<label><?php esc_html_e('PC 列', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="6" name="<?php echo esc_attr($this->get_field_name('pc_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['pc_row'] ?? $instance['columns'] ?? 2))); ?>">
</label>
<label><?php esc_html_e('移动列', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="3" name="<?php echo esc_attr($this->get_field_name('m_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['m_row'] ?? 1))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('卡片样式', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('type')); ?>">
<option value="style-1" <?php selected(($instance['type'] ?? 'style-1'), 'style-1'); ?>><?php esc_html_e('样式 1(左下标题)', 'aksrt'); ?></option>
<option value="style-2" <?php selected(($instance['type'] ?? ''), 'style-2'); ?>><?php esc_html_e('样式 2(居中)', 'aksrt'); ?></option>
<option value="style-3" <?php selected(($instance['type'] ?? ''), 'style-3'); ?>><?php esc_html_e('样式 3(左下多层)', 'aksrt'); ?></option>
<option value="style-4" <?php selected(($instance['type'] ?? ''), 'style-4'); ?>><?php esc_html_e('样式 4(图+下方文案)', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('高度占比 %', 'aksrt'); ?>
<input class="tiny-text" type="number" min="30" max="120" name="<?php echo esc_attr($this->get_field_name('height_scale')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['height_scale'] ?? 60))); ?>">
</label>
<label><?php esc_html_e('遮罩 0-80', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" max="80" name="<?php echo esc_attr($this->get_field_name('mask_opacity')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['mask_opacity'] ?? 10))); ?>">
</label>
</p>
<p>
@@ -162,20 +267,36 @@ class WidgetTermCards extends \WP_Widget {
</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>
<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><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('target_blank')); ?>" value="1" <?php checked(!empty($instance['target_blank'])); ?>> <?php esc_html_e('新窗口打开', 'aksrt'); ?></label>
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$type = sanitize_key((string) ($new_instance['type'] ?? 'style-1'));
if (!in_array($type, ['style-1', 'style-2', 'style-3', 'style-4'], true)) {
$type = 'style-1';
}
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'taxonomy' => sanitize_key((string) ($new_instance['taxonomy'] ?? 'category')),
'number' => max(1, min(30, (int) ($new_instance['number'] ?? 8))),
'columns' => max(1, min(4, (int) ($new_instance['columns'] ?? 2))),
'orderby' => in_array(($new_instance['orderby'] ?? ''), ['count', 'name', 'id'], true) ? $new_instance['orderby'] : 'count',
'include' => sanitize_text_field((string) ($new_instance['include'] ?? '')),
'hide_empty' => !empty($new_instance['hide_empty']) ? 1 : 0,
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'taxonomy' => sanitize_key((string) ($new_instance['taxonomy'] ?? 'category')),
'number' => max(1, min(30, (int) ($new_instance['number'] ?? 8))),
'pc_row' => max(1, min(6, (int) ($new_instance['pc_row'] ?? 2))),
'm_row' => max(1, min(3, (int) ($new_instance['m_row'] ?? 1))),
'columns' => max(1, min(6, (int) ($new_instance['pc_row'] ?? $new_instance['columns'] ?? 2))),
'type' => $type,
'height_scale' => max(30, min(120, (int) ($new_instance['height_scale'] ?? 60))),
'mask_opacity' => max(0, min(80, (int) ($new_instance['mask_opacity'] ?? 10))),
'orderby' => in_array(($new_instance['orderby'] ?? ''), ['count', 'name', 'id'], true) ? $new_instance['orderby'] : 'count',
'include' => sanitize_text_field((string) ($new_instance['include'] ?? '')),
'hide_empty' => !empty($new_instance['hide_empty']) ? 1 : 0,
'target_blank' => !empty($new_instance['target_blank']) ? 1 : 0,
];
}
}
+46 -26
View File
@@ -1,6 +1,6 @@
<?php
/**
* Registered user list (zibll widget_ui_user_lists + remote HTML).
* Registered user list (zibll widget_ui_user_lists → zib_author_card).
*
* @package AKSRT
*/
@@ -16,7 +16,7 @@ class WidgetUserLists extends \WP_Widget {
'aksrt_user_lists',
esc_html__('AKSRT 用户列表', 'aksrt'),
[
'description' => esc_html__('展示站点用户头像网格,支持排序与筛选。', 'aksrt'),
'description' => esc_html__('展示站点用户迷你名片(头像/身份/关注/简介)。', 'aksrt'),
'classname' => 'widget_aksrt_user_lists',
]
);
@@ -37,13 +37,17 @@ class WidgetUserLists extends \WP_Widget {
return array_values(array_unique($ids));
}
/**
* @param array<string,mixed> $instance
*/
public static function render_html(array $instance): string {
$number = max(1, min(50, (int) ($instance['number'] ?? 8)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'user_registered'));
$order = strtoupper((string) ($instance['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC';
$include = self::parse_ids_static((string) ($instance['include'] ?? ''));
$exclude = self::parse_ids_static((string) ($instance['exclude'] ?? ''));
$number = max(1, min(50, (int) ($instance['number'] ?? 8)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'user_registered'));
$order = strtoupper((string) ($instance['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC';
$include = self::parse_ids_static((string) ($instance['include'] ?? ''));
$exclude = self::parse_ids_static((string) ($instance['exclude'] ?? ''));
$hide_box = !empty($instance['hide_box']);
$blur_bg = !empty($instance['blur_bg']);
$query = [
'number' => $number,
@@ -102,26 +106,36 @@ class WidgetUserLists extends \WP_Widget {
}
ob_start();
$wrap = $hide_box ? 'aksrt-user-lists is-bare ajax-item' : 'aksrt-user-lists ajax-item';
echo '<div class="' . esc_attr($wrap) . '">';
// zibll: optional zib-widget + text-center user_lists of author-minicard.
$outer = $hide_box ? 'aksrt-user-lists is-bare ajax-item' : 'aksrt-user-lists zib-widget ajax-item';
echo '<div class="' . esc_attr($outer) . '">';
echo '<div class="text-center user_lists">';
foreach ($users as $user) {
$uid = (int) $user->ID;
$avatar = function_exists('aksrt_get_avatar_url')
? aksrt_get_avatar_url($uid, ['size' => 96])
: get_avatar_url($uid, ['size' => 96]);
$home = function_exists('aksrt_get_user_home_url')
? aksrt_get_user_home_url($uid)
: get_author_posts_url($uid);
$name = $user->display_name ?: $user->user_nicename;
echo '<a class="aksrt-user-lists__item" href="' . esc_url($home) . '" title="' . esc_attr($name) . '">';
echo '<img class="aksrt-user-lists__avatar" src="' . esc_url($avatar) . '" alt="' . esc_attr($name) . '" loading="lazy" width="48" height="48" />';
echo '<span class="aksrt-user-lists__name">' . esc_html($name) . '</span>';
echo '</a>';
if (function_exists('aksrt_author_minicard')) {
echo aksrt_author_minicard($uid, 'mb10', $blur_bg); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} else {
$avatar = function_exists('aksrt_get_avatar_url')
? aksrt_get_avatar_url($uid, ['size' => 96])
: get_avatar_url($uid, ['size' => 96]);
$home = function_exists('aksrt_get_user_home_url')
? aksrt_get_user_home_url($uid)
: get_author_posts_url($uid);
$name = $user->display_name ?: $user->user_nicename;
echo '<a class="author-minicard radius8" href="' . esc_url($home) . '">';
echo '<img class="avatar" src="' . esc_url($avatar) . '" alt="" loading="lazy" width="42" height="42" /> ';
echo esc_html($name);
echo '</a>';
}
}
echo '</div>';
echo '</div></div>';
return (string) ob_get_clean();
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$html = self::render_html($instance);
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
@@ -136,10 +150,9 @@ class WidgetUserLists extends \WP_Widget {
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
private function parse_ids(string $raw): array {
return self::parse_ids_static($raw);
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
?>
<p>
@@ -182,11 +195,17 @@ class WidgetUserLists extends \WP_Widget {
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hide_box')); ?>" value="1" <?php checked(!empty($instance['hide_box'])); ?>> <?php esc_html_e('不显示背景盒子', 'aksrt'); ?></label>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hide_box')); ?>" value="1" <?php checked(!empty($instance['hide_box'])); ?>> <?php esc_html_e('不显示背景盒子', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('blur_bg')); ?>" value="1" <?php checked(!empty($instance['blur_bg'])); ?>> <?php esc_html_e('头像模糊背景', 'aksrt'); ?></label>
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'user_registered'));
$allowed = ['user_registered', 'display_name', 'post_count', 'last_login', 'followed-user-count'];
@@ -201,6 +220,7 @@ class WidgetUserLists extends \WP_Widget {
'orderby' => $orderby,
'order' => strtoupper((string) ($new_instance['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC',
'hide_box' => !empty($new_instance['hide_box']) ? 1 : 0,
'blur_bg' => !empty($new_instance['blur_bg']) ? 1 : 0,
];
}
}