refactor(classes): organize inc/classes into domain folders
Move ~94 root class shells and root *-traits into domain directories (core, auth, user, credits, content, payment/gateways, forum, shop, sms, ai, messages, extensions, widgets, settings, admin). Update bootstrap require paths, co-locate traits with owners, fix forum/user relative requires, drop duplicate shop controller requires, and wire home-modules load. Path-only; class names and init order unchanged.
This commit is contained in:
1 parent
2a8fe68048
commit
ce1c13b947
115 files changed
+171
-224
No files matched your search
@@ -0,0 +1,564 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT 第一批通用小工具 — v4.0 Zibll-aligned
|
||||
*
|
||||
* New widgets:
|
||||
* 1. AKSRT_Search_Widget — 搜索框
|
||||
* 2. AKSRT_Notice_Widget — 公告通知
|
||||
* 3. AKSRT_Recent_Comments_Widget — 最新评论
|
||||
* 4. AKSRT_Friend_Links_Widget — 友情链接
|
||||
* 5. AKSRT_Icon_Cards_Widget — 图标卡片
|
||||
* 6. AKSRT_Buttons_Widget — 按钮组
|
||||
* 7. AKSRT_Custom_HTML_Gap_Widget — 自定义 HTML / 间距
|
||||
*
|
||||
* @package AKSRT
|
||||
* @version 4.0.0
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
// ============================================================
|
||||
// 1. 搜索框小工具
|
||||
// ============================================================
|
||||
|
||||
class AKSRT_Search_Widget extends WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct('aksrt_search', esc_html__('AKSRT 搜索', 'aksrt'), [
|
||||
'description' => esc_html__('带样式的搜索框', 'aksrt'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
echo $args['before_widget'];
|
||||
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
|
||||
if ($title) echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
$placeholder = sanitize_text_field($instance['placeholder'] ?? __('搜索文章...', 'aksrt'));
|
||||
?>
|
||||
<div class="widget-search-box">
|
||||
<form role="search" method="get" action="<?php echo esc_url(home_url('/')); ?>">
|
||||
<div class="widget-search-box__inner">
|
||||
<input type="search" name="s" class="widget-search-box__input" placeholder="<?php echo esc_attr($placeholder); ?>" value="<?php echo esc_attr(get_search_query()); ?>" autocomplete="off">
|
||||
<button type="submit" class="widget-search-box__btn" aria-label="<?php esc_attr_e('搜索', 'aksrt'); ?>">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<?php
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题(可选):', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" value="<?php echo esc_attr($instance['title'] ?? ''); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('placeholder'); ?>"><?php esc_html_e('占位文本:', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" id="<?php echo $this->get_field_id('placeholder'); ?>" name="<?php echo $this->get_field_name('placeholder'); ?>" value="<?php echo esc_attr($instance['placeholder'] ?? __('搜索文章...', 'aksrt')); ?>">
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title' => sanitize_text_field($new_instance['title'] ?? ''),
|
||||
'placeholder' => sanitize_text_field($new_instance['placeholder'] ?? ''),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 2. 公告通知小工具
|
||||
// ============================================================
|
||||
|
||||
class AKSRT_Notice_Widget extends WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct('aksrt_notice', esc_html__('AKSRT 公告', 'aksrt'), [
|
||||
'description' => esc_html__('显示公告/通知信息', 'aksrt'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
echo $args['before_widget'];
|
||||
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
|
||||
$content = wp_kses_post($instance['content'] ?? '');
|
||||
$style = sanitize_key($instance['style'] ?? 'info');
|
||||
$icon = sanitize_text_field($instance['icon'] ?? 'fas fa-bullhorn');
|
||||
|
||||
if ($title) echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
?>
|
||||
<div class="widget-notice widget-notice--<?php echo esc_attr($style); ?>">
|
||||
<?php if ($icon) : ?>
|
||||
<div class="widget-notice__icon"><i class="<?php echo esc_attr($icon); ?>" aria-hidden="true"></i></div>
|
||||
<?php endif; ?>
|
||||
<div class="widget-notice__content"><?php echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
|
||||
</div>
|
||||
<?php
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题(可选):', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" value="<?php echo esc_attr($instance['title'] ?? ''); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('content'); ?>"><?php esc_html_e('公告内容:', 'aksrt'); ?></label>
|
||||
<textarea class="widefat" rows="4" id="<?php echo $this->get_field_id('content'); ?>" name="<?php echo $this->get_field_name('content'); ?>"><?php echo esc_textarea($instance['content'] ?? ''); ?></textarea>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('style'); ?>"><?php esc_html_e('样式:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo $this->get_field_id('style'); ?>" name="<?php echo $this->get_field_name('style'); ?>">
|
||||
<option value="info" <?php selected($instance['style'] ?? '', 'info'); ?>><?php esc_html_e('信息(蓝色)', 'aksrt'); ?></option>
|
||||
<option value="success" <?php selected($instance['style'] ?? '', 'success'); ?>><?php esc_html_e('成功(绿色)', 'aksrt'); ?></option>
|
||||
<option value="warning" <?php selected($instance['style'] ?? '', 'warning'); ?>><?php esc_html_e('警告(黄色)', 'aksrt'); ?></option>
|
||||
<option value="danger" <?php selected($instance['style'] ?? '', 'danger'); ?>><?php esc_html_e('危险(红色)', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('icon'); ?>"><?php esc_html_e('图标类名:', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" id="<?php echo $this->get_field_id('icon'); ?>" name="<?php echo $this->get_field_name('icon'); ?>" value="<?php echo esc_attr($instance['icon'] ?? 'fas fa-bullhorn'); ?>">
|
||||
<small style="color:#666;"><?php esc_html_e('Font Awesome 类名,如: fas fa-bullhorn', 'aksrt'); ?></small>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title' => sanitize_text_field($new_instance['title'] ?? ''),
|
||||
'content' => wp_kses_post($new_instance['content'] ?? ''),
|
||||
'style' => in_array($new_instance['style'] ?? '', ['info', 'success', 'warning', 'danger'], true) ? $new_instance['style'] : 'info',
|
||||
'icon' => sanitize_text_field($new_instance['icon'] ?? 'fas fa-bullhorn'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 3. 最新评论小工具
|
||||
// ============================================================
|
||||
|
||||
class AKSRT_Recent_Comments_Widget extends WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct('aksrt_recent_comments', esc_html__('AKSRT 最新评论', 'aksrt'), [
|
||||
'description' => esc_html__('显示最新评论列表', 'aksrt'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
echo $args['before_widget'];
|
||||
$title = apply_filters('widget_title', $instance['title'] ?? __('最新评论', 'aksrt'), $instance, $this->id_base);
|
||||
$count = max(1, min(20, (int) ($instance['count'] ?? 5)));
|
||||
$show_avatar = !empty($instance['show_avatar']);
|
||||
|
||||
if ($title) echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
|
||||
$comments = get_comments([
|
||||
'number' => $count,
|
||||
'status' => 'approve',
|
||||
'post_status' => 'publish',
|
||||
]);
|
||||
|
||||
if ($comments) : ?>
|
||||
<div class="widget-recent-comments">
|
||||
<?php foreach ($comments as $comment) :
|
||||
$post = get_post($comment->comment_post_ID);
|
||||
if (!$post) continue;
|
||||
?>
|
||||
<div class="widget-recent-comments__item">
|
||||
<?php if ($show_avatar) : ?>
|
||||
<div class="widget-recent-comments__avatar">
|
||||
<?php echo get_avatar($comment, 32); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="widget-recent-comments__body">
|
||||
<div class="widget-recent-comments__meta">
|
||||
<strong class="widget-recent-comments__author"><?php echo esc_html($comment->comment_author); ?></strong>
|
||||
<span class="widget-recent-comments__time"><?php echo esc_html(human_time_diff(strtotime($comment->comment_date), current_time('timestamp'))); ?></span>
|
||||
</div>
|
||||
<p class="widget-recent-comments__text">
|
||||
<a href="<?php echo esc_url(get_comment_link($comment)); ?>"><?php echo esc_html(wp_trim_words($comment->comment_content, 15, '...')); ?></a>
|
||||
</p>
|
||||
<span class="widget-recent-comments__post">
|
||||
<?php esc_html_e('于', 'aksrt'); ?> <a href="<?php echo esc_url(get_permalink($post->ID)); ?>"><?php echo esc_html(get_the_title($post->ID)); ?></a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<?php echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无评论', 'aksrt')) : '<p class="widget-empty">' . esc_html__('暂无评论', 'aksrt') . '</p>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<?php endif;
|
||||
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" value="<?php echo esc_attr($instance['title'] ?? __('最新评论', 'aksrt')); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('count'); ?>"><?php esc_html_e('显示数量:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" type="number" min="1" max="20" id="<?php echo $this->get_field_id('count'); ?>" name="<?php echo $this->get_field_name('count'); ?>" value="<?php echo (int) ($instance['count'] ?? 5); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo $this->get_field_name('show_avatar'); ?>" value="1" <?php checked(!empty($instance['show_avatar'])); ?>> <?php esc_html_e('显示头像', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title' => sanitize_text_field($new_instance['title'] ?? ''),
|
||||
'count' => max(1, min(20, (int) ($new_instance['count'] ?? 5))),
|
||||
'show_avatar' => !empty($new_instance['show_avatar']),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 4. 友情链接小工具
|
||||
// ============================================================
|
||||
|
||||
class AKSRT_Friend_Links_Widget extends WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct('aksrt_friend_links', esc_html__('AKSRT 友情链接', 'aksrt'), [
|
||||
'description' => esc_html__('显示友情链接列表', 'aksrt'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
echo $args['before_widget'];
|
||||
$title = apply_filters('widget_title', $instance['title'] ?? __('友情链接', 'aksrt'), $instance, $this->id_base);
|
||||
$show_icon = !empty($instance['show_icon']);
|
||||
$columns = max(1, min(4, (int) ($instance['columns'] ?? 2)));
|
||||
|
||||
if ($title) echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
|
||||
$links = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_friend_links() : [];
|
||||
|
||||
if (!empty($links)) : ?>
|
||||
<div class="widget-friend-links" style="--fl-columns:<?php echo (int) $columns; ?>;">
|
||||
<?php foreach ($links as $link) :
|
||||
$name = sanitize_text_field($link['name'] ?? '');
|
||||
$url = esc_url($link['url'] ?? '');
|
||||
$desc = sanitize_text_field($link['description'] ?? '');
|
||||
$icon = esc_url($link['icon'] ?? '');
|
||||
if ($name === '' || $url === '') continue;
|
||||
?>
|
||||
<a href="<?php echo $url; ?>" class="widget-friend-links__item" target="_blank" rel="noopener noreferrer" title="<?php echo esc_attr($desc); ?>">
|
||||
<?php if ($show_icon && $icon) : ?>
|
||||
<img src="<?php echo $icon; ?>" alt="" class="widget-friend-links__icon" width="16" height="16"<?php echo function_exists('aksrt_lazy_loading') ? aksrt_lazy_loading('widget') : ' loading="lazy"'; ?>>
|
||||
<?php endif; ?>
|
||||
<span class="widget-friend-links__name"><?php echo esc_html($name); ?></span>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<?php echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无友情链接', 'aksrt')) : '<p class="widget-empty">' . esc_html__('暂无友情链接', 'aksrt') . '</p>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<?php endif;
|
||||
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" value="<?php echo esc_attr($instance['title'] ?? __('友情链接', 'aksrt')); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('columns'); ?>"><?php esc_html_e('列数:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" type="number" min="1" max="4" id="<?php echo $this->get_field_id('columns'); ?>" name="<?php echo $this->get_field_name('columns'); ?>" value="<?php echo (int) ($instance['columns'] ?? 2); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo $this->get_field_name('show_icon'); ?>" value="1" <?php checked(!empty($instance['show_icon'])); ?>> <?php esc_html_e('显示站点图标', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<p style="color:#666;font-size:12px;"><?php esc_html_e('链接数据在后台"友链管理"中配置。', 'aksrt'); ?></p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title' => sanitize_text_field($new_instance['title'] ?? ''),
|
||||
'columns' => max(1, min(4, (int) ($new_instance['columns'] ?? 2))),
|
||||
'show_icon'=> !empty($new_instance['show_icon']),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 5. 图标卡片小工具
|
||||
// ============================================================
|
||||
|
||||
class AKSRT_Icon_Cards_Widget extends WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct('aksrt_icon_cards', esc_html__('AKSRT 图标卡片', 'aksrt'), [
|
||||
'description' => esc_html__('显示图标+文字卡片网格', 'aksrt'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
echo $args['before_widget'];
|
||||
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
|
||||
$cards = isset($instance['cards']) && is_array($instance['cards']) ? $instance['cards'] : [];
|
||||
$columns = max(1, min(4, (int) ($instance['columns'] ?? 2)));
|
||||
|
||||
if ($title) echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
|
||||
if (!empty($cards)) : ?>
|
||||
<div class="widget-icon-cards" style="--ic-columns:<?php echo (int) $columns; ?>;">
|
||||
<?php foreach ($cards as $card) :
|
||||
$card_title = sanitize_text_field($card['title'] ?? '');
|
||||
$card_icon = sanitize_text_field($card['icon'] ?? '');
|
||||
$card_link = esc_url($card['link'] ?? '');
|
||||
$card_desc = sanitize_text_field($card['description'] ?? '');
|
||||
if ($card_title === '') continue;
|
||||
$tag = $card_link !== '' ? 'a' : 'div';
|
||||
$extra = $card_link !== '' ? ' href="' . $card_link . '" target="_blank" rel="noopener"' : '';
|
||||
?>
|
||||
<<?php echo $tag; ?> class="widget-icon-cards__item"<?php echo $extra; ?>>
|
||||
<?php if ($card_icon) : ?>
|
||||
<div class="widget-icon-cards__icon"><i class="<?php echo esc_attr($card_icon); ?>" aria-hidden="true"></i></div>
|
||||
<?php endif; ?>
|
||||
<div class="widget-icon-cards__text">
|
||||
<div class="widget-icon-cards__title"><?php echo esc_html($card_title); ?></div>
|
||||
<?php if ($card_desc) : ?>
|
||||
<div class="widget-icon-cards__desc"><?php echo esc_html($card_desc); ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</<?php echo $tag; ?>>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif;
|
||||
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$cards = isset($instance['cards']) && is_array($instance['cards']) ? $instance['cards'] : [];
|
||||
while (count($cards) < 4) {
|
||||
$cards[] = ['title' => '', 'icon' => '', 'link' => '', 'description' => ''];
|
||||
}
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题(可选):', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" value="<?php echo esc_attr($instance['title'] ?? ''); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('columns'); ?>"><?php esc_html_e('列数:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" type="number" min="1" max="4" id="<?php echo $this->get_field_id('columns'); ?>" name="<?php echo $this->get_field_name('columns'); ?>" value="<?php echo (int) ($instance['columns'] ?? 2); ?>">
|
||||
</p>
|
||||
<hr>
|
||||
<?php foreach ($cards as $i => $card) : ?>
|
||||
<div style="border:1px solid #ddd;padding:6px;margin-bottom:6px;border-radius:4px;">
|
||||
<p style="margin:0 0 4px;"><input class="widefat" type="text" name="<?php echo $this->get_field_name("cards[{$i}][title]"); ?>" value="<?php echo esc_attr($card['title']); ?>" placeholder="<?php esc_attr_e('标题', 'aksrt'); ?>"></p>
|
||||
<p style="margin:0 0 4px;"><input class="widefat" type="text" name="<?php echo $this->get_field_name("cards[{$i}][icon]"); ?>" value="<?php echo esc_attr($card['icon']); ?>" placeholder="<?php esc_attr_e('图标类名 (如 fas fa-star)', 'aksrt'); ?>"></p>
|
||||
<p style="margin:0 0 4px;"><input class="widefat" type="text" name="<?php echo $this->get_field_name("cards[{$i}][description]"); ?>" value="<?php echo esc_attr($card['description']); ?>" placeholder="<?php esc_attr_e('描述(可选)', 'aksrt'); ?>"></p>
|
||||
<p style="margin:0;"><input class="widefat" type="url" name="<?php echo $this->get_field_name("cards[{$i}][link]"); ?>" value="<?php echo esc_attr($card['link']); ?>" placeholder="<?php esc_attr_e('链接(可选)', 'aksrt'); ?>"></p>
|
||||
</div>
|
||||
<?php endforeach;
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$cards = [];
|
||||
if (isset($new_instance['cards']) && is_array($new_instance['cards'])) {
|
||||
foreach ($new_instance['cards'] as $card) {
|
||||
if (!is_array($card)) continue;
|
||||
$cards[] = [
|
||||
'title' => sanitize_text_field($card['title'] ?? ''),
|
||||
'icon' => sanitize_text_field($card['icon'] ?? ''),
|
||||
'link' => esc_url_raw($card['link'] ?? ''),
|
||||
'description' => sanitize_text_field($card['description'] ?? ''),
|
||||
];
|
||||
}
|
||||
}
|
||||
return [
|
||||
'title' => sanitize_text_field($new_instance['title'] ?? ''),
|
||||
'columns' => max(1, min(4, (int) ($new_instance['columns'] ?? 2))),
|
||||
'cards' => $cards,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 6. 按钮组小工具
|
||||
// ============================================================
|
||||
|
||||
class AKSRT_Buttons_Widget extends WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct('aksrt_buttons', esc_html__('AKSRT 按钮组', 'aksrt'), [
|
||||
'description' => esc_html__('显示一组自定义按钮', 'aksrt'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
echo $args['before_widget'];
|
||||
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
|
||||
$buttons = isset($instance['buttons']) && is_array($instance['buttons']) ? $instance['buttons'] : [];
|
||||
$layout = sanitize_key($instance['layout'] ?? 'vertical');
|
||||
|
||||
if ($title) echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
|
||||
if (!empty($buttons)) : ?>
|
||||
<div class="widget-buttons widget-buttons--<?php echo esc_attr($layout); ?>">
|
||||
<?php foreach ($buttons as $btn) :
|
||||
$btn_text = sanitize_text_field($btn['text'] ?? '');
|
||||
$btn_url = esc_url($btn['url'] ?? '');
|
||||
$btn_style = sanitize_key($btn['style'] ?? 'primary');
|
||||
$btn_icon = sanitize_text_field($btn['icon'] ?? '');
|
||||
$btn_target = !empty($btn['new_window']) ? '_blank' : '_self';
|
||||
if ($btn_text === '' || $btn_url === '') continue;
|
||||
?>
|
||||
<a href="<?php echo $btn_url; ?>" class="widget-buttons__btn widget-buttons__btn--<?php echo esc_attr($btn_style); ?>" target="<?php echo esc_attr($btn_target); ?>" rel="noopener">
|
||||
<?php if ($btn_icon) : ?><i class="<?php echo esc_attr($btn_icon); ?>" aria-hidden="true"></i> <?php endif; ?>
|
||||
<?php echo esc_html($btn_text); ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif;
|
||||
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$buttons = isset($instance['buttons']) && is_array($instance['buttons']) ? $instance['buttons'] : [];
|
||||
while (count($buttons) < 3) {
|
||||
$buttons[] = ['text' => '', 'url' => '', 'style' => 'primary', 'icon' => '', 'new_window' => false];
|
||||
}
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题(可选):', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" value="<?php echo esc_attr($instance['title'] ?? ''); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('layout'); ?>"><?php esc_html_e('布局:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo $this->get_field_id('layout'); ?>" name="<?php echo $this->get_field_name('layout'); ?>">
|
||||
<option value="vertical" <?php selected($instance['layout'] ?? '', 'vertical'); ?>><?php esc_html_e('垂直', 'aksrt'); ?></option>
|
||||
<option value="horizontal" <?php selected($instance['layout'] ?? '', 'horizontal'); ?>><?php esc_html_e('水平', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<hr>
|
||||
<?php foreach ($buttons as $i => $btn) : ?>
|
||||
<div style="border:1px solid #ddd;padding:6px;margin-bottom:6px;border-radius:4px;">
|
||||
<p style="margin:0 0 4px;">
|
||||
<input class="widefat" type="text" name="<?php echo $this->get_field_name("buttons[{$i}][text]"); ?>" value="<?php echo esc_attr($btn['text']); ?>" placeholder="<?php esc_attr_e('按钮文字', 'aksrt'); ?>">
|
||||
</p>
|
||||
<p style="margin:0 0 4px;">
|
||||
<input class="widefat" type="url" name="<?php echo $this->get_field_name("buttons[{$i}][url]"); ?>" value="<?php echo esc_attr($btn['url']); ?>" placeholder="<?php esc_attr_e('链接地址', 'aksrt'); ?>">
|
||||
</p>
|
||||
<p style="margin:0 0 4px;">
|
||||
<select class="widefat" name="<?php echo $this->get_field_name("buttons[{$i}][style]"); ?>">
|
||||
<option value="primary" <?php selected($btn['style'] ?? '', 'primary'); ?>><?php esc_html_e('主要', 'aksrt'); ?></option>
|
||||
<option value="secondary" <?php selected($btn['style'] ?? '', 'secondary'); ?>><?php esc_html_e('次要', 'aksrt'); ?></option>
|
||||
<option value="outline" <?php selected($btn['style'] ?? '', 'outline'); ?>><?php esc_html_e('线框', 'aksrt'); ?></option>
|
||||
<option value="text" <?php selected($btn['style'] ?? '', 'text'); ?>><?php esc_html_e('文字', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<p style="margin:0 0 4px;">
|
||||
<input class="widefat" type="text" name="<?php echo $this->get_field_name("buttons[{$i}][icon]"); ?>" value="<?php echo esc_attr($btn['icon'] ?? ''); ?>" placeholder="<?php esc_attr_e('图标(可选)', 'aksrt'); ?>">
|
||||
</p>
|
||||
<p style="margin:0;">
|
||||
<label><input type="checkbox" name="<?php echo $this->get_field_name("buttons[{$i}][new_window]"); ?>" value="1" <?php checked(!empty($btn['new_window'])); ?>> <?php esc_html_e('新窗口打开', 'aksrt'); ?></label>
|
||||
</p>
|
||||
</div>
|
||||
<?php endforeach;
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$buttons = [];
|
||||
if (isset($new_instance['buttons']) && is_array($new_instance['buttons'])) {
|
||||
$allowed_styles = ['primary', 'secondary', 'outline', 'text'];
|
||||
foreach ($new_instance['buttons'] as $btn) {
|
||||
if (!is_array($btn)) continue;
|
||||
$style = sanitize_key($btn['style'] ?? 'primary');
|
||||
if (!in_array($style, $allowed_styles, true)) $style = 'primary';
|
||||
$buttons[] = [
|
||||
'text' => sanitize_text_field($btn['text'] ?? ''),
|
||||
'url' => esc_url_raw($btn['url'] ?? ''),
|
||||
'style' => $style,
|
||||
'icon' => sanitize_text_field($btn['icon'] ?? ''),
|
||||
'new_window' => !empty($btn['new_window']),
|
||||
];
|
||||
}
|
||||
}
|
||||
return [
|
||||
'title' => sanitize_text_field($new_instance['title'] ?? ''),
|
||||
'layout' => in_array($new_instance['layout'] ?? '', ['vertical', 'horizontal'], true) ? $new_instance['layout'] : 'vertical',
|
||||
'buttons' => $buttons,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 7. 自定义 HTML / 间距小工具
|
||||
// ============================================================
|
||||
|
||||
class AKSRT_Custom_HTML_Gap_Widget extends WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct('aksrt_custom_html_gap', esc_html__('AKSRT 自定义HTML/间距', 'aksrt'), [
|
||||
'description' => esc_html__('插入自定义 HTML 或间距', 'aksrt'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$mode = sanitize_key($instance['mode'] ?? 'html');
|
||||
|
||||
if ($mode === 'gap') {
|
||||
$height = max(0, min(200, (int) ($instance['gap_height'] ?? 20)));
|
||||
echo '<div class="widget-custom-gap" style="height:' . (int) $height . 'px;" aria-hidden="true"></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
echo $args['before_widget'];
|
||||
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
|
||||
if ($title) echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
echo '<div class="widget-custom-html">' . wp_kses_post($instance['content'] ?? '') . '</div>';
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$mode = sanitize_key($instance['mode'] ?? 'html');
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('mode'); ?>"><?php esc_html_e('模式:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo $this->get_field_id('mode'); ?>" name="<?php echo $this->get_field_name('mode'); ?>">
|
||||
<option value="html" <?php selected($mode, 'html'); ?>><?php esc_html_e('自定义 HTML', 'aksrt'); ?></option>
|
||||
<option value="gap" <?php selected($mode, 'gap'); ?>><?php esc_html_e('间距', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题(仅 HTML 模式,可选):', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" value="<?php echo esc_attr($instance['title'] ?? ''); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('content'); ?>"><?php esc_html_e('HTML 内容:', 'aksrt'); ?></label>
|
||||
<textarea class="widefat" rows="6" id="<?php echo $this->get_field_id('content'); ?>" name="<?php echo $this->get_field_name('content'); ?>"><?php echo esc_textarea($instance['content'] ?? ''); ?></textarea>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('gap_height'); ?>"><?php esc_html_e('间距高度(px,仅间距模式):', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" type="number" min="0" max="200" id="<?php echo $this->get_field_id('gap_height'); ?>" name="<?php echo $this->get_field_name('gap_height'); ?>" value="<?php echo (int) ($instance['gap_height'] ?? 20); ?>">
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'mode' => in_array($new_instance['mode'] ?? '', ['html', 'gap'], true) ? $new_instance['mode'] : 'html',
|
||||
'title' => sanitize_text_field($new_instance['title'] ?? ''),
|
||||
'content' => wp_kses_post($new_instance['content'] ?? ''),
|
||||
'gap_height' => max(0, min(200, (int) ($new_instance['gap_height'] ?? 20))),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,467 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT 新增小工具 — v4.0 Zibll-Inspired
|
||||
*
|
||||
* 新增:
|
||||
* 1. AKSRT_Hot_Posts_Widget — 热榜文章(按浏览/评论/点赞排序)
|
||||
* 2. AKSRT_Tab_Posts_Widget — 多栏目 Tab 切换文章列表
|
||||
*
|
||||
* @package AKSRT
|
||||
* @version 4.0.0
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
// ============================================================
|
||||
// 1. 热榜文章小工具
|
||||
// ============================================================
|
||||
|
||||
class AKSRT_Hot_Posts_Widget extends WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_hot_posts',
|
||||
esc_html__('AKSRT 热榜文章', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('按浏览量/评论数/点赞数排序的热门文章列表', 'aksrt'),
|
||||
'customize_selective_refresh' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
echo $args['before_widget'];
|
||||
|
||||
$title = apply_filters('widget_title', $instance['title'] ?? __('热门文章', 'aksrt'), $instance, $this->id_base);
|
||||
$count = absint($instance['count'] ?? 5);
|
||||
$order_by = $instance['order_by'] ?? 'views';
|
||||
$days = absint($instance['days'] ?? 30);
|
||||
|
||||
if ($title) {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
}
|
||||
|
||||
$query_args = [
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $count,
|
||||
'ignore_sticky_posts' => true,
|
||||
'no_found_rows' => true,
|
||||
];
|
||||
|
||||
if ($days > 0 && $days < 365) {
|
||||
$query_args['date_query'] = [['after' => $days . ' days ago']];
|
||||
}
|
||||
|
||||
switch ($order_by) {
|
||||
case 'comments':
|
||||
$query_args['orderby'] = 'comment_count';
|
||||
$query_args['order'] = 'DESC';
|
||||
break;
|
||||
case 'likes':
|
||||
$query_args['meta_key'] = '_aksrt_like_count';
|
||||
$query_args['orderby'] = 'meta_value_num';
|
||||
$query_args['order'] = 'DESC';
|
||||
break;
|
||||
case 'favorites':
|
||||
$query_args['meta_key'] = '_aksrt_favorite_count';
|
||||
$query_args['orderby'] = 'meta_value_num';
|
||||
$query_args['order'] = 'DESC';
|
||||
break;
|
||||
case 'downloads':
|
||||
$query_args['meta_key'] = '_aksrt_res_download_count';
|
||||
$query_args['orderby'] = 'meta_value_num';
|
||||
$query_args['order'] = 'DESC';
|
||||
break;
|
||||
case 'views':
|
||||
default:
|
||||
$query_args['meta_key'] = 'aksrt_post_views';
|
||||
$query_args['orderby'] = 'meta_value_num';
|
||||
$query_args['order'] = 'DESC';
|
||||
break;
|
||||
}
|
||||
|
||||
$query = new WP_Query($query_args);
|
||||
|
||||
if ($query->have_posts()) {
|
||||
echo '<div class="widget-hot-posts widget-hot-posts--mini">';
|
||||
$index = 0;
|
||||
while ($query->have_posts()) {
|
||||
$query->the_post();
|
||||
$index++;
|
||||
$rank_class = $index <= 3 ? ' rank-top' : '';
|
||||
$views = (int) get_post_meta(get_the_ID(), 'aksrt_post_views', true);
|
||||
?>
|
||||
<div class="hot-post-item <?php echo esc_attr($rank_class); ?>">
|
||||
<span class="hot-post-item__rank"><?php echo esc_html($index); ?></span>
|
||||
<div class="hot-post-item__info">
|
||||
<a href="<?php the_permalink(); ?>" class="hot-post-item__title"><?php the_title(); ?></a>
|
||||
<div class="hot-post-item__meta">
|
||||
<span><i class="fas fa-eye" aria-hidden="true"></i> <?php echo esc_html(function_exists('aksrt_format_number') ? aksrt_format_number($views) : (string) $views); ?></span>
|
||||
<span class="hot-post-item__time"><?php echo esc_html(human_time_diff(get_the_time('U'), current_time('timestamp')) . __('前', 'aksrt')); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
echo '</div>';
|
||||
wp_reset_postdata();
|
||||
} else {
|
||||
echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无热门文章', 'aksrt')) : '<p class="widget-empty">' . esc_html__('暂无热门文章', 'aksrt') . '</p>';
|
||||
}
|
||||
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = $instance['title'] ?? __('热门文章', 'aksrt');
|
||||
$count = (int) ($instance['count'] ?? 5);
|
||||
$order_by = $instance['order_by'] ?? 'views';
|
||||
$days = (int) ($instance['days'] ?? 30);
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('count'); ?>"><?php esc_html_e('显示数量:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo $this->get_field_id('count'); ?>" name="<?php echo $this->get_field_name('count'); ?>" type="number" min="1" max="20" value="<?php echo $count; ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('order_by'); ?>"><?php esc_html_e('排序方式:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo $this->get_field_id('order_by'); ?>" name="<?php echo $this->get_field_name('order_by'); ?>">
|
||||
<option value="views" <?php selected($order_by, 'views'); ?>><?php esc_html_e('浏览量', 'aksrt'); ?></option>
|
||||
<option value="comments" <?php selected($order_by, 'comments'); ?>><?php esc_html_e('评论数', 'aksrt'); ?></option>
|
||||
<option value="likes" <?php selected($order_by, 'likes'); ?>><?php esc_html_e('点赞数', 'aksrt'); ?></option>
|
||||
<option value="favorites" <?php selected($order_by, 'favorites'); ?>><?php esc_html_e('收藏数', 'aksrt'); ?></option>
|
||||
<option value="downloads" <?php selected($order_by, 'downloads'); ?>><?php esc_html_e('下载数', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('days'); ?>"><?php esc_html_e('时间范围(天,0=不限):', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo $this->get_field_id('days'); ?>" name="<?php echo $this->get_field_name('days'); ?>" type="number" min="0" max="365" value="<?php echo $days; ?>">
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title' => sanitize_text_field($new_instance['title'] ?? ''),
|
||||
'count' => absint($new_instance['count'] ?? 5),
|
||||
'order_by' => in_array($new_instance['order_by'] ?? '', ['views', 'comments', 'likes', 'favorites', 'downloads'], true) ? $new_instance['order_by'] : 'views',
|
||||
'days' => absint($new_instance['days'] ?? 30),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 2. 多栏目 Tab 切换文章列表小工具
|
||||
// ============================================================
|
||||
|
||||
class AKSRT_Tab_Posts_Widget extends WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_tab_posts',
|
||||
esc_html__('AKSRT 多栏目文章', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('Tab 切换显示不同分类的文章列表', 'aksrt'),
|
||||
'customize_selective_refresh' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize tabs from legacy comma-separated string or structured array.
|
||||
*
|
||||
* @param mixed $tabs Raw tabs data.
|
||||
* @return array Normalized structured tabs.
|
||||
*/
|
||||
private function normalize_tabs($tabs): array {
|
||||
// Legacy comma-separated format
|
||||
if (is_string($tabs) && $tabs !== '') {
|
||||
$legacy_keys = array_map('trim', explode(',', $tabs));
|
||||
$legacy_defs = [
|
||||
'latest' => ['title' => '最新', 'orderby' => 'date'],
|
||||
'comments' => ['title' => '热评', 'orderby' => 'comments'],
|
||||
'random' => ['title' => '随机', 'orderby' => 'rand'],
|
||||
'views' => ['title' => '热门', 'orderby' => 'views'],
|
||||
];
|
||||
$result = [];
|
||||
foreach ($legacy_keys as $key) {
|
||||
if (isset($legacy_defs[$key])) {
|
||||
$result[] = [
|
||||
'title' => __($legacy_defs[$key]['title'], 'aksrt'),
|
||||
'category_id' => 0,
|
||||
'orderby' => $legacy_defs[$key]['orderby'],
|
||||
'count' => 0,
|
||||
'enabled' => true,
|
||||
];
|
||||
} elseif (is_numeric($key) && absint($key) > 0) {
|
||||
$term = get_term(absint($key), 'category');
|
||||
$result[] = [
|
||||
'title' => ($term && !is_wp_error($term)) ? $term->name : '',
|
||||
'category_id' => absint($key),
|
||||
'orderby' => 'date',
|
||||
'count' => 0,
|
||||
'enabled' => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Structured array format
|
||||
if (is_array($tabs)) {
|
||||
$allowed_orderby = ['date', 'modified', 'views', 'likes', 'comments', 'favorites', 'downloads', 'rand'];
|
||||
$result = [];
|
||||
foreach ($tabs as $tab) {
|
||||
if (!is_array($tab)) continue;
|
||||
$orderby = sanitize_key($tab['orderby'] ?? 'date');
|
||||
if (!in_array($orderby, $allowed_orderby, true)) $orderby = 'date';
|
||||
$result[] = [
|
||||
'title' => sanitize_text_field($tab['title'] ?? ''),
|
||||
'category_id' => absint($tab['category_id'] ?? 0),
|
||||
'orderby' => $orderby,
|
||||
'count' => max(0, min(20, (int) ($tab['count'] ?? 0))),
|
||||
'enabled' => !empty($tab['enabled']),
|
||||
];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build WP_Query args for a tab's orderby.
|
||||
*/
|
||||
private function build_tab_query_args(array $tab, int $default_count): array {
|
||||
$count = $tab['count'] > 0 ? $tab['count'] : $default_count;
|
||||
$args = [
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $count,
|
||||
'ignore_sticky_posts' => true,
|
||||
'no_found_rows' => true,
|
||||
];
|
||||
|
||||
if ($tab['category_id'] > 0) {
|
||||
$args['cat'] = $tab['category_id'];
|
||||
}
|
||||
|
||||
switch ($tab['orderby']) {
|
||||
case 'views':
|
||||
$args['meta_key'] = 'aksrt_post_views';
|
||||
$args['orderby'] = 'meta_value_num';
|
||||
$args['order'] = 'DESC';
|
||||
break;
|
||||
case 'likes':
|
||||
$args['meta_key'] = '_aksrt_like_count';
|
||||
$args['orderby'] = 'meta_value_num';
|
||||
$args['order'] = 'DESC';
|
||||
break;
|
||||
case 'favorites':
|
||||
$args['meta_key'] = '_aksrt_favorite_count';
|
||||
$args['orderby'] = 'meta_value_num';
|
||||
$args['order'] = 'DESC';
|
||||
break;
|
||||
case 'downloads':
|
||||
$args['meta_key'] = '_aksrt_res_download_count';
|
||||
$args['orderby'] = 'meta_value_num';
|
||||
$args['order'] = 'DESC';
|
||||
break;
|
||||
case 'comments':
|
||||
$args['orderby'] = 'comment_count';
|
||||
$args['order'] = 'DESC';
|
||||
break;
|
||||
case 'rand':
|
||||
$args['orderby'] = 'rand';
|
||||
break;
|
||||
case 'modified':
|
||||
$args['orderby'] = 'modified';
|
||||
$args['order'] = 'DESC';
|
||||
break;
|
||||
case 'date':
|
||||
default:
|
||||
$args['orderby'] = 'date';
|
||||
$args['order'] = 'DESC';
|
||||
break;
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
echo $args['before_widget'];
|
||||
|
||||
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
|
||||
$count = absint($instance['count'] ?? 5);
|
||||
$tabs = $this->normalize_tabs($instance['tabs'] ?? 'latest,comments,random');
|
||||
|
||||
// Filter enabled tabs
|
||||
$tabs = array_filter($tabs, fn($t) => !empty($t['enabled']));
|
||||
if (empty($tabs)) {
|
||||
echo $args['after_widget'];
|
||||
return;
|
||||
}
|
||||
|
||||
if ($title) {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
}
|
||||
|
||||
$widget_id = $this->id;
|
||||
$use_shared = function_exists('aksrt_render_mini_list');
|
||||
?>
|
||||
<div class="widget-tab-posts" id="<?php echo esc_attr($widget_id); ?>-tabs">
|
||||
<!-- Tab 导航 -->
|
||||
<ul class="widget-tab-posts__nav" role="tablist">
|
||||
<?php $first = true; foreach ($tabs as $i => $tab) :
|
||||
$tab_key = 'tab-' . $i;
|
||||
$tab_title = $tab['title'] !== '' ? $tab['title'] : __('Tab ' . ($i + 1), 'aksrt');
|
||||
?>
|
||||
<li role="presentation">
|
||||
<button type="button" role="tab"
|
||||
class="widget-tab-posts__tab<?php echo $first ? ' active' : ''; ?>"
|
||||
data-tab="<?php echo esc_attr($tab_key); ?>"
|
||||
aria-selected="<?php echo $first ? 'true' : 'false'; ?>"
|
||||
><?php echo esc_html($tab_title); ?></button>
|
||||
</li>
|
||||
<?php $first = false; endforeach; ?>
|
||||
</ul>
|
||||
|
||||
<!-- Tab 内容 -->
|
||||
<?php $first = true; foreach ($tabs as $i => $tab) :
|
||||
$tab_key = 'tab-' . $i;
|
||||
$q = new WP_Query($this->build_tab_query_args($tab, $count));
|
||||
?>
|
||||
<div class="widget-tab-posts__panel<?php echo $first ? ' active' : ''; ?>" data-tab-panel="<?php echo esc_attr($tab_key); ?>" role="tabpanel"<?php echo $first ? '' : ' hidden'; ?>>
|
||||
<?php if ($q->have_posts()) : ?>
|
||||
<?php if ($use_shared) : ?>
|
||||
<div class="widget-tab-posts__list widget-tab-posts__list--mini">
|
||||
<?php aksrt_render_mini_list($q, ['context' => 'sidebar', 'show_thumb' => true, 'show_meta' => true], true); ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<ul class="widget-tab-posts__list">
|
||||
<?php while ($q->have_posts()) : $q->the_post(); ?>
|
||||
<li class="widget-tab-posts__item">
|
||||
<?php if (has_post_thumbnail()) : ?>
|
||||
<a href="<?php the_permalink(); ?>" class="widget-tab-posts__thumb">
|
||||
<?php the_post_thumbnail('thumbnail', ['loading' => function_exists('aksrt_is_lazyload_enabled') && aksrt_is_lazyload_enabled('widget') ? 'lazy' : false, 'class' => 'fit-cover']); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<div class="widget-tab-posts__info">
|
||||
<a href="<?php the_permalink(); ?>" class="widget-tab-posts__title"><?php the_title(); ?></a>
|
||||
<span class="widget-tab-posts__meta"><?php echo esc_html(human_time_diff(get_the_time('U'), current_time('timestamp')) . __('前', 'aksrt')); ?></span>
|
||||
</div>
|
||||
</li>
|
||||
<?php endwhile; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
<?php else : ?>
|
||||
<?php echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无文章', 'aksrt')) : '<p class="widget-empty">' . esc_html__('暂无文章', 'aksrt') . '</p>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php wp_reset_postdata(); $first = false; endforeach; ?>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = $instance['title'] ?? '';
|
||||
$count = (int) ($instance['count'] ?? 5);
|
||||
$tabs = $this->normalize_tabs($instance['tabs'] ?? 'latest,comments,random');
|
||||
$orderby_options = [
|
||||
'date' => __('最新', 'aksrt'),
|
||||
'modified' => __('更新', 'aksrt'),
|
||||
'views' => __('浏览', 'aksrt'),
|
||||
'likes' => __('点赞', 'aksrt'),
|
||||
'comments' => __('评论', 'aksrt'),
|
||||
'favorites' => __('收藏', 'aksrt'),
|
||||
'downloads' => __('下载', 'aksrt'),
|
||||
'rand' => __('随机', 'aksrt'),
|
||||
];
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题(可选):', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('count'); ?>"><?php esc_html_e('默认每个 Tab 显示数量:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo $this->get_field_id('count'); ?>" name="<?php echo $this->get_field_name('count'); ?>" type="number" min="1" max="20" value="<?php echo $count; ?>">
|
||||
</p>
|
||||
<hr style="margin:12px 0;">
|
||||
<p style="font-weight:600;margin-bottom:8px;"><?php esc_html_e('Tab 配置', 'aksrt'); ?></p>
|
||||
<?php
|
||||
// Render each tab as a mini-form
|
||||
$max_tabs = 8;
|
||||
$tabs = array_slice($tabs, 0, $max_tabs);
|
||||
// Ensure at least 3 slots
|
||||
while (count($tabs) < 3) {
|
||||
$tabs[] = ['title' => '', 'category_id' => 0, 'orderby' => 'date', 'count' => 0, 'enabled' => false];
|
||||
}
|
||||
foreach ($tabs as $i => $tab) :
|
||||
?>
|
||||
<div style="border:1px solid #ddd;padding:8px;margin-bottom:8px;border-radius:4px;background:#fafafa;">
|
||||
<p style="margin:0 0 6px;">
|
||||
<label><input type="checkbox" name="<?php echo $this->get_field_name("tabs[{$i}][enabled]"); ?>" value="1" <?php checked(!empty($tab['enabled'])); ?>> <?php printf(esc_html__('Tab %d 启用', 'aksrt'), $i + 1); ?></label>
|
||||
</p>
|
||||
<p style="margin:0 0 6px;">
|
||||
<label><?php esc_html_e('标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" name="<?php echo $this->get_field_name("tabs[{$i}][title]"); ?>" value="<?php echo esc_attr($tab['title']); ?>" placeholder="<?php esc_attr_e('留空则使用分类名', 'aksrt'); ?>">
|
||||
</p>
|
||||
<p style="margin:0 0 6px;">
|
||||
<label><?php esc_html_e('分类 ID:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" type="number" min="0" name="<?php echo $this->get_field_name("tabs[{$i}][category_id]"); ?>" value="<?php echo (int) $tab['category_id']; ?>" placeholder="0">
|
||||
</p>
|
||||
<p style="margin:0 0 6px;">
|
||||
<label><?php esc_html_e('排序:', 'aksrt'); ?></label>
|
||||
<select class="widefat" name="<?php echo $this->get_field_name("tabs[{$i}][orderby]"); ?>">
|
||||
<?php foreach ($orderby_options as $val => $label) : ?>
|
||||
<option value="<?php echo esc_attr($val); ?>" <?php selected($tab['orderby'], $val); ?>><?php echo esc_html($label); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</p>
|
||||
<p style="margin:0;">
|
||||
<label><?php esc_html_e('数量(0=使用默认):', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" type="number" min="0" max="20" name="<?php echo $this->get_field_name("tabs[{$i}][count]"); ?>" value="<?php echo (int) $tab['count']; ?>">
|
||||
</p>
|
||||
</div>
|
||||
<?php endforeach;
|
||||
// Render hidden fields for any remaining tabs to preserve data
|
||||
for ($i = count($tabs); $i < $max_tabs; $i++) :
|
||||
?>
|
||||
<input type="hidden" name="<?php echo $this->get_field_name("tabs[{$i}][enabled]"); ?>" value="">
|
||||
<?php endfor;
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$result = [
|
||||
'title' => sanitize_text_field($new_instance['title'] ?? ''),
|
||||
'count' => max(1, min(20, (int) ($new_instance['count'] ?? 5))),
|
||||
];
|
||||
|
||||
$tabs = [];
|
||||
if (isset($new_instance['tabs']) && is_array($new_instance['tabs'])) {
|
||||
$allowed_orderby = ['date', 'modified', 'views', 'likes', 'comments', 'favorites', 'downloads', 'rand'];
|
||||
foreach ($new_instance['tabs'] as $tab) {
|
||||
if (!is_array($tab)) continue;
|
||||
$orderby = sanitize_key($tab['orderby'] ?? 'date');
|
||||
if (!in_array($orderby, $allowed_orderby, true)) $orderby = 'date';
|
||||
$tabs[] = [
|
||||
'title' => sanitize_text_field($tab['title'] ?? ''),
|
||||
'category_id' => absint($tab['category_id'] ?? 0),
|
||||
'orderby' => $orderby,
|
||||
'count' => max(0, min(20, (int) ($tab['count'] ?? 0))),
|
||||
'enabled' => !empty($tab['enabled']),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$result['tabs'] = !empty($tabs) ? $tabs : sanitize_text_field($new_instance['tabs'] ?? 'latest,comments,random');
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,518 @@
|
||||
<?php
|
||||
/**
|
||||
* AKSRT 自定义小工具
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
// ============================================================
|
||||
// 1. 用户中心小工具 — 替换旧作者信息小工具
|
||||
// ============================================================
|
||||
|
||||
class AKSRT_About_Widget extends WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_about',
|
||||
esc_html__('AKSRT 用户中心', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('登录状态面板:未登录显示登录/注册按钮,已登录显示用户信息与快捷入口', 'aksrt'),
|
||||
'customize_selective_refresh' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 前端渲染
|
||||
*/
|
||||
public function widget($args, $instance): void {
|
||||
echo $args['before_widget'];
|
||||
|
||||
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
|
||||
if ($title) {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
}
|
||||
|
||||
// 判断登录状态
|
||||
if (is_user_logged_in()) {
|
||||
$this->render_logged_in();
|
||||
} else {
|
||||
$this->render_logged_out($instance);
|
||||
}
|
||||
|
||||
// 输出内联样式 & JS(仅在本 widget 实例加载时注入一次)
|
||||
$this->inline_assets();
|
||||
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 已登录状态渲染(Zibll 风格用户卡片)
|
||||
// ==========================================
|
||||
private function render_logged_in(): void {
|
||||
$u = wp_get_current_user();
|
||||
$avatar = get_avatar_url($u->ID, ['size' => 160]);
|
||||
$profile = aksrt_get_user_center_url();
|
||||
$logout = aksrt_get_logout_url(home_url('/'));
|
||||
|
||||
// 封面图:用户在用户中心上传的背景图 > 默认背景图
|
||||
$cover = get_user_meta($u->ID, 'aksrt_background_image', true);
|
||||
if (! $cover) {
|
||||
$cover = get_theme_file_uri('assets/img/person_background.png');
|
||||
}
|
||||
?>
|
||||
<div class="aksrt-user-card">
|
||||
<div class="aksrt-user-card__cover" style="padding-bottom: 50%;">
|
||||
<img class="aksrt-user-card__cover-img" src="<?php echo esc_url($cover); ?>" alt=""<?php echo function_exists('aksrt_lazy_loading') ? aksrt_lazy_loading('widget') : ' loading="lazy"'; ?>>
|
||||
</div>
|
||||
|
||||
<div class="aksrt-user-card__content">
|
||||
<div class="aksrt-user-card__avatar">
|
||||
<img src="<?php echo esc_url($avatar); ?>" alt="" width="80" height="80"<?php echo function_exists('aksrt_lazy_loading') ? aksrt_lazy_loading('avatar') : ' loading="lazy"'; ?>>
|
||||
</div>
|
||||
|
||||
<div class="aksrt-user-card__info">
|
||||
<div class="aksrt-user-card__name"><?php echo esc_html($u->display_name); ?></div>
|
||||
<div class="aksrt-user-card__desc"><?php echo esc_html($u->user_email); ?></div>
|
||||
</div>
|
||||
|
||||
<div class="aksrt-user-card__btns">
|
||||
<a class="aksrt-user-card__btn aksrt-user-card__btn--primary" href="<?php echo esc_url($profile); ?>">
|
||||
<i class="fas fa-user" aria-hidden="true"></i> <?php esc_html_e('个人主页', 'aksrt'); ?>
|
||||
</a>
|
||||
<a class="aksrt-user-card__btn aksrt-user-card__btn--outline" href="<?php echo esc_url($logout); ?>">
|
||||
<i class="fas fa-sign-out-alt" aria-hidden="true"></i> <?php esc_html_e('退出', 'aksrt'); ?>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 未登录状态渲染(Zibll 风格用户卡片)
|
||||
// ==========================================
|
||||
private function render_logged_out(array $instance = []): void {
|
||||
$login_url = aksrt_get_login_url();
|
||||
$register_url = aksrt_get_register_url();
|
||||
|
||||
// 封面图:后台配置 > 默认
|
||||
$cover = ! empty($instance['cover']) ? $instance['cover'] : get_theme_file_uri('assets/img/widgets-original.png');
|
||||
$avatar = get_avatar_url(0, ['size' => 160, 'default' => 'mystery']);
|
||||
|
||||
$oauth_html = '';
|
||||
if (function_exists('AKSRT_OAuth') && method_exists(AKSRT_OAuth::get_instance(), 'get_login_buttons')) {
|
||||
$oauth_html = AKSRT_OAuth::get_instance()->get_login_buttons();
|
||||
}
|
||||
?>
|
||||
<div class="aksrt-user-card">
|
||||
<div class="aksrt-user-card__cover" style="padding-bottom: 50%;">
|
||||
<?php if ($cover) : ?>
|
||||
<img class="aksrt-user-card__cover-img" src="<?php echo esc_url($cover); ?>" alt=""<?php echo function_exists('aksrt_lazy_loading') ? aksrt_lazy_loading('widget') : ' loading="lazy"'; ?>>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="aksrt-user-card__content">
|
||||
<div class="aksrt-user-card__avatar">
|
||||
<img src="<?php echo esc_url($avatar); ?>" alt="" width="80" height="80"<?php echo function_exists('aksrt_lazy_loading') ? aksrt_lazy_loading('avatar') : ' loading="lazy"'; ?>>
|
||||
</div>
|
||||
|
||||
<div class="aksrt-user-card__info">
|
||||
<div class="aksrt-user-card__desc"><?php esc_html_e('Hi! 请登录', 'aksrt'); ?></div>
|
||||
</div>
|
||||
|
||||
<div class="aksrt-user-card__btns">
|
||||
<a class="aksrt-user-card__btn aksrt-user-card__btn--primary" href="<?php echo esc_url($login_url); ?>"><?php esc_html_e('登 录', 'aksrt'); ?></a>
|
||||
<?php if (get_option('users_can_register')) : ?>
|
||||
<a class="aksrt-user-card__btn aksrt-user-card__btn--outline" href="<?php echo esc_url($register_url); ?>"><?php esc_html_e('注 册', 'aksrt'); ?></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($oauth_html) : ?>
|
||||
<div class="aksrt-user-card__oauth">
|
||||
<div class="aksrt-user-card__divider"><?php esc_html_e('社交账号登录', 'aksrt'); ?></div>
|
||||
<div class="aksrt-user-card__social-icons">
|
||||
<?php echo $oauth_html; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 内联样式(Zibll 风格用户卡片,只输出一次)
|
||||
// ==========================================
|
||||
private function inline_assets(): void {
|
||||
if (did_action('aksrt_user_card_assets')) {
|
||||
return;
|
||||
}
|
||||
do_action('aksrt_user_card_assets');
|
||||
?>
|
||||
<style>
|
||||
/* ========== AKSRT 用户卡片(Zibll 风格) ========== */
|
||||
|
||||
/*
|
||||
* 消除"卡片套卡片":
|
||||
* 外层 .widget-card 提供 background/shadow/border-radius/padding:15px
|
||||
* 内层 .aksrt-user-card 用负 margin 把封面图扩展到外层边缘,
|
||||
* 使封面图与外层卡片融为一体,不再有 15px 间距。
|
||||
*/
|
||||
.widget-card > .aksrt-user-card {
|
||||
margin: -15px -15px 0;
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius-card, 8px) var(--radius-card, 8px) 0 0;
|
||||
}
|
||||
|
||||
/* 有标题时:取消标题下方的 margin-bottom,因为卡片已无额外间距 */
|
||||
.widget.aksrt_about.widget-card > .widget-title + .aksrt-user-card {
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
||||
/* ---- 封面图 ---- */
|
||||
.aksrt-user-card__cover {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(135deg, var(--color-primary), var(--color-primary-dark, #4f46e5));
|
||||
}
|
||||
.aksrt-user-card__cover-img {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* ---- 内容区 — 悬浮在封面上方 ---- */
|
||||
.aksrt-user-card__content {
|
||||
padding: 15px;
|
||||
margin-top: -55px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ---- 头像(绝对居中) ---- */
|
||||
.aksrt-user-card__avatar {
|
||||
text-align: center;
|
||||
line-height: 0;
|
||||
}
|
||||
.aksrt-user-card__avatar img {
|
||||
display: block;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin: 0 auto;
|
||||
border-radius: 50%;
|
||||
border: 3px solid var(--color-primary);
|
||||
object-fit: cover;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* ---- 用户信息 ---- */
|
||||
.aksrt-user-card__info {
|
||||
margin-top: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
.aksrt-user-card__name {
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
color: var(--color-ink, var(--color-text));
|
||||
}
|
||||
.aksrt-user-card__desc {
|
||||
font-size: 13px;
|
||||
color: var(--color-text-muted, #9ca3af);
|
||||
margin-top: 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ---- 按钮组 ---- */
|
||||
.aksrt-user-card__btns {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.aksrt-user-card__btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 8px 20px;
|
||||
border-radius: 20px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
transition: opacity 0.2s ease, transform 0.15s ease;
|
||||
flex: 1;
|
||||
max-width: 140px;
|
||||
}
|
||||
.aksrt-user-card__btn:hover {
|
||||
opacity: 0.88;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.aksrt-user-card__btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.aksrt-user-card__btn--primary {
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
}
|
||||
.aksrt-user-card__btn--outline {
|
||||
background: var(--color-primary-softer, rgba(136,171,218,0.05));
|
||||
color: var(--color-primary);
|
||||
border: 1px solid var(--color-primary-50, rgba(136,171,218,0.12));
|
||||
}
|
||||
|
||||
/* ---- OAuth 社交登录 ---- */
|
||||
.aksrt-user-card__oauth {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.aksrt-user-card__divider {
|
||||
font-size: 12px;
|
||||
color: var(--color-text-muted, #9ca3af);
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
}
|
||||
.aksrt-user-card__divider::before,
|
||||
.aksrt-user-card__divider::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 28%;
|
||||
height: 1px;
|
||||
background: var(--color-border, #e5e7eb);
|
||||
}
|
||||
.aksrt-user-card__divider::before { left: 0; }
|
||||
.aksrt-user-card__divider::after { right: 0; }
|
||||
|
||||
.aksrt-user-card__social-icons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
}
|
||||
.aksrt-user-card__social-icons a {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
font-size: 15px;
|
||||
color: var(--color-text-muted, #6b7280);
|
||||
background: var(--color-bg-tertiary, #f3f4f6);
|
||||
text-decoration: none;
|
||||
transition: background 0.18s ease, color 0.18s ease;
|
||||
}
|
||||
.aksrt-user-card__social-icons a:hover {
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* ---- 暗色适配 ---- */
|
||||
html.dark .aksrt-user-card__avatar img,
|
||||
body.theme-dark .aksrt-user-card__avatar img {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
html.dark .aksrt-user-card__social-icons a,
|
||||
body.theme-dark .aksrt-user-card__social-icons a {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
html.dark .aksrt-user-card__social-icons a:hover,
|
||||
body.theme-dark .aksrt-user-card__social-icons a:hover {
|
||||
background: var(--color-primary);
|
||||
}
|
||||
|
||||
/* ---- 移动端 ---- */
|
||||
@media screen and (max-width: 480px) {
|
||||
.aksrt-user-card__content {
|
||||
margin-top: -45px;
|
||||
}
|
||||
.aksrt-user-card__avatar img {
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 后台表单
|
||||
// ==========================================
|
||||
public function form($instance): void {
|
||||
$title = $instance['title'] ?? '';
|
||||
$cover = $instance['cover'] ?? '';
|
||||
$default_cover = get_theme_file_uri('assets/img/widgets-original.png');
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题(可选):', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>"
|
||||
name="<?php echo $this->get_field_name('title'); ?>"
|
||||
type="text" value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('cover'); ?>"><?php esc_html_e('未登录背景图 URL:', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo $this->get_field_id('cover'); ?>"
|
||||
name="<?php echo $this->get_field_name('cover'); ?>"
|
||||
type="url" value="<?php echo esc_attr($cover); ?>"
|
||||
placeholder="<?php echo esc_attr($default_cover); ?>">
|
||||
</p>
|
||||
<p style="font-size:12px;color:#666;">
|
||||
<?php esc_html_e('留空则使用默认背景图。已登录用户可使用个人封面图。', 'aksrt'); ?>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title' => sanitize_text_field($new_instance['title'] ?? ''),
|
||||
'cover' => esc_url_raw($new_instance['cover'] ?? ''),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 2. 分类目录小工具
|
||||
// ============================================================
|
||||
|
||||
class AKSRT_Categories_Widget extends WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_categories',
|
||||
esc_html__('AKSRT 分类目录', 'aksrt'),
|
||||
array(
|
||||
'description' => esc_html__('以列表形式展示文章分类', 'aksrt'),
|
||||
'customize_selective_refresh' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance) {
|
||||
echo $args['before_widget'];
|
||||
|
||||
$title = apply_filters('widget_title', $instance['title'], $instance, $this->id_base);
|
||||
|
||||
if ($title): ?>
|
||||
<h3 class="widget-title"><?php echo $title; ?></h3>
|
||||
<?php endif;
|
||||
|
||||
$count = absint($instance['count'] ?: 8);
|
||||
$cats = get_categories(array('number' => $count, 'orderby' => 'count', 'order' => 'DESC'));
|
||||
?>
|
||||
<ul class="widget-list">
|
||||
<?php foreach ($cats as $cat): ?>
|
||||
<li>
|
||||
<a href="<?php echo esc_url(get_category_link($cat->term_id)); ?>"><?php echo esc_html($cat->name); ?></a>
|
||||
<span><?php echo (int)$cat->count; ?></span>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance) {
|
||||
$title = $instance['title'] ?? __('分类目录', 'aksrt');
|
||||
$count = isset($instance['count']) ? (int)$instance['count'] : 8;
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>"
|
||||
name="<?php echo $this->get_field_name('title'); ?>"
|
||||
type="text" value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('count'); ?>"><?php esc_html_e('显示数量:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo $this->get_field_id('count'); ?>"
|
||||
name="<?php echo $this->get_field_name('count'); ?>"
|
||||
type="number" min="1" max="50" value="<?php echo $count; ?>">
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new, $old) {
|
||||
return [
|
||||
'title' => sanitize_text_field($new['title']),
|
||||
'count' => absint($new['count']),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 3. 标签云小工具
|
||||
// ============================================================
|
||||
|
||||
class AKSRT_Tag_Cloud_Widget extends WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_tag_cloud',
|
||||
esc_html__('AKSRT 标签云', 'aksrt'),
|
||||
array(
|
||||
'description' => esc_html__('展示文章标签云', 'aksrt'),
|
||||
'customize_selective_refresh' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance) {
|
||||
echo $args['before_widget'];
|
||||
|
||||
$title = apply_filters('widget_title', $instance['title'], $instance, $this->id_base);
|
||||
$count = absint($instance['count'] ?: 15);
|
||||
|
||||
if ($title): ?>
|
||||
<h3 class="widget-title"><?php echo $title; ?></h3>
|
||||
<?php endif;
|
||||
?>
|
||||
|
||||
<div class="tag-cloud">
|
||||
<?php
|
||||
$tags = get_tags(['number' => $count, 'orderby' => 'count', 'order' => 'DESC']);
|
||||
foreach ($tags as $tag):
|
||||
$size = 12 + min(4, (int)($tag->count / 5));
|
||||
?>
|
||||
<a href="<?php echo esc_url(get_tag_link($tag->term_id)); ?>" style="font-size:<?php echo $size ?>px;">#<?php echo esc_html($tag->name); ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance) {
|
||||
$title = $instance['title'] ?? __('标签云', 'aksrt');
|
||||
$count = isset($instance['count']) ? (int)$instance['count'] : 15;
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('title'); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>"
|
||||
name="<?php echo $this->get_field_name('title'); ?>"
|
||||
type="text" value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo $this->get_field_id('count'); ?>"><?php esc_html_e('标签数量:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo $this->get_field_id('count'); ?>"
|
||||
name="<?php echo $this->get_field_name('count'); ?>"
|
||||
type="number" min="1" max="50" value="<?php echo $count; ?>">
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new, $old) {
|
||||
return [
|
||||
'title' => sanitize_text_field($new['title']),
|
||||
'count' => absint($new['count']),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 注册函数已迁移至 class-aksrt-widgets.php(注册中心)
|
||||
@@ -0,0 +1,726 @@
|
||||
<?php
|
||||
/**
|
||||
* Widget registration and placement rules.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Register custom widgets.
|
||||
*/
|
||||
function aksrt_register_widgets() {
|
||||
register_widget('AKSRT_About_Widget');
|
||||
register_widget('AKSRT_Categories_Widget');
|
||||
register_widget('AKSRT_Tag_Cloud_Widget');
|
||||
register_widget('AKSRT_Hot_Posts_Widget');
|
||||
register_widget('AKSRT_Tab_Posts_Widget');
|
||||
register_widget('AKSRT_Category_Block_Widget');
|
||||
register_widget('AKSRT_Category_Tabs_Widget');
|
||||
register_widget('AKSRT_Category_Recommend_Widget');
|
||||
|
||||
// 第一批通用 widget
|
||||
register_widget('AKSRT_Search_Widget');
|
||||
register_widget('AKSRT_Notice_Widget');
|
||||
register_widget('AKSRT_Recent_Comments_Widget');
|
||||
register_widget('AKSRT_Friend_Links_Widget');
|
||||
register_widget('AKSRT_Icon_Cards_Widget');
|
||||
register_widget('AKSRT_Buttons_Widget');
|
||||
register_widget('AKSRT_Custom_HTML_Gap_Widget');
|
||||
|
||||
// 公告跑马灯
|
||||
register_widget('AKSRT_Widget_Announcement');
|
||||
|
||||
// 用户卡片
|
||||
register_widget('AKSRT_Widget_User_Card');
|
||||
|
||||
// 用户排行榜
|
||||
register_widget('AKSRT_Widget_User_Ranking');
|
||||
|
||||
// 图文封面卡片
|
||||
register_widget('AKSRT_Widget_Cover_Cards');
|
||||
|
||||
// 视频播放器
|
||||
register_widget('AKSRT_Widget_Video_Player');
|
||||
|
||||
// 语言切换器
|
||||
register_widget('AKSRT_Widget_Lang_Switch');
|
||||
|
||||
// 一言轮播
|
||||
register_widget('AKSRT_Widget_Yiyan');
|
||||
|
||||
// Shop product widgets (§6.9)
|
||||
register_widget('AKSRT_Widget_Product_List');
|
||||
register_widget('AKSRT_Widget_Product_Oneline');
|
||||
register_widget('AKSRT_Widget_Product_Tab');
|
||||
|
||||
aksrt_maybe_repair_sidebar_blog_widgets();
|
||||
}
|
||||
add_action('widgets_init', 'aksrt_register_widgets');
|
||||
|
||||
if (!function_exists('aksrt_widget_sidebar_area_ids')) {
|
||||
/**
|
||||
* Sidebars intended for sidebar-only widgets.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
function aksrt_widget_sidebar_area_ids(): array {
|
||||
return [
|
||||
'home_sidebar',
|
||||
'sidebar-blog',
|
||||
'sidebar-article',
|
||||
'all_sidebar_top',
|
||||
'all_sidebar_bottom',
|
||||
'cat_sidebar',
|
||||
'tag_sidebar',
|
||||
'search_sidebar',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_widget_archive_area_ids')) {
|
||||
/**
|
||||
* Widget zones for archive pages (cat/tag/search).
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
function aksrt_widget_archive_area_ids(): array {
|
||||
return [
|
||||
'cat_top_fluid',
|
||||
'cat_top_content',
|
||||
'cat_bottom_content',
|
||||
'cat_bottom_fluid',
|
||||
'cat_sidebar',
|
||||
'tag_top_fluid',
|
||||
'tag_top_content',
|
||||
'tag_bottom_content',
|
||||
'tag_bottom_fluid',
|
||||
'tag_sidebar',
|
||||
'search_top_fluid',
|
||||
'search_top_content',
|
||||
'search_bottom_content',
|
||||
'search_bottom_fluid',
|
||||
'search_sidebar',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_widget_homepage_area_ids')) {
|
||||
/**
|
||||
* Homepage widget zones for homepage-only widgets.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
function aksrt_widget_homepage_area_ids(): array {
|
||||
return [
|
||||
'home_top_fluid',
|
||||
'home_top_content',
|
||||
'home_bottom_content',
|
||||
'home_bottom_fluid',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_widget_single_area_ids')) {
|
||||
/**
|
||||
* Single post/page widget zones.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
function aksrt_widget_single_area_ids(): array {
|
||||
return [
|
||||
'single_top_fluid',
|
||||
'single_top_content',
|
||||
'single_bottom_content',
|
||||
'single_bottom_fluid',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_widget_base_from_id')) {
|
||||
/**
|
||||
* Get widget base id from a stored widget instance id.
|
||||
*/
|
||||
function aksrt_widget_base_from_id(string $widget_id): string {
|
||||
return preg_replace('/-\d+$/', '', $widget_id) ?: $widget_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_widget_allowed_sidebars')) {
|
||||
/**
|
||||
* Allowed sidebar ids for a widget base id.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
function aksrt_widget_allowed_sidebars(string $widget_base_id): array {
|
||||
$sidebar_only = [
|
||||
'aksrt_about',
|
||||
'aksrt_categories',
|
||||
'aksrt_tag_cloud',
|
||||
'aksrt_hot_posts',
|
||||
];
|
||||
$homepage_only = [
|
||||
'aksrt_category_recommend',
|
||||
];
|
||||
$hybrid = [
|
||||
'aksrt_tab_posts',
|
||||
'aksrt_category_block',
|
||||
'aksrt_category_tabs',
|
||||
];
|
||||
|
||||
if (in_array($widget_base_id, $sidebar_only, true)) {
|
||||
return aksrt_widget_sidebar_area_ids();
|
||||
}
|
||||
|
||||
if (in_array($widget_base_id, $homepage_only, true)) {
|
||||
return aksrt_widget_homepage_area_ids();
|
||||
}
|
||||
|
||||
if (in_array($widget_base_id, $hybrid, true)) {
|
||||
return array_values(array_unique(array_merge(
|
||||
aksrt_widget_sidebar_area_ids(),
|
||||
aksrt_widget_homepage_area_ids()
|
||||
)));
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_widget_is_allowed_in_sidebar')) {
|
||||
/**
|
||||
* Whether a widget base id can be assigned to the given sidebar id.
|
||||
*/
|
||||
function aksrt_widget_is_allowed_in_sidebar(string $widget_base_id, string $sidebar_id): bool {
|
||||
if ($widget_base_id === '' || $sidebar_id === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($sidebar_id === 'wp_inactive_widgets' || strpos($sidebar_id, 'orphaned_widgets') === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$allowed = aksrt_widget_allowed_sidebars($widget_base_id);
|
||||
if (empty($allowed)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return in_array($sidebar_id, $allowed, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_widget_get_area_context')) {
|
||||
/**
|
||||
* Resolve widget area context from sidebar args.
|
||||
*/
|
||||
function aksrt_widget_get_area_context(array $args): string {
|
||||
$sidebar_id = isset($args['id']) ? (string) $args['id'] : '';
|
||||
|
||||
if (in_array($sidebar_id, aksrt_widget_sidebar_area_ids(), true)) {
|
||||
return 'sidebar';
|
||||
}
|
||||
|
||||
if (in_array($sidebar_id, aksrt_widget_homepage_area_ids(), true)) {
|
||||
return 'homepage';
|
||||
}
|
||||
|
||||
if (in_array($sidebar_id, aksrt_widget_archive_area_ids(), true)) {
|
||||
return 'archive';
|
||||
}
|
||||
|
||||
if (in_array($sidebar_id, aksrt_widget_single_area_ids(), true)) {
|
||||
return 'single';
|
||||
}
|
||||
|
||||
return 'other';
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Shared Widget Form Helper — 公共表单字段与显示规则
|
||||
// ============================================================
|
||||
|
||||
if (!function_exists('aksrt_widget_common_defaults')) {
|
||||
/**
|
||||
* Default values for shared widget instance fields.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
function aksrt_widget_common_defaults(): array {
|
||||
return [
|
||||
'subtitle' => '',
|
||||
'title_link' => '',
|
||||
'show_type' => 'all',
|
||||
'show_id_type' => '',
|
||||
'show_ids' => '',
|
||||
'sidebar_affix' => false,
|
||||
'animation_in' => '',
|
||||
'animation_repeat'=> false,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_widget_normalize_common')) {
|
||||
/**
|
||||
* Normalize common widget instance fields, filling defaults for legacy instances.
|
||||
*
|
||||
* @param array<string, mixed> $instance
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
function aksrt_widget_normalize_common(array $instance): array {
|
||||
$defaults = aksrt_widget_common_defaults();
|
||||
$result = array_merge($defaults, $instance);
|
||||
|
||||
$result['subtitle'] = sanitize_text_field($result['subtitle'] ?? '');
|
||||
$result['title_link'] = esc_url_raw($result['title_link'] ?? '');
|
||||
$result['show_type'] = in_array($result['show_type'] ?? '', ['all', 'pc', 'mobile'], true) ? $result['show_type'] : 'all';
|
||||
$result['show_id_type'] = in_array($result['show_id_type'] ?? '', ['', 'include', 'exclude'], true) ? $result['show_id_type'] : '';
|
||||
$result['show_ids'] = sanitize_text_field($result['show_ids'] ?? '');
|
||||
$result['sidebar_affix'] = !empty($result['sidebar_affix']);
|
||||
$result['animation_in'] = sanitize_key($result['animation_in'] ?? '');
|
||||
$result['animation_repeat']= !empty($result['animation_repeat']);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_widget_render_common_form')) {
|
||||
/**
|
||||
* Render shared widget admin form fields.
|
||||
*
|
||||
* Call this from your widget's form() method after rendering custom fields.
|
||||
*
|
||||
* @param array<string, mixed> $instance Current widget instance.
|
||||
*/
|
||||
function aksrt_widget_render_common_form(array $instance): void {
|
||||
$instance = aksrt_widget_normalize_common($instance);
|
||||
?>
|
||||
<hr style="margin:16px 0 12px;border-color:#e0e0e0;">
|
||||
<p style="font-size:12px;color:#666;margin-bottom:8px;font-weight:600;"><?php esc_html_e('显示规则', 'aksrt'); ?></p>
|
||||
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($instance['show_type_id'] ?? ''); ?>"><?php esc_html_e('显示设备', 'aksrt'); ?></label><br>
|
||||
<select name="<?php echo esc_attr($instance['show_type_name'] ?? 'show_type'); ?>" id="<?php echo esc_attr($instance['show_type_id'] ?? ''); ?>" class="widefat">
|
||||
<option value="all" <?php selected($instance['show_type'], 'all'); ?>><?php esc_html_e('全部', 'aksrt'); ?></option>
|
||||
<option value="pc" <?php selected($instance['show_type'], 'pc'); ?>><?php esc_html_e('仅 PC', 'aksrt'); ?></option>
|
||||
<option value="mobile" <?php selected($instance['show_type'], 'mobile'); ?>><?php esc_html_e('仅移动端', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($instance['show_id_type_id'] ?? ''); ?>"><?php esc_html_e('ID 显示规则', 'aksrt'); ?></label><br>
|
||||
<select name="<?php echo esc_attr($instance['show_id_type_name'] ?? 'show_id_type'); ?>" id="<?php echo esc_attr($instance['show_id_type_id'] ?? ''); ?>" class="widefat">
|
||||
<option value="" <?php selected($instance['show_id_type'], ''); ?>><?php esc_html_e('不限制', 'aksrt'); ?></option>
|
||||
<option value="include" <?php selected($instance['show_id_type'], 'include'); ?>><?php esc_html_e('仅指定 ID 显示', 'aksrt'); ?></option>
|
||||
<option value="exclude" <?php selected($instance['show_id_type'], 'exclude'); ?>><?php esc_html_e('指定 ID 隐藏', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($instance['show_ids_id'] ?? ''); ?>"><?php esc_html_e('文章/页面 ID', 'aksrt'); ?></label><br>
|
||||
<input type="text" name="<?php echo esc_attr($instance['show_ids_name'] ?? 'show_ids'); ?>" id="<?php echo esc_attr($instance['show_ids_id'] ?? ''); ?>" class="widefat" value="<?php echo esc_attr($instance['show_ids']); ?>" placeholder="<?php esc_attr_e('逗号分隔,如: 1,2,3', 'aksrt'); ?>">
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label>
|
||||
<input type="checkbox" name="<?php echo esc_attr($instance['sidebar_affix_name'] ?? 'sidebar_affix'); ?>" value="1" <?php checked($instance['sidebar_affix']); ?>>
|
||||
<?php esc_html_e('侧栏吸顶', 'aksrt'); ?>
|
||||
</label>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($instance['animation_in_id'] ?? ''); ?>"><?php esc_html_e('入场动画', 'aksrt'); ?></label><br>
|
||||
<select name="<?php echo esc_attr($instance['animation_in_name'] ?? 'animation_in'); ?>" id="<?php echo esc_attr($instance['animation_in_id'] ?? ''); ?>" class="widefat">
|
||||
<option value="" <?php selected($instance['animation_in'], ''); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
|
||||
<option value="fadeIn" <?php selected($instance['animation_in'], 'fadeIn'); ?>><?php esc_html_e('淡入', 'aksrt'); ?></option>
|
||||
<option value="fadeInUp" <?php selected($instance['animation_in'], 'fadeInUp'); ?>><?php esc_html_e('上移淡入', 'aksrt'); ?></option>
|
||||
<option value="fadeInDown" <?php selected($instance['animation_in'], 'fadeInDown'); ?>><?php esc_html_e('下移淡入', 'aksrt'); ?></option>
|
||||
<option value="fadeInLeft" <?php selected($instance['animation_in'], 'fadeInLeft'); ?>><?php esc_html_e('左移淡入', 'aksrt'); ?></option>
|
||||
<option value="fadeInRight" <?php selected($instance['animation_in'], 'fadeInRight'); ?>><?php esc_html_e('右移淡入', 'aksrt'); ?></option>
|
||||
<option value="zoomIn" <?php selected($instance['animation_in'], 'zoomIn'); ?>><?php esc_html_e('缩放进入', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label>
|
||||
<input type="checkbox" name="<?php echo esc_attr($instance['animation_repeat_name'] ?? 'animation_repeat'); ?>" value="1" <?php checked($instance['animation_repeat']); ?>>
|
||||
<?php esc_html_e('动画重复播放', 'aksrt'); ?>
|
||||
</label>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_widget_update_common')) {
|
||||
/**
|
||||
* Process shared widget form fields in update().
|
||||
*
|
||||
* @param array<string, mixed> $new_instance
|
||||
* @param array<string, mixed> $old_instance
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
function aksrt_widget_update_common(array $new_instance, array $old_instance): array {
|
||||
$merged = array_merge($old_instance, $new_instance);
|
||||
return aksrt_widget_normalize_common($merged);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_widget_render_common_before')) {
|
||||
/**
|
||||
* Render common wrapper attributes and classes before widget content.
|
||||
*
|
||||
* Outputs CSS classes for animation, affix, show_type, etc.
|
||||
* Call this at the start of widget() before rendering content.
|
||||
*
|
||||
* @param array $args Sidebar args.
|
||||
* @param array $instance Widget instance.
|
||||
* @return array Modified args with extra classes.
|
||||
*/
|
||||
function aksrt_widget_get_common_wrapper_classes(array $args, array $instance): array {
|
||||
$instance = aksrt_widget_normalize_common($instance);
|
||||
$classes = [];
|
||||
|
||||
// Show type: PC or mobile
|
||||
if ($instance['show_type'] === 'pc') {
|
||||
$classes[] = 'aksrt-widget--pc-only';
|
||||
} elseif ($instance['show_type'] === 'mobile') {
|
||||
$classes[] = 'aksrt-widget--mobile-only';
|
||||
}
|
||||
|
||||
// Sidebar affix
|
||||
if ($instance['sidebar_affix']) {
|
||||
$classes[] = 'aksrt-widget--affix';
|
||||
}
|
||||
|
||||
// Animation
|
||||
if ($instance['animation_in'] !== '') {
|
||||
$classes[] = 'aksrt-widget--animated';
|
||||
$classes[] = 'aksrt-anim-' . $instance['animation_in'];
|
||||
if ($instance['animation_repeat']) {
|
||||
$classes[] = 'aksrt-anim-repeat';
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_widget_should_display_by_ids')) {
|
||||
/**
|
||||
* Check if the current post/page should display the widget based on show_id_type and show_ids.
|
||||
*
|
||||
* @param array $instance Widget instance.
|
||||
* @return bool
|
||||
*/
|
||||
function aksrt_widget_should_display_by_ids(array $instance): bool {
|
||||
$instance = aksrt_widget_normalize_common($instance);
|
||||
|
||||
if ($instance['show_id_type'] === '' || $instance['show_ids'] === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$current_id = get_the_ID();
|
||||
if (!$current_id) {
|
||||
return true; // Not on a singular page, allow display
|
||||
}
|
||||
|
||||
$ids = array_filter(array_map('absint', explode(',', $instance['show_ids'])));
|
||||
|
||||
if ($instance['show_id_type'] === 'include') {
|
||||
return in_array($current_id, $ids, true);
|
||||
}
|
||||
|
||||
if ($instance['show_id_type'] === 'exclude') {
|
||||
return !in_array($current_id, $ids, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_widget_render_location_help')) {
|
||||
/**
|
||||
* Render placement hint inside widget admin forms.
|
||||
*/
|
||||
function aksrt_widget_render_location_help(string $scope): void {
|
||||
$messages = [
|
||||
'sidebar' => __('仅限侧栏区域:首页侧栏、博客侧栏、文章侧栏、全站侧栏、分类/标签/搜索侧栏。', 'aksrt'),
|
||||
'homepage' => __('仅限首页挂载区域:首页顶部/底部全宽、首页内容上方/下方。', 'aksrt'),
|
||||
'hybrid' => __('支持侧栏、首页和归档页挂载区域。', 'aksrt'),
|
||||
'archive' => __('仅限归档页挂载区域:分类页、标签页、搜索页的顶部/底部/侧栏。', 'aksrt'),
|
||||
];
|
||||
|
||||
if (empty($messages[$scope])) {
|
||||
return;
|
||||
}
|
||||
|
||||
printf(
|
||||
'<p style="margin-top:10px;font-size:12px;color:#666;">%s</p>',
|
||||
esc_html($messages[$scope])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_filter_widget_display_by_area')) {
|
||||
/**
|
||||
* Prevent widgets from rendering outside their allowed areas,
|
||||
* and apply per-widget display rules (show_type, show_id_type/show_ids).
|
||||
*
|
||||
* @param array|false $instance Widget instance.
|
||||
* @param WP_Widget $widget Widget object.
|
||||
* @param array $args Sidebar args.
|
||||
* @return array|false
|
||||
*/
|
||||
function aksrt_filter_widget_display_by_area($instance, WP_Widget $widget, array $args) {
|
||||
$sidebar_id = isset($args['id']) ? (string) $args['id'] : '';
|
||||
$widget_base_id = isset($widget->id_base) ? (string) $widget->id_base : '';
|
||||
|
||||
if ($instance === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Layer 1: Area legality check
|
||||
if (!aksrt_widget_is_allowed_in_sidebar($widget_base_id, $sidebar_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Layer 2: Display rule check (show_type for PC/mobile)
|
||||
if (is_array($instance) && function_exists('aksrt_widget_normalize_common')) {
|
||||
$normalized = aksrt_widget_normalize_common($instance);
|
||||
|
||||
// Show type: PC / mobile filter
|
||||
if ($normalized['show_type'] === 'pc' && wp_is_mobile()) {
|
||||
return false;
|
||||
}
|
||||
if ($normalized['show_type'] === 'mobile' && !wp_is_mobile()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Show IDs: whitelist / blacklist filter
|
||||
if (!aksrt_widget_should_display_by_ids($normalized)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
add_filter('widget_display_callback', 'aksrt_filter_widget_display_by_area', 10, 3);
|
||||
|
||||
if (!function_exists('aksrt_filter_widget_sidebar_assignments')) {
|
||||
/**
|
||||
* Move disallowed widget assignments back to the inactive widgets bucket.
|
||||
*
|
||||
* @param mixed $new_value New sidebars_widgets option value.
|
||||
* @param mixed $old_value Previous option value.
|
||||
* @return mixed
|
||||
*/
|
||||
function aksrt_filter_widget_sidebar_assignments($new_value, $old_value) {
|
||||
if (!is_array($new_value)) {
|
||||
return $new_value;
|
||||
}
|
||||
|
||||
$removed = [];
|
||||
|
||||
foreach ($new_value as $sidebar_id => $widget_ids) {
|
||||
if (!is_array($widget_ids) || $sidebar_id === 'wp_inactive_widgets') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$filtered = [];
|
||||
foreach ($widget_ids as $widget_id) {
|
||||
if (!is_string($widget_id) || $widget_id === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$widget_base_id = aksrt_widget_base_from_id($widget_id);
|
||||
if (aksrt_widget_is_allowed_in_sidebar($widget_base_id, (string) $sidebar_id)) {
|
||||
$filtered[] = $widget_id;
|
||||
} else {
|
||||
$removed[] = $widget_id;
|
||||
}
|
||||
}
|
||||
|
||||
$new_value[$sidebar_id] = array_values($filtered);
|
||||
}
|
||||
|
||||
if (empty($removed)) {
|
||||
return $new_value;
|
||||
}
|
||||
|
||||
if (!isset($new_value['wp_inactive_widgets']) || !is_array($new_value['wp_inactive_widgets'])) {
|
||||
$new_value['wp_inactive_widgets'] = [];
|
||||
}
|
||||
|
||||
$assigned = [];
|
||||
foreach ($new_value as $widget_ids) {
|
||||
if (!is_array($widget_ids)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($widget_ids as $widget_id) {
|
||||
if (is_string($widget_id) && $widget_id !== '') {
|
||||
$assigned[$widget_id] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (array_values(array_unique($removed)) as $widget_id) {
|
||||
if (!isset($assigned[$widget_id]) && !in_array($widget_id, $new_value['wp_inactive_widgets'], true)) {
|
||||
$new_value['wp_inactive_widgets'][] = $widget_id;
|
||||
}
|
||||
}
|
||||
|
||||
return $new_value;
|
||||
}
|
||||
}
|
||||
add_filter('pre_update_option_sidebars_widgets', 'aksrt_filter_widget_sidebar_assignments', 10, 2);
|
||||
|
||||
/**
|
||||
* Ensure widget instance config exists.
|
||||
*/
|
||||
function aksrt_ensure_widget_instance(string $option_name, int $number, array $defaults): void {
|
||||
$instances = get_option($option_name, []);
|
||||
|
||||
if (!is_array($instances)) {
|
||||
$instances = [];
|
||||
}
|
||||
|
||||
if (!isset($instances['_multiwidget'])) {
|
||||
$instances['_multiwidget'] = 1;
|
||||
}
|
||||
|
||||
if (empty($instances[$number]) || !is_array($instances[$number])) {
|
||||
$instances[$number] = $defaults;
|
||||
update_option($option_name, $instances);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Repair the sidebar-blog structure and seed default sidebar widgets on first install.
|
||||
*/
|
||||
function aksrt_maybe_repair_sidebar_blog_widgets(): void {
|
||||
$sidebars = get_option('sidebars_widgets', []);
|
||||
$seeded = (bool) get_option('aksrt_sidebar_blog_seeded', false);
|
||||
|
||||
if (!is_array($sidebars)) {
|
||||
$sidebars = [];
|
||||
}
|
||||
|
||||
$current = $sidebars['sidebar-blog'] ?? null;
|
||||
$needs_repair = false;
|
||||
$normalized = [];
|
||||
|
||||
if (is_array($current)) {
|
||||
foreach ($current as $item) {
|
||||
if (is_array($item)) {
|
||||
$needs_repair = true;
|
||||
}
|
||||
|
||||
if (is_string($item) && $item !== '') {
|
||||
$normalized[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($needs_repair) {
|
||||
$sidebars['sidebar-blog'] = array_values($normalized);
|
||||
update_option('sidebars_widgets', $sidebars);
|
||||
update_option('aksrt_sidebar_blog_seeded', true);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($current !== null) {
|
||||
if (!$seeded) {
|
||||
update_option('aksrt_sidebar_blog_seeded', true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ($seeded) {
|
||||
return;
|
||||
}
|
||||
|
||||
aksrt_ensure_widget_instance('widget_aksrt_about', 2, [
|
||||
'title' => '',
|
||||
]);
|
||||
aksrt_ensure_widget_instance('widget_aksrt_categories', 2, [
|
||||
'title' => __('分类目录', 'aksrt'),
|
||||
'count' => 8,
|
||||
]);
|
||||
aksrt_ensure_widget_instance('widget_aksrt_tag_cloud', 2, [
|
||||
'title' => __('标签云', 'aksrt'),
|
||||
'count' => 15,
|
||||
]);
|
||||
|
||||
$sidebars['sidebar-blog'] = [
|
||||
'aksrt_about-2',
|
||||
'aksrt_categories-2',
|
||||
'aksrt_tag_cloud-2',
|
||||
];
|
||||
update_option('sidebars_widgets', $sidebars);
|
||||
update_option('aksrt_sidebar_blog_seeded', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a block editor collection for AKSRT widgets.
|
||||
*/
|
||||
add_action('enqueue_block_editor_assets', function () {
|
||||
$screen = get_current_screen();
|
||||
if (!$screen || $screen->id !== 'widgets') {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_add_inline_script('wp-blocks', '
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
if (typeof wp !== "undefined" && wp.blocks && wp.blocks.registerBlockCollection) {
|
||||
wp.blocks.registerBlockCollection("aksrt", {
|
||||
title: "AKSRT 主题小工具",
|
||||
icon: undefined
|
||||
});
|
||||
}
|
||||
});
|
||||
');
|
||||
});
|
||||
|
||||
/**
|
||||
* Assign AKSRT widgets to the AKSRT block category in the widgets editor.
|
||||
*
|
||||
* @param array<string, mixed> $metadata Block metadata.
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
add_filter('block_type_metadata', function ($metadata) {
|
||||
$aksrt_widgets = [
|
||||
'aksrt_about',
|
||||
'aksrt_categories',
|
||||
'aksrt_tag_cloud',
|
||||
];
|
||||
|
||||
if (!empty($metadata['name']) && in_array($metadata['name'], $aksrt_widgets, true)) {
|
||||
$metadata['category'] = 'aksrt';
|
||||
}
|
||||
|
||||
return $metadata;
|
||||
});
|
||||
|
||||
/**
|
||||
* Show widget placement hints in the admin form.
|
||||
*/
|
||||
add_action('in_widget_form', function (WP_Widget $widget): void {
|
||||
$map = [
|
||||
'aksrt_about' => 'sidebar',
|
||||
'aksrt_categories' => 'sidebar',
|
||||
'aksrt_tag_cloud' => 'sidebar',
|
||||
'aksrt_hot_posts' => 'sidebar',
|
||||
'aksrt_tab_posts' => 'hybrid',
|
||||
'aksrt_category_block' => 'hybrid',
|
||||
'aksrt_category_tabs' => 'hybrid',
|
||||
'aksrt_category_recommend' => 'homepage',
|
||||
];
|
||||
|
||||
$scope = $map[$widget->id_base] ?? '';
|
||||
if ($scope !== '') {
|
||||
aksrt_widget_render_location_help($scope);
|
||||
}
|
||||
}, 10, 1);
|
||||
Reference in new issue
Block a user