refactor(autoload): migrate theme classes to Composer PSR-4
Move 288 types from global AKSRT_* + inc/classes manual requires into
namespace AKSRT\{Domain} under src/AKSRT/, wire composer.json psr-4,
and slim bootstrap to helpers/modules/hooks only. Split multi-class
widget/gateway files, update admin contract audit paths, and document
the layout in ADR/README.
This commit is contained in:
1 parent
8f349b40fb
commit
aaf458af1c
404 files changed
+13602
-7705
No files matched your search
@@ -0,0 +1,373 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AboutWidget 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\Auth\OAuth') && method_exists(\AKSRT\Auth\OAuth::get_instance(), 'get_login_buttons')) {
|
||||
$oauth_html = \AKSRT\Auth\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'] ?? ''),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class ButtonsWidget 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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class CategoriesWidget 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']),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,439 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Homepage content widgets aligned to the fixed Zibll-like layout.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!function_exists('aksrt_home_widget_allowed_layouts')) {
|
||||
/**
|
||||
* Allowed post-card layouts for homepage content widgets.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
function aksrt_home_widget_allowed_layouts(): array {
|
||||
return [
|
||||
'card' => __('卡片', 'aksrt'),
|
||||
'list' => __('列表', 'aksrt'),
|
||||
'grid-cms' => __('资源网格', 'aksrt'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_post_types')) {
|
||||
/**
|
||||
* Post types used by homepage widgets.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
function aksrt_home_widget_post_types(): array {
|
||||
return ['post'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_available_categories')) {
|
||||
/**
|
||||
* Return categories for widget admin forms.
|
||||
*
|
||||
* @return array<int, \WP_Term>
|
||||
*/
|
||||
function aksrt_home_widget_available_categories(): array {
|
||||
$categories = get_categories([
|
||||
'taxonomy' => 'category',
|
||||
'hide_empty' => false,
|
||||
'orderby' => 'name',
|
||||
'order' => 'ASC',
|
||||
]);
|
||||
|
||||
return is_array($categories) ? $categories : [];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_fallback_category_ids')) {
|
||||
/**
|
||||
* Pick top categories as fallback when widget instance has no selection.
|
||||
*
|
||||
* @param int $limit Maximum category ids to return.
|
||||
* @return array<int, int>
|
||||
*/
|
||||
function aksrt_home_widget_fallback_category_ids(int $limit = 1): array {
|
||||
$limit = max(1, min(8, $limit));
|
||||
$terms = get_terms([
|
||||
'taxonomy' => 'category',
|
||||
'orderby' => 'count',
|
||||
'order' => 'DESC',
|
||||
'number' => $limit,
|
||||
'hide_empty' => true,
|
||||
'fields' => 'ids',
|
||||
]);
|
||||
|
||||
if (is_wp_error($terms) || !is_array($terms)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_values(array_filter(array_map('absint', $terms)));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_normalize_category_ids')) {
|
||||
/**
|
||||
* Normalize selected category ids.
|
||||
*
|
||||
* @param mixed $raw_ids Raw ids from widget instance.
|
||||
* @param int $limit Maximum ids to keep.
|
||||
* @return array<int, int>
|
||||
*/
|
||||
function aksrt_home_widget_normalize_category_ids($raw_ids, int $limit = 4): array {
|
||||
$ids = array_map('absint', (array) $raw_ids);
|
||||
$ids = array_values(array_filter($ids, static fn($id) => $id > 0));
|
||||
|
||||
if ($limit > 0) {
|
||||
$ids = array_slice($ids, 0, $limit);
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_resolve_layout')) {
|
||||
/**
|
||||
* Sanitize requested widget post layout.
|
||||
*
|
||||
* @param mixed $layout Layout value.
|
||||
* @param string $default Default layout.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_home_widget_resolve_layout($layout, string $default = 'card'): string {
|
||||
$allowed = array_keys(aksrt_home_widget_allowed_layouts());
|
||||
$layout = is_string($layout) ? $layout : $default;
|
||||
|
||||
return in_array($layout, $allowed, true) ? $layout : $default;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_resolve_context')) {
|
||||
/**
|
||||
* Resolve widget area context from sidebar args.
|
||||
*/
|
||||
function aksrt_home_widget_resolve_context(array $args): string {
|
||||
if (function_exists('aksrt_widget_get_area_context')) {
|
||||
return aksrt_widget_get_area_context($args);
|
||||
}
|
||||
|
||||
return 'homepage';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_effective_layout')) {
|
||||
/**
|
||||
* Compact rich homepage widgets when placed into a sidebar area.
|
||||
*/
|
||||
function aksrt_home_widget_effective_layout(string $layout, string $context): string {
|
||||
if ($context === 'sidebar' && $layout !== 'list') {
|
||||
return 'list';
|
||||
}
|
||||
|
||||
return $layout;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_effective_columns')) {
|
||||
/**
|
||||
* Sidebar widgets should stay single-column.
|
||||
*/
|
||||
function aksrt_home_widget_effective_columns(int $columns, string $context): int {
|
||||
if ($context === 'sidebar') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return max(1, min(4, $columns));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_render_header')) {
|
||||
/**
|
||||
* Render a compact Zibll-like section header.
|
||||
*
|
||||
* @param string $title Section title.
|
||||
* @param string $more_link Optional more link.
|
||||
* @param string $more_text Optional more text.
|
||||
* @return void
|
||||
*/
|
||||
function aksrt_home_widget_render_header(string $title, string $more_link = '', string $more_text = ''): void {
|
||||
?>
|
||||
<header class="aksrt-home-widget__header">
|
||||
<h3 class="aksrt-home-widget__title"><?php echo esc_html($title); ?></h3>
|
||||
<?php if ($more_link !== '') : ?>
|
||||
<a class="aksrt-home-widget__more" href="<?php echo esc_url($more_link); ?>">
|
||||
<?php echo esc_html($more_text !== '' ? $more_text : __('查看更多', 'aksrt')); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</header>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_render_post_grid')) {
|
||||
/**
|
||||
* Render posts for homepage widgets.
|
||||
*
|
||||
* Delegates to the unified shared renderer (aksrt_render_post_list).
|
||||
*
|
||||
* @param \WP_Query $query Posts query.
|
||||
* @param string $layout Card layout.
|
||||
* @param int $columns Desktop columns.
|
||||
* @param string $context Sidebar or homepage context.
|
||||
* @return void
|
||||
*/
|
||||
function aksrt_home_widget_render_post_grid(\WP_Query $query, string $layout = 'card', int $columns = 3, string $context = 'homepage'): void {
|
||||
if (!$query->have_posts()) {
|
||||
echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无内容', 'aksrt')) : '<p class="widget-empty">' . esc_html__('暂无内容', 'aksrt') . '</p>';
|
||||
wp_reset_postdata();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($context === 'sidebar') {
|
||||
$use_homepage_mini = function_exists('aksrt_is_fixed_homepage_request')
|
||||
&& aksrt_is_fixed_homepage_request()
|
||||
&& function_exists('aksrt_render_mini_list')
|
||||
&& (!class_exists('\AKSRT\Settings\Settings') || \AKSRT\Settings\Settings::homepage_sidebar_mini_enabled());
|
||||
|
||||
if ($use_homepage_mini && function_exists('aksrt_render_mini_list')) {
|
||||
echo '<div class="widget-tab-posts__list widget-tab-posts__list--mini aksrt-home-widget__sidebar-list">';
|
||||
aksrt_render_mini_list($query, ['context' => 'sidebar', 'show_thumb' => true]);
|
||||
echo '</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: simple sidebar list
|
||||
echo '<ul class="widget-tab-posts__list aksrt-home-widget__sidebar-list">';
|
||||
while ($query->have_posts()) {
|
||||
$query->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' => 'lazy', '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
|
||||
}
|
||||
echo '</ul>';
|
||||
wp_reset_postdata();
|
||||
return;
|
||||
}
|
||||
|
||||
// Homepage / content area: delegate to shared renderer
|
||||
$layout = aksrt_home_widget_resolve_layout($layout, 'card');
|
||||
$columns = max(1, min(4, $columns));
|
||||
|
||||
if (function_exists('aksrt_render_post_list')) {
|
||||
aksrt_render_post_list($query, [
|
||||
'mode' => $layout === 'grid-cms' ? 'grid-cms' : $layout,
|
||||
'context' => 'widget',
|
||||
'columns' => $columns,
|
||||
], true);
|
||||
} else {
|
||||
// Fallback: use template part
|
||||
$previous_layout = get_query_var('aksrt_post_card_layout', '');
|
||||
$previous_scene = get_query_var('aksrt_post_card_scene', '');
|
||||
|
||||
set_query_var('aksrt_post_card_layout', $layout);
|
||||
set_query_var('aksrt_post_card_scene', 'home');
|
||||
|
||||
$classes = [
|
||||
'aksrt-home-widget__post-grid',
|
||||
'aksrt-home-widget__post-grid--' . $layout,
|
||||
'post-grid',
|
||||
'post-grid--' . $layout,
|
||||
];
|
||||
|
||||
printf(
|
||||
'<div class="%1$s" style="--aksrt-widget-columns:%2$d;">',
|
||||
esc_attr(implode(' ', $classes)),
|
||||
(int) $columns
|
||||
);
|
||||
|
||||
while ($query->have_posts()) {
|
||||
$query->the_post();
|
||||
get_template_part('templates/parts/content', 'post-card');
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
|
||||
set_query_var('aksrt_post_card_layout', $previous_layout);
|
||||
set_query_var('aksrt_post_card_scene', $previous_scene);
|
||||
}
|
||||
|
||||
wp_reset_postdata();
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_get_lazyload_args')) {
|
||||
/**
|
||||
* Lazyload settings reused by category recommend widget.
|
||||
*
|
||||
* @return array{enabled: bool, placeholder_url: string}
|
||||
*/
|
||||
function aksrt_home_widget_get_lazyload_args(): array {
|
||||
$config = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_lazyload_config()
|
||||
: ['enabled' => false, 'placeholder_url' => ''];
|
||||
|
||||
return [
|
||||
'enabled' => ! empty($config['enabled']),
|
||||
'placeholder_url' => ! empty($config['placeholder_url'])
|
||||
? (string) $config['placeholder_url']
|
||||
: get_template_directory_uri() . '/assets/img/placeholder.svg',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Homepage category block widget.
|
||||
*/
|
||||
class CategoryBlockWidget extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_category_block',
|
||||
esc_html__('AKSRT 分类块', 'aksrt'),
|
||||
[
|
||||
'classname' => 'widget_aksrt_category_block',
|
||||
'description' => esc_html__('指定分类的最新内容区块,可挂载到首页内容挂载区。', 'aksrt'),
|
||||
'customize_selective_refresh' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$context = aksrt_home_widget_resolve_context($args);
|
||||
$category_id = $this->resolve_category_id($instance);
|
||||
$term = $category_id > 0 ? get_term($category_id, 'category') : null;
|
||||
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
|
||||
$title = $title !== '' ? $title : (($term && ! is_wp_error($term)) ? $term->name : __('精选分类', 'aksrt'));
|
||||
$count = max(1, min(12, (int) ($instance['count'] ?? 6)));
|
||||
$columns = aksrt_home_widget_effective_columns((int) ($instance['columns'] ?? 3), $context);
|
||||
$layout = aksrt_home_widget_effective_layout(
|
||||
aksrt_home_widget_resolve_layout($instance['layout'] ?? 'card', 'card'),
|
||||
$context
|
||||
);
|
||||
$show_more = ! empty($instance['show_more']);
|
||||
|
||||
$query_args = [
|
||||
'post_type' => aksrt_home_widget_post_types(),
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $count,
|
||||
'ignore_sticky_posts' => true,
|
||||
'no_found_rows' => true,
|
||||
];
|
||||
|
||||
if ($category_id > 0) {
|
||||
$query_args['cat'] = $category_id;
|
||||
}
|
||||
|
||||
$query = new \WP_Query($query_args);
|
||||
|
||||
echo $args['before_widget'];
|
||||
echo '<section class="aksrt-home-widget aksrt-home-widget--category-block">';
|
||||
|
||||
aksrt_home_widget_render_header(
|
||||
(string) $title,
|
||||
($show_more && $term && ! is_wp_error($term)) ? (string) get_category_link($category_id) : '',
|
||||
__('查看分类', 'aksrt')
|
||||
);
|
||||
|
||||
aksrt_home_widget_render_post_grid($query, $layout, $columns, $context);
|
||||
|
||||
echo '</section>';
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = (string) ($instance['title'] ?? '');
|
||||
$category_id = (int) ($instance['category_id'] ?? 0);
|
||||
$count = (int) ($instance['count'] ?? 6);
|
||||
$columns = (int) ($instance['columns'] ?? 3);
|
||||
$layout = aksrt_home_widget_resolve_layout($instance['layout'] ?? 'card', 'card');
|
||||
$show_more = ! empty($instance['show_more']);
|
||||
$categories = aksrt_home_widget_available_categories();
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('category_id')); ?>"><?php esc_html_e('分类:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('category_id')); ?>" name="<?php echo esc_attr($this->get_field_name('category_id')); ?>">
|
||||
<option value="0"><?php esc_html_e('自动选择最热门分类', 'aksrt'); ?></option>
|
||||
<?php foreach ($categories as $category) : ?>
|
||||
<option value="<?php echo (int) $category->term_id; ?>" <?php selected($category_id, (int) $category->term_id); ?>>
|
||||
<?php echo esc_html($category->name); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('文章数量:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" min="1" max="12" value="<?php echo (int) $count; ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('columns')); ?>"><?php esc_html_e('桌面列数:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('columns')); ?>" name="<?php echo esc_attr($this->get_field_name('columns')); ?>" type="number" min="1" max="4" value="<?php echo (int) $columns; ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('layout')); ?>"><?php esc_html_e('布局:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('layout')); ?>" name="<?php echo esc_attr($this->get_field_name('layout')); ?>">
|
||||
<?php foreach (aksrt_home_widget_allowed_layouts() as $value => $label) : ?>
|
||||
<option value="<?php echo esc_attr($value); ?>" <?php selected($layout, $value); ?>>
|
||||
<?php echo esc_html($label); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<input class="checkbox" type="checkbox" <?php checked($show_more); ?> id="<?php echo esc_attr($this->get_field_id('show_more')); ?>" name="<?php echo esc_attr($this->get_field_name('show_more')); ?>" value="1">
|
||||
<label for="<?php echo esc_attr($this->get_field_id('show_more')); ?>"><?php esc_html_e('显示分类链接', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php if (function_exists('aksrt_widget_render_location_help')) { aksrt_widget_render_location_help('hybrid'); } ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title' => sanitize_text_field($new_instance['title'] ?? ''),
|
||||
'category_id' => absint($new_instance['category_id'] ?? 0),
|
||||
'count' => max(1, min(12, absint($new_instance['count'] ?? 6))),
|
||||
'columns' => max(1, min(4, absint($new_instance['columns'] ?? 3))),
|
||||
'layout' => aksrt_home_widget_resolve_layout($new_instance['layout'] ?? 'card', 'card'),
|
||||
'show_more' => ! empty($new_instance['show_more']) ? 1 : 0,
|
||||
];
|
||||
}
|
||||
|
||||
private function resolve_category_id(array $instance): int {
|
||||
$category_id = absint($instance['category_id'] ?? 0);
|
||||
if ($category_id > 0) {
|
||||
return $category_id;
|
||||
}
|
||||
|
||||
$fallback = aksrt_home_widget_fallback_category_ids(1);
|
||||
return $fallback[0] ?? 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,844 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Homepage content widgets aligned to the fixed Zibll-like layout.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!function_exists('aksrt_home_widget_allowed_layouts')) {
|
||||
/**
|
||||
* Allowed post-card layouts for homepage content widgets.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
function aksrt_home_widget_allowed_layouts(): array {
|
||||
return [
|
||||
'card' => __('卡片', 'aksrt'),
|
||||
'list' => __('列表', 'aksrt'),
|
||||
'grid-cms' => __('资源网格', 'aksrt'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_post_types')) {
|
||||
/**
|
||||
* Post types used by homepage widgets.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
function aksrt_home_widget_post_types(): array {
|
||||
return ['post'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_available_categories')) {
|
||||
/**
|
||||
* Return categories for widget admin forms.
|
||||
*
|
||||
* @return array<int, \WP_Term>
|
||||
*/
|
||||
function aksrt_home_widget_available_categories(): array {
|
||||
$categories = get_categories([
|
||||
'taxonomy' => 'category',
|
||||
'hide_empty' => false,
|
||||
'orderby' => 'name',
|
||||
'order' => 'ASC',
|
||||
]);
|
||||
|
||||
return is_array($categories) ? $categories : [];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_fallback_category_ids')) {
|
||||
/**
|
||||
* Pick top categories as fallback when widget instance has no selection.
|
||||
*
|
||||
* @param int $limit Maximum category ids to return.
|
||||
* @return array<int, int>
|
||||
*/
|
||||
function aksrt_home_widget_fallback_category_ids(int $limit = 1): array {
|
||||
$limit = max(1, min(8, $limit));
|
||||
$terms = get_terms([
|
||||
'taxonomy' => 'category',
|
||||
'orderby' => 'count',
|
||||
'order' => 'DESC',
|
||||
'number' => $limit,
|
||||
'hide_empty' => true,
|
||||
'fields' => 'ids',
|
||||
]);
|
||||
|
||||
if (is_wp_error($terms) || !is_array($terms)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_values(array_filter(array_map('absint', $terms)));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_normalize_category_ids')) {
|
||||
/**
|
||||
* Normalize selected category ids.
|
||||
*
|
||||
* @param mixed $raw_ids Raw ids from widget instance.
|
||||
* @param int $limit Maximum ids to keep.
|
||||
* @return array<int, int>
|
||||
*/
|
||||
function aksrt_home_widget_normalize_category_ids($raw_ids, int $limit = 4): array {
|
||||
$ids = array_map('absint', (array) $raw_ids);
|
||||
$ids = array_values(array_filter($ids, static fn($id) => $id > 0));
|
||||
|
||||
if ($limit > 0) {
|
||||
$ids = array_slice($ids, 0, $limit);
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_resolve_layout')) {
|
||||
/**
|
||||
* Sanitize requested widget post layout.
|
||||
*
|
||||
* @param mixed $layout Layout value.
|
||||
* @param string $default Default layout.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_home_widget_resolve_layout($layout, string $default = 'card'): string {
|
||||
$allowed = array_keys(aksrt_home_widget_allowed_layouts());
|
||||
$layout = is_string($layout) ? $layout : $default;
|
||||
|
||||
return in_array($layout, $allowed, true) ? $layout : $default;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_resolve_context')) {
|
||||
/**
|
||||
* Resolve widget area context from sidebar args.
|
||||
*/
|
||||
function aksrt_home_widget_resolve_context(array $args): string {
|
||||
if (function_exists('aksrt_widget_get_area_context')) {
|
||||
return aksrt_widget_get_area_context($args);
|
||||
}
|
||||
|
||||
return 'homepage';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_effective_layout')) {
|
||||
/**
|
||||
* Compact rich homepage widgets when placed into a sidebar area.
|
||||
*/
|
||||
function aksrt_home_widget_effective_layout(string $layout, string $context): string {
|
||||
if ($context === 'sidebar' && $layout !== 'list') {
|
||||
return 'list';
|
||||
}
|
||||
|
||||
return $layout;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_effective_columns')) {
|
||||
/**
|
||||
* Sidebar widgets should stay single-column.
|
||||
*/
|
||||
function aksrt_home_widget_effective_columns(int $columns, string $context): int {
|
||||
if ($context === 'sidebar') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return max(1, min(4, $columns));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_render_header')) {
|
||||
/**
|
||||
* Render a compact Zibll-like section header.
|
||||
*
|
||||
* @param string $title Section title.
|
||||
* @param string $more_link Optional more link.
|
||||
* @param string $more_text Optional more text.
|
||||
* @return void
|
||||
*/
|
||||
function aksrt_home_widget_render_header(string $title, string $more_link = '', string $more_text = ''): void {
|
||||
?>
|
||||
<header class="aksrt-home-widget__header">
|
||||
<h3 class="aksrt-home-widget__title"><?php echo esc_html($title); ?></h3>
|
||||
<?php if ($more_link !== '') : ?>
|
||||
<a class="aksrt-home-widget__more" href="<?php echo esc_url($more_link); ?>">
|
||||
<?php echo esc_html($more_text !== '' ? $more_text : __('查看更多', 'aksrt')); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</header>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_render_post_grid')) {
|
||||
/**
|
||||
* Render posts for homepage widgets.
|
||||
*
|
||||
* Delegates to the unified shared renderer (aksrt_render_post_list).
|
||||
*
|
||||
* @param \WP_Query $query Posts query.
|
||||
* @param string $layout Card layout.
|
||||
* @param int $columns Desktop columns.
|
||||
* @param string $context Sidebar or homepage context.
|
||||
* @return void
|
||||
*/
|
||||
function aksrt_home_widget_render_post_grid(\WP_Query $query, string $layout = 'card', int $columns = 3, string $context = 'homepage'): void {
|
||||
if (!$query->have_posts()) {
|
||||
echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无内容', 'aksrt')) : '<p class="widget-empty">' . esc_html__('暂无内容', 'aksrt') . '</p>';
|
||||
wp_reset_postdata();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($context === 'sidebar') {
|
||||
$use_homepage_mini = function_exists('aksrt_is_fixed_homepage_request')
|
||||
&& aksrt_is_fixed_homepage_request()
|
||||
&& function_exists('aksrt_render_mini_list')
|
||||
&& (!class_exists('\AKSRT\Settings\Settings') || \AKSRT\Settings\Settings::homepage_sidebar_mini_enabled());
|
||||
|
||||
if ($use_homepage_mini && function_exists('aksrt_render_mini_list')) {
|
||||
echo '<div class="widget-tab-posts__list widget-tab-posts__list--mini aksrt-home-widget__sidebar-list">';
|
||||
aksrt_render_mini_list($query, ['context' => 'sidebar', 'show_thumb' => true]);
|
||||
echo '</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: simple sidebar list
|
||||
echo '<ul class="widget-tab-posts__list aksrt-home-widget__sidebar-list">';
|
||||
while ($query->have_posts()) {
|
||||
$query->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' => 'lazy', '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
|
||||
}
|
||||
echo '</ul>';
|
||||
wp_reset_postdata();
|
||||
return;
|
||||
}
|
||||
|
||||
// Homepage / content area: delegate to shared renderer
|
||||
$layout = aksrt_home_widget_resolve_layout($layout, 'card');
|
||||
$columns = max(1, min(4, $columns));
|
||||
|
||||
if (function_exists('aksrt_render_post_list')) {
|
||||
aksrt_render_post_list($query, [
|
||||
'mode' => $layout === 'grid-cms' ? 'grid-cms' : $layout,
|
||||
'context' => 'widget',
|
||||
'columns' => $columns,
|
||||
], true);
|
||||
} else {
|
||||
// Fallback: use template part
|
||||
$previous_layout = get_query_var('aksrt_post_card_layout', '');
|
||||
$previous_scene = get_query_var('aksrt_post_card_scene', '');
|
||||
|
||||
set_query_var('aksrt_post_card_layout', $layout);
|
||||
set_query_var('aksrt_post_card_scene', 'home');
|
||||
|
||||
$classes = [
|
||||
'aksrt-home-widget__post-grid',
|
||||
'aksrt-home-widget__post-grid--' . $layout,
|
||||
'post-grid',
|
||||
'post-grid--' . $layout,
|
||||
];
|
||||
|
||||
printf(
|
||||
'<div class="%1$s" style="--aksrt-widget-columns:%2$d;">',
|
||||
esc_attr(implode(' ', $classes)),
|
||||
(int) $columns
|
||||
);
|
||||
|
||||
while ($query->have_posts()) {
|
||||
$query->the_post();
|
||||
get_template_part('templates/parts/content', 'post-card');
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
|
||||
set_query_var('aksrt_post_card_layout', $previous_layout);
|
||||
set_query_var('aksrt_post_card_scene', $previous_scene);
|
||||
}
|
||||
|
||||
wp_reset_postdata();
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_get_lazyload_args')) {
|
||||
/**
|
||||
* Lazyload settings reused by category recommend widget.
|
||||
*
|
||||
* @return array{enabled: bool, placeholder_url: string}
|
||||
*/
|
||||
function aksrt_home_widget_get_lazyload_args(): array {
|
||||
$config = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_lazyload_config()
|
||||
: ['enabled' => false, 'placeholder_url' => ''];
|
||||
|
||||
return [
|
||||
'enabled' => ! empty($config['enabled']),
|
||||
'placeholder_url' => ! empty($config['placeholder_url'])
|
||||
? (string) $config['placeholder_url']
|
||||
: get_template_directory_uri() . '/assets/img/placeholder.svg',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Homepage category block widget.
|
||||
*/
|
||||
class CategoryBlockWidget extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_category_block',
|
||||
esc_html__('AKSRT 分类块', 'aksrt'),
|
||||
[
|
||||
'classname' => 'widget_aksrt_category_block',
|
||||
'description' => esc_html__('指定分类的最新内容区块,可挂载到首页内容挂载区。', 'aksrt'),
|
||||
'customize_selective_refresh' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$context = aksrt_home_widget_resolve_context($args);
|
||||
$category_id = $this->resolve_category_id($instance);
|
||||
$term = $category_id > 0 ? get_term($category_id, 'category') : null;
|
||||
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
|
||||
$title = $title !== '' ? $title : (($term && ! is_wp_error($term)) ? $term->name : __('精选分类', 'aksrt'));
|
||||
$count = max(1, min(12, (int) ($instance['count'] ?? 6)));
|
||||
$columns = aksrt_home_widget_effective_columns((int) ($instance['columns'] ?? 3), $context);
|
||||
$layout = aksrt_home_widget_effective_layout(
|
||||
aksrt_home_widget_resolve_layout($instance['layout'] ?? 'card', 'card'),
|
||||
$context
|
||||
);
|
||||
$show_more = ! empty($instance['show_more']);
|
||||
|
||||
$query_args = [
|
||||
'post_type' => aksrt_home_widget_post_types(),
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $count,
|
||||
'ignore_sticky_posts' => true,
|
||||
'no_found_rows' => true,
|
||||
];
|
||||
|
||||
if ($category_id > 0) {
|
||||
$query_args['cat'] = $category_id;
|
||||
}
|
||||
|
||||
$query = new \WP_Query($query_args);
|
||||
|
||||
echo $args['before_widget'];
|
||||
echo '<section class="aksrt-home-widget aksrt-home-widget--category-block">';
|
||||
|
||||
aksrt_home_widget_render_header(
|
||||
(string) $title,
|
||||
($show_more && $term && ! is_wp_error($term)) ? (string) get_category_link($category_id) : '',
|
||||
__('查看分类', 'aksrt')
|
||||
);
|
||||
|
||||
aksrt_home_widget_render_post_grid($query, $layout, $columns, $context);
|
||||
|
||||
echo '</section>';
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = (string) ($instance['title'] ?? '');
|
||||
$category_id = (int) ($instance['category_id'] ?? 0);
|
||||
$count = (int) ($instance['count'] ?? 6);
|
||||
$columns = (int) ($instance['columns'] ?? 3);
|
||||
$layout = aksrt_home_widget_resolve_layout($instance['layout'] ?? 'card', 'card');
|
||||
$show_more = ! empty($instance['show_more']);
|
||||
$categories = aksrt_home_widget_available_categories();
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('category_id')); ?>"><?php esc_html_e('分类:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('category_id')); ?>" name="<?php echo esc_attr($this->get_field_name('category_id')); ?>">
|
||||
<option value="0"><?php esc_html_e('自动选择最热门分类', 'aksrt'); ?></option>
|
||||
<?php foreach ($categories as $category) : ?>
|
||||
<option value="<?php echo (int) $category->term_id; ?>" <?php selected($category_id, (int) $category->term_id); ?>>
|
||||
<?php echo esc_html($category->name); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('文章数量:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" min="1" max="12" value="<?php echo (int) $count; ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('columns')); ?>"><?php esc_html_e('桌面列数:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('columns')); ?>" name="<?php echo esc_attr($this->get_field_name('columns')); ?>" type="number" min="1" max="4" value="<?php echo (int) $columns; ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('layout')); ?>"><?php esc_html_e('布局:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('layout')); ?>" name="<?php echo esc_attr($this->get_field_name('layout')); ?>">
|
||||
<?php foreach (aksrt_home_widget_allowed_layouts() as $value => $label) : ?>
|
||||
<option value="<?php echo esc_attr($value); ?>" <?php selected($layout, $value); ?>>
|
||||
<?php echo esc_html($label); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<input class="checkbox" type="checkbox" <?php checked($show_more); ?> id="<?php echo esc_attr($this->get_field_id('show_more')); ?>" name="<?php echo esc_attr($this->get_field_name('show_more')); ?>" value="1">
|
||||
<label for="<?php echo esc_attr($this->get_field_id('show_more')); ?>"><?php esc_html_e('显示分类链接', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php if (function_exists('aksrt_widget_render_location_help')) { aksrt_widget_render_location_help('hybrid'); } ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title' => sanitize_text_field($new_instance['title'] ?? ''),
|
||||
'category_id' => absint($new_instance['category_id'] ?? 0),
|
||||
'count' => max(1, min(12, absint($new_instance['count'] ?? 6))),
|
||||
'columns' => max(1, min(4, absint($new_instance['columns'] ?? 3))),
|
||||
'layout' => aksrt_home_widget_resolve_layout($new_instance['layout'] ?? 'card', 'card'),
|
||||
'show_more' => ! empty($new_instance['show_more']) ? 1 : 0,
|
||||
];
|
||||
}
|
||||
|
||||
private function resolve_category_id(array $instance): int {
|
||||
$category_id = absint($instance['category_id'] ?? 0);
|
||||
if ($category_id > 0) {
|
||||
return $category_id;
|
||||
}
|
||||
|
||||
$fallback = aksrt_home_widget_fallback_category_ids(1);
|
||||
return $fallback[0] ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Homepage category tabs widget.
|
||||
*/
|
||||
class CategoryTabsWidget extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_category_tabs',
|
||||
esc_html__('AKSRT 分类 Tabs', 'aksrt'),
|
||||
[
|
||||
'classname' => 'widget_aksrt_category_tabs',
|
||||
'description' => esc_html__('多分类切换内容流,可挂载到首页内容挂载区。', 'aksrt'),
|
||||
'customize_selective_refresh' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$context = aksrt_home_widget_resolve_context($args);
|
||||
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
|
||||
$title = $title !== '' ? $title : __('热门分类', 'aksrt');
|
||||
$count = max(1, min(12, (int) ($instance['count'] ?? 6)));
|
||||
$columns = aksrt_home_widget_effective_columns((int) ($instance['columns'] ?? 3), $context);
|
||||
$layout = aksrt_home_widget_effective_layout(
|
||||
aksrt_home_widget_resolve_layout($instance['layout'] ?? 'card', 'card'),
|
||||
$context
|
||||
);
|
||||
$show_more = ! empty($instance['show_more']);
|
||||
$tabs = $this->resolve_tabs($instance);
|
||||
|
||||
echo $args['before_widget'];
|
||||
echo '<section class="aksrt-home-widget aksrt-home-widget--tabs widget-tab-posts">';
|
||||
|
||||
if (empty($tabs)) {
|
||||
echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无可用分类', 'aksrt')) : '<p class="widget-empty">' . esc_html__('暂无可用分类', 'aksrt') . '</p>';
|
||||
echo '</section>';
|
||||
echo $args['after_widget'];
|
||||
return;
|
||||
}
|
||||
|
||||
?>
|
||||
<header class="aksrt-home-widget__header aksrt-home-widget__header--tabs">
|
||||
<h3 class="aksrt-home-widget__title"><?php echo esc_html($title); ?></h3>
|
||||
<ul class="widget-tab-posts__nav aksrt-home-widget__tab-nav" role="tablist">
|
||||
<?php foreach ($tabs as $index => $tab) : ?>
|
||||
<li role="presentation">
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
class="widget-tab-posts__tab<?php echo 0 === $index ? ' active' : ''; ?>"
|
||||
data-tab="cat-<?php echo (int) $tab['id']; ?>"
|
||||
aria-selected="<?php echo 0 === $index ? 'true' : 'false'; ?>"
|
||||
>
|
||||
<?php echo esc_html($tab['name']); ?>
|
||||
</button>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</header>
|
||||
<?php
|
||||
|
||||
foreach ($tabs as $index => $tab) {
|
||||
$query = new \WP_Query([
|
||||
'post_type' => aksrt_home_widget_post_types(),
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $count,
|
||||
'ignore_sticky_posts' => true,
|
||||
'no_found_rows' => true,
|
||||
'cat' => (int) $tab['id'],
|
||||
]);
|
||||
?>
|
||||
<div class="widget-tab-posts__panel<?php echo 0 === $index ? ' active' : ''; ?>" data-tab-panel="cat-<?php echo (int) $tab['id']; ?>" role="tabpanel"<?php echo 0 === $index ? '' : ' hidden'; ?>>
|
||||
<?php aksrt_home_widget_render_post_grid($query, $layout, $columns, $context); ?>
|
||||
<?php if ($show_more) : ?>
|
||||
<div class="aksrt-home-widget__footer">
|
||||
<a class="aksrt-home-widget__more" href="<?php echo esc_url($tab['link']); ?>">
|
||||
<?php esc_html_e('查看更多', 'aksrt'); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
echo '</section>';
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = (string) ($instance['title'] ?? '');
|
||||
$category_ids = aksrt_home_widget_normalize_category_ids($instance['category_ids'] ?? [], 6);
|
||||
$count = (int) ($instance['count'] ?? 6);
|
||||
$columns = (int) ($instance['columns'] ?? 3);
|
||||
$layout = aksrt_home_widget_resolve_layout($instance['layout'] ?? 'card', 'card');
|
||||
$show_more = ! empty($instance['show_more']);
|
||||
$categories = aksrt_home_widget_available_categories();
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('category_ids')); ?>"><?php esc_html_e('分类(可多选):', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('category_ids')); ?>" name="<?php echo esc_attr($this->get_field_name('category_ids')); ?>[]" multiple size="6">
|
||||
<?php foreach ($categories as $category) : ?>
|
||||
<option value="<?php echo (int) $category->term_id; ?>" <?php selected(in_array((int) $category->term_id, $category_ids, true)); ?>>
|
||||
<?php echo esc_html($category->name); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<small><?php esc_html_e('留空时自动选择热门分类。', 'aksrt'); ?></small>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('每个 Tab 的文章数量:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" min="1" max="12" value="<?php echo (int) $count; ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('columns')); ?>"><?php esc_html_e('桌面列数:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('columns')); ?>" name="<?php echo esc_attr($this->get_field_name('columns')); ?>" type="number" min="1" max="4" value="<?php echo (int) $columns; ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('layout')); ?>"><?php esc_html_e('布局:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('layout')); ?>" name="<?php echo esc_attr($this->get_field_name('layout')); ?>">
|
||||
<?php foreach (aksrt_home_widget_allowed_layouts() as $value => $label) : ?>
|
||||
<option value="<?php echo esc_attr($value); ?>" <?php selected($layout, $value); ?>>
|
||||
<?php echo esc_html($label); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<input class="checkbox" type="checkbox" <?php checked($show_more); ?> id="<?php echo esc_attr($this->get_field_id('show_more')); ?>" name="<?php echo esc_attr($this->get_field_name('show_more')); ?>" value="1">
|
||||
<label for="<?php echo esc_attr($this->get_field_id('show_more')); ?>"><?php esc_html_e('显示“查看更多”链接', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php if (function_exists('aksrt_widget_render_location_help')) { aksrt_widget_render_location_help('hybrid'); } ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title' => sanitize_text_field($new_instance['title'] ?? ''),
|
||||
'category_ids' => aksrt_home_widget_normalize_category_ids($new_instance['category_ids'] ?? [], 6),
|
||||
'count' => max(1, min(12, absint($new_instance['count'] ?? 6))),
|
||||
'columns' => max(1, min(4, absint($new_instance['columns'] ?? 3))),
|
||||
'layout' => aksrt_home_widget_resolve_layout($new_instance['layout'] ?? 'card', 'card'),
|
||||
'show_more' => ! empty($new_instance['show_more']) ? 1 : 0,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve configured category tabs.
|
||||
*
|
||||
* @param array $instance Widget instance.
|
||||
* @return array<int, array{id:int,name:string,link:string}>
|
||||
*/
|
||||
private function resolve_tabs(array $instance): array {
|
||||
$category_ids = aksrt_home_widget_normalize_category_ids($instance['category_ids'] ?? [], 6);
|
||||
if (empty($category_ids)) {
|
||||
$category_ids = aksrt_home_widget_fallback_category_ids(4);
|
||||
}
|
||||
|
||||
$tabs = [];
|
||||
foreach ($category_ids as $category_id) {
|
||||
$term = get_term($category_id, 'category');
|
||||
if (! $term || is_wp_error($term)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tabs[] = [
|
||||
'id' => (int) $category_id,
|
||||
'name' => $term->name,
|
||||
'link' => get_category_link($category_id),
|
||||
];
|
||||
}
|
||||
|
||||
return $tabs;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Homepage category recommend cards widget.
|
||||
*/
|
||||
class CategoryRecommendWidget extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_category_recommend',
|
||||
esc_html__('AKSRT 分类推荐卡片', 'aksrt'),
|
||||
[
|
||||
'classname' => 'widget_aksrt_category_recommend',
|
||||
'description' => esc_html__('分类推荐卡片区块,可挂载到首页内容挂载区。', 'aksrt'),
|
||||
'customize_selective_refresh' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
|
||||
$title = $title !== '' ? $title : __('分类推荐', 'aksrt');
|
||||
$category_ids = aksrt_home_widget_normalize_category_ids($instance['category_ids'] ?? [], 4);
|
||||
$posts_per_cat = max(1, min(8, (int) ($instance['posts_per_category'] ?? 5)));
|
||||
$columns = max(1, min(4, (int) ($instance['columns'] ?? 2)));
|
||||
$show_views = ! empty($instance['show_views']);
|
||||
$show_description = ! empty($instance['show_description']);
|
||||
$show_dates = ! empty($instance['show_dates']);
|
||||
$show_more = ! empty($instance['show_more']);
|
||||
|
||||
if (empty($category_ids)) {
|
||||
$category_ids = aksrt_home_widget_fallback_category_ids(4);
|
||||
}
|
||||
|
||||
$cards = $this->build_cards($category_ids, $posts_per_cat, $show_views, $show_description);
|
||||
|
||||
echo $args['before_widget'];
|
||||
echo '<section class="aksrt-home-widget aksrt-home-widget--recommend">';
|
||||
|
||||
aksrt_home_widget_render_header($title);
|
||||
|
||||
if (empty($cards)) {
|
||||
echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无推荐分类', 'aksrt')) : '<p class="widget-empty">' . esc_html__('暂无推荐分类', 'aksrt') . '</p>';
|
||||
echo '</section>';
|
||||
echo $args['after_widget'];
|
||||
return;
|
||||
}
|
||||
|
||||
$lazy = aksrt_home_widget_get_lazyload_args();
|
||||
printf('<div class="aksrt-category-recommend__grid" style="--aksrt-recommend-columns:%d;">', (int) $columns);
|
||||
|
||||
foreach ($cards as $card) {
|
||||
?>
|
||||
<article class="aksrt-category-recommend__card">
|
||||
<div class="aksrt-category-recommend__hero">
|
||||
<a href="<?php echo esc_url($card['link']); ?>">
|
||||
<?php if ($lazy['enabled']) : ?>
|
||||
<span class="aksrt-skeleton"></span>
|
||||
<img class="aksrt-category-recommend__cover aksrt-lazy" data-src="<?php echo esc_url($card['cover']); ?>" src="<?php echo esc_url($lazy['placeholder_url']); ?>" alt="<?php echo esc_attr($card['name']); ?>">
|
||||
<?php else : ?>
|
||||
<img class="aksrt-category-recommend__cover" src="<?php echo esc_url($card['cover']); ?>" alt="<?php echo esc_attr($card['name']); ?>">
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
<span class="aksrt-category-recommend__badge"><?php echo esc_html(sprintf(_n('%d 篇文章', '%d 篇文章', $card['count'], 'aksrt'), $card['count'])); ?></span>
|
||||
<?php if ($show_views && $card['views'] > 0) : ?>
|
||||
<span class="aksrt-category-recommend__views">
|
||||
<i class="fas fa-eye" aria-hidden="true"></i>
|
||||
<?php echo esc_html(function_exists('aksrt_format_number') ? aksrt_format_number($card['views']) : number_format_i18n($card['views'])); ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="aksrt-category-recommend__body">
|
||||
<div class="aksrt-category-recommend__meta">
|
||||
<h4 class="aksrt-category-recommend__name">
|
||||
<a href="<?php echo esc_url($card['link']); ?>"><?php echo esc_html($card['name']); ?></a>
|
||||
</h4>
|
||||
<?php if ($show_description && $card['description'] !== '') : ?>
|
||||
<p class="aksrt-category-recommend__desc"><?php echo esc_html($card['description']); ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<ul class="aksrt-category-recommend__posts">
|
||||
<?php foreach ($card['posts'] as $post_item) : ?>
|
||||
<li class="aksrt-category-recommend__post">
|
||||
<a class="aksrt-category-recommend__post-link" href="<?php echo esc_url($post_item['link']); ?>">
|
||||
<?php echo esc_html($post_item['title']); ?>
|
||||
</a>
|
||||
<?php if ($show_dates && $post_item['date'] !== '') : ?>
|
||||
<span class="aksrt-category-recommend__date"><?php echo esc_html($post_item['date']); ?></span>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php if ($show_more) : ?>
|
||||
<div class="aksrt-home-widget__footer">
|
||||
<a class="aksrt-home-widget__more" href="<?php echo esc_url($card['link']); ?>">
|
||||
<?php esc_html_e('查看更多', 'aksrt'); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</article>
|
||||
<?php
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
echo '</section>';
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = (string) ($instance['title'] ?? __('分类推荐', 'aksrt'));
|
||||
$category_ids = aksrt_home_widget_normalize_category_ids($instance['category_ids'] ?? [], 4);
|
||||
$posts_per_cat = (int) ($instance['posts_per_category'] ?? 5);
|
||||
$columns = (int) ($instance['columns'] ?? 2);
|
||||
$show_views = ! empty($instance['show_views']);
|
||||
$show_description = ! array_key_exists('show_description', $instance) || ! empty($instance['show_description']);
|
||||
$show_dates = ! array_key_exists('show_dates', $instance) || ! empty($instance['show_dates']);
|
||||
$show_more = ! array_key_exists('show_more', $instance) || ! empty($instance['show_more']);
|
||||
$categories = aksrt_home_widget_available_categories();
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('category_ids')); ?>"><?php esc_html_e('分类(最多 4 个):', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('category_ids')); ?>" name="<?php echo esc_attr($this->get_field_name('category_ids')); ?>[]" multiple size="6">
|
||||
<?php foreach ($categories as $category) : ?>
|
||||
<option value="<?php echo (int) $category->term_id; ?>" <?php selected(in_array((int) $category->term_id, $category_ids, true)); ?>>
|
||||
<?php echo esc_html($category->name); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<small><?php esc_html_e('留空时自动选择热门分类。', 'aksrt'); ?></small>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('posts_per_category')); ?>"><?php esc_html_e('每个分类展示文章数:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('posts_per_category')); ?>" name="<?php echo esc_attr($this->get_field_name('posts_per_category')); ?>" type="number" min="1" max="8" value="<?php echo (int) $posts_per_cat; ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('columns')); ?>"><?php esc_html_e('桌面列数:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('columns')); ?>" name="<?php echo esc_attr($this->get_field_name('columns')); ?>" type="number" min="1" max="4" value="<?php echo (int) $columns; ?>">
|
||||
</p>
|
||||
<p>
|
||||
<input class="checkbox" type="checkbox" <?php checked($show_views); ?> id="<?php echo esc_attr($this->get_field_id('show_views')); ?>" name="<?php echo esc_attr($this->get_field_name('show_views')); ?>" value="1">
|
||||
<label for="<?php echo esc_attr($this->get_field_id('show_views')); ?>"><?php esc_html_e('显示分类总浏览量', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<p>
|
||||
<input class="checkbox" type="checkbox" <?php checked($show_description); ?> id="<?php echo esc_attr($this->get_field_id('show_description')); ?>" name="<?php echo esc_attr($this->get_field_name('show_description')); ?>" value="1">
|
||||
<label for="<?php echo esc_attr($this->get_field_id('show_description')); ?>"><?php esc_html_e('显示分类描述', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<p>
|
||||
<input class="checkbox" type="checkbox" <?php checked($show_dates); ?> id="<?php echo esc_attr($this->get_field_id('show_dates')); ?>" name="<?php echo esc_attr($this->get_field_name('show_dates')); ?>" value="1">
|
||||
<label for="<?php echo esc_attr($this->get_field_id('show_dates')); ?>"><?php esc_html_e('显示文章时间', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<p>
|
||||
<input class="checkbox" type="checkbox" <?php checked($show_more); ?> id="<?php echo esc_attr($this->get_field_id('show_more')); ?>" name="<?php echo esc_attr($this->get_field_name('show_more')); ?>" value="1">
|
||||
<label for="<?php echo esc_attr($this->get_field_id('show_more')); ?>"><?php esc_html_e('显示“查看更多”链接', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php if (function_exists('aksrt_widget_render_location_help')) { aksrt_widget_render_location_help('homepage'); } ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title' => sanitize_text_field($new_instance['title'] ?? ''),
|
||||
'category_ids' => aksrt_home_widget_normalize_category_ids($new_instance['category_ids'] ?? [], 4),
|
||||
'posts_per_category' => max(1, min(8, absint($new_instance['posts_per_category'] ?? 5))),
|
||||
'columns' => max(1, min(4, absint($new_instance['columns'] ?? 2))),
|
||||
'show_views' => ! empty($new_instance['show_views']) ? 1 : 0,
|
||||
'show_description' => ! empty($new_instance['show_description']) ? 1 : 0,
|
||||
'show_dates' => ! empty($new_instance['show_dates']) ? 1 : 0,
|
||||
'show_more' => ! empty($new_instance['show_more']) ? 1 : 0,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build category recommend cards.
|
||||
*
|
||||
* @param array<int, int> $category_ids Category ids.
|
||||
* @param int $posts_per_cat Post count per category.
|
||||
* @param bool $show_views Whether to calculate views.
|
||||
* @param bool $show_description Whether to fetch description.
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function build_cards(array $category_ids, int $posts_per_cat, bool $show_views, bool $show_description): array {
|
||||
$cards = [];
|
||||
|
||||
foreach ($category_ids as $category_id) {
|
||||
$term = get_term($category_id, 'category');
|
||||
if (! $term || is_wp_error($term)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$posts = get_posts([
|
||||
'post_type' => aksrt_home_widget_post_types(),
|
||||
'cat' => $category_id,
|
||||
'posts_per_page' => $posts_per_cat,
|
||||
'post_status' => 'publish',
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'no_found_rows' => true,
|
||||
]);
|
||||
|
||||
$post_items = [];
|
||||
foreach ($posts as $post) {
|
||||
$post_items[] = [
|
||||
'title' => get_the_title($post->ID),
|
||||
'link' => get_permalink($post->ID),
|
||||
'date' => function_exists('aksrt_time_ago') ? aksrt_time_ago($post->post_date) : get_the_date('Y-m-d', $post->ID),
|
||||
];
|
||||
}
|
||||
|
||||
$cards[] = [
|
||||
'name' => $term->name,
|
||||
'link' => get_category_link($category_id),
|
||||
'cover' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_category_cover_url($category_id) : (get_template_directory_uri() . '/assets/img/placeholder.svg'),
|
||||
'description' => $show_description && class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_category_recommend_desc($category_id) : ($show_description ? (string) $term->description : ''),
|
||||
'count' => (int) $term->count,
|
||||
'views' => $show_views && function_exists('aksrt_get_category_total_views') ? aksrt_get_category_total_views($category_id) : 0,
|
||||
'posts' => $post_items,
|
||||
];
|
||||
}
|
||||
|
||||
return $cards;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,618 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Homepage content widgets aligned to the fixed Zibll-like layout.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!function_exists('aksrt_home_widget_allowed_layouts')) {
|
||||
/**
|
||||
* Allowed post-card layouts for homepage content widgets.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
function aksrt_home_widget_allowed_layouts(): array {
|
||||
return [
|
||||
'card' => __('卡片', 'aksrt'),
|
||||
'list' => __('列表', 'aksrt'),
|
||||
'grid-cms' => __('资源网格', 'aksrt'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_post_types')) {
|
||||
/**
|
||||
* Post types used by homepage widgets.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
function aksrt_home_widget_post_types(): array {
|
||||
return ['post'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_available_categories')) {
|
||||
/**
|
||||
* Return categories for widget admin forms.
|
||||
*
|
||||
* @return array<int, \WP_Term>
|
||||
*/
|
||||
function aksrt_home_widget_available_categories(): array {
|
||||
$categories = get_categories([
|
||||
'taxonomy' => 'category',
|
||||
'hide_empty' => false,
|
||||
'orderby' => 'name',
|
||||
'order' => 'ASC',
|
||||
]);
|
||||
|
||||
return is_array($categories) ? $categories : [];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_fallback_category_ids')) {
|
||||
/**
|
||||
* Pick top categories as fallback when widget instance has no selection.
|
||||
*
|
||||
* @param int $limit Maximum category ids to return.
|
||||
* @return array<int, int>
|
||||
*/
|
||||
function aksrt_home_widget_fallback_category_ids(int $limit = 1): array {
|
||||
$limit = max(1, min(8, $limit));
|
||||
$terms = get_terms([
|
||||
'taxonomy' => 'category',
|
||||
'orderby' => 'count',
|
||||
'order' => 'DESC',
|
||||
'number' => $limit,
|
||||
'hide_empty' => true,
|
||||
'fields' => 'ids',
|
||||
]);
|
||||
|
||||
if (is_wp_error($terms) || !is_array($terms)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_values(array_filter(array_map('absint', $terms)));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_normalize_category_ids')) {
|
||||
/**
|
||||
* Normalize selected category ids.
|
||||
*
|
||||
* @param mixed $raw_ids Raw ids from widget instance.
|
||||
* @param int $limit Maximum ids to keep.
|
||||
* @return array<int, int>
|
||||
*/
|
||||
function aksrt_home_widget_normalize_category_ids($raw_ids, int $limit = 4): array {
|
||||
$ids = array_map('absint', (array) $raw_ids);
|
||||
$ids = array_values(array_filter($ids, static fn($id) => $id > 0));
|
||||
|
||||
if ($limit > 0) {
|
||||
$ids = array_slice($ids, 0, $limit);
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_resolve_layout')) {
|
||||
/**
|
||||
* Sanitize requested widget post layout.
|
||||
*
|
||||
* @param mixed $layout Layout value.
|
||||
* @param string $default Default layout.
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_home_widget_resolve_layout($layout, string $default = 'card'): string {
|
||||
$allowed = array_keys(aksrt_home_widget_allowed_layouts());
|
||||
$layout = is_string($layout) ? $layout : $default;
|
||||
|
||||
return in_array($layout, $allowed, true) ? $layout : $default;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_resolve_context')) {
|
||||
/**
|
||||
* Resolve widget area context from sidebar args.
|
||||
*/
|
||||
function aksrt_home_widget_resolve_context(array $args): string {
|
||||
if (function_exists('aksrt_widget_get_area_context')) {
|
||||
return aksrt_widget_get_area_context($args);
|
||||
}
|
||||
|
||||
return 'homepage';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_effective_layout')) {
|
||||
/**
|
||||
* Compact rich homepage widgets when placed into a sidebar area.
|
||||
*/
|
||||
function aksrt_home_widget_effective_layout(string $layout, string $context): string {
|
||||
if ($context === 'sidebar' && $layout !== 'list') {
|
||||
return 'list';
|
||||
}
|
||||
|
||||
return $layout;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_effective_columns')) {
|
||||
/**
|
||||
* Sidebar widgets should stay single-column.
|
||||
*/
|
||||
function aksrt_home_widget_effective_columns(int $columns, string $context): int {
|
||||
if ($context === 'sidebar') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return max(1, min(4, $columns));
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_render_header')) {
|
||||
/**
|
||||
* Render a compact Zibll-like section header.
|
||||
*
|
||||
* @param string $title Section title.
|
||||
* @param string $more_link Optional more link.
|
||||
* @param string $more_text Optional more text.
|
||||
* @return void
|
||||
*/
|
||||
function aksrt_home_widget_render_header(string $title, string $more_link = '', string $more_text = ''): void {
|
||||
?>
|
||||
<header class="aksrt-home-widget__header">
|
||||
<h3 class="aksrt-home-widget__title"><?php echo esc_html($title); ?></h3>
|
||||
<?php if ($more_link !== '') : ?>
|
||||
<a class="aksrt-home-widget__more" href="<?php echo esc_url($more_link); ?>">
|
||||
<?php echo esc_html($more_text !== '' ? $more_text : __('查看更多', 'aksrt')); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</header>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_render_post_grid')) {
|
||||
/**
|
||||
* Render posts for homepage widgets.
|
||||
*
|
||||
* Delegates to the unified shared renderer (aksrt_render_post_list).
|
||||
*
|
||||
* @param \WP_Query $query Posts query.
|
||||
* @param string $layout Card layout.
|
||||
* @param int $columns Desktop columns.
|
||||
* @param string $context Sidebar or homepage context.
|
||||
* @return void
|
||||
*/
|
||||
function aksrt_home_widget_render_post_grid(\WP_Query $query, string $layout = 'card', int $columns = 3, string $context = 'homepage'): void {
|
||||
if (!$query->have_posts()) {
|
||||
echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无内容', 'aksrt')) : '<p class="widget-empty">' . esc_html__('暂无内容', 'aksrt') . '</p>';
|
||||
wp_reset_postdata();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($context === 'sidebar') {
|
||||
$use_homepage_mini = function_exists('aksrt_is_fixed_homepage_request')
|
||||
&& aksrt_is_fixed_homepage_request()
|
||||
&& function_exists('aksrt_render_mini_list')
|
||||
&& (!class_exists('\AKSRT\Settings\Settings') || \AKSRT\Settings\Settings::homepage_sidebar_mini_enabled());
|
||||
|
||||
if ($use_homepage_mini && function_exists('aksrt_render_mini_list')) {
|
||||
echo '<div class="widget-tab-posts__list widget-tab-posts__list--mini aksrt-home-widget__sidebar-list">';
|
||||
aksrt_render_mini_list($query, ['context' => 'sidebar', 'show_thumb' => true]);
|
||||
echo '</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: simple sidebar list
|
||||
echo '<ul class="widget-tab-posts__list aksrt-home-widget__sidebar-list">';
|
||||
while ($query->have_posts()) {
|
||||
$query->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' => 'lazy', '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
|
||||
}
|
||||
echo '</ul>';
|
||||
wp_reset_postdata();
|
||||
return;
|
||||
}
|
||||
|
||||
// Homepage / content area: delegate to shared renderer
|
||||
$layout = aksrt_home_widget_resolve_layout($layout, 'card');
|
||||
$columns = max(1, min(4, $columns));
|
||||
|
||||
if (function_exists('aksrt_render_post_list')) {
|
||||
aksrt_render_post_list($query, [
|
||||
'mode' => $layout === 'grid-cms' ? 'grid-cms' : $layout,
|
||||
'context' => 'widget',
|
||||
'columns' => $columns,
|
||||
], true);
|
||||
} else {
|
||||
// Fallback: use template part
|
||||
$previous_layout = get_query_var('aksrt_post_card_layout', '');
|
||||
$previous_scene = get_query_var('aksrt_post_card_scene', '');
|
||||
|
||||
set_query_var('aksrt_post_card_layout', $layout);
|
||||
set_query_var('aksrt_post_card_scene', 'home');
|
||||
|
||||
$classes = [
|
||||
'aksrt-home-widget__post-grid',
|
||||
'aksrt-home-widget__post-grid--' . $layout,
|
||||
'post-grid',
|
||||
'post-grid--' . $layout,
|
||||
];
|
||||
|
||||
printf(
|
||||
'<div class="%1$s" style="--aksrt-widget-columns:%2$d;">',
|
||||
esc_attr(implode(' ', $classes)),
|
||||
(int) $columns
|
||||
);
|
||||
|
||||
while ($query->have_posts()) {
|
||||
$query->the_post();
|
||||
get_template_part('templates/parts/content', 'post-card');
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
|
||||
set_query_var('aksrt_post_card_layout', $previous_layout);
|
||||
set_query_var('aksrt_post_card_scene', $previous_scene);
|
||||
}
|
||||
|
||||
wp_reset_postdata();
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_home_widget_get_lazyload_args')) {
|
||||
/**
|
||||
* Lazyload settings reused by category recommend widget.
|
||||
*
|
||||
* @return array{enabled: bool, placeholder_url: string}
|
||||
*/
|
||||
function aksrt_home_widget_get_lazyload_args(): array {
|
||||
$config = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_lazyload_config()
|
||||
: ['enabled' => false, 'placeholder_url' => ''];
|
||||
|
||||
return [
|
||||
'enabled' => ! empty($config['enabled']),
|
||||
'placeholder_url' => ! empty($config['placeholder_url'])
|
||||
? (string) $config['placeholder_url']
|
||||
: get_template_directory_uri() . '/assets/img/placeholder.svg',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Homepage category block widget.
|
||||
*/
|
||||
class CategoryBlockWidget extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_category_block',
|
||||
esc_html__('AKSRT 分类块', 'aksrt'),
|
||||
[
|
||||
'classname' => 'widget_aksrt_category_block',
|
||||
'description' => esc_html__('指定分类的最新内容区块,可挂载到首页内容挂载区。', 'aksrt'),
|
||||
'customize_selective_refresh' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$context = aksrt_home_widget_resolve_context($args);
|
||||
$category_id = $this->resolve_category_id($instance);
|
||||
$term = $category_id > 0 ? get_term($category_id, 'category') : null;
|
||||
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
|
||||
$title = $title !== '' ? $title : (($term && ! is_wp_error($term)) ? $term->name : __('精选分类', 'aksrt'));
|
||||
$count = max(1, min(12, (int) ($instance['count'] ?? 6)));
|
||||
$columns = aksrt_home_widget_effective_columns((int) ($instance['columns'] ?? 3), $context);
|
||||
$layout = aksrt_home_widget_effective_layout(
|
||||
aksrt_home_widget_resolve_layout($instance['layout'] ?? 'card', 'card'),
|
||||
$context
|
||||
);
|
||||
$show_more = ! empty($instance['show_more']);
|
||||
|
||||
$query_args = [
|
||||
'post_type' => aksrt_home_widget_post_types(),
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $count,
|
||||
'ignore_sticky_posts' => true,
|
||||
'no_found_rows' => true,
|
||||
];
|
||||
|
||||
if ($category_id > 0) {
|
||||
$query_args['cat'] = $category_id;
|
||||
}
|
||||
|
||||
$query = new \WP_Query($query_args);
|
||||
|
||||
echo $args['before_widget'];
|
||||
echo '<section class="aksrt-home-widget aksrt-home-widget--category-block">';
|
||||
|
||||
aksrt_home_widget_render_header(
|
||||
(string) $title,
|
||||
($show_more && $term && ! is_wp_error($term)) ? (string) get_category_link($category_id) : '',
|
||||
__('查看分类', 'aksrt')
|
||||
);
|
||||
|
||||
aksrt_home_widget_render_post_grid($query, $layout, $columns, $context);
|
||||
|
||||
echo '</section>';
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = (string) ($instance['title'] ?? '');
|
||||
$category_id = (int) ($instance['category_id'] ?? 0);
|
||||
$count = (int) ($instance['count'] ?? 6);
|
||||
$columns = (int) ($instance['columns'] ?? 3);
|
||||
$layout = aksrt_home_widget_resolve_layout($instance['layout'] ?? 'card', 'card');
|
||||
$show_more = ! empty($instance['show_more']);
|
||||
$categories = aksrt_home_widget_available_categories();
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('category_id')); ?>"><?php esc_html_e('分类:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('category_id')); ?>" name="<?php echo esc_attr($this->get_field_name('category_id')); ?>">
|
||||
<option value="0"><?php esc_html_e('自动选择最热门分类', 'aksrt'); ?></option>
|
||||
<?php foreach ($categories as $category) : ?>
|
||||
<option value="<?php echo (int) $category->term_id; ?>" <?php selected($category_id, (int) $category->term_id); ?>>
|
||||
<?php echo esc_html($category->name); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('文章数量:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" min="1" max="12" value="<?php echo (int) $count; ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('columns')); ?>"><?php esc_html_e('桌面列数:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('columns')); ?>" name="<?php echo esc_attr($this->get_field_name('columns')); ?>" type="number" min="1" max="4" value="<?php echo (int) $columns; ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('layout')); ?>"><?php esc_html_e('布局:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('layout')); ?>" name="<?php echo esc_attr($this->get_field_name('layout')); ?>">
|
||||
<?php foreach (aksrt_home_widget_allowed_layouts() as $value => $label) : ?>
|
||||
<option value="<?php echo esc_attr($value); ?>" <?php selected($layout, $value); ?>>
|
||||
<?php echo esc_html($label); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<input class="checkbox" type="checkbox" <?php checked($show_more); ?> id="<?php echo esc_attr($this->get_field_id('show_more')); ?>" name="<?php echo esc_attr($this->get_field_name('show_more')); ?>" value="1">
|
||||
<label for="<?php echo esc_attr($this->get_field_id('show_more')); ?>"><?php esc_html_e('显示分类链接', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php if (function_exists('aksrt_widget_render_location_help')) { aksrt_widget_render_location_help('hybrid'); } ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title' => sanitize_text_field($new_instance['title'] ?? ''),
|
||||
'category_id' => absint($new_instance['category_id'] ?? 0),
|
||||
'count' => max(1, min(12, absint($new_instance['count'] ?? 6))),
|
||||
'columns' => max(1, min(4, absint($new_instance['columns'] ?? 3))),
|
||||
'layout' => aksrt_home_widget_resolve_layout($new_instance['layout'] ?? 'card', 'card'),
|
||||
'show_more' => ! empty($new_instance['show_more']) ? 1 : 0,
|
||||
];
|
||||
}
|
||||
|
||||
private function resolve_category_id(array $instance): int {
|
||||
$category_id = absint($instance['category_id'] ?? 0);
|
||||
if ($category_id > 0) {
|
||||
return $category_id;
|
||||
}
|
||||
|
||||
$fallback = aksrt_home_widget_fallback_category_ids(1);
|
||||
return $fallback[0] ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Homepage category tabs widget.
|
||||
*/
|
||||
class CategoryTabsWidget extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_category_tabs',
|
||||
esc_html__('AKSRT 分类 Tabs', 'aksrt'),
|
||||
[
|
||||
'classname' => 'widget_aksrt_category_tabs',
|
||||
'description' => esc_html__('多分类切换内容流,可挂载到首页内容挂载区。', 'aksrt'),
|
||||
'customize_selective_refresh' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$context = aksrt_home_widget_resolve_context($args);
|
||||
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
|
||||
$title = $title !== '' ? $title : __('热门分类', 'aksrt');
|
||||
$count = max(1, min(12, (int) ($instance['count'] ?? 6)));
|
||||
$columns = aksrt_home_widget_effective_columns((int) ($instance['columns'] ?? 3), $context);
|
||||
$layout = aksrt_home_widget_effective_layout(
|
||||
aksrt_home_widget_resolve_layout($instance['layout'] ?? 'card', 'card'),
|
||||
$context
|
||||
);
|
||||
$show_more = ! empty($instance['show_more']);
|
||||
$tabs = $this->resolve_tabs($instance);
|
||||
|
||||
echo $args['before_widget'];
|
||||
echo '<section class="aksrt-home-widget aksrt-home-widget--tabs widget-tab-posts">';
|
||||
|
||||
if (empty($tabs)) {
|
||||
echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无可用分类', 'aksrt')) : '<p class="widget-empty">' . esc_html__('暂无可用分类', 'aksrt') . '</p>';
|
||||
echo '</section>';
|
||||
echo $args['after_widget'];
|
||||
return;
|
||||
}
|
||||
|
||||
?>
|
||||
<header class="aksrt-home-widget__header aksrt-home-widget__header--tabs">
|
||||
<h3 class="aksrt-home-widget__title"><?php echo esc_html($title); ?></h3>
|
||||
<ul class="widget-tab-posts__nav aksrt-home-widget__tab-nav" role="tablist">
|
||||
<?php foreach ($tabs as $index => $tab) : ?>
|
||||
<li role="presentation">
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
class="widget-tab-posts__tab<?php echo 0 === $index ? ' active' : ''; ?>"
|
||||
data-tab="cat-<?php echo (int) $tab['id']; ?>"
|
||||
aria-selected="<?php echo 0 === $index ? 'true' : 'false'; ?>"
|
||||
>
|
||||
<?php echo esc_html($tab['name']); ?>
|
||||
</button>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</header>
|
||||
<?php
|
||||
|
||||
foreach ($tabs as $index => $tab) {
|
||||
$query = new \WP_Query([
|
||||
'post_type' => aksrt_home_widget_post_types(),
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $count,
|
||||
'ignore_sticky_posts' => true,
|
||||
'no_found_rows' => true,
|
||||
'cat' => (int) $tab['id'],
|
||||
]);
|
||||
?>
|
||||
<div class="widget-tab-posts__panel<?php echo 0 === $index ? ' active' : ''; ?>" data-tab-panel="cat-<?php echo (int) $tab['id']; ?>" role="tabpanel"<?php echo 0 === $index ? '' : ' hidden'; ?>>
|
||||
<?php aksrt_home_widget_render_post_grid($query, $layout, $columns, $context); ?>
|
||||
<?php if ($show_more) : ?>
|
||||
<div class="aksrt-home-widget__footer">
|
||||
<a class="aksrt-home-widget__more" href="<?php echo esc_url($tab['link']); ?>">
|
||||
<?php esc_html_e('查看更多', 'aksrt'); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
echo '</section>';
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = (string) ($instance['title'] ?? '');
|
||||
$category_ids = aksrt_home_widget_normalize_category_ids($instance['category_ids'] ?? [], 6);
|
||||
$count = (int) ($instance['count'] ?? 6);
|
||||
$columns = (int) ($instance['columns'] ?? 3);
|
||||
$layout = aksrt_home_widget_resolve_layout($instance['layout'] ?? 'card', 'card');
|
||||
$show_more = ! empty($instance['show_more']);
|
||||
$categories = aksrt_home_widget_available_categories();
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('category_ids')); ?>"><?php esc_html_e('分类(可多选):', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('category_ids')); ?>" name="<?php echo esc_attr($this->get_field_name('category_ids')); ?>[]" multiple size="6">
|
||||
<?php foreach ($categories as $category) : ?>
|
||||
<option value="<?php echo (int) $category->term_id; ?>" <?php selected(in_array((int) $category->term_id, $category_ids, true)); ?>>
|
||||
<?php echo esc_html($category->name); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<small><?php esc_html_e('留空时自动选择热门分类。', 'aksrt'); ?></small>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('每个 Tab 的文章数量:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" min="1" max="12" value="<?php echo (int) $count; ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('columns')); ?>"><?php esc_html_e('桌面列数:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('columns')); ?>" name="<?php echo esc_attr($this->get_field_name('columns')); ?>" type="number" min="1" max="4" value="<?php echo (int) $columns; ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('layout')); ?>"><?php esc_html_e('布局:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('layout')); ?>" name="<?php echo esc_attr($this->get_field_name('layout')); ?>">
|
||||
<?php foreach (aksrt_home_widget_allowed_layouts() as $value => $label) : ?>
|
||||
<option value="<?php echo esc_attr($value); ?>" <?php selected($layout, $value); ?>>
|
||||
<?php echo esc_html($label); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<input class="checkbox" type="checkbox" <?php checked($show_more); ?> id="<?php echo esc_attr($this->get_field_id('show_more')); ?>" name="<?php echo esc_attr($this->get_field_name('show_more')); ?>" value="1">
|
||||
<label for="<?php echo esc_attr($this->get_field_id('show_more')); ?>"><?php esc_html_e('显示“查看更多”链接', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php if (function_exists('aksrt_widget_render_location_help')) { aksrt_widget_render_location_help('hybrid'); } ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title' => sanitize_text_field($new_instance['title'] ?? ''),
|
||||
'category_ids' => aksrt_home_widget_normalize_category_ids($new_instance['category_ids'] ?? [], 6),
|
||||
'count' => max(1, min(12, absint($new_instance['count'] ?? 6))),
|
||||
'columns' => max(1, min(4, absint($new_instance['columns'] ?? 3))),
|
||||
'layout' => aksrt_home_widget_resolve_layout($new_instance['layout'] ?? 'card', 'card'),
|
||||
'show_more' => ! empty($new_instance['show_more']) ? 1 : 0,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve configured category tabs.
|
||||
*
|
||||
* @param array $instance Widget instance.
|
||||
* @return array<int, array{id:int,name:string,link:string}>
|
||||
*/
|
||||
private function resolve_tabs(array $instance): array {
|
||||
$category_ids = aksrt_home_widget_normalize_category_ids($instance['category_ids'] ?? [], 6);
|
||||
if (empty($category_ids)) {
|
||||
$category_ids = aksrt_home_widget_fallback_category_ids(4);
|
||||
}
|
||||
|
||||
$tabs = [];
|
||||
foreach ($category_ids as $category_id) {
|
||||
$term = get_term($category_id, 'category');
|
||||
if (! $term || is_wp_error($term)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tabs[] = [
|
||||
'id' => (int) $category_id,
|
||||
'name' => $term->name,
|
||||
'link' => get_category_link($category_id),
|
||||
];
|
||||
}
|
||||
|
||||
return $tabs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class CustomHtmlGapWidget 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,76 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class FriendLinksWidget 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\Settings') ? \AKSRT\Settings\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']),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class HotPostsWidget 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),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class IconCardsWidget 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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class NoticeWidget 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'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class RecentCommentsWidget 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']),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class SearchWidget 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'] ?? ''),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class TabPostsWidget 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,72 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class TagCloudWidget 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']),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetAnnouncement extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_announcement',
|
||||
__('公告跑马灯', 'aksrt'),
|
||||
['description' => __('滚动显示公告/通知。', 'aksrt')]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : '';
|
||||
$items = !empty($instance['items']) ? $instance['items'] : '';
|
||||
$icon = !empty($instance['icon']) ? $instance['icon'] : 'fas fa-bullhorn';
|
||||
$speed = !empty($instance['speed']) ? max(5, min(30, (int) $instance['speed'])) : 10;
|
||||
|
||||
$lines = array_filter(array_map('trim', explode("\n", $items)));
|
||||
if (empty($lines)) return;
|
||||
|
||||
echo $args['before_widget'];
|
||||
|
||||
if ($title) {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
}
|
||||
|
||||
echo '<div class="aksrt-announcement">';
|
||||
echo '<span class="aksrt-announcement__icon"><i class="' . esc_attr($icon) . '"></i></span>';
|
||||
echo '<div class="aksrt-announcement__track">';
|
||||
echo '<ul class="aksrt-announcement__list" style="animation-duration: ' . esc_attr((string) $speed) . 's;">';
|
||||
|
||||
foreach ($lines as $line) {
|
||||
// 支持 "文本|链接" 格式
|
||||
$parts = explode('|', $line, 2);
|
||||
$text = trim($parts[0]);
|
||||
$link = isset($parts[1]) ? trim($parts[1]) : '';
|
||||
|
||||
echo '<li class="aksrt-announcement__item">';
|
||||
if ($link) {
|
||||
echo '<a href="' . esc_url($link) . '">' . esc_html($text) . '</a>';
|
||||
} else {
|
||||
echo esc_html($text);
|
||||
}
|
||||
echo '</li>';
|
||||
}
|
||||
|
||||
// 复制第一条实现无缝滚动
|
||||
$first_line = $lines[0];
|
||||
$first_parts = explode('|', $first_line, 2);
|
||||
$first_text = trim($first_parts[0]);
|
||||
$first_link = isset($first_parts[1]) ? trim($first_parts[1]) : '';
|
||||
echo '<li class="aksrt-announcement__item">';
|
||||
if ($first_link) {
|
||||
echo '<a href="' . esc_url($first_link) . '">' . esc_html($first_text) . '</a>';
|
||||
} else {
|
||||
echo esc_html($first_text);
|
||||
}
|
||||
echo '</li>';
|
||||
|
||||
echo '</ul>';
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : '';
|
||||
$items = !empty($instance['items']) ? $instance['items'] : '';
|
||||
$icon = !empty($instance['icon']) ? $instance['icon'] : 'fas fa-bullhorn';
|
||||
$speed = !empty($instance['speed']) ? (int) $instance['speed'] : 10;
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题(可选):', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('items')); ?>"><?php esc_html_e('公告内容(每行一条):', 'aksrt'); ?></label>
|
||||
<textarea class="widefat" id="<?php echo esc_attr($this->get_field_id('items')); ?>" name="<?php echo esc_attr($this->get_field_name('items')); ?>" rows="5" placeholder="系统维护通知 新功能上线|https://example.com 活动公告"><?php echo esc_textarea($items); ?></textarea>
|
||||
<small><?php esc_html_e('每行一条,格式:文本 或 文本|链接', 'aksrt'); ?></small>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('icon')); ?>"><?php esc_html_e('图标:', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('icon')); ?>" name="<?php echo esc_attr($this->get_field_name('icon')); ?>" type="text" value="<?php echo esc_attr($icon); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('speed')); ?>"><?php esc_html_e('滚动速度(秒):', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('speed')); ?>" name="<?php echo esc_attr($this->get_field_name('speed')); ?>" type="number" value="<?php echo esc_attr((string) $speed); ?>" min="5" max="30">
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$instance = [];
|
||||
$instance['title'] = sanitize_text_field($new_instance['title'] ?? '');
|
||||
$instance['items'] = sanitize_textarea_field($new_instance['items'] ?? '');
|
||||
$instance['icon'] = sanitize_text_field($new_instance['icon'] ?? 'fas fa-bullhorn');
|
||||
$instance['speed'] = max(5, min(30, (int) ($new_instance['speed'] ?? 10)));
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetCoverCards extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_cover_cards',
|
||||
__('图文封面卡片', 'aksrt'),
|
||||
['description' => __('图片背景 + 文字叠加的卡片网格。', 'aksrt')]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : '';
|
||||
$items_json = !empty($instance['items']) ? $instance['items'] : '[]';
|
||||
$columns = max(1, min(4, (int) ($instance['columns'] ?? 2)));
|
||||
|
||||
$items = json_decode($items_json, true);
|
||||
if (!is_array($items) || empty($items)) return;
|
||||
|
||||
echo $args['before_widget'];
|
||||
|
||||
if ($title) {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
}
|
||||
|
||||
echo '<div class="aksrt-cover-cards" style="--cols: ' . esc_attr((string) $columns) . ';">';
|
||||
|
||||
foreach ($items as $item) {
|
||||
$image = esc_url($item['image'] ?? '');
|
||||
$link = esc_url($item['link'] ?? '');
|
||||
$card_title = sanitize_text_field($item['title'] ?? '');
|
||||
$desc = sanitize_text_field($item['desc'] ?? '');
|
||||
$overlay = sanitize_hex_color($item['overlay'] ?? '');
|
||||
$text_color = sanitize_hex_color($item['text_color'] ?? '');
|
||||
$height = sanitize_text_field($item['height'] ?? '');
|
||||
|
||||
$card_style = '';
|
||||
if ($height) $card_style .= 'height:' . esc_attr($height) . ';';
|
||||
|
||||
$overlay_style = '';
|
||||
if ($overlay) $overlay_style .= 'background:' . esc_attr($overlay) . ';';
|
||||
|
||||
$text_style = '';
|
||||
if ($text_color) $text_style .= 'color:' . esc_attr($text_color) . ';';
|
||||
|
||||
echo '<div class="aksrt-cover-card"' . ($card_style ? ' style="' . esc_attr($card_style) . '"' : '') . '>';
|
||||
|
||||
if ($image) {
|
||||
echo '<div class="aksrt-cover-card__bg">';
|
||||
echo '<img src="' . esc_url($image) . '" alt="' . esc_attr($card_title) . '"' . (function_exists('aksrt_lazy_loading') ? aksrt_lazy_loading('widget') : ' loading="lazy"') . '>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
if ($overlay_style) {
|
||||
echo '<div class="aksrt-cover-card__overlay" style="' . esc_attr($overlay_style) . '"></div>';
|
||||
}
|
||||
|
||||
echo '<div class="aksrt-cover-card__content"' . ($text_style ? ' style="' . esc_attr($text_style) . '"' : '') . '>';
|
||||
|
||||
if ($link) {
|
||||
echo '<a href="' . esc_url($link) . '" class="aksrt-cover-card__link">';
|
||||
}
|
||||
|
||||
if ($card_title) {
|
||||
echo '<h3 class="aksrt-cover-card__title">' . esc_html($card_title) . '</h3>';
|
||||
}
|
||||
if ($desc) {
|
||||
echo '<p class="aksrt-cover-card__desc">' . esc_html($desc) . '</p>';
|
||||
}
|
||||
|
||||
if ($link) {
|
||||
echo '</a>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : '';
|
||||
$items = !empty($instance['items']) ? $instance['items'] : '[]';
|
||||
$columns = !empty($instance['columns']) ? (int) $instance['columns'] : 2;
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题(可选):', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('columns')); ?>"><?php esc_html_e('列数:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('columns')); ?>" name="<?php echo esc_attr($this->get_field_name('columns')); ?>">
|
||||
<option value="1" <?php selected($columns, 1); ?>><?php esc_html_e('1 列', 'aksrt'); ?></option>
|
||||
<option value="2" <?php selected($columns, 2); ?>><?php esc_html_e('2 列', 'aksrt'); ?></option>
|
||||
<option value="3" <?php selected($columns, 3); ?>><?php esc_html_e('3 列', 'aksrt'); ?></option>
|
||||
<option value="4" <?php selected($columns, 4); ?>><?php esc_html_e('4 列', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('items')); ?>"><?php esc_html_e('卡片数据 (JSON):', 'aksrt'); ?></label>
|
||||
<textarea class="widefat" id="<?php echo esc_attr($this->get_field_id('items')); ?>" name="<?php echo esc_attr($this->get_field_name('items')); ?>" rows="6" placeholder='[{"image":"https://...","title":"标题","desc":"描述","link":"https://..."}]'><?php echo esc_textarea($items); ?></textarea>
|
||||
<small><?php esc_html_e('JSON 数组,每项支持: image, title, desc, link, overlay, text_color, height', 'aksrt'); ?></small>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$instance = [];
|
||||
$instance['title'] = sanitize_text_field($new_instance['title'] ?? '');
|
||||
$instance['columns'] = max(1, min(4, (int) ($new_instance['columns'] ?? 2)));
|
||||
$items_raw = $new_instance['items'] ?? '[]';
|
||||
$items = json_decode($items_raw, true);
|
||||
$instance['items'] = is_array($items) ? wp_json_encode($items) : '[]';
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Forum-specific widgets.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Forum Hot Topics Widget
|
||||
*/
|
||||
class WidgetForumHotTopics extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_forum_hot_topics',
|
||||
__('论坛 - 热门话题', 'aksrt'),
|
||||
['description' => __('显示论坛热门话题列表。', 'aksrt')]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : __('热门话题', 'aksrt');
|
||||
$count = !empty($instance['count']) ? max(1, min(20, (int) $instance['count'])) : 5;
|
||||
$period = !empty($instance['period']) ? sanitize_key($instance['period']) : 'week';
|
||||
|
||||
echo $args['before_widget'];
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
|
||||
$date_query = [];
|
||||
if ($period === 'week') {
|
||||
$date_query = [['after' => '7 days ago']];
|
||||
} elseif ($period === 'month') {
|
||||
$date_query = [['after' => '30 days ago']];
|
||||
}
|
||||
|
||||
$query = new \WP_Query([
|
||||
'post_type' => 'aksrt_forum_topic',
|
||||
'posts_per_page' => $count,
|
||||
'post_status' => 'publish',
|
||||
'meta_key' => '_aksrt_views',
|
||||
'orderby' => 'meta_value_num',
|
||||
'order' => 'DESC',
|
||||
'date_query' => $date_query,
|
||||
]);
|
||||
|
||||
if ($query->have_posts()) {
|
||||
echo '<ul class="aksrt-forum-widget__list">';
|
||||
$i = 0;
|
||||
while ($query->have_posts()) {
|
||||
$query->the_post();
|
||||
$i++;
|
||||
$views = (int) get_post_meta(get_the_ID(), '_aksrt_views', true);
|
||||
echo '<li class="aksrt-forum-widget__item">';
|
||||
echo '<span class="aksrt-forum-widget__rank">' . $i . '</span>';
|
||||
echo '<a href="' . esc_url(get_permalink()) . '" class="aksrt-forum-widget__link">';
|
||||
echo esc_html(get_the_title());
|
||||
echo '</a>';
|
||||
echo '<span class="aksrt-forum-widget__views"><i class="fas fa-eye"></i> ' . $views . '</span>';
|
||||
echo '</li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
wp_reset_postdata();
|
||||
} else {
|
||||
echo '<p class="aksrt-forum-widget__empty">' . esc_html__('暂无话题', 'aksrt') . '</p>';
|
||||
}
|
||||
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : __('热门话题', 'aksrt');
|
||||
$count = !empty($instance['count']) ? (int) $instance['count'] : 5;
|
||||
$period = !empty($instance['period']) ? $instance['period'] : 'week';
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('数量:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" value="<?php echo esc_attr($count); ?>" min="1" max="20">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('period')); ?>"><?php esc_html_e('时间范围:', 'aksrt'); ?></label>
|
||||
<select id="<?php echo esc_attr($this->get_field_id('period')); ?>" name="<?php echo esc_attr($this->get_field_name('period')); ?>">
|
||||
<option value="all" <?php selected($period, 'all'); ?>><?php esc_html_e('全部', 'aksrt'); ?></option>
|
||||
<option value="week" <?php selected($period, 'week'); ?>><?php esc_html_e('最近一周', 'aksrt'); ?></option>
|
||||
<option value="month" <?php selected($period, 'month'); ?>><?php esc_html_e('最近一月', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$instance = [];
|
||||
$instance['title'] = sanitize_text_field($new_instance['title'] ?? '');
|
||||
$instance['count'] = max(1, min(20, (int) ($new_instance['count'] ?? 5)));
|
||||
$instance['period'] = in_array($new_instance['period'] ?? '', ['all', 'week', 'month'], true) ? $new_instance['period'] : 'week';
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forum Recent Topics Widget
|
||||
*/
|
||||
class WidgetForumRecentTopics extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_forum_recent_topics',
|
||||
__('论坛 - 最新话题', 'aksrt'),
|
||||
['description' => __('显示论坛最新话题列表。', 'aksrt')]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : __('最新话题', 'aksrt');
|
||||
$count = !empty($instance['count']) ? max(1, min(20, (int) $instance['count'])) : 5;
|
||||
|
||||
echo $args['before_widget'];
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
|
||||
$query = new \WP_Query([
|
||||
'post_type' => 'aksrt_forum_topic',
|
||||
'posts_per_page' => $count,
|
||||
'post_status' => 'publish',
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
]);
|
||||
|
||||
if ($query->have_posts()) {
|
||||
echo '<ul class="aksrt-forum-widget__list">';
|
||||
while ($query->have_posts()) {
|
||||
$query->the_post();
|
||||
$author = get_the_author_meta('display_name');
|
||||
echo '<li class="aksrt-forum-widget__item">';
|
||||
echo '<a href="' . esc_url(get_permalink()) . '" class="aksrt-forum-widget__link">';
|
||||
echo esc_html(get_the_title());
|
||||
echo '</a>';
|
||||
echo '<span class="aksrt-forum-widget__author">' . esc_html($author) . '</span>';
|
||||
echo '</li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
wp_reset_postdata();
|
||||
} else {
|
||||
echo '<p class="aksrt-forum-widget__empty">' . esc_html__('暂无话题', 'aksrt') . '</p>';
|
||||
}
|
||||
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : __('最新话题', 'aksrt');
|
||||
$count = !empty($instance['count']) ? (int) $instance['count'] : 5;
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('数量:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" value="<?php echo esc_attr($count); ?>" min="1" max="20">
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$instance = [];
|
||||
$instance['title'] = sanitize_text_field($new_instance['title'] ?? '');
|
||||
$instance['count'] = max(1, min(20, (int) ($new_instance['count'] ?? 5)));
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forum Board List Widget
|
||||
*/
|
||||
class WidgetForumBoardList extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_forum_board_list',
|
||||
__('论坛 - 版块列表', 'aksrt'),
|
||||
['description' => __('显示论坛版块列表及话题数量。', 'aksrt')]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : __('版块列表', 'aksrt');
|
||||
|
||||
echo $args['before_widget'];
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
|
||||
$boards = get_terms([
|
||||
'taxonomy' => 'aksrt_forum_board',
|
||||
'hide_empty' => true,
|
||||
'orderby' => 'meta_value_num',
|
||||
'meta_key' => '_aksrt_sort_order',
|
||||
'order' => 'ASC',
|
||||
]);
|
||||
|
||||
if (!is_wp_error($boards) && !empty($boards)) {
|
||||
echo '<ul class="aksrt-forum-widget__boards">';
|
||||
foreach ($boards as $board) {
|
||||
$icon = get_term_meta($board->term_id, '_aksrt_icon', true);
|
||||
echo '<li class="aksrt-forum-widget__board">';
|
||||
echo '<a href="' . esc_url(get_term_link($board)) . '">';
|
||||
if ($icon) {
|
||||
echo '<i class="' . esc_attr($icon) . '"></i> ';
|
||||
}
|
||||
echo esc_html($board->name);
|
||||
echo '<span class="aksrt-forum-widget__count">' . (int) $board->count . '</span>';
|
||||
echo '</a>';
|
||||
echo '</li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
} else {
|
||||
echo '<p class="aksrt-forum-widget__empty">' . esc_html__('暂无版块', 'aksrt') . '</p>';
|
||||
}
|
||||
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : __('版块列表', 'aksrt');
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$instance = [];
|
||||
$instance['title'] = sanitize_text_field($new_instance['title'] ?? '');
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Forum-specific widgets.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Forum Hot Topics Widget
|
||||
*/
|
||||
class WidgetForumHotTopics extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_forum_hot_topics',
|
||||
__('论坛 - 热门话题', 'aksrt'),
|
||||
['description' => __('显示论坛热门话题列表。', 'aksrt')]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : __('热门话题', 'aksrt');
|
||||
$count = !empty($instance['count']) ? max(1, min(20, (int) $instance['count'])) : 5;
|
||||
$period = !empty($instance['period']) ? sanitize_key($instance['period']) : 'week';
|
||||
|
||||
echo $args['before_widget'];
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
|
||||
$date_query = [];
|
||||
if ($period === 'week') {
|
||||
$date_query = [['after' => '7 days ago']];
|
||||
} elseif ($period === 'month') {
|
||||
$date_query = [['after' => '30 days ago']];
|
||||
}
|
||||
|
||||
$query = new \WP_Query([
|
||||
'post_type' => 'aksrt_forum_topic',
|
||||
'posts_per_page' => $count,
|
||||
'post_status' => 'publish',
|
||||
'meta_key' => '_aksrt_views',
|
||||
'orderby' => 'meta_value_num',
|
||||
'order' => 'DESC',
|
||||
'date_query' => $date_query,
|
||||
]);
|
||||
|
||||
if ($query->have_posts()) {
|
||||
echo '<ul class="aksrt-forum-widget__list">';
|
||||
$i = 0;
|
||||
while ($query->have_posts()) {
|
||||
$query->the_post();
|
||||
$i++;
|
||||
$views = (int) get_post_meta(get_the_ID(), '_aksrt_views', true);
|
||||
echo '<li class="aksrt-forum-widget__item">';
|
||||
echo '<span class="aksrt-forum-widget__rank">' . $i . '</span>';
|
||||
echo '<a href="' . esc_url(get_permalink()) . '" class="aksrt-forum-widget__link">';
|
||||
echo esc_html(get_the_title());
|
||||
echo '</a>';
|
||||
echo '<span class="aksrt-forum-widget__views"><i class="fas fa-eye"></i> ' . $views . '</span>';
|
||||
echo '</li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
wp_reset_postdata();
|
||||
} else {
|
||||
echo '<p class="aksrt-forum-widget__empty">' . esc_html__('暂无话题', 'aksrt') . '</p>';
|
||||
}
|
||||
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : __('热门话题', 'aksrt');
|
||||
$count = !empty($instance['count']) ? (int) $instance['count'] : 5;
|
||||
$period = !empty($instance['period']) ? $instance['period'] : 'week';
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('数量:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" value="<?php echo esc_attr($count); ?>" min="1" max="20">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('period')); ?>"><?php esc_html_e('时间范围:', 'aksrt'); ?></label>
|
||||
<select id="<?php echo esc_attr($this->get_field_id('period')); ?>" name="<?php echo esc_attr($this->get_field_name('period')); ?>">
|
||||
<option value="all" <?php selected($period, 'all'); ?>><?php esc_html_e('全部', 'aksrt'); ?></option>
|
||||
<option value="week" <?php selected($period, 'week'); ?>><?php esc_html_e('最近一周', 'aksrt'); ?></option>
|
||||
<option value="month" <?php selected($period, 'month'); ?>><?php esc_html_e('最近一月', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$instance = [];
|
||||
$instance['title'] = sanitize_text_field($new_instance['title'] ?? '');
|
||||
$instance['count'] = max(1, min(20, (int) ($new_instance['count'] ?? 5)));
|
||||
$instance['period'] = in_array($new_instance['period'] ?? '', ['all', 'week', 'month'], true) ? $new_instance['period'] : 'week';
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Forum-specific widgets.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Forum Hot Topics Widget
|
||||
*/
|
||||
class WidgetForumHotTopics extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_forum_hot_topics',
|
||||
__('论坛 - 热门话题', 'aksrt'),
|
||||
['description' => __('显示论坛热门话题列表。', 'aksrt')]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : __('热门话题', 'aksrt');
|
||||
$count = !empty($instance['count']) ? max(1, min(20, (int) $instance['count'])) : 5;
|
||||
$period = !empty($instance['period']) ? sanitize_key($instance['period']) : 'week';
|
||||
|
||||
echo $args['before_widget'];
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
|
||||
$date_query = [];
|
||||
if ($period === 'week') {
|
||||
$date_query = [['after' => '7 days ago']];
|
||||
} elseif ($period === 'month') {
|
||||
$date_query = [['after' => '30 days ago']];
|
||||
}
|
||||
|
||||
$query = new \WP_Query([
|
||||
'post_type' => 'aksrt_forum_topic',
|
||||
'posts_per_page' => $count,
|
||||
'post_status' => 'publish',
|
||||
'meta_key' => '_aksrt_views',
|
||||
'orderby' => 'meta_value_num',
|
||||
'order' => 'DESC',
|
||||
'date_query' => $date_query,
|
||||
]);
|
||||
|
||||
if ($query->have_posts()) {
|
||||
echo '<ul class="aksrt-forum-widget__list">';
|
||||
$i = 0;
|
||||
while ($query->have_posts()) {
|
||||
$query->the_post();
|
||||
$i++;
|
||||
$views = (int) get_post_meta(get_the_ID(), '_aksrt_views', true);
|
||||
echo '<li class="aksrt-forum-widget__item">';
|
||||
echo '<span class="aksrt-forum-widget__rank">' . $i . '</span>';
|
||||
echo '<a href="' . esc_url(get_permalink()) . '" class="aksrt-forum-widget__link">';
|
||||
echo esc_html(get_the_title());
|
||||
echo '</a>';
|
||||
echo '<span class="aksrt-forum-widget__views"><i class="fas fa-eye"></i> ' . $views . '</span>';
|
||||
echo '</li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
wp_reset_postdata();
|
||||
} else {
|
||||
echo '<p class="aksrt-forum-widget__empty">' . esc_html__('暂无话题', 'aksrt') . '</p>';
|
||||
}
|
||||
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : __('热门话题', 'aksrt');
|
||||
$count = !empty($instance['count']) ? (int) $instance['count'] : 5;
|
||||
$period = !empty($instance['period']) ? $instance['period'] : 'week';
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('数量:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" value="<?php echo esc_attr($count); ?>" min="1" max="20">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('period')); ?>"><?php esc_html_e('时间范围:', 'aksrt'); ?></label>
|
||||
<select id="<?php echo esc_attr($this->get_field_id('period')); ?>" name="<?php echo esc_attr($this->get_field_name('period')); ?>">
|
||||
<option value="all" <?php selected($period, 'all'); ?>><?php esc_html_e('全部', 'aksrt'); ?></option>
|
||||
<option value="week" <?php selected($period, 'week'); ?>><?php esc_html_e('最近一周', 'aksrt'); ?></option>
|
||||
<option value="month" <?php selected($period, 'month'); ?>><?php esc_html_e('最近一月', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$instance = [];
|
||||
$instance['title'] = sanitize_text_field($new_instance['title'] ?? '');
|
||||
$instance['count'] = max(1, min(20, (int) ($new_instance['count'] ?? 5)));
|
||||
$instance['period'] = in_array($new_instance['period'] ?? '', ['all', 'week', 'month'], true) ? $new_instance['period'] : 'week';
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forum Recent Topics Widget
|
||||
*/
|
||||
class WidgetForumRecentTopics extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_forum_recent_topics',
|
||||
__('论坛 - 最新话题', 'aksrt'),
|
||||
['description' => __('显示论坛最新话题列表。', 'aksrt')]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : __('最新话题', 'aksrt');
|
||||
$count = !empty($instance['count']) ? max(1, min(20, (int) $instance['count'])) : 5;
|
||||
|
||||
echo $args['before_widget'];
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
|
||||
$query = new \WP_Query([
|
||||
'post_type' => 'aksrt_forum_topic',
|
||||
'posts_per_page' => $count,
|
||||
'post_status' => 'publish',
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
]);
|
||||
|
||||
if ($query->have_posts()) {
|
||||
echo '<ul class="aksrt-forum-widget__list">';
|
||||
while ($query->have_posts()) {
|
||||
$query->the_post();
|
||||
$author = get_the_author_meta('display_name');
|
||||
echo '<li class="aksrt-forum-widget__item">';
|
||||
echo '<a href="' . esc_url(get_permalink()) . '" class="aksrt-forum-widget__link">';
|
||||
echo esc_html(get_the_title());
|
||||
echo '</a>';
|
||||
echo '<span class="aksrt-forum-widget__author">' . esc_html($author) . '</span>';
|
||||
echo '</li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
wp_reset_postdata();
|
||||
} else {
|
||||
echo '<p class="aksrt-forum-widget__empty">' . esc_html__('暂无话题', 'aksrt') . '</p>';
|
||||
}
|
||||
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : __('最新话题', 'aksrt');
|
||||
$count = !empty($instance['count']) ? (int) $instance['count'] : 5;
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('数量:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" value="<?php echo esc_attr($count); ?>" min="1" max="20">
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$instance = [];
|
||||
$instance['title'] = sanitize_text_field($new_instance['title'] ?? '');
|
||||
$instance['count'] = max(1, min(20, (int) ($new_instance['count'] ?? 5)));
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetLangSwitch extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_lang_switch',
|
||||
__('语言切换器', 'aksrt'),
|
||||
['description' => __('在侧栏显示多语言切换按钮。', 'aksrt')]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : '';
|
||||
$style = !empty($instance['style']) ? $instance['style'] : 'dropdown';
|
||||
|
||||
if (!function_exists('aksrt_has_multilingual') || !aksrt_has_multilingual()) return;
|
||||
|
||||
$html = function_exists('aksrt_render_language_switcher') ? aksrt_render_language_switcher($style) : '';
|
||||
if (empty($html)) return;
|
||||
|
||||
echo $args['before_widget'];
|
||||
|
||||
if ($title) {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
}
|
||||
|
||||
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : '';
|
||||
$style = !empty($instance['style']) ? $instance['style'] : 'dropdown';
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题(可选):', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('style')); ?>"><?php esc_html_e('样式:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('style')); ?>" name="<?php echo esc_attr($this->get_field_name('style')); ?>">
|
||||
<option value="dropdown" <?php selected($style, 'dropdown'); ?>><?php esc_html_e('下拉菜单', 'aksrt'); ?></option>
|
||||
<option value="flags" <?php selected($style, 'flags'); ?>><?php esc_html_e('国旗图标', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<p><small><?php esc_html_e('需要安装 Polylang 或 WPML 等多语言插件才能生效。', 'aksrt'); ?></small></p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$instance = [];
|
||||
$instance['title'] = sanitize_text_field($new_instance['title'] ?? '');
|
||||
$instance['style'] = in_array($new_instance['style'] ?? '', ['dropdown', 'flags'], true) ? $new_instance['style'] : 'dropdown';
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Shop product widgets (§6.9)
|
||||
*
|
||||
* - WidgetProductList: product list widget (filter/sort/paginate/AJAX)
|
||||
* - WidgetProductOneline: horizontal swiper product widget
|
||||
* - WidgetProductTab: multi-tab product list widget
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Product list widget — equivalent to zibll `zib_shop_widget_ui_product_lists`.
|
||||
*
|
||||
* Supports: include/exclude category, discount, tag; sort order; list style;
|
||||
* AJAX pagination; per-page count.
|
||||
*/
|
||||
class WidgetProductList extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_product_list',
|
||||
__('商品列表', 'aksrt'),
|
||||
['description' => __('AKSRT 商品列表组件:筛选、排序、AJAX 翻页。', 'aksrt')]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$title = apply_filters('widget_title', $instance['title'] ?? '');
|
||||
$per_page = max(1, min(50, (int) ($instance['per_page'] ?? 8)));
|
||||
$orderby = sanitize_key($instance['orderby'] ?? 'date');
|
||||
$order = in_array(strtoupper($instance['order'] ?? ''), ['ASC', 'DESC'], true) ? strtoupper($instance['order']) : 'DESC';
|
||||
$cat_ids = array_filter(array_map('intval', explode(',', (string) ($instance['cat_ids'] ?? ''))));
|
||||
$tag_ids = array_filter(array_map('intval', explode(',', (string) ($instance['tag_ids'] ?? ''))));
|
||||
$discount_ids = array_filter(array_map('intval', explode(',', (string) ($instance['discount_ids'] ?? ''))));
|
||||
$exclude_cat = !empty($instance['exclude_cat']);
|
||||
$style = sanitize_key($instance['style'] ?? 'default');
|
||||
$paged = max(1, (int) ($_GET['aksrt_product_page'] ?? 1));
|
||||
|
||||
$query_args = [
|
||||
'post_type' => 'aksrt_product',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $per_page,
|
||||
'paged' => $paged,
|
||||
'orderby' => $orderby === 'sales' ? 'meta_value_num' : $orderby,
|
||||
'order' => $order,
|
||||
];
|
||||
|
||||
if ($orderby === 'sales') {
|
||||
$query_args['meta_key'] = '_aksrt_sales';
|
||||
}
|
||||
|
||||
// Taxonomy filters
|
||||
$tax_query = [];
|
||||
if (!empty($cat_ids)) {
|
||||
$tax_query[] = [
|
||||
'taxonomy' => 'aksrt_product_cat',
|
||||
'field' => 'term_id',
|
||||
'terms' => $cat_ids,
|
||||
'operator' => $exclude_cat ? 'NOT IN' : 'IN',
|
||||
];
|
||||
}
|
||||
if (!empty($tag_ids)) {
|
||||
$tax_query[] = [
|
||||
'taxonomy' => 'aksrt_product_tag',
|
||||
'field' => 'term_id',
|
||||
'terms' => $tag_ids,
|
||||
];
|
||||
}
|
||||
if (!empty($discount_ids)) {
|
||||
$tax_query[] = [
|
||||
'taxonomy' => 'shop_discount',
|
||||
'field' => 'term_id',
|
||||
'terms' => $discount_ids,
|
||||
];
|
||||
}
|
||||
if (!empty($tax_query)) {
|
||||
$query_args['tax_query'] = $tax_query;
|
||||
}
|
||||
|
||||
$products = new \WP_Query($query_args);
|
||||
|
||||
echo $args['before_widget'];
|
||||
if ($title) {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
}
|
||||
|
||||
$style_class = 'aksrt-product-widget--' . $style;
|
||||
echo '<div class="aksrt-product-widget ' . esc_attr($style_class) . '" data-aksrt-product-widget>';
|
||||
|
||||
if ($products->have_posts()) {
|
||||
echo '<div class="aksrt-product-widget__grid">';
|
||||
while ($products->have_posts()) {
|
||||
$products->the_post();
|
||||
$pid = get_the_ID();
|
||||
$card = class_exists('\AKSRT\Shop\Shop')
|
||||
? \AKSRT\Shop\Shop::get_instance()->build_product_card_view($pid)
|
||||
: [];
|
||||
$price = (float) ($card['price'] ?? 0);
|
||||
$image = $card['image'] ?? '';
|
||||
echo '<div class="aksrt-product-widget__item">';
|
||||
if ($image) {
|
||||
echo '<a href="' . esc_url(get_permalink($pid)) . '" class="aksrt-product-widget__thumb">';
|
||||
echo '<img src="' . esc_url($image) . '" alt="' . esc_attr(get_the_title($pid)) . '" loading="lazy">';
|
||||
echo '</a>';
|
||||
}
|
||||
echo '<div class="aksrt-product-widget__info">';
|
||||
echo '<a href="' . esc_url(get_permalink($pid)) . '" class="aksrt-product-widget__title">' . esc_html(get_the_title($pid)) . '</a>';
|
||||
echo '<div class="aksrt-product-widget__meta">';
|
||||
echo '<span class="aksrt-product-widget__price">¥' . esc_html(number_format_i18n($price, 2)) . '</span>';
|
||||
if (!empty($card['sales'])) {
|
||||
echo '<span class="aksrt-product-widget__sales">' . esc_html($card['sales']) . __(' 已售', 'aksrt') . '</span>';
|
||||
}
|
||||
echo '</div></div></div>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
// Pagination
|
||||
if ($products->max_num_pages > 1) {
|
||||
echo '<div class="aksrt-product-widget__pagination">';
|
||||
echo paginate_links([
|
||||
'base' => add_query_arg('aksrt_product_page', '%#%'),
|
||||
'format' => '',
|
||||
'current' => $paged,
|
||||
'total' => $products->max_num_pages,
|
||||
'prev_text' => '«',
|
||||
'next_text' => '»',
|
||||
]);
|
||||
echo '</div>';
|
||||
}
|
||||
} else {
|
||||
echo '<p class="aksrt-product-widget__empty">' . esc_html__('暂无商品', 'aksrt') . '</p>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
echo $args['after_widget'];
|
||||
|
||||
wp_reset_postdata();
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$defaults = [
|
||||
'title' => __('商品推荐', 'aksrt'),
|
||||
'per_page' => 8,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'cat_ids' => '',
|
||||
'tag_ids' => '',
|
||||
'discount_ids' => '',
|
||||
'exclude_cat' => false,
|
||||
'style' => 'default',
|
||||
];
|
||||
$instance = wp_parse_args($instance, $defaults);
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr($instance['title']); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('per_page')); ?>"><?php esc_html_e('每页数量:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" type="number" min="1" max="50" id="<?php echo esc_attr($this->get_field_id('per_page')); ?>" name="<?php echo esc_attr($this->get_field_name('per_page')); ?>" value="<?php echo esc_attr((string) $instance['per_page']); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('orderby')); ?>"><?php esc_html_e('排序:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('orderby')); ?>" name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
|
||||
<option value="date" <?php selected($instance['orderby'], 'date'); ?>><?php esc_html_e('最新', 'aksrt'); ?></option>
|
||||
<option value="sales" <?php selected($instance['orderby'], 'sales'); ?>><?php esc_html_e('销量', 'aksrt'); ?></option>
|
||||
<option value="title" <?php selected($instance['orderby'], 'title'); ?>><?php esc_html_e('标题', 'aksrt'); ?></option>
|
||||
<option value="rand" <?php selected($instance['orderby'], 'rand'); ?>><?php esc_html_e('随机', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('order')); ?>"><?php esc_html_e('方向:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('order')); ?>" name="<?php echo esc_attr($this->get_field_name('order')); ?>">
|
||||
<option value="DESC" <?php selected($instance['order'], 'DESC'); ?>><?php esc_html_e('降序', 'aksrt'); ?></option>
|
||||
<option value="ASC" <?php selected($instance['order'], 'ASC'); ?>><?php esc_html_e('升序', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('cat_ids')); ?>"><?php esc_html_e('分类ID(逗号分隔):', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" id="<?php echo esc_attr($this->get_field_id('cat_ids')); ?>" name="<?php echo esc_attr($this->get_field_name('cat_ids')); ?>" value="<?php echo esc_attr($instance['cat_ids']); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
<input type="checkbox" name="<?php echo esc_attr($this->get_field_name('exclude_cat')); ?>" value="1" <?php checked($instance['exclude_cat']); ?>>
|
||||
<?php esc_html_e('排除上述分类', 'aksrt'); ?>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('tag_ids')); ?>"><?php esc_html_e('标签ID(逗号分隔):', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" id="<?php echo esc_attr($this->get_field_id('tag_ids')); ?>" name="<?php echo esc_attr($this->get_field_name('tag_ids')); ?>" value="<?php echo esc_attr($instance['tag_ids']); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('discount_ids')); ?>"><?php esc_html_e('优惠活动ID(逗号分隔):', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" id="<?php echo esc_attr($this->get_field_id('discount_ids')); ?>" name="<?php echo esc_attr($this->get_field_name('discount_ids')); ?>" value="<?php echo esc_attr($instance['discount_ids']); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('style')); ?>"><?php esc_html_e('列表样式:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('style')); ?>" name="<?php echo esc_attr($this->get_field_name('style')); ?>">
|
||||
<option value="default" <?php selected($instance['style'], 'default'); ?>><?php esc_html_e('默认', 'aksrt'); ?></option>
|
||||
<option value="card" <?php selected($instance['style'], 'card'); ?>><?php esc_html_e('卡片', 'aksrt'); ?></option>
|
||||
<option value="list" <?php selected($instance['style'], 'list'); ?>><?php esc_html_e('列表', 'aksrt'); ?></option>
|
||||
<option value="mini" <?php selected($instance['style'], 'mini'); ?>><?php esc_html_e('迷你', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title' => sanitize_text_field($new_instance['title'] ?? ''),
|
||||
'per_page' => max(1, min(50, (int) ($new_instance['per_page'] ?? 8))),
|
||||
'orderby' => sanitize_key($new_instance['orderby'] ?? 'date'),
|
||||
'order' => in_array(strtoupper($new_instance['order'] ?? ''), ['ASC', 'DESC'], true) ? strtoupper($new_instance['order']) : 'DESC',
|
||||
'cat_ids' => sanitize_text_field($new_instance['cat_ids'] ?? ''),
|
||||
'tag_ids' => sanitize_text_field($new_instance['tag_ids'] ?? ''),
|
||||
'discount_ids' => sanitize_text_field($new_instance['discount_ids'] ?? ''),
|
||||
'exclude_cat' => !empty($new_instance['exclude_cat']),
|
||||
'style' => sanitize_key($new_instance['style'] ?? 'default'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Shop product widgets (§6.9)
|
||||
*
|
||||
* - WidgetProductList: product list widget (filter/sort/paginate/AJAX)
|
||||
* - WidgetProductOneline: horizontal swiper product widget
|
||||
* - WidgetProductTab: multi-tab product list widget
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Product list widget — equivalent to zibll `zib_shop_widget_ui_product_lists`.
|
||||
*
|
||||
* Supports: include/exclude category, discount, tag; sort order; list style;
|
||||
* AJAX pagination; per-page count.
|
||||
*/
|
||||
class WidgetProductList extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_product_list',
|
||||
__('商品列表', 'aksrt'),
|
||||
['description' => __('AKSRT 商品列表组件:筛选、排序、AJAX 翻页。', 'aksrt')]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$title = apply_filters('widget_title', $instance['title'] ?? '');
|
||||
$per_page = max(1, min(50, (int) ($instance['per_page'] ?? 8)));
|
||||
$orderby = sanitize_key($instance['orderby'] ?? 'date');
|
||||
$order = in_array(strtoupper($instance['order'] ?? ''), ['ASC', 'DESC'], true) ? strtoupper($instance['order']) : 'DESC';
|
||||
$cat_ids = array_filter(array_map('intval', explode(',', (string) ($instance['cat_ids'] ?? ''))));
|
||||
$tag_ids = array_filter(array_map('intval', explode(',', (string) ($instance['tag_ids'] ?? ''))));
|
||||
$discount_ids = array_filter(array_map('intval', explode(',', (string) ($instance['discount_ids'] ?? ''))));
|
||||
$exclude_cat = !empty($instance['exclude_cat']);
|
||||
$style = sanitize_key($instance['style'] ?? 'default');
|
||||
$paged = max(1, (int) ($_GET['aksrt_product_page'] ?? 1));
|
||||
|
||||
$query_args = [
|
||||
'post_type' => 'aksrt_product',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $per_page,
|
||||
'paged' => $paged,
|
||||
'orderby' => $orderby === 'sales' ? 'meta_value_num' : $orderby,
|
||||
'order' => $order,
|
||||
];
|
||||
|
||||
if ($orderby === 'sales') {
|
||||
$query_args['meta_key'] = '_aksrt_sales';
|
||||
}
|
||||
|
||||
// Taxonomy filters
|
||||
$tax_query = [];
|
||||
if (!empty($cat_ids)) {
|
||||
$tax_query[] = [
|
||||
'taxonomy' => 'aksrt_product_cat',
|
||||
'field' => 'term_id',
|
||||
'terms' => $cat_ids,
|
||||
'operator' => $exclude_cat ? 'NOT IN' : 'IN',
|
||||
];
|
||||
}
|
||||
if (!empty($tag_ids)) {
|
||||
$tax_query[] = [
|
||||
'taxonomy' => 'aksrt_product_tag',
|
||||
'field' => 'term_id',
|
||||
'terms' => $tag_ids,
|
||||
];
|
||||
}
|
||||
if (!empty($discount_ids)) {
|
||||
$tax_query[] = [
|
||||
'taxonomy' => 'shop_discount',
|
||||
'field' => 'term_id',
|
||||
'terms' => $discount_ids,
|
||||
];
|
||||
}
|
||||
if (!empty($tax_query)) {
|
||||
$query_args['tax_query'] = $tax_query;
|
||||
}
|
||||
|
||||
$products = new \WP_Query($query_args);
|
||||
|
||||
echo $args['before_widget'];
|
||||
if ($title) {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
}
|
||||
|
||||
$style_class = 'aksrt-product-widget--' . $style;
|
||||
echo '<div class="aksrt-product-widget ' . esc_attr($style_class) . '" data-aksrt-product-widget>';
|
||||
|
||||
if ($products->have_posts()) {
|
||||
echo '<div class="aksrt-product-widget__grid">';
|
||||
while ($products->have_posts()) {
|
||||
$products->the_post();
|
||||
$pid = get_the_ID();
|
||||
$card = class_exists('\AKSRT\Shop\Shop')
|
||||
? \AKSRT\Shop\Shop::get_instance()->build_product_card_view($pid)
|
||||
: [];
|
||||
$price = (float) ($card['price'] ?? 0);
|
||||
$image = $card['image'] ?? '';
|
||||
echo '<div class="aksrt-product-widget__item">';
|
||||
if ($image) {
|
||||
echo '<a href="' . esc_url(get_permalink($pid)) . '" class="aksrt-product-widget__thumb">';
|
||||
echo '<img src="' . esc_url($image) . '" alt="' . esc_attr(get_the_title($pid)) . '" loading="lazy">';
|
||||
echo '</a>';
|
||||
}
|
||||
echo '<div class="aksrt-product-widget__info">';
|
||||
echo '<a href="' . esc_url(get_permalink($pid)) . '" class="aksrt-product-widget__title">' . esc_html(get_the_title($pid)) . '</a>';
|
||||
echo '<div class="aksrt-product-widget__meta">';
|
||||
echo '<span class="aksrt-product-widget__price">¥' . esc_html(number_format_i18n($price, 2)) . '</span>';
|
||||
if (!empty($card['sales'])) {
|
||||
echo '<span class="aksrt-product-widget__sales">' . esc_html($card['sales']) . __(' 已售', 'aksrt') . '</span>';
|
||||
}
|
||||
echo '</div></div></div>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
// Pagination
|
||||
if ($products->max_num_pages > 1) {
|
||||
echo '<div class="aksrt-product-widget__pagination">';
|
||||
echo paginate_links([
|
||||
'base' => add_query_arg('aksrt_product_page', '%#%'),
|
||||
'format' => '',
|
||||
'current' => $paged,
|
||||
'total' => $products->max_num_pages,
|
||||
'prev_text' => '«',
|
||||
'next_text' => '»',
|
||||
]);
|
||||
echo '</div>';
|
||||
}
|
||||
} else {
|
||||
echo '<p class="aksrt-product-widget__empty">' . esc_html__('暂无商品', 'aksrt') . '</p>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
echo $args['after_widget'];
|
||||
|
||||
wp_reset_postdata();
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$defaults = [
|
||||
'title' => __('商品推荐', 'aksrt'),
|
||||
'per_page' => 8,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'cat_ids' => '',
|
||||
'tag_ids' => '',
|
||||
'discount_ids' => '',
|
||||
'exclude_cat' => false,
|
||||
'style' => 'default',
|
||||
];
|
||||
$instance = wp_parse_args($instance, $defaults);
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr($instance['title']); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('per_page')); ?>"><?php esc_html_e('每页数量:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" type="number" min="1" max="50" id="<?php echo esc_attr($this->get_field_id('per_page')); ?>" name="<?php echo esc_attr($this->get_field_name('per_page')); ?>" value="<?php echo esc_attr((string) $instance['per_page']); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('orderby')); ?>"><?php esc_html_e('排序:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('orderby')); ?>" name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
|
||||
<option value="date" <?php selected($instance['orderby'], 'date'); ?>><?php esc_html_e('最新', 'aksrt'); ?></option>
|
||||
<option value="sales" <?php selected($instance['orderby'], 'sales'); ?>><?php esc_html_e('销量', 'aksrt'); ?></option>
|
||||
<option value="title" <?php selected($instance['orderby'], 'title'); ?>><?php esc_html_e('标题', 'aksrt'); ?></option>
|
||||
<option value="rand" <?php selected($instance['orderby'], 'rand'); ?>><?php esc_html_e('随机', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('order')); ?>"><?php esc_html_e('方向:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('order')); ?>" name="<?php echo esc_attr($this->get_field_name('order')); ?>">
|
||||
<option value="DESC" <?php selected($instance['order'], 'DESC'); ?>><?php esc_html_e('降序', 'aksrt'); ?></option>
|
||||
<option value="ASC" <?php selected($instance['order'], 'ASC'); ?>><?php esc_html_e('升序', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('cat_ids')); ?>"><?php esc_html_e('分类ID(逗号分隔):', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" id="<?php echo esc_attr($this->get_field_id('cat_ids')); ?>" name="<?php echo esc_attr($this->get_field_name('cat_ids')); ?>" value="<?php echo esc_attr($instance['cat_ids']); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
<input type="checkbox" name="<?php echo esc_attr($this->get_field_name('exclude_cat')); ?>" value="1" <?php checked($instance['exclude_cat']); ?>>
|
||||
<?php esc_html_e('排除上述分类', 'aksrt'); ?>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('tag_ids')); ?>"><?php esc_html_e('标签ID(逗号分隔):', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" id="<?php echo esc_attr($this->get_field_id('tag_ids')); ?>" name="<?php echo esc_attr($this->get_field_name('tag_ids')); ?>" value="<?php echo esc_attr($instance['tag_ids']); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('discount_ids')); ?>"><?php esc_html_e('优惠活动ID(逗号分隔):', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" id="<?php echo esc_attr($this->get_field_id('discount_ids')); ?>" name="<?php echo esc_attr($this->get_field_name('discount_ids')); ?>" value="<?php echo esc_attr($instance['discount_ids']); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('style')); ?>"><?php esc_html_e('列表样式:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('style')); ?>" name="<?php echo esc_attr($this->get_field_name('style')); ?>">
|
||||
<option value="default" <?php selected($instance['style'], 'default'); ?>><?php esc_html_e('默认', 'aksrt'); ?></option>
|
||||
<option value="card" <?php selected($instance['style'], 'card'); ?>><?php esc_html_e('卡片', 'aksrt'); ?></option>
|
||||
<option value="list" <?php selected($instance['style'], 'list'); ?>><?php esc_html_e('列表', 'aksrt'); ?></option>
|
||||
<option value="mini" <?php selected($instance['style'], 'mini'); ?>><?php esc_html_e('迷你', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title' => sanitize_text_field($new_instance['title'] ?? ''),
|
||||
'per_page' => max(1, min(50, (int) ($new_instance['per_page'] ?? 8))),
|
||||
'orderby' => sanitize_key($new_instance['orderby'] ?? 'date'),
|
||||
'order' => in_array(strtoupper($new_instance['order'] ?? ''), ['ASC', 'DESC'], true) ? strtoupper($new_instance['order']) : 'DESC',
|
||||
'cat_ids' => sanitize_text_field($new_instance['cat_ids'] ?? ''),
|
||||
'tag_ids' => sanitize_text_field($new_instance['tag_ids'] ?? ''),
|
||||
'discount_ids' => sanitize_text_field($new_instance['discount_ids'] ?? ''),
|
||||
'exclude_cat' => !empty($new_instance['exclude_cat']),
|
||||
'style' => sanitize_key($new_instance['style'] ?? 'default'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Horizontal swiper product widget — equivalent to zibll `zib_shop_widget_ui_oneline_product_lists`.
|
||||
*/
|
||||
class WidgetProductOneline extends WidgetProductList {
|
||||
|
||||
public function __construct() {
|
||||
\WP_Widget::__construct(
|
||||
'aksrt_product_oneline',
|
||||
__('横滑商品', 'aksrt'),
|
||||
['description' => __('AKSRT 单行横滑商品 Swiper 组件。', 'aksrt')]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$instance['style'] = 'oneline';
|
||||
parent::widget($args, $instance);
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$instance = wp_parse_args($instance, ['style' => 'oneline']);
|
||||
parent::form($instance);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Shop product widgets (§6.9)
|
||||
*
|
||||
* - WidgetProductList: product list widget (filter/sort/paginate/AJAX)
|
||||
* - WidgetProductOneline: horizontal swiper product widget
|
||||
* - WidgetProductTab: multi-tab product list widget
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Product list widget — equivalent to zibll `zib_shop_widget_ui_product_lists`.
|
||||
*
|
||||
* Supports: include/exclude category, discount, tag; sort order; list style;
|
||||
* AJAX pagination; per-page count.
|
||||
*/
|
||||
class WidgetProductList extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_product_list',
|
||||
__('商品列表', 'aksrt'),
|
||||
['description' => __('AKSRT 商品列表组件:筛选、排序、AJAX 翻页。', 'aksrt')]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$title = apply_filters('widget_title', $instance['title'] ?? '');
|
||||
$per_page = max(1, min(50, (int) ($instance['per_page'] ?? 8)));
|
||||
$orderby = sanitize_key($instance['orderby'] ?? 'date');
|
||||
$order = in_array(strtoupper($instance['order'] ?? ''), ['ASC', 'DESC'], true) ? strtoupper($instance['order']) : 'DESC';
|
||||
$cat_ids = array_filter(array_map('intval', explode(',', (string) ($instance['cat_ids'] ?? ''))));
|
||||
$tag_ids = array_filter(array_map('intval', explode(',', (string) ($instance['tag_ids'] ?? ''))));
|
||||
$discount_ids = array_filter(array_map('intval', explode(',', (string) ($instance['discount_ids'] ?? ''))));
|
||||
$exclude_cat = !empty($instance['exclude_cat']);
|
||||
$style = sanitize_key($instance['style'] ?? 'default');
|
||||
$paged = max(1, (int) ($_GET['aksrt_product_page'] ?? 1));
|
||||
|
||||
$query_args = [
|
||||
'post_type' => 'aksrt_product',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $per_page,
|
||||
'paged' => $paged,
|
||||
'orderby' => $orderby === 'sales' ? 'meta_value_num' : $orderby,
|
||||
'order' => $order,
|
||||
];
|
||||
|
||||
if ($orderby === 'sales') {
|
||||
$query_args['meta_key'] = '_aksrt_sales';
|
||||
}
|
||||
|
||||
// Taxonomy filters
|
||||
$tax_query = [];
|
||||
if (!empty($cat_ids)) {
|
||||
$tax_query[] = [
|
||||
'taxonomy' => 'aksrt_product_cat',
|
||||
'field' => 'term_id',
|
||||
'terms' => $cat_ids,
|
||||
'operator' => $exclude_cat ? 'NOT IN' : 'IN',
|
||||
];
|
||||
}
|
||||
if (!empty($tag_ids)) {
|
||||
$tax_query[] = [
|
||||
'taxonomy' => 'aksrt_product_tag',
|
||||
'field' => 'term_id',
|
||||
'terms' => $tag_ids,
|
||||
];
|
||||
}
|
||||
if (!empty($discount_ids)) {
|
||||
$tax_query[] = [
|
||||
'taxonomy' => 'shop_discount',
|
||||
'field' => 'term_id',
|
||||
'terms' => $discount_ids,
|
||||
];
|
||||
}
|
||||
if (!empty($tax_query)) {
|
||||
$query_args['tax_query'] = $tax_query;
|
||||
}
|
||||
|
||||
$products = new \WP_Query($query_args);
|
||||
|
||||
echo $args['before_widget'];
|
||||
if ($title) {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
}
|
||||
|
||||
$style_class = 'aksrt-product-widget--' . $style;
|
||||
echo '<div class="aksrt-product-widget ' . esc_attr($style_class) . '" data-aksrt-product-widget>';
|
||||
|
||||
if ($products->have_posts()) {
|
||||
echo '<div class="aksrt-product-widget__grid">';
|
||||
while ($products->have_posts()) {
|
||||
$products->the_post();
|
||||
$pid = get_the_ID();
|
||||
$card = class_exists('\AKSRT\Shop\Shop')
|
||||
? \AKSRT\Shop\Shop::get_instance()->build_product_card_view($pid)
|
||||
: [];
|
||||
$price = (float) ($card['price'] ?? 0);
|
||||
$image = $card['image'] ?? '';
|
||||
echo '<div class="aksrt-product-widget__item">';
|
||||
if ($image) {
|
||||
echo '<a href="' . esc_url(get_permalink($pid)) . '" class="aksrt-product-widget__thumb">';
|
||||
echo '<img src="' . esc_url($image) . '" alt="' . esc_attr(get_the_title($pid)) . '" loading="lazy">';
|
||||
echo '</a>';
|
||||
}
|
||||
echo '<div class="aksrt-product-widget__info">';
|
||||
echo '<a href="' . esc_url(get_permalink($pid)) . '" class="aksrt-product-widget__title">' . esc_html(get_the_title($pid)) . '</a>';
|
||||
echo '<div class="aksrt-product-widget__meta">';
|
||||
echo '<span class="aksrt-product-widget__price">¥' . esc_html(number_format_i18n($price, 2)) . '</span>';
|
||||
if (!empty($card['sales'])) {
|
||||
echo '<span class="aksrt-product-widget__sales">' . esc_html($card['sales']) . __(' 已售', 'aksrt') . '</span>';
|
||||
}
|
||||
echo '</div></div></div>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
// Pagination
|
||||
if ($products->max_num_pages > 1) {
|
||||
echo '<div class="aksrt-product-widget__pagination">';
|
||||
echo paginate_links([
|
||||
'base' => add_query_arg('aksrt_product_page', '%#%'),
|
||||
'format' => '',
|
||||
'current' => $paged,
|
||||
'total' => $products->max_num_pages,
|
||||
'prev_text' => '«',
|
||||
'next_text' => '»',
|
||||
]);
|
||||
echo '</div>';
|
||||
}
|
||||
} else {
|
||||
echo '<p class="aksrt-product-widget__empty">' . esc_html__('暂无商品', 'aksrt') . '</p>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
echo $args['after_widget'];
|
||||
|
||||
wp_reset_postdata();
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$defaults = [
|
||||
'title' => __('商品推荐', 'aksrt'),
|
||||
'per_page' => 8,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'cat_ids' => '',
|
||||
'tag_ids' => '',
|
||||
'discount_ids' => '',
|
||||
'exclude_cat' => false,
|
||||
'style' => 'default',
|
||||
];
|
||||
$instance = wp_parse_args($instance, $defaults);
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr($instance['title']); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('per_page')); ?>"><?php esc_html_e('每页数量:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" type="number" min="1" max="50" id="<?php echo esc_attr($this->get_field_id('per_page')); ?>" name="<?php echo esc_attr($this->get_field_name('per_page')); ?>" value="<?php echo esc_attr((string) $instance['per_page']); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('orderby')); ?>"><?php esc_html_e('排序:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('orderby')); ?>" name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
|
||||
<option value="date" <?php selected($instance['orderby'], 'date'); ?>><?php esc_html_e('最新', 'aksrt'); ?></option>
|
||||
<option value="sales" <?php selected($instance['orderby'], 'sales'); ?>><?php esc_html_e('销量', 'aksrt'); ?></option>
|
||||
<option value="title" <?php selected($instance['orderby'], 'title'); ?>><?php esc_html_e('标题', 'aksrt'); ?></option>
|
||||
<option value="rand" <?php selected($instance['orderby'], 'rand'); ?>><?php esc_html_e('随机', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('order')); ?>"><?php esc_html_e('方向:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('order')); ?>" name="<?php echo esc_attr($this->get_field_name('order')); ?>">
|
||||
<option value="DESC" <?php selected($instance['order'], 'DESC'); ?>><?php esc_html_e('降序', 'aksrt'); ?></option>
|
||||
<option value="ASC" <?php selected($instance['order'], 'ASC'); ?>><?php esc_html_e('升序', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('cat_ids')); ?>"><?php esc_html_e('分类ID(逗号分隔):', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" id="<?php echo esc_attr($this->get_field_id('cat_ids')); ?>" name="<?php echo esc_attr($this->get_field_name('cat_ids')); ?>" value="<?php echo esc_attr($instance['cat_ids']); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
<input type="checkbox" name="<?php echo esc_attr($this->get_field_name('exclude_cat')); ?>" value="1" <?php checked($instance['exclude_cat']); ?>>
|
||||
<?php esc_html_e('排除上述分类', 'aksrt'); ?>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('tag_ids')); ?>"><?php esc_html_e('标签ID(逗号分隔):', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" id="<?php echo esc_attr($this->get_field_id('tag_ids')); ?>" name="<?php echo esc_attr($this->get_field_name('tag_ids')); ?>" value="<?php echo esc_attr($instance['tag_ids']); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('discount_ids')); ?>"><?php esc_html_e('优惠活动ID(逗号分隔):', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" id="<?php echo esc_attr($this->get_field_id('discount_ids')); ?>" name="<?php echo esc_attr($this->get_field_name('discount_ids')); ?>" value="<?php echo esc_attr($instance['discount_ids']); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('style')); ?>"><?php esc_html_e('列表样式:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('style')); ?>" name="<?php echo esc_attr($this->get_field_name('style')); ?>">
|
||||
<option value="default" <?php selected($instance['style'], 'default'); ?>><?php esc_html_e('默认', 'aksrt'); ?></option>
|
||||
<option value="card" <?php selected($instance['style'], 'card'); ?>><?php esc_html_e('卡片', 'aksrt'); ?></option>
|
||||
<option value="list" <?php selected($instance['style'], 'list'); ?>><?php esc_html_e('列表', 'aksrt'); ?></option>
|
||||
<option value="mini" <?php selected($instance['style'], 'mini'); ?>><?php esc_html_e('迷你', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title' => sanitize_text_field($new_instance['title'] ?? ''),
|
||||
'per_page' => max(1, min(50, (int) ($new_instance['per_page'] ?? 8))),
|
||||
'orderby' => sanitize_key($new_instance['orderby'] ?? 'date'),
|
||||
'order' => in_array(strtoupper($new_instance['order'] ?? ''), ['ASC', 'DESC'], true) ? strtoupper($new_instance['order']) : 'DESC',
|
||||
'cat_ids' => sanitize_text_field($new_instance['cat_ids'] ?? ''),
|
||||
'tag_ids' => sanitize_text_field($new_instance['tag_ids'] ?? ''),
|
||||
'discount_ids' => sanitize_text_field($new_instance['discount_ids'] ?? ''),
|
||||
'exclude_cat' => !empty($new_instance['exclude_cat']),
|
||||
'style' => sanitize_key($new_instance['style'] ?? 'default'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Horizontal swiper product widget — equivalent to zibll `zib_shop_widget_ui_oneline_product_lists`.
|
||||
*/
|
||||
class WidgetProductOneline extends WidgetProductList {
|
||||
|
||||
public function __construct() {
|
||||
\WP_Widget::__construct(
|
||||
'aksrt_product_oneline',
|
||||
__('横滑商品', 'aksrt'),
|
||||
['description' => __('AKSRT 单行横滑商品 Swiper 组件。', 'aksrt')]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$instance['style'] = 'oneline';
|
||||
parent::widget($args, $instance);
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$instance = wp_parse_args($instance, ['style' => 'oneline']);
|
||||
parent::form($instance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Multi-tab product widget — equivalent to zibll `zib_shop_widget_ui_tab_product`.
|
||||
* Each tab has independent category/discount filter.
|
||||
*/
|
||||
class WidgetProductTab extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_product_tab',
|
||||
__('多TAB商品', 'aksrt'),
|
||||
['description' => __('AKSRT 多 TAB 商品列表组件(每 tab 独立筛选)。', 'aksrt')]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$title = apply_filters('widget_title', $instance['title'] ?? '');
|
||||
$tabs_raw = (string) ($instance['tabs'] ?? '');
|
||||
$per_page = max(1, min(20, (int) ($instance['per_page'] ?? 8)));
|
||||
|
||||
// Parse tabs: "Tab Name|cat_ids|discount_ids\n..."
|
||||
$tabs = [];
|
||||
foreach (array_filter(explode("\n", $tabs_raw)) as $line) {
|
||||
$parts = explode('|', trim($line));
|
||||
if (count($parts) >= 1 && trim($parts[0]) !== '') {
|
||||
$tabs[] = [
|
||||
'label' => trim($parts[0]),
|
||||
'cat_ids' => trim($parts[1] ?? ''),
|
||||
'discount_ids' => trim($parts[2] ?? ''),
|
||||
];
|
||||
}
|
||||
}
|
||||
if (empty($tabs)) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo $args['before_widget'];
|
||||
if ($title) {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
}
|
||||
|
||||
echo '<div class="aksrt-product-tab-widget" data-aksrt-product-tab>';
|
||||
|
||||
// Tab headers
|
||||
echo '<div class="aksrt-product-tab__headers">';
|
||||
foreach ($tabs as $i => $tab) {
|
||||
$active = $i === 0 ? ' is-active' : '';
|
||||
echo '<button type="button" class="aksrt-product-tab__header' . esc_attr($active) . '" data-tab-index="' . esc_attr((string) $i) . '">';
|
||||
echo esc_html($tab['label']);
|
||||
echo '</button>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
// Tab panels
|
||||
foreach ($tabs as $i => $tab) {
|
||||
$hidden = $i > 0 ? ' style="display:none"' : '';
|
||||
echo '<div class="aksrt-product-tab__panel" data-tab-index="' . esc_attr((string) $i) . '"' . $hidden . '>';
|
||||
|
||||
$query_args = [
|
||||
'post_type' => 'aksrt_product',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $per_page,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
];
|
||||
|
||||
$tax_query = [];
|
||||
$cat_ids = array_filter(array_map('intval', explode(',', $tab['cat_ids'])));
|
||||
$discount_ids = array_filter(array_map('intval', explode(',', $tab['discount_ids'])));
|
||||
if (!empty($cat_ids)) {
|
||||
$tax_query[] = ['taxonomy' => 'aksrt_product_cat', 'field' => 'term_id', 'terms' => $cat_ids];
|
||||
}
|
||||
if (!empty($discount_ids)) {
|
||||
$tax_query[] = ['taxonomy' => 'shop_discount', 'field' => 'term_id', 'terms' => $discount_ids];
|
||||
}
|
||||
if (!empty($tax_query)) {
|
||||
$query_args['tax_query'] = $tax_query;
|
||||
}
|
||||
|
||||
$products = new \WP_Query($query_args);
|
||||
if ($products->have_posts()) {
|
||||
echo '<div class="aksrt-product-widget__grid">';
|
||||
while ($products->have_posts()) {
|
||||
$products->the_post();
|
||||
$pid = get_the_ID();
|
||||
$card = class_exists('\AKSRT\Shop\Shop')
|
||||
? \AKSRT\Shop\Shop::get_instance()->build_product_card_view($pid)
|
||||
: [];
|
||||
$price = (float) ($card['price'] ?? 0);
|
||||
$image = $card['image'] ?? '';
|
||||
echo '<div class="aksrt-product-widget__item">';
|
||||
if ($image) {
|
||||
echo '<a href="' . esc_url(get_permalink($pid)) . '" class="aksrt-product-widget__thumb">';
|
||||
echo '<img src="' . esc_url($image) . '" alt="' . esc_attr(get_the_title($pid)) . '" loading="lazy">';
|
||||
echo '</a>';
|
||||
}
|
||||
echo '<div class="aksrt-product-widget__info">';
|
||||
echo '<a href="' . esc_url(get_permalink($pid)) . '" class="aksrt-product-widget__title">' . esc_html(get_the_title($pid)) . '</a>';
|
||||
echo '<span class="aksrt-product-widget__price">¥' . esc_html(number_format_i18n($price, 2)) . '</span>';
|
||||
echo '</div></div>';
|
||||
}
|
||||
echo '</div>';
|
||||
} else {
|
||||
echo '<p class="aksrt-product-widget__empty">' . esc_html__('暂无商品', 'aksrt') . '</p>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
wp_reset_postdata();
|
||||
}
|
||||
|
||||
// Tab switching JS
|
||||
echo '<script>';
|
||||
echo '(function(){';
|
||||
echo 'var w=document.currentScript.parentElement;';
|
||||
echo 'w.querySelectorAll(".aksrt-product-tab__header").forEach(function(btn){';
|
||||
echo 'btn.addEventListener("click",function(){';
|
||||
echo 'w.querySelectorAll(".aksrt-product-tab__header").forEach(function(b){b.classList.remove("is-active")});';
|
||||
echo 'w.querySelectorAll(".aksrt-product-tab__panel").forEach(function(p){p.style.display="none"});';
|
||||
echo 'btn.classList.add("is-active");';
|
||||
echo 'var idx=btn.getAttribute("data-tab-index");';
|
||||
echo 'var panel=w.querySelector(".aksrt-product-tab__panel[data-tab-index=\'\"+idx+\"\']");';
|
||||
echo 'if(panel)panel.style.display="";';
|
||||
echo '});';
|
||||
echo '});';
|
||||
echo '})();';
|
||||
echo '</script>';
|
||||
|
||||
echo '</div>';
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$defaults = [
|
||||
'title' => __('商品精选', 'aksrt'),
|
||||
'tabs' => "热门推荐|\n最新上架|",
|
||||
'per_page' => 8,
|
||||
];
|
||||
$instance = wp_parse_args($instance, $defaults);
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr($instance['title']); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('tabs')); ?>"><?php esc_html_e('TAB配置(每行: 名称|分类ID|优惠ID):', 'aksrt'); ?></label>
|
||||
<textarea class="widefat" rows="5" id="<?php echo esc_attr($this->get_field_id('tabs')); ?>" name="<?php echo esc_attr($this->get_field_name('tabs')); ?>"><?php echo esc_textarea($instance['tabs']); ?></textarea>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('per_page')); ?>"><?php esc_html_e('每页数量:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" type="number" min="1" max="20" id="<?php echo esc_attr($this->get_field_id('per_page')); ?>" name="<?php echo esc_attr($this->get_field_name('per_page')); ?>" value="<?php echo esc_attr((string) $instance['per_page']); ?>">
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return [
|
||||
'title' => sanitize_text_field($new_instance['title'] ?? ''),
|
||||
'tabs' => sanitize_textarea_field($new_instance['tabs'] ?? ''),
|
||||
'per_page' => max(1, min(20, (int) ($new_instance['per_page'] ?? 8))),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetUserCard extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_user_card',
|
||||
__('用户卡片', 'aksrt'),
|
||||
['description' => __('在侧栏显示当前登录用户或指定用户的信息卡片。', 'aksrt')]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : '';
|
||||
$show_stats = !empty($instance['show_stats']);
|
||||
$show_actions = !empty($instance['show_actions']);
|
||||
|
||||
// 获取目标用户
|
||||
$user_id = !empty($instance['user_id']) ? (int) $instance['user_id'] : 0;
|
||||
if ($user_id <= 0 && is_user_logged_in()) {
|
||||
$user_id = get_current_user_id();
|
||||
}
|
||||
|
||||
if ($user_id <= 0) return;
|
||||
|
||||
$user = get_userdata($user_id);
|
||||
if (!$user) return;
|
||||
|
||||
// 获取用户统计
|
||||
$post_count = count_user_posts($user_id, 'post', true);
|
||||
$comment_count = get_comments(['user_id' => $user_id, 'count' => true]);
|
||||
$follower_count = 0;
|
||||
$following_count = 0;
|
||||
if (class_exists('\AKSRT\User\Follow')) {
|
||||
$follower_count = \AKSRT\User\Follow::get_instance()->get_follower_count($user_id);
|
||||
$following_count = \AKSRT\User\Follow::get_instance()->get_following_count($user_id);
|
||||
}
|
||||
|
||||
// 头像
|
||||
$avatar_url = function_exists('aksrt_get_avatar_url')
|
||||
? aksrt_get_avatar_url($user_id, ['size' => 144])
|
||||
: get_avatar_url($user_id, ['size' => 144]);
|
||||
|
||||
// 用户主页链接
|
||||
$profile_url = function_exists('aksrt_get_user_home_url')
|
||||
? aksrt_get_user_home_url($user_id)
|
||||
: get_author_posts_url($user_id);
|
||||
|
||||
// 简介
|
||||
$bio = get_user_meta($user_id, 'description', true);
|
||||
if (!$bio) $bio = $user->description ?: '';
|
||||
|
||||
echo $args['before_widget'];
|
||||
|
||||
if ($title) {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
}
|
||||
|
||||
echo '<div class="aksrt-user-card">';
|
||||
|
||||
// 头像
|
||||
echo '<div class="aksrt-user-card__avatar">';
|
||||
echo '<a href="' . esc_url($profile_url) . '">';
|
||||
echo '<img src="' . esc_url($avatar_url) . '" alt="' . esc_attr($user->display_name) . '"' . (function_exists('aksrt_lazy_loading') ? aksrt_lazy_loading('avatar') : ' loading="lazy"') . '>';
|
||||
echo '</a>';
|
||||
echo '</div>';
|
||||
|
||||
// 昵称
|
||||
echo '<h4 class="aksrt-user-card__name">';
|
||||
echo '<a href="' . esc_url($profile_url) . '">' . esc_html($user->display_name) . '</a>';
|
||||
echo '</h4>';
|
||||
|
||||
// 简介
|
||||
if ($bio) {
|
||||
echo '<p class="aksrt-user-card__bio">' . esc_html(wp_trim_words($bio, 20)) . '</p>';
|
||||
}
|
||||
|
||||
// 统计
|
||||
if ($show_stats) {
|
||||
echo '<div class="aksrt-user-card__stats">';
|
||||
echo '<div class="aksrt-user-card__stat"><span class="aksrt-user-card__stat-value">' . number_format_i18n($post_count) . '</span><span class="aksrt-user-card__stat-label">' . esc_html__('文章', 'aksrt') . '</span></div>';
|
||||
echo '<div class="aksrt-user-card__stat"><span class="aksrt-user-card__stat-value">' . number_format_i18n($follower_count) . '</span><span class="aksrt-user-card__stat-label">' . esc_html__('粉丝', 'aksrt') . '</span></div>';
|
||||
echo '<div class="aksrt-user-card__stat"><span class="aksrt-user-card__stat-value">' . number_format_i18n($following_count) . '</span><span class="aksrt-user-card__stat-label">' . esc_html__('关注', 'aksrt') . '</span></div>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
// 操作按钮
|
||||
if ($show_actions) {
|
||||
echo '<div class="aksrt-user-card__actions">';
|
||||
if (is_user_logged_in() && (int) get_current_user_id() === $user_id) {
|
||||
echo '<a href="' . esc_url($profile_url) . '" class="aksrt-user-card__btn aksrt-user-card__btn--primary">' . esc_html__('个人主页', 'aksrt') . '</a>';
|
||||
if (function_exists('aksrt_get_user_center_url')) {
|
||||
echo '<a href="' . esc_url(aksrt_get_user_center_url()) . '" class="aksrt-user-card__btn aksrt-user-card__btn--ghost">' . esc_html__('设置', 'aksrt') . '</a>';
|
||||
}
|
||||
} else {
|
||||
echo '<a href="' . esc_url($profile_url) . '" class="aksrt-user-card__btn aksrt-user-card__btn--primary">' . esc_html__('查看主页', 'aksrt') . '</a>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : '';
|
||||
$user_id = !empty($instance['user_id']) ? (int) $instance['user_id'] : 0;
|
||||
$show_stats = !empty($instance['show_stats']);
|
||||
$show_actions = !empty($instance['show_actions']);
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题(可选):', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('user_id')); ?>"><?php esc_html_e('用户 ID(0 = 当前登录用户):', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('user_id')); ?>" name="<?php echo esc_attr($this->get_field_name('user_id')); ?>" type="number" value="<?php echo esc_attr((string) $user_id); ?>" min="0">
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
<input type="checkbox" id="<?php echo esc_attr($this->get_field_id('show_stats')); ?>" name="<?php echo esc_attr($this->get_field_name('show_stats')); ?>" value="1" <?php checked($show_stats); ?>>
|
||||
<?php esc_html_e('显示统计(文章、粉丝、关注)', 'aksrt'); ?>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
<input type="checkbox" id="<?php echo esc_attr($this->get_field_id('show_actions')); ?>" name="<?php echo esc_attr($this->get_field_name('show_actions')); ?>" value="1" <?php checked($show_actions); ?>>
|
||||
<?php esc_html_e('显示操作按钮', 'aksrt'); ?>
|
||||
</label>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$instance = [];
|
||||
$instance['title'] = sanitize_text_field($new_instance['title'] ?? '');
|
||||
$instance['user_id'] = (int) ($new_instance['user_id'] ?? 0);
|
||||
$instance['show_stats'] = !empty($new_instance['show_stats']);
|
||||
$instance['show_actions'] = !empty($new_instance['show_actions']);
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetUserRanking extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_user_ranking',
|
||||
__('用户排行榜', 'aksrt'),
|
||||
['description' => __('按积分、文章数或粉丝数排行的用户列表。', 'aksrt')]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : __('排行榜', 'aksrt');
|
||||
$rank_by = !empty($instance['rank_by']) ? $instance['rank_by'] : 'credits';
|
||||
$count = max(1, min(20, (int) ($instance['count'] ?? 5)));
|
||||
$show_avatar = !empty($instance['show_avatar']);
|
||||
|
||||
$users = $this->get_ranking($rank_by, $count);
|
||||
if (empty($users)) return;
|
||||
|
||||
echo $args['before_widget'];
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
|
||||
echo '<ul class="aksrt-user-ranking">';
|
||||
$rank = 0;
|
||||
foreach ($users as $user) {
|
||||
$rank++;
|
||||
$rank_class = $rank <= 3 ? ' aksrt-user-ranking__item--top' : '';
|
||||
$user_url = function_exists('aksrt_get_user_home_url')
|
||||
? aksrt_get_user_home_url($user['id'])
|
||||
: get_author_posts_url($user['id']);
|
||||
|
||||
echo '<li class="aksrt-user-ranking__item' . esc_attr($rank_class) . '">';
|
||||
|
||||
// 排名
|
||||
echo '<span class="aksrt-user-ranking__rank">' . esc_html((string) $rank) . '</span>';
|
||||
|
||||
// 头像
|
||||
if ($show_avatar) {
|
||||
$avatar_url = function_exists('aksrt_get_avatar_url')
|
||||
? aksrt_get_avatar_url($user['id'], ['size' => 36])
|
||||
: get_avatar_url($user['id'], ['size' => 36]);
|
||||
echo '<a href="' . esc_url($user_url) . '" class="aksrt-user-ranking__avatar">';
|
||||
echo '<img src="' . esc_url($avatar_url) . '" alt=""' . (function_exists('aksrt_lazy_loading') ? aksrt_lazy_loading('avatar') : ' loading="lazy"') . ' width="36" height="36">';
|
||||
echo '</a>';
|
||||
}
|
||||
|
||||
// 昵称
|
||||
echo '<a href="' . esc_url($user_url) . '" class="aksrt-user-ranking__name">' . esc_html($user['name']) . '</a>';
|
||||
|
||||
// 数值
|
||||
echo '<span class="aksrt-user-ranking__value">' . esc_html($user['value']) . '</span>';
|
||||
|
||||
echo '</li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
private function get_ranking(string $rank_by, int $count): array {
|
||||
$users = [];
|
||||
|
||||
switch ($rank_by) {
|
||||
case 'credits':
|
||||
global $wpdb;
|
||||
$rows = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT user_id, meta_value as credits FROM {$wpdb->usermeta}
|
||||
WHERE meta_key = 'aksrt_credits'
|
||||
ORDER BY CAST(meta_value AS UNSIGNED) DESC
|
||||
LIMIT %d",
|
||||
$count
|
||||
), ARRAY_A);
|
||||
foreach ((array) $rows as $row) {
|
||||
$user = get_userdata((int) $row['user_id']);
|
||||
if ($user) {
|
||||
$users[] = [
|
||||
'id' => (int) $row['user_id'],
|
||||
'name' => $user->display_name,
|
||||
'value' => number_format_i18n((int) $row['credits']) . ' 积分',
|
||||
];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'posts':
|
||||
$blog_users = get_users([
|
||||
'number' => $count,
|
||||
'orderby' => 'post_count',
|
||||
'order' => 'DESC',
|
||||
'who' => 'authors',
|
||||
]);
|
||||
foreach ($blog_users as $user) {
|
||||
$post_count = count_user_posts($user->ID, 'post', true);
|
||||
if ($post_count > 0) {
|
||||
$users[] = [
|
||||
'id' => $user->ID,
|
||||
'name' => $user->display_name,
|
||||
'value' => number_format_i18n($post_count) . ' 篇',
|
||||
];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'followers':
|
||||
if (class_exists('\AKSRT\User\Follow')) {
|
||||
global $wpdb;
|
||||
$follow_table = \AKSRT\Core\DBMigrator::table('follow');
|
||||
if ($follow_table) {
|
||||
$rows = $wpdb->get_results($wpdb->prepare(
|
||||
"SELECT following_id, COUNT(*) as cnt
|
||||
FROM {$follow_table}
|
||||
GROUP BY following_id
|
||||
ORDER BY cnt DESC
|
||||
LIMIT %d",
|
||||
$count
|
||||
), ARRAY_A);
|
||||
foreach ((array) $rows as $row) {
|
||||
$user = get_userdata((int) $row['following_id']);
|
||||
if ($user) {
|
||||
$users[] = [
|
||||
'id' => (int) $row['following_id'],
|
||||
'name' => $user->display_name,
|
||||
'value' => number_format_i18n((int) $row['cnt']) . ' 粉丝',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $users;
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : __('排行榜', 'aksrt');
|
||||
$rank_by = !empty($instance['rank_by']) ? $instance['rank_by'] : 'credits';
|
||||
$count = !empty($instance['count']) ? (int) $instance['count'] : 5;
|
||||
$show_avatar = !empty($instance['show_avatar']);
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('rank_by')); ?>"><?php esc_html_e('排行依据:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('rank_by')); ?>" name="<?php echo esc_attr($this->get_field_name('rank_by')); ?>">
|
||||
<option value="credits" <?php selected($rank_by, 'credits'); ?>><?php esc_html_e('积分', 'aksrt'); ?></option>
|
||||
<option value="posts" <?php selected($rank_by, 'posts'); ?>><?php esc_html_e('文章数', 'aksrt'); ?></option>
|
||||
<option value="followers" <?php selected($rank_by, 'followers'); ?>><?php esc_html_e('粉丝数', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('显示数量:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" value="<?php echo esc_attr((string) $count); ?>" min="1" max="20">
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
<input type="checkbox" id="<?php echo esc_attr($this->get_field_id('show_avatar')); ?>" name="<?php echo esc_attr($this->get_field_name('show_avatar')); ?>" value="1" <?php checked($show_avatar); ?>>
|
||||
<?php esc_html_e('显示头像', 'aksrt'); ?>
|
||||
</label>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$instance = [];
|
||||
$instance['title'] = sanitize_text_field($new_instance['title'] ?? '');
|
||||
$instance['rank_by'] = in_array($new_instance['rank_by'] ?? '', ['credits', 'posts', 'followers'], true) ? $new_instance['rank_by'] : 'credits';
|
||||
$instance['count'] = max(1, min(20, (int) ($new_instance['count'] ?? 5)));
|
||||
$instance['show_avatar'] = !empty($new_instance['show_avatar']);
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetVideoPlayer extends \WP_Widget {
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_video_player',
|
||||
__('视频播放器', 'aksrt'),
|
||||
['description' => __('在侧栏嵌入视频播放器。', 'aksrt')]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : '';
|
||||
$video_url = !empty($instance['video_url']) ? $instance['video_url'] : '';
|
||||
$poster = !empty($instance['poster']) ? $instance['poster'] : '';
|
||||
$autoplay = !empty($instance['autoplay']);
|
||||
$aspect_ratio = !empty($instance['aspect_ratio']) ? $instance['aspect_ratio'] : '16:9';
|
||||
|
||||
if (empty($video_url)) return;
|
||||
|
||||
// 解析视频类型
|
||||
$embed_url = $this->get_embed_url($video_url);
|
||||
$is_embed = !empty($embed_url);
|
||||
|
||||
echo $args['before_widget'];
|
||||
|
||||
if ($title) {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title'];
|
||||
}
|
||||
|
||||
// 计算 padding-bottom
|
||||
$ratio_parts = explode(':', $aspect_ratio);
|
||||
if (count($ratio_parts) === 2 && (int) $ratio_parts[1] > 0) {
|
||||
$padding = ((int) $ratio_parts[1] / (int) $ratio_parts[0]) * 100;
|
||||
} else {
|
||||
$padding = 56.25; // 16:9
|
||||
}
|
||||
|
||||
echo '<div class="aksrt-video-widget" style="padding-bottom: ' . esc_attr(number_format($padding, 4)) . '%;">';
|
||||
|
||||
if ($is_embed) {
|
||||
echo '<iframe src="' . esc_url($embed_url) . '" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen' . (function_exists('aksrt_lazy_loading') ? aksrt_lazy_loading('widget') : ' loading="lazy"') . '></iframe>';
|
||||
} else {
|
||||
echo '<video' . ($autoplay ? ' autoplay' : '') . ' controls playsinline';
|
||||
if ($poster) echo ' poster="' . esc_url($poster) . '"';
|
||||
echo '>';
|
||||
echo '<source src="' . esc_url($video_url) . '">';
|
||||
echo '</video>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
|
||||
echo $args['after_widget'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 YouTube / Bilibili 链接为嵌入 URL
|
||||
*/
|
||||
private function get_embed_url(string $url): string {
|
||||
// YouTube
|
||||
if (preg_match('/(?:youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]+)/', $url, $m)) {
|
||||
return 'https://www.youtube.com/embed/' . $m[1];
|
||||
}
|
||||
|
||||
// Bilibili
|
||||
if (preg_match('/bilibili\.com\/video\/(BV[a-zA-Z0-9]+)/', $url, $m)) {
|
||||
return 'https://player.bilibili.com/player.html?bvid=' . $m[1] . '&high_quality=1';
|
||||
}
|
||||
|
||||
// 腾讯视频
|
||||
if (preg_match('/v\.qq\.com\/x\/page\/([a-zA-Z0-9]+)/', $url, $m)) {
|
||||
return 'https://v.qq.com/txp/iframe/player.html?vid=' . $m[1];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : '';
|
||||
$video_url = !empty($instance['video_url']) ? $instance['video_url'] : '';
|
||||
$poster = !empty($instance['poster']) ? $instance['poster'] : '';
|
||||
$autoplay = !empty($instance['autoplay']);
|
||||
$aspect_ratio = !empty($instance['aspect_ratio']) ? $instance['aspect_ratio'] : '16:9';
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题(可选):', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('video_url')); ?>"><?php esc_html_e('视频 URL:', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('video_url')); ?>" name="<?php echo esc_attr($this->get_field_name('video_url')); ?>" type="url" value="<?php echo esc_attr($video_url); ?>" placeholder="https://...">
|
||||
<small><?php esc_html_e('支持 MP4、YouTube、Bilibili、腾讯视频链接', 'aksrt'); ?></small>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('poster')); ?>"><?php esc_html_e('封面图 URL:', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('poster')); ?>" name="<?php echo esc_attr($this->get_field_name('poster')); ?>" type="url" value="<?php echo esc_attr($poster); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('aspect_ratio')); ?>"><?php esc_html_e('宽高比:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('aspect_ratio')); ?>" name="<?php echo esc_attr($this->get_field_name('aspect_ratio')); ?>">
|
||||
<option value="16:9" <?php selected($aspect_ratio, '16:9'); ?>>16:9</option>
|
||||
<option value="4:3" <?php selected($aspect_ratio, '4:3'); ?>>4:3</option>
|
||||
<option value="1:1" <?php selected($aspect_ratio, '1:1'); ?>>1:1</option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
<input type="checkbox" id="<?php echo esc_attr($this->get_field_id('autoplay')); ?>" name="<?php echo esc_attr($this->get_field_name('autoplay')); ?>" value="1" <?php checked($autoplay); ?>>
|
||||
<?php esc_html_e('自动播放(仅 MP4)', 'aksrt'); ?>
|
||||
</label>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$instance = [];
|
||||
$instance['title'] = sanitize_text_field($new_instance['title'] ?? '');
|
||||
$instance['video_url'] = esc_url_raw($new_instance['video_url'] ?? '');
|
||||
$instance['poster'] = esc_url_raw($new_instance['poster'] ?? '');
|
||||
$instance['autoplay'] = !empty($new_instance['autoplay']);
|
||||
$instance['aspect_ratio'] = in_array($new_instance['aspect_ratio'] ?? '', ['16:9', '4:3', '1:1'], true) ? $new_instance['aspect_ratio'] : '16:9';
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetYiyan extends \WP_Widget {
|
||||
|
||||
/**
|
||||
* Hitokoto category codes → labels (API `c` param).
|
||||
*
|
||||
* @return array<string,string>
|
||||
*/
|
||||
private static function type_labels(): array {
|
||||
return [
|
||||
'' => __('随机', 'aksrt'),
|
||||
'a' => __('动画', 'aksrt'),
|
||||
'b' => __('漫画', 'aksrt'),
|
||||
'c' => __('游戏', 'aksrt'),
|
||||
'd' => __('文学', 'aksrt'),
|
||||
'e' => __('原创', 'aksrt'),
|
||||
'f' => __('来自网络', 'aksrt'),
|
||||
'g' => __('其他', 'aksrt'),
|
||||
'h' => __('影视', 'aksrt'),
|
||||
'i' => __('诗词', 'aksrt'),
|
||||
'j' => __('网易云', 'aksrt'),
|
||||
'k' => __('哲学', 'aksrt'),
|
||||
'l' => __('抖机灵', 'aksrt'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize stored type to a valid Hitokoto category letter or empty.
|
||||
*/
|
||||
private static function sanitize_type(string $type): string {
|
||||
$type = strtolower(trim($type));
|
||||
return preg_match('/^[a-l]$/', $type) ? $type : '';
|
||||
}
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_yiyan',
|
||||
__('一言轮播', 'aksrt'),
|
||||
['description' => __('显示随机一言/每日语录,支持自动轮播。', 'aksrt')]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : '';
|
||||
$count = !empty($instance['count']) ? max(1, min(10, (int) $instance['count'])) : 5;
|
||||
$interval = !empty($instance['interval']) ? max(3, min(30, (int) $instance['interval'])) : 5;
|
||||
$type = self::sanitize_type((string) ($instance['type'] ?? ''));
|
||||
$icon = !empty($instance['icon']) ? $instance['icon'] : 'fas fa-quote-left';
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
|
||||
if ($title) {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_render_yiyan_block')) {
|
||||
aksrt_render_yiyan_block([
|
||||
'class' => 'aksrt-yiyan',
|
||||
'type' => $type,
|
||||
'count' => $count,
|
||||
'interval' => $interval,
|
||||
'icon' => $icon,
|
||||
]);
|
||||
} else {
|
||||
// Fallback if single-render helpers are not loaded.
|
||||
?>
|
||||
<div class="aksrt-yiyan"
|
||||
data-aksrt-yiyan
|
||||
data-count="<?php echo esc_attr((string) $count); ?>"
|
||||
data-interval="<?php echo esc_attr((string) $interval); ?>"
|
||||
<?php echo $type !== '' ? ' data-type="' . esc_attr($type) . '"' : ''; ?>>
|
||||
<div class="aksrt-yiyan__content">
|
||||
<i class="<?php echo esc_attr($icon); ?> aksrt-yiyan__icon" aria-hidden="true"></i>
|
||||
<p class="aksrt-yiyan__text"><?php esc_html_e('加载中…', 'aksrt'); ?></p>
|
||||
<p class="aksrt-yiyan__from"></p>
|
||||
</div>
|
||||
<div class="aksrt-yiyan__dots" aria-hidden="true"></div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : '一言';
|
||||
$count = !empty($instance['count']) ? (int) $instance['count'] : 5;
|
||||
$interval = !empty($instance['interval']) ? (int) $instance['interval'] : 5;
|
||||
$type = self::sanitize_type((string) ($instance['type'] ?? ''));
|
||||
$icon = !empty($instance['icon']) ? $instance['icon'] : 'fas fa-quote-left';
|
||||
$labels = self::type_labels();
|
||||
// Curated subset for the form (full map still used for sanitization).
|
||||
$form_types = ['', 'a', 'd', 'i', 'k', 'h', 'j'];
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>"
|
||||
name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text"
|
||||
value="<?php echo esc_attr($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('轮播条数', 'aksrt'); ?></label>
|
||||
<input id="<?php echo esc_attr($this->get_field_id('count')); ?>"
|
||||
name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number"
|
||||
min="1" max="10" value="<?php echo esc_attr((string) $count); ?>" style="width:80px;">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('interval')); ?>"><?php esc_html_e('切换间隔(秒)', 'aksrt'); ?></label>
|
||||
<input id="<?php echo esc_attr($this->get_field_id('interval')); ?>"
|
||||
name="<?php echo esc_attr($this->get_field_name('interval')); ?>" type="number"
|
||||
min="3" max="30" value="<?php echo esc_attr((string) $interval); ?>" style="width:80px;">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('type')); ?>"><?php esc_html_e('分类', 'aksrt'); ?></label>
|
||||
<select id="<?php echo esc_attr($this->get_field_id('type')); ?>"
|
||||
name="<?php echo esc_attr($this->get_field_name('type')); ?>">
|
||||
<?php foreach ($form_types as $code) : ?>
|
||||
<option value="<?php echo esc_attr($code); ?>" <?php selected($type, $code); ?>>
|
||||
<?php echo esc_html($labels[$code] ?? $code); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('icon')); ?>"><?php esc_html_e('图标', 'aksrt'); ?></label>
|
||||
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('icon')); ?>"
|
||||
name="<?php echo esc_attr($this->get_field_name('icon')); ?>" type="text"
|
||||
value="<?php echo esc_attr($icon); ?>">
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$instance = [];
|
||||
$instance['title'] = sanitize_text_field($new_instance['title'] ?? '');
|
||||
$instance['count'] = max(1, min(10, absint($new_instance['count'] ?? 5)));
|
||||
$instance['interval'] = max(3, min(30, absint($new_instance['interval'] ?? 5)));
|
||||
$instance['type'] = self::sanitize_type((string) ($new_instance['type'] ?? ''));
|
||||
$instance['icon'] = sanitize_text_field($new_instance['icon'] ?? 'fas fa-quote-left');
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user