Files
wordpress-theme-aksrt/src/AKSRT/Widgets/WidgetForumPlateModerator.php
T
Claude d975ff4ce6 feat(phase2): shared common-form layer + CommonWidgetFields trait for all widgets
- Extract Zibll CSF_Widget::params() common-field parity into
  inc/modules/widget-common.php (required by widgets-register.php to keep
  both files <1000 lines): subtitle, title_style(simple/big), title_highlight
  +color, title_link x3, title_link_style colors, sidebar_affix, show_type
  (all/only_pc/only_sm), show_id_type(show/hide), show_ids, animation_in,
  animation_repeat, layout_pin_pc/m, layout_bg(white/dark). Adds
  normalize_link/normalize_bg helpers, affix_attrs, common_wrapper_attr,
  render_title, animation_class. Back-compat normalizes legacy tokens
  (in_affix->sidebar_affix, pc/only_pc, include/exclude->show/hide).
- Add src/AKSRT/Widgets/Traits/CommonWidgetFields.php trait
  (render_common_form_section + update_common_fields).
- Wire all 57 widgets to use the trait; migrate the 16 hand-rolled in_affix
  implementations to shared sidebar_affix (fixes 6 latent bugs where
  in_affix was never persisted). SearchWidget intentionally skips the shared
  form section (its business show_type collides) but adopts the affix helper.
- widget_display_callback parity verified via tmp assertion script.

Refs .specs/plans/zibll-widgets-1to1.md Phase 2 (US1/US5)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-30 20:42:46 +08:00

249 lines
11 KiB
PHP

<?php
/**
* Current plate moderators (zibll zib_bbs_widget_ui_plate_moderator).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
use AKSRT\Widgets\Traits\CommonWidgetFields;
defined('ABSPATH') || exit;
class WidgetForumPlateModerator extends \WP_Widget {
use CommonWidgetFields;
public function __construct() {
parent::__construct(
'aksrt_forum_plate_moderator',
esc_html__('论坛 - 版主列表', 'aksrt'),
[
'description' => esc_html__('显示当前版块的版主与超级版主。', 'aksrt'),
'classname' => 'widget_aksrt_forum_plate_moderator',
]
);
}
/**
* Build apply entry HTML (modal form).
*/
public static function render_apply_html(int $plate_id, bool $show_apply = true): string {
if (!$show_apply || $plate_id <= 0) {
return '';
}
$forum = class_exists('\AKSRT\Forum\Forum') ? \AKSRT\Forum\Forum::get_instance() : null;
$cuid = get_current_user_id();
$show_always = true;
if (function_exists('aksrt_get_setting')) {
$show_always = (bool) aksrt_get_setting('bbs_show_apply_moderator', true);
}
$can_apply = $cuid > 0 && $forum && method_exists($forum, 'forum_user_can')
? $forum->forum_user_can($cuid, 'moderator_apply')
: ($cuid > 0);
if (!$show_always && !$can_apply) {
return '';
}
$is_mod = false;
if ($cuid > 0 && $forum && method_exists($forum, 'is_board_moderator')) {
$is_mod = $forum->is_board_moderator($cuid, $plate_id);
}
if ($is_mod || ($cuid > 0 && user_can($cuid, 'manage_options'))) {
return '';
}
if ($cuid <= 0) {
$login = function_exists('aksrt_get_login_url') ? aksrt_get_login_url(get_permalink($plate_id)) : wp_login_url(get_permalink($plate_id));
return '<a class="but hollow c-blue p2-10 em09" href="' . esc_url($login) . '">' . esc_html__('登录后申请版主', 'aksrt') . '</a>';
}
$pending = false;
if ($forum && method_exists($forum, 'get_board_moderator_applications')) {
foreach ((array) $forum->get_board_moderator_applications($plate_id) as $app) {
if ((int) ($app['user_id'] ?? 0) === $cuid && (string) ($app['status'] ?? '') === 'pending') {
$pending = true;
break;
}
}
}
if ($pending) {
return '<span class="badg c-yellow hollow em09">' . esc_html__('申请审核中', 'aksrt') . '</span>';
}
$uid = 'aksrt-mod-apply-' . $plate_id . '-' . wp_rand(1000, 9999);
$desc = function_exists('aksrt_get_setting') ? (string) aksrt_get_setting('bbs_apply_moderator_desc', '') : '';
if ($desc === '') {
$desc = __('欢迎有热情的用户申请成为版主,协助管理社区秩序。', 'aksrt');
}
obs_start();
echo '<button type="button" class="but hollow c-blue p2-10 em09" data-aksrt-mod-apply-open="' . esc_attr((string) $uid) . '">' . esc_html__('申请版主', 'aksrt') . '</button>';
echo '<div class="aksrt-mod-apply-modal" id="' . esc_attr((string) $uid) . '" hidden>';
echo '<div class="aksrt-mod-apply-modal__backdrop" data-aksrt-mod-apply-close></div>';
echo '<div class="aksrt-mod-apply-modal__dialog zib-widget" role="dialog" aria-modal="true">';
echo '<div class="flex jsb ac mb10"><div class="title-theme">' . esc_html__('申请版主', 'aksrt') . '</div>';
echo '<button type="button" class="but hollow p2-10 em09" data-aksrt-mod-apply-close aria-label="' . esc_attr__('关闭', 'aksrt') . '">&times;</button></div>';
echo '<div class="muted-2-color em09 mb10">' . esc_html($desc) . '</div>';
echo '<form class="js-aksrt-moderator-apply-form" data-board-id="' . esc_attr((string) $plate_id) . '">';
echo '<label class="mb6 block">' . esc_html__('申请理由', 'aksrt') . '</label>';
echo '<textarea name="reason" rows="4" class="mb10" required placeholder="' . esc_attr__('请简要说明你的社区经验、擅长领域或管理计划。', 'aksrt') . '"></textarea>';
echo '<div class="js-aksrt-action-feedback mb10" aria-live="polite"></div>';
echo '<button type="submit" class="but c-blue padding-lg btn-block">' . esc_html__('提交版主申请', 'aksrt') . '</button>';
echo '</form></div></div>';
return (string) ob_get_clean();
}
/**
* @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 ['html' => ''];
}
$id = (int) $plate->ID;
$author_id = (int) $plate->post_author;
$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 || in_array($mid, $super, true)) {
continue;
}
$people[] = ['id' => $mid, 'role' => 'moderator'];
}
$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';
}
$show_apply = !isset($instance['apply_btn']) || !empty($instance['apply_btn']);
$apply_html = self::render_apply_html($id, $show_apply);
if (!$people && $apply_html === '') {
if (current_user_can('manage_options')) {
return [
'html' => function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('当前版块暂无版主', 'aksrt'))
: '<p class="widget-empty">' . esc_html__('当前版块暂无版主', 'aksrt') . '</p>',
];
}
return ['html' => ''];
}
obs_start();
echo '<div class="zib-widget moderator-box aksrt-plate-mods style-' . esc_attr($style) . ' ajax-item" data-aksrt-plate-mods="1">';
if ($title_text !== '' || $apply_html !== '') {
echo '<div class="mb10 flex jsb ac aksrt-plate-mods__head">';
echo $title_text !== '' ? '<div class="title-theme">' . esc_html($title_text) . '</div>' : '<div></div>';
echo $apply_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
}
if ($people) {
echo '<div class="aksrt-plate-mods__list">';
foreach ($people as $row) {
$uid = (int) $row['id'];
if (function_exists('aksrt_render_moderator_row')) {
echo aksrt_render_moderator_row($uid, (string) $row['role'], $show_avatar, $style); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
continue;
}
$user = get_userdata($uid);
if (!$user) {
continue;
}
$avatar = function_exists('aksrt_get_avatar_url')
? aksrt_get_avatar_url($uid, ['size' => 64])
: get_avatar_url($uid, ['size' => 64]);
$home = function_exists('aksrt_get_user_home_url')
? aksrt_get_user_home_url($uid)
: get_author_posts_url($uid);
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 class="aksrt-plate-mods__avatar aksrt-lazy lazyload" data-src="' . esc_url($avatar) . '" src="' . esc_url(get_template_directory_uri() . '/assets/img/placeholder.svg') . '" alt="" 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>';
} else {
echo '<div class="muted-2-color em09 mb10">' . esc_html__('暂无版主,欢迎申请加入管理。', 'aksrt') . '</div>';
}
echo '</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_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
}
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'); ?>
<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><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('apply_btn')); ?>" value="1" <?php checked(!isset($instance['apply_btn']) || !empty($instance['apply_btn'])); ?>> <?php esc_html_e('显示申请版主入口', 'aksrt'); ?></label>
</p>
<p class="description"><?php esc_html_e('仅在版块/帖子页显示。', 'aksrt'); ?></p>
<?php $this->render_common_form_section($instance); ?>
<?php
}
public function update($new_instance, $old_instance): array {
return $this->update_common_fields($new_instance, $old_instance, [
'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,
'apply_btn' => !empty($new_instance['apply_btn']) ? 1 : 0,
]);
}
}