refactor: flatten src/AKSRT → src (drop theme-identifier dir)
Move all domain folders from src/AKSRT/{Domain} to src/{Domain}; remap
Composer PSR-4 "AKSRT\\": "src/"; update audit script + README + ADR
paths; remove dead tools/build-psr4-map.php (scanned deleted inc/classes).
This commit is contained in:
1 parent
56f1a8175f
commit
8c5b1555c9
285 files changed
+26
-3600
No files matched your search
@@ -0,0 +1,383 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class AboutWidget extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
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 aksrt-lazy lazyload" data-src="<?php echo esc_url($cover); ?>" src="<?php echo esc_url(get_template_directory_uri() . '/assets/img/placeholder.svg'); ?>" alt="" loading="lazy">
|
||||
</div>
|
||||
|
||||
<div class="aksrt-user-card__content">
|
||||
<div class="aksrt-user-card__avatar">
|
||||
<img class="aksrt-lazy lazyload" data-src="<?php echo esc_url($avatar); ?>" src="<?php echo esc_url(get_template_directory_uri() . '/assets/img/placeholder.svg'); ?>" alt="" width="80" height="80" loading="lazy">
|
||||
</div>
|
||||
|
||||
<div class="aksrt-user-card__info">
|
||||
<div class="aksrt-user-card__name"><?php if (function_exists('aksrt_get_user_name')) { echo aksrt_get_user_name(['id' => (int) $u->ID, 'class' => 'flex ac', 'follow' => false, 'medal' => true, 'auth' => true, 'level' => true]); } else { echo esc_html($u->display_name); } ?></div>
|
||||
<div class="aksrt-user-card__desc"><?php $bio = trim((string) get_user_meta($u->ID, 'description', true)); echo esc_html($bio !== '' ? $bio : $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 (class_exists('\AKSRT\Auth\OAuth')) {
|
||||
$oauth = \AKSRT\Auth\OAuth::get_instance();
|
||||
if (method_exists($oauth, 'render_social_buttons')) {
|
||||
$oauth_html = (string) $oauth->render_social_buttons(__('社交账号登录', 'aksrt'));
|
||||
} elseif (method_exists($oauth, 'get_login_buttons')) {
|
||||
$oauth_html = (string) $oauth->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 aksrt-lazy lazyload" data-src="<?php echo esc_url($cover); ?>" src="<?php echo esc_url(get_template_directory_uri() . '/assets/img/placeholder.svg'); ?>" alt="" loading="lazy">
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="aksrt-user-card__content">
|
||||
<div class="aksrt-user-card__avatar">
|
||||
<img class="aksrt-lazy lazyload" data-src="<?php echo esc_url($avatar); ?>" src="<?php echo esc_url(get_template_directory_uri() . '/assets/img/placeholder.svg'); ?>" alt="" width="80" height="80" 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 signin-loader" 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 signup-loader" 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
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$business = [
|
||||
'title' => sanitize_text_field($new_instance['title'] ?? ''),
|
||||
'cover' => esc_url_raw($new_instance['cover'] ?? ''),
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
/**
|
||||
* Custom buttons group (zibll zib_widget_ui_buttons).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class ButtonsWidget extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct('aksrt_buttons', esc_html__('AKSRT 按钮组', 'aksrt'), [
|
||||
'description' => esc_html__('显示一组自定义按钮,支持外链跳转、色系与入场动画。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_buttons',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $args
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function widget($args, $instance): void {
|
||||
$buttons = isset($instance['buttons']) && is_array($instance['buttons']) ? $instance['buttons'] : [];
|
||||
// Also accept zibll-style `items`.
|
||||
if (!$buttons && isset($instance['items']) && is_array($instance['items'])) {
|
||||
$buttons = $instance['items'];
|
||||
}
|
||||
if (!$buttons) {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('请添加至少一个按钮', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<p class="widget-empty">' . esc_html__('请添加至少一个按钮', 'aksrt') . '</p>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$align = sanitize_key((string) ($instance['align'] ?? 'center'));
|
||||
if (!in_array($align, ['left', 'center', 'right'], true)) {
|
||||
$align = 'center';
|
||||
}
|
||||
$radius = !empty($instance['radius']);
|
||||
$hollow = !empty($instance['hollow']);
|
||||
$go_link = !empty($instance['go_link']);
|
||||
$pc_gap = max(0, min(40, (int) ($instance['pc_gap'] ?? $instance['gap'] ?? 12)));
|
||||
$m_gap = max(0, min(30, (int) ($instance['m_gap'] ?? 6)));
|
||||
$pc_pad = sanitize_key((string) ($instance['pc_padding'] ?? 'large'));
|
||||
$m_pad = sanitize_key((string) ($instance['m_padding'] ?? 'medium'));
|
||||
if (!in_array($pc_pad, ['small', 'medium', 'large'], true)) {
|
||||
$pc_pad = 'large';
|
||||
}
|
||||
if (!in_array($m_pad, ['small', 'medium', 'large'], true)) {
|
||||
$m_pad = 'medium';
|
||||
}
|
||||
$anim = sanitize_key((string) ($instance['obs_animation'] ?? ''));
|
||||
$anim_repeat = !empty($instance['animation_repeat']);
|
||||
$pc_width = max(0, min(400, (int) ($instance['pc_width'] ?? 0)));
|
||||
$m_width = max(0, min(400, (int) ($instance['m_width'] ?? 0)));
|
||||
|
||||
$but_class = 'but';
|
||||
if ($radius) {
|
||||
$but_class .= ' radius';
|
||||
}
|
||||
if ($hollow) {
|
||||
$but_class .= ' hollow';
|
||||
}
|
||||
|
||||
$buts = '';
|
||||
$key = 0;
|
||||
foreach ($buttons as $btn) {
|
||||
if (!is_array($btn)) {
|
||||
continue;
|
||||
}
|
||||
$text = sanitize_text_field((string) ($btn['text'] ?? ''));
|
||||
if ($text === '') {
|
||||
continue;
|
||||
}
|
||||
$url = '';
|
||||
$target = '';
|
||||
if (is_array($btn['link'] ?? null)) {
|
||||
$url = esc_url_raw(trim((string) ($btn['link']['url'] ?? '')));
|
||||
$target = sanitize_text_field((string) ($btn['link']['target'] ?? ''));
|
||||
} else {
|
||||
$url = esc_url_raw(trim((string) ($btn['url'] ?? '')));
|
||||
$target = !empty($btn['new_window']) || !empty($btn['target']) ? '_blank' : '';
|
||||
}
|
||||
if ($url !== '' && $go_link && function_exists('aksrt_go_link')) {
|
||||
$url = aksrt_go_link($url, true);
|
||||
}
|
||||
if ($url === '') {
|
||||
$url = 'javascript:void(0)';
|
||||
}
|
||||
|
||||
$color = sanitize_html_class((string) ($btn['color'] ?? $btn['style'] ?? ''));
|
||||
// Map legacy style keys to color classes.
|
||||
$style_map = [
|
||||
'primary' => 'c-blue',
|
||||
'secondary' => 'c-blue-2',
|
||||
'outline' => '',
|
||||
'text' => '',
|
||||
'pink' => 'c-red',
|
||||
'blue' => 'c-blue',
|
||||
'green' => 'c-green',
|
||||
'yellow' => 'c-yellow',
|
||||
'purple' => 'c-purple',
|
||||
];
|
||||
if (isset($style_map[$color])) {
|
||||
$color = $style_map[$color];
|
||||
}
|
||||
$item_class = trim($but_class . ' ' . $color);
|
||||
|
||||
$icon_left = sanitize_text_field((string) ($btn['icon'] ?? $btn['icon_left'] ?? ''));
|
||||
$icon_right = sanitize_text_field((string) ($btn['icon_right'] ?? ''));
|
||||
$left_html = $icon_left !== '' ? '<i class="' . esc_attr($icon_left) . '" aria-hidden="true"></i> ' : '';
|
||||
$right_html = $icon_right !== '' ? ' <span class="ml6"><i class="' . esc_attr($icon_right) . '" aria-hidden="true"></i></span>' : '';
|
||||
|
||||
$anim_pack = function_exists('aksrt_obs_animate_attrs')
|
||||
? aksrt_obs_animate_attrs($anim, $anim_repeat, $key * 0.15)
|
||||
: ['class' => '', 'attr' => ''];
|
||||
|
||||
$tgt = $target !== '' && strpos($url, 'javascript:') !== 0
|
||||
? ' target="' . esc_attr($target) . '" rel="noopener noreferrer"'
|
||||
: '';
|
||||
|
||||
$buts .= '<div class="' . esc_attr(trim($anim_pack['class'])) . '"' . $anim_pack['attr'] . '>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$buts .= '<a class="' . esc_attr($item_class) . '" href="' . esc_url($url) . '"' . $tgt . '>'
|
||||
. $left_html . esc_html($text) . $right_html . '</a>';
|
||||
$buts .= '</div>';
|
||||
$key++;
|
||||
}
|
||||
|
||||
if ($buts === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
$style_attr = '';
|
||||
if ($pc_width > 0) {
|
||||
$style_attr .= '--pc-width:' . $pc_width . 'px;';
|
||||
}
|
||||
if ($m_width > 0) {
|
||||
$style_attr .= '--m-width:' . $m_width . 'px;';
|
||||
}
|
||||
$style_attr .= '--pc-gap:' . $pc_gap . 'px;--m-gap:' . $m_gap . 'px;--btn-gap:' . $pc_gap . 'px;';
|
||||
|
||||
echo '<div class="widget-buttons mb20 pc-size-' . esc_attr($pc_pad) . ' m-size-' . esc_attr($m_pad) . '">';
|
||||
echo '<div class="widget-buttons-wrap flex hh align-' . esc_attr($align) . '" style="' . esc_attr($style_attr) . '">';
|
||||
echo $buts; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '</div></div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function form($instance): void {
|
||||
$buttons = isset($instance['buttons']) && is_array($instance['buttons']) ? $instance['buttons'] : [];
|
||||
while (count($buttons) < 4) {
|
||||
$buttons[] = ['text' => '', 'url' => '', 'style' => 'primary', 'icon' => '', 'icon_right' => '', 'new_window' => false];
|
||||
}
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题(可选)', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('对齐', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('align')); ?>">
|
||||
<option value="center" <?php selected(($instance['align'] ?? 'center'), 'center'); ?>><?php esc_html_e('居中', 'aksrt'); ?></option>
|
||||
<option value="left" <?php selected(($instance['align'] ?? ''), 'left'); ?>><?php esc_html_e('左', 'aksrt'); ?></option>
|
||||
<option value="right" <?php selected(($instance['align'] ?? ''), 'right'); ?>><?php esc_html_e('右', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('radius')); ?>" value="1" <?php checked(!empty($instance['radius'])); ?>> <?php esc_html_e('圆角胶囊', 'aksrt'); ?></label>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hollow')); ?>" value="1" <?php checked(!empty($instance['hollow'])); ?>> <?php esc_html_e('镂空', 'aksrt'); ?></label>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('go_link')); ?>" value="1" <?php checked(!empty($instance['go_link'])); ?>> <?php esc_html_e('外链跳转页', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('入场动画', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('obs_animation')); ?>">
|
||||
<option value="" <?php selected(($instance['obs_animation'] ?? ''), ''); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
|
||||
<option value="fade" <?php selected(($instance['obs_animation'] ?? ''), 'fade'); ?>><?php esc_html_e('淡入', 'aksrt'); ?></option>
|
||||
<option value="slideup" <?php selected(($instance['obs_animation'] ?? ''), 'slideup'); ?>><?php esc_html_e('上滑', 'aksrt'); ?></option>
|
||||
<option value="slideright" <?php selected(($instance['obs_animation'] ?? ''), 'slideright'); ?>><?php esc_html_e('右滑', 'aksrt'); ?></option>
|
||||
<option value="zoomin" <?php selected(($instance['obs_animation'] ?? ''), 'zoomin'); ?>><?php esc_html_e('放大', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<?php foreach ($buttons as $i => $btn) : ?>
|
||||
<div style="border:1px solid #ddd;padding:6px;margin-bottom:6px;border-radius:4px;">
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name("buttons[{$i}][text]")); ?>" value="<?php echo esc_attr((string) ($btn['text'] ?? '')); ?>" placeholder="<?php esc_attr_e('按钮文字', 'aksrt'); ?>">
|
||||
<input class="widefat" type="url" name="<?php echo esc_attr($this->get_field_name("buttons[{$i}][url]")); ?>" value="<?php echo esc_attr((string) ($btn['url'] ?? '')); ?>" placeholder="<?php esc_attr_e('链接', 'aksrt'); ?>">
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name("buttons[{$i}][icon]")); ?>" value="<?php echo esc_attr((string) ($btn['icon'] ?? '')); ?>" placeholder="<?php esc_attr_e('左图标 class', 'aksrt'); ?>">
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name("buttons[{$i}][icon_right]")); ?>" value="<?php echo esc_attr((string) ($btn['icon_right'] ?? '')); ?>" placeholder="<?php esc_attr_e('右图标 class', 'aksrt'); ?>">
|
||||
<select class="widefat" name="<?php echo esc_attr($this->get_field_name("buttons[{$i}][style]")); ?>">
|
||||
<?php foreach (['primary' => '主要', 'secondary' => '次要', 'outline' => '线框', 'text' => '文字', 'pink' => '粉', 'blue' => '蓝', 'green' => '绿', 'yellow' => '黄', 'purple' => '紫'] as $k => $lab) : ?>
|
||||
<option value="<?php echo esc_attr($k); ?>" <?php selected(($btn['style'] ?? 'primary'), $k); ?>><?php echo esc_html($lab); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name("buttons[{$i}][new_window]")); ?>" value="1" <?php checked(!empty($btn['new_window'])); ?>> <?php esc_html_e('新窗口', 'aksrt'); ?></label>
|
||||
</div>
|
||||
<?php endforeach;
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $new_instance
|
||||
* @param array<string,mixed> $old_instance
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$buttons = [];
|
||||
if (isset($new_instance['buttons']) && is_array($new_instance['buttons'])) {
|
||||
foreach ($new_instance['buttons'] as $btn) {
|
||||
if (!is_array($btn)) {
|
||||
continue;
|
||||
}
|
||||
$text = sanitize_text_field((string) ($btn['text'] ?? ''));
|
||||
if ($text === '') {
|
||||
continue;
|
||||
}
|
||||
$buttons[] = [
|
||||
'text' => $text,
|
||||
'url' => esc_url_raw(trim((string) ($btn['url'] ?? ''))),
|
||||
'style' => sanitize_key((string) ($btn['style'] ?? 'primary')),
|
||||
'icon' => sanitize_text_field((string) ($btn['icon'] ?? '')),
|
||||
'icon_right' => sanitize_text_field((string) ($btn['icon_right'] ?? '')),
|
||||
'new_window' => !empty($btn['new_window']),
|
||||
];
|
||||
if (count($buttons) >= 8) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$business = [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'align' => in_array(($new_instance['align'] ?? ''), ['left', 'center', 'right'], true) ? $new_instance['align'] : 'center',
|
||||
'radius' => !empty($new_instance['radius']) ? 1 : 0,
|
||||
'hollow' => !empty($new_instance['hollow']) ? 1 : 0,
|
||||
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
|
||||
'gap' => max(4, min(30, (int) ($new_instance['gap'] ?? 12))),
|
||||
'pc_gap' => max(0, min(60, (int) ($new_instance['pc_gap'] ?? $new_instance['gap'] ?? 12))),
|
||||
'm_gap' => max(0, min(60, (int) ($new_instance['m_gap'] ?? 6))),
|
||||
'pc_padding' => in_array(($new_instance['pc_padding'] ?? 'large'), ['small', 'medium', 'large'], true) ? $new_instance['pc_padding'] : 'large',
|
||||
'm_padding' => in_array(($new_instance['m_padding'] ?? 'medium'), ['small', 'medium', 'large'], true) ? $new_instance['m_padding'] : 'medium',
|
||||
'pc_width' => max(0, min(500, (int) ($new_instance['pc_width'] ?? 0))),
|
||||
'm_width' => max(0, min(500, (int) ($new_instance['m_width'] ?? 0))),
|
||||
'obs_animation' => sanitize_key((string) ($new_instance['obs_animation'] ?? '')),
|
||||
'animation_repeat'=> !empty($new_instance['animation_repeat']) ? 1 : 0,
|
||||
'buttons' => $buttons,
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
/**
|
||||
* Categories list (zibll widget categories depth).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class CategoriesWidget extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_categories',
|
||||
esc_html__('AKSRT 分类目录', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('以列表形式展示文章分类', 'aksrt'),
|
||||
'customize_selective_refresh' => true,
|
||||
'classname' => 'widget_aksrt_categories',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/** @return array<int,int> */
|
||||
public static function parse_ids_static(string $raw): array {
|
||||
if ($raw === '') {
|
||||
return [];
|
||||
}
|
||||
$ids = [];
|
||||
foreach (preg_split('/[\s,]+/', $raw) ?: [] as $p) {
|
||||
$id = (int) $p;
|
||||
if ($id > 0) {
|
||||
$ids[] = $id;
|
||||
}
|
||||
}
|
||||
return array_values(array_unique($ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public static function render_html(array $instance): string {
|
||||
$count = max(1, min(50, (int) ($instance['count'] ?? 8)));
|
||||
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'count'));
|
||||
if (!in_array($orderby, ['count', 'name', 'id', 'slug'], true)) {
|
||||
$orderby = 'count';
|
||||
}
|
||||
$order = strtoupper((string) ($instance['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC';
|
||||
$hide_empty = !isset($instance['hide_empty']) || !empty($instance['hide_empty']);
|
||||
$hierarchical = !empty($instance['hierarchical']);
|
||||
$parent = max(0, (int) ($instance['parent'] ?? 0));
|
||||
$show_count = !isset($instance['show_count']) || !empty($instance['show_count']);
|
||||
$show_desc = !empty($instance['show_desc']);
|
||||
$show_thumb = !empty($instance['show_thumb']);
|
||||
$exclude = self::parse_ids_static((string) ($instance['exclude'] ?? ''));
|
||||
$include = self::parse_ids_static((string) ($instance['include'] ?? ''));
|
||||
|
||||
$term_args = [
|
||||
'taxonomy' => 'category',
|
||||
'number' => $hierarchical ? 0 : $count,
|
||||
'orderby' => $orderby,
|
||||
'order' => $order,
|
||||
'hide_empty' => $hide_empty,
|
||||
'hierarchical' => $hierarchical,
|
||||
];
|
||||
if ($parent > 0) {
|
||||
$term_args['parent'] = $parent;
|
||||
} elseif ($hierarchical) {
|
||||
$term_args['parent'] = 0;
|
||||
}
|
||||
if ($include) {
|
||||
$term_args['include'] = $include;
|
||||
$term_args['orderby'] = 'include';
|
||||
unset($term_args['number']);
|
||||
}
|
||||
if ($exclude) {
|
||||
$term_args['exclude'] = $exclude;
|
||||
}
|
||||
|
||||
$cats = get_categories($term_args);
|
||||
if (!$cats) {
|
||||
return function_exists('aksrt_get_ajax_null')
|
||||
? aksrt_get_ajax_null(__('暂无分类', 'aksrt'))
|
||||
: '<p class="widget-empty ajax-item">' . esc_html__('暂无分类', 'aksrt') . '</p>';
|
||||
}
|
||||
|
||||
ob_start();
|
||||
$cls = 'widget-list aksrt-cat-list ajax-item' . ($hierarchical ? ' is-tree' : '') . ($show_thumb ? ' has-thumb' : '');
|
||||
echo '<ul class="' . esc_attr($cls) . '">';
|
||||
$n = 0;
|
||||
foreach ($cats as $cat) {
|
||||
if (!$hierarchical && $n >= $count) {
|
||||
break;
|
||||
}
|
||||
echo self::render_cat_item($cat, $show_count, $show_desc, $show_thumb, $hierarchical ? 0 : -1, $count, $n, $hide_empty, $orderby, $order);
|
||||
$n++;
|
||||
}
|
||||
echo '</ul>';
|
||||
return (string) ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WP_Term $cat
|
||||
*/
|
||||
private static function render_cat_item($cat, bool $show_count, bool $show_desc, bool $show_thumb, int $depth, int $limit, int &$n, bool $hide_empty, string $orderby, string $order): string {
|
||||
if ($n > 0 && $limit > 0 && $n >= $limit && $depth >= 0) {
|
||||
return '';
|
||||
}
|
||||
$html = '<li class="aksrt-cat-list__item depth-' . max(0, $depth) . '">';
|
||||
$html .= '<a class="aksrt-cat-list__link" href="' . esc_url(get_category_link($cat->term_id)) . '">';
|
||||
if ($show_thumb) {
|
||||
$thumb = '';
|
||||
$img_id = (int) get_term_meta($cat->term_id, 'thumbnail_id', true);
|
||||
if (!$img_id) {
|
||||
$img_id = (int) get_term_meta($cat->term_id, '_aksrt_cover', true);
|
||||
}
|
||||
if ($img_id) {
|
||||
$thumb = (string) (wp_get_attachment_image_url($img_id, 'thumbnail') ?: '');
|
||||
}
|
||||
if ($thumb === '') {
|
||||
$thumb = (string) get_term_meta($cat->term_id, '_aksrt_cover_url', true);
|
||||
}
|
||||
if ($thumb !== '') {
|
||||
$ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
|
||||
$html .= '<span class="aksrt-cat-list__thumb"><img class="aksrt-lazy lazyload" data-src="' . esc_url($thumb) . '" src="' . esc_url($ph) . '" alt="" loading="lazy" width="32" height="32" /></span>';
|
||||
} else {
|
||||
$html .= '<span class="aksrt-cat-list__thumb is-empty"><i class="fa fa-folder-o" aria-hidden="true"></i></span>';
|
||||
}
|
||||
}
|
||||
$html .= '<span class="aksrt-cat-list__text">';
|
||||
$html .= '<span class="aksrt-cat-list__name">' . esc_html($cat->name) . '</span>';
|
||||
if ($show_desc && $cat->description !== '') {
|
||||
$desc = wp_trim_words(wp_strip_all_tags((string) $cat->description), 12, '…');
|
||||
$html .= '<span class="aksrt-cat-list__desc muted-2-color em09">' . esc_html($desc) . '</span>';
|
||||
}
|
||||
$html .= '</span>';
|
||||
if ($show_count) {
|
||||
$html .= '<span class="count">' . (int) $cat->count . '</span>';
|
||||
}
|
||||
$html .= '</a>';
|
||||
|
||||
if ($depth >= 0) {
|
||||
$children = get_categories([
|
||||
'taxonomy' => 'category',
|
||||
'parent' => (int) $cat->term_id,
|
||||
'hide_empty' => $hide_empty,
|
||||
'orderby' => $orderby,
|
||||
'order' => $order,
|
||||
]);
|
||||
if ($children) {
|
||||
$html .= '<ul class="aksrt-cat-list__children">';
|
||||
foreach ($children as $child) {
|
||||
$n++;
|
||||
if ($limit > 0 && $n > $limit) {
|
||||
break;
|
||||
}
|
||||
$html .= self::render_cat_item($child, $show_count, $show_desc, $show_thumb, $depth + 1, $limit, $n, $hide_empty, $orderby, $order);
|
||||
}
|
||||
$html .= '</ul>';
|
||||
}
|
||||
}
|
||||
$html .= '</li>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $args
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function widget($args, $instance): void {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
$affix = function_exists('aksrt_widget_affix_attrs') ? aksrt_widget_affix_attrs($instance) : ['class' => '', 'attr' => ''];
|
||||
if ($affix['class'] !== '') {
|
||||
echo '<div class="is-affix"' . $affix['attr'] . '>';
|
||||
}
|
||||
echo self::render_html($instance); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
if ($affix['class'] !== '') {
|
||||
echo '</div>';
|
||||
}
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function form($instance): void {
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题:', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? __('分类目录', 'aksrt'))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('显示数量:', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="50" name="<?php echo esc_attr($this->get_field_name('count')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['count'] ?? 8))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('排序', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
|
||||
<option value="count" <?php selected(($instance['orderby'] ?? 'count'), 'count'); ?>><?php esc_html_e('文章数', 'aksrt'); ?></option>
|
||||
<option value="name" <?php selected(($instance['orderby'] ?? ''), 'name'); ?>><?php esc_html_e('名称', 'aksrt'); ?></option>
|
||||
<option value="id" <?php selected(($instance['orderby'] ?? ''), 'id'); ?>><?php esc_html_e('ID', 'aksrt'); ?></option>
|
||||
<option value="slug" <?php selected(($instance['orderby'] ?? ''), 'slug'); ?>><?php esc_html_e('别名', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('order')); ?>">
|
||||
<option value="DESC" <?php selected(($instance['order'] ?? 'DESC'), '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><?php esc_html_e('父分类 ID(0=全部)', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('parent')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['parent'] ?? 0))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('包含 ID', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('include')); ?>" value="<?php echo esc_attr((string) ($instance['include'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('排除 ID', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('exclude')); ?>" value="<?php echo esc_attr((string) ($instance['exclude'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_count')); ?>" value="1" <?php checked(!isset($instance['show_count']) || !empty($instance['show_count'])); ?>> <?php esc_html_e('显示数量', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_desc')); ?>" value="1" <?php checked(!empty($instance['show_desc'])); ?>> <?php esc_html_e('显示描述', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_thumb')); ?>" value="1" <?php checked(!empty($instance['show_thumb'])); ?>> <?php esc_html_e('显示封面', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hide_empty')); ?>" value="1" <?php checked(!isset($instance['hide_empty']) || !empty($instance['hide_empty'])); ?>> <?php esc_html_e('隐藏空分类', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hierarchical')); ?>" value="1" <?php checked(!empty($instance['hierarchical'])); ?>> <?php esc_html_e('层级树', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php $this->render_common_form_section($instance); ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $new_instance
|
||||
* @param array<string,mixed> $old_instance
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'count'));
|
||||
if (!in_array($orderby, ['count', 'name', 'id', 'slug'], true)) {
|
||||
$orderby = 'count';
|
||||
}
|
||||
return $this->update_common_fields($new_instance, $old_instance, [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'count' => max(1, min(50, (int) ($new_instance['count'] ?? 8))),
|
||||
'orderby' => $orderby,
|
||||
'order' => strtoupper((string) ($new_instance['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC',
|
||||
'parent' => max(0, (int) ($new_instance['parent'] ?? 0)),
|
||||
'include' => sanitize_text_field((string) ($new_instance['include'] ?? '')),
|
||||
'exclude' => sanitize_text_field((string) ($new_instance['exclude'] ?? '')),
|
||||
'show_count' => !empty($new_instance['show_count']) ? 1 : 0,
|
||||
'show_desc' => !empty($new_instance['show_desc']) ? 1 : 0,
|
||||
'show_thumb' => !empty($new_instance['show_thumb']) ? 1 : 0,
|
||||
'hide_empty' => !empty($new_instance['hide_empty']) ? 1 : 0,
|
||||
'hierarchical' => !empty($new_instance['hierarchical']) ? 1 : 0,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,521 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
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: shared mini list (lazy thumbs + meta).
|
||||
if (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;
|
||||
}
|
||||
echo '<ul class="widget-tab-posts__list aksrt-home-widget__sidebar-list">';
|
||||
$ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
|
||||
while ($query->have_posts()) {
|
||||
$query->the_post();
|
||||
$thumb = get_the_post_thumbnail_url(get_the_ID(), 'thumbnail');
|
||||
echo '<li class="widget-tab-posts__item">';
|
||||
if ($thumb) {
|
||||
echo '<a href="' . esc_url(get_permalink()) . '" class="widget-tab-posts__thumb"><img class="fit-cover aksrt-lazy lazyload" data-src="' . esc_url($thumb) . '" src="' . esc_url($ph) . '" alt="" loading="lazy" /></a>';
|
||||
}
|
||||
echo '<div class="widget-tab-posts__info">';
|
||||
echo '<a href="' . esc_url(get_permalink()) . '" class="widget-tab-posts__title">' . esc_html(get_the_title()) . '</a>';
|
||||
echo '<span class="widget-tab-posts__meta">' . esc_html(human_time_diff(get_the_time('U'), current_time('timestamp')) . __('前', 'aksrt')) . '</span>';
|
||||
echo '</div></li>';
|
||||
}
|
||||
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 {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
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,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{html:string,paginate_html:string,has_more:bool,page:int,max_pages:int,total:int}
|
||||
*/
|
||||
public static function query_payload(array $instance, int $page = 1, string $context = 'homepage'): array {
|
||||
$page = max(1, $page);
|
||||
$category_id = self::resolve_category_id_static($instance);
|
||||
$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
|
||||
);
|
||||
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'none'));
|
||||
if (!in_array($paginate, ['ajax', 'none', 'number'], true)) {
|
||||
$paginate = 'none';
|
||||
}
|
||||
|
||||
$query_args = [
|
||||
'post_type' => aksrt_home_widget_post_types(),
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $count,
|
||||
'paged' => $page,
|
||||
'ignore_sticky_posts' => true,
|
||||
];
|
||||
if ($paginate === 'none') {
|
||||
$query_args['no_found_rows'] = true;
|
||||
}
|
||||
if ($category_id > 0) {
|
||||
$query_args['cat'] = $category_id;
|
||||
}
|
||||
|
||||
$query = new \WP_Query($query_args);
|
||||
ob_start();
|
||||
aksrt_home_widget_render_post_grid($query, $layout, $columns, $context);
|
||||
$html = (string) ob_get_clean();
|
||||
if ($html === '' && $page === 1) {
|
||||
$html = function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('暂无内容', 'aksrt'))
|
||||
: '<p class="widget-empty ajax-item">' . esc_html__('暂无内容', 'aksrt') . '</p>';
|
||||
}
|
||||
|
||||
$max_pages = max(1, (int) $query->max_num_pages);
|
||||
$total = (int) $query->found_posts;
|
||||
$has_more = in_array($paginate, ['ajax', 'number'], true) && $page < $max_pages;
|
||||
$base_qs = [
|
||||
'count' => $count,
|
||||
'cat' => $category_id,
|
||||
'columns' => $columns,
|
||||
'layout' => $layout,
|
||||
'context' => $context,
|
||||
'paginate' => $paginate,
|
||||
];
|
||||
$rest_base = add_query_arg($base_qs, rest_url('aksrt/v1/widgets/category-block'));
|
||||
$paginate_html = '';
|
||||
if ($paginate === 'ajax' && $has_more) {
|
||||
$next = add_query_arg(['page' => $page + 1], $rest_base);
|
||||
$paginate_html = function_exists('aksrt_get_ajax_next_paginate')
|
||||
? aksrt_get_ajax_next_paginate($total, $page, $count, $next)
|
||||
: '<div class="text-center theme-pagination ajax-pag"><div class="next-page ajax-next"><a href="' . esc_url($next) . '">' . esc_html__('加载更多', 'aksrt') . '</a></div></div>';
|
||||
} elseif ($paginate === 'number' && $max_pages > 1) {
|
||||
$paginate_html = function_exists('aksrt_get_ajax_number_paginate')
|
||||
? aksrt_get_ajax_number_paginate($total, $page, $count, $rest_base)
|
||||
: '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
|
||||
} else {
|
||||
$paginate_html = '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
|
||||
}
|
||||
|
||||
return compact('html', 'paginate_html', 'has_more', 'page', 'max_pages', 'total');
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$context = aksrt_home_widget_resolve_context($args);
|
||||
$category_id = self::resolve_category_id_static($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'));
|
||||
$show_more = ! empty($instance['show_more']);
|
||||
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'none'));
|
||||
if (!in_array($paginate, ['ajax', 'none', 'number'], true)) {
|
||||
$paginate = 'none';
|
||||
}
|
||||
$instance['paginate'] = $paginate;
|
||||
|
||||
$payload = self::query_payload($instance, 1, $context);
|
||||
|
||||
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')
|
||||
);
|
||||
|
||||
$rest = rest_url('aksrt/v1/widgets/category-block');
|
||||
echo '<div class="ajaxpager aksrt-home-widget__ajax" data-aksrt-ajaxpager data-aksrt-ajax-url="' . esc_url($rest) . '">';
|
||||
echo $payload['html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $payload['paginate_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '<div class="post_ajax_loader" data-aksrt-ajax-loader data-aksrt-ajax-loader-hide hidden></div>';
|
||||
echo '</div>';
|
||||
|
||||
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>
|
||||
<p>
|
||||
<label><?php esc_html_e('分页', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('paginate')); ?>">
|
||||
<option value="none" <?php selected(($instance['paginate'] ?? 'none'), 'none'); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
|
||||
<option value="ajax" <?php selected(($instance['paginate'] ?? ''), 'ajax'); ?>><?php esc_html_e('AJAX', 'aksrt'); ?></option>
|
||||
<option value="number" <?php selected(($instance['paginate'] ?? ''), 'number'); ?>><?php esc_html_e('数字分页', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<?php if (function_exists('aksrt_widget_render_location_help')) { aksrt_widget_render_location_help('hybrid'); } ?>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$business = [
|
||||
'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,
|
||||
'paginate' => in_array(sanitize_key((string) ($new_instance['paginate'] ?? 'none')), ['none', 'ajax', 'number'], true) ? sanitize_key((string) ($new_instance['paginate'] ?? 'none')) : 'none',
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
|
||||
public static function resolve_category_id_static(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;
|
||||
}
|
||||
|
||||
private function resolve_category_id(array $instance): int {
|
||||
return self::resolve_category_id_static($instance);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Homepage category recommend cards widget.
|
||||
*/
|
||||
class CategoryRecommendWidget extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
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 static function render_html(array $instance): string {
|
||||
$category_ids = aksrt_home_widget_normalize_category_ids($instance['category_ids'] ?? [], 4);
|
||||
$posts_per_cat = max(1, min(8, (int) ($instance['posts_per_category'] ?? 5)));
|
||||
$columns = max(1, min(4, (int) ($instance['columns'] ?? 2)));
|
||||
$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 = self::build_cards_static($category_ids, $posts_per_cat, $show_views, $show_description);
|
||||
if (empty($cards)) {
|
||||
return function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('暂无推荐分类', 'aksrt'))
|
||||
: '<p class="widget-empty ajax-item">' . esc_html__('暂无推荐分类', 'aksrt') . '</p>';
|
||||
}
|
||||
|
||||
$lazy = aksrt_home_widget_get_lazyload_args();
|
||||
ob_start();
|
||||
printf('<div class="aksrt-category-recommend__grid ajax-item" style="--aksrt-recommend-columns:%d;">', (int) $columns);
|
||||
foreach ($cards as $card) {
|
||||
$cover = (string) ($card['cover'] ?? '');
|
||||
$link = (string) ($card['link'] ?? '');
|
||||
$name = (string) ($card['name'] ?? '');
|
||||
$badge = sprintf(_n('%d 篇文章', '%d 篇文章', (int) $card['count'], 'aksrt'), (int) $card['count']);
|
||||
$more = '';
|
||||
if ($show_views && (int) $card['views'] > 0) {
|
||||
$v = function_exists('aksrt_format_number') ? aksrt_format_number((int) $card['views']) : number_format_i18n((int) $card['views']);
|
||||
$more = '<span class="aksrt-category-recommend__views"><i class="fa fa-eye" aria-hidden="true"></i> ' . esc_html((string) $v) . '</span>';
|
||||
}
|
||||
echo '<article class="aksrt-category-recommend__card" data-aksrt-animate="fade-up">';
|
||||
if (function_exists('aksrt_graphic_card') && $cover !== '') {
|
||||
echo aksrt_graphic_card([ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
'type' => 'style-2',
|
||||
'class' => 'aksrt-category-recommend__hero mb10',
|
||||
'img' => $cover,
|
||||
'alt' => $name,
|
||||
'link' => ['url' => $link],
|
||||
'text1' => $name,
|
||||
'text2' => $show_description ? (string) $card['description'] : '',
|
||||
'text3' => $badge,
|
||||
'more' => $more,
|
||||
'lazy' => true,
|
||||
]);
|
||||
} else {
|
||||
echo '<div class="aksrt-category-recommend__hero">';
|
||||
echo '<a href="' . esc_url($link) . '">';
|
||||
if ($lazy['enabled']) {
|
||||
echo '<span class="aksrt-skeleton"></span>';
|
||||
echo '<img class="aksrt-category-recommend__cover aksrt-lazy" data-src="' . esc_url($cover) . '" src="' . esc_url($lazy['placeholder_url']) . '" alt="' . esc_attr($name) . '">';
|
||||
} else {
|
||||
echo '<img class="aksrt-category-recommend__cover" src="' . esc_url($cover) . '" alt="' . esc_attr($name) . '">';
|
||||
}
|
||||
echo '</a>';
|
||||
echo '<span class="aksrt-category-recommend__badge">' . esc_html($badge) . '</span>';
|
||||
echo $more; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '</div>';
|
||||
echo '<div class="aksrt-category-recommend__meta">';
|
||||
echo '<h4 class="aksrt-category-recommend__name"><a href="' . esc_url($link) . '">' . esc_html($name) . '</a></h4>';
|
||||
if ($show_description && $card['description'] !== '') {
|
||||
echo '<p class="aksrt-category-recommend__desc">' . esc_html((string) $card['description']) . '</p>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '<div class="aksrt-category-recommend__body">';
|
||||
// Prefer shared mini list when post IDs available.
|
||||
if (!empty($card['post_ids']) && function_exists('aksrt_render_mini_list')) {
|
||||
$q = new \WP_Query([
|
||||
'post_type' => 'any',
|
||||
'post__in' => array_map('intval', (array) $card['post_ids']),
|
||||
'orderby' => 'post__in',
|
||||
'posts_per_page' => count((array) $card['post_ids']),
|
||||
'ignore_sticky_posts' => true,
|
||||
'no_found_rows' => true,
|
||||
]);
|
||||
echo '<div class="aksrt-category-recommend__posts posts-mini-wrap">';
|
||||
aksrt_render_mini_list($q, ['context' => 'widget', 'show_thumb' => true, 'show_meta' => $show_dates]);
|
||||
echo '</div>';
|
||||
wp_reset_postdata();
|
||||
} else {
|
||||
echo '<ul class="aksrt-category-recommend__posts">';
|
||||
foreach ($card['posts'] as $post_item) {
|
||||
echo '<li class="aksrt-category-recommend__post">';
|
||||
echo '<a class="aksrt-category-recommend__post-link" href="' . esc_url($post_item['link']) . '">' . esc_html($post_item['title']) . '</a>';
|
||||
if ($show_dates && $post_item['date'] !== '') {
|
||||
echo '<span class="aksrt-category-recommend__date">' . esc_html($post_item['date']) . '</span>';
|
||||
}
|
||||
echo '</li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
}
|
||||
if ($show_more) {
|
||||
echo '<div class="aksrt-home-widget__footer">';
|
||||
echo '<a class="aksrt-home-widget__more" href="' . esc_url($link) . '">' . esc_html__('查看更多', 'aksrt') . '</a>';
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div></article>';
|
||||
}
|
||||
echo '</div>';
|
||||
return (string) ob_get_clean();
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
|
||||
$title = $title !== '' ? $title : __('分类推荐', 'aksrt');
|
||||
$html = self::render_html($instance);
|
||||
|
||||
echo $args['before_widget'];
|
||||
echo '<section class="aksrt-home-widget aksrt-home-widget--recommend">';
|
||||
aksrt_home_widget_render_header($title);
|
||||
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
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
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$business = [
|
||||
'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,
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>>
|
||||
*/
|
||||
public static function build_cards_static(array $category_ids, int $posts_per_cat, bool $show_views, bool $show_description): array {
|
||||
$cards = [];
|
||||
|
||||
foreach ($category_ids as $category_id) {
|
||||
$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,206 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Homepage category tabs widget.
|
||||
*/
|
||||
class CategoryTabsWidget extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
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" data-aksrt-home-cat-tabs data-aksrt-tabs="1" data-rest="' . esc_url(rest_url('aksrt/v1/widgets/category-tabs')) . '" data-count="' . esc_attr((string) $count) . '" data-layout="' . esc_attr($layout) . '" data-columns="' . esc_attr((string) $columns) . '" data-context="' . esc_attr($context) . '" data-show-more="' . (!empty($show_more) ? '1' : '0') . '">';
|
||||
|
||||
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) {
|
||||
?>
|
||||
<div class="widget-tab-posts__panel<?php echo 0 === $index ? ' active' : ''; ?>"
|
||||
data-tab-panel="cat-<?php echo (int) $tab['id']; ?>"
|
||||
data-loaded="<?php echo 0 === $index ? '1' : '0'; ?>"
|
||||
data-cat="<?php echo (int) $tab['id']; ?>"
|
||||
data-more="<?php echo esc_url($tab['link']); ?>"
|
||||
role="tabpanel"<?php echo 0 === $index ? '' : ' hidden'; ?>>
|
||||
<?php if (0 === $index) :
|
||||
$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'],
|
||||
]);
|
||||
aksrt_home_widget_render_post_grid($query, $layout, $columns, $context);
|
||||
wp_reset_postdata();
|
||||
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;
|
||||
else : ?>
|
||||
<div class="post_ajax_loader" data-aksrt-ajax-loader>
|
||||
<?php for ($si = 0; $si < $count; $si++) : ?>
|
||||
<div class="posts-mini placeholder-row"><div class="placeholder radius thumb-placeholder"></div><div class="flex1"><div class="placeholder k1"></div><div class="placeholder s1 mt6"></div></div></div>
|
||||
<?php endfor; ?>
|
||||
</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
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$business = [
|
||||
'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,
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,115 @@
|
||||
<?php
|
||||
/**
|
||||
* Custom HTML / layout gap (zibll zib_widget_ui_layout_gap + custom HTML).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class CustomHtmlGapWidget extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct('aksrt_custom_html_gap', esc_html__('AKSRT 自定义HTML/间距', 'aksrt'), [
|
||||
'description' => esc_html__('插入自定义 HTML 或布局间距(PC/移动可分设)。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_custom_html_gap',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $args
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function widget($args, $instance): void {
|
||||
$mode = sanitize_key((string) ($instance['mode'] ?? 'html'));
|
||||
|
||||
if ($mode === 'gap') {
|
||||
$pc = max(0, min(500, (int) ($instance['gap_height'] ?? 20)));
|
||||
$m = max(0, min(500, (int) ($instance['gap_height_m'] ?? $pc)));
|
||||
// zibll layout gap: empty spacing block without widget chrome.
|
||||
$style = sprintf('--aksrt-gap-pc:%dpx;--aksrt-gap-m:%dpx;height:%dpx;', $pc, $m, $pc);
|
||||
echo '<div class="widget-custom-gap aksrt-layout-gap" style="' . esc_attr($style) . '" aria-hidden="true"></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
$content = (string) ($instance['content'] ?? '');
|
||||
if (trim(wp_strip_all_tags($content)) === '' && trim($content) === '') {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('请填写 HTML 内容或切换为间距模式', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<div class="c-red muted-box mb10"><b>' . esc_html__('自定义 HTML:', 'aksrt') . '</b>' . esc_html__('请填写内容或切换为间距模式', 'aksrt') . '</div>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
$full_width = !empty($instance['full_width']);
|
||||
$cls = 'widget-custom-html' . ($full_width ? ' is-full' : '');
|
||||
echo '<div class="' . esc_attr($cls) . '">' . wp_kses_post($content) . '</div>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function form($instance): void {
|
||||
$mode = sanitize_key((string) ($instance['mode'] ?? 'html'));
|
||||
?>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('mode')); ?>"><?php esc_html_e('模式:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('mode')); ?>" name="<?php echo esc_attr($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 esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题(仅 HTML 模式,可选):', '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((string) ($instance['title'] ?? '')); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('content')); ?>"><?php esc_html_e('HTML 内容:', 'aksrt'); ?></label>
|
||||
<textarea class="widefat" rows="6" id="<?php echo esc_attr($this->get_field_id('content')); ?>" name="<?php echo esc_attr($this->get_field_name('content')); ?>"><?php echo esc_textarea((string) ($instance['content'] ?? '')); ?></textarea>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('gap_height')); ?>"><?php esc_html_e('PC 间距高度 px:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" type="number" min="0" max="500" id="<?php echo esc_attr($this->get_field_id('gap_height')); ?>" name="<?php echo esc_attr($this->get_field_name('gap_height')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['gap_height'] ?? 20))); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('gap_height_m')); ?>"><?php esc_html_e('移动间距高度 px:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" type="number" min="0" max="500" id="<?php echo esc_attr($this->get_field_id('gap_height_m')); ?>" name="<?php echo esc_attr($this->get_field_name('gap_height_m')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['gap_height_m'] ?? $instance['gap_height'] ?? 20))); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('full_width')); ?>" value="1" <?php checked(!empty($instance['full_width'])); ?>> <?php esc_html_e('HTML 通栏', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $new_instance
|
||||
* @param array<string,mixed> $old_instance
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$business = [
|
||||
'mode' => in_array(($new_instance['mode'] ?? ''), ['html', 'gap'], true) ? $new_instance['mode'] : 'html',
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'content' => wp_kses_post((string) ($new_instance['content'] ?? '')),
|
||||
'gap_height' => max(0, min(500, (int) ($new_instance['gap_height'] ?? 20))),
|
||||
'gap_height_m' => max(0, min(500, (int) ($new_instance['gap_height_m'] ?? $new_instance['gap_height'] ?? 20))),
|
||||
'full_width' => !empty($new_instance['full_width']) ? 1 : 0,
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
/**
|
||||
* Friend links / bookmarks widget — routes through aksrt_links_box (zibll parity).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class FriendLinksWidget extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct('aksrt_friend_links', esc_html__('AKSRT 友情链接', 'aksrt'), [
|
||||
'description' => esc_html__('友情链接:card / bigcard / image / simple(共享 aksrt_links_box)。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_friend_links',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $args
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function widget($args, $instance): void {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? __('友情链接', 'aksrt')), $instance, $this->id_base);
|
||||
$source = sanitize_key((string) ($instance['source'] ?? 'settings'));
|
||||
$go_link = !empty($instance['go_link']);
|
||||
$limit = max(1, min(100, (int) ($instance['limit'] ?? 24)));
|
||||
$type = sanitize_key((string) ($instance['type'] ?? 'card'));
|
||||
if (!in_array($type, ['card', 'bigcard', 'image', 'simple'], true)) {
|
||||
$type = 'card';
|
||||
}
|
||||
$blank = !isset($instance['blank']) || !empty($instance['blank']);
|
||||
$nofollow = !isset($instance['nofollow']) || !empty($instance['nofollow']);
|
||||
$show_box = !empty($instance['show_box']) || !isset($instance['show_box']);
|
||||
|
||||
if ($title) {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
$bookmarks = $this->resolve_bookmarks($source, $instance, $limit);
|
||||
if (!$bookmarks) {
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('暂无友情链接', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<p class="widget-empty">' . esc_html__('暂无友情链接', 'aksrt') . '</p>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
return;
|
||||
}
|
||||
|
||||
$wrap = 'aksrt-friend-links aksrt-bookmark-links links-box style-' . $type . ($show_box ? ' has-box zib-widget' : '');
|
||||
echo '<div class="' . esc_attr($wrap) . '">';
|
||||
if (function_exists('aksrt_links_box')) {
|
||||
echo aksrt_links_box($bookmarks, $type, $nofollow, $go_link, $blank); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
} else {
|
||||
foreach ($bookmarks as $b) {
|
||||
$row = (array) $b;
|
||||
$url = (string) ($row['link_url'] ?? $row['url'] ?? '');
|
||||
$name = (string) ($row['link_name'] ?? $row['name'] ?? '');
|
||||
if ($url === '' || $name === '') {
|
||||
continue;
|
||||
}
|
||||
if ($go_link && function_exists('aksrt_go_link')) {
|
||||
$url = aksrt_go_link($url, true);
|
||||
}
|
||||
echo '<a class="aksrt-friend-links__item" href="' . esc_url($url) . '"' . ($blank ? ' target="_blank" rel="noopener noreferrer"' : '') . '>' . esc_html($name) . '</a>';
|
||||
}
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize to get_bookmarks-like objects/arrays for aksrt_links_box.
|
||||
*
|
||||
* @param array<string,mixed> $instance
|
||||
* @return array<int, object|array>
|
||||
*/
|
||||
private function resolve_bookmarks(string $source, array $instance, int $limit): array {
|
||||
if ($source === 'bookmarks') {
|
||||
$bm_args = [
|
||||
'orderby' => sanitize_key((string) ($instance['orderby'] ?? 'name')) ?: 'name',
|
||||
'order' => strtoupper((string) ($instance['order'] ?? 'ASC')) === 'DESC' ? 'DESC' : 'ASC',
|
||||
'limit' => $limit,
|
||||
];
|
||||
$cats = trim((string) ($instance['cats'] ?? ''));
|
||||
if ($cats !== '' && $cats !== 'all') {
|
||||
$bm_args['category'] = $cats;
|
||||
}
|
||||
return get_bookmarks($bm_args) ?: [];
|
||||
}
|
||||
|
||||
$links = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_friend_links() : [];
|
||||
if (!is_array($links) || !$links) {
|
||||
return $this->resolve_bookmarks('bookmarks', $instance, $limit);
|
||||
}
|
||||
$out = [];
|
||||
foreach (array_slice(array_values($links), 0, $limit) as $link) {
|
||||
if (!is_array($link)) {
|
||||
continue;
|
||||
}
|
||||
$out[] = (object) [
|
||||
'link_name' => (string) ($link['name'] ?? ''),
|
||||
'link_url' => (string) ($link['url'] ?? ''),
|
||||
'link_description' => (string) ($link['description'] ?? $link['desc'] ?? ''),
|
||||
'link_image' => (string) ($link['icon'] ?? $link['image'] ?? ''),
|
||||
'link_target' => '_blank',
|
||||
];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function form($instance): void {
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题:', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? __('友情链接', 'aksrt'))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('数据源', 'aksrt'); ?>
|
||||
<select class="widefat" name="<?php echo esc_attr($this->get_field_name('source')); ?>">
|
||||
<option value="settings" <?php selected(($instance['source'] ?? 'settings'), 'settings'); ?>><?php esc_html_e('后台友链设置', 'aksrt'); ?></option>
|
||||
<option value="bookmarks" <?php selected(($instance['source'] ?? ''), 'bookmarks'); ?>><?php esc_html_e('WordPress 链接', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('样式', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('type')); ?>">
|
||||
<option value="card" <?php selected(($instance['type'] ?? 'card'), 'card'); ?>><?php esc_html_e('图文卡片', 'aksrt'); ?></option>
|
||||
<option value="bigcard" <?php selected(($instance['type'] ?? ''), 'bigcard'); ?>><?php esc_html_e('大卡片', 'aksrt'); ?></option>
|
||||
<option value="image" <?php selected(($instance['type'] ?? ''), 'image'); ?>><?php esc_html_e('图片', 'aksrt'); ?></option>
|
||||
<option value="simple" <?php selected(($instance['type'] ?? ''), 'simple'); ?>><?php esc_html_e('纯文字', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
<label><?php esc_html_e('数量', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="100" name="<?php echo esc_attr($this->get_field_name('limit')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['limit'] ?? 24))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('书签分类 ID(书签模式)', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('cats')); ?>" value="<?php echo esc_attr((string) ($instance['cats'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('书签排序(书签模式)', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
|
||||
<option value="name" <?php selected(($instance['orderby'] ?? 'name'), 'name'); ?>><?php esc_html_e('名称', 'aksrt'); ?></option>
|
||||
<option value="updated" <?php selected(($instance['orderby'] ?? ''), 'updated'); ?>><?php esc_html_e('更新时间', 'aksrt'); ?></option>
|
||||
<option value="rating" <?php selected(($instance['orderby'] ?? ''), 'rating'); ?>><?php esc_html_e('评分', 'aksrt'); ?></option>
|
||||
<option value="rand" <?php selected(($instance['orderby'] ?? ''), 'rand'); ?>><?php esc_html_e('随机', 'aksrt'); ?></option>
|
||||
</select>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('order')); ?>">
|
||||
<option value="ASC" <?php selected(($instance['order'] ?? 'ASC'), 'ASC'); ?>><?php esc_html_e('升序', 'aksrt'); ?></option>
|
||||
<option value="DESC" <?php selected(($instance['order'] ?? ''), 'DESC'); ?>><?php esc_html_e('降序', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('blank')); ?>" value="1" <?php checked(!isset($instance['blank']) || !empty($instance['blank'])); ?>> <?php esc_html_e('新窗口', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('nofollow')); ?>" value="1" <?php checked(!isset($instance['nofollow']) || !empty($instance['nofollow'])); ?>> <?php esc_html_e('nofollow', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('go_link')); ?>" value="1" <?php checked(!empty($instance['go_link'])); ?>> <?php esc_html_e('外链跳转页', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_box')); ?>" value="1" <?php checked(!isset($instance['show_box']) || !empty($instance['show_box'])); ?>> <?php esc_html_e('外框盒子', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $new_instance
|
||||
* @param array<string,mixed> $old_instance
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$type = sanitize_key((string) ($new_instance['type'] ?? 'card'));
|
||||
if (!in_array($type, ['card', 'bigcard', 'image', 'simple'], true)) {
|
||||
$type = 'card';
|
||||
}
|
||||
$business = [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'source' => ($new_instance['source'] ?? '') === 'bookmarks' ? 'bookmarks' : 'settings',
|
||||
'type' => $type,
|
||||
'limit' => max(1, min(100, (int) ($new_instance['limit'] ?? 24))),
|
||||
'cats' => sanitize_text_field((string) ($new_instance['cats'] ?? '')),
|
||||
'blank' => !empty($new_instance['blank']) ? 1 : 0,
|
||||
'nofollow' => !empty($new_instance['nofollow']) ? 1 : 0,
|
||||
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
|
||||
'show_box' => !empty($new_instance['show_box']) ? 1 : 0,
|
||||
'orderby' => sanitize_key((string) ($new_instance['orderby'] ?? 'name')),
|
||||
'order' => strtoupper((string) ($new_instance['order'] ?? 'ASC')) === 'DESC' ? 'DESC' : 'ASC',
|
||||
// Keep legacy keys so old instances don't explode.
|
||||
'columns' => max(1, min(4, (int) ($new_instance['columns'] ?? 2))),
|
||||
'show_icon' => !empty($new_instance['show_icon']) ? 1 : 0,
|
||||
];
|
||||
return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
/**
|
||||
* Hot posts ranking (zibll hot posts + ajax).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class HotPostsWidget extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_hot_posts',
|
||||
esc_html__('AKSRT 热榜文章', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('按浏览量/评论数/点赞数排序的热门文章列表', 'aksrt'),
|
||||
'customize_selective_refresh' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public static function build_query_args(array $instance, int $paged = 1): array {
|
||||
$count = max(1, min(20, (int) ($instance['count'] ?? 5)));
|
||||
$order_by = sanitize_key((string) ($instance['order_by'] ?? 'views'));
|
||||
$days = max(0, (int) ($instance['days'] ?? 30));
|
||||
$query_args = [
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $count,
|
||||
'paged' => max(1, $paged),
|
||||
'ignore_sticky_posts' => 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;
|
||||
}
|
||||
return $query_args;
|
||||
}
|
||||
|
||||
public static function render_items_html(\WP_Query $query, array $instance, int $start = 0): string {
|
||||
if (!$query->have_posts()) {
|
||||
return '';
|
||||
}
|
||||
$show_thumb = !isset($instance['show_thumb']) || !empty($instance['show_thumb']);
|
||||
$order_by = sanitize_key((string) ($instance['order_by'] ?? 'views'));
|
||||
$placeholder = get_template_directory_uri() . '/assets/img/placeholder.svg';
|
||||
$top_tones = ['jb-red', 'jb-yellow', 'b-gray'];
|
||||
ob_start();
|
||||
$index = $start;
|
||||
while ($query->have_posts()) {
|
||||
$query->the_post();
|
||||
$index++;
|
||||
$pid = (int) get_the_ID();
|
||||
if ($order_by === 'comments') {
|
||||
$metric = (int) get_comments_number($pid);
|
||||
$metric_icon = 'fa-comment-o';
|
||||
$metric_label = __('条评论', 'aksrt');
|
||||
} elseif ($order_by === 'likes') {
|
||||
$metric = function_exists('aksrt_get_post_likes') ? (int) aksrt_get_post_likes($pid) : (int) get_post_meta($pid, '_aksrt_like_count', true);
|
||||
$metric_icon = 'fa-heart';
|
||||
$metric_label = __('人点赞', 'aksrt');
|
||||
} elseif ($order_by === 'favorites') {
|
||||
$metric = (int) get_post_meta($pid, '_aksrt_favorite_count', true);
|
||||
$metric_icon = 'fa-star';
|
||||
$metric_label = __('人收藏', 'aksrt');
|
||||
} elseif ($order_by === 'downloads') {
|
||||
$metric = (int) get_post_meta($pid, '_aksrt_res_download_count', true);
|
||||
$metric_icon = 'fa-download';
|
||||
$metric_label = __('次下载', 'aksrt');
|
||||
} else {
|
||||
$metric = (int) get_post_meta($pid, 'aksrt_post_views', true);
|
||||
$metric_icon = 'fa-eye';
|
||||
$metric_label = __('次阅读', 'aksrt');
|
||||
}
|
||||
$metric_txt = function_exists('aksrt_format_number') ? aksrt_format_number($metric) : (string) $metric;
|
||||
$tone = $top_tones[$index - 1] ?? 'b-gray';
|
||||
$top_badge = '<badge class="img-badge left hot ' . ($index === 1 ? 'em12 ' : '') . esc_attr($tone) . '"><i>TOP' . (int) $index . '</i></badge>';
|
||||
$permalink = get_permalink($pid);
|
||||
$title = get_the_title($pid);
|
||||
$time_ago = human_time_diff(get_the_time('U'), current_time('timestamp')) . __('前', 'aksrt');
|
||||
$tgt = !empty($instance['target_blank']) ? ' target="_blank" rel="noopener noreferrer"' : '';
|
||||
|
||||
// Graphic hero for #1 (zibll zib_hot_posts).
|
||||
if ($index === 1 && $start === 0) {
|
||||
$thumb = get_the_post_thumbnail_url($pid, 'large');
|
||||
if (!$thumb) {
|
||||
$thumb = get_the_post_thumbnail_url($pid, 'medium');
|
||||
}
|
||||
echo '<div class="hot-posts-graphic relative ajax-item is-top1">';
|
||||
echo '<a href="' . esc_url($permalink) . '"' . $tgt . '>';
|
||||
echo '<div class="graphic hover-zoom-img" style="padding-bottom:60%;">';
|
||||
if ($thumb) {
|
||||
echo '<img class="fit-cover radius8 aksrt-lazy lazyload" data-src="' . esc_url($thumb) . '" src="' . esc_url($placeholder) . '" alt="" loading="lazy" />';
|
||||
} else {
|
||||
echo '<span class="hot-posts-graphic__empty"></span>';
|
||||
}
|
||||
echo '<div class="absolute linear-mask"></div>';
|
||||
echo '<div class="abs-center left-bottom box-body">';
|
||||
echo '<div class="mb6"><span class="badg b-theme badg-sm"><i class="fa ' . esc_attr($metric_icon) . '" aria-hidden="true"></i> ' . esc_html($metric_txt . $metric_label) . '</span></div>';
|
||||
echo '<div class="hot-posts-graphic__title">' . esc_html(wp_html_excerpt($title, 48, '…')) . '</div>';
|
||||
echo '</div></div></a>';
|
||||
echo $top_badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '</div>';
|
||||
continue;
|
||||
}
|
||||
|
||||
$rank_class = $index <= 3 ? ' rank-top' : '';
|
||||
$thumb = $show_thumb ? get_the_post_thumbnail_url($pid, 'thumbnail') : '';
|
||||
?>
|
||||
<div class="hot-post-item flex mt15 relative hover-zoom-img ajax-item <?php echo esc_attr($rank_class); ?>">
|
||||
<?php echo $top_badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
<?php if ($show_thumb) : ?>
|
||||
<a class="hot-post-item__thumb graphic<?php echo $thumb ? '' : ' is-empty'; ?>" href="<?php echo esc_url($permalink); ?>"<?php echo $tgt; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
|
||||
<?php if ($thumb) : ?><img class="fit-cover radius8 aksrt-lazy lazyload" data-src="<?php echo esc_url($thumb); ?>" src="<?php echo esc_url($placeholder); ?>" alt="" loading="lazy" /><?php endif; ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<div class="hot-post-item__info term-title ml10 flex xx flex1 jsb">
|
||||
<div class="text-ellipsis-2"><a href="<?php echo esc_url($permalink); ?>" class="hot-post-item__title"<?php echo $tgt; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>><?php echo esc_html($title); ?></a></div>
|
||||
<div class="px12 muted-3-color text-ellipsis flex jsb hot-post-item__meta">
|
||||
<span class="hot-post-item__time"><i class="fa fa-clock-o mr3" aria-hidden="true"></i><?php echo esc_html($time_ago); ?></span>
|
||||
<span><i class="fa <?php echo esc_attr($metric_icon); ?>" aria-hidden="true"></i> <?php echo esc_html((string) $metric_txt); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
wp_reset_postdata();
|
||||
return (string) ob_get_clean();
|
||||
}
|
||||
|
||||
|
||||
public static function query_payload(array $instance, int $page = 1): array {
|
||||
$page = max(1, $page);
|
||||
$count = max(1, min(20, (int) ($instance['count'] ?? 5)));
|
||||
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'ajax'));
|
||||
$q = new \WP_Query(self::build_query_args($instance, $page));
|
||||
$items = self::render_items_html($q, $instance, ($page - 1) * $count);
|
||||
$html = $items !== '' ? '<div class="widget-hot-posts widget-hot-posts--graphic zib-widget">' . $items . '</div>' : '';
|
||||
if ($html === '' && $page === 1) {
|
||||
$html = function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('暂无热门文章', 'aksrt'))
|
||||
: '<p class="widget-empty ajax-item">' . esc_html__('暂无热门文章', 'aksrt') . '</p>';
|
||||
}
|
||||
$max_pages = max(1, (int) $q->max_num_pages);
|
||||
$total = (int) $q->found_posts;
|
||||
$has_more = $page < $max_pages;
|
||||
$base_qs = array_filter([
|
||||
'count' => $count,
|
||||
'order_by' => sanitize_key((string) ($instance['order_by'] ?? 'views')),
|
||||
'days' => max(0, (int) ($instance['days'] ?? 30)),
|
||||
'show_thumb' => !empty($instance['show_thumb']) ? 1 : 0,
|
||||
'target_blank'=> !empty($instance['target_blank']) ? 1 : 0,
|
||||
'paginate' => $paginate,
|
||||
], static function ($v) {
|
||||
return $v !== '' && $v !== null;
|
||||
});
|
||||
$rest_base = add_query_arg($base_qs, rest_url('aksrt/v1/widgets/hot-posts'));
|
||||
$paginate_html = '';
|
||||
if ($paginate === 'ajax' && $has_more) {
|
||||
$next = add_query_arg(['page' => $page + 1], $rest_base);
|
||||
$paginate_html = function_exists('aksrt_get_ajax_next_paginate')
|
||||
? aksrt_get_ajax_next_paginate($total, $page, $count, $next)
|
||||
: '<div class="text-center theme-pagination ajax-pag"><div class="next-page ajax-next"><a href="' . esc_url($next) . '">' . esc_html__('加载更多', 'aksrt') . '</a></div></div>';
|
||||
} elseif ($paginate === 'number' && $max_pages > 1) {
|
||||
$paginate_html = function_exists('aksrt_get_ajax_number_paginate')
|
||||
? aksrt_get_ajax_number_paginate($total, $page, $count, $rest_base)
|
||||
: '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
|
||||
} else {
|
||||
$paginate_html = '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
|
||||
}
|
||||
return compact('html', 'paginate_html', 'has_more', 'page', 'max_pages', 'total');
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', $instance['title'] ?? __('热门文章', 'aksrt'), $instance, $this->id_base);
|
||||
if ($title) {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
$affix = function_exists('aksrt_widget_affix_attrs') ? aksrt_widget_affix_attrs($instance) : ['class' => '', 'attr' => ''];
|
||||
$instance['paginate'] = sanitize_key((string) ($instance['paginate'] ?? 'ajax'));
|
||||
$payload = self::query_payload($instance, 1);
|
||||
$rest = rest_url('aksrt/v1/widgets/hot-posts');
|
||||
$wrap = 'ajaxpager' . $affix['class'];
|
||||
echo '<div class="' . esc_attr($wrap) . '" data-aksrt-ajaxpager data-aksrt-ajax-url="' . esc_url($rest) . '"' . $affix['attr'] . '>';
|
||||
echo $payload['html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $payload['paginate_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '<div class="post_ajax_loader" data-aksrt-ajax-loader data-aksrt-ajax-loader-hide hidden></div>';
|
||||
echo '</div>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
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 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((string) $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" min="1" max="20" value="<?php echo (int) $count; ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('order_by')); ?>"><?php esc_html_e('排序方式:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('order_by')); ?>" name="<?php echo esc_attr($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 esc_attr($this->get_field_id('days')); ?>"><?php esc_html_e('时间范围(天,0=不限):', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('days')); ?>" name="<?php echo esc_attr($this->get_field_name('days')); ?>" type="number" min="0" max="365" value="<?php echo (int) $days; ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_thumb')); ?>" value="1" <?php checked(!empty($instance['show_thumb'])); ?>> <?php esc_html_e('显示缩略图', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('target_blank')); ?>" value="1" <?php checked(!empty($instance['target_blank'])); ?>> <?php esc_html_e('新窗口打开', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('分页', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('paginate')); ?>">
|
||||
<option value="ajax" <?php selected(($instance['paginate'] ?? 'ajax'), 'ajax'); ?>><?php esc_html_e('AJAX', 'aksrt'); ?></option>
|
||||
<option value="number" <?php selected(($instance['paginate'] ?? ''), 'number'); ?>><?php esc_html_e('数字分页', 'aksrt'); ?></option>
|
||||
<option value="none" <?php selected(($instance['paginate'] ?? ''), 'none'); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<?php $this->render_common_form_section($instance); ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return $this->update_common_fields($new_instance, $old_instance, [
|
||||
'title' => 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),
|
||||
'show_thumb' => !empty($new_instance['show_thumb']) ? 1 : 0,
|
||||
'target_blank' => !empty($new_instance['target_blank']) ? 1 : 0,
|
||||
'paginate' => in_array(sanitize_key((string) ($new_instance['paginate'] ?? 'ajax')), ['none', 'ajax', 'number'], true) ? sanitize_key((string) ($new_instance['paginate'] ?? 'ajax')) : 'ajax',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
/**
|
||||
* Vertical icon cards grid (zibll zib_widget_ui_icon_card → aksrt_icon_card).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class IconCardsWidget extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct('aksrt_icon_cards', esc_html__('AKSRT 图标卡片', 'aksrt'), [
|
||||
'description' => esc_html__('图标在上、文案在下的竖版图标卡片网格。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_icon_cards',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $args
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function widget($args, $instance): void {
|
||||
$cards = $this->normalize($instance['cards'] ?? []);
|
||||
if (!$cards) {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('请添加至少一张图标卡', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<p class="widget-empty">' . esc_html__('请添加至少一张图标卡', 'aksrt') . '</p>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$pc_row = max(1, min(6, (int) ($instance['columns'] ?? $instance['pc_row'] ?? 2)));
|
||||
$m_row = max(1, min(4, (int) ($instance['m_row'] ?? 2)));
|
||||
$go_link = !empty($instance['go_link']);
|
||||
$show_bg = !isset($instance['show_widget_bg']) || !empty($instance['show_widget_bg']);
|
||||
$icon_size = max(0, min(64, (int) ($instance['icon_size'] ?? $instance['size'] ?? 0)));
|
||||
$icon_radius = !empty($instance['icon_radius']);
|
||||
$anim = sanitize_key((string) ($instance['obs_animation'] ?? ''));
|
||||
$anim_repeat = !empty($instance['animation_repeat']);
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
$is_row = count($cards) > 1;
|
||||
$pc_span = max(1, (int) floor(12 / $pc_row));
|
||||
$m_span = max(1, (int) floor(12 / $m_row));
|
||||
$row_class = 'col-sm-' . $pc_span . ($m_row > 1 ? ' col-xs-' . $m_span : '');
|
||||
|
||||
echo $show_bg ? '<div class="zib-widget nobottom notop">' : '<div class="clear">';
|
||||
if ($is_row) {
|
||||
echo '<div class="row gutters-5">';
|
||||
}
|
||||
|
||||
$idx = 0;
|
||||
foreach ($cards as $card) {
|
||||
$url = (string) ($card['url'] ?? '');
|
||||
if ($url !== '' && $go_link && function_exists('aksrt_go_link')) {
|
||||
$url = aksrt_go_link($url, true);
|
||||
}
|
||||
$card_args = [
|
||||
'class' => $show_bg ? 'box-body nopw-sm' : 'mb20',
|
||||
'icon' => (string) ($card['icon'] ?? ''),
|
||||
'icon_image' => (string) ($card['icon_image'] ?? ''),
|
||||
'icon_size' => $icon_size > 0 ? (16 + $icon_size) : 0,
|
||||
'icon_radius' => $icon_radius || !empty($card['icon_radius']),
|
||||
'icon_color' => (string) ($card['icon_color'] ?? ''),
|
||||
'icon_class' => (string) ($card['icon_class'] ?? ''),
|
||||
'title' => (string) ($card['title'] ?? ''),
|
||||
'desc' => (string) ($card['desc'] ?? ''),
|
||||
'link' => [
|
||||
'url' => $url,
|
||||
'target' => (string) ($card['target'] ?? ''),
|
||||
],
|
||||
];
|
||||
|
||||
$anim_pack = function_exists('aksrt_obs_animate_attrs')
|
||||
? aksrt_obs_animate_attrs($anim, $anim_repeat, $idx * 0.08)
|
||||
: ['class' => '', 'attr' => ''];
|
||||
|
||||
if ($is_row) {
|
||||
echo '<div class="' . esc_attr(trim($row_class . $anim_pack['class'])) . '"' . $anim_pack['attr'] . '>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
} elseif ($anim_pack['class'] !== '') {
|
||||
echo '<div class="' . esc_attr(trim($anim_pack['class'])) . '"' . $anim_pack['attr'] . '>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_icon_card')) {
|
||||
echo aksrt_icon_card($card_args); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
if ($is_row || $anim_pack['class'] !== '') {
|
||||
echo '</div>';
|
||||
}
|
||||
$idx++;
|
||||
}
|
||||
|
||||
if ($is_row) {
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return array<int, array<string,string>>
|
||||
*/
|
||||
private function normalize($raw): array {
|
||||
if (is_string($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
$raw = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($raw as $card) {
|
||||
if (!is_array($card)) {
|
||||
continue;
|
||||
}
|
||||
$title = sanitize_text_field((string) ($card['title'] ?? ''));
|
||||
$icon = sanitize_text_field((string) ($card['icon'] ?? ''));
|
||||
$icon_image = esc_url_raw(trim((string) ($card['icon_image'] ?? $card['customize_icon'] ?? '')));
|
||||
if ($title === '' && $icon === '' && $icon_image === '') {
|
||||
continue;
|
||||
}
|
||||
$url = esc_url_raw(trim((string) ($card['link'] ?? $card['url'] ?? '')));
|
||||
if (is_array($card['link'] ?? null)) {
|
||||
$url = esc_url_raw(trim((string) ($card['link']['url'] ?? '')));
|
||||
$target = sanitize_text_field((string) ($card['link']['target'] ?? ''));
|
||||
} else {
|
||||
$target = sanitize_text_field((string) ($card['target'] ?? ''));
|
||||
}
|
||||
$out[] = [
|
||||
'title' => $title,
|
||||
'desc' => sanitize_text_field((string) ($card['description'] ?? $card['desc'] ?? '')),
|
||||
'icon' => $icon,
|
||||
'icon_image' => $icon_image,
|
||||
'icon_color' => sanitize_html_class((string) ($card['icon_color'] ?? '')),
|
||||
'icon_class' => sanitize_html_class((string) ($card['icon_class'] ?? '')),
|
||||
'url' => $url,
|
||||
'target' => $target,
|
||||
];
|
||||
if (count($out) >= 12) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function form($instance): void {
|
||||
$cards = $this->normalize($instance['cards'] ?? []);
|
||||
while (count($cards) < 4) {
|
||||
$cards[] = ['title' => '', 'icon' => '', 'url' => '', 'desc' => ''];
|
||||
}
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题(可选):', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('PC 列数:', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="6" name="<?php echo esc_attr($this->get_field_name('columns')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['columns'] ?? 2))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('移动列数', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="4" name="<?php echo esc_attr($this->get_field_name('m_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['m_row'] ?? 2))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('go_link')); ?>" value="1" <?php checked(!empty($instance['go_link'])); ?>> <?php esc_html_e('外链跳转页', 'aksrt'); ?></label>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_widget_bg')); ?>" value="1" <?php checked(!isset($instance['show_widget_bg']) || !empty($instance['show_widget_bg'])); ?>> <?php esc_html_e('卡片背景', 'aksrt'); ?></label>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('icon_radius')); ?>" value="1" <?php checked(!empty($instance['icon_radius'])); ?>> <?php esc_html_e('圆形底图标', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<hr>
|
||||
<?php foreach ($cards as $i => $card) : ?>
|
||||
<div style="border:1px solid #ddd;padding:6px;margin-bottom:6px;border-radius:4px;">
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name("cards[{$i}][title]")); ?>" value="<?php echo esc_attr((string) ($card['title'] ?? '')); ?>" placeholder="<?php esc_attr_e('标题', 'aksrt'); ?>">
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name("cards[{$i}][icon]")); ?>" value="<?php echo esc_attr((string) ($card['icon'] ?? '')); ?>" placeholder="<?php esc_attr_e('图标类名', 'aksrt'); ?>">
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name("cards[{$i}][desc]")); ?>" value="<?php echo esc_attr((string) ($card['desc'] ?? '')); ?>" placeholder="<?php esc_attr_e('描述', 'aksrt'); ?>">
|
||||
<input class="widefat" type="url" name="<?php echo esc_attr($this->get_field_name("cards[{$i}][url]")); ?>" value="<?php echo esc_attr((string) ($card['url'] ?? '')); ?>" placeholder="<?php esc_attr_e('链接', 'aksrt'); ?>">
|
||||
</div>
|
||||
<?php endforeach;
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $new_instance
|
||||
* @param array<string,mixed> $old_instance
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$business = [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'columns' => max(1, min(6, (int) ($new_instance['columns'] ?? 2))),
|
||||
'm_row' => max(1, min(4, (int) ($new_instance['m_row'] ?? 2))),
|
||||
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
|
||||
'show_widget_bg' => !empty($new_instance['show_widget_bg']) ? 1 : 0,
|
||||
'icon_radius' => !empty($new_instance['icon_radius']) ? 1 : 0,
|
||||
'obs_animation' => sanitize_key((string) ($new_instance['obs_animation'] ?? '')),
|
||||
'cards' => $this->normalize($new_instance['cards'] ?? []),
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
/**
|
||||
* Scrolling notice bar (zibll widget_ui_notice / zib_notice → vertical new-swiper).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class NoticeWidget extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct('aksrt_notice', esc_html__('AKSRT 滚动公告', 'aksrt'), [
|
||||
'description' => esc_html__('多条公告垂直滚动(共享 Swiper 栈)。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_notice',
|
||||
]);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$items = $this->normalize_items($instance);
|
||||
if (!$items) {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('请添加至少一条公告', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<p class="widget-empty">' . esc_html__('请添加至少一条公告', 'aksrt') . '</p>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$color = sanitize_html_class((string) ($instance['color'] ?? 'c-blue'));
|
||||
$blank = !empty($instance['blank']);
|
||||
$radius = !empty($instance['radius']);
|
||||
$interval = max(2, min(15, (int) ($instance['interval'] ?? 4)));
|
||||
$align = sanitize_html_class((string) ($instance['alignment'] ?? ''));
|
||||
$affix = function_exists('aksrt_widget_affix_attrs') ? aksrt_widget_affix_attrs($instance) : ['class' => '', 'attr' => ''];
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
$wrap_cls = 'aksrt-scroll-notice swiper-bulletin color-' . $color;
|
||||
if ($radius) {
|
||||
$wrap_cls .= ' is-radius radius';
|
||||
}
|
||||
if ($align !== '') {
|
||||
$wrap_cls .= ' ' . $align;
|
||||
}
|
||||
$wrap_cls .= $affix['class'];
|
||||
|
||||
echo '<div class="' . esc_attr($wrap_cls) . '"' . $affix['attr'] . '>';
|
||||
echo '<div class="aksrt-scroll-notice__icon bulletin-icon"><i class="fas fa-bullhorn" aria-hidden="true"></i></div>';
|
||||
|
||||
// Prefer shared vertical new-swiper (zib_notice parity) via slider.js Type C.
|
||||
echo '<div class="new-swiper aksrt-notice-swiper" data-direction="vertical" data-loop data-autoplay data-interval="' . esc_attr((string) ($interval * 1000)) . '">';
|
||||
echo '<div class="swiper-wrapper">';
|
||||
foreach ($items as $item) {
|
||||
$inner = '';
|
||||
if ($item['icon'] !== '') {
|
||||
$icon = $item['icon'];
|
||||
if (strpos($icon, 'fa-') === 0 && strpos($icon, ' ') === false) {
|
||||
$icon = 'fa ' . $icon;
|
||||
}
|
||||
$inner .= '<div class="relative bulletin-icon mr6"><i class="abs-center ' . esc_attr($icon) . '" aria-hidden="true"></i></div>';
|
||||
}
|
||||
$inner .= esc_html($item['title']);
|
||||
echo '<div class="swiper-slide notice-slide">';
|
||||
if ($item['href'] !== '') {
|
||||
echo '<a class="text-ellipsis"' .
|
||||
($blank ? ' target="_blank" rel="noopener noreferrer"' : '') .
|
||||
' href="' . esc_url($item['href']) . '">' . $inner . '</a>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
} else {
|
||||
echo '<span class="text-ellipsis">' . $inner . '</span>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div></div>';
|
||||
|
||||
echo '</div>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
* @return array<int, array{title:string,icon:string,href:string}>
|
||||
*/
|
||||
private function normalize_items(array $instance): array {
|
||||
$raw = $instance['items'] ?? $instance['img_ids'] ?? null;
|
||||
if ($raw === null && !empty($instance['content'])) {
|
||||
return [[
|
||||
'title' => wp_strip_all_tags((string) $instance['content']),
|
||||
'icon' => sanitize_text_field((string) ($instance['icon'] ?? 'fas fa-bullhorn')),
|
||||
'href' => '',
|
||||
]];
|
||||
}
|
||||
if (is_string($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
$raw = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($raw as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$title = sanitize_text_field((string) ($row['title'] ?? $row['content'] ?? ''));
|
||||
if ($title === '') {
|
||||
continue;
|
||||
}
|
||||
$out[] = [
|
||||
'title' => $title,
|
||||
'icon' => sanitize_text_field((string) ($row['icon'] ?? '')),
|
||||
'href' => esc_url_raw(trim((string) ($row['href'] ?? $row['url'] ?? $row['link'] ?? ''))),
|
||||
];
|
||||
if (count($out) >= 12) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$items = $this->normalize_items($instance);
|
||||
if (!$items) {
|
||||
$items = [
|
||||
['title' => '', 'icon' => 'fas fa-bullhorn', 'href' => ''],
|
||||
['title' => '', 'icon' => 'fas fa-info-circle', 'href' => ''],
|
||||
];
|
||||
}
|
||||
$json = wp_json_encode($items, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
$colors = [
|
||||
'c-blue' => __('透明蓝', 'aksrt'),
|
||||
'c-green' => __('透明绿', 'aksrt'),
|
||||
'c-yellow' => __('透明黄', 'aksrt'),
|
||||
'c-red' => __('透明粉红', 'aksrt'),
|
||||
'c-purple' => __('透明紫', 'aksrt'),
|
||||
'c-blue-2' => __('透明深蓝', 'aksrt'),
|
||||
'c-green-2' => __('透明墨绿', 'aksrt'),
|
||||
'c-yellow-2' => __('透明橘黄', 'aksrt'),
|
||||
'c-red-2' => __('透明红', 'aksrt'),
|
||||
'c-purple-2' => __('透明深紫', 'aksrt'),
|
||||
'b-theme' => __('主题色', 'aksrt'),
|
||||
'b-red' => __('红色', 'aksrt'),
|
||||
'b-yellow' => __('黄色', 'aksrt'),
|
||||
'b-blue' => __('蓝色', 'aksrt'),
|
||||
'b-green' => __('绿色', 'aksrt'),
|
||||
'b-purple' => __('紫色', 'aksrt'),
|
||||
];
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题(可选)', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('公告条目 JSON', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="6" name="<?php echo esc_attr($this->get_field_name('items')); ?>" placeholder='[{"title":"公告内容","icon":"fas fa-bullhorn","href":""}]'><?php echo esc_textarea((string) $json); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('切换间隔(秒)', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="2" max="15" name="<?php echo esc_attr($this->get_field_name('interval')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['interval'] ?? 4))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('主题色彩', 'aksrt'); ?>
|
||||
<select class="widefat" name="<?php echo esc_attr($this->get_field_name('color')); ?>">
|
||||
<?php foreach ($colors as $k => $label) : ?>
|
||||
<option value="<?php echo esc_attr($k); ?>" <?php selected(($instance['color'] ?? 'c-blue'), $k); ?>><?php echo esc_html($label); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('对齐', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('alignment')); ?>">
|
||||
<option value="" <?php selected(($instance['alignment'] ?? ''), ''); ?>><?php esc_html_e('靠左', 'aksrt'); ?></option>
|
||||
<option value="text-center" <?php selected(($instance['alignment'] ?? ''), 'text-center'); ?>><?php esc_html_e('居中', 'aksrt'); ?></option>
|
||||
<option value="text-right" <?php selected(($instance['alignment'] ?? ''), 'text-right'); ?>><?php esc_html_e('靠右', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('blank')); ?>" value="1" <?php checked(!empty($instance['blank'])); ?>> <?php esc_html_e('链接新窗口', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('radius')); ?>" value="1" <?php checked(!empty($instance['radius'])); ?>> <?php esc_html_e('两端圆形', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php $this->render_common_form_section($instance); ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return $this->update_common_fields($new_instance, $old_instance, [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'items' => $this->normalize_items(['items' => $new_instance['items'] ?? []]),
|
||||
'interval' => max(2, min(15, (int) ($new_instance['interval'] ?? 4))),
|
||||
'color' => sanitize_html_class((string) ($new_instance['color'] ?? 'c-blue')),
|
||||
'alignment' => sanitize_html_class((string) ($new_instance['alignment'] ?? '')),
|
||||
'blank' => !empty($new_instance['blank']) ? 1 : 0,
|
||||
'radius' => !empty($new_instance['radius']) ? 1 : 0,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
<?php
|
||||
/**
|
||||
* Recent comments mini list (zibll widget_ui_new_comment → zib_widget_comments).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class RecentCommentsWidget extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct('aksrt_recent_comments', esc_html__('AKSRT 最新评论', 'aksrt'), [
|
||||
'description' => esc_html__('显示最新评论列表(posts-mini + 身份/点赞)。', 'aksrt'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public static function render_html(array $instance): string {
|
||||
$count = max(1, min(50, (int) ($instance['count'] ?? $instance['limit'] ?? 5)));
|
||||
$show_avatar = !isset($instance['show_avatar']) || !empty($instance['show_avatar']);
|
||||
// zibll: outpost = post__not_in (multi-ID), outer = author__not_in (multi-ID).
|
||||
// Also keep optional keyword filter via outer_keywords for legacy installs.
|
||||
$outpost_raw = (string) ($instance['outpost'] ?? '');
|
||||
$outer_raw = (string) ($instance['outer'] ?? '');
|
||||
$keywords = sanitize_text_field((string) ($instance['outer_keywords'] ?? ''));
|
||||
|
||||
$post_not_in = array_values(array_filter(array_map('intval', preg_split('/[,,\s]+/', $outpost_raw) ?: [])));
|
||||
// Heuristic: if outer looks numeric/id-list → author__not_in; else treat as keyword filter (legacy).
|
||||
$author_not_in = [];
|
||||
$legacy_keys = [];
|
||||
if ($outer_raw !== '') {
|
||||
$parts = array_filter(array_map('trim', preg_split('/[,,\s]+/', $outer_raw) ?: []));
|
||||
$all_numeric = $parts && array_reduce($parts, static function ($ok, $p) {
|
||||
return $ok && ctype_digit((string) $p);
|
||||
}, true);
|
||||
if ($all_numeric) {
|
||||
$author_not_in = array_values(array_filter(array_map('intval', $parts)));
|
||||
} else {
|
||||
$legacy_keys = $parts;
|
||||
}
|
||||
}
|
||||
if ($keywords !== '') {
|
||||
$legacy_keys = array_merge($legacy_keys, array_filter(array_map('trim', preg_split('/[,,]+/', $keywords) ?: [])));
|
||||
}
|
||||
|
||||
$query = [
|
||||
'number' => $count * 2, // over-fetch when filtering keywords.
|
||||
'status' => 'approve',
|
||||
'post_status' => 'publish',
|
||||
'post_type' => ['post'],
|
||||
'orderby' => 'comment_date_gmt',
|
||||
'order' => 'DESC',
|
||||
];
|
||||
if ($post_not_in) {
|
||||
$query['post__not_in'] = $post_not_in;
|
||||
}
|
||||
if ($author_not_in) {
|
||||
$query['author__not_in'] = $author_not_in;
|
||||
}
|
||||
|
||||
$comments = get_comments($query);
|
||||
if ($legacy_keys && $comments) {
|
||||
$comments = array_values(array_filter($comments, static function ($c) use ($legacy_keys) {
|
||||
$body = (string) $c->comment_content;
|
||||
foreach ($legacy_keys as $k) {
|
||||
if ($k !== '' && str_contains($body, $k)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
$comments = array_slice($comments ?: [], 0, $count);
|
||||
|
||||
if (!$comments) {
|
||||
return function_exists('aksrt_get_ajax_null')
|
||||
? aksrt_get_ajax_null(__('暂无评论', 'aksrt'))
|
||||
: '<p class="widget-empty ajax-item">' . esc_html__('暂无评论', 'aksrt') . '</p>';
|
||||
}
|
||||
|
||||
ob_start();
|
||||
// zibll: .box-body.comment-mini-lists.zib-widget + .posts-mini rows.
|
||||
echo '<div class="box-body comment-mini-lists zib-widget ajax-item widget-recent-comments">';
|
||||
foreach ($comments as $comment) {
|
||||
$cid = (int) $comment->comment_ID;
|
||||
$user_id = (int) $comment->user_id;
|
||||
$link = get_comment_link($comment);
|
||||
$time = human_time_diff(strtotime((string) $comment->comment_date), current_time('timestamp'));
|
||||
|
||||
// Content: strip imgs/code like zibll noimg filter.
|
||||
$cont = get_comment_text($cid);
|
||||
$cont = convert_smilies($cont);
|
||||
$cont = preg_replace('/\<img(.*?)\>/i', '[' . __('图片', 'aksrt') . ']', $cont);
|
||||
$cont = preg_replace('/\[code]([\s\S]*?)\[\/code]/i', '[' . __('代码', 'aksrt') . ']', (string) $cont);
|
||||
$cont = wp_strip_all_tags((string) $cont);
|
||||
$cont = wp_trim_words($cont, 40, '…');
|
||||
|
||||
// Name with identity.
|
||||
if ($user_id > 0 && function_exists('aksrt_get_user_name')) {
|
||||
$user_name = aksrt_get_user_name([
|
||||
'id' => $user_id,
|
||||
'class' => 'inflex ac relative-h',
|
||||
'follow' => false,
|
||||
'medal' => true,
|
||||
'level' => true,
|
||||
'auth' => true,
|
||||
]);
|
||||
} elseif ($user_id > 0) {
|
||||
$home = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($user_id) : get_author_posts_url($user_id);
|
||||
$user_name = '<a href="' . esc_url($home) . '">' . esc_html(get_comment_author($comment)) . '</a>';
|
||||
} else {
|
||||
$user_name = esc_html(get_comment_author($comment));
|
||||
}
|
||||
|
||||
$avatar = '';
|
||||
if ($show_avatar) {
|
||||
$avatar_url = $user_id > 0 && function_exists('aksrt_get_avatar_url')
|
||||
? aksrt_get_avatar_url($user_id, ['size' => 42])
|
||||
: get_avatar_url($comment, ['size' => 42]);
|
||||
$ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
|
||||
$avatar = '<div class="avatar-img"><img class="avatar aksrt-lazy lazyload" data-src="' . esc_url($avatar_url) . '" src="' . esc_url($ph) . '" alt="" width="36" height="36" loading="lazy" /></div>';
|
||||
}
|
||||
|
||||
$like = function_exists('aksrt_get_comment_like_html')
|
||||
? aksrt_get_comment_like_html($comment, true)
|
||||
: '';
|
||||
|
||||
echo '<div class="posts-mini">';
|
||||
echo $avatar; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '<div class="posts-mini-con em09 ml10 flex xx jsb">';
|
||||
echo '<p class="flex jsb">';
|
||||
echo '<span class="flex1 flex">';
|
||||
echo $user_name; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '<span class="flex0 icon-spot muted-3-color" title="' . esc_attr((string) $comment->comment_date) . '">' . esc_html($time) . '</span>';
|
||||
echo '</span>';
|
||||
if ($like !== '') {
|
||||
echo '<span class="ml10 flex0">' . $like . '</span>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
echo '</p>';
|
||||
echo '<a class="muted-color text-ellipsis-5" href="' . esc_url($link) . '">' . esc_html($cont) . '</a>';
|
||||
echo '</div></div>';
|
||||
}
|
||||
echo '</div>';
|
||||
return (string) ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $args
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function widget($args, $instance): void {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? __('最新评论', 'aksrt')), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
$affix = function_exists('aksrt_widget_affix_attrs') ? aksrt_widget_affix_attrs($instance) : ['class' => '', 'attr' => ''];
|
||||
if ($affix['class'] !== '') {
|
||||
echo '<div class="is-affix"' . $affix['attr'] . '>';
|
||||
}
|
||||
echo self::render_html($instance); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
if ($affix['class'] !== '') {
|
||||
echo '</div>';
|
||||
}
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function form($instance): void {
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? __('最新评论', 'aksrt'))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('数量', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="50" name="<?php echo esc_attr($this->get_field_name('count')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['count'] ?? 5))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_avatar')); ?>" value="1" <?php checked(!isset($instance['show_avatar']) || !empty($instance['show_avatar'])); ?>> <?php esc_html_e('显示头像', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('排除文章 ID(逗号)', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('outpost')); ?>" value="<?php echo esc_attr((string) ($instance['outpost'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('排除作者 ID(逗号)', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('outer')); ?>" value="<?php echo esc_attr((string) ($instance['outer'] ?? '')); ?>">
|
||||
</label>
|
||||
<small><?php esc_html_e('填数字 ID 时按作者排除;填文字时按内容关键词排除(兼容旧配置)。', 'aksrt'); ?></small>
|
||||
</p>
|
||||
<?php $this->render_common_form_section($instance); ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $new_instance
|
||||
* @param array<string,mixed> $old_instance
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return $this->update_common_fields($new_instance, $old_instance, [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'count' => max(1, min(50, (int) ($new_instance['count'] ?? 5))),
|
||||
'show_avatar' => !empty($new_instance['show_avatar']) ? 1 : 0,
|
||||
'outpost' => sanitize_text_field((string) ($new_instance['outpost'] ?? '')),
|
||||
'outer' => sanitize_text_field((string) ($new_instance['outer'] ?? '')),
|
||||
'outer_keywords' => sanitize_text_field((string) ($new_instance['outer_keywords'] ?? '')),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
<?php
|
||||
/**
|
||||
* Search box with hot keywords + type/cat scope (zibll widget_ui_search / zib_get_search_box).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class SearchWidget extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct('aksrt_search', esc_html__('AKSRT 搜索', 'aksrt'), [
|
||||
'description' => esc_html__('搜索框:类型切换、分类范围、热门关键词与搜索历史。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_search',
|
||||
]);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
$placeholder = sanitize_text_field((string) ($instance['placeholder'] ?? __('开启精彩搜索', 'aksrt')));
|
||||
if ($placeholder === '' && function_exists('aksrt_get_search_placeholder')) {
|
||||
$placeholder = aksrt_get_search_placeholder();
|
||||
}
|
||||
$show_kw = !empty($instance['show_keywords']);
|
||||
$show_history = !empty($instance['show_history']);
|
||||
$show_types = !empty($instance['show_type']) || !isset($instance['show_type']);
|
||||
// NOTE: SearchWidget owns a business `show_type` checkbox (search-type
|
||||
// toggle) that shares the key name with the common `show_type` (device
|
||||
// visibility). To avoid an admin-form collision, this widget does NOT
|
||||
// render the shared common-form section (render_common_form_section is
|
||||
// intentionally not called). The shared affix helper is still wired in
|
||||
// widget() via aksrt_widget_affix_attrs(), and update_common_fields()
|
||||
// persists common keys — but note the shared show_type cannot be set
|
||||
// here, so display_callback treats it as 'all' (always visible).
|
||||
$show_cat = !empty($instance['show_input_cat']);
|
||||
$kw_title = sanitize_text_field((string) ($instance['keywords_title'] ?? __('热门搜索', 'aksrt')));
|
||||
$more_but = sanitize_text_field((string) ($instance['more_but'] ?? ''));
|
||||
$more_url = esc_url_raw(trim((string) ($instance['more_but_url'] ?? '')));
|
||||
// Affix helper (shared layer sidebar_affix).
|
||||
$affix = function_exists('aksrt_widget_affix_attrs') ? aksrt_widget_affix_attrs($instance) : ['class' => '', 'attr' => ''];
|
||||
|
||||
$state = function_exists('aksrt_get_search_form_state')
|
||||
? aksrt_get_search_form_state()
|
||||
: [
|
||||
'type' => 'post',
|
||||
'types' => ['post' => __('文章', 'aksrt')],
|
||||
'category_id' => 0,
|
||||
'category_enabled' => false,
|
||||
'categories' => [],
|
||||
];
|
||||
|
||||
$type_options = is_array($state['types'] ?? null) ? $state['types'] : ['post' => __('文章', 'aksrt')];
|
||||
$in_type = sanitize_key((string) ($instance['in_type'] ?? $state['type'] ?? 'post'));
|
||||
if (!isset($type_options[$in_type])) {
|
||||
$in_type = array_key_first($type_options) ?: 'post';
|
||||
}
|
||||
|
||||
// Category scope.
|
||||
$in_cat = max(0, (int) ($instance['in_cat'] ?? 0));
|
||||
if ($in_cat <= 0) {
|
||||
$in_cat = (int) ($state['category_id'] ?? 0);
|
||||
}
|
||||
$cat_enabled = $show_cat && ($state['category_enabled'] || !empty($instance['show_input_cat']));
|
||||
$categories = [];
|
||||
if ($cat_enabled) {
|
||||
$more_raw = trim((string) ($instance['more_cats'] ?? ''));
|
||||
if ($more_raw !== '') {
|
||||
$ids = array_values(array_filter(array_map('intval', preg_split('/[,,\s]+/', $more_raw) ?: [])));
|
||||
if ($ids) {
|
||||
$got = get_categories([
|
||||
'taxonomy' => 'category',
|
||||
'include' => $ids,
|
||||
'hide_empty' => false,
|
||||
'orderby' => 'include',
|
||||
]);
|
||||
$categories = is_array($got) ? $got : [];
|
||||
}
|
||||
}
|
||||
if (!$categories) {
|
||||
$categories = is_array($state['categories'] ?? null) ? $state['categories'] : [];
|
||||
}
|
||||
if (!$categories && function_exists('aksrt_get_search_categories')) {
|
||||
$categories = aksrt_get_search_categories();
|
||||
}
|
||||
}
|
||||
|
||||
// Only show type UI when more than one type is configured, unless forced.
|
||||
if ($show_types && count($type_options) < 2 && empty($instance['force_show_type'])) {
|
||||
// Still keep hidden type field for default.
|
||||
$show_types_ui = false;
|
||||
} else {
|
||||
$show_types_ui = $show_types && count($type_options) >= 1;
|
||||
}
|
||||
|
||||
$keywords = [];
|
||||
if ($show_kw) {
|
||||
if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'hot_search_keywords')) {
|
||||
$keywords = \AKSRT\Settings\Settings::hot_search_keywords();
|
||||
} elseif (function_exists('aksrt_get_search_hot_keywords')) {
|
||||
$keywords = aksrt_get_search_hot_keywords();
|
||||
} elseif (function_exists('aksrt_get_setting')) {
|
||||
$raw = (string) aksrt_get_setting('hot_search_keywords', '');
|
||||
$keywords = array_values(array_filter(array_map('trim', preg_split('/[\r\n,,]+/', $raw) ?: [])));
|
||||
}
|
||||
$custom = trim((string) ($instance['keywords'] ?? ''));
|
||||
if ($custom !== '') {
|
||||
$keywords = array_values(array_filter(array_map('trim', preg_split('/[\r\n,,]+/', $custom) ?: [])));
|
||||
}
|
||||
$keywords = array_slice(is_array($keywords) ? $keywords : [], 0, 12);
|
||||
}
|
||||
|
||||
$uid = 'aksrt-sw-' . esc_attr($this->id);
|
||||
?>
|
||||
<div class="widget-search-box aksrt-search-widget search-input<?php echo $affix['class']; ?>" id="<?php echo $uid; ?>"<?php echo $affix['attr']; ?>>
|
||||
<form
|
||||
role="search"
|
||||
method="get"
|
||||
class="aksrt-search-widget__form search-form"
|
||||
action="<?php echo esc_url(home_url('/')); ?>"
|
||||
data-aksrt-search-form="widget"
|
||||
data-default-search-type="<?php echo esc_attr($in_type); ?>"
|
||||
data-default-category="<?php echo esc_attr((string) $in_cat); ?>"
|
||||
>
|
||||
<?php if ($show_types_ui) : ?>
|
||||
<div class="aksrt-search-widget__types" role="tablist" aria-label="<?php esc_attr_e('搜索类型', 'aksrt'); ?>">
|
||||
<?php foreach ($type_options as $type_key => $type_label) : ?>
|
||||
<button
|
||||
type="button"
|
||||
class="aksrt-search-widget__type<?php echo $in_type === $type_key ? ' is-active' : ''; ?>"
|
||||
data-search-type="<?php echo esc_attr((string) $type_key); ?>"
|
||||
><?php echo esc_html((string) $type_label); ?></button>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="aksrt-search-widget__line line-form">
|
||||
<?php if ($cat_enabled && !empty($categories)) : ?>
|
||||
<div class="aksrt-search-widget__cat option-dropdown">
|
||||
<label class="screen-reader-text" for="<?php echo $uid; ?>-cat"><?php esc_html_e('分类', 'aksrt'); ?></label>
|
||||
<select
|
||||
id="<?php echo $uid; ?>-cat"
|
||||
class="aksrt-search-widget__cat-select"
|
||||
data-search-category-select
|
||||
<?php disabled($in_type !== 'post'); ?>
|
||||
>
|
||||
<option value="0"><?php esc_html_e('全部分类', 'aksrt'); ?></option>
|
||||
<?php foreach ($categories as $term) :
|
||||
if (!$term instanceof \WP_Term) {
|
||||
continue;
|
||||
}
|
||||
?>
|
||||
<option value="<?php echo esc_attr((string) $term->term_id); ?>" <?php selected($in_cat, (int) $term->term_id); ?>>
|
||||
<?php echo esc_html($term->name); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="aksrt-search-widget__field search-input-text">
|
||||
<label class="screen-reader-text" for="<?php echo $uid; ?>-s"><?php esc_html_e('搜索关键词', 'aksrt'); ?></label>
|
||||
<input
|
||||
id="<?php echo $uid; ?>-s"
|
||||
type="search"
|
||||
name="s"
|
||||
class="widget-search-box__input line-form-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" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="aksrt_search_type" value="<?php echo esc_attr($in_type); ?>">
|
||||
<?php if ($cat_enabled) : ?>
|
||||
<input type="hidden" name="aksrt_category" value="<?php echo esc_attr($in_type === 'post' && $in_cat > 0 ? (string) $in_cat : ''); ?>">
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
|
||||
<?php if ($show_history) : ?>
|
||||
<div class="aksrt-search-widget__history" data-aksrt-search-history>
|
||||
<div class="aksrt-search-widget__kw-head">
|
||||
<div class="aksrt-search-widget__kw-title"><?php esc_html_e('搜索历史', 'aksrt'); ?></div>
|
||||
<button type="button" class="aksrt-search-widget__history-clear" data-aksrt-search-history-clear hidden><?php esc_html_e('清空', 'aksrt'); ?></button>
|
||||
</div>
|
||||
<div class="aksrt-search-widget__history-list" data-aksrt-search-history-list></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($keywords) : ?>
|
||||
<div class="aksrt-search-widget__keywords">
|
||||
<?php if ($kw_title !== '') : ?>
|
||||
<div class="aksrt-search-widget__kw-title"><?php echo esc_html($kw_title); ?></div>
|
||||
<?php endif; ?>
|
||||
<div class="aksrt-search-widget__kw-list">
|
||||
<?php foreach ($keywords as $kw) :
|
||||
$kw = is_array($kw) ? (string) ($kw['keyword'] ?? $kw['text'] ?? '') : (string) $kw;
|
||||
if ($kw === '') {
|
||||
continue;
|
||||
}
|
||||
$url = function_exists('aksrt_build_search_url')
|
||||
? aksrt_build_search_url($kw, ['aksrt_search_type' => $in_type])
|
||||
: add_query_arg(['s' => $kw, 'aksrt_search_type' => $in_type], home_url('/'));
|
||||
?>
|
||||
<a class="aksrt-search-widget__kw" href="<?php echo esc_url($url); ?>"><?php echo esc_html($kw); ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($more_but !== '' && $more_url !== '') : ?>
|
||||
<div class="aksrt-search-widget__more">
|
||||
<a href="<?php echo esc_url($more_url); ?>"><?php echo esc_html($more_but); ?></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$type_options = function_exists('aksrt_get_search_type_options')
|
||||
? aksrt_get_search_type_options()
|
||||
: ['post' => __('文章', 'aksrt')];
|
||||
$in_type = sanitize_key((string) ($instance['in_type'] ?? 'post'));
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题(可选)', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('占位文本', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('placeholder')); ?>" value="<?php echo esc_attr((string) ($instance['placeholder'] ?? __('开启精彩搜索', 'aksrt'))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_type')); ?>" value="1" <?php checked(!empty($instance['show_type']) || !isset($instance['show_type'])); ?>> <?php esc_html_e('显示类型切换(≥2 种时)', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_input_cat')); ?>" value="1" <?php checked(!empty($instance['show_input_cat'])); ?>> <?php esc_html_e('显示分类筛选', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_keywords')); ?>" value="1" <?php checked(!empty($instance['show_keywords'])); ?>> <?php esc_html_e('显示热门关键词', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_history')); ?>" value="1" <?php checked(!empty($instance['show_history'])); ?>> <?php esc_html_e('显示搜索历史', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('默认类型', 'aksrt'); ?>
|
||||
<select class="widefat" name="<?php echo esc_attr($this->get_field_name('in_type')); ?>">
|
||||
<?php foreach ($type_options as $k => $label) : ?>
|
||||
<option value="<?php echo esc_attr($k); ?>" <?php selected($in_type, $k); ?>><?php echo esc_html($label); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('默认分类 ID(0=全部)', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('in_cat')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['in_cat'] ?? 0))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('可选分类 ID(逗号;空=后台/全部分类)', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('more_cats')); ?>" value="<?php echo esc_attr((string) ($instance['more_cats'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('关键词标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('keywords_title')); ?>" value="<?php echo esc_attr((string) ($instance['keywords_title'] ?? __('热门搜索', 'aksrt'))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('自定义关键词(逗号/换行;空=后台配置)', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="2" name="<?php echo esc_attr($this->get_field_name('keywords')); ?>"><?php echo esc_textarea((string) ($instance['keywords'] ?? '')); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('更多按钮文案', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('more_but')); ?>" value="<?php echo esc_attr((string) ($instance['more_but'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('更多按钮链接', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('more_but_url')); ?>" value="<?php echo esc_attr((string) ($instance['more_but_url'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<?php // Common-form section intentionally skipped: SearchWidget owns a
|
||||
// business `show_type` checkbox (search-type toggle) that collides
|
||||
// with the shared common `show_type` (device visibility). The
|
||||
// shared affix/display/animation/layout fields are therefore not
|
||||
// rendered here; update_common_fields() below still persists any
|
||||
// common keys submitted through other surfaces, and the affix
|
||||
// helper is wired in widget() via aksrt_widget_affix_attrs(). ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return $this->update_common_fields($new_instance, $old_instance, [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'placeholder' => sanitize_text_field((string) ($new_instance['placeholder'] ?? '')),
|
||||
'show_keywords' => !empty($new_instance['show_keywords']) ? 1 : 0,
|
||||
'show_history' => !empty($new_instance['show_history']) ? 1 : 0,
|
||||
'show_type' => !empty($new_instance['show_type']) ? 1 : 0,
|
||||
'show_input_cat' => !empty($new_instance['show_input_cat']) ? 1 : 0,
|
||||
'in_type' => sanitize_key((string) ($new_instance['in_type'] ?? 'post')),
|
||||
'in_cat' => max(0, (int) ($new_instance['in_cat'] ?? 0)),
|
||||
'more_cats' => sanitize_text_field((string) ($new_instance['more_cats'] ?? '')),
|
||||
'keywords_title' => sanitize_text_field((string) ($new_instance['keywords_title'] ?? '')),
|
||||
'keywords' => sanitize_textarea_field((string) ($new_instance['keywords'] ?? '')),
|
||||
'more_but' => sanitize_text_field((string) ($new_instance['more_but'] ?? '')),
|
||||
'more_but_url' => esc_url_raw(trim((string) ($new_instance['more_but_url'] ?? ''))),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,401 @@
|
||||
<?php
|
||||
/**
|
||||
* Multi-tab posts (zibll zib_widget_ui_tab_post + lazy REST).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class TabPostsWidget extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_tab_posts',
|
||||
esc_html__('AKSRT 多栏目文章', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('Tab 切换显示不同分类/排序的文章(懒加载)。', 'aksrt'),
|
||||
'customize_selective_refresh' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $tabs
|
||||
* @return list<array{title:string,category_id:int,orderby:string,count:int,enabled:bool}>
|
||||
*/
|
||||
public static function normalize_tabs_static($tabs): array {
|
||||
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;
|
||||
}
|
||||
if (is_array($tabs)) {
|
||||
$allowed = ['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, true)) {
|
||||
$orderby = 'date';
|
||||
}
|
||||
$result[] = [
|
||||
'title' => sanitize_text_field((string) ($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 [];
|
||||
}
|
||||
|
||||
private function normalize_tabs($tabs): array {
|
||||
return self::normalize_tabs_static($tabs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build query for a tab → reuse main posts payload renderer.
|
||||
*
|
||||
* @param array $tab
|
||||
* @param int $default_count
|
||||
* @return array
|
||||
*/
|
||||
public static function tab_to_main_instance(array $tab, int $default_count, array $opts = []): array {
|
||||
$count = !empty($tab['count']) ? (int) $tab['count'] : $default_count;
|
||||
$style = sanitize_key((string) ($opts['style'] ?? $tab['style'] ?? 'mini'));
|
||||
if (!in_array($style, ['list', 'card', 'mini'], true)) {
|
||||
$style = 'mini';
|
||||
}
|
||||
$paginate = sanitize_key((string) ($opts['paginate'] ?? $tab['paginate'] ?? 'none'));
|
||||
if (!in_array($paginate, ['none', 'ajax', 'number'], true)) {
|
||||
$paginate = 'none';
|
||||
}
|
||||
return [
|
||||
'count' => max(1, min(30, $count)),
|
||||
'orderby' => sanitize_key((string) ($tab['orderby'] ?? 'date')),
|
||||
'order' => strtoupper(sanitize_key((string) ($tab['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
|
||||
'cat' => absint($tab['category_id'] ?? 0),
|
||||
'topics' => sanitize_text_field((string) ($tab['topics'] ?? '')),
|
||||
'limit_day' => max(0, (int) ($tab['limit_day'] ?? 0)),
|
||||
'pay_type' => sanitize_key((string) ($tab['pay_type'] ?? '')),
|
||||
'style' => $style,
|
||||
'show_thumb' => !isset($opts['show_thumb']) ? 1 : (int) $opts['show_thumb'],
|
||||
'show_meta' => !isset($opts['show_meta']) ? 1 : (int) $opts['show_meta'],
|
||||
'show_number' => !empty($opts['show_number']) ? 1 : 0,
|
||||
'paginate' => $paginate,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* REST helper: render one tab list HTML.
|
||||
*
|
||||
* @param array<string,mixed> $opts
|
||||
*/
|
||||
public static function render_tab_html(array $tab, int $default_count, array $opts = []): string {
|
||||
$payload = self::render_tab_payload($tab, $default_count, $opts, 1);
|
||||
return (string) ($payload['html'] ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Full tab payload (html + pager) for REST/ajax.
|
||||
*
|
||||
* @param array<string,mixed> $opts
|
||||
* @return array{html:string,paginate_html:string,has_more:bool,page:int,max_pages:int,total:int}
|
||||
*/
|
||||
public static function render_tab_payload(array $tab, int $default_count, array $opts = [], int $page = 1): array {
|
||||
$inst = self::tab_to_main_instance($tab, $default_count, $opts);
|
||||
if (class_exists('\AKSRT\Widgets\WidgetMainPosts')) {
|
||||
return WidgetMainPosts::query_payload($inst, $page);
|
||||
}
|
||||
return [
|
||||
'html' => '',
|
||||
'paginate_html' => '',
|
||||
'has_more' => false,
|
||||
'page' => 1,
|
||||
'max_pages' => 1,
|
||||
'total' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
/** Skeleton rows for lazy tab panels. */
|
||||
public static function skeleton_html(int $count = 5, string $style = 'mini'): string {
|
||||
$count = max(1, min(12, $count));
|
||||
ob_start();
|
||||
echo '<div class="widget-tab-posts__skeleton posts-mini-lists style-' . esc_attr($style) . '">';
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
echo '<div class="posts-mini placeholder-row">';
|
||||
echo '<div class="placeholder radius thumb-placeholder"></div>';
|
||||
echo '<div class="flex1"><div class="placeholder k1"></div><div class="placeholder s1 mt6"></div></div>';
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
return (string) ob_get_clean();
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
$count = absint($instance['count'] ?? 5);
|
||||
$style = sanitize_key((string) ($instance['style'] ?? 'mini'));
|
||||
if (!in_array($style, ['list', 'card', 'mini'], true)) {
|
||||
$style = 'mini';
|
||||
}
|
||||
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'none'));
|
||||
if (!in_array($paginate, ['none', 'ajax', 'number'], true)) {
|
||||
$paginate = 'none';
|
||||
}
|
||||
$tabs = $this->normalize_tabs($instance['tabs'] ?? 'latest,comments,random');
|
||||
$tabs = array_values(array_filter($tabs, static fn($t) => !empty($t['enabled'])));
|
||||
if (!$tabs) {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('请至少启用一个 Tab', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<p class="widget-empty">' . esc_html__('请至少启用一个 Tab', 'aksrt') . '</p>';
|
||||
}
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
return;
|
||||
}
|
||||
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
$widget_id = $this->id;
|
||||
$rest = rest_url('aksrt/v1/widgets/tab-posts');
|
||||
$use_shared = function_exists('aksrt_render_mini_list');
|
||||
?>
|
||||
<div class="widget-tab-posts" id="<?php echo esc_attr($widget_id); ?>-tabs"
|
||||
data-aksrt-tab-posts
|
||||
data-rest="<?php echo esc_url($rest); ?>"
|
||||
data-count="<?php echo esc_attr((string) $count); ?>"
|
||||
data-style="<?php echo esc_attr($style); ?>"
|
||||
data-paginate="<?php echo esc_attr($paginate); ?>">
|
||||
<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'] : sprintf(__('Tab %d', 'aksrt'), $i + 1);
|
||||
?>
|
||||
<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); ?>"
|
||||
data-index="<?php echo (int) $i; ?>"
|
||||
data-orderby="<?php echo esc_attr($tab['orderby']); ?>"
|
||||
data-cat="<?php echo (int) $tab['category_id']; ?>"
|
||||
data-tab-count="<?php echo (int) $tab['count']; ?>"
|
||||
data-topics="<?php echo esc_attr((string) ($tab['topics'] ?? '')); ?>"
|
||||
data-limit-day="<?php echo esc_attr((string) ((int) ($tab['limit_day'] ?? 0))); ?>"
|
||||
data-pay-type="<?php echo esc_attr((string) ($tab['pay_type'] ?? '')); ?>"
|
||||
aria-selected="<?php echo $first ? 'true' : 'false'; ?>"
|
||||
><?php echo esc_html($tab_title); ?></button>
|
||||
</li>
|
||||
<?php $first = false; endforeach; ?>
|
||||
</ul>
|
||||
|
||||
<?php $first = true; foreach ($tabs as $i => $tab) :
|
||||
$tab_key = 'tab-' . $i;
|
||||
?>
|
||||
<div class="widget-tab-posts__panel<?php echo $first ? ' active' : ''; ?>"
|
||||
data-tab-panel="<?php echo esc_attr($tab_key); ?>"
|
||||
data-loaded="<?php echo $first ? '1' : '0'; ?>"
|
||||
role="tabpanel"<?php echo $first ? '' : ' hidden'; ?>>
|
||||
<?php if ($first) :
|
||||
$opts = ['style' => $style, 'paginate' => $paginate];
|
||||
$payload = self::render_tab_payload($tab, $count, $opts, 1);
|
||||
$html = (string) ($payload['html'] ?? '');
|
||||
if ($html !== '') {
|
||||
echo '<div class="widget-tab-posts__list widget-tab-posts__list--' . esc_attr($style) . ' aksrt-main-posts style-' . esc_attr($style) . '">' . $html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
if (!empty($payload['paginate_html'])) {
|
||||
echo $payload['paginate_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
} else {
|
||||
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
|
||||
}
|
||||
else : ?>
|
||||
<div class="post_ajax_loader" data-aksrt-ajax-loader><?php echo self::skeleton_html($count, $style); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php $first = false; endforeach; ?>
|
||||
</div>
|
||||
<?php
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
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 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((string) $title); ?>">
|
||||
</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="20" value="<?php echo (int) $count; ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('列表样式', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('style')); ?>">
|
||||
<option value="mini" <?php selected(($instance['style'] ?? 'mini'), 'mini'); ?>><?php esc_html_e('迷你', 'aksrt'); ?></option>
|
||||
<option value="list" <?php selected(($instance['style'] ?? ''), 'list'); ?>><?php esc_html_e('列表', 'aksrt'); ?></option>
|
||||
<option value="card" <?php selected(($instance['style'] ?? ''), 'card'); ?>><?php esc_html_e('卡片', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('分页', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('paginate')); ?>">
|
||||
<option value="none" <?php selected(($instance['paginate'] ?? 'none'), 'none'); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
|
||||
<option value="ajax" <?php selected(($instance['paginate'] ?? ''), 'ajax'); ?>><?php esc_html_e('AJAX 加载更多', 'aksrt'); ?></option>
|
||||
<option value="number" <?php selected(($instance['paginate'] ?? ''), 'number'); ?>><?php esc_html_e('数字分页', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<hr style="margin:12px 0;">
|
||||
<p style="font-weight:600;margin-bottom:8px;"><?php esc_html_e('Tab 配置', 'aksrt'); ?></p>
|
||||
<?php
|
||||
$max_tabs = 8;
|
||||
$tabs = array_slice($tabs, 0, $max_tabs);
|
||||
while (count($tabs) < 3) {
|
||||
$tabs[] = ['title' => '', 'category_id' => 0, 'orderby' => 'date', 'count' => 0, 'topics' => '', 'limit_day' => 0, 'pay_type' => '', '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 esc_attr($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 esc_attr($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 esc_attr($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 esc_attr($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 esc_attr($this->get_field_name("tabs[{$i}][count]")); ?>" value="<?php echo (int) $tab['count']; ?>">
|
||||
</p>
|
||||
<p style="margin:0 0 6px;">
|
||||
<label><?php esc_html_e('专题 topics ID(逗号):', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name("tabs[{$i}][topics]")); ?>" value="<?php echo esc_attr((string) ($tab['topics'] ?? '')); ?>">
|
||||
</p>
|
||||
<p style="margin:0 0 6px;">
|
||||
<label><?php esc_html_e('最近 N 天(0=不限):', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name("tabs[{$i}][limit_day]")); ?>" value="<?php echo (int) ($tab['limit_day'] ?? 0); ?>">
|
||||
</p>
|
||||
<p style="margin:0;">
|
||||
<label><?php esc_html_e('付费类型:', 'aksrt'); ?></label>
|
||||
<select class="widefat" name="<?php echo esc_attr($this->get_field_name("tabs[{$i}][pay_type]")); ?>">
|
||||
<option value="" <?php selected(($tab['pay_type'] ?? ''), ''); ?>><?php esc_html_e('不限', 'aksrt'); ?></option>
|
||||
<option value="paid" <?php selected(($tab['pay_type'] ?? ''), 'paid'); ?>><?php esc_html_e('任意付费', 'aksrt'); ?></option>
|
||||
<option value="reading" <?php selected(($tab['pay_type'] ?? ''), 'reading'); ?>><?php esc_html_e('付费阅读', 'aksrt'); ?></option>
|
||||
<option value="download" <?php selected(($tab['pay_type'] ?? ''), 'download'); ?>><?php esc_html_e('付费下载', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
</div>
|
||||
<?php endforeach;
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$style = sanitize_key((string) ($new_instance['style'] ?? 'mini'));
|
||||
if (!in_array($style, ['list', 'card', 'mini'], true)) {
|
||||
$style = 'mini';
|
||||
}
|
||||
$paginate = sanitize_key((string) ($new_instance['paginate'] ?? 'none'));
|
||||
if (!in_array($paginate, ['none', 'ajax', 'number'], true)) {
|
||||
$paginate = 'none';
|
||||
}
|
||||
$result = [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'count' => max(1, min(20, (int) ($new_instance['count'] ?? 5))),
|
||||
'style' => $style,
|
||||
'paginate' => $paginate,
|
||||
];
|
||||
$tabs = [];
|
||||
if (isset($new_instance['tabs']) && is_array($new_instance['tabs'])) {
|
||||
$allowed = ['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, true)) {
|
||||
$orderby = 'date';
|
||||
}
|
||||
$tabs[] = [
|
||||
'title' => sanitize_text_field((string) ($tab['title'] ?? '')),
|
||||
'category_id' => absint($tab['category_id'] ?? 0),
|
||||
'orderby' => $orderby,
|
||||
'order' => strtoupper(sanitize_key((string) ($tab['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
|
||||
'count' => max(0, min(20, (int) ($tab['count'] ?? 0))),
|
||||
'topics' => sanitize_text_field((string) ($tab['topics'] ?? '')),
|
||||
'limit_day' => max(0, (int) ($tab['limit_day'] ?? 0)),
|
||||
'pay_type' => sanitize_key((string) ($tab['pay_type'] ?? '')),
|
||||
'enabled' => !empty($tab['enabled']),
|
||||
];
|
||||
}
|
||||
}
|
||||
$result['tabs'] = !empty($tabs) ? $tabs : 'latest,comments,random';
|
||||
return $this->update_common_fields($new_instance, $old_instance, $result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
/**
|
||||
* Tag cloud (zibll widget_ui_tag_cloud lite).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class TagCloudWidget extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_tag_cloud',
|
||||
esc_html__('AKSRT 标签云', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('展示文章标签/分类云', 'aksrt'),
|
||||
'customize_selective_refresh' => true,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public static function render_html(array $instance): string {
|
||||
$taxonomy = sanitize_key((string) ($instance['taxonomy'] ?? 'post_tag'));
|
||||
if (!taxonomy_exists($taxonomy)) {
|
||||
$taxonomy = 'post_tag';
|
||||
}
|
||||
$number = max(1, min(100, (int) ($instance['count'] ?? $instance['number'] ?? 20)));
|
||||
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'name'));
|
||||
// Zibll widget_ui_tag_cloud: name / count / term_id(创建时间).
|
||||
if (!in_array($orderby, ['count', 'name', 'term_id', 'id'], true)) {
|
||||
$orderby = 'name';
|
||||
}
|
||||
// Zibll `order` ASC/DESC (default ASC).
|
||||
$order = strtoupper((string) ($instance['order'] ?? 'ASC')) === 'DESC' ? 'DESC' : 'ASC';
|
||||
$show_count = !empty($instance['show_count']);
|
||||
$blank = !empty($instance['blank']);
|
||||
$color = sanitize_html_class((string) ($instance['color'] ?? 'rand'));
|
||||
$fixed_width = !empty($instance['fixed_width']);
|
||||
|
||||
$wp_orderby = $orderby === 'id' ? 'term_id' : $orderby;
|
||||
$tags = get_terms([
|
||||
'taxonomy' => $taxonomy,
|
||||
'orderby' => $wp_orderby,
|
||||
'order' => $order,
|
||||
'number' => $number,
|
||||
'hide_empty' => !empty($instance['hide_empty']),
|
||||
]);
|
||||
if (is_wp_error($tags) || !$tags) {
|
||||
return function_exists('aksrt_get_ajax_null')
|
||||
? aksrt_get_ajax_null(__('暂无标签', 'aksrt'))
|
||||
: '<p class="widget-empty ajax-item">' . esc_html__('暂无标签', 'aksrt') . '</p>';
|
||||
}
|
||||
|
||||
$palette = ['c-blue', 'c-yellow', 'c-green', 'c-purple', 'c-red', '', 'c-blue-2', 'c-yellow-2', 'c-green-2', 'c-purple-2', 'c-red-2', ''];
|
||||
$i = random_int(0, 10);
|
||||
$scale = !empty($instance['scale']);
|
||||
$min_c = null;
|
||||
$max_c = null;
|
||||
if ($scale) {
|
||||
foreach ($tags as $t0) {
|
||||
$c0 = (int) $t0->count;
|
||||
$min_c = $min_c === null ? $c0 : min($min_c, $c0);
|
||||
$max_c = $max_c === null ? $c0 : max($max_c, $c0);
|
||||
}
|
||||
}
|
||||
$wrap = 'zib-widget widget-tag-cloud tag-cloud aksrt-tag-cloud author-tag ajax-item' . ($fixed_width ? ' fixed-width' : '');
|
||||
ob_start();
|
||||
echo '<div class="' . esc_attr($wrap) . '">';
|
||||
foreach ($tags as $tag) {
|
||||
$url = get_term_link($tag);
|
||||
if (is_wp_error($url)) {
|
||||
continue;
|
||||
}
|
||||
$cls = 'but';
|
||||
if ($color === 'rand') {
|
||||
if ($i > 10) {
|
||||
$i = 0;
|
||||
}
|
||||
$cls .= ' ' . $palette[$i];
|
||||
$i++;
|
||||
} elseif ($color !== '') {
|
||||
$cls .= ' ' . $color;
|
||||
}
|
||||
$tgt = $blank ? ' target="_blank" rel="noopener noreferrer"' : '';
|
||||
$count_html = $show_count ? '<span class="em09 tag-count"> (' . (int) $tag->count . ')</span>' : '';
|
||||
$style_attr = '';
|
||||
if ($scale && $max_c !== null && $min_c !== null) {
|
||||
$span = max(1, $max_c - $min_c);
|
||||
$ratio = ((int) $tag->count - $min_c) / $span;
|
||||
$em = round(0.85 + ($ratio * 0.4), 3);
|
||||
$style_attr = ' style="font-size:' . esc_attr((string) $em) . 'em;--tag-scale:' . esc_attr((string) $em) . ';"';
|
||||
}
|
||||
echo '<a href="' . esc_url($url) . '" class="text-ellipsis ' . esc_attr(trim($cls)) . '"' . $tgt . $style_attr . '>' . esc_html($tag->name) . $count_html . '</a>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
echo '</div>';
|
||||
return (string) ob_get_clean();
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
$affix = function_exists('aksrt_widget_affix_attrs') ? aksrt_widget_affix_attrs($instance) : ['class' => '', 'attr' => ''];
|
||||
if ($affix['class'] !== '') {
|
||||
echo '<div class="is-affix"' . $affix['attr'] . '>';
|
||||
}
|
||||
echo self::render_html($instance); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
if ($affix['class'] !== '') {
|
||||
echo '</div>';
|
||||
}
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$taxs = get_taxonomies(['public' => true], 'objects');
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题:', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? __('标签云', 'aksrt'))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('分类法', 'aksrt'); ?>
|
||||
<select class="widefat" name="<?php echo esc_attr($this->get_field_name('taxonomy')); ?>">
|
||||
<?php foreach ($taxs as $tax) : ?>
|
||||
<option value="<?php echo esc_attr($tax->name); ?>" <?php selected(($instance['taxonomy'] ?? 'post_tag'), $tax->name); ?>><?php echo esc_html($tax->labels->name); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('标签数量:', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="100" name="<?php echo esc_attr($this->get_field_name('count')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['count'] ?? 20))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('排序', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
|
||||
<option value="name" <?php selected(($instance['orderby'] ?? 'name'), 'name'); ?>><?php esc_html_e('名称', 'aksrt'); ?></option>
|
||||
<option value="count" <?php selected(($instance['orderby'] ?? ''), 'count'); ?>><?php esc_html_e('文章数量', 'aksrt'); ?></option>
|
||||
<option value="term_id" <?php selected(($instance['orderby'] ?? ''), 'term_id'); ?>><?php esc_html_e('创建时间', 'aksrt'); ?></option>
|
||||
</select>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('order')); ?>">
|
||||
<option value="ASC" <?php selected(($instance['order'] ?? 'ASC'), 'ASC'); ?>><?php esc_html_e('升序', 'aksrt'); ?></option>
|
||||
<option value="DESC" <?php selected(($instance['order'] ?? ''), 'DESC'); ?>><?php esc_html_e('降序', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('颜色', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('color')); ?>">
|
||||
<option value="rand" <?php selected(($instance['color'] ?? 'rand'), 'rand'); ?>><?php esc_html_e('随机颜色', 'aksrt'); ?></option>
|
||||
<option value="c-hui" <?php selected(($instance['color'] ?? ''), 'c-hui'); ?>><?php esc_html_e('灰色', 'aksrt'); ?></option>
|
||||
<option value="c-blue" <?php selected(($instance['color'] ?? ''), 'c-blue'); ?>><?php esc_html_e('蓝色', 'aksrt'); ?></option>
|
||||
<option value="c-blue-2" <?php selected(($instance['color'] ?? ''), 'c-blue-2'); ?>><?php esc_html_e('深蓝色', 'aksrt'); ?></option>
|
||||
<option value="c-cyan" <?php selected(($instance['color'] ?? ''), 'c-cyan'); ?>><?php esc_html_e('青色', 'aksrt'); ?></option>
|
||||
<option value="c-yellow" <?php selected(($instance['color'] ?? ''), 'c-yellow'); ?>><?php esc_html_e('黄色', 'aksrt'); ?></option>
|
||||
<option value="c-yellow-2" <?php selected(($instance['color'] ?? ''), 'c-yellow-2'); ?>><?php esc_html_e('橙黄色', 'aksrt'); ?></option>
|
||||
<option value="c-green" <?php selected(($instance['color'] ?? ''), 'c-green'); ?>><?php esc_html_e('绿色', 'aksrt'); ?></option>
|
||||
<option value="c-green-2" <?php selected(($instance['color'] ?? ''), 'c-green-2'); ?>><?php esc_html_e('墨绿色', 'aksrt'); ?></option>
|
||||
<option value="c-purple" <?php selected(($instance['color'] ?? ''), 'c-purple'); ?>><?php esc_html_e('紫色', 'aksrt'); ?></option>
|
||||
<option value="c-purple-2" <?php selected(($instance['color'] ?? ''), 'c-purple-2'); ?>><?php esc_html_e('深紫色', 'aksrt'); ?></option>
|
||||
<option value="c-red" <?php selected(($instance['color'] ?? ''), 'c-red'); ?>><?php esc_html_e('粉红色', 'aksrt'); ?></option>
|
||||
<option value="c-red-2" <?php selected(($instance['color'] ?? ''), 'c-red-2'); ?>><?php esc_html_e('红色', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_count')); ?>" value="1" <?php checked(!empty($instance['show_count'])); ?>> <?php esc_html_e('显示数量', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('blank')); ?>" value="1" <?php checked(!empty($instance['blank'])); ?>> <?php esc_html_e('新窗口', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('fixed_width')); ?>" value="1" <?php checked(!empty($instance['fixed_width'])); ?>> <?php esc_html_e('固定宽度', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hide_empty')); ?>" value="1" <?php checked(!empty($instance['hide_empty'])); ?>> <?php esc_html_e('隐藏空标签', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('scale')); ?>" value="1" <?php checked(!empty($instance['scale'])); ?>> <?php esc_html_e('按数量缩放字号', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php $this->render_common_form_section($instance); ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return $this->update_common_fields($new_instance, $old_instance, [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'taxonomy' => sanitize_key((string) ($new_instance['taxonomy'] ?? 'post_tag')),
|
||||
'count' => max(1, min(100, (int) ($new_instance['count'] ?? 20))),
|
||||
'orderby' => in_array(($new_instance['orderby'] ?? ''), ['count', 'name', 'term_id', 'id'], true) ? $new_instance['orderby'] : 'name',
|
||||
'order' => strtoupper((string) ($new_instance['order'] ?? 'ASC')) === 'DESC' ? 'DESC' : 'ASC',
|
||||
'color' => sanitize_html_class((string) ($new_instance['color'] ?? 'rand')),
|
||||
'show_count' => !empty($new_instance['show_count']) ? 1 : 0,
|
||||
'blank' => !empty($new_instance['blank']) ? 1 : 0,
|
||||
'fixed_width' => !empty($new_instance['fixed_width']) ? 1 : 0,
|
||||
'hide_empty' => !empty($new_instance['hide_empty']) ? 1 : 0,
|
||||
'scale' => !empty($new_instance['scale']) ? 1 : 0,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* CommonWidgetFields trait — single seat-belt for shared widget form/update.
|
||||
*
|
||||
* Every AKSRT widget `use`s this trait so the Zibll common-field family
|
||||
* (title block, display rules, affix, animation, layout bg) is declared and
|
||||
* persisted in exactly one place: inc/modules/widget-common.php.
|
||||
*
|
||||
* Widgets keep only their business fields; the trait wires the shared section
|
||||
* into form() and folds the shared keys into update().
|
||||
*
|
||||
* @package AKSRT\Widgets\Traits
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets\Traits;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
trait CommonWidgetFields {
|
||||
|
||||
/**
|
||||
* Whether this widget should render the shared common-form section.
|
||||
*
|
||||
* Override and return false for pure-display widgets with no admin form.
|
||||
* Default: true (every widget gets the shared block).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function use_common_form(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the shared common-form section at the end of a widget's form().
|
||||
*
|
||||
* Call from form(): render business fields, then `$this->render_common_form_section($instance);`.
|
||||
*
|
||||
* @param array<string,mixed> $instance Widget instance.
|
||||
*/
|
||||
protected function render_common_form_section(array $instance): void {
|
||||
if (!$this->use_common_form()) {
|
||||
return;
|
||||
}
|
||||
if (function_exists('aksrt_widget_render_common_form')) {
|
||||
aksrt_widget_render_common_form($instance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge shared common fields into a widget's update() result.
|
||||
*
|
||||
* Usage in update():
|
||||
* return $this->update_common_fields($new_instance, $old_instance, [
|
||||
* 'title' => sanitize_text_field(...),
|
||||
* ...business fields...
|
||||
* ]);
|
||||
*
|
||||
* @param array<string,mixed> $new_instance New submitted instance.
|
||||
* @param array<string,mixed> $old_instance Previous instance.
|
||||
* @param array<string,mixed> $business Sanitized business fields to keep.
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
protected function update_common_fields(array $new_instance, array $old_instance, array $business = []): array {
|
||||
if (!$this->use_common_form()) {
|
||||
return $business;
|
||||
}
|
||||
if (function_exists('aksrt_widget_update_common')) {
|
||||
$common = aksrt_widget_update_common($new_instance, $old_instance);
|
||||
return array_merge($business, $common);
|
||||
}
|
||||
return $business;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
/**
|
||||
* Announcement marquee → vertical notice swiper (zibll notice family parity).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetAnnouncement extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_announcement',
|
||||
__('公告跑马灯', 'aksrt'),
|
||||
[
|
||||
'description' => __('滚动显示公告/通知(共享垂直 Swiper)。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_announcement',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $args
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function widget($args, $instance): void {
|
||||
$title = !empty($instance['title']) ? (string) $instance['title'] : '';
|
||||
$items = !empty($instance['items']) ? (string) $instance['items'] : '';
|
||||
$icon = !empty($instance['icon']) ? (string) $instance['icon'] : 'fas fa-bullhorn';
|
||||
$speed = !empty($instance['speed']) ? max(5, min(30, (int) $instance['speed'])) : 10;
|
||||
$mode = sanitize_key((string) ($instance['mode'] ?? 'swiper'));
|
||||
if (!in_array($mode, ['swiper', 'marquee'], true)) {
|
||||
$mode = 'swiper';
|
||||
}
|
||||
$interval = max(2, min(15, (int) ($instance['interval'] ?? max(3, (int) round($speed / 2)))));
|
||||
$blank = !empty($instance['blank']);
|
||||
|
||||
$lines = array_values(array_filter(array_map('trim', explode("\n", $items))));
|
||||
if (!$lines) {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('请添加至少一条公告', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<p class="widget-empty">' . esc_html__('请添加至少一条公告', 'aksrt') . '</p>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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 ($mode === 'marquee') {
|
||||
// Legacy CSS marquee path.
|
||||
echo '<div class="aksrt-announcement">';
|
||||
echo '<span class="aksrt-announcement__icon"><i class="' . esc_attr($icon) . '" aria-hidden="true"></i></span>';
|
||||
echo '<div class="aksrt-announcement__track">';
|
||||
echo '<ul class="aksrt-announcement__list" style="animation-duration: ' . esc_attr((string) $speed) . 's;">';
|
||||
$render = function (string $line) use ($blank): void {
|
||||
$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) . '"' . ($blank ? ' target="_blank" rel="noopener noreferrer"' : '') . '>' . esc_html($text) . '</a>';
|
||||
} else {
|
||||
echo esc_html($text);
|
||||
}
|
||||
echo '</li>';
|
||||
};
|
||||
foreach ($lines as $line) {
|
||||
$render($line);
|
||||
}
|
||||
// Duplicate first for seamless CSS loop.
|
||||
$render($lines[0]);
|
||||
echo '</ul></div></div>';
|
||||
} else {
|
||||
// Shared vertical new-swiper (same stack as NoticeWidget).
|
||||
echo '<div class="aksrt-scroll-notice aksrt-announcement-swiper-wrap swiper-bulletin">';
|
||||
echo '<div class="aksrt-scroll-notice__icon bulletin-icon"><i class="' . esc_attr($icon) . '" aria-hidden="true"></i></div>';
|
||||
echo '<div class="new-swiper aksrt-notice-swiper aksrt-announcement-swiper" data-direction="vertical" data-loop data-autoplay data-interval="' . esc_attr((string) ($interval * 1000)) . '">';
|
||||
echo '<div class="swiper-wrapper">';
|
||||
foreach ($lines as $line) {
|
||||
$parts = explode('|', $line, 2);
|
||||
$text = trim($parts[0]);
|
||||
$link = isset($parts[1]) ? trim($parts[1]) : '';
|
||||
echo '<div class="swiper-slide notice-slide">';
|
||||
if ($link !== '') {
|
||||
echo '<a class="text-ellipsis"' . ($blank ? ' target="_blank" rel="noopener noreferrer"' : '') . ' href="' . esc_url($link) . '">' . esc_html($text) . '</a>';
|
||||
} else {
|
||||
echo '<span class="text-ellipsis">' . esc_html($text) . '</span>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div></div></div>';
|
||||
}
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function form($instance): void {
|
||||
$title = !empty($instance['title']) ? (string) $instance['title'] : '';
|
||||
$items = !empty($instance['items']) ? (string) $instance['items'] : '';
|
||||
$icon = !empty($instance['icon']) ? (string) $instance['icon'] : 'fas fa-bullhorn';
|
||||
$speed = !empty($instance['speed']) ? (int) $instance['speed'] : 10;
|
||||
$mode = (string) ($instance['mode'] ?? 'swiper');
|
||||
?>
|
||||
<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><?php esc_html_e('模式', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('mode')); ?>">
|
||||
<option value="swiper" <?php selected($mode, 'swiper'); ?>><?php esc_html_e('垂直轮播(推荐)', 'aksrt'); ?></option>
|
||||
<option value="marquee" <?php selected($mode, 'marquee'); ?>><?php esc_html_e('横向跑马灯', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</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>
|
||||
<p>
|
||||
<label><?php esc_html_e('轮播间隔(秒)', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="2" max="15" name="<?php echo esc_attr($this->get_field_name('interval')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['interval'] ?? 4))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('blank')); ?>" value="1" <?php checked(!empty($instance['blank'])); ?>> <?php esc_html_e('链接新窗口', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $new_instance
|
||||
* @param array<string,mixed> $old_instance
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$mode = sanitize_key((string) ($new_instance['mode'] ?? 'swiper'));
|
||||
if (!in_array($mode, ['swiper', 'marquee'], true)) {
|
||||
$mode = 'swiper';
|
||||
}
|
||||
$business = [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'items' => sanitize_textarea_field((string) ($new_instance['items'] ?? '')),
|
||||
'icon' => sanitize_text_field((string) ($new_instance['icon'] ?? 'fas fa-bullhorn')),
|
||||
'speed' => max(5, min(30, (int) ($new_instance['speed'] ?? 10))),
|
||||
'mode' => $mode,
|
||||
'interval' => max(2, min(15, (int) ($new_instance['interval'] ?? 4))),
|
||||
'blank' => !empty($new_instance['blank']) ? 1 : 0,
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
<?php
|
||||
/**
|
||||
* Post author card (zibll widget_ui_avatar + zib_get_user_card_box depth).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetAuthorCard extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_author_card',
|
||||
esc_html__('AKSRT 文章作者卡片', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('在文章/帖子侧栏显示当前内容作者信息与更多文章。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_author_card',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$author_id = 0;
|
||||
if (is_singular()) {
|
||||
$post = get_queried_object();
|
||||
if ($post instanceof \WP_Post) {
|
||||
$author_id = (int) $post->post_author;
|
||||
}
|
||||
}
|
||||
if ($author_id <= 0 && !empty($instance['user_id'])) {
|
||||
$author_id = (int) $instance['user_id'];
|
||||
}
|
||||
if ($author_id <= 0) {
|
||||
return;
|
||||
}
|
||||
$user = get_userdata($author_id);
|
||||
if (!$user) {
|
||||
return;
|
||||
}
|
||||
|
||||
$show_bg = !isset($instance['show_img_bg']) || !empty($instance['show_img_bg']);
|
||||
$show_stats = !isset($instance['show_stats']) || !empty($instance['show_stats']);
|
||||
$show_posts = !isset($instance['show_posts']) || !empty($instance['show_posts']);
|
||||
$post_style = sanitize_key((string) ($instance['post_style'] ?? 'mini'));
|
||||
if (!in_array($post_style, ['card', 'mini'], true)) {
|
||||
// Legacy show_img checkbox from zibll.
|
||||
if (isset($instance['show_img'])) {
|
||||
$post_style = !empty($instance['show_img']) ? 'card' : 'mini';
|
||||
} else {
|
||||
$post_style = 'mini';
|
||||
}
|
||||
}
|
||||
$limit = max(0, min(12, (int) ($instance['limit'] ?? 6)));
|
||||
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
|
||||
// Zibll button palettes / button_2 type (shown only when viewer is the author).
|
||||
$show_button = !isset($instance['show_button']) || !empty($instance['show_button']);
|
||||
$btn1_text = (string) ($instance['button_1_text'] ?? __('发布文章', 'aksrt'));
|
||||
$btn2_text = (string) ($instance['button_2_text'] ?? __('用户中心', 'aksrt'));
|
||||
$btn1_class = WidgetUserCard::sanitize_palette((string) ($instance['button_1_class'] ?? 'jb-pink'), 'jb-pink');
|
||||
$btn2_class = WidgetUserCard::sanitize_palette((string) ($instance['button_2_class'] ?? 'jb-blue'), 'jb-blue');
|
||||
$btn2_type = (string) ($instance['button_2'] ?? 'center');
|
||||
|
||||
$avatar = function_exists('aksrt_get_avatar_url')
|
||||
? aksrt_get_avatar_url($author_id, ['size' => 120])
|
||||
: get_avatar_url($author_id, ['size' => 120]);
|
||||
$home = function_exists('aksrt_get_user_home_url')
|
||||
? aksrt_get_user_home_url($author_id)
|
||||
: get_author_posts_url($author_id);
|
||||
$bio = get_user_meta($author_id, 'description', true);
|
||||
if (!is_string($bio) || $bio === '') {
|
||||
$bio = (string) $user->description;
|
||||
}
|
||||
$cover = (string) get_user_meta($author_id, 'aksrt_cover', true);
|
||||
if ($cover === '') {
|
||||
$cover = (string) get_user_meta($author_id, 'custom_avatar_cover', true);
|
||||
}
|
||||
|
||||
$post_count = count_user_posts($author_id, 'post', true);
|
||||
$followers = 0;
|
||||
if (class_exists('\AKSRT\User\Follow')) {
|
||||
$followers = \AKSRT\User\Follow::get_instance()->get_follower_count($author_id);
|
||||
}
|
||||
|
||||
$name_html = function_exists('aksrt_get_user_name')
|
||||
? aksrt_get_user_name(['id' => $author_id, 'follow' => false, 'class' => 'flex ac jc'])
|
||||
: '<a href="' . esc_url($home) . '">' . esc_html($user->display_name) . '</a>';
|
||||
|
||||
$affix = function_exists('aksrt_widget_affix_attrs') ? aksrt_widget_affix_attrs($instance) : ['class' => '', 'attr' => ''];
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
$card_cls = 'aksrt-author-card user-card author' . ($show_bg ? ' has-cover' : '') . $affix['class'];
|
||||
echo '<div class="' . esc_attr($card_cls) . '"' . $affix['attr'] . '>';
|
||||
if ($show_bg) {
|
||||
$bg = $cover !== '' ? ' style="background-image:url(' . esc_url($cover) . ')"' : '';
|
||||
echo '<div class="aksrt-author-card__cover user-cover graphic"' . $bg . '></div>';
|
||||
}
|
||||
echo '<div class="aksrt-author-card__body card-content">';
|
||||
echo '<div class="user-content">';
|
||||
$ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
|
||||
echo '<a class="aksrt-author-card__avatar user-avatar" href="' . esc_url($home) . '"><img class="avatar aksrt-lazy lazyload" data-src="' . esc_url($avatar) . '" src="' . esc_url($ph) . '" alt="" width="72" height="72" loading="lazy" /></a>';
|
||||
echo '<div class="aksrt-author-card__name user-name">' . $name_html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
if ($bio !== '') {
|
||||
echo '<div class="aksrt-author-card__bio user-desc muted-2-color em09">' . esc_html(wp_trim_words($bio, 28)) . '</div>';
|
||||
}
|
||||
$show_badge = !isset($instance['show_badge']) || !empty($instance['show_badge']);
|
||||
if ($show_badge && class_exists('\AKSRT\User\Badges') && \AKSRT\User\Badges::public_display()) {
|
||||
$wear = \AKSRT\User\Badges::get_wear($author_id);
|
||||
if ($wear === '') {
|
||||
$wear = sanitize_key((string) get_user_meta($author_id, 'wear_medal', true));
|
||||
}
|
||||
if ($wear !== '') {
|
||||
$map = \AKSRT\User\Badges::get_dictionary_map();
|
||||
if (isset($map[$wear])) {
|
||||
$badge = $map[$wear];
|
||||
echo '<div class="aksrt-author-card__badge author-tag tone-' . esc_attr((string) ($badge['tone'] ?? 'neutral')) . '">';
|
||||
if (!empty($badge['icon_url'])) {
|
||||
echo '<img src="' . esc_url((string) $badge['icon_url']) . '" alt="" width="16" height="16" loading="lazy" /> ';
|
||||
} else {
|
||||
echo '<i class="fas fa-medal" aria-hidden="true"></i> ';
|
||||
}
|
||||
echo esc_html((string) $badge['label']);
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($show_stats) {
|
||||
echo '<div class="aksrt-author-card__stats">';
|
||||
echo '<span><b>' . esc_html((string) $post_count) . '</b>' . esc_html__('文章', 'aksrt') . '</span>';
|
||||
echo '<span><b>' . esc_html((string) $followers) . '</b>' . esc_html__('粉丝', 'aksrt') . '</span>';
|
||||
echo '</div>';
|
||||
}
|
||||
$cuid = get_current_user_id();
|
||||
$show_follow = !isset($instance['show_follow']) || !empty($instance['show_follow']);
|
||||
echo '<div class="aksrt-author-card__actions user-btns">';
|
||||
if ($show_follow && $cuid > 0 && $cuid !== $author_id && class_exists('\AKSRT\User\Follow')) {
|
||||
$following = \AKSRT\User\Follow::get_instance()->is_following($cuid, $author_id);
|
||||
$label = $following ? __('已关注', 'aksrt') : __('关注', 'aksrt');
|
||||
echo '<button type="button" class="aksrt-author-card__btn aksrt-author-card__follow but' . ($following ? ' is-following' : '') . '" data-aksrt-follow="' . (int) $author_id . '">' . esc_html($label) . '</button>';
|
||||
}
|
||||
echo '<a class="aksrt-author-card__btn but" href="' . esc_url($home) . '">' . esc_html__('查看主页', 'aksrt') . '</a>';
|
||||
// Zibll: button_1 / button_2 only render when the logged-in viewer IS the author.
|
||||
if ($show_button && $cuid > 0 && $cuid === $author_id) {
|
||||
$center_url = function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url() : $home;
|
||||
$btn2_url = $btn2_type === 'home' ? $home : $center_url;
|
||||
$compose_url = $home;
|
||||
if (function_exists('aksrt_get_user_center_url')) {
|
||||
$compose_url = aksrt_get_user_center_url('posts');
|
||||
}
|
||||
echo '<a class="aksrt-author-card__btn but ' . esc_attr($btn1_class) . '" href="' . esc_url($compose_url) . '">' . esc_html($btn1_text) . '</a>';
|
||||
echo '<a class="aksrt-author-card__btn but ' . esc_attr($btn2_class) . '" href="' . esc_url($btn2_url) . '">' . esc_html($btn2_text) . '</a>';
|
||||
}
|
||||
echo '</div>';
|
||||
echo '</div>'; // .user-content
|
||||
|
||||
if ($show_posts && $limit > 0 && function_exists('aksrt_render_user_card_posts')) {
|
||||
echo aksrt_render_user_card_posts([ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
'user_id' => $author_id,
|
||||
'limit' => $limit,
|
||||
'orderby' => $orderby,
|
||||
'post_style' => $post_style,
|
||||
'post_type' => 'post',
|
||||
'show_empty' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
echo '</div></div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$post_style = sanitize_key((string) ($instance['post_style'] ?? 'mini'));
|
||||
if (!in_array($post_style, ['card', 'mini'], true)) {
|
||||
$post_style = !empty($instance['show_img']) ? 'card' : 'mini';
|
||||
}
|
||||
?>
|
||||
<p class="description"><?php esc_html_e('自动读取当前文章/帖子作者;也可固定用户 ID。', 'aksrt'); ?></p>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('固定用户 ID(可选)', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('user_id')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['user_id'] ?? 0))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_img_bg')); ?>" value="1" <?php checked(!isset($instance['show_img_bg']) || !empty($instance['show_img_bg'])); ?>> <?php esc_html_e('显示封面', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_stats')); ?>" value="1" <?php checked(!isset($instance['show_stats']) || !empty($instance['show_stats'])); ?>> <?php esc_html_e('显示统计', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_follow')); ?>" value="1" <?php checked(!isset($instance['show_follow']) || !empty($instance['show_follow'])); ?>> <?php esc_html_e('显示关注按钮', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_badge')); ?>" value="1" <?php checked(!isset($instance['show_badge']) || !empty($instance['show_badge'])); ?>> <?php esc_html_e('显示佩戴徽章', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_posts')); ?>" value="1" <?php checked(!isset($instance['show_posts']) || !empty($instance['show_posts'])); ?>> <?php esc_html_e('显示更多文章', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('文章数量', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="0" max="12" name="<?php echo esc_attr($this->get_field_name('limit')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['limit'] ?? 6))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('文章样式', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('post_style')); ?>">
|
||||
<option value="mini" <?php selected($post_style, 'mini'); ?>><?php esc_html_e('迷你列表', 'aksrt'); ?></option>
|
||||
<option value="card" <?php selected($post_style, 'card'); ?>><?php esc_html_e('卡片横滑', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('排序', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
|
||||
<option value="date" <?php selected(($instance['orderby'] ?? 'date'), 'date'); ?>><?php esc_html_e('最新', 'aksrt'); ?></option>
|
||||
<option value="modified" <?php selected(($instance['orderby'] ?? ''), 'modified'); ?>><?php esc_html_e('最近更新', 'aksrt'); ?></option>
|
||||
<option value="views" <?php selected(($instance['orderby'] ?? ''), 'views'); ?>><?php esc_html_e('阅读量', 'aksrt'); ?></option>
|
||||
<option value="like" <?php selected(($instance['orderby'] ?? ''), 'like'); ?>><?php esc_html_e('点赞', 'aksrt'); ?></option>
|
||||
<option value="comment_count" <?php selected(($instance['orderby'] ?? ''), 'comment_count'); ?>><?php esc_html_e('评论数', 'aksrt'); ?></option>
|
||||
<option value="favorite" <?php selected(($instance['orderby'] ?? ''), 'favorite'); ?>><?php esc_html_e('收藏', 'aksrt'); ?></option>
|
||||
<option value="rand" <?php selected(($instance['orderby'] ?? ''), 'rand'); ?>><?php esc_html_e('随机', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<hr style="margin:12px 0;">
|
||||
<p style="font-weight:600;margin-bottom:8px;"><?php esc_html_e('按钮(仅当登录用户为作者时显示)', 'aksrt'); ?></p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_button')); ?>" value="1" <?php checked(!isset($instance['show_button']) || !empty($instance['show_button'])); ?>> <?php esc_html_e('显示按钮', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('按钮1文案', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('button_1_text')); ?>" value="<?php echo esc_attr((string) ($instance['button_1_text'] ?? __('发布文章', 'aksrt'))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('按钮1颜色', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('button_1_class')); ?>">
|
||||
<?php foreach (WidgetUserCard::palette_opts() as $k => $label) : ?>
|
||||
<option value="<?php echo esc_attr($k); ?>" <?php selected(($instance['button_1_class'] ?? 'jb-pink'), $k); ?>><?php echo esc_html($label); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('按钮2类型', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('button_2')); ?>">
|
||||
<option value="home" <?php selected(($instance['button_2'] ?? 'center'), 'home'); ?>><?php esc_html_e('个人主页', 'aksrt'); ?></option>
|
||||
<option value="center" <?php selected(($instance['button_2'] ?? 'center'), 'center'); ?>><?php esc_html_e('用户中心', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
<label><?php esc_html_e('按钮2文案', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('button_2_text')); ?>" value="<?php echo esc_attr((string) ($instance['button_2_text'] ?? __('用户中心', 'aksrt'))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('按钮2颜色', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('button_2_class')); ?>">
|
||||
<?php foreach (WidgetUserCard::palette_opts() as $k => $label) : ?>
|
||||
<option value="<?php echo esc_attr($k); ?>" <?php selected(($instance['button_2_class'] ?? 'jb-blue'), $k); ?>><?php echo esc_html($label); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<?php $this->render_common_form_section($instance); ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$post_style = sanitize_key((string) ($new_instance['post_style'] ?? 'mini'));
|
||||
if (!in_array($post_style, ['card', 'mini'], true)) {
|
||||
$post_style = 'mini';
|
||||
}
|
||||
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'date'));
|
||||
if (!in_array($orderby, ['date', 'views', 'comment_count', 'modified', 'like', 'favorite', 'rand'], true)) {
|
||||
$orderby = 'date';
|
||||
}
|
||||
return $this->update_common_fields($new_instance, $old_instance, [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'user_id' => max(0, (int) ($new_instance['user_id'] ?? 0)),
|
||||
'show_img_bg' => !empty($new_instance['show_img_bg']) ? 1 : 0,
|
||||
'show_stats' => !empty($new_instance['show_stats']) ? 1 : 0,
|
||||
'show_follow' => !empty($new_instance['show_follow']) ? 1 : 0,
|
||||
'show_badge' => !empty($new_instance['show_badge']) ? 1 : 0,
|
||||
'show_posts' => !empty($new_instance['show_posts']) ? 1 : 0,
|
||||
'show_button' => !empty($new_instance['show_button']) ? 1 : 0,
|
||||
'limit' => max(0, min(12, (int) ($new_instance['limit'] ?? 6))),
|
||||
'post_style' => $post_style,
|
||||
'orderby' => $orderby,
|
||||
'button_1' => 'post',
|
||||
'button_1_text'=> sanitize_text_field((string) ($new_instance['button_1_text'] ?? '')),
|
||||
'button_1_class'=> WidgetUserCard::sanitize_palette((string) ($new_instance['button_1_class'] ?? 'jb-pink'), 'jb-pink'),
|
||||
'button_2' => in_array((string) ($new_instance['button_2'] ?? 'center'), ['home', 'center'], true) ? $new_instance['button_2'] : 'center',
|
||||
'button_2_text'=> sanitize_text_field((string) ($new_instance['button_2_text'] ?? '')),
|
||||
'button_2_class'=> WidgetUserCard::sanitize_palette((string) ($new_instance['button_2_class'] ?? 'jb-blue'), 'jb-blue'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
/**
|
||||
* WordPress bookmark / links list (zibll widget_ui_links_lists via aksrt_links_box).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetBookmarkLinks extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_bookmark_links',
|
||||
esc_html__('AKSRT 链接列表', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('WordPress 链接(书签)列表:card / bigcard / image / simple。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_bookmark_links',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$limit = max(0, min(100, (int) ($instance['links_limit'] ?? 12)));
|
||||
$orderby = sanitize_key((string) ($instance['links_orderby'] ?? 'name'));
|
||||
if (!in_array($orderby, ['name', 'updated', 'rating', 'rand', 'id'], true)) {
|
||||
$orderby = 'name';
|
||||
}
|
||||
$order = strtoupper((string) ($instance['links_order'] ?? 'ASC')) === 'DESC' ? 'DESC' : 'ASC';
|
||||
// Zibll widget_ui_links_lists_2: type card/bigcard/image/simple.
|
||||
$type = sanitize_key((string) ($instance['type'] ?? 'card'));
|
||||
if (!in_array($type, ['card', 'bigcard', 'image', 'simple'], true)) {
|
||||
$type = 'card';
|
||||
}
|
||||
// Zibll alignment ('' / center / right).
|
||||
$align = sanitize_key((string) ($instance['alignment'] ?? ''));
|
||||
if (!in_array($align, ['', 'center', 'right'], true)) {
|
||||
$align = '';
|
||||
}
|
||||
$go_link = !empty($instance['go_link']);
|
||||
$blank = !empty($instance['blank']);
|
||||
$nofollow = !isset($instance['nofollow']) || !empty($instance['nofollow']);
|
||||
$show_box = !empty($instance['show_box']);
|
||||
|
||||
$bm_args = [
|
||||
'orderby' => $orderby === 'rand' ? 'rand' : $orderby,
|
||||
'order' => $order,
|
||||
'limit' => $limit > 0 ? $limit : -1,
|
||||
];
|
||||
$cats = trim((string) ($instance['links_cats'] ?? ''));
|
||||
if ($cats !== '' && $cats !== 'all') {
|
||||
$bm_args['category'] = $cats;
|
||||
}
|
||||
$bookmarks = get_bookmarks($bm_args);
|
||||
if (!$bookmarks) {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('暂无链接,请在「链接」中添加', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<p class="widget-empty">' . esc_html__('暂无链接', 'aksrt') . '</p>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
$wrap = 'aksrt-bookmark-links links-box style-' . $type . ($show_box ? ' has-box' : '') . ($align ? ' text-' . $align : '');
|
||||
echo '<div class="' . esc_attr($wrap) . '">';
|
||||
if (function_exists('aksrt_links_box')) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo aksrt_links_box($bookmarks, $type, $nofollow, $go_link, $blank);
|
||||
} else {
|
||||
// Minimal fallback.
|
||||
foreach ($bookmarks as $b) {
|
||||
$url = (string) $b->link_url;
|
||||
if ($url !== '' && $go_link && function_exists('aksrt_go_link')) {
|
||||
$url = aksrt_go_link($url, true);
|
||||
}
|
||||
echo '<a class="aksrt-bookmark-links__item" href="' . esc_url($url) . '"' . ($blank ? ' target="_blank" rel="noopener noreferrer"' : '') . '>';
|
||||
echo esc_html((string) $b->link_name);
|
||||
echo '</a>';
|
||||
}
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('链接分类 ID(逗号,空=全部)', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('links_cats')); ?>" value="<?php echo esc_attr((string) ($instance['links_cats'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('数量(0=不限)', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="0" max="100" name="<?php echo esc_attr($this->get_field_name('links_limit')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['links_limit'] ?? 12))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('样式', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('type')); ?>">
|
||||
<option value="card" <?php selected(($instance['type'] ?? 'card'), 'card'); ?>><?php esc_html_e('图文卡片', 'aksrt'); ?></option>
|
||||
<option value="bigcard" <?php selected(($instance['type'] ?? ''), 'bigcard'); ?>><?php esc_html_e('大卡片', 'aksrt'); ?></option>
|
||||
<option value="image" <?php selected(($instance['type'] ?? ''), 'image'); ?>><?php esc_html_e('图片', 'aksrt'); ?></option>
|
||||
<option value="simple" <?php selected(($instance['type'] ?? ''), 'simple'); ?>><?php esc_html_e('纯文字', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('排序', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('links_orderby')); ?>">
|
||||
<option value="name" <?php selected(($instance['links_orderby'] ?? 'name'), 'name'); ?>><?php esc_html_e('名称', 'aksrt'); ?></option>
|
||||
<option value="updated" <?php selected(($instance['links_orderby'] ?? ''), 'updated'); ?>><?php esc_html_e('更新', 'aksrt'); ?></option>
|
||||
<option value="rating" <?php selected(($instance['links_orderby'] ?? ''), 'rating'); ?>><?php esc_html_e('评分', 'aksrt'); ?></option>
|
||||
<option value="rand" <?php selected(($instance['links_orderby'] ?? ''), 'rand'); ?>><?php esc_html_e('随机', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('links_order')); ?>">
|
||||
<option value="ASC" <?php selected(($instance['links_order'] ?? 'ASC'), 'ASC'); ?>><?php esc_html_e('升序', 'aksrt'); ?></option>
|
||||
<option value="DESC" <?php selected(($instance['links_order'] ?? ''), 'DESC'); ?>><?php esc_html_e('降序', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('对齐方式', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('alignment')); ?>">
|
||||
<option value="" <?php selected(($instance['alignment'] ?? ''), ''); ?>><?php esc_html_e('左对齐', 'aksrt'); ?></option>
|
||||
<option value="center" <?php selected(($instance['alignment'] ?? ''), 'center'); ?>><?php esc_html_e('居中', 'aksrt'); ?></option>
|
||||
<option value="right" <?php selected(($instance['alignment'] ?? ''), 'right'); ?>><?php esc_html_e('右对齐', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('blank')); ?>" value="1" <?php checked(!isset($instance['blank']) || !empty($instance['blank'])); ?>> <?php esc_html_e('新窗口', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('nofollow')); ?>" value="1" <?php checked(!isset($instance['nofollow']) || !empty($instance['nofollow'])); ?>> <?php esc_html_e('nofollow', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('go_link')); ?>" value="1" <?php checked(!empty($instance['go_link'])); ?>> <?php esc_html_e('外链跳转页', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_box')); ?>" value="1" <?php checked(!empty($instance['show_box'])); ?>> <?php esc_html_e('外框盒子', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$type = sanitize_key((string) ($new_instance['type'] ?? 'card'));
|
||||
if (!in_array($type, ['card', 'bigcard', 'image', 'simple'], true)) {
|
||||
$type = 'card';
|
||||
}
|
||||
$business = [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'links_cats' => sanitize_text_field((string) ($new_instance['links_cats'] ?? '')),
|
||||
'links_limit' => max(0, min(100, (int) ($new_instance['links_limit'] ?? 12))),
|
||||
'links_orderby' => sanitize_key((string) ($new_instance['links_orderby'] ?? 'name')),
|
||||
'links_order' => strtoupper((string) ($new_instance['links_order'] ?? 'ASC')) === 'DESC' ? 'DESC' : 'ASC',
|
||||
'type' => $type,
|
||||
'alignment' => in_array((string) ($new_instance['alignment'] ?? ''), ['', 'center', 'right'], true) ? $new_instance['alignment'] : '',
|
||||
'blank' => !empty($new_instance['blank']) ? 1 : 0,
|
||||
'nofollow' => !empty($new_instance['nofollow']) ? 1 : 0,
|
||||
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
|
||||
'show_box' => !empty($new_instance['show_box']) ? 1 : 0,
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
<?php
|
||||
/**
|
||||
* Combo info cards (zibll zib_widget_ui_combo_info_card).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetComboInfo extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
private const BANDS = ['blue', 'cyan', 'green', 'yellow', 'pink', 'purple'];
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_combo_info',
|
||||
esc_html__('AKSRT 复合信息卡片', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('图标 + 标题 + 标签 + 描述 + CTA 的多列信息卡。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_combo_info',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$cards = $this->normalize_cards($instance['cards'] ?? []);
|
||||
if (!$cards) {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('请添加至少一张组合信息卡', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<p class="widget-empty">' . esc_html__('请添加至少一张组合信息卡', 'aksrt') . '</p>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$pc_row = max(1, min(6, (int) ($instance['pc_row'] ?? 4)));
|
||||
$m_row = max(1, min(3, (int) ($instance['m_row'] ?? 1)));
|
||||
$radius = max(0, min(40, (int) ($instance['card_radius'] ?? 12)));
|
||||
$top_band = !empty($instance['top_band']) || !isset($instance['top_band']);
|
||||
$band_h = max(20, min(160, (int) ($instance['top_band_height'] ?? 70)));
|
||||
$cta_default = (string) ($instance['cta_text'] ?? __('立即体验', 'aksrt'));
|
||||
$go_link = !empty($instance['go_link']);
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
$style = sprintf('--aksrt-zci-radius:%dpx;--aksrt-zci-band-h:%dpx;--aksrt-zci-pc:%d;--aksrt-zci-m:%d;--zci-radius:%dpx;--zci-band-h:%dpx;', $radius, $band_h, $pc_row, $m_row, $radius, $band_h);
|
||||
$wrap = 'aksrt-combo-info zib-combo-info-card row gutters-10' . ($top_band ? ' has-band zci-has-band' : '');
|
||||
$anim = sanitize_key((string) ($instance['obs_animation'] ?? ''));
|
||||
$anim_repeat = !empty($instance['animation_repeat']);
|
||||
$pc_span = max(1, (int) floor(12 / $pc_row));
|
||||
$m_span = max(1, (int) floor(12 / $m_row));
|
||||
$col = 'col-sm-' . $pc_span . ' col-xs-' . $m_span;
|
||||
|
||||
echo '<div class="' . esc_attr($wrap) . '" style="' . esc_attr($style) . '">';
|
||||
$auto_idx = 0;
|
||||
$ci = 0;
|
||||
foreach ($cards as $card) {
|
||||
$html = $this->render_card($card, $cta_default, $auto_idx, $go_link, $top_band);
|
||||
if ($html === '') {
|
||||
continue;
|
||||
}
|
||||
$pack = function_exists('aksrt_obs_animate_attrs')
|
||||
? aksrt_obs_animate_attrs($anim, $anim_repeat, $ci * 0.1)
|
||||
: ['class' => '', 'attr' => ''];
|
||||
echo '<div class="' . esc_attr(trim($col . $pack['class'])) . '"' . $pack['attr'] . '>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '</div>';
|
||||
$ci++;
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $card
|
||||
*/
|
||||
private function render_card(array $card, string $default_cta, int &$auto_idx, bool $go_link, bool $top_band): string {
|
||||
$title = trim((string) ($card['title'] ?? ''));
|
||||
$desc = trim((string) ($card['desc'] ?? ''));
|
||||
if ($title === '' && $desc === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$band = sanitize_key((string) ($card['band_color'] ?? 'auto'));
|
||||
if ($band === '' || $band === 'auto' || !in_array($band, self::BANDS, true)) {
|
||||
$band = self::BANDS[$auto_idx % count(self::BANDS)];
|
||||
$auto_idx++;
|
||||
}
|
||||
|
||||
$icon_html = '';
|
||||
$icon_img = (string) ($card['icon_image'] ?? '');
|
||||
$icon_fa = (string) ($card['icon'] ?? '');
|
||||
if ($icon_img !== '') {
|
||||
$icon_html = '<div class="aksrt-zci-icon"><img src="' . esc_url($icon_img) . '" alt="" loading="lazy" /></div>';
|
||||
} elseif ($icon_fa !== '') {
|
||||
$icon_html = '<div class="aksrt-zci-icon"><i class="' . esc_attr($icon_fa) . '" aria-hidden="true"></i></div>';
|
||||
}
|
||||
|
||||
$tags_html = '';
|
||||
$tags = $card['tags'] ?? [];
|
||||
if (is_string($tags)) {
|
||||
$tags = array_filter(array_map('trim', explode(',', $tags)));
|
||||
$tags = array_map(static fn($t) => ['text' => $t, 'color' => ''], $tags);
|
||||
}
|
||||
if (is_array($tags) && $tags) {
|
||||
$tags_html .= '<div class="aksrt-zci-tags">';
|
||||
foreach ($tags as $tag) {
|
||||
if (is_string($tag)) {
|
||||
$tag = ['text' => $tag, 'color' => ''];
|
||||
}
|
||||
if (!is_array($tag)) {
|
||||
continue;
|
||||
}
|
||||
$tt = trim((string) ($tag['text'] ?? ''));
|
||||
if ($tt === '') {
|
||||
continue;
|
||||
}
|
||||
$color = sanitize_html_class((string) ($tag['color'] ?? ''));
|
||||
$tags_html .= '<span class="aksrt-zci-tag' . ($color !== '' ? ' ' . esc_attr($color) : '') . '">' . esc_html($tt) . '</span>';
|
||||
}
|
||||
$tags_html .= '</div>';
|
||||
}
|
||||
|
||||
$cta_text = trim((string) ($card['cta_text'] ?? ''));
|
||||
if ($cta_text === '') {
|
||||
$cta_text = $default_cta;
|
||||
}
|
||||
$url = (string) ($card['url'] ?? '');
|
||||
if ($url === '' && is_array($card['link'] ?? null)) {
|
||||
$url = (string) ($card['link']['url'] ?? '');
|
||||
}
|
||||
$target = (string) ($card['target'] ?? '');
|
||||
if ($target === '' && is_array($card['link'] ?? null)) {
|
||||
$target = (string) ($card['link']['target'] ?? '');
|
||||
}
|
||||
$cta_html = '';
|
||||
if ($cta_text !== '' && $url !== '') {
|
||||
if ($go_link && function_exists('aksrt_go_link')) {
|
||||
$url = aksrt_go_link($url, true);
|
||||
}
|
||||
$tgt = $target !== '' ? ' target="' . esc_attr($target) . '" rel="noopener noreferrer"' : '';
|
||||
$cta_html = '<a class="aksrt-zci-cta" href="' . esc_url($url) . '"' . $tgt . '><span>' . esc_html($cta_text) . '</span><i class="fa fa-arrow-right" aria-hidden="true"></i></a>';
|
||||
}
|
||||
|
||||
$band_class = $top_band ? ' aksrt-zci-band-' . esc_attr($band) : '';
|
||||
$html = '<div class="aksrt-zci-card' . $band_class . '">';
|
||||
$html .= $icon_html;
|
||||
if ($title !== '') {
|
||||
$html .= '<div class="aksrt-zci-title">' . esc_html($title) . '</div>';
|
||||
}
|
||||
$html .= $tags_html;
|
||||
if ($desc !== '') {
|
||||
$html .= '<div class="aksrt-zci-desc">' . esc_html($desc) . '</div>';
|
||||
}
|
||||
$html .= $cta_html;
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return array<int, array<string,mixed>>
|
||||
*/
|
||||
private function normalize_cards($raw): array {
|
||||
if (is_string($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
$raw = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($raw as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$title = sanitize_text_field((string) ($row['title'] ?? ''));
|
||||
$desc = sanitize_text_field((string) ($row['desc'] ?? ''));
|
||||
if ($title === '' && $desc === '') {
|
||||
continue;
|
||||
}
|
||||
$out[] = [
|
||||
'title' => $title,
|
||||
'desc' => $desc,
|
||||
'icon' => sanitize_text_field((string) ($row['icon'] ?? '')),
|
||||
'icon_image' => esc_url_raw(trim((string) ($row['icon_image'] ?? ''))),
|
||||
'url' => esc_url_raw(trim((string) ($row['url'] ?? (is_array($row['link'] ?? null) ? ($row['link']['url'] ?? '') : '')))),
|
||||
'target' => sanitize_text_field((string) ($row['target'] ?? '_blank')),
|
||||
'cta_text' => sanitize_text_field((string) ($row['cta_text'] ?? '')),
|
||||
'band_color' => sanitize_key((string) ($row['band_color'] ?? 'auto')),
|
||||
'tags' => $row['tags'] ?? [],
|
||||
];
|
||||
if (count($out) >= 12) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = (string) ($instance['title'] ?? '');
|
||||
$cards = is_string($instance['cards'] ?? null)
|
||||
? (string) $instance['cards']
|
||||
: wp_json_encode($this->normalize_cards($instance['cards'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr($title); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('卡片 JSON', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="8" name="<?php echo esc_attr($this->get_field_name('cards')); ?>" placeholder='[{"title":"功能","desc":"说明","icon":"fas fa-star","url":"https://","cta_text":"了解更多","tags":[{"text":"热门","color":"c-blue"}]}]'><?php echo esc_textarea((string) $cards); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('PC 列数', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="6" name="<?php echo esc_attr($this->get_field_name('pc_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['pc_row'] ?? 4))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('移动列数', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="3" name="<?php echo esc_attr($this->get_field_name('m_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['m_row'] ?? 1))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('默认 CTA 文案', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('cta_text')); ?>" value="<?php echo esc_attr((string) ($instance['cta_text'] ?? __('立即体验', 'aksrt'))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('top_band')); ?>" value="1" <?php checked(!empty($instance['top_band']) || !isset($instance['top_band'])); ?>> <?php esc_html_e('顶部色带', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('go_link')); ?>" value="1" <?php checked(!empty($instance['go_link'])); ?>> <?php esc_html_e('外链跳转页', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('入场动画', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('obs_animation')); ?>">
|
||||
<option value="" <?php selected(($instance['obs_animation'] ?? ''), ''); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
|
||||
<option value="slideup" <?php selected(($instance['obs_animation'] ?? ''), 'slideup'); ?>><?php esc_html_e('上滑', 'aksrt'); ?></option>
|
||||
<option value="fade" <?php selected(($instance['obs_animation'] ?? ''), 'fade'); ?>><?php esc_html_e('淡入', 'aksrt'); ?></option>
|
||||
<option value="zoomin" <?php selected(($instance['obs_animation'] ?? ''), 'zoomin'); ?>><?php esc_html_e('放大', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$business = [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'cards' => $this->normalize_cards($new_instance['cards'] ?? []),
|
||||
'pc_row' => max(1, min(6, (int) ($new_instance['pc_row'] ?? 4))),
|
||||
'm_row' => max(1, min(3, (int) ($new_instance['m_row'] ?? 1))),
|
||||
'card_radius' => max(0, min(40, (int) ($new_instance['card_radius'] ?? 12))),
|
||||
'top_band' => !empty($new_instance['top_band']) ? 1 : 0,
|
||||
'top_band_height' => max(20, min(160, (int) ($new_instance['top_band_height'] ?? 70))),
|
||||
'cta_text' => sanitize_text_field((string) ($new_instance['cta_text'] ?? '')),
|
||||
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
|
||||
'obs_animation' => sanitize_key((string) ($new_instance['obs_animation'] ?? '')),
|
||||
'animation_repeat'=> !empty($new_instance['animation_repeat']) ? 1 : 0,
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
/**
|
||||
* Graphic cover cards (zibll zib_widget_ui_graphic_cover → aksrt_graphic_card).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetCoverCards extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_cover_cards',
|
||||
__('图文封面卡片', 'aksrt'),
|
||||
['description' => __('图片/视频封面 + 文字叠加的图文卡片(共享 graphic_card)。', 'aksrt')]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $args
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function widget($args, $instance): void {
|
||||
$title = !empty($instance['title']) ? (string) $instance['title'] : '';
|
||||
$items = $this->normalize($instance['items'] ?? []);
|
||||
if (!$items) {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('请添加至少一张封面卡片', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<p class="widget-empty">' . esc_html__('请添加至少一张封面卡片', 'aksrt') . '</p>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$pc_row = max(1, min(6, (int) ($instance['pc_row'] ?? $instance['columns'] ?? 2)));
|
||||
$m_row = max(1, min(3, (int) ($instance['m_row'] ?? 1)));
|
||||
$height_scale = max(30, min(120, (int) ($instance['height_scale'] ?? 70)));
|
||||
$mask_opacity = max(0, min(80, (int) ($instance['mask_opacity'] ?? 20)));
|
||||
$font_size_pc = max(12, min(32, (int) ($instance['font_size_pc'] ?? 18)));
|
||||
$font_size_m = max(11, min(24, (int) ($instance['font_size_m'] ?? 14)));
|
||||
$font_bold = !empty($instance['font_bold']);
|
||||
$font_color = sanitize_hex_color((string) ($instance['font_color'] ?? '')) ?: '';
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
$is_row = count($items) > 1;
|
||||
$pc_span = max(1, (int) floor(12 / $pc_row));
|
||||
$m_span = max(1, (int) floor(12 / $m_row));
|
||||
$row_class = 'col-sm-' . $pc_span . ($m_row > 1 ? ' col-xs-' . $m_span : '');
|
||||
|
||||
$style = '';
|
||||
if ($font_size_pc !== 14) {
|
||||
$style .= '--font-size:' . $font_size_pc . 'px;';
|
||||
}
|
||||
if ($font_size_m !== 14) {
|
||||
$style .= '--font-size-sm:' . $font_size_m . 'px;';
|
||||
}
|
||||
if ($font_bold) {
|
||||
$style .= '--font-weight:bold;--font-weight-sm:bold;';
|
||||
}
|
||||
if ($font_color !== '') {
|
||||
$style .= '--color:' . $font_color . ';--color-sm:' . $font_color . ';';
|
||||
}
|
||||
|
||||
echo '<div class="widget-graphic-cover aksrt-cover-cards' . ($is_row ? ' mb10' : ' mb20') . '"' . ($style !== '' ? ' style="' . esc_attr($style) . '"' : '') . '>';
|
||||
if ($is_row) {
|
||||
echo '<div class="row gutters-5">';
|
||||
}
|
||||
|
||||
foreach ($items as $item) {
|
||||
$hide = sanitize_key((string) ($item['hide'] ?? ''));
|
||||
if ($hide === 'pc' || $hide === 'm') {
|
||||
$is_mobile = function_exists('wp_is_mobile') ? wp_is_mobile() : false;
|
||||
if ((!$is_mobile && $hide === 'pc') || ($is_mobile && $hide === 'm')) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$card_title = (string) ($item['title'] ?? '');
|
||||
$more = $card_title !== ''
|
||||
? '<div class="abs-center text-center graphic-text this-font">' . esc_html($card_title) . '</div>'
|
||||
: '';
|
||||
|
||||
$card = [
|
||||
'class' => 'noshadow mb10',
|
||||
'img' => (string) ($item['image'] ?? ''),
|
||||
'video' => (string) ($item['video'] ?? ''),
|
||||
'alt' => wp_strip_all_tags($card_title),
|
||||
'link' => [
|
||||
'url' => (string) ($item['link'] ?? ''),
|
||||
'target' => (string) ($item['target'] ?? ''),
|
||||
],
|
||||
'lazy' => true,
|
||||
'more' => $more,
|
||||
'height_scale' => !empty($item['height']) && is_numeric($item['height'])
|
||||
? (int) $item['height']
|
||||
: $height_scale,
|
||||
'mask_opacity' => isset($item['mask']) ? (int) $item['mask'] : $mask_opacity,
|
||||
];
|
||||
|
||||
if ($is_row) {
|
||||
echo '<div class="' . esc_attr($row_class) . '">';
|
||||
}
|
||||
if (function_exists('aksrt_graphic_card')) {
|
||||
echo aksrt_graphic_card($card); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
if ($is_row) {
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
if ($is_row) {
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return array<int, array<string,mixed>>
|
||||
*/
|
||||
private function normalize($raw): array {
|
||||
if (is_string($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
$raw = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($raw as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$image = esc_url_raw(trim((string) ($row['image'] ?? '')));
|
||||
$video = esc_url_raw(trim((string) ($row['video'] ?? '')));
|
||||
if ($image === '' && $video === '') {
|
||||
continue;
|
||||
}
|
||||
$out[] = [
|
||||
'title' => sanitize_text_field((string) ($row['title'] ?? '')),
|
||||
'desc' => sanitize_text_field((string) ($row['desc'] ?? '')),
|
||||
'image' => $image,
|
||||
'video' => $video,
|
||||
'link' => esc_url_raw(trim((string) ($row['link'] ?? ''))),
|
||||
'target' => sanitize_text_field((string) ($row['target'] ?? '')),
|
||||
'hide' => sanitize_key((string) ($row['hide'] ?? '')),
|
||||
'height' => isset($row['height']) ? (int) $row['height'] : 0,
|
||||
'mask' => isset($row['mask']) ? (int) $row['mask'] : null,
|
||||
];
|
||||
if (count($out) >= 12) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function form($instance): void {
|
||||
$items = $this->normalize($instance['items'] ?? []);
|
||||
$json = wp_json_encode($items, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题(可选):', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('PC 列', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="6" name="<?php echo esc_attr($this->get_field_name('pc_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['pc_row'] ?? $instance['columns'] ?? 2))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('移动列', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="3" name="<?php echo esc_attr($this->get_field_name('m_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['m_row'] ?? 1))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('高度占比 %', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="30" max="120" name="<?php echo esc_attr($this->get_field_name('height_scale')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['height_scale'] ?? 70))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('遮罩 0-80', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="0" max="80" name="<?php echo esc_attr($this->get_field_name('mask_opacity')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['mask_opacity'] ?? 20))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('卡片 JSON', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="6" name="<?php echo esc_attr($this->get_field_name('items')); ?>" placeholder='[{"image":"https://...","title":"标题","link":"https://..."}]'><?php echo esc_textarea((string) $json); ?></textarea>
|
||||
</label>
|
||||
<small><?php esc_html_e('字段: image, video, title, link, target, hide(pc|m), height, mask', 'aksrt'); ?></small>
|
||||
</p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $new_instance
|
||||
* @param array<string,mixed> $old_instance
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$business = [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'pc_row' => max(1, min(6, (int) ($new_instance['pc_row'] ?? 2))),
|
||||
'm_row' => max(1, min(3, (int) ($new_instance['m_row'] ?? 1))),
|
||||
'columns' => max(1, min(6, (int) ($new_instance['pc_row'] ?? $new_instance['columns'] ?? 2))),
|
||||
'height_scale' => max(30, min(120, (int) ($new_instance['height_scale'] ?? 70))),
|
||||
'mask_opacity' => max(0, min(80, (int) ($new_instance['mask_opacity'] ?? 20))),
|
||||
'font_size_pc' => max(12, min(32, (int) ($new_instance['font_size_pc'] ?? 18))),
|
||||
'font_size_m' => max(11, min(24, (int) ($new_instance['font_size_m'] ?? 14))),
|
||||
'font_bold' => !empty($new_instance['font_bold']) ? 1 : 0,
|
||||
'font_color' => sanitize_hex_color((string) ($new_instance['font_color'] ?? '')) ?: '',
|
||||
'items' => $this->normalize($new_instance['items'] ?? []),
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
<?php
|
||||
/**
|
||||
* FAQ accordion (zibll zib_widget_faq / produck-wid-faq structure parity).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetFaq extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
private const BADGE_TONES = ['purple', 'pink', 'green', 'orange', 'blue'];
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_faq',
|
||||
esc_html__('AKSRT 常见问题', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('FAQ 摘要卡 + 文档链接列表 + 帮助 CTA。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_faq',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$faqs = $this->normalize_faqs($instance['faqs'] ?? []);
|
||||
if (!$faqs) {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('请添加至少一条问题', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<div class="c-red muted-box mb20"><b>' . esc_html__('常见问题解答:', 'aksrt') . '</b>' . esc_html__('请添加至少一条问题', 'aksrt') . '</div>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
$uid = 'aksrt-faq-' . esc_attr($this->id);
|
||||
echo '<section class="aksrt-faq produck-wid-faq-block" id="' . $uid . '">';
|
||||
|
||||
if (!empty($instance['search_s'])) {
|
||||
$ph = (string) ($instance['search_placeholder'] ?? __('搜索问题…', 'aksrt'));
|
||||
$btn = (string) ($instance['search_btn_text'] ?? __('搜索', 'aksrt'));
|
||||
echo '<header class="aksrt-faq__head produck-faq-head">';
|
||||
echo '<form class="aksrt-faq__search search-form" method="get" action="' . esc_url(home_url('/')) . '" role="search">';
|
||||
echo '<div class="aksrt-faq__search-wrap produck-faq-search">';
|
||||
echo '<span class="aksrt-faq__search-icon" aria-hidden="true"><i class="fas fa-search"></i></span>';
|
||||
echo '<input type="search" name="s" class="aksrt-faq__search-input produck-faq-search-input" placeholder="' . esc_attr($ph) . '" autocomplete="off" />';
|
||||
echo '<button type="submit" class="aksrt-faq__search-btn produck-faq-search-btn"><span>' . esc_html($btn) . '</span></button>';
|
||||
echo '</div></form></header>';
|
||||
}
|
||||
|
||||
$doc_title = trim((string) ($instance['doc_title'] ?? ''));
|
||||
$doc_desc = trim((string) ($instance['doc_desc'] ?? ''));
|
||||
$doc_icon = sanitize_text_field((string) ($instance['doc_icon'] ?? 'fas fa-book'));
|
||||
$doc_links = $this->normalize_doc_links($instance['doc_links'] ?? []);
|
||||
$help_title = trim((string) ($instance['help_title'] ?? ''));
|
||||
$help_sub = trim((string) ($instance['help_subtitle'] ?? ''));
|
||||
$help_icon = sanitize_text_field((string) ($instance['help_icon'] ?? 'fas fa-graduation-cap'));
|
||||
$help_url = esc_url_raw(trim((string) ($instance['help_btn_url'] ?? '')));
|
||||
$help_btn = trim((string) ($instance['help_btn_text'] ?? ''));
|
||||
|
||||
$has_doc = ($doc_title !== '' || $doc_desc !== '' || $doc_links);
|
||||
$has_help = ($help_title !== '' || $help_sub !== '' || ($help_btn !== '' && $help_url !== ''));
|
||||
|
||||
$main_cls = 'aksrt-faq__main produck-faq-main';
|
||||
if (!$has_doc && !$has_help) {
|
||||
$main_cls .= ' is-full';
|
||||
}
|
||||
echo '<div class="' . esc_attr($main_cls) . '">';
|
||||
echo '<div class="aksrt-faq__list produck-faq-list-col">';
|
||||
|
||||
$n = 0;
|
||||
foreach ($faqs as $f) {
|
||||
$n++;
|
||||
$num = str_pad((string) $n, 2, '0', STR_PAD_LEFT);
|
||||
$tone = self::BADGE_TONES[($n - 1) % count(self::BADGE_TONES)];
|
||||
$preview = $f['answer'] !== '' ? wp_strip_all_tags($f['answer']) : '';
|
||||
if ($preview !== '') {
|
||||
$preview = wp_trim_words($preview, 28);
|
||||
}
|
||||
echo '<details class="aksrt-faq__item produck-faq-summary">';
|
||||
echo '<summary class="aksrt-faq__summary">';
|
||||
echo '<span class="aksrt-faq__badge produck-faq-badge tone-' . esc_attr($tone) . '">' . esc_html($num) . '</span>';
|
||||
echo '<span class="aksrt-faq__qblock">';
|
||||
echo '<span class="aksrt-faq__q produck-faq-q">' . esc_html($f['question']) . '</span>';
|
||||
if ($preview !== '') {
|
||||
echo '<span class="aksrt-faq__preview muted-2-color">' . esc_html($preview) . '</span>';
|
||||
}
|
||||
echo '</span>';
|
||||
echo '<i class="fas fa-angle-down aksrt-faq__chevron" aria-hidden="true"></i>';
|
||||
echo '</summary>';
|
||||
if ($f['answer'] !== '') {
|
||||
echo '<div class="aksrt-faq__a">' . wp_kses_post(wpautop($f['answer'])) . '</div>';
|
||||
}
|
||||
echo '</details>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
if ($has_doc || $has_help) {
|
||||
echo '<aside class="aksrt-faq__aside produck-faq-aside">';
|
||||
if ($has_doc) {
|
||||
echo '<div class="aksrt-faq__doc produck-faq-doc-card zib-widget">';
|
||||
echo '<div class="aksrt-faq__doc-head">';
|
||||
if ($doc_icon !== '') {
|
||||
echo '<span class="aksrt-faq__doc-ico"><i class="' . esc_attr($doc_icon) . '" aria-hidden="true"></i></span>';
|
||||
}
|
||||
echo '<div>';
|
||||
if ($doc_title !== '') {
|
||||
echo '<h3 class="aksrt-faq__doc-title">' . esc_html($doc_title) . '</h3>';
|
||||
}
|
||||
if ($doc_desc !== '') {
|
||||
echo '<p class="aksrt-faq__doc-desc muted-2-color">' . nl2br(esc_html($doc_desc)) . '</p>';
|
||||
}
|
||||
echo '</div></div>';
|
||||
if ($doc_links) {
|
||||
echo '<ul class="aksrt-faq__doc-list produck-faq-doc-list">';
|
||||
foreach ($doc_links as $dl) {
|
||||
$durl = $dl['url'] !== '' ? $dl['url'] : '#';
|
||||
echo '<li class="aksrt-faq__doc-li">';
|
||||
echo '<a class="aksrt-faq__doc-a" href="' . esc_url($durl) . '">';
|
||||
if ($dl['icon'] !== '') {
|
||||
echo '<span class="aksrt-faq__doc-li-ico"><i class="' . esc_attr($dl['icon']) . '" aria-hidden="true"></i></span>';
|
||||
}
|
||||
echo '<span class="aksrt-faq__doc-li-mid">';
|
||||
echo '<span class="aksrt-faq__doc-li-title">' . esc_html($dl['title']);
|
||||
if ($dl['tag'] !== '') {
|
||||
echo ' <span class="badg badg-sm c-red ml6">' . esc_html($dl['tag']) . '</span>';
|
||||
}
|
||||
echo '</span>';
|
||||
if ($dl['desc'] !== '') {
|
||||
echo '<span class="aksrt-faq__doc-li-desc muted-2-color">' . esc_html($dl['desc']) . '</span>';
|
||||
}
|
||||
echo '</span>';
|
||||
$go = $dl['btn_text'] !== '' ? $dl['btn_text'] : __('查看教程', 'aksrt');
|
||||
echo '<span class="aksrt-faq__doc-go">' . esc_html($go) . ' <i class="fas fa-angle-right" aria-hidden="true"></i></span>';
|
||||
echo '</a></li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
if ($has_help) {
|
||||
echo '<div class="aksrt-faq__help produck-faq-help-card">';
|
||||
echo '<div class="aksrt-faq__help-top">';
|
||||
if ($help_icon !== '') {
|
||||
echo '<span class="aksrt-faq__help-ico"><i class="' . esc_attr($help_icon) . '" aria-hidden="true"></i></span>';
|
||||
}
|
||||
echo '<div class="aksrt-faq__help-text">';
|
||||
if ($help_title !== '') {
|
||||
echo '<h3 class="aksrt-faq__help-title">' . esc_html($help_title) . '</h3>';
|
||||
}
|
||||
if ($help_sub !== '') {
|
||||
echo '<p class="aksrt-faq__help-sub">' . nl2br(esc_html($help_sub)) . '</p>';
|
||||
}
|
||||
echo '</div></div>';
|
||||
if ($help_btn !== '' && $help_url !== '') {
|
||||
echo '<a class="aksrt-faq__help-btn" href="' . esc_url($help_url) . '">' . esc_html($help_btn) . ' <i class="fas fa-arrow-right" aria-hidden="true"></i></a>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</aside>';
|
||||
}
|
||||
|
||||
echo '</div></section>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return array<int, array{question:string,answer:string,category:string}>
|
||||
*/
|
||||
private function normalize_faqs($raw): array {
|
||||
if (is_string($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
$raw = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($raw as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$q = sanitize_text_field((string) ($row['question'] ?? $row['q'] ?? ''));
|
||||
if ($q === '') {
|
||||
continue;
|
||||
}
|
||||
$out[] = [
|
||||
'question' => $q,
|
||||
'answer' => wp_kses_post((string) ($row['answer'] ?? $row['a'] ?? '')),
|
||||
'category' => sanitize_key((string) ($row['category'] ?? 'all')),
|
||||
];
|
||||
if (count($out) >= 30) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return array<int, array{title:string,url:string,desc:string,icon:string,tag:string,btn_text:string}>
|
||||
*/
|
||||
private function normalize_doc_links($raw): array {
|
||||
if (is_string($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
$raw = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($raw as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$title = sanitize_text_field((string) ($row['title'] ?? ''));
|
||||
$url = esc_url_raw(trim((string) ($row['url'] ?? $row['link'] ?? '')));
|
||||
if ($title === '' && $url === '') {
|
||||
continue;
|
||||
}
|
||||
$out[] = [
|
||||
'title' => $title,
|
||||
'url' => $url,
|
||||
'desc' => sanitize_text_field((string) ($row['desc'] ?? '')),
|
||||
'icon' => sanitize_text_field((string) ($row['icon'] ?? '')),
|
||||
'tag' => sanitize_text_field((string) ($row['tag'] ?? '')),
|
||||
'btn_text' => sanitize_text_field((string) ($row['btn_text'] ?? '')),
|
||||
];
|
||||
if (count($out) >= 12) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$faqs = $this->normalize_faqs($instance['faqs'] ?? []);
|
||||
if (!$faqs) {
|
||||
$faqs = [
|
||||
['question' => '', 'answer' => '', 'category' => 'all'],
|
||||
['question' => '', 'answer' => '', 'category' => 'all'],
|
||||
];
|
||||
}
|
||||
$doc_links = $this->normalize_doc_links($instance['doc_links'] ?? []);
|
||||
if (!$doc_links) {
|
||||
$doc_links = [
|
||||
['title' => '', 'url' => '', 'desc' => '', 'icon' => 'fas fa-file-alt', 'tag' => '', 'btn_text' => ''],
|
||||
];
|
||||
}
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('search_s')); ?>" value="1" <?php checked(!empty($instance['search_s'])); ?>> <?php esc_html_e('显示搜索框', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('搜索占位', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('search_placeholder')); ?>" value="<?php echo esc_attr((string) ($instance['search_placeholder'] ?? __('搜索问题…', 'aksrt'))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('搜索按钮', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('search_btn_text')); ?>" value="<?php echo esc_attr((string) ($instance['search_btn_text'] ?? __('搜索', 'aksrt'))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('FAQ JSON', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="6" name="<?php echo esc_attr($this->get_field_name('faqs')); ?>" placeholder='[{"question":"…","answer":"…"}]'><?php echo esc_textarea((string) wp_json_encode($faqs, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<hr>
|
||||
<p><strong><?php esc_html_e('文档卡', 'aksrt'); ?></strong></p>
|
||||
<p>
|
||||
<label><?php esc_html_e('文档标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('doc_title')); ?>" value="<?php echo esc_attr((string) ($instance['doc_title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('文档描述', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="2" name="<?php echo esc_attr($this->get_field_name('doc_desc')); ?>"><?php echo esc_textarea((string) ($instance['doc_desc'] ?? '')); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('文档图标 class', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('doc_icon')); ?>" value="<?php echo esc_attr((string) ($instance['doc_icon'] ?? 'fas fa-book')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('文档链接 JSON', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="5" name="<?php echo esc_attr($this->get_field_name('doc_links')); ?>" placeholder='[{"title":"教程","url":"#","desc":"","icon":"fas fa-file","tag":"热门","btn_text":"查看教程"}]'><?php echo esc_textarea((string) wp_json_encode($doc_links, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<hr>
|
||||
<p><strong><?php esc_html_e('帮助卡', 'aksrt'); ?></strong></p>
|
||||
<p>
|
||||
<label><?php esc_html_e('帮助标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('help_title')); ?>" value="<?php echo esc_attr((string) ($instance['help_title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('帮助副标题', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="2" name="<?php echo esc_attr($this->get_field_name('help_subtitle')); ?>"><?php echo esc_textarea((string) ($instance['help_subtitle'] ?? '')); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('帮助图标 class', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('help_icon')); ?>" value="<?php echo esc_attr((string) ($instance['help_icon'] ?? 'fas fa-graduation-cap')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('帮助按钮文案', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('help_btn_text')); ?>" value="<?php echo esc_attr((string) ($instance['help_btn_text'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('帮助按钮链接', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('help_btn_url')); ?>" value="<?php echo esc_attr((string) ($instance['help_btn_url'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$business = [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'search_s' => !empty($new_instance['search_s']) ? 1 : 0,
|
||||
'search_placeholder' => sanitize_text_field((string) ($new_instance['search_placeholder'] ?? '')),
|
||||
'search_btn_text' => sanitize_text_field((string) ($new_instance['search_btn_text'] ?? '')),
|
||||
'faqs' => $this->normalize_faqs($new_instance['faqs'] ?? []),
|
||||
'doc_title' => sanitize_text_field((string) ($new_instance['doc_title'] ?? '')),
|
||||
'doc_desc' => sanitize_textarea_field((string) ($new_instance['doc_desc'] ?? '')),
|
||||
'doc_icon' => sanitize_text_field((string) ($new_instance['doc_icon'] ?? '')),
|
||||
'doc_links' => $this->normalize_doc_links($new_instance['doc_links'] ?? []),
|
||||
'help_title' => sanitize_text_field((string) ($new_instance['help_title'] ?? '')),
|
||||
'help_subtitle' => sanitize_textarea_field((string) ($new_instance['help_subtitle'] ?? '')),
|
||||
'help_icon' => sanitize_text_field((string) ($new_instance['help_icon'] ?? '')),
|
||||
'help_btn_text' => sanitize_text_field((string) ($new_instance['help_btn_text'] ?? '')),
|
||||
'help_btn_url' => esc_url_raw(trim((string) ($new_instance['help_btn_url'] ?? ''))),
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
/**
|
||||
* Feature intro with media tabs (zibll zib_widget_feature_intro lite).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetFeatureIntro extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_feature_intro',
|
||||
esc_html__('AKSRT 功能介绍', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('左文右图/视频的 TAB 功能介绍模块。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_feature_intro',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$slides = $this->normalize_slides($instance['slides'] ?? []);
|
||||
if (!$slides) {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('请添加至少一个功能介绍面板', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<p class="widget-empty">' . esc_html__('请添加至少一个功能介绍面板', 'aksrt') . '</p>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
$heading = trim((string) ($instance['heading'] ?? ''));
|
||||
$desc = trim((string) ($instance['description'] ?? ''));
|
||||
$media_pos = (($instance['media_position'] ?? 'right') === 'left') ? 'left' : 'right';
|
||||
$chrome = !empty($instance['browser_chrome']) || !isset($instance['browser_chrome']);
|
||||
$ratio = max(30, min(100, (int) ($instance['media_ratio'] ?? 66)));
|
||||
$show_stat = !empty($instance['show_stat']);
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$uid = 'aksrt-fi-' . esc_attr($this->id);
|
||||
|
||||
echo '<div class="aksrt-fi media-' . esc_attr($media_pos) . '" id="' . $uid . '" data-aksrt-feature-intro="1">';
|
||||
echo '<div class="aksrt-fi__text">';
|
||||
if ($heading !== '') {
|
||||
echo '<h2 class="aksrt-fi__title">' . esc_html($heading) . '</h2>';
|
||||
}
|
||||
if ($desc !== '') {
|
||||
echo '<div class="aksrt-fi__desc">' . wp_kses_post(wpautop($desc)) . '</div>';
|
||||
}
|
||||
if ($show_stat) {
|
||||
$sn = sanitize_text_field((string) ($instance['stat_number'] ?? ''));
|
||||
$ss = sanitize_text_field((string) ($instance['stat_suffix'] ?? '+'));
|
||||
$sl = sanitize_text_field((string) ($instance['stat_label'] ?? ''));
|
||||
if ($sn !== '') {
|
||||
$num_attr = is_numeric($sn) ? ' data-aksrt-countup data-target="' . esc_attr((string) (float) $sn) . '" data-duration="1600"' : '';
|
||||
echo '<div class="aksrt-fi__stat"><span class="aksrt-fi__stat-num"' . $num_attr . '>' . esc_html(is_numeric($sn) ? '0' : $sn) . '</span>';
|
||||
if ($ss !== '') {
|
||||
echo '<span class="aksrt-fi__stat-plus">' . esc_html($ss) . '</span>';
|
||||
}
|
||||
if ($sl !== '') {
|
||||
echo '<span class="aksrt-fi__stat-label">' . esc_html($sl) . '</span>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
if (count($slides) > 1) {
|
||||
echo '<div class="aksrt-fi__tabs" role="tablist">';
|
||||
foreach ($slides as $i => $s) {
|
||||
$cls = $i === 0 ? ' is-active' : '';
|
||||
$label = $s['tab_label'] !== '' ? $s['tab_label'] : sprintf(__('面板 %d', 'aksrt'), $i + 1);
|
||||
echo '<button type="button" class="aksrt-fi__tab' . esc_attr($cls) . '" data-index="' . (int) $i . '" role="tab">' . esc_html($label) . '</button>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo '<div class="aksrt-fi__media">';
|
||||
foreach ($slides as $i => $s) {
|
||||
$cls = $i === 0 ? ' is-active' : '';
|
||||
echo '<div class="aksrt-fi__panel' . esc_attr($cls) . '" data-index="' . (int) $i . '">';
|
||||
$box_cls = $chrome ? 'aksrt-fi__browser' : 'aksrt-fi__browser is-plain';
|
||||
echo '<div class="' . esc_attr($box_cls) . '">';
|
||||
if ($chrome) {
|
||||
echo '<div class="aksrt-fi__browser-bar" aria-hidden="true"><span></span><span></span><span></span></div>';
|
||||
}
|
||||
echo '<div class="aksrt-fi__browser-body" style="padding-bottom:' . esc_attr((string) $ratio) . '%">';
|
||||
if ($s['media_type'] === 'video' && $s['video_url'] !== '') {
|
||||
$poster = $s['video_poster'] !== '' ? ' poster="' . esc_url($s['video_poster']) . '"' : '';
|
||||
$auto = $i === 0 ? ' autoplay' : '';
|
||||
if ($i === 0) {
|
||||
echo '<video class="aksrt-fi__video" src="' . esc_url($s['video_url']) . '"' . $poster . $auto . ' muted loop playsinline></video>';
|
||||
} else {
|
||||
echo '<video class="aksrt-fi__video" data-src="' . esc_url($s['video_url']) . '"' . $poster . ' muted loop playsinline></video>';
|
||||
}
|
||||
} elseif ($s['image'] !== '') {
|
||||
$ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
|
||||
echo '<img class="aksrt-fi__img aksrt-lazy lazyload" data-src="' . esc_url($s['image']) . '" src="' . esc_url($ph) . '" alt="' . esc_attr($s['tab_label']) . '" loading="lazy" />';
|
||||
}
|
||||
echo '</div></div></div>';
|
||||
}
|
||||
echo '</div></div>';
|
||||
|
||||
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return array<int, array<string,string>>
|
||||
*/
|
||||
private function normalize_slides($raw): array {
|
||||
if (is_string($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
$raw = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($raw as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$type = sanitize_key((string) ($row['media_type'] ?? 'image'));
|
||||
if ($type !== 'video') {
|
||||
$type = 'image';
|
||||
}
|
||||
$image = esc_url_raw(trim((string) ($row['image'] ?? '')));
|
||||
$video = esc_url_raw(trim((string) ($row['video_url'] ?? '')));
|
||||
if ($type === 'video' && $video === '') {
|
||||
continue;
|
||||
}
|
||||
if ($type === 'image' && $image === '') {
|
||||
continue;
|
||||
}
|
||||
$out[] = [
|
||||
'tab_label' => sanitize_text_field((string) ($row['tab_label'] ?? '')),
|
||||
'media_type' => $type,
|
||||
'image' => $image,
|
||||
'video_url' => $video,
|
||||
'video_poster' => esc_url_raw(trim((string) ($row['video_poster'] ?? ''))),
|
||||
];
|
||||
if (count($out) >= 8) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$json = is_string($instance['slides'] ?? null)
|
||||
? (string) $instance['slides']
|
||||
: wp_json_encode($this->normalize_slides($instance['slides'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('主标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('heading')); ?>" value="<?php echo esc_attr((string) ($instance['heading'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('描述', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="3" name="<?php echo esc_attr($this->get_field_name('description')); ?>"><?php echo esc_textarea((string) ($instance['description'] ?? '')); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('媒体 TAB JSON', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="6" name="<?php echo esc_attr($this->get_field_name('slides')); ?>" placeholder='[{"tab_label":"面板1","media_type":"image","image":"https://..."}]'><?php echo esc_textarea((string) $json); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('媒体位置', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('media_position')); ?>">
|
||||
<option value="right" <?php selected(($instance['media_position'] ?? 'right'), 'right'); ?>><?php esc_html_e('右侧', 'aksrt'); ?></option>
|
||||
<option value="left" <?php selected(($instance['media_position'] ?? ''), 'left'); ?>><?php esc_html_e('左侧', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('媒体高度占比 %', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="30" max="100" name="<?php echo esc_attr($this->get_field_name('media_ratio')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['media_ratio'] ?? 66))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('browser_chrome')); ?>" value="1" <?php checked(!empty($instance['browser_chrome']) || !isset($instance['browser_chrome'])); ?>> <?php esc_html_e('浏览器外框', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_stat')); ?>" value="1" <?php checked(!empty($instance['show_stat'])); ?>> <?php esc_html_e('显示统计数字', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('统计数字', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('stat_number')); ?>" value="<?php echo esc_attr((string) ($instance['stat_number'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('统计后缀', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('stat_suffix')); ?>" value="<?php echo esc_attr((string) ($instance['stat_suffix'] ?? '+')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('统计标签', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('stat_label')); ?>" value="<?php echo esc_attr((string) ($instance['stat_label'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$business = [
|
||||
'heading' => sanitize_text_field((string) ($new_instance['heading'] ?? '')),
|
||||
'description' => wp_kses_post((string) ($new_instance['description'] ?? '')),
|
||||
'slides' => $this->normalize_slides($new_instance['slides'] ?? []),
|
||||
'media_position' => ($new_instance['media_position'] ?? '') === 'left' ? 'left' : 'right',
|
||||
'media_ratio' => max(30, min(100, (int) ($new_instance['media_ratio'] ?? 66))),
|
||||
'browser_chrome' => !empty($new_instance['browser_chrome']) ? 1 : 0,
|
||||
'show_stat' => !empty($new_instance['show_stat']) ? 1 : 0,
|
||||
'stat_number' => sanitize_text_field((string) ($new_instance['stat_number'] ?? '')),
|
||||
'stat_suffix' => sanitize_text_field((string) ($new_instance['stat_suffix'] ?? '')),
|
||||
'stat_label' => sanitize_text_field((string) ($new_instance['stat_label'] ?? '')),
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
/**
|
||||
* Highlights cards (zibll zib_widget_highlights) with scroll animation + go_link.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetHighlights extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_highlights',
|
||||
esc_html__('AKSRT 亮点卡片', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('大中小尺寸亮点/卖点卡片栅格。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_highlights',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $args
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function widget($args, $instance): void {
|
||||
$cards = $this->normalize($instance['cards'] ?? []);
|
||||
if (!$cards) {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('请添加至少一张亮点卡片', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<p class="widget-empty">' . esc_html__('请添加至少一张亮点卡片', 'aksrt') . '</p>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
$go_link = !empty($instance['go_link']);
|
||||
$anim = sanitize_key((string) ($instance['obs_animation'] ?? 'slideup'));
|
||||
$anim_repeat = !empty($instance['animation_repeat']);
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
$large = array_values(array_filter($cards, static fn($c) => ($c['size'] ?? 'medium') === 'large'));
|
||||
$medium = array_values(array_filter($cards, static fn($c) => ($c['size'] ?? 'medium') === 'medium'));
|
||||
$small = array_values(array_filter($cards, static fn($c) => ($c['size'] ?? 'medium') === 'small'));
|
||||
if (!$large && !$medium && !$small) {
|
||||
$medium = $cards;
|
||||
}
|
||||
|
||||
echo '<div class="aksrt-highlights">';
|
||||
$idx = 0;
|
||||
foreach (['large' => $large, 'medium' => $medium, 'small' => $small] as $size => $group) {
|
||||
if (!$group) {
|
||||
continue;
|
||||
}
|
||||
echo '<div class="aksrt-highlights__row is-' . esc_attr($size) . '">';
|
||||
foreach ($group as $c) {
|
||||
$pack = function_exists('aksrt_obs_animate_attrs')
|
||||
? aksrt_obs_animate_attrs($anim, $anim_repeat, $idx * 0.08)
|
||||
: ['class' => '', 'attr' => ''];
|
||||
echo $this->card($c, $size, $go_link, $pack); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$idx++;
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $c
|
||||
* @param array{class:string,attr:string} $pack
|
||||
*/
|
||||
private function card(array $c, string $size, bool $go_link, array $pack): string {
|
||||
$color = sanitize_key((string) ($c['color'] ?? 'blue'));
|
||||
if (!in_array($color, ['blue', 'pink', 'purple', 'green', 'orange', 'cyan'], true)) {
|
||||
$color = 'blue';
|
||||
}
|
||||
$url = (string) ($c['url'] ?? '');
|
||||
if ($url !== '' && $go_link && function_exists('aksrt_go_link')) {
|
||||
$url = aksrt_go_link($url, true);
|
||||
}
|
||||
$tag = $url !== '' ? 'a' : 'div';
|
||||
$href = $url !== '' ? ' href="' . esc_url($url) . '"' : '';
|
||||
$img = (string) ($c['image'] ?? '');
|
||||
$cls = 'aksrt-hl-card size-' . esc_attr($size) . ' color-' . esc_attr($color) . $pack['class'];
|
||||
$html = '<' . $tag . ' class="' . esc_attr(trim($cls)) . '"' . $href . $pack['attr'] . '>';
|
||||
if ($img !== '') {
|
||||
$placeholder = get_template_directory_uri() . '/assets/img/placeholder.svg';
|
||||
$html .= '<div class="aksrt-hl-card__media"><img class="fit-cover aksrt-lazy lazyload" data-src="' . esc_url($img) . '" src="' . esc_url($placeholder) . '" alt="" loading="lazy" /></div>';
|
||||
}
|
||||
$html .= '<div class="aksrt-hl-card__body">';
|
||||
if (!empty($c['icon'])) {
|
||||
$html .= '<div class="aksrt-hl-card__icon"><i class="' . esc_attr((string) $c['icon']) . '" aria-hidden="true"></i></div>';
|
||||
}
|
||||
if ($c['title'] !== '') {
|
||||
$html .= '<div class="aksrt-hl-card__title">' . esc_html($c['title']) . '</div>';
|
||||
}
|
||||
if ($c['desc'] !== '') {
|
||||
$html .= '<div class="aksrt-hl-card__desc">' . esc_html($c['desc']) . '</div>';
|
||||
}
|
||||
$html .= '</div></' . $tag . '>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return array<int, array<string,mixed>>
|
||||
*/
|
||||
private function normalize($raw): array {
|
||||
if (is_string($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
$raw = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($raw as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$title = sanitize_text_field((string) ($row['title'] ?? ''));
|
||||
$desc = sanitize_text_field((string) ($row['desc'] ?? ''));
|
||||
if ($title === '' && $desc === '') {
|
||||
continue;
|
||||
}
|
||||
$size = sanitize_key((string) ($row['size'] ?? 'medium'));
|
||||
if (!in_array($size, ['large', 'medium', 'small'], true)) {
|
||||
$size = 'medium';
|
||||
}
|
||||
$out[] = [
|
||||
'title' => $title,
|
||||
'desc' => $desc,
|
||||
'icon' => sanitize_text_field((string) ($row['icon'] ?? '')),
|
||||
'image' => esc_url_raw(trim((string) ($row['image'] ?? ''))),
|
||||
'url' => esc_url_raw(trim((string) ($row['url'] ?? ''))),
|
||||
'color' => sanitize_key((string) ($row['color'] ?? 'blue')),
|
||||
'size' => $size,
|
||||
];
|
||||
if (count($out) >= 12) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function form($instance): void {
|
||||
$json = is_string($instance['cards'] ?? null)
|
||||
? (string) $instance['cards']
|
||||
: wp_json_encode($this->normalize($instance['cards'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('卡片 JSON', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="8" name="<?php echo esc_attr($this->get_field_name('cards')); ?>" placeholder='[{"title":"高速","desc":"…","size":"large","color":"blue","icon":"fas fa-bolt"}]'><?php echo esc_textarea((string) $json); ?></textarea>
|
||||
</label>
|
||||
<small><?php esc_html_e('size: large|medium|small;color: blue/pink/…', 'aksrt'); ?></small>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('入场动画', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('obs_animation')); ?>">
|
||||
<option value="" <?php selected(($instance['obs_animation'] ?? 'slideup'), ''); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
|
||||
<option value="fade" <?php selected(($instance['obs_animation'] ?? 'slideup'), 'fade'); ?>><?php esc_html_e('淡入', 'aksrt'); ?></option>
|
||||
<option value="slideup" <?php selected(($instance['obs_animation'] ?? 'slideup'), 'slideup'); ?>><?php esc_html_e('上滑', 'aksrt'); ?></option>
|
||||
<option value="zoomin" <?php selected(($instance['obs_animation'] ?? 'slideup'), 'zoomin'); ?>><?php esc_html_e('放大', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('go_link')); ?>" value="1" <?php checked(!empty($instance['go_link'])); ?>> <?php esc_html_e('外链跳转页', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $new_instance
|
||||
* @param array<string,mixed> $old_instance
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$business = [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'cards' => $this->normalize($new_instance['cards'] ?? []),
|
||||
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
|
||||
'obs_animation' => sanitize_key((string) ($new_instance['obs_animation'] ?? 'slideup')),
|
||||
'animation_repeat' => !empty($new_instance['animation_repeat']) ? 1 : 0,
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
/**
|
||||
* Icon cover cards (zibll zib_widget_ui_icon_cover_card → aksrt_icon_cover_card).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetIconCoverCards extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_icon_cover_cards',
|
||||
esc_html__('AKSRT 图标封面卡', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('横向图标 + 标题/描述的导航卡片。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_icon_cover_cards',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $args
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function widget($args, $instance): void {
|
||||
$cards = $this->normalize($instance['cards'] ?? []);
|
||||
if (!$cards) {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('请添加至少一张图标封面卡', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<p class="widget-empty">' . esc_html__('请添加至少一张图标封面卡', 'aksrt') . '</p>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
$pc_row = max(1, min(4, (int) ($instance['pc_row'] ?? 2)));
|
||||
$m_row = max(1, min(2, (int) ($instance['m_row'] ?? 1)));
|
||||
$icon_size = max(16, min(48, (int) ($instance['icon_size'] ?? 25)));
|
||||
$radius = !empty($instance['icon_radius4']);
|
||||
$go_link = !empty($instance['go_link']);
|
||||
$show_bg = !empty($instance['show_widget_bg']);
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
$is_row = count($cards) > 1;
|
||||
$pc_span = max(1, (int) floor(12 / $pc_row));
|
||||
$m_span = max(1, (int) floor(12 / $m_row));
|
||||
$row_class = 'col-sm-' . $pc_span . ($m_row > 1 ? ' col-xs-' . $m_span : '');
|
||||
|
||||
echo $show_bg ? '<div class="zib-widget padding-10">' : '<div class="mb10">';
|
||||
if ($is_row) {
|
||||
echo '<div class="row gutters-5">';
|
||||
}
|
||||
|
||||
foreach ($cards as $card) {
|
||||
$url = (string) ($card['url'] ?? '');
|
||||
if ($url !== '' && $go_link && function_exists('aksrt_go_link')) {
|
||||
$url = aksrt_go_link($url, true);
|
||||
}
|
||||
$card_args = [
|
||||
'class' => $show_bg ? 'padding-10' : 'zib-widget mb10',
|
||||
'icon' => (string) ($card['icon'] ?? ''),
|
||||
'icon_image' => (string) ($card['icon_image'] ?? ''),
|
||||
'icon_size' => $icon_size,
|
||||
'icon_class' => $radius ? 'radius4' : '',
|
||||
'icon_color' => (string) ($card['icon_color'] ?? ''),
|
||||
'title' => (string) ($card['title'] ?? ''),
|
||||
'desc' => (string) ($card['desc'] ?? ''),
|
||||
'link' => [
|
||||
'url' => $url,
|
||||
'target' => (string) ($card['target'] ?? ''),
|
||||
],
|
||||
];
|
||||
|
||||
if ($is_row) {
|
||||
echo '<div class="' . esc_attr($row_class) . '">';
|
||||
}
|
||||
if (function_exists('aksrt_icon_cover_card')) {
|
||||
echo aksrt_icon_cover_card($card_args); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
if ($is_row) {
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
if ($is_row) {
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return array<int, array<string,string>>
|
||||
*/
|
||||
private function normalize($raw): array {
|
||||
if (is_string($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
$raw = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($raw as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$title = sanitize_text_field((string) ($row['title'] ?? ''));
|
||||
$icon = sanitize_text_field((string) ($row['icon'] ?? ''));
|
||||
$icon_image = esc_url_raw(trim((string) ($row['icon_image'] ?? $row['customize_icon'] ?? '')));
|
||||
if ($title === '' && $icon === '' && $icon_image === '') {
|
||||
continue;
|
||||
}
|
||||
$url = '';
|
||||
$target = '';
|
||||
if (is_array($row['link'] ?? null)) {
|
||||
$url = esc_url_raw(trim((string) ($row['link']['url'] ?? '')));
|
||||
$target = sanitize_text_field((string) ($row['link']['target'] ?? ''));
|
||||
} else {
|
||||
$url = esc_url_raw(trim((string) ($row['url'] ?? $row['link'] ?? '')));
|
||||
$target = sanitize_text_field((string) ($row['target'] ?? '_blank'));
|
||||
}
|
||||
$out[] = [
|
||||
'title' => $title,
|
||||
'desc' => sanitize_text_field((string) ($row['desc'] ?? '')),
|
||||
'icon' => $icon,
|
||||
'icon_image' => $icon_image,
|
||||
'icon_color' => sanitize_html_class((string) ($row['icon_color'] ?? '')),
|
||||
'url' => $url,
|
||||
'target' => $target,
|
||||
];
|
||||
if (count($out) >= 16) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function form($instance): void {
|
||||
$json = is_string($instance['cards'] ?? null)
|
||||
? (string) $instance['cards']
|
||||
: wp_json_encode($this->normalize($instance['cards'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('卡片 JSON', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="7" name="<?php echo esc_attr($this->get_field_name('cards')); ?>" placeholder='[{"title":"文档","desc":"使用说明","icon":"fas fa-book","url":"https://"}]'><?php echo esc_textarea((string) $json); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('PC 列', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="4" name="<?php echo esc_attr($this->get_field_name('pc_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['pc_row'] ?? 2))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('移动列', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="2" name="<?php echo esc_attr($this->get_field_name('m_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['m_row'] ?? 1))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('图标字号', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="16" max="48" name="<?php echo esc_attr($this->get_field_name('icon_size')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['icon_size'] ?? 25))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('icon_radius4')); ?>" value="1" <?php checked(!empty($instance['icon_radius4'])); ?>> <?php esc_html_e('图标圆角方形', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_widget_bg')); ?>" value="1" <?php checked(!empty($instance['show_widget_bg'])); ?>> <?php esc_html_e('模块背景', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('go_link')); ?>" value="1" <?php checked(!empty($instance['go_link'])); ?>> <?php esc_html_e('外链跳转页', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $new_instance
|
||||
* @param array<string,mixed> $old_instance
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$business = [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'cards' => $this->normalize($new_instance['cards'] ?? []),
|
||||
'pc_row' => max(1, min(4, (int) ($new_instance['pc_row'] ?? 2))),
|
||||
'm_row' => max(1, min(2, (int) ($new_instance['m_row'] ?? 1))),
|
||||
'icon_size' => max(16, min(48, (int) ($new_instance['icon_size'] ?? 25))),
|
||||
'icon_radius4' => !empty($new_instance['icon_radius4']) ? 1 : 0,
|
||||
'show_widget_bg' => !empty($new_instance['show_widget_bg']) ? 1 : 0,
|
||||
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
/**
|
||||
* Super embed / iframe widget (zibll zib_widget_ui_iframe).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetIframe extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_iframe',
|
||||
esc_html__('AKSRT 超级嵌入', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('嵌入在线视频/音乐播放器或任意 iframe 内容。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_iframe',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $args
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function widget($args, $instance): void {
|
||||
$raw = trim((string) ($instance['url'] ?? ''));
|
||||
if ($raw === '') {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('请配置 URL 或 iframe 代码', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<div class="c-red muted-box mb20"><b>' . esc_html__('超级嵌入:', 'aksrt') . '</b>' . esc_html__('请配置 URL 或 iframe 代码', 'aksrt') . '</div>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
$aspect = max(20, min(300, (int) ($instance['aspect'] ?? 55)));
|
||||
$allow_fs = !empty($instance['allowfullscreen']);
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
$iframe_html = '';
|
||||
if (stripos($raw, '<iframe') !== false && stripos($raw, '</iframe>') !== false) {
|
||||
$iframe_html = wp_kses(
|
||||
$raw,
|
||||
[
|
||||
'iframe' => [
|
||||
'src' => true,
|
||||
'width' => true,
|
||||
'height' => true,
|
||||
'frameborder' => true,
|
||||
'allow' => true,
|
||||
'allowfullscreen' => true,
|
||||
'loading' => true,
|
||||
'class' => true,
|
||||
'style' => true,
|
||||
'referrerpolicy' => true,
|
||||
'title' => true,
|
||||
'sandbox' => true,
|
||||
'data-src' => true,
|
||||
'framespacing' => true,
|
||||
'border' => true,
|
||||
],
|
||||
]
|
||||
);
|
||||
// Prefer lazy: if iframe has src but no data-src, convert.
|
||||
if (preg_match('/<iframe\b[^>]*>/i', $iframe_html, $m)
|
||||
&& stripos($m[0], 'data-src=') === false
|
||||
&& preg_match('/\ssrc=["\']([^"\']+)["\']/i', $m[0], $sm)
|
||||
) {
|
||||
$src = $sm[1];
|
||||
$tag = preg_replace('/\ssrc=["\'][^"\']*["\']/i', ' src="" data-src="' . esc_url($src) . '"', $m[0], 1);
|
||||
if (is_string($tag) && stripos($tag, 'lazyload') === false) {
|
||||
if (preg_match('/\sclass=["\']([^"\']*)["\']/', $tag)) {
|
||||
$tag = preg_replace('/\sclass=["\']([^"\']*)["\']/', ' class="$1 lazyload aksrt-lazy"', $tag, 1);
|
||||
} else {
|
||||
$tag = preg_replace('/^<iframe\b/i', '<iframe class="lazyload aksrt-lazy"', $tag, 1);
|
||||
}
|
||||
}
|
||||
if (is_string($tag)) {
|
||||
$iframe_html = str_replace($m[0], $tag, $iframe_html);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$url = esc_url($raw);
|
||||
if ($url === '') {
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
return;
|
||||
}
|
||||
$fs = $allow_fs ? ' allowfullscreen="allowfullscreen"' : '';
|
||||
// zibll: class="lazyload" + data-src (not immediate src).
|
||||
$iframe_html = '<iframe class="lazyload aksrt-lazy aksrt-iframe-el" src="" data-src="' . $url . '" loading="lazy" frameborder="0" framespacing="0" border="0"' . $fs . '></iframe>';
|
||||
}
|
||||
|
||||
// zibll shell: mb20 > wp-block-embed is-type-video relative-h radius8 > padding-bottom.
|
||||
echo '<div class="mb20 aksrt-iframe-embed">';
|
||||
echo '<div class="wp-block-embed is-type-video relative-h radius8">';
|
||||
echo '<div class="aksrt-iframe-ratio" style="padding-bottom:' . esc_attr((string) $aspect) . '%">';
|
||||
echo $iframe_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '</div></div></div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function form($instance): void {
|
||||
$title = (string) ($instance['title'] ?? '');
|
||||
$url = (string) ($instance['url'] ?? '');
|
||||
$aspect = (int) ($instance['aspect'] ?? 55);
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr($title); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('嵌入地址或 iframe 代码', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="4" name="<?php echo esc_attr($this->get_field_name('url')); ?>"><?php echo esc_textarea($url); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('高度占比 %', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="20" max="300" step="5" name="<?php echo esc_attr($this->get_field_name('aspect')); ?>" value="<?php echo esc_attr((string) $aspect); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('allowfullscreen')); ?>" value="1" <?php checked(!empty($instance['allowfullscreen'])); ?>> <?php esc_html_e('允许全屏', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $new_instance
|
||||
* @param array<string,mixed> $old_instance
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$business = [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'url' => trim((string) ($new_instance['url'] ?? '')),
|
||||
'aspect' => max(20, min(300, (int) ($new_instance['aspect'] ?? 55))),
|
||||
'allowfullscreen' => !empty($new_instance['allowfullscreen']) ? 1 : 0,
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
/**
|
||||
* Language switcher widget (Polylang / WPML / filter).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetLangSwitch extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_lang_switch',
|
||||
__('语言切换器', 'aksrt'),
|
||||
[
|
||||
'description' => __('在侧栏显示多语言切换按钮。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_lang_switch',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $args
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function widget($args, $instance): void {
|
||||
$title = !empty($instance['title']) ? (string) $instance['title'] : '';
|
||||
$style = sanitize_key((string) ($instance['style'] ?? 'dropdown'));
|
||||
if (!in_array($style, ['dropdown', 'flags', 'inline'], true)) {
|
||||
$style = 'dropdown';
|
||||
}
|
||||
$show_name = !isset($instance['show_name']) || !empty($instance['show_name']);
|
||||
$hide_current = !empty($instance['hide_current']);
|
||||
|
||||
if (!function_exists('aksrt_has_multilingual') || !aksrt_has_multilingual()) {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '<div class="c-red muted-box mb10"><b>' . esc_html__('语言切换:', 'aksrt') . '</b>' .
|
||||
esc_html__('未检测到多语言插件(Polylang/WPML)或可用语言不足。', 'aksrt') . '</div>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// inline style: treat as flags row without dropdown chrome.
|
||||
$render_style = $style === 'inline' ? 'flags' : $style;
|
||||
$html = function_exists('aksrt_render_language_switcher') ? aksrt_render_language_switcher($render_style) : '';
|
||||
if ($html === '') {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('暂无可用语言', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<p class="widget-empty">' . esc_html__('暂无可用语言', 'aksrt') . '</p>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Optional modifiers via wrapper classes.
|
||||
$wrap = 'aksrt-lang-switch-widget style-' . esc_attr($style);
|
||||
if (!$show_name) {
|
||||
$wrap .= ' hide-name';
|
||||
}
|
||||
if ($hide_current) {
|
||||
$wrap .= ' hide-current';
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
echo '<div class="' . esc_attr($wrap) . '">' . $html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function form($instance): void {
|
||||
$title = !empty($instance['title']) ? (string) $instance['title'] : '';
|
||||
$style = !empty($instance['style']) ? (string) $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>
|
||||
<option value="inline" <?php selected($style, 'inline'); ?>><?php esc_html_e('横向排列', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_name')); ?>" value="1" <?php checked(!isset($instance['show_name']) || !empty($instance['show_name'])); ?>> <?php esc_html_e('显示语言名', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hide_current')); ?>" value="1" <?php checked(!empty($instance['hide_current'])); ?>> <?php esc_html_e('隐藏当前语言', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<p><small><?php esc_html_e('需要安装 Polylang 或 WPML 等多语言插件才能生效。', 'aksrt'); ?></small></p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $new_instance
|
||||
* @param array<string,mixed> $old_instance
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$style = sanitize_key((string) ($new_instance['style'] ?? 'dropdown'));
|
||||
if (!in_array($style, ['dropdown', 'flags', 'inline'], true)) {
|
||||
$style = 'dropdown';
|
||||
}
|
||||
$business = [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'style' => $style,
|
||||
'show_name' => !empty($new_instance['show_name']) ? 1 : 0,
|
||||
'hide_current' => !empty($new_instance['hide_current']) ? 1 : 0,
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,487 @@
|
||||
<?php
|
||||
/**
|
||||
* Main posts list (zibll zib_widget_ui_main_post + ajax pager).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetMainPosts extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_main_posts',
|
||||
esc_html__('AKSRT 文章列表', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('卡片/迷你/列表样式的文章模块(支持 AJAX)。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_main_posts',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $instance
|
||||
* @param int $paged
|
||||
* @return array
|
||||
*/
|
||||
public static function build_query_args(array $instance, int $paged = 1): array {
|
||||
$count = max(1, min(30, (int) ($instance['count'] ?? 6)));
|
||||
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
|
||||
$cat = (int) ($instance['cat'] ?? 0);
|
||||
$limit_day = max(0, (int) ($instance['limit_day'] ?? 0));
|
||||
// Zibll `order` parity: asc/desc. Default desc.
|
||||
$order = strtoupper(sanitize_key((string) ($instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC';
|
||||
|
||||
$query = [
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $count,
|
||||
'paged' => max(1, $paged),
|
||||
'ignore_sticky_posts' => true,
|
||||
];
|
||||
if ($cat > 0) {
|
||||
$query['cat'] = $cat;
|
||||
}
|
||||
if ($limit_day > 0) {
|
||||
$query['date_query'] = [['after' => $limit_day . ' days ago']];
|
||||
}
|
||||
switch ($orderby) {
|
||||
case 'views':
|
||||
$query['meta_key'] = 'aksrt_post_views';
|
||||
$query['orderby'] = 'meta_value_num';
|
||||
$query['order'] = $order;
|
||||
break;
|
||||
case 'comments':
|
||||
$query['orderby'] = 'comment_count';
|
||||
$query['order'] = $order;
|
||||
break;
|
||||
case 'likes':
|
||||
$query['meta_key'] = '_aksrt_like_count';
|
||||
$query['orderby'] = 'meta_value_num';
|
||||
$query['order'] = $order;
|
||||
break;
|
||||
case 'favorites':
|
||||
$query['meta_key'] = '_aksrt_favorite_count';
|
||||
$query['orderby'] = 'meta_value_num';
|
||||
$query['order'] = $order;
|
||||
break;
|
||||
case 'downloads':
|
||||
$query['meta_key'] = '_aksrt_res_download_count';
|
||||
$query['orderby'] = 'meta_value_num';
|
||||
$query['order'] = $order;
|
||||
break;
|
||||
case 'rand':
|
||||
$query['orderby'] = 'rand';
|
||||
break;
|
||||
case 'modified':
|
||||
$query['orderby'] = 'modified';
|
||||
$query['order'] = $order;
|
||||
break;
|
||||
default:
|
||||
$query['orderby'] = 'date';
|
||||
$query['order'] = $order;
|
||||
}
|
||||
|
||||
// Topics taxonomy (CSV term IDs).
|
||||
$topics_raw = trim((string) ($instance['topics'] ?? ''));
|
||||
if ($topics_raw !== '' && taxonomy_exists('topics')) {
|
||||
$topic_ids = array_values(array_filter(array_map('intval', preg_split('/[,,\s]+/', $topics_raw) ?: [])));
|
||||
if ($topic_ids) {
|
||||
$query['tax_query'] = [
|
||||
[
|
||||
'taxonomy' => 'topics',
|
||||
'field' => 'term_id',
|
||||
'terms' => $topic_ids,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Pay type filter (reading/download/gallery/video/vip or any paid).
|
||||
$pay_type = sanitize_key((string) ($instance['pay_type'] ?? ''));
|
||||
if ($pay_type !== '' && $pay_type !== '0' && $pay_type !== 'no') {
|
||||
$mq = isset($query['meta_query']) && is_array($query['meta_query']) ? $query['meta_query'] : [];
|
||||
if ($pay_type === 'any' || $pay_type === 'paid') {
|
||||
$mq[] = [
|
||||
'relation' => 'OR',
|
||||
[
|
||||
'key' => '_aksrt_pay_price',
|
||||
'value' => 0,
|
||||
'compare' => '>',
|
||||
'type' => 'NUMERIC',
|
||||
],
|
||||
[
|
||||
'key' => '_aksrt_pay_type',
|
||||
'value' => ['reading', 'download', 'gallery', 'video', 'vip'],
|
||||
'compare' => 'IN',
|
||||
],
|
||||
[
|
||||
'key' => '_aksrt_visibility',
|
||||
'value' => ['pay', 'credits'],
|
||||
'compare' => 'IN',
|
||||
],
|
||||
];
|
||||
} else {
|
||||
$mq[] = [
|
||||
'key' => '_aksrt_pay_type',
|
||||
'value' => $pay_type,
|
||||
];
|
||||
}
|
||||
if ($mq) {
|
||||
if (count($mq) > 1) {
|
||||
$mq['relation'] = 'AND';
|
||||
}
|
||||
$query['meta_query'] = $mq;
|
||||
}
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public static function render_items_html(\WP_Query $q, array $instance): string {
|
||||
if (!$q->have_posts()) {
|
||||
return '';
|
||||
}
|
||||
$style = sanitize_key((string) ($instance['style'] ?? 'list'));
|
||||
if (!in_array($style, ['list', 'card', 'mini'], true)) {
|
||||
$style = 'list';
|
||||
}
|
||||
$show_thumb = !isset($instance['show_thumb']) || !empty($instance['show_thumb']);
|
||||
$show_meta = !isset($instance['show_meta']) || !empty($instance['show_meta']);
|
||||
$show_number = !empty($instance['show_number']);
|
||||
$start = max(0, (int) ($instance['_start_index'] ?? 0));
|
||||
|
||||
ob_start();
|
||||
// Outer wrapper only on page 1 full render handled by caller for ajax append items.
|
||||
$mode = $style === 'card' ? 'card' : ($style === 'mini' ? 'mini' : 'list');
|
||||
if (function_exists('aksrt_render_post')) {
|
||||
$html = '';
|
||||
$i = $start;
|
||||
while ($q->have_posts()) {
|
||||
$q->the_post();
|
||||
$i++;
|
||||
$html .= aksrt_render_post([
|
||||
'mode' => $mode,
|
||||
'context' => 'widget',
|
||||
'show_thumb' => $show_thumb,
|
||||
'show_meta' => $show_meta,
|
||||
'show_excerpt' => $mode === 'list',
|
||||
'show_number' => $show_number && $mode === 'mini',
|
||||
'number_offset' => $show_number ? $i : 0,
|
||||
'extra_class' => 'ajax-item',
|
||||
]);
|
||||
}
|
||||
wp_reset_postdata();
|
||||
return $html;
|
||||
}
|
||||
|
||||
$placeholder = get_template_directory_uri() . '/assets/img/placeholder.svg';
|
||||
$i = $start;
|
||||
while ($q->have_posts()) {
|
||||
$q->the_post();
|
||||
$i++;
|
||||
$thumb = $show_thumb ? get_the_post_thumbnail_url(get_the_ID(), $style === 'card' ? 'medium' : 'thumbnail') : '';
|
||||
$views = (int) get_post_meta(get_the_ID(), 'aksrt_post_views', true);
|
||||
|
||||
if ($style === 'card') {
|
||||
echo '<a class="aksrt-main-posts__card ajax-item" href="' . esc_url(get_permalink()) . '">';
|
||||
echo '<div class="aksrt-main-posts__thumb' . ($thumb ? '' : ' is-empty') . '">';
|
||||
if ($thumb) {
|
||||
echo '<img class="aksrt-lazy lazyload" data-src="' . esc_url($thumb) . '" src="' . esc_url($placeholder) . '" alt="" loading="lazy" />';
|
||||
}
|
||||
echo '</div>';
|
||||
echo '<div class="aksrt-main-posts__body">';
|
||||
echo '<div class="aksrt-main-posts__title">' . esc_html(get_the_title()) . '</div>';
|
||||
if ($show_meta) {
|
||||
echo '<div class="aksrt-main-posts__meta"><span>' . esc_html(get_the_date()) . '</span>';
|
||||
if ($views > 0) {
|
||||
echo '<span><i class="fas fa-eye"></i> ' . esc_html((string) $views) . '</span>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div></a>';
|
||||
} else {
|
||||
echo '<a class="aksrt-main-posts__row ajax-item" href="' . esc_url(get_permalink()) . '">';
|
||||
if ($show_number) {
|
||||
echo '<span class="aksrt-main-posts__num">' . esc_html((string) $i) . '</span>';
|
||||
}
|
||||
if ($show_thumb) {
|
||||
echo '<span class="aksrt-main-posts__thumb mini' . ($thumb ? '' : ' is-empty') . '">';
|
||||
if ($thumb) {
|
||||
echo '<img class="aksrt-lazy lazyload" data-src="' . esc_url($thumb) . '" src="' . esc_url($placeholder) . '" alt="" loading="lazy" />';
|
||||
}
|
||||
echo '</span>';
|
||||
}
|
||||
echo '<span class="aksrt-main-posts__body">';
|
||||
echo '<span class="aksrt-main-posts__title">' . esc_html(get_the_title()) . '</span>';
|
||||
if ($show_meta) {
|
||||
echo '<span class="aksrt-main-posts__meta">' . esc_html(get_the_date()) . '</span>';
|
||||
}
|
||||
echo '</span></a>';
|
||||
}
|
||||
}
|
||||
wp_reset_postdata();
|
||||
return (string) ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{html:string,paginate_html:string,has_more:bool,page:int,max_pages:int,total:int}
|
||||
*/
|
||||
public static function query_payload(array $instance, int $page = 1): array {
|
||||
$page = max(1, $page);
|
||||
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'none'));
|
||||
$count = max(1, min(30, (int) ($instance['count'] ?? 6)));
|
||||
$instance['_start_index'] = ($page - 1) * $count;
|
||||
$q = new \WP_Query(self::build_query_args($instance, $page));
|
||||
$html = self::render_items_html($q, $instance);
|
||||
if ($html === '' && $page === 1) {
|
||||
$html = function_exists('aksrt_get_ajax_null')
|
||||
? aksrt_get_ajax_null(__('暂无文章', 'aksrt'))
|
||||
: '<p class="aksrt-main-posts__empty ajax-item">' . esc_html__('暂无文章', 'aksrt') . '</p>';
|
||||
}
|
||||
$max_pages = max(1, (int) $q->max_num_pages);
|
||||
$total = (int) $q->found_posts;
|
||||
$has_more = $page < $max_pages;
|
||||
$base_qs = array_filter([
|
||||
'count' => $count,
|
||||
'orderby' => sanitize_key((string) ($instance['orderby'] ?? 'date')),
|
||||
'order' => strtoupper(sanitize_key((string) ($instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
|
||||
'cat' => (int) ($instance['cat'] ?? 0),
|
||||
'limit_day' => (int) ($instance['limit_day'] ?? 0),
|
||||
'topics' => (string) ($instance['topics'] ?? ''),
|
||||
'pay_type' => sanitize_key((string) ($instance['pay_type'] ?? '')),
|
||||
'style' => sanitize_key((string) ($instance['style'] ?? 'list')),
|
||||
'show_thumb' => !isset($instance['show_thumb']) || !empty($instance['show_thumb']) ? 1 : 0,
|
||||
'show_meta' => !isset($instance['show_meta']) || !empty($instance['show_meta']) ? 1 : 0,
|
||||
'show_number' => !empty($instance['show_number']) ? 1 : 0,
|
||||
'paginate' => $paginate,
|
||||
'load_mode' => sanitize_key((string) ($instance['load_mode'] ?? 'detail')) === 'ajax' ? 'ajax' : 'detail',
|
||||
], static function ($v) {
|
||||
return $v !== '' && $v !== null;
|
||||
});
|
||||
$rest_base = add_query_arg($base_qs, rest_url('aksrt/v1/widgets/posts'));
|
||||
$paginate_html = '';
|
||||
if ($paginate === 'ajax' && $has_more) {
|
||||
$next = add_query_arg(['page' => $page + 1], $rest_base);
|
||||
$paginate_html = function_exists('aksrt_get_ajax_next_paginate')
|
||||
? aksrt_get_ajax_next_paginate($total, $page, $count, $next)
|
||||
: '<div class="text-center theme-pagination ajax-pag"><div class="next-page ajax-next"><a href="' . esc_url($next) . '">' . esc_html__('加载更多', 'aksrt') . '</a></div></div>';
|
||||
} elseif ($paginate === 'number' && $max_pages > 1) {
|
||||
$paginate_html = function_exists('aksrt_get_ajax_number_paginate')
|
||||
? aksrt_get_ajax_number_paginate($total, $page, $count, $rest_base)
|
||||
: '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
|
||||
} else {
|
||||
$paginate_html = '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
|
||||
}
|
||||
return [
|
||||
'html' => $html,
|
||||
'paginate_html' => $paginate_html,
|
||||
'has_more' => $has_more,
|
||||
'page' => $page,
|
||||
'max_pages' => $max_pages,
|
||||
'total' => $total,
|
||||
];
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$style = sanitize_key((string) ($instance['style'] ?? 'list'));
|
||||
if (!in_array($style, ['list', 'card', 'mini'], true)) {
|
||||
$style = 'list';
|
||||
}
|
||||
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'none'));
|
||||
if (!in_array($paginate, ['none', 'ajax', 'number'], true)) {
|
||||
$paginate = 'none';
|
||||
}
|
||||
$load_mode = sanitize_key((string) ($instance['load_mode'] ?? 'detail'));
|
||||
if (!in_array($load_mode, ['detail', 'ajax'], true)) {
|
||||
$load_mode = 'detail';
|
||||
}
|
||||
$instance['style'] = $style;
|
||||
$instance['paginate'] = $paginate;
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
$count = max(1, min(30, (int) ($instance['count'] ?? 6)));
|
||||
$rest = rest_url('aksrt/v1/widgets/posts');
|
||||
$qs = http_build_query(array_filter([
|
||||
'page' => 1,
|
||||
'count' => $count,
|
||||
'orderby' => sanitize_key((string) ($instance['orderby'] ?? 'date')),
|
||||
'order' => strtoupper(sanitize_key((string) ($instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
|
||||
'cat' => (int) ($instance['cat'] ?? 0),
|
||||
'limit_day' => (int) ($instance['limit_day'] ?? 0),
|
||||
'topics' => (string) ($instance['topics'] ?? ''),
|
||||
'pay_type' => sanitize_key((string) ($instance['pay_type'] ?? '')),
|
||||
'style' => $style,
|
||||
'show_thumb' => !isset($instance['show_thumb']) || !empty($instance['show_thumb']) ? 1 : 0,
|
||||
'show_meta' => !isset($instance['show_meta']) || !empty($instance['show_meta']) ? 1 : 0,
|
||||
'show_number' => !empty($instance['show_number']) ? 1 : 0,
|
||||
'paginate' => $paginate,
|
||||
'load_mode' => sanitize_key((string) ($instance['load_mode'] ?? 'detail')) === 'ajax' ? 'ajax' : 'detail',
|
||||
], static function ($v) {
|
||||
return $v !== '' && $v !== null;
|
||||
}));
|
||||
$affix = function_exists('aksrt_widget_affix_attrs') ? aksrt_widget_affix_attrs($instance) : ['class' => '', 'attr' => ''];
|
||||
$wrap_cls = 'aksrt-main-posts style-' . $style . ' ajaxpager';
|
||||
if ($style === 'mini') {
|
||||
$wrap_cls .= ' zib-widget posts-mini-lists';
|
||||
}
|
||||
$wrap_cls .= $affix['class'];
|
||||
echo '<div class="' . esc_attr($wrap_cls) . '" data-aksrt-ajaxpager data-aksrt-ajax-url="' . esc_url($rest) . '" data-load-mode="' . esc_attr($load_mode) . '" data-query="' . esc_attr($qs) . '"' . ($load_mode === 'ajax' ? ' data-loaded="0"' : ' data-loaded="1"') . $affix['attr'] . '>';
|
||||
|
||||
if ($load_mode === 'ajax') {
|
||||
// Skeleton first paint (zibll load_mode=ajax).
|
||||
echo '<div class="aksrt-main-posts__skeleton">';
|
||||
for ($si = 0; $si < $count; $si++) {
|
||||
if ($style === 'card') {
|
||||
echo '<div class="placeholder-row card"><div class="placeholder thumb"></div><div class="placeholder k1"></div></div>';
|
||||
} else {
|
||||
echo '<div class="posts-mini placeholder-row"><div class="placeholder radius thumb-placeholder"></div><div class="flex1"><div class="placeholder t1 k1"></div><div class="placeholder s1"></div></div></div>';
|
||||
}
|
||||
}
|
||||
echo '</div>';
|
||||
} else {
|
||||
$payload = self::query_payload($instance, 1);
|
||||
echo $payload['html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $payload['paginate_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
echo '<div class="post_ajax_loader" data-aksrt-ajax-loader data-aksrt-ajax-loader-hide hidden></div>';
|
||||
echo '</div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('数量', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="30" name="<?php echo esc_attr($this->get_field_name('count')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['count'] ?? 6))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('样式', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('style')); ?>">
|
||||
<option value="list" <?php selected(($instance['style'] ?? 'list'), 'list'); ?>><?php esc_html_e('列表', 'aksrt'); ?></option>
|
||||
<option value="mini" <?php selected(($instance['style'] ?? ''), 'mini'); ?>><?php esc_html_e('迷你', 'aksrt'); ?></option>
|
||||
<option value="card" <?php selected(($instance['style'] ?? ''), 'card'); ?>><?php esc_html_e('卡片', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('排序', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
|
||||
<option value="date" <?php selected(($instance['orderby'] ?? 'date'), 'date'); ?>><?php esc_html_e('时间', 'aksrt'); ?></option>
|
||||
<option value="modified" <?php selected(($instance['orderby'] ?? ''), 'modified'); ?>><?php esc_html_e('更新', 'aksrt'); ?></option>
|
||||
<option value="views" <?php selected(($instance['orderby'] ?? ''), 'views'); ?>><?php esc_html_e('浏览', 'aksrt'); ?></option>
|
||||
<option value="comments" <?php selected(($instance['orderby'] ?? ''), 'comments'); ?>><?php esc_html_e('评论', 'aksrt'); ?></option>
|
||||
<option value="likes" <?php selected(($instance['orderby'] ?? ''), 'likes'); ?>><?php esc_html_e('点赞', 'aksrt'); ?></option>
|
||||
<option value="favorites" <?php selected(($instance['orderby'] ?? ''), 'favorites'); ?>><?php esc_html_e('收藏', 'aksrt'); ?></option>
|
||||
<option value="downloads" <?php selected(($instance['orderby'] ?? ''), 'downloads'); ?>><?php esc_html_e('下载', 'aksrt'); ?></option>
|
||||
<option value="rand" <?php selected(($instance['orderby'] ?? ''), 'rand'); ?>><?php esc_html_e('随机', 'aksrt'); ?></option>
|
||||
</select>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('order')); ?>">
|
||||
<option value="desc" <?php selected(($instance['order'] ?? 'desc'), 'desc'); ?>><?php esc_html_e('降序', 'aksrt'); ?></option>
|
||||
<option value="asc" <?php selected(($instance['order'] ?? ''), 'asc'); ?>><?php esc_html_e('升序', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('分类 ID(0=全部)', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('cat')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['cat'] ?? 0))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('最近 N 天(0=不限)', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('limit_day')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['limit_day'] ?? 0))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('模块加载', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('load_mode')); ?>">
|
||||
<option value="detail" <?php selected(($instance['load_mode'] ?? 'detail'), 'detail'); ?>><?php esc_html_e('直接加载', 'aksrt'); ?></option>
|
||||
<option value="ajax" <?php selected(($instance['load_mode'] ?? ''), 'ajax'); ?>><?php esc_html_e('AJAX 懒加载', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('分页', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('paginate')); ?>">
|
||||
<option value="none" <?php selected(($instance['paginate'] ?? 'none'), 'none'); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
|
||||
<option value="ajax" <?php selected(($instance['paginate'] ?? ''), 'ajax'); ?>><?php esc_html_e('AJAX 加载更多', 'aksrt'); ?></option>
|
||||
<option value="number" <?php selected(($instance['paginate'] ?? ''), 'number'); ?>><?php esc_html_e('数字分页', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('专题 topics ID(逗号)', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('topics')); ?>" value="<?php echo esc_attr((string) ($instance['topics'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('付费类型', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('pay_type')); ?>">
|
||||
<option value="" <?php selected(($instance['pay_type'] ?? ''), ''); ?>><?php esc_html_e('不限', 'aksrt'); ?></option>
|
||||
<option value="paid" <?php selected(($instance['pay_type'] ?? ''), 'paid'); ?>><?php esc_html_e('任意付费', 'aksrt'); ?></option>
|
||||
<option value="reading" <?php selected(($instance['pay_type'] ?? ''), 'reading'); ?>><?php esc_html_e('付费阅读', 'aksrt'); ?></option>
|
||||
<option value="download" <?php selected(($instance['pay_type'] ?? ''), 'download'); ?>><?php esc_html_e('付费下载', 'aksrt'); ?></option>
|
||||
<option value="gallery" <?php selected(($instance['pay_type'] ?? ''), 'gallery'); ?>><?php esc_html_e('付费图库', 'aksrt'); ?></option>
|
||||
<option value="video" <?php selected(($instance['pay_type'] ?? ''), 'video'); ?>><?php esc_html_e('付费视频', 'aksrt'); ?></option>
|
||||
<option value="vip" <?php selected(($instance['pay_type'] ?? ''), 'vip'); ?>><?php esc_html_e('会员专属', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_thumb')); ?>" value="1" <?php checked(!isset($instance['show_thumb']) || !empty($instance['show_thumb'])); ?>> <?php esc_html_e('显示缩略图', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_meta')); ?>" value="1" <?php checked(!isset($instance['show_meta']) || !empty($instance['show_meta'])); ?>> <?php esc_html_e('显示 meta', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_number')); ?>" value="1" <?php checked(!empty($instance['show_number'])); ?>> <?php esc_html_e('显示序号', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php $this->render_common_form_section($instance); ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$style = sanitize_key((string) ($new_instance['style'] ?? 'list'));
|
||||
if (!in_array($style, ['list', 'card', 'mini'], true)) {
|
||||
$style = 'list';
|
||||
}
|
||||
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'date'));
|
||||
if (!in_array($orderby, ['date', 'modified', 'views', 'comments', 'likes', 'favorites', 'downloads', 'rand'], true)) {
|
||||
$orderby = 'date';
|
||||
}
|
||||
$paginate = sanitize_key((string) ($new_instance['paginate'] ?? 'none'));
|
||||
if (!in_array($paginate, ['none', 'ajax', 'number'], true)) {
|
||||
$paginate = 'none';
|
||||
}
|
||||
return $this->update_common_fields($new_instance, $old_instance, [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'count' => max(1, min(30, (int) ($new_instance['count'] ?? 6))),
|
||||
'style' => $style,
|
||||
'orderby' => $orderby,
|
||||
'order' => strtoupper(sanitize_key((string) ($new_instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
|
||||
'cat' => max(0, (int) ($new_instance['cat'] ?? 0)),
|
||||
'limit_day' => max(0, (int) ($new_instance['limit_day'] ?? 0)),
|
||||
'topics' => sanitize_text_field((string) ($new_instance['topics'] ?? '')),
|
||||
'pay_type' => sanitize_key((string) ($new_instance['pay_type'] ?? '')),
|
||||
'paginate' => $paginate,
|
||||
'load_mode' => sanitize_key((string) ($new_instance['load_mode'] ?? 'detail')) === 'ajax' ? 'ajax' : 'detail',
|
||||
'show_thumb' => !empty($new_instance['show_thumb']) ? 1 : 0,
|
||||
'show_meta' => !empty($new_instance['show_meta']) ? 1 : 0,
|
||||
'show_number' => !empty($new_instance['show_number']) ? 1 : 0,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
<?php
|
||||
/**
|
||||
* Horizontal one-line posts scroller (zibll zib_widget_ui_oneline_posts).
|
||||
*
|
||||
* Uses shared post renderer + slider.js Type A (.swiper-scroll).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetOnelinePosts extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_oneline_posts',
|
||||
esc_html__('AKSRT 横向文章', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('横向滚动的文章卡片/迷你列表(共享 swiper-scroll)。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_oneline_posts',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public static function build_query_args(array $instance): array {
|
||||
$count = max(1, min(20, (int) ($instance['count'] ?? 8)));
|
||||
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
|
||||
$cat = (int) ($instance['cat'] ?? 0);
|
||||
$limit_day = max(0, (int) ($instance['limit_day'] ?? 0));
|
||||
$order = strtoupper(sanitize_key((string) ($instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC';
|
||||
$query = [
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $count,
|
||||
'ignore_sticky_posts' => true,
|
||||
'no_found_rows' => true,
|
||||
];
|
||||
if ($cat > 0) {
|
||||
$query['cat'] = $cat;
|
||||
}
|
||||
if ($limit_day > 0) {
|
||||
$query['date_query'] = [['after' => $limit_day . ' days ago']];
|
||||
}
|
||||
switch ($orderby) {
|
||||
case 'views':
|
||||
$query['meta_key'] = 'aksrt_post_views';
|
||||
$query['orderby'] = 'meta_value_num';
|
||||
$query['order'] = $order;
|
||||
break;
|
||||
case 'comments':
|
||||
$query['orderby'] = 'comment_count';
|
||||
$query['order'] = $order;
|
||||
break;
|
||||
case 'likes':
|
||||
$query['meta_key'] = '_aksrt_like_count';
|
||||
$query['orderby'] = 'meta_value_num';
|
||||
$query['order'] = $order;
|
||||
break;
|
||||
case 'favorites':
|
||||
$query['meta_key'] = '_aksrt_favorite_count';
|
||||
$query['orderby'] = 'meta_value_num';
|
||||
$query['order'] = $order;
|
||||
break;
|
||||
case 'modified':
|
||||
$query['orderby'] = 'modified';
|
||||
$query['order'] = $order;
|
||||
break;
|
||||
case 'rand':
|
||||
$query['orderby'] = 'rand';
|
||||
break;
|
||||
default:
|
||||
$query['orderby'] = 'date';
|
||||
$query['order'] = $order;
|
||||
}
|
||||
$topics_raw = trim((string) ($instance['topics'] ?? ''));
|
||||
if ($topics_raw !== '' && taxonomy_exists('topics')) {
|
||||
$topic_ids = array_values(array_filter(array_map('intval', preg_split('/[,,\s]+/', $topics_raw) ?: [])));
|
||||
if ($topic_ids) {
|
||||
$query['tax_query'] = [[
|
||||
'taxonomy' => 'topics',
|
||||
'field' => 'term_id',
|
||||
'terms' => $topic_ids,
|
||||
]];
|
||||
}
|
||||
}
|
||||
$pay_type = sanitize_key((string) ($instance['pay_type'] ?? ''));
|
||||
if ($pay_type !== '' && $pay_type !== '0' && $pay_type !== 'no') {
|
||||
if ($pay_type === 'any' || $pay_type === 'paid') {
|
||||
$query['meta_query'] = [
|
||||
'relation' => 'OR',
|
||||
['key' => '_aksrt_pay_price', 'value' => 0, 'compare' => '>', 'type' => 'NUMERIC'],
|
||||
['key' => '_aksrt_pay_type', 'value' => ['reading', 'download', 'gallery', 'video', 'vip'], 'compare' => 'IN'],
|
||||
['key' => '_aksrt_visibility', 'value' => ['pay', 'credits'], 'compare' => 'IN'],
|
||||
];
|
||||
} else {
|
||||
$query['meta_query'] = [['key' => '_aksrt_pay_type', 'value' => $pay_type]];
|
||||
}
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public static function render_html(array $instance): string {
|
||||
$style = sanitize_key((string) ($instance['style'] ?? 'card'));
|
||||
if (!in_array($style, ['card', 'mini', 'list'], true)) {
|
||||
$style = 'card';
|
||||
}
|
||||
|
||||
$q = new \WP_Query(self::build_query_args($instance));
|
||||
if (!$q->have_posts()) {
|
||||
wp_reset_postdata();
|
||||
return function_exists('aksrt_get_ajax_null')
|
||||
? aksrt_get_ajax_null(__('暂无文章', 'aksrt'))
|
||||
: '<p class="widget-empty ajax-item">' . esc_html__('暂无文章', 'aksrt') . '</p>';
|
||||
}
|
||||
|
||||
$mode = $style === 'mini' ? 'mini' : ($style === 'list' ? 'list' : 'card');
|
||||
$cfg = [
|
||||
'mode' => $mode,
|
||||
'context' => 'widget',
|
||||
'show_thumb' => true,
|
||||
'show_meta' => $style !== 'mini',
|
||||
'show_excerpt' => false,
|
||||
'show_author' => false,
|
||||
'show_comments' => false,
|
||||
'show_likes' => false,
|
||||
'show_tags' => false,
|
||||
'show_badges' => $style === 'card',
|
||||
'skip_wrapper' => true,
|
||||
'extra_class' => 'posts-item',
|
||||
];
|
||||
|
||||
$slides = '';
|
||||
if (function_exists('aksrt_render_post')) {
|
||||
while ($q->have_posts()) {
|
||||
$q->the_post();
|
||||
$item = aksrt_render_post($cfg);
|
||||
if ($item === '') {
|
||||
continue;
|
||||
}
|
||||
// Ensure freeMode width calc has a posts-item root when renderer wraps differently.
|
||||
if (strpos($item, 'posts-item') === false && strpos($item, 'posts-mini') === false) {
|
||||
$item = '<div class="posts-item">' . $item . '</div>';
|
||||
}
|
||||
$slides .= '<div class="swiper-slide ajax-item">' . $item . '</div>';
|
||||
}
|
||||
wp_reset_postdata();
|
||||
} else {
|
||||
// Fallback: bare thumb+title (should rarely hit).
|
||||
while ($q->have_posts()) {
|
||||
$q->the_post();
|
||||
$thumb = get_the_post_thumbnail_url(get_the_ID(), $style === 'card' ? 'medium' : 'thumbnail');
|
||||
$slides .= '<div class="swiper-slide ajax-item"><a class="posts-item aksrt-oneline-fallback" href="' . esc_url(get_permalink()) . '">';
|
||||
if ($thumb) {
|
||||
$slides .= '<div class="item-thumbnail"><img src="' . esc_url($thumb) . '" alt="" loading="lazy" /></div>';
|
||||
}
|
||||
$slides .= '<div class="item-body"><div class="item-heading">' . esc_html(get_the_title()) . '</div></div></a></div>';
|
||||
}
|
||||
wp_reset_postdata();
|
||||
}
|
||||
|
||||
if ($slides === '') {
|
||||
return function_exists('aksrt_get_ajax_null')
|
||||
? aksrt_get_ajax_null(__('暂无文章', 'aksrt'))
|
||||
: '<p class="widget-empty ajax-item">' . esc_html__('暂无文章', 'aksrt') . '</p>';
|
||||
}
|
||||
|
||||
// zibll: .widget-main-post.style-{card|mini|list} + .swiper-container.swiper-scroll data-slideClass="posts-item".
|
||||
ob_start();
|
||||
echo '<div class="widget-main-post style-' . esc_attr($style) . ' aksrt-oneline-posts ajax-item">';
|
||||
echo '<div class="swiper-container swiper-scroll aksrt-oneline-swiper" data-slideClass="posts-item">';
|
||||
echo '<div class="swiper-wrapper">';
|
||||
echo $slides; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '</div>';
|
||||
echo '<div class="swiper-button-prev" aria-label="' . esc_attr__('上一页', 'aksrt') . '"></div>';
|
||||
echo '<div class="swiper-button-next" aria-label="' . esc_attr__('下一页', 'aksrt') . '"></div>';
|
||||
echo '</div></div>';
|
||||
return (string) ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $args
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function widget($args, $instance): void {
|
||||
$html = self::render_html($instance);
|
||||
if ($html === '') {
|
||||
return;
|
||||
}
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function form($instance): void {
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('数量', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="20" name="<?php echo esc_attr($this->get_field_name('count')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['count'] ?? 8))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('样式', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('style')); ?>">
|
||||
<option value="card" <?php selected(($instance['style'] ?? 'card'), 'card'); ?>><?php esc_html_e('卡片', 'aksrt'); ?></option>
|
||||
<option value="mini" <?php selected(($instance['style'] ?? ''), 'mini'); ?>><?php esc_html_e('迷你', 'aksrt'); ?></option>
|
||||
<option value="list" <?php selected(($instance['style'] ?? ''), 'list'); ?>><?php esc_html_e('列表', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('排序', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
|
||||
<option value="date" <?php selected(($instance['orderby'] ?? 'date'), 'date'); ?>><?php esc_html_e('时间', 'aksrt'); ?></option>
|
||||
<option value="modified" <?php selected(($instance['orderby'] ?? ''), 'modified'); ?>><?php esc_html_e('更新', 'aksrt'); ?></option>
|
||||
<option value="views" <?php selected(($instance['orderby'] ?? ''), 'views'); ?>><?php esc_html_e('浏览', 'aksrt'); ?></option>
|
||||
<option value="comments" <?php selected(($instance['orderby'] ?? ''), 'comments'); ?>><?php esc_html_e('评论', 'aksrt'); ?></option>
|
||||
<option value="likes" <?php selected(($instance['orderby'] ?? ''), 'likes'); ?>><?php esc_html_e('点赞', 'aksrt'); ?></option>
|
||||
<option value="favorites" <?php selected(($instance['orderby'] ?? ''), 'favorites'); ?>><?php esc_html_e('收藏', 'aksrt'); ?></option>
|
||||
<option value="rand" <?php selected(($instance['orderby'] ?? ''), 'rand'); ?>><?php esc_html_e('随机', 'aksrt'); ?></option>
|
||||
</select>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('order')); ?>">
|
||||
<option value="desc" <?php selected(($instance['order'] ?? 'desc'), 'desc'); ?>><?php esc_html_e('降序', 'aksrt'); ?></option>
|
||||
<option value="asc" <?php selected(($instance['order'] ?? ''), 'asc'); ?>><?php esc_html_e('升序', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('分类 ID(0=全部)', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('cat')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['cat'] ?? 0))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('限制最近 N 天(0=不限)', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('limit_day')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['limit_day'] ?? 0))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('专题 topics ID(逗号)', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('topics')); ?>" value="<?php echo esc_attr((string) ($instance['topics'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('付费类型', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('pay_type')); ?>">
|
||||
<option value="" <?php selected(($instance['pay_type'] ?? ''), ''); ?>><?php esc_html_e('不限', 'aksrt'); ?></option>
|
||||
<option value="paid" <?php selected(($instance['pay_type'] ?? ''), 'paid'); ?>><?php esc_html_e('任意付费', 'aksrt'); ?></option>
|
||||
<option value="reading" <?php selected(($instance['pay_type'] ?? ''), 'reading'); ?>><?php esc_html_e('付费阅读', 'aksrt'); ?></option>
|
||||
<option value="download" <?php selected(($instance['pay_type'] ?? ''), 'download'); ?>><?php esc_html_e('付费下载', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $new_instance
|
||||
* @param array<string,mixed> $old_instance
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$style = sanitize_key((string) ($new_instance['style'] ?? 'card'));
|
||||
if (!in_array($style, ['card', 'mini', 'list'], true)) {
|
||||
$style = 'card';
|
||||
}
|
||||
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'date'));
|
||||
if (!in_array($orderby, ['date', 'modified', 'views', 'comments', 'likes', 'favorites', 'rand'], true)) {
|
||||
$orderby = 'date';
|
||||
}
|
||||
$business = [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'count' => max(1, min(20, (int) ($new_instance['count'] ?? 8))),
|
||||
'style' => $style,
|
||||
'orderby' => $orderby,
|
||||
'order' => strtoupper(sanitize_key((string) ($new_instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
|
||||
'cat' => max(0, (int) ($new_instance['cat'] ?? 0)),
|
||||
'limit_day' => max(0, (int) ($new_instance['limit_day'] ?? 0)),
|
||||
'topics' => sanitize_text_field((string) ($new_instance['topics'] ?? '')),
|
||||
'pay_type' => sanitize_key((string) ($new_instance['pay_type'] ?? '')),
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
/**
|
||||
* Paid posts list (zibll zib_widget_ui_posts_pay + ajax).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetPaidPosts extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_paid_posts',
|
||||
esc_html__('AKSRT 付费内容列表', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('展示开启付费/积分可见的文章(支持 AJAX)。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_paid_posts',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public static function build_query_args(array $instance, int $paged = 1): array {
|
||||
$count = max(1, min(20, (int) ($instance['count'] ?? 6)));
|
||||
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
|
||||
$query = [
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $count,
|
||||
'paged' => max(1, $paged),
|
||||
'order' => 'DESC',
|
||||
'meta_query' => [
|
||||
'relation' => 'OR',
|
||||
[
|
||||
'key' => '_aksrt_pay_price',
|
||||
'value' => 0,
|
||||
'compare' => '>',
|
||||
'type' => 'NUMERIC',
|
||||
],
|
||||
[
|
||||
'key' => '_aksrt_credits_cost',
|
||||
'value' => 0,
|
||||
'compare' => '>',
|
||||
'type' => 'NUMERIC',
|
||||
],
|
||||
[
|
||||
'key' => '_aksrt_visibility',
|
||||
'value' => ['pay', 'credits'],
|
||||
'compare' => 'IN',
|
||||
],
|
||||
],
|
||||
];
|
||||
if ($orderby === 'views') {
|
||||
$query['meta_key'] = 'aksrt_post_views';
|
||||
$query['orderby'] = 'meta_value_num';
|
||||
} elseif ($orderby === 'price') {
|
||||
$query['meta_key'] = '_aksrt_pay_price';
|
||||
$query['orderby'] = 'meta_value_num';
|
||||
} else {
|
||||
$query['orderby'] = 'date';
|
||||
}
|
||||
$cat = max(0, (int) ($instance['cat'] ?? 0));
|
||||
if ($cat > 0) {
|
||||
$query['cat'] = $cat;
|
||||
}
|
||||
$limit_day = max(0, (int) ($instance['limit_day'] ?? 0));
|
||||
if ($limit_day > 0) {
|
||||
$query['date_query'] = [['after' => $limit_day . ' days ago']];
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
public static function render_items_html(\WP_Query $q, string $style = 'mini'): string {
|
||||
if (!$q->have_posts()) {
|
||||
return '';
|
||||
}
|
||||
$placeholder = get_template_directory_uri() . '/assets/img/placeholder.svg';
|
||||
ob_start();
|
||||
while ($q->have_posts()) {
|
||||
$q->the_post();
|
||||
$pid = get_the_ID();
|
||||
$price = (float) get_post_meta($pid, '_aksrt_pay_price', true);
|
||||
$credits = (int) get_post_meta($pid, '_aksrt_credits_cost', true);
|
||||
$thumb = get_the_post_thumbnail_url($pid, 'thumbnail');
|
||||
if ($price > 0) {
|
||||
$badge = '¥' . number_format($price, 2, '.', '');
|
||||
} elseif ($credits > 0) {
|
||||
$badge = sprintf(__('%d 积分', 'aksrt'), $credits);
|
||||
} else {
|
||||
$badge = __('付费', 'aksrt');
|
||||
}
|
||||
$mode = in_array($style, ['list', 'card', 'mini'], true) ? $style : 'mini';
|
||||
if (function_exists('aksrt_render_post')) {
|
||||
$item = aksrt_render_post([
|
||||
'mode' => $mode,
|
||||
'context' => 'widget',
|
||||
'show_thumb' => true,
|
||||
'show_meta' => true,
|
||||
'show_excerpt' => $mode === 'list',
|
||||
'extra_class' => 'ajax-item aksrt-paid-posts__' . $mode,
|
||||
]);
|
||||
if ($item !== '') {
|
||||
echo '<div class="aksrt-paid-posts__item is-' . esc_attr($mode) . ' ajax-item">';
|
||||
echo $item; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '<span class="aksrt-paid-posts__price badge">' . esc_html($badge) . '</span>';
|
||||
echo '</div>';
|
||||
continue;
|
||||
}
|
||||
}
|
||||
echo '<li class="aksrt-paid-posts__item ajax-item">';
|
||||
if ($thumb) {
|
||||
echo '<a class="aksrt-paid-posts__thumb" href="' . esc_url(get_permalink()) . '"><img class="aksrt-lazy lazyload" data-src="' . esc_url($thumb) . '" src="' . esc_url($placeholder) . '" alt="" loading="lazy" /></a>';
|
||||
}
|
||||
echo '<div class="aksrt-paid-posts__body">';
|
||||
echo '<a class="aksrt-paid-posts__title" href="' . esc_url(get_permalink()) . '">' . esc_html(get_the_title()) . '</a>';
|
||||
echo '<div class="aksrt-paid-posts__meta"><span class="aksrt-paid-posts__price">' . esc_html($badge) . '</span></div>';
|
||||
echo '</div></li>';
|
||||
}
|
||||
wp_reset_postdata();
|
||||
return (string) ob_get_clean();
|
||||
}
|
||||
|
||||
public static function query_payload(array $instance, int $page = 1): array {
|
||||
$page = max(1, $page);
|
||||
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'ajax'));
|
||||
$count = max(1, min(20, (int) ($instance['count'] ?? 6)));
|
||||
$q = new \WP_Query(self::build_query_args($instance, $page));
|
||||
$style = sanitize_key((string) ($instance['style'] ?? 'mini'));
|
||||
if (!in_array($style, ['mini', 'list', 'card'], true)) {
|
||||
$style = 'mini';
|
||||
}
|
||||
$items = self::render_items_html($q, $style);
|
||||
$wrap_tag = (strpos($items, 'posts-mini') !== false || strpos($items, 'aksrt-paid-posts__mini') !== false) ? 'div' : 'ul';
|
||||
$html = $items !== '' ? '<' . $wrap_tag . ' class="aksrt-paid-posts style-' . esc_attr($style) . '">' . $items . '</' . $wrap_tag . '>' : '';
|
||||
if ($html === '' && $page === 1) {
|
||||
$html = function_exists('aksrt_get_ajax_null')
|
||||
? aksrt_get_ajax_null(__('暂无付费内容', 'aksrt'))
|
||||
: '<p class="widget-empty ajax-item">' . esc_html__('暂无付费内容', 'aksrt') . '</p>';
|
||||
}
|
||||
$max_pages = max(1, (int) $q->max_num_pages);
|
||||
$total = (int) $q->found_posts;
|
||||
$has_more = $page < $max_pages;
|
||||
$base_qs = array_filter([
|
||||
'count' => $count,
|
||||
'orderby' => sanitize_key((string) ($instance['orderby'] ?? 'date')),
|
||||
'cat' => max(0, (int) ($instance['cat'] ?? 0)),
|
||||
'limit_day' => max(0, (int) ($instance['limit_day'] ?? 0)),
|
||||
'style' => $style,
|
||||
'paginate' => $paginate,
|
||||
], static function ($v) {
|
||||
return $v !== '' && $v !== null;
|
||||
});
|
||||
$rest_base = add_query_arg($base_qs, rest_url('aksrt/v1/widgets/paid-posts'));
|
||||
$paginate_html = '';
|
||||
if ($paginate === 'ajax' && $has_more) {
|
||||
$next = add_query_arg(['page' => $page + 1], $rest_base);
|
||||
$paginate_html = function_exists('aksrt_get_ajax_next_paginate')
|
||||
? aksrt_get_ajax_next_paginate($total, $page, $count, $next)
|
||||
: '<div class="text-center theme-pagination ajax-pag"><div class="next-page ajax-next"><a href="' . esc_url($next) . '">' . esc_html__('加载更多', 'aksrt') . '</a></div></div>';
|
||||
} elseif ($paginate === 'number' && $max_pages > 1) {
|
||||
$paginate_html = function_exists('aksrt_get_ajax_number_paginate')
|
||||
? aksrt_get_ajax_number_paginate($total, $page, $count, $rest_base)
|
||||
: '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
|
||||
} else {
|
||||
$paginate_html = '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
|
||||
}
|
||||
return [
|
||||
'html' => $html,
|
||||
'paginate_html' => $paginate_html,
|
||||
'has_more' => $has_more,
|
||||
'page' => $page,
|
||||
'max_pages' => $max_pages,
|
||||
'total' => $total,
|
||||
];
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'ajax'));
|
||||
$instance['paginate'] = $paginate;
|
||||
$payload = self::query_payload($instance, 1);
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
$affix = function_exists('aksrt_widget_affix_attrs') ? aksrt_widget_affix_attrs($instance) : ['class' => '', 'attr' => ''];
|
||||
$rest = rest_url('aksrt/v1/widgets/paid-posts');
|
||||
$wrap = 'aksrt-paid-posts-wrap ajaxpager' . $affix['class'];
|
||||
echo '<div class="' . esc_attr($wrap) . '" data-aksrt-ajaxpager data-aksrt-ajax-url="' . esc_url($rest) . '"' . $affix['attr'] . '>';
|
||||
echo $payload['html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $payload['paginate_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '<div class="post_ajax_loader" data-aksrt-ajax-loader data-aksrt-ajax-loader-hide hidden></div>';
|
||||
echo '</div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? __('付费精选', 'aksrt'))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('数量', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="20" name="<?php echo esc_attr($this->get_field_name('count')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['count'] ?? 6))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('样式', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('style')); ?>">
|
||||
<option value="mini" <?php selected(($instance['style'] ?? 'mini'), 'mini'); ?>><?php esc_html_e('迷你', 'aksrt'); ?></option>
|
||||
<option value="list" <?php selected(($instance['style'] ?? ''), 'list'); ?>><?php esc_html_e('列表', 'aksrt'); ?></option>
|
||||
<option value="card" <?php selected(($instance['style'] ?? ''), 'card'); ?>><?php esc_html_e('卡片', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('排序', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
|
||||
<option value="date" <?php selected(($instance['orderby'] ?? 'date'), 'date'); ?>><?php esc_html_e('时间', 'aksrt'); ?></option>
|
||||
<option value="views" <?php selected(($instance['orderby'] ?? ''), 'views'); ?>><?php esc_html_e('浏览', 'aksrt'); ?></option>
|
||||
<option value="price" <?php selected(($instance['orderby'] ?? ''), 'price'); ?>><?php esc_html_e('价格', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('分页', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('paginate')); ?>">
|
||||
<option value="ajax" <?php selected(($instance['paginate'] ?? 'ajax'), 'ajax'); ?>><?php esc_html_e('AJAX', 'aksrt'); ?></option>
|
||||
<option value="number" <?php selected(($instance['paginate'] ?? ''), 'number'); ?>><?php esc_html_e('数字分页', 'aksrt'); ?></option>
|
||||
<option value="none" <?php selected(($instance['paginate'] ?? ''), 'none'); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('分类 ID(0=全部)', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('cat')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['cat'] ?? 0))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('最近 N 天(0=不限)', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('limit_day')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['limit_day'] ?? 0))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<?php $this->render_common_form_section($instance); ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'date'));
|
||||
if (!in_array($orderby, ['date', 'views', 'price'], true)) {
|
||||
$orderby = 'date';
|
||||
}
|
||||
return $this->update_common_fields($new_instance, $old_instance, [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'count' => max(1, min(20, (int) ($new_instance['count'] ?? 6))),
|
||||
'orderby' => $orderby,
|
||||
'style' => in_array(sanitize_key((string) ($new_instance['style'] ?? 'mini')), ['mini', 'list', 'card'], true) ? sanitize_key((string) ($new_instance['style'] ?? 'mini')) : 'mini',
|
||||
'cat' => max(0, (int) ($new_instance['cat'] ?? 0)),
|
||||
'limit_day' => max(0, (int) ($new_instance['limit_day'] ?? 0)),
|
||||
'paginate' => in_array(sanitize_key((string) ($new_instance['paginate'] ?? 'ajax')), ['none', 'ajax', 'number'], true) ? sanitize_key((string) ($new_instance['paginate'] ?? 'ajax')) : 'ajax',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
<?php
|
||||
/**
|
||||
* Horizontal partners / friends scroll (zibll zib_widget_ui_partners_scroll).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetPartnersScroll extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_partners_scroll',
|
||||
esc_html__('AKSRT 合作伙伴滚动', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('横向无缝滚动的合作伙伴/友情链接条。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_partners_scroll',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$partners = $this->resolve_partners($instance);
|
||||
if (!$partners) {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('暂无合作伙伴,请在模块中添加', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<p class="widget-empty">' . esc_html__('暂无合作伙伴', 'aksrt') . '</p>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$rows = max(1, min(4, (int) ($instance['rows'] ?? 1)));
|
||||
$m_rows = max(1, min(4, (int) ($instance['m_rows'] ?? 1)));
|
||||
$style_type = sanitize_key((string) ($instance['style_type'] ?? 'card'));
|
||||
if (!in_array($style_type, ['card', 'logo', 'chip'], true)) {
|
||||
$style_type = 'card';
|
||||
}
|
||||
$card_w = max(60, min(400, (int) ($instance['card_width'] ?? 220)));
|
||||
$card_h = max(40, min(200, (int) ($instance['card_height'] ?? 90)));
|
||||
$speed = max(1, min(20, (int) ($instance['speed'] ?? 3)));
|
||||
$direction = sanitize_key((string) ($instance['direction'] ?? 'alternate'));
|
||||
$pause = !empty($instance['pause_hover']) || !isset($instance['pause_hover']);
|
||||
$card_bg = !empty($instance['card_bg']) || !isset($instance['card_bg']);
|
||||
$end_fade = !empty($instance['end_fade']) || !isset($instance['end_fade']);
|
||||
$go_link = !empty($instance['go_link']);
|
||||
$show_bg = !empty($instance['show_widget_bg']) || !isset($instance['show_widget_bg']);
|
||||
$radius = max(0, min(40, (int) ($instance['card_radius'] ?? 10)));
|
||||
|
||||
$use_rows = wp_is_mobile() ? $m_rows : $rows;
|
||||
$rows_data = array_fill(0, $use_rows, []);
|
||||
foreach (array_values($partners) as $i => $p) {
|
||||
$rows_data[$i % $use_rows][] = $p;
|
||||
}
|
||||
$rows_data = array_values(array_filter($rows_data, static fn($r) => !empty($r)));
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
$wrap_class = 'aksrt-partners-scroll aksrt-ps-style-' . $style_type;
|
||||
if ($pause) {
|
||||
$wrap_class .= ' aksrt-ps-pause-hover';
|
||||
}
|
||||
if ($card_bg) {
|
||||
$wrap_class .= ' aksrt-ps-card-bg';
|
||||
}
|
||||
if ($end_fade) {
|
||||
$wrap_class .= ' aksrt-ps-end-fade';
|
||||
}
|
||||
|
||||
$style = sprintf(
|
||||
'--aksrt-ps-w:%dpx;--aksrt-ps-h:%dpx;--aksrt-ps-radius:%dpx;--aksrt-ps-speed:%d;',
|
||||
$card_w,
|
||||
$card_h,
|
||||
$radius,
|
||||
$speed
|
||||
);
|
||||
|
||||
$outer = $show_bg ? 'aksrt-partners-scroll-wrap' : 'aksrt-partners-scroll-wrap is-bare';
|
||||
echo '<div class="' . esc_attr($outer) . '">';
|
||||
echo '<div class="' . esc_attr($wrap_class) . '" style="' . esc_attr($style) . '">';
|
||||
|
||||
foreach ($rows_data as $row_index => $row_partners) {
|
||||
switch ($direction) {
|
||||
case 'left':
|
||||
$dir = 'left';
|
||||
break;
|
||||
case 'right':
|
||||
$dir = 'right';
|
||||
break;
|
||||
default:
|
||||
$dir = ($row_index % 2 === 0) ? 'left' : 'right';
|
||||
}
|
||||
echo '<div class="aksrt-ps-row aksrt-ps-dir-' . esc_attr($dir) . '">';
|
||||
echo '<div class="aksrt-ps-track">';
|
||||
// Duplicate group for seamless CSS marquee.
|
||||
for ($dup = 0; $dup < 2; $dup++) {
|
||||
echo '<div class="aksrt-ps-group">';
|
||||
foreach ($row_partners as $partner) {
|
||||
echo $this->render_card($partner, $style_type, $go_link); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div></div>';
|
||||
}
|
||||
|
||||
echo '</div></div>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $partner
|
||||
*/
|
||||
private function render_card(array $partner, string $style_type, bool $go_link): string {
|
||||
$title = trim((string) ($partner['title'] ?? ''));
|
||||
$desc = trim((string) ($partner['desc'] ?? ''));
|
||||
$image = (string) ($partner['image'] ?? '');
|
||||
$url = (string) ($partner['url'] ?? '');
|
||||
$target = (string) ($partner['target'] ?? '');
|
||||
|
||||
if ($image === '' && $title === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$alt = esc_attr($title !== '' ? $title : __('合作伙伴', 'aksrt'));
|
||||
$img_tag = $image !== ''
|
||||
? '<img class="aksrt-ps-img aksrt-lazy lazyload" data-src="' . esc_url($image) . '" src="' . esc_url(get_template_directory_uri() . '/assets/img/placeholder.svg') . '" alt="' . $alt . '" loading="lazy" />'
|
||||
: '';
|
||||
|
||||
$inner = '';
|
||||
if ($style_type === 'logo') {
|
||||
if ($img_tag === '') {
|
||||
return '';
|
||||
}
|
||||
$inner = $img_tag;
|
||||
} elseif ($style_type === 'chip') {
|
||||
if ($img_tag !== '') {
|
||||
$inner .= '<span class="aksrt-ps-chip-icon">' . $img_tag . '</span>';
|
||||
}
|
||||
$inner .= '<span class="aksrt-ps-chip-title">' . esc_html($title) . '</span>';
|
||||
} else {
|
||||
if ($img_tag !== '') {
|
||||
$inner .= '<div class="aksrt-ps-card-icon">' . $img_tag . '</div>';
|
||||
}
|
||||
$inner .= '<div class="aksrt-ps-card-text">';
|
||||
if ($title !== '') {
|
||||
$inner .= '<div class="aksrt-ps-card-title">' . esc_html($title) . '</div>';
|
||||
}
|
||||
if ($desc !== '') {
|
||||
$inner .= '<div class="aksrt-ps-card-desc">' . esc_html($desc) . '</div>';
|
||||
}
|
||||
$inner .= '</div>';
|
||||
}
|
||||
|
||||
if ($url !== '') {
|
||||
if ($go_link && function_exists('aksrt_go_link')) {
|
||||
$url = aksrt_go_link($url, true);
|
||||
}
|
||||
$tgt = $target !== '' ? ' target="' . esc_attr($target) . '" rel="noopener noreferrer"' : '';
|
||||
return '<a class="aksrt-ps-item" href="' . esc_url($url) . '"' . $tgt . '>' . $inner . '</a>';
|
||||
}
|
||||
|
||||
return '<div class="aksrt-ps-item">' . $inner . '</div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
* @return array<int, array{title:string,desc:string,image:string,url:string,target:string}>
|
||||
*/
|
||||
private function resolve_partners(array $instance): array {
|
||||
$source = sanitize_key((string) ($instance['link_type'] ?? 'manual'));
|
||||
if ($source === 'link') {
|
||||
$args = [
|
||||
'orderby' => sanitize_key((string) ($instance['link_orderby'] ?? 'name')) ?: 'name',
|
||||
'order' => strtoupper((string) ($instance['link_order'] ?? 'ASC')) === 'DESC' ? 'DESC' : 'ASC',
|
||||
'limit' => max(1, min(100, (int) ($instance['link_limit'] ?? 50))),
|
||||
];
|
||||
$cats = $instance['link_cats'] ?? '';
|
||||
if (is_array($cats)) {
|
||||
$cats = implode(',', array_map('intval', $cats));
|
||||
}
|
||||
$cats = trim((string) $cats);
|
||||
if ($cats !== '') {
|
||||
$args['category'] = $cats;
|
||||
}
|
||||
$bookmarks = get_bookmarks($args);
|
||||
$out = [];
|
||||
foreach ($bookmarks as $b) {
|
||||
$out[] = [
|
||||
'title' => (string) $b->link_name,
|
||||
'desc' => (string) $b->link_description,
|
||||
'image' => (string) $b->link_image,
|
||||
'url' => (string) $b->link_url,
|
||||
'target' => (string) $b->link_target,
|
||||
];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
return $this->normalize_manual($instance['partners'] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return array<int, array{title:string,desc:string,image:string,url:string,target:string}>
|
||||
*/
|
||||
private function normalize_manual($raw): array {
|
||||
if (is_string($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
$raw = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($raw as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$title = sanitize_text_field((string) ($row['title'] ?? ''));
|
||||
$image = esc_url_raw(trim((string) ($row['image'] ?? '')));
|
||||
$url = esc_url_raw(trim((string) ($row['url'] ?? $row['link'] ?? '')));
|
||||
if ($title === '' && $image === '') {
|
||||
continue;
|
||||
}
|
||||
$out[] = [
|
||||
'title' => $title,
|
||||
'desc' => sanitize_text_field((string) ($row['desc'] ?? '')),
|
||||
'image' => $image,
|
||||
'url' => $url,
|
||||
'target' => sanitize_text_field((string) ($row['target'] ?? '_blank')),
|
||||
];
|
||||
if (count($out) >= 40) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = (string) ($instance['title'] ?? '');
|
||||
$link_type = (string) ($instance['link_type'] ?? 'manual');
|
||||
$partners_json = is_string($instance['partners'] ?? null)
|
||||
? (string) $instance['partners']
|
||||
: wp_json_encode($this->normalize_manual($instance['partners'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr($title); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('数据来源', 'aksrt'); ?>
|
||||
<select class="widefat" name="<?php echo esc_attr($this->get_field_name('link_type')); ?>">
|
||||
<option value="manual" <?php selected($link_type, 'manual'); ?>><?php esc_html_e('手动 JSON', 'aksrt'); ?></option>
|
||||
<option value="link" <?php selected($link_type, 'link'); ?>><?php esc_html_e('WordPress 链接(书签)', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('合作伙伴 JSON', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="6" name="<?php echo esc_attr($this->get_field_name('partners')); ?>" placeholder='[{"title":"Name","image":"https://...","url":"https://...","desc":""}]'><?php echo esc_textarea((string) $partners_json); ?></textarea>
|
||||
</label>
|
||||
<small><?php esc_html_e('字段: title, image, url, desc, target', 'aksrt'); ?></small>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('链接分类 ID(逗号分隔,书签模式)', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('link_cats')); ?>" value="<?php echo esc_attr((string) ($instance['link_cats'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('书签数量上限', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="100" name="<?php echo esc_attr($this->get_field_name('link_limit')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['link_limit'] ?? 50))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('样式', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('style_type')); ?>">
|
||||
<option value="card" <?php selected(($instance['style_type'] ?? 'card'), 'card'); ?>><?php esc_html_e('图文卡片', 'aksrt'); ?></option>
|
||||
<option value="logo" <?php selected(($instance['style_type'] ?? ''), 'logo'); ?>><?php esc_html_e('纯 Logo', 'aksrt'); ?></option>
|
||||
<option value="chip" <?php selected(($instance['style_type'] ?? ''), 'chip'); ?>><?php esc_html_e('胶囊', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('PC 行数', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="4" name="<?php echo esc_attr($this->get_field_name('rows')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['rows'] ?? 1))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('移动行数', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="4" name="<?php echo esc_attr($this->get_field_name('m_rows')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['m_rows'] ?? 1))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('卡片宽', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="60" max="400" name="<?php echo esc_attr($this->get_field_name('card_width')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['card_width'] ?? 220))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('高', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="40" max="200" name="<?php echo esc_attr($this->get_field_name('card_height')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['card_height'] ?? 90))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('速度', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="20" name="<?php echo esc_attr($this->get_field_name('speed')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['speed'] ?? 3))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('方向', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('direction')); ?>">
|
||||
<option value="alternate" <?php selected(($instance['direction'] ?? 'alternate'), 'alternate'); ?>><?php esc_html_e('交替', 'aksrt'); ?></option>
|
||||
<option value="left" <?php selected(($instance['direction'] ?? ''), 'left'); ?>><?php esc_html_e('向左', 'aksrt'); ?></option>
|
||||
<option value="right" <?php selected(($instance['direction'] ?? ''), 'right'); ?>><?php esc_html_e('向右', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('pause_hover')); ?>" value="1" <?php checked(!empty($instance['pause_hover']) || !isset($instance['pause_hover'])); ?>> <?php esc_html_e('悬停暂停', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('card_bg')); ?>" value="1" <?php checked(!empty($instance['card_bg']) || !isset($instance['card_bg'])); ?>> <?php esc_html_e('卡片背景', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('end_fade')); ?>" value="1" <?php checked(!empty($instance['end_fade']) || !isset($instance['end_fade'])); ?>> <?php esc_html_e('两端渐隐', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('go_link')); ?>" value="1" <?php checked(!empty($instance['go_link'])); ?>> <?php esc_html_e('外链跳转页', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_widget_bg')); ?>" value="1" <?php checked(!empty($instance['show_widget_bg']) || !isset($instance['show_widget_bg'])); ?>> <?php esc_html_e('显示背景盒子', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$business = [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'link_type' => in_array(($new_instance['link_type'] ?? ''), ['manual', 'link'], true) ? $new_instance['link_type'] : 'manual',
|
||||
'partners' => $this->normalize_manual($new_instance['partners'] ?? []),
|
||||
'link_cats' => sanitize_text_field((string) ($new_instance['link_cats'] ?? '')),
|
||||
'link_limit' => max(1, min(100, (int) ($new_instance['link_limit'] ?? 50))),
|
||||
'link_orderby' => sanitize_key((string) ($new_instance['link_orderby'] ?? 'name')),
|
||||
'link_order' => strtoupper((string) ($new_instance['link_order'] ?? 'ASC')) === 'DESC' ? 'DESC' : 'ASC',
|
||||
'style_type' => in_array(($new_instance['style_type'] ?? ''), ['card', 'logo', 'chip'], true) ? $new_instance['style_type'] : 'card',
|
||||
'rows' => max(1, min(4, (int) ($new_instance['rows'] ?? 1))),
|
||||
'm_rows' => max(1, min(4, (int) ($new_instance['m_rows'] ?? 1))),
|
||||
'card_width' => max(60, min(400, (int) ($new_instance['card_width'] ?? 220))),
|
||||
'card_height' => max(40, min(200, (int) ($new_instance['card_height'] ?? 90))),
|
||||
'card_radius' => max(0, min(40, (int) ($new_instance['card_radius'] ?? 10))),
|
||||
'speed' => max(1, min(20, (int) ($new_instance['speed'] ?? 3))),
|
||||
'direction' => in_array(($new_instance['direction'] ?? ''), ['alternate', 'left', 'right'], true) ? $new_instance['direction'] : 'alternate',
|
||||
'pause_hover' => !empty($new_instance['pause_hover']) ? 1 : 0,
|
||||
'card_bg' => !empty($new_instance['card_bg']) ? 1 : 0,
|
||||
'end_fade' => !empty($new_instance['end_fade']) ? 1 : 0,
|
||||
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
|
||||
'show_widget_bg' => !empty($new_instance['show_widget_bg']) ? 1 : 0,
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
/**
|
||||
* Singular post pay purchase sidebar card (zibll zib_widget_ui_posts_pay).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetPostPayBox extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_post_pay_box',
|
||||
esc_html__('AKSRT 付费购买', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('显示当前文章/帖子的付费购买模块,推荐放在详情侧栏。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_post_pay_box',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public static function render_html(array $instance = []): string {
|
||||
if (!is_singular(['post', 'page'])) {
|
||||
return '';
|
||||
}
|
||||
$post = get_queried_object();
|
||||
if (!$post instanceof \WP_Post) {
|
||||
return '';
|
||||
}
|
||||
if (!function_exists('aksrt_get_pay_meta') || !function_exists('aksrt_is_paid_post')) {
|
||||
return '';
|
||||
}
|
||||
if (!aksrt_is_paid_post($post)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$meta = aksrt_get_pay_meta($post);
|
||||
$pay_type = (string) ($meta['pay_type'] ?? 'no');
|
||||
if ($pay_type === '' || $pay_type === 'no') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$theme = sanitize_html_class((string) ($instance['theme'] ?? 'jb-red'));
|
||||
if ($theme === '') {
|
||||
$theme = 'jb-red';
|
||||
}
|
||||
|
||||
$has_access = function_exists('aksrt_has_paid_access') ? aksrt_has_paid_access($post) : false;
|
||||
$priced = function_exists('aksrt_get_user_pay_price') ? aksrt_get_user_pay_price($post) : null;
|
||||
$pay_modo = is_array($priced) && (($priced['modo'] ?? '') === 'points') ? 'points' : '0';
|
||||
$amount = is_array($priced) ? (float) ($priced['amount'] ?? 0) : (float) ($meta['price'] ?? 0);
|
||||
$currency = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_currency_name() : __('积分', 'aksrt');
|
||||
$cash_symbol = '¥';
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
$paid_s = \AKSRT\Settings\Settings::get_paid_content_settings();
|
||||
$cash_symbol = (string) ($paid_s['display']['currency_symbol'] ?? '¥');
|
||||
}
|
||||
$price_text = $pay_modo === 'points'
|
||||
? (function_exists('aksrt_format_price') ? aksrt_format_price($amount) : (string) $amount) . ' ' . $currency
|
||||
: $cash_symbol . (function_exists('aksrt_format_price') ? aksrt_format_price($amount) : number_format($amount, 2, '.', ''));
|
||||
|
||||
$type_labels = [
|
||||
'reading' => __('付费阅读', 'aksrt'),
|
||||
'download' => __('付费下载', 'aksrt'),
|
||||
'gallery' => __('付费图库', 'aksrt'),
|
||||
'video' => __('付费视频', 'aksrt'),
|
||||
'vip' => __('会员专属', 'aksrt'),
|
||||
];
|
||||
$type_label = $type_labels[$pay_type] ?? __('付费内容', 'aksrt');
|
||||
$cta_map = [
|
||||
'reading' => __('查看内容', 'aksrt'),
|
||||
'download' => __('获取下载', 'aksrt'),
|
||||
'gallery' => __('查看图片', 'aksrt'),
|
||||
'video' => __('查看视频', 'aksrt'),
|
||||
'vip' => __('查看内容', 'aksrt'),
|
||||
];
|
||||
$cta_label = $cta_map[$pay_type] ?? __('查看内容', 'aksrt');
|
||||
|
||||
$sales = 0;
|
||||
if (function_exists('aksrt_get_download_box_meta')) {
|
||||
$box = aksrt_get_download_box_meta($post);
|
||||
$sales = (int) ($box['sales_count'] ?? 0);
|
||||
}
|
||||
|
||||
$login_url = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url(get_permalink($post));
|
||||
$is_logged_in = is_user_logged_in();
|
||||
$demo = trim((string) ($meta['demo_url'] ?? ''));
|
||||
|
||||
ob_start();
|
||||
echo '<div class="zib-widget pay-box pay-widget aksrt-post-pay-box order-type-' . esc_attr($pay_type) . ($has_access ? ' is-paid' : ' is-unpaid') . '" style="padding:0;">';
|
||||
echo '<badge class="pay-tag abs-center">' . esc_html($type_label) . '</badge>';
|
||||
echo '<div class="relative-h ' . esc_attr($theme) . ' aksrt-post-pay-box__hero">';
|
||||
echo '<div class="absolute radius ' . esc_attr($theme) . ' aksrt-post-pay-box__blob aksrt-post-pay-box__blob--a"></div>';
|
||||
echo '<div class="absolute radius ' . esc_attr($theme) . ' aksrt-post-pay-box__blob aksrt-post-pay-box__blob--b"></div>';
|
||||
echo '<div class="relative box-body text-center">';
|
||||
if ($has_access) {
|
||||
echo '<p class="mt10 mb6"><i class="fa fa-shopping-bag fa-3x" aria-hidden="true"></i></p>';
|
||||
echo '<b class="em14">' . esc_html__('已解锁', 'aksrt') . '</b>';
|
||||
} else {
|
||||
echo '<div class="text-center mt10 aksrt-post-pay-box__price">';
|
||||
if ($amount > 0) {
|
||||
echo '<div class="em14 font-bold">' . esc_html($price_text) . '</div>';
|
||||
} else {
|
||||
echo '<div class="em14 font-bold">' . esc_html__('免费', 'aksrt') . '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div></div>';
|
||||
|
||||
echo '<div class="box-body">';
|
||||
if ($has_access) {
|
||||
echo '<div class="mt10"><a href="#posts-pay" class="but padding-lg btn-block ' . esc_attr($theme) . '"><i class="fa fa-dot-circle-o fa-fw" aria-hidden="true"></i> ' . esc_html($cta_label) . '</a></div>';
|
||||
} elseif (!$is_logged_in) {
|
||||
echo '<div class="mt10"><a href="' . esc_url($login_url) . '" class="but padding-lg btn-block jb-blue"><i class="fa fa-sign-in" aria-hidden="true"></i> ' . esc_html__('登录后购买', 'aksrt') . '</a></div>';
|
||||
if ($amount > 0 && $pay_modo !== 'points' && function_exists('aksrt_allow_guest_content_pay') && aksrt_allow_guest_content_pay($post)) {
|
||||
echo '<div class="mt10"><button type="button" class="but padding-lg btn-block ' . esc_attr($theme) . '" data-aksrt-pay-content="' . esc_attr((string) $post->ID) . '"><i class="fa fa-shopping-cart fa-fw" aria-hidden="true"></i> ' . esc_html(sprintf(/* translators: price */ __('游客购买(%s)', 'aksrt'), $price_text)) . '</button></div>';
|
||||
}
|
||||
} else {
|
||||
echo '<div class="mt10"><button type="button" class="but padding-lg btn-block ' . esc_attr($theme) . ' cashier-link" data-aksrt-pay-content="' . esc_attr((string) $post->ID) . '"><i class="fa fa-shopping-cart fa-fw" aria-hidden="true"></i> ' . esc_html(sprintf(/* translators: price */ __('立即购买(%s)', 'aksrt'), $price_text)) . '</button></div>';
|
||||
}
|
||||
if ($demo !== '') {
|
||||
echo '<div class="mt10"><a class="but hollow btn-block" href="' . esc_url($demo) . '" target="_blank" rel="noopener noreferrer"><i class="fa fa-external-link" aria-hidden="true"></i> ' . esc_html__('演示地址', 'aksrt') . '</a></div>';
|
||||
}
|
||||
echo '</div>';
|
||||
if ($sales > 0) {
|
||||
echo '<badge class="img-badge hot jb-blue px12">' . esc_html(sprintf(/* translators: sales count */ __('已售 %s', 'aksrt'), (string) $sales)) . '</badge>';
|
||||
}
|
||||
echo '</div>';
|
||||
return (string) ob_get_clean();
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$html = self::render_html($instance);
|
||||
if ($html === '') {
|
||||
return;
|
||||
}
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$theme = sanitize_html_class((string) ($instance['theme'] ?? 'jb-red'));
|
||||
$opts = [
|
||||
'jb-red' => __('红', 'aksrt'),
|
||||
'jb-yellow' => __('橙', 'aksrt'),
|
||||
'jb-blue' => __('蓝', 'aksrt'),
|
||||
'jb-green' => __('绿', 'aksrt'),
|
||||
'jb-purple' => __('紫', 'aksrt'),
|
||||
'jb-vip1' => __('会员金', 'aksrt'),
|
||||
'jb-vip2' => __('会员灰', 'aksrt'),
|
||||
];
|
||||
?>
|
||||
<p class="description"><?php esc_html_e('仅在文章/帖子详情侧栏显示当前内容的付费购买卡。', 'aksrt'); ?></p>
|
||||
<p>
|
||||
<label><?php esc_html_e('色彩主题', 'aksrt'); ?>
|
||||
<select class="widefat" name="<?php echo esc_attr($this->get_field_name('theme')); ?>">
|
||||
<?php foreach ($opts as $k => $label) : ?>
|
||||
<option value="<?php echo esc_attr($k); ?>" <?php selected($theme, $k); ?>><?php echo esc_html($label); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$theme = sanitize_html_class((string) ($new_instance['theme'] ?? 'jb-red'));
|
||||
$allowed = ['jb-red', 'jb-yellow', 'jb-blue', 'jb-green', 'jb-purple', 'jb-vip1', 'jb-vip2'];
|
||||
if (!in_array($theme, $allowed, true)) {
|
||||
$theme = 'jb-red';
|
||||
}
|
||||
$business = ['theme' => $theme]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/**
|
||||
* In-article / topic TOC shell (zibll widget_ui_posts_navs parity).
|
||||
*
|
||||
* Shell matches the built-in single sidebar TOC so page-template.js can fill
|
||||
* `.posts-nav-box.toc-widget > .toc-list` from `[data-nav]` headings.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetPostsNavs extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_posts_navs',
|
||||
esc_html__('AKSRT 文章目录树', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('显示当前文章/帖子标题目录,非详情页不输出。标题超过 2 个才填充。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_posts_navs',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
if (!is_singular(['post', 'page'])) {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('目录树仅在文章页面显示', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<p class="widget-empty">' . esc_html__('目录树仅在详情页显示', 'aksrt') . '</p>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$title = sanitize_text_field((string) ($instance['title'] ?? __('文章目录', 'aksrt')));
|
||||
$mini_title = sanitize_text_field((string) ($instance['mini_title'] ?? ''));
|
||||
$affix = function_exists('aksrt_widget_affix_attrs') ? aksrt_widget_affix_attrs($instance) : ['class' => '', 'attr' => ''];
|
||||
|
||||
$data_title = $title;
|
||||
if ($mini_title !== '') {
|
||||
$data_title .= ($data_title !== '' ? ' · ' : '') . $mini_title;
|
||||
}
|
||||
if ($data_title === '') {
|
||||
$data_title = __('文章目录', 'aksrt');
|
||||
}
|
||||
|
||||
// Mirror aksrt-single-render built-in TOC shell so page-template.js binds.
|
||||
$classes = 'zib-widget toc-widget posts-nav-box' . $affix['class'];
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
printf(
|
||||
'<div class="%s"%s data-title="%s" aria-label="%s">',
|
||||
esc_attr($classes),
|
||||
$affix['attr'],
|
||||
esc_attr($data_title),
|
||||
esc_attr($data_title)
|
||||
);
|
||||
echo '<h3 class="widget-title toc-widget__title">' . esc_html($data_title) . '</h3>';
|
||||
echo '<ol class="toc-list"></ol>';
|
||||
echo '</div>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = (string) ($instance['title'] ?? __('文章目录', 'aksrt'));
|
||||
$mini_title = (string) ($instance['mini_title'] ?? '');
|
||||
?>
|
||||
<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($title); ?>">
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('mini_title')); ?>"><?php esc_html_e('副标题:', 'aksrt'); ?></label>
|
||||
<input class="widefat" type="text" id="<?php echo esc_attr($this->get_field_id('mini_title')); ?>" name="<?php echo esc_attr($this->get_field_name('mini_title')); ?>" value="<?php echo esc_attr($mini_title); ?>">
|
||||
</p>
|
||||
<p class="description"><?php esc_html_e('请添加到文章/帖子侧栏。正文标题 ≥3 个时由前端填充目录;无标题则自动移除。', 'aksrt'); ?></p>
|
||||
<?php $this->render_common_form_section($instance); ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return $this->update_common_fields($new_instance, $old_instance, [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'mini_title' => sanitize_text_field((string) ($new_instance['mini_title'] ?? '')),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
<?php
|
||||
/**
|
||||
* Pricing plan cards (zibll zib_widget_pricing_plans lite).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetPricingPlans extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_pricing_plans',
|
||||
esc_html__('AKSRT 价格方案', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('多列定价卡片 + 信任条(落地页/VIP 介绍)。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_pricing_plans',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$plans = $this->normalize_plans($instance['lists'] ?? $instance['plans'] ?? []);
|
||||
if (!$plans) {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('请配置至少一个价格方案', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<p class="widget-empty">' . esc_html__('请配置至少一个价格方案', 'aksrt') . '</p>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
$trust = $this->normalize_trust($instance['trust_items'] ?? []);
|
||||
$currency = sanitize_text_field((string) ($instance['currency'] ?? '¥'));
|
||||
if ($currency === '') {
|
||||
$currency = '¥';
|
||||
}
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
echo '<div class="aksrt-pricing">';
|
||||
echo '<div class="aksrt-pricing__cards">';
|
||||
$pi = 0;
|
||||
foreach ($plans as $plan) {
|
||||
$accent = sanitize_key((string) ($plan['color'] ?? 'blue'));
|
||||
if (!in_array($accent, ['pink', 'purple', 'blue', 'green', 'yellow', 'cyan'], true)) {
|
||||
$accent = 'blue';
|
||||
}
|
||||
$featured = !empty($plan['is_featured']);
|
||||
$pack = function_exists('aksrt_obs_animate_attrs') ? aksrt_obs_animate_attrs('slideup', false, $pi * 0.12) : ['class' => '', 'attr' => ''];
|
||||
$class = 'aksrt-pricing__card accent-' . $accent . ($featured ? ' is-featured' : '') . $pack['class'];
|
||||
echo '<div class="' . esc_attr(trim($class)) . '"' . $pack['attr'] . '>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$pi++;
|
||||
if ($featured || !empty($plan['tag'])) {
|
||||
$badge = (string) ($plan['tag'] !== '' ? $plan['tag'] : __('推荐', 'aksrt'));
|
||||
echo '<span class="aksrt-pricing__badge">' . esc_html($badge) . '</span>';
|
||||
}
|
||||
echo '<h3 class="aksrt-pricing__name">' . esc_html($plan['name']) . '</h3>';
|
||||
if ($plan['desc'] !== '') {
|
||||
echo '<p class="aksrt-pricing__desc">' . esc_html($plan['desc']) . '</p>';
|
||||
}
|
||||
$price = (float) $plan['price'];
|
||||
$show = (float) $plan['show_price'];
|
||||
echo '<div class="aksrt-pricing__price">';
|
||||
echo '<span class="aksrt-pricing__main"><span class="aksrt-pricing__mark">' . esc_html($currency) . '</span>' . esc_html($this->fmt($price)) . '</span>';
|
||||
if ($show > $price && $show > 0) {
|
||||
echo '<span class="aksrt-pricing__original"><span class="aksrt-pricing__mark">' . esc_html($currency) . '</span>' . esc_html($this->fmt($show)) . '</span>';
|
||||
}
|
||||
echo '</div>';
|
||||
if ($plan['features']) {
|
||||
echo '<ul class="aksrt-pricing__features">';
|
||||
foreach ($plan['features'] as $ft) {
|
||||
echo '<li><i class="fa fa-check" aria-hidden="true"></i><span>' . esc_html($ft) . '</span></li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
}
|
||||
if ($plan['btn_text'] !== '' && $plan['btn_url'] !== '') {
|
||||
$url = $plan['btn_url'];
|
||||
if (!empty($instance['go_link']) && function_exists('aksrt_go_link')) {
|
||||
$url = aksrt_go_link($url, true);
|
||||
}
|
||||
echo '<div class="aksrt-pricing__action"><a class="aksrt-pricing__btn" href="' . esc_url($url) . '">' . esc_html($plan['btn_text']) . '</a></div>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
if ($trust) {
|
||||
echo '<div class="aksrt-pricing__trust">';
|
||||
foreach ($trust as $ti) {
|
||||
echo '<div class="aksrt-pricing__trust-item">';
|
||||
if ($ti['icon'] !== '') {
|
||||
echo '<i class="' . esc_attr($ti['icon']) . '" aria-hidden="true"></i>';
|
||||
}
|
||||
echo '<div><strong>' . esc_html($ti['title']) . '</strong>';
|
||||
if ($ti['desc'] !== '') {
|
||||
echo '<span>' . esc_html($ti['desc']) . '</span>';
|
||||
}
|
||||
echo '</div></div>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
private function fmt(float $n): string {
|
||||
if (abs($n - round($n)) < 0.001) {
|
||||
return (string) (int) round($n);
|
||||
}
|
||||
return number_format($n, 2, '.', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return array<int, array<string,mixed>>
|
||||
*/
|
||||
private function normalize_plans($raw): array {
|
||||
if (is_string($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
$raw = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($raw as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$name = sanitize_text_field((string) ($row['name'] ?? $row['title'] ?? ''));
|
||||
if ($name === '') {
|
||||
continue;
|
||||
}
|
||||
$intro = (string) ($row['intro'] ?? $row['features'] ?? '');
|
||||
if (is_array($row['features'] ?? null)) {
|
||||
$features = array_values(array_filter(array_map('sanitize_text_field', $row['features'])));
|
||||
} else {
|
||||
$features = array_values(array_filter(array_map('sanitize_text_field', preg_split('/\r\n|\r|\n/', $intro) ?: [])));
|
||||
}
|
||||
$btn = is_array($row['btn'] ?? null) ? $row['btn'] : [];
|
||||
$out[] = [
|
||||
'name' => $name,
|
||||
'desc' => sanitize_text_field((string) ($row['desc'] ?? '')),
|
||||
'price' => (float) ($row['price'] ?? 0),
|
||||
'show_price' => (float) ($row['show_price'] ?? 0),
|
||||
'color' => sanitize_key((string) ($row['color'] ?? 'blue')),
|
||||
'is_featured' => !empty($row['is_featured']) ? 1 : 0,
|
||||
'tag' => sanitize_text_field((string) ($row['tag'] ?? '')),
|
||||
'features' => $features,
|
||||
'btn_text' => sanitize_text_field((string) ($row['btn_text'] ?? $btn['text'] ?? '')),
|
||||
'btn_url' => esc_url_raw(trim((string) ($row['btn_url'] ?? $btn['url'] ?? ''))),
|
||||
];
|
||||
if (count($out) >= 6) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return array<int, array{title:string,desc:string,icon:string}>
|
||||
*/
|
||||
private function normalize_trust($raw): array {
|
||||
if (is_string($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
$raw = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($raw as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$title = sanitize_text_field((string) ($row['title'] ?? ''));
|
||||
if ($title === '') {
|
||||
continue;
|
||||
}
|
||||
$out[] = [
|
||||
'title' => $title,
|
||||
'desc' => sanitize_text_field((string) ($row['desc'] ?? '')),
|
||||
'icon' => sanitize_text_field((string) ($row['icon'] ?? 'fa fa-check-circle')),
|
||||
];
|
||||
if (count($out) >= 6) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$plans = is_string($instance['lists'] ?? null)
|
||||
? (string) $instance['lists']
|
||||
: wp_json_encode($this->normalize_plans($instance['lists'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
$trust = is_string($instance['trust_items'] ?? null)
|
||||
? (string) $instance['trust_items']
|
||||
: wp_json_encode($this->normalize_trust($instance['trust_items'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('货币符号', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="text" name="<?php echo esc_attr($this->get_field_name('currency')); ?>" value="<?php echo esc_attr((string) ($instance['currency'] ?? '¥')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('方案 JSON', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="8" name="<?php echo esc_attr($this->get_field_name('lists')); ?>" placeholder='[{"name":"Pro","price":99,"show_price":199,"desc":"适合创作者","features":["无限下载","优先支持"],"btn_text":"立即开通","btn_url":"#","is_featured":1,"color":"pink"}]'><?php echo esc_textarea((string) $plans); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('信任条 JSON', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="3" name="<?php echo esc_attr($this->get_field_name('trust_items')); ?>"><?php echo esc_textarea((string) $trust); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('go_link')); ?>" value="1" <?php checked(!empty($instance['go_link'])); ?>> <?php esc_html_e('外链跳转页', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$business = [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'currency' => sanitize_text_field((string) ($new_instance['currency'] ?? '¥')),
|
||||
'lists' => $this->normalize_plans($new_instance['lists'] ?? []),
|
||||
'trust_items' => $this->normalize_trust($new_instance['trust_items'] ?? []),
|
||||
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
/**
|
||||
* Testimonials / virtual reviews carousel (zibll zib_widget_reviews lite).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetReviews extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_reviews',
|
||||
esc_html__('AKSRT 用户评价', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('虚拟用户评价卡片横向滚动。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_reviews',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$reviews = $this->normalize($instance['reviews'] ?? []);
|
||||
if (!$reviews) {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('请添加至少一条评价', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<p class="widget-empty">' . esc_html__('请添加至少一条评价', 'aksrt') . '</p>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
$pc = max(1, min(6, (float) ($instance['desktop_per_view'] ?? 3)));
|
||||
$m = max(1, min(3, (float) ($instance['mobile_per_view'] ?? 1.2)));
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
$style = sprintf('--aksrt-rev-pc:%s;--aksrt-rev-m:%s;', rtrim(rtrim(number_format($pc, 2, '.', ''), '0'), '.'), rtrim(rtrim(number_format($m, 2, '.', ''), '0'), '.'));
|
||||
echo '<div class="aksrt-reviews" style="' . esc_attr($style) . '">';
|
||||
echo '<div class="swiper-container swiper-scroll aksrt-reviews-swiper">';
|
||||
echo '<div class="swiper-wrapper aksrt-reviews__track">';
|
||||
foreach ($reviews as $r) {
|
||||
echo '<div class="swiper-slide aksrt-reviews__slide"><div class="aksrt-reviews__card" data-aksrt-tilt data-tilt-max="8">';
|
||||
echo '<div class="aksrt-reviews__head">';
|
||||
if ($r['avatar'] !== '') {
|
||||
$ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
|
||||
echo '<img class="aksrt-reviews__avatar aksrt-lazy lazyload" data-src="' . esc_url($r['avatar']) . '" src="' . esc_url($ph) . '" alt="" loading="lazy" width="44" height="44" />';
|
||||
} else {
|
||||
echo '<span class="aksrt-reviews__avatar is-placeholder"><i class="fa fa-user" aria-hidden="true"></i></span>';
|
||||
}
|
||||
echo '<div class="aksrt-reviews__meta">';
|
||||
if ($r['name'] !== '') {
|
||||
$name_html = esc_html($r['name']);
|
||||
if ($r['url'] !== '') {
|
||||
$name_html = '<a href="' . esc_url($r['url']) . '" target="_blank" rel="noopener noreferrer">' . $name_html . '</a>';
|
||||
}
|
||||
echo '<div class="aksrt-reviews__name">' . $name_html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
if ($r['role'] !== '') {
|
||||
echo '<div class="aksrt-reviews__role">' . esc_html($r['role']) . '</div>';
|
||||
}
|
||||
echo '</div></div>';
|
||||
echo '<div class="aksrt-reviews__stars" aria-label="' . esc_attr(sprintf(__('%d 星', 'aksrt'), $r['rating'])) . '">';
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
echo '<i class="fa fa-star' . ($i <= $r['rating'] ? ' is-on' : ' is-off') . '" aria-hidden="true"></i>';
|
||||
}
|
||||
echo '</div>';
|
||||
if ($r['content'] !== '') {
|
||||
echo '<p class="aksrt-reviews__content">' . nl2br(esc_html($r['content'])) . '</p>';
|
||||
}
|
||||
echo '<i class="fa fa-quote-right aksrt-reviews__quote" aria-hidden="true"></i>';
|
||||
echo '</div></div>';
|
||||
}
|
||||
echo '</div>';
|
||||
echo '<div class="swiper-button-prev" aria-label="' . esc_attr__('上一页', 'aksrt') . '"></div>';
|
||||
echo '<div class="swiper-button-next" aria-label="' . esc_attr__('下一页', 'aksrt') . '"></div>';
|
||||
echo '</div></div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return array<int, array{name:string,role:string,avatar:string,content:string,rating:int,url:string}>
|
||||
*/
|
||||
private function normalize($raw): array {
|
||||
if (is_string($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
$raw = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($raw as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$name = sanitize_text_field((string) ($row['name'] ?? ''));
|
||||
$content = sanitize_textarea_field((string) ($row['content'] ?? ''));
|
||||
if ($name === '' && $content === '') {
|
||||
continue;
|
||||
}
|
||||
$out[] = [
|
||||
'name' => $name,
|
||||
'role' => sanitize_text_field((string) ($row['role'] ?? '')),
|
||||
'avatar' => esc_url_raw(trim((string) ($row['avatar'] ?? ''))),
|
||||
'content' => $content,
|
||||
'rating' => max(0, min(5, (int) ($row['rating'] ?? 5))),
|
||||
'url' => esc_url_raw(trim((string) ($row['url'] ?? ''))),
|
||||
];
|
||||
if (count($out) >= 20) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$json = is_string($instance['reviews'] ?? null)
|
||||
? (string) $instance['reviews']
|
||||
: wp_json_encode($this->normalize($instance['reviews'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('评价 JSON', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="8" name="<?php echo esc_attr($this->get_field_name('reviews')); ?>" placeholder='[{"name":"张三","role":"站长","rating":5,"content":"很好用","avatar":""}]'><?php echo esc_textarea((string) $json); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('PC 每屏', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="6" step="0.1" name="<?php echo esc_attr($this->get_field_name('desktop_per_view')); ?>" value="<?php echo esc_attr((string) ($instance['desktop_per_view'] ?? '3')); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('移动每屏', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="3" step="0.1" name="<?php echo esc_attr($this->get_field_name('mobile_per_view')); ?>" value="<?php echo esc_attr((string) ($instance['mobile_per_view'] ?? '1.2')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$business = [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'reviews' => $this->normalize($new_instance['reviews'] ?? []),
|
||||
'desktop_per_view' => max(1, min(6, (float) ($new_instance['desktop_per_view'] ?? 3))),
|
||||
'mobile_per_view' => max(1, min(3, (float) ($new_instance['mobile_per_view'] ?? 1.2))),
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
<?php
|
||||
/**
|
||||
* Simple image slider widget (zibll widget_ui_slider → aksrt_render_slider / new-swiper).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetSlider extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_slider',
|
||||
esc_html__('AKSRT 幻灯片', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('多图轮播(共享 new-swiper / Swiper 栈),支持链接与标题。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_slider slide-widget',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$slides = $this->normalize_slides($instance['slides'] ?? []);
|
||||
if (!$slides) {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('当前配置下没有可显示的内容', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<div class="c-red muted-box mb20"><b>' . esc_html__('幻灯片模块:', 'aksrt') . '</b>' . esc_html__('当前配置下没有可显示的内容,请正确配置或移出该模块', 'aksrt') . '</div>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$interval = max(0, min(20, (int) ($instance['interval'] ?? 4)));
|
||||
$loop = !isset($instance['loop']) || !empty($instance['loop']);
|
||||
$dots = !isset($instance['pagination']) || !empty($instance['pagination']);
|
||||
$nav = !isset($instance['button']) || !empty($instance['button']);
|
||||
$blank = !empty($instance['blank']);
|
||||
$height = max(80, min(800, (int) ($instance['height'] ?? $instance['pc_height'] ?? 400)));
|
||||
$m_height = max(60, min(800, (int) ($instance['m_height'] ?? $height)));
|
||||
$effect = sanitize_key((string) ($instance['effect'] ?? 'slide'));
|
||||
if (!in_array($effect, ['slide', 'fade', 'cube', 'coverflow', 'flip'], true)) {
|
||||
$effect = 'slide';
|
||||
}
|
||||
// Zibll CFS_Module::slide() options parity.
|
||||
$direction = sanitize_key((string) ($instance['direction'] ?? 'horizontal'));
|
||||
if (!in_array($direction, ['horizontal', 'vertical'], true)) {
|
||||
$direction = 'horizontal';
|
||||
}
|
||||
$autoplay = !isset($instance['autoplay']) || !empty($instance['autoplay']);
|
||||
$speed = max(0, min(3000, (int) ($instance['speed'] ?? 0)));
|
||||
$spacebetween= max(0, min(500, (int) ($instance['spacebetween'] ?? 15)));
|
||||
$scale_height= !empty($instance['scale_height']) || !isset($instance['scale_height']);
|
||||
$scale = max(10, min(300, (int) ($instance['scale'] ?? 35)));
|
||||
|
||||
$slide_payload = [];
|
||||
foreach ($slides as $slide) {
|
||||
$slide_payload[] = [
|
||||
'image' => $slide['image'],
|
||||
'link' => $slide['href'],
|
||||
'title' => $slide['title'],
|
||||
'desc' => $slide['desc'],
|
||||
'alt' => $slide['title'],
|
||||
'target' => $blank ? '_blank' : '_self',
|
||||
];
|
||||
}
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_render_slider')) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo aksrt_render_slider([
|
||||
'slides' => $slide_payload,
|
||||
'effect' => $effect,
|
||||
'loop' => $loop,
|
||||
'autoplay' => $autoplay,
|
||||
'interval' => $interval * 1000,
|
||||
'speed' => $speed,
|
||||
'direction' => $direction,
|
||||
'spaceBetween' => $spacebetween,
|
||||
'pc_height' => $height . 'px',
|
||||
'm_height' => $m_height . 'px',
|
||||
'scale_height' => $scale_height && $direction !== 'vertical',
|
||||
'scale_ratio' => $scale / 100,
|
||||
'show_nav' => $nav,
|
||||
'show_pagination' => $dots,
|
||||
'slider_class' => 'slide-widget',
|
||||
'wrapper_class' => 'aksrt-widget-slider-wrap',
|
||||
]);
|
||||
} else {
|
||||
// Minimal fallback without helper.
|
||||
echo '<div class="aksrt-widget-slider" style="--aksrt-slider-h:' . (int) $height . 'px">';
|
||||
foreach ($slide_payload as $i => $slide) {
|
||||
$active = $i === 0 ? ' is-active' : '';
|
||||
echo '<div class="aksrt-widget-slider__slide' . esc_attr($active) . '">';
|
||||
$img = '<img src="' . esc_url($slide['image']) . '" alt="' . esc_attr($slide['title']) . '" loading="lazy" />';
|
||||
if ($slide['link'] !== '') {
|
||||
echo '<a href="' . esc_url($slide['link']) . '"' . ($blank ? ' target="_blank" rel="noopener noreferrer"' : '') . '>' . $img . '</a>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
} else {
|
||||
echo $img; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = (string) ($instance['title'] ?? '');
|
||||
$interval = (int) ($instance['interval'] ?? 4);
|
||||
$height = (int) ($instance['height'] ?? $instance['pc_height'] ?? 400);
|
||||
$m_height = (int) ($instance['m_height'] ?? $height);
|
||||
$effect = sanitize_key((string) ($instance['effect'] ?? 'slide'));
|
||||
$direction = sanitize_key((string) ($instance['direction'] ?? 'horizontal'));
|
||||
$speed = (int) ($instance['speed'] ?? 0);
|
||||
$spacebetween = (int) ($instance['spacebetween'] ?? 15);
|
||||
$scale = (int) ($instance['scale'] ?? 35);
|
||||
$slides = $this->normalize_slides($instance['slides'] ?? []);
|
||||
if (!$slides) {
|
||||
$slides = [
|
||||
['image' => '', 'href' => '', 'title' => '', 'desc' => ''],
|
||||
['image' => '', 'href' => '', 'title' => '', 'desc' => ''],
|
||||
];
|
||||
}
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr($title); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('幻灯片方向', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('direction')); ?>">
|
||||
<option value="horizontal" <?php selected($direction, 'horizontal'); ?>><?php esc_html_e('左右切换', 'aksrt'); ?></option>
|
||||
<option value="vertical" <?php selected($direction, 'vertical'); ?>><?php esc_html_e('上下切换', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('切换效果', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('effect')); ?>">
|
||||
<option value="slide" <?php selected($effect, 'slide'); ?>><?php esc_html_e('滑动', 'aksrt'); ?></option>
|
||||
<option value="fade" <?php selected($effect, 'fade'); ?>><?php esc_html_e('淡入', 'aksrt'); ?></option>
|
||||
<option value="coverflow" <?php selected($effect, 'coverflow'); ?>><?php esc_html_e('Coverflow', 'aksrt'); ?></option>
|
||||
<option value="cube" <?php selected($effect, 'cube'); ?>><?php esc_html_e('Cube', 'aksrt'); ?></option>
|
||||
<option value="flip" <?php selected($effect, 'flip'); ?>><?php esc_html_e('Flip', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('PC 高度(px)', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="80" max="800" name="<?php echo esc_attr($this->get_field_name('height')); ?>" value="<?php echo esc_attr((string) $height); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('移动端高度(px)', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="60" max="800" name="<?php echo esc_attr($this->get_field_name('m_height')); ?>" value="<?php echo esc_attr((string) $m_height); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('scale_height')); ?>" value="1" <?php checked(!isset($instance['scale_height']) || !empty($instance['scale_height'])); ?>> <?php esc_html_e('按比例自动高度', 'aksrt'); ?></label>
|
||||
<label><?php esc_html_e('长宽比例 %', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="10" max="300" step="5" name="<?php echo esc_attr($this->get_field_name('scale')); ?>" value="<?php echo esc_attr((string) $scale); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('间距(px)', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="0" max="500" step="5" name="<?php echo esc_attr($this->get_field_name('spacebetween')); ?>" value="<?php echo esc_attr((string) $spacebetween); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('切换速度(ms,0=自动)', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="0" max="3000" step="100" name="<?php echo esc_attr($this->get_field_name('speed')); ?>" value="<?php echo esc_attr((string) $speed); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('停顿时间(秒)', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="0" max="20" name="<?php echo esc_attr($this->get_field_name('interval')); ?>" value="<?php echo esc_attr((string) $interval); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('loop')); ?>" value="1" <?php checked(!empty($instance['loop']) || !isset($instance['loop'])); ?>> <?php esc_html_e('循环播放', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('button')); ?>" value="1" <?php checked(!empty($instance['button']) || !isset($instance['button'])); ?>> <?php esc_html_e('翻页按钮', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('pagination')); ?>" value="1" <?php checked(!empty($instance['pagination']) || !isset($instance['pagination'])); ?>> <?php esc_html_e('指示点', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('autoplay')); ?>" value="1" <?php checked(!isset($instance['autoplay']) || !empty($instance['autoplay'])); ?>> <?php esc_html_e('自动播放', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('blank')); ?>" value="1" <?php checked(!empty($instance['blank'])); ?>> <?php esc_html_e('新窗口打开', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php foreach ($slides as $i => $slide) : ?>
|
||||
<p style="border-top:1px solid #ddd;padding-top:8px;">
|
||||
<strong><?php echo esc_html(sprintf(__('幻灯片 %d', 'aksrt'), $i + 1)); ?></strong><br>
|
||||
<label><?php esc_html_e('图片 URL', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][image]"); ?>" value="<?php echo esc_attr($slide['image']); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('链接', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][href]"); ?>" value="<?php echo esc_attr($slide['href']); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][title]"); ?>" value="<?php echo esc_attr($slide['title']); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('简介', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][desc]"); ?>" value="<?php echo esc_attr($slide['desc']); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<?php endforeach; ?>
|
||||
<?php
|
||||
$extra = max(0, 4 - count($slides));
|
||||
for ($j = 0; $j < $extra; $j++) {
|
||||
$i = count($slides) + $j;
|
||||
?>
|
||||
<p style="border-top:1px dashed #eee;padding-top:8px;">
|
||||
<strong><?php echo esc_html(sprintf(__('空槽 %d', 'aksrt'), $i + 1)); ?></strong>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][image]"); ?>" placeholder="<?php esc_attr_e('图片 URL', 'aksrt'); ?>">
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][href]"); ?>" placeholder="<?php esc_attr_e('链接', 'aksrt'); ?>">
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][title]"); ?>" placeholder="<?php esc_attr_e('标题', 'aksrt'); ?>">
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][desc]"); ?>" placeholder="<?php esc_attr_e('简介', 'aksrt'); ?>">
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<p class="description"><?php esc_html_e('使用主题共享 Swiper(.new-swiper)。图片 URL 必填;仅 1 张时不滑动。', 'aksrt'); ?></p>
|
||||
<p class="description"><?php esc_html_e('对齐 Zibll 幻灯片:方向/效果/比例自动高度/间距/速度/自动播放/间隔。视频背景与图层视差由共享 Swiper 栈支持。', 'aksrt'); ?></p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$slides = $this->normalize_slides($new_instance['slides'] ?? []);
|
||||
$effect = sanitize_key((string) ($new_instance['effect'] ?? 'slide'));
|
||||
if (!in_array($effect, ['slide', 'fade', 'cube', 'coverflow', 'flip'], true)) {
|
||||
$effect = 'slide';
|
||||
}
|
||||
$direction = sanitize_key((string) ($new_instance['direction'] ?? 'horizontal'));
|
||||
if (!in_array($direction, ['horizontal', 'vertical'], true)) {
|
||||
$direction = 'horizontal';
|
||||
}
|
||||
$business = [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'interval' => max(0, min(20, (int) ($new_instance['interval'] ?? 4))),
|
||||
'height' => max(80, min(800, (int) ($new_instance['height'] ?? $new_instance['pc_height'] ?? 400))),
|
||||
'm_height' => max(60, min(800, (int) ($new_instance['m_height'] ?? ($new_instance['height'] ?? $new_instance['pc_height'] ?? 400)))),
|
||||
'effect' => $effect,
|
||||
'direction' => $direction,
|
||||
'speed' => max(0, min(3000, (int) ($new_instance['speed'] ?? 0))),
|
||||
'spacebetween'=> max(0, min(500, (int) ($new_instance['spacebetween'] ?? 15))),
|
||||
'scale_height'=> !empty($new_instance['scale_height']) ? 1 : 0,
|
||||
'scale' => max(10, min(300, (int) ($new_instance['scale'] ?? 35))),
|
||||
'loop' => !empty($new_instance['loop']) ? 1 : 0,
|
||||
'button' => !empty($new_instance['button']) ? 1 : 0,
|
||||
'pagination' => !empty($new_instance['pagination']) ? 1 : 0,
|
||||
'autoplay' => !empty($new_instance['autoplay']) ? 1 : 0,
|
||||
'blank' => !empty($new_instance['blank']) ? 1 : 0,
|
||||
'slides' => $slides,
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return array<int, array{image:string,href:string,title:string,desc:string}>
|
||||
*/
|
||||
private function normalize_slides($raw): array {
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($raw as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$image = esc_url_raw(trim((string) ($row['image'] ?? $row['link'] ?? '')));
|
||||
if ($image === '') {
|
||||
continue;
|
||||
}
|
||||
$out[] = [
|
||||
'image' => $image,
|
||||
'href' => esc_url_raw(trim((string) ($row['href'] ?? ''))),
|
||||
'title' => sanitize_text_field((string) ($row['title'] ?? '')),
|
||||
'desc' => sanitize_text_field((string) ($row['desc'] ?? $row['dec'] ?? '')),
|
||||
];
|
||||
if (count($out) >= 8) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
/**
|
||||
* Stats number cards (zibll zib_widget_stats lite).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetStats extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_stats',
|
||||
esc_html__('AKSRT 数据统计', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('数字统计卡片网格(站点数据展示)。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_stats',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$stats = $this->normalize_stats($instance['stats'] ?? []);
|
||||
if (!$stats) {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('请配置至少一项统计数据', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<p class="widget-empty">' . esc_html__('请配置至少一项统计数据', 'aksrt') . '</p>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
$cols = max(1, min(4, (int) ($instance['columns'] ?? 4)));
|
||||
$m_cols = max(1, min(3, (int) ($instance['mobile_columns'] ?? 2)));
|
||||
$intro_title = trim((string) ($instance['intro_title'] ?? ''));
|
||||
$intro_sub = trim((string) ($instance['intro_subtitle'] ?? ''));
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
$style = sprintf('--aksrt-stats-pc:%d;--aksrt-stats-m:%d;', $cols, $m_cols);
|
||||
echo '<div class="aksrt-stats" style="' . esc_attr($style) . '">';
|
||||
if ($intro_title !== '' || $intro_sub !== '') {
|
||||
echo '<div class="aksrt-stats__intro">';
|
||||
if ($intro_title !== '') {
|
||||
echo '<h2 class="aksrt-stats__intro-title">' . esc_html($intro_title) . '</h2>';
|
||||
}
|
||||
if ($intro_sub !== '') {
|
||||
echo '<p class="aksrt-stats__intro-sub">' . esc_html($intro_sub) . '</p>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '<div class="aksrt-stats__grid">';
|
||||
$palette = ['pink', 'purple', 'orange', 'blue', 'green'];
|
||||
$i = 0;
|
||||
foreach ($stats as $s) {
|
||||
$color = sanitize_key((string) ($s['color'] ?? 'auto'));
|
||||
if ($color === '' || $color === 'auto') {
|
||||
$color = $palette[$i % count($palette)];
|
||||
}
|
||||
$i++;
|
||||
echo '<div class="aksrt-stats__item color-' . esc_attr($color) . '" data-aksrt-animate="fade-up">';
|
||||
if ($s['icon'] !== '') {
|
||||
echo '<div class="aksrt-stats__icon"><i class="' . esc_attr($s['icon']) . '" aria-hidden="true"></i></div>';
|
||||
}
|
||||
echo '<div class="aksrt-stats__value">';
|
||||
if ($s['prefix'] !== '') {
|
||||
echo '<span class="aksrt-stats__prefix">' . esc_html($s['prefix']) . '</span>';
|
||||
}
|
||||
// Wire effects.js count-up when number is numeric.
|
||||
$raw_num = preg_replace('/[^\d.]/', '', (string) $s['number']);
|
||||
$is_numeric = $raw_num !== '' && is_numeric($raw_num);
|
||||
if ($is_numeric) {
|
||||
$dec = 0;
|
||||
if (str_contains((string) $raw_num, '.')) {
|
||||
$dec = strlen(substr(strrchr((string) $raw_num, '.'), 1));
|
||||
}
|
||||
printf(
|
||||
'<span class="aksrt-stats__number" data-aksrt-countup data-target="%s" data-duration="1600" data-decimals="%d">0</span>',
|
||||
esc_attr((string) $raw_num),
|
||||
(int) $dec
|
||||
);
|
||||
} else {
|
||||
echo '<span class="aksrt-stats__number" data-target="' . esc_attr($s['number']) . '">' . esc_html($s['number']) . '</span>';
|
||||
}
|
||||
if ($s['unit'] !== '') {
|
||||
echo '<span class="aksrt-stats__unit">' . esc_html($s['unit']) . '</span>';
|
||||
}
|
||||
echo '</div>';
|
||||
if ($s['label'] !== '') {
|
||||
echo '<div class="aksrt-stats__label">' . esc_html($s['label']) . '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div></div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return array<int, array{number:string,label:string,unit:string,prefix:string,icon:string,color:string}>
|
||||
*/
|
||||
private function normalize_stats($raw): array {
|
||||
if (is_string($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
$raw = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($raw as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$number = sanitize_text_field((string) ($row['number'] ?? ''));
|
||||
if ($number === '') {
|
||||
continue;
|
||||
}
|
||||
$out[] = [
|
||||
'number' => $number,
|
||||
'label' => sanitize_text_field((string) ($row['label'] ?? '')),
|
||||
'unit' => sanitize_text_field((string) ($row['unit'] ?? '')),
|
||||
'prefix' => sanitize_text_field((string) ($row['prefix'] ?? '')),
|
||||
'icon' => sanitize_text_field((string) ($row['icon'] ?? '')),
|
||||
'color' => sanitize_key((string) ($row['color'] ?? 'auto')),
|
||||
];
|
||||
if (count($out) >= 12) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$stats = is_string($instance['stats'] ?? null)
|
||||
? (string) $instance['stats']
|
||||
: wp_json_encode($this->normalize_stats($instance['stats'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('数据 JSON', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="6" name="<?php echo esc_attr($this->get_field_name('stats')); ?>" placeholder='[{"number":"1280","label":"用户","unit":"+","icon":"fas fa-users","color":"blue"}]'><?php echo esc_textarea((string) $stats); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('PC 列数', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="4" name="<?php echo esc_attr($this->get_field_name('columns')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['columns'] ?? 4))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('移动列数', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="3" name="<?php echo esc_attr($this->get_field_name('mobile_columns')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['mobile_columns'] ?? 2))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('介绍标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('intro_title')); ?>" value="<?php echo esc_attr((string) ($instance['intro_title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('介绍副标题', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="2" name="<?php echo esc_attr($this->get_field_name('intro_subtitle')); ?>"><?php echo esc_textarea((string) ($instance['intro_subtitle'] ?? '')); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$business = [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'stats' => $this->normalize_stats($new_instance['stats'] ?? []),
|
||||
'columns' => max(1, min(4, (int) ($new_instance['columns'] ?? 4))),
|
||||
'mobile_columns' => max(1, min(3, (int) ($new_instance['mobile_columns'] ?? 2))),
|
||||
'intro_title' => sanitize_text_field((string) ($new_instance['intro_title'] ?? '')),
|
||||
'intro_subtitle' => sanitize_text_field((string) ($new_instance['intro_subtitle'] ?? '')),
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,357 @@
|
||||
<?php
|
||||
/**
|
||||
* Term aggregation cards (zibll zib_widget_ui_term_lists_card → zib_term_aggregation).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetTermAggregation extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_term_aggregation',
|
||||
esc_html__('AKSRT 分类聚合', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('分类封面 + 描述 + 更多文章 CTA + 文章列表聚合卡。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_term_aggregation',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/** @return array<int,int> */
|
||||
public static function parse_ids_static(string $raw): array {
|
||||
if ($raw === '') {
|
||||
return [];
|
||||
}
|
||||
$ids = [];
|
||||
foreach (preg_split('/[\s,]+/', $raw) ?: [] as $p) {
|
||||
$id = (int) $p;
|
||||
if ($id > 0) {
|
||||
$ids[] = $id;
|
||||
}
|
||||
}
|
||||
return array_values(array_unique($ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \WP_Term $term
|
||||
*/
|
||||
private static function term_cover_url(\WP_Term $term): string {
|
||||
if (class_exists(WidgetTermCards::class) && method_exists(WidgetTermCards::class, 'term_cover_url')) {
|
||||
return WidgetTermCards::term_cover_url($term);
|
||||
}
|
||||
$img_id = (int) get_term_meta($term->term_id, 'thumbnail_id', true);
|
||||
if (!$img_id) {
|
||||
$img_id = (int) get_term_meta($term->term_id, '_aksrt_cover', true);
|
||||
}
|
||||
if ($img_id) {
|
||||
$url = wp_get_attachment_image_url($img_id, 'medium');
|
||||
if ($url) {
|
||||
return (string) $url;
|
||||
}
|
||||
}
|
||||
$url = (string) get_term_meta($term->term_id, '_aksrt_cover_url', true);
|
||||
return $url !== '' ? $url : get_template_directory_uri() . '/assets/img/placeholder.svg';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sum of post views under a term (best-effort).
|
||||
*/
|
||||
private static function term_views_sum(int $term_id, string $taxonomy): int {
|
||||
$q = new \WP_Query([
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => 50,
|
||||
'fields' => 'ids',
|
||||
'no_found_rows' => true,
|
||||
'update_post_meta_cache' => true,
|
||||
'update_post_term_cache' => false,
|
||||
'tax_query' => [[
|
||||
'taxonomy' => $taxonomy,
|
||||
'field' => 'term_id',
|
||||
'terms' => $term_id,
|
||||
]],
|
||||
]);
|
||||
$sum = 0;
|
||||
foreach ($q->posts as $pid) {
|
||||
$sum += function_exists('aksrt_get_post_views')
|
||||
? (int) aksrt_get_post_views((int) $pid)
|
||||
: (int) get_post_meta((int) $pid, 'aksrt_post_views', true);
|
||||
}
|
||||
return $sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public static function render_html(array $instance): string {
|
||||
$taxonomy = sanitize_key((string) ($instance['taxonomy'] ?? 'category'));
|
||||
if (!taxonomy_exists($taxonomy)) {
|
||||
$taxonomy = 'category';
|
||||
}
|
||||
$term_ids = self::parse_ids_static((string) ($instance['term_ids'] ?? ''));
|
||||
$count = max(1, min(20, (int) ($instance['count'] ?? 4)));
|
||||
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
|
||||
$order = strtoupper(sanitize_key((string) ($instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC';
|
||||
$columns = max(1, min(3, (int) ($instance['columns'] ?? 1)));
|
||||
$target_blank = !empty($instance['target_blank']);
|
||||
$tgt = $target_blank ? ' target="_blank" rel="noopener noreferrer"' : '';
|
||||
|
||||
if (!$term_ids) {
|
||||
$terms = get_terms([
|
||||
'taxonomy' => $taxonomy,
|
||||
'number' => max(1, min(4, (int) ($instance['auto_terms'] ?? 2))),
|
||||
'orderby' => 'count',
|
||||
'order' => 'DESC',
|
||||
'hide_empty' => true,
|
||||
]);
|
||||
if (is_wp_error($terms) || !$terms) {
|
||||
return function_exists('aksrt_get_ajax_null')
|
||||
? aksrt_get_ajax_null(__('暂无分类', 'aksrt'))
|
||||
: '<p class="widget-empty ajax-item">' . esc_html__('暂无分类', 'aksrt') . '</p>';
|
||||
}
|
||||
$term_ids = array_map(static fn($t) => (int) $t->term_id, $terms);
|
||||
}
|
||||
|
||||
ob_start();
|
||||
echo '<div class="aksrt-term-agg ajax-item" style="--aksrt-ta-cols:' . esc_attr((string) $columns) . '">';
|
||||
foreach ($term_ids as $term_id) {
|
||||
$term = get_term($term_id, $taxonomy);
|
||||
if (!$term || is_wp_error($term)) {
|
||||
continue;
|
||||
}
|
||||
$link = get_term_link($term);
|
||||
if (is_wp_error($link)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$cover = self::term_cover_url($term);
|
||||
$views = self::term_views_sum((int) $term->term_id, $taxonomy);
|
||||
$more_badge = '<badge class="img-badge px12"><i class="fa fa-fire" aria-hidden="true"></i> ' . esc_html((string) $views) . '</badge>';
|
||||
|
||||
$img_html = '';
|
||||
if (function_exists('aksrt_graphic_card')) {
|
||||
$img_html = aksrt_graphic_card([
|
||||
'type' => '',
|
||||
'class' => '',
|
||||
'img' => $cover,
|
||||
'alt' => $term->name,
|
||||
'link' => [
|
||||
'url' => $link,
|
||||
'target' => $target_blank ? '_blank' : '',
|
||||
],
|
||||
'lazy' => true,
|
||||
'height_scale' => 70,
|
||||
'mask_opacity' => 0,
|
||||
'more' => $more_badge,
|
||||
]);
|
||||
}
|
||||
|
||||
$description = trim((string) $term->description);
|
||||
if ($description === '' && current_user_can('manage_categories')) {
|
||||
$edit = get_edit_term_link((int) $term->term_id, $taxonomy);
|
||||
$description = esc_html__('请在后台为该分类添加描述。', 'aksrt');
|
||||
if ($edit) {
|
||||
$description .= ' <a href="' . esc_url($edit) . '">' . esc_html__('立即编辑', 'aksrt') . '</a>';
|
||||
}
|
||||
} else {
|
||||
$description = esc_html($description);
|
||||
}
|
||||
|
||||
$posts_q = [
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => $count,
|
||||
'ignore_sticky_posts' => true,
|
||||
'no_found_rows' => false,
|
||||
'tax_query' => [[
|
||||
'taxonomy' => $taxonomy,
|
||||
'field' => 'term_id',
|
||||
'terms' => (int) $term->term_id,
|
||||
]],
|
||||
];
|
||||
if ($orderby === 'views') {
|
||||
$posts_q['meta_key'] = 'aksrt_post_views';
|
||||
$posts_q['orderby'] = 'meta_value_num';
|
||||
$posts_q['order'] = $order;
|
||||
} elseif ($orderby === 'like') {
|
||||
$posts_q['meta_key'] = '_aksrt_like_count';
|
||||
$posts_q['orderby'] = 'meta_value_num';
|
||||
$posts_q['order'] = $order;
|
||||
} elseif ($orderby === 'favorite') {
|
||||
$posts_q['meta_key'] = '_aksrt_favorite_count';
|
||||
$posts_q['orderby'] = 'meta_value_num';
|
||||
$posts_q['order'] = $order;
|
||||
} elseif ($orderby === 'comment_count') {
|
||||
$posts_q['orderby'] = 'comment_count';
|
||||
$posts_q['order'] = $order;
|
||||
} elseif ($orderby === 'modified') {
|
||||
$posts_q['orderby'] = 'modified';
|
||||
$posts_q['order'] = $order;
|
||||
} elseif ($orderby === 'rand') {
|
||||
$posts_q['orderby'] = 'rand';
|
||||
} else {
|
||||
$posts_q['orderby'] = 'date';
|
||||
$posts_q['order'] = $order;
|
||||
}
|
||||
$q = new \WP_Query($posts_q);
|
||||
$found = (int) $q->found_posts;
|
||||
|
||||
echo '<div class="zib-widget term-aggregation mb10 aksrt-term-agg__card">';
|
||||
echo '<div class="mb20 hover-zoom-img flex px12-sm">';
|
||||
echo '<div class="term-img flex0 em09-sm">' . $img_html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '<div class="term-title ml10 flex xx flex1 jsb">';
|
||||
echo '<a class="em14 key-color text-ellipsis"' . $tgt . ' href="' . esc_url($link) . '">' . esc_html($term->name) . '</a>';
|
||||
echo '<div class="text-ellipsis-2 muted-color">' . $description . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '<div class="term-meta flex jsb ac">';
|
||||
echo '<span class="px12 muted-3-color text-ellipsis"><i class="fa fa-file-text-o fa-fw" aria-hidden="true"></i>'
|
||||
. esc_html(sprintf(
|
||||
/* translators: %s: article count */
|
||||
__('%s篇文章', 'aksrt'),
|
||||
(string) $found
|
||||
))
|
||||
. '</span>';
|
||||
echo '<a class="but p2-10 px12 c-blue hide-sm shrink0"' . $tgt . ' href="' . esc_url($link) . '"><i class="fa fa-angle-right" aria-hidden="true"></i>' . esc_html__('更多文章', 'aksrt') . '</a>';
|
||||
echo '</div></div></div>';
|
||||
|
||||
if ($q->have_posts()) {
|
||||
while ($q->have_posts()) {
|
||||
$q->the_post();
|
||||
$pid = get_the_ID();
|
||||
$title = get_the_title($pid);
|
||||
$meta = '';
|
||||
if ($orderby === 'views') {
|
||||
$v = function_exists('aksrt_get_post_views') ? (int) aksrt_get_post_views($pid) : (int) get_post_meta($pid, 'aksrt_post_views', true);
|
||||
$meta = '<i class="fa fa-eye mr3" aria-hidden="true"></i>' . esc_html((string) $v);
|
||||
} elseif ($orderby === 'like') {
|
||||
$v = function_exists('aksrt_get_post_likes') ? (int) aksrt_get_post_likes($pid) : (int) get_post_meta($pid, '_aksrt_like_count', true);
|
||||
$meta = '<i class="fa fa-heart mr3" aria-hidden="true"></i>' . esc_html((string) $v);
|
||||
} elseif ($orderby === 'favorite') {
|
||||
$v = (int) get_post_meta($pid, '_aksrt_favorite_count', true);
|
||||
$meta = '<i class="fa fa-star mr3" aria-hidden="true"></i>' . esc_html((string) $v);
|
||||
} elseif ($orderby === 'comment_count') {
|
||||
$meta = '<i class="fa fa-comment-o mr3" aria-hidden="true"></i>' . esc_html((string) get_comments_number($pid));
|
||||
} else {
|
||||
$ts = $orderby === 'modified' ? get_the_modified_time('U', $pid) : get_the_time('U', $pid);
|
||||
$ago = human_time_diff((int) $ts, current_time('timestamp'));
|
||||
$meta = '<i class="fa fa-clock-o mr3" aria-hidden="true"></i>' . esc_html($ago);
|
||||
}
|
||||
echo '<div class="mt10 flex jsb ac">';
|
||||
echo '<div class="text-ellipsis"><a class="icon-circle mln3"' . $tgt . ' href="' . esc_url(get_permalink($pid)) . '">' . esc_html($title) . '</a></div>';
|
||||
echo '<div class="em09 muted-3-color flex0 ml10">' . $meta . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '</div>';
|
||||
}
|
||||
wp_reset_postdata();
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
return (string) ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $args
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function widget($args, $instance): void {
|
||||
$html = self::render_html($instance);
|
||||
if ($html === '') {
|
||||
return;
|
||||
}
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function form($instance): void {
|
||||
$taxs = get_taxonomies(['public' => true], 'objects');
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('分类法', 'aksrt'); ?>
|
||||
<select class="widefat" name="<?php echo esc_attr($this->get_field_name('taxonomy')); ?>">
|
||||
<?php foreach ($taxs as $tax) : ?>
|
||||
<option value="<?php echo esc_attr($tax->name); ?>" <?php selected(($instance['taxonomy'] ?? 'category'), $tax->name); ?>><?php echo esc_html($tax->labels->name); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('term ID(逗号,空=自动热门)', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('term_ids')); ?>" value="<?php echo esc_attr((string) ($instance['term_ids'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('每分类文章数', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="20" name="<?php echo esc_attr($this->get_field_name('count')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['count'] ?? 4))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('列数', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="3" name="<?php echo esc_attr($this->get_field_name('columns')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['columns'] ?? 1))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('文章排序', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
|
||||
<option value="date" <?php selected(($instance['orderby'] ?? 'date'), 'date'); ?>><?php esc_html_e('时间', 'aksrt'); ?></option>
|
||||
<option value="modified" <?php selected(($instance['orderby'] ?? ''), 'modified'); ?>><?php esc_html_e('更新', 'aksrt'); ?></option>
|
||||
<option value="views" <?php selected(($instance['orderby'] ?? ''), 'views'); ?>><?php esc_html_e('浏览', 'aksrt'); ?></option>
|
||||
<option value="like" <?php selected(($instance['orderby'] ?? ''), 'like'); ?>><?php esc_html_e('点赞', 'aksrt'); ?></option>
|
||||
<option value="favorite" <?php selected(($instance['orderby'] ?? ''), 'favorite'); ?>><?php esc_html_e('收藏', 'aksrt'); ?></option>
|
||||
<option value="comment_count" <?php selected(($instance['orderby'] ?? ''), 'comment_count'); ?>><?php esc_html_e('评论', 'aksrt'); ?></option>
|
||||
<option value="rand" <?php selected(($instance['orderby'] ?? ''), 'rand'); ?>><?php esc_html_e('随机', 'aksrt'); ?></option>
|
||||
</select>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('order')); ?>">
|
||||
<option value="desc" <?php selected(($instance['order'] ?? 'desc'), 'desc'); ?>><?php esc_html_e('降序', 'aksrt'); ?></option>
|
||||
<option value="asc" <?php selected(($instance['order'] ?? ''), 'asc'); ?>><?php esc_html_e('升序', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('target_blank')); ?>" value="1" <?php checked(!empty($instance['target_blank'])); ?>> <?php esc_html_e('新窗口打开', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $new_instance
|
||||
* @param array<string,mixed> $old_instance
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'date'));
|
||||
if (!in_array($orderby, ['date', 'modified', 'views', 'like', 'favorite', 'comment_count', 'rand'], true)) {
|
||||
$orderby = 'date';
|
||||
}
|
||||
$business = [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'taxonomy' => sanitize_key((string) ($new_instance['taxonomy'] ?? 'category')),
|
||||
'term_ids' => sanitize_text_field((string) ($new_instance['term_ids'] ?? '')),
|
||||
'count' => max(1, min(20, (int) ($new_instance['count'] ?? 4))),
|
||||
'columns' => max(1, min(3, (int) ($new_instance['columns'] ?? 1))),
|
||||
'orderby' => $orderby,
|
||||
'order' => strtoupper(sanitize_key((string) ($new_instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
|
||||
'auto_terms' => max(1, min(4, (int) ($new_instance['auto_terms'] ?? 2))),
|
||||
'target_blank' => !empty($new_instance['target_blank']) ? 1 : 0,
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
<?php
|
||||
/**
|
||||
* Taxonomy term graphic cards (zibll zib_widget_ui_term_card → zib_graphic_card).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetTermCards extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_term_cards',
|
||||
esc_html__('AKSRT 分类/标签卡片', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('以图文封面卡片展示分类或标签(style-1..4)。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_term_cards',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/** @return array<int,int> */
|
||||
public static function parse_ids_static(string $raw): array {
|
||||
if ($raw === '') {
|
||||
return [];
|
||||
}
|
||||
$ids = [];
|
||||
foreach (preg_split('/[\s,]+/', $raw) ?: [] as $p) {
|
||||
$id = (int) $p;
|
||||
if ($id > 0) {
|
||||
$ids[] = $id;
|
||||
}
|
||||
}
|
||||
return array_values(array_unique($ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve term cover URL.
|
||||
*/
|
||||
public static function term_cover_url(\WP_Term $term): string {
|
||||
$thumb = '';
|
||||
$img_id = (int) get_term_meta($term->term_id, 'thumbnail_id', true);
|
||||
if (!$img_id) {
|
||||
$img_id = (int) get_term_meta($term->term_id, '_aksrt_cover', true);
|
||||
}
|
||||
if ($img_id) {
|
||||
$thumb = (string) (wp_get_attachment_image_url($img_id, 'medium_large') ?: wp_get_attachment_image_url($img_id, 'medium') ?: '');
|
||||
}
|
||||
if ($thumb === '') {
|
||||
$thumb = (string) get_term_meta($term->term_id, '_aksrt_cover_url', true);
|
||||
}
|
||||
if ($thumb === '') {
|
||||
// Lightweight default gradient placeholder as data URI is heavy — use theme placeholder.
|
||||
$thumb = get_template_directory_uri() . '/assets/img/placeholder.svg';
|
||||
}
|
||||
return $thumb;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public static function render_html(array $instance): string {
|
||||
$taxonomy = sanitize_key((string) ($instance['taxonomy'] ?? 'category'));
|
||||
if (!taxonomy_exists($taxonomy)) {
|
||||
$taxonomy = 'category';
|
||||
}
|
||||
$number = max(1, min(30, (int) ($instance['number'] ?? 8)));
|
||||
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'count'));
|
||||
$include = self::parse_ids_static((string) ($instance['include'] ?? ''));
|
||||
$hide_empty = !isset($instance['hide_empty']) || !empty($instance['hide_empty']);
|
||||
$type = sanitize_key((string) ($instance['type'] ?? 'style-1'));
|
||||
if (!in_array($type, ['style-1', 'style-2', 'style-3', 'style-4'], true)) {
|
||||
$type = 'style-1';
|
||||
}
|
||||
$height_scale = max(20, min(300, (int) ($instance['height_scale'] ?? 60)));
|
||||
$mask_opacity = max(0, min(90, (int) ($instance['mask_opacity'] ?? 10)));
|
||||
$pc_row = max(1, min(12, (int) ($instance['pc_row'] ?? $instance['columns'] ?? 2)));
|
||||
$m_row = max(1, min(12, (int) ($instance['m_row'] ?? 1)));
|
||||
$target_blank = !empty($instance['target_blank']);
|
||||
|
||||
$term_args = [
|
||||
'taxonomy' => $taxonomy,
|
||||
'number' => $number,
|
||||
'hide_empty' => $hide_empty,
|
||||
];
|
||||
if ($include) {
|
||||
$term_args['include'] = $include;
|
||||
$term_args['orderby'] = 'include';
|
||||
} else {
|
||||
$term_args['orderby'] = in_array($orderby, ['name', 'count', 'id'], true) ? $orderby : 'count';
|
||||
$term_args['order'] = $orderby === 'name' ? 'ASC' : 'DESC';
|
||||
}
|
||||
|
||||
$terms = get_terms($term_args);
|
||||
if (is_wp_error($terms) || !$terms) {
|
||||
return function_exists('aksrt_get_ajax_null')
|
||||
? aksrt_get_ajax_null(__('暂无分类', 'aksrt'))
|
||||
: '<p class="widget-empty ajax-item">' . esc_html__('暂无分类', 'aksrt') . '</p>';
|
||||
}
|
||||
|
||||
$icon = $taxonomy === 'category'
|
||||
? '<i class="fa fa-folder-open-o mr6" aria-hidden="true"></i>'
|
||||
: '<i class="fa fa-cube mr6" aria-hidden="true"></i>';
|
||||
|
||||
$is_row = count($terms) > 1;
|
||||
// Bootstrap-like col classes already used elsewhere; keep numeric CSS vars as fallback.
|
||||
$pc_span = max(1, (int) floor(12 / $pc_row));
|
||||
$m_span = max(1, (int) floor(12 / $m_row));
|
||||
$row_class = 'col-sm-' . $pc_span . ($m_row > 1 ? ' col-xs-' . $m_span : '');
|
||||
|
||||
ob_start();
|
||||
echo '<div class="aksrt-term-cards ajax-item' . ($is_row ? ' mb10' : ' mb20') . '">';
|
||||
if ($is_row) {
|
||||
echo '<div class="row gutters-5">';
|
||||
}
|
||||
foreach ($terms as $term) {
|
||||
if (!($term instanceof \WP_Term)) {
|
||||
continue;
|
||||
}
|
||||
$link = get_term_link($term);
|
||||
if (is_wp_error($link)) {
|
||||
continue;
|
||||
}
|
||||
$name = mb_strlen($term->name) > 16 ? mb_substr($term->name, 0, 16) . '…' : $term->name;
|
||||
$desc = trim((string) $term->description);
|
||||
if (mb_strlen($desc) > 24) {
|
||||
$desc = mb_substr($desc, 0, 24) . '…';
|
||||
}
|
||||
$count = (int) $term->count;
|
||||
$count_label = sprintf(
|
||||
/* translators: %d: post count */
|
||||
_n('%d篇文章', '%d篇文章', $count, 'aksrt'),
|
||||
$count
|
||||
);
|
||||
|
||||
$text1 = $name;
|
||||
$text2 = $desc;
|
||||
$text3 = $icon . esc_html($count_label);
|
||||
if ($type === 'style-2' || $type === 'style-4') {
|
||||
$text3 = '<item data-toggle="tooltip" title="' . esc_attr(sprintf(__('共%d篇文章', 'aksrt'), $count)) . '">' . $icon . $count . '</item>';
|
||||
}
|
||||
if ($type === 'style-3' || $type === 'style-4') {
|
||||
$text1 = $icon . esc_html($name);
|
||||
if ($type === 'style-3') {
|
||||
$text3 = '<i class="fa mr6 fa-file-text-o" aria-hidden="true"></i>' . esc_html($count_label);
|
||||
}
|
||||
}
|
||||
|
||||
$card_args = [
|
||||
'type' => $type,
|
||||
'class' => 'mb10',
|
||||
'img' => self::term_cover_url($term),
|
||||
'alt' => $name . ($desc !== '' ? '-' . $desc : ''),
|
||||
'link' => [
|
||||
'url' => $link,
|
||||
'target' => $target_blank ? '_blank' : '',
|
||||
],
|
||||
'text1' => $text1,
|
||||
'text2' => $text2,
|
||||
'text3' => $text3,
|
||||
'lazy' => true,
|
||||
'height_scale' => $height_scale,
|
||||
'mask_opacity' => $mask_opacity,
|
||||
];
|
||||
|
||||
if ($is_row) {
|
||||
echo '<div class="' . esc_attr($row_class) . '">';
|
||||
}
|
||||
if (function_exists('aksrt_graphic_card')) {
|
||||
echo aksrt_graphic_card($card_args); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
} else {
|
||||
echo '<a class="aksrt-term-card" href="' . esc_url($link) . '"><div class="aksrt-term-card__name">' . esc_html($name) . '</div></a>';
|
||||
}
|
||||
if ($is_row) {
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
if ($is_row) {
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
return (string) ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $args
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function widget($args, $instance): void {
|
||||
$html = self::render_html($instance);
|
||||
if ($html === '') {
|
||||
return;
|
||||
}
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function form($instance): void {
|
||||
$taxs = get_taxonomies(['public' => true], 'objects');
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('分类法', 'aksrt'); ?>
|
||||
<select class="widefat" name="<?php echo esc_attr($this->get_field_name('taxonomy')); ?>">
|
||||
<?php foreach ($taxs as $tax) : ?>
|
||||
<option value="<?php echo esc_attr($tax->name); ?>" <?php selected(($instance['taxonomy'] ?? 'category'), $tax->name); ?>><?php echo esc_html($tax->labels->name . ' (' . $tax->name . ')'); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('数量', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="30" name="<?php echo esc_attr($this->get_field_name('number')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['number'] ?? 8))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('PC 列', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="12" name="<?php echo esc_attr($this->get_field_name('pc_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['pc_row'] ?? $instance['columns'] ?? 2))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('移动列', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="12" name="<?php echo esc_attr($this->get_field_name('m_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['m_row'] ?? 1))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('卡片样式', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('type')); ?>">
|
||||
<option value="style-1" <?php selected(($instance['type'] ?? 'style-1'), 'style-1'); ?>><?php esc_html_e('样式 1(左下标题)', 'aksrt'); ?></option>
|
||||
<option value="style-2" <?php selected(($instance['type'] ?? ''), 'style-2'); ?>><?php esc_html_e('样式 2(居中)', 'aksrt'); ?></option>
|
||||
<option value="style-3" <?php selected(($instance['type'] ?? ''), 'style-3'); ?>><?php esc_html_e('样式 3(左下多层)', 'aksrt'); ?></option>
|
||||
<option value="style-4" <?php selected(($instance['type'] ?? ''), 'style-4'); ?>><?php esc_html_e('样式 4(图+下方文案)', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('高度占比 %', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="20" max="300" name="<?php echo esc_attr($this->get_field_name('height_scale')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['height_scale'] ?? 60))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('遮罩 0-90', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="0" max="90" name="<?php echo esc_attr($this->get_field_name('mask_opacity')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['mask_opacity'] ?? 10))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('排序', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
|
||||
<option value="count" <?php selected(($instance['orderby'] ?? 'count'), 'count'); ?>><?php esc_html_e('文章数', 'aksrt'); ?></option>
|
||||
<option value="name" <?php selected(($instance['orderby'] ?? ''), 'name'); ?>><?php esc_html_e('名称', 'aksrt'); ?></option>
|
||||
<option value="id" <?php selected(($instance['orderby'] ?? ''), 'id'); ?>><?php esc_html_e('ID', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('包含 term ID(逗号)', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('include')); ?>" value="<?php echo esc_attr((string) ($instance['include'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hide_empty')); ?>" value="1" <?php checked(!isset($instance['hide_empty']) || !empty($instance['hide_empty'])); ?>> <?php esc_html_e('隐藏空分类', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('target_blank')); ?>" value="1" <?php checked(!empty($instance['target_blank'])); ?>> <?php esc_html_e('新窗口打开', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $new_instance
|
||||
* @param array<string,mixed> $old_instance
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$type = sanitize_key((string) ($new_instance['type'] ?? 'style-1'));
|
||||
if (!in_array($type, ['style-1', 'style-2', 'style-3', 'style-4'], true)) {
|
||||
$type = 'style-1';
|
||||
}
|
||||
$business = [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'taxonomy' => sanitize_key((string) ($new_instance['taxonomy'] ?? 'category')),
|
||||
'number' => max(1, min(30, (int) ($new_instance['number'] ?? 8))),
|
||||
'pc_row' => max(1, min(12, (int) ($new_instance['pc_row'] ?? 2))),
|
||||
'm_row' => max(1, min(12, (int) ($new_instance['m_row'] ?? 1))),
|
||||
'columns' => max(1, min(12, (int) ($new_instance['pc_row'] ?? $new_instance['columns'] ?? 2))),
|
||||
'type' => $type,
|
||||
'height_scale' => max(20, min(300, (int) ($new_instance['height_scale'] ?? 60))),
|
||||
'mask_opacity' => max(0, min(90, (int) ($new_instance['mask_opacity'] ?? 10))),
|
||||
'orderby' => in_array(($new_instance['orderby'] ?? ''), ['count', 'name', 'id'], true) ? $new_instance['orderby'] : 'count',
|
||||
'include' => sanitize_text_field((string) ($new_instance['include'] ?? '')),
|
||||
'hide_empty' => !empty($new_instance['hide_empty']) ? 1 : 0,
|
||||
'target_blank' => !empty($new_instance['target_blank']) ? 1 : 0,
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
/**
|
||||
* Text title / section heading (zibll zib_widget_ui_text_title).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetTextTitle extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_text_title',
|
||||
esc_html__('AKSRT 文字标题', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('自定义标题 + 副标题区块,支持逐字入场动画。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_text_title',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $args
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function widget($args, $instance): void {
|
||||
$title = trim((string) ($instance['title'] ?? ''));
|
||||
$desc = trim((string) ($instance['desc'] ?? ''));
|
||||
if ($title === '' && $desc === '') {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('请填写标题或描述', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<p class="widget-empty">' . esc_html__('请填写标题或描述', 'aksrt') . '</p>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$align = sanitize_key((string) ($instance['align'] ?? 'center'));
|
||||
if (!in_array($align, ['left', 'center', 'right'], true)) {
|
||||
$align = 'center';
|
||||
}
|
||||
$icon_left = sanitize_text_field((string) ($instance['title_icon_left'] ?? ''));
|
||||
$icon_right = sanitize_text_field((string) ($instance['title_icon_right'] ?? ''));
|
||||
$title_size = max(12, min(48, (int) ($instance['title_size'] ?? 22)));
|
||||
$desc_size = max(11, min(24, (int) ($instance['desc_size'] ?? 14)));
|
||||
$title_color = sanitize_hex_color((string) ($instance['title_color'] ?? '')) ?: '';
|
||||
$desc_color = sanitize_hex_color((string) ($instance['desc_color'] ?? '')) ?: '';
|
||||
$anim = sanitize_key((string) ($instance['obs_animation'] ?? ''));
|
||||
$anim_repeat = !empty($instance['animation_repeat']);
|
||||
$icon_width = max(0, min(64, (int) ($instance['title_icon_width'] ?? 0)));
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
|
||||
$style = sprintf(
|
||||
'--aksrt-tt-title-size:%dpx;--aksrt-tt-desc-size:%dpx;%s%s',
|
||||
$title_size,
|
||||
$desc_size,
|
||||
$title_color !== '' ? '--aksrt-tt-title-color:' . $title_color . ';' : '',
|
||||
$desc_color !== '' ? '--aksrt-tt-desc-color:' . $desc_color . ';' : ''
|
||||
);
|
||||
|
||||
$icon_w_attr = $icon_width > 0 ? ' style="--icon-width:' . (int) $icon_width . 'px;"' : '';
|
||||
$left_html = $icon_left !== ''
|
||||
? '<span class="text-title-icon aksrt-text-title__icon mr6"' . $icon_w_attr . '><i class="' . esc_attr($icon_left) . '" aria-hidden="true"></i></span>'
|
||||
: '';
|
||||
$right_html = $icon_right !== ''
|
||||
? '<span class="text-title-icon aksrt-text-title__icon ml6"' . $icon_w_attr . '><i class="' . esc_attr($icon_right) . '" aria-hidden="true"></i></span>'
|
||||
: '';
|
||||
|
||||
$title_html = '';
|
||||
if ($title !== '' && $anim !== '' && function_exists('aksrt_obs_animate_attrs')) {
|
||||
// Split title into per-char spans with staggered delay (zibll).
|
||||
$chars = preg_split('//u', $title, -1, PREG_SPLIT_NO_EMPTY) ?: [];
|
||||
$pieces = [];
|
||||
if ($left_html !== '') {
|
||||
$pieces[] = $left_html;
|
||||
}
|
||||
foreach ($chars as $ch) {
|
||||
$pieces[] = $ch;
|
||||
}
|
||||
if ($right_html !== '') {
|
||||
$pieces[] = $right_html;
|
||||
}
|
||||
foreach ($pieces as $i => $piece) {
|
||||
$pack = aksrt_obs_animate_attrs($anim, $anim_repeat, $i * 0.05);
|
||||
$is_html = str_starts_with((string) $piece, '<');
|
||||
$inner = $is_html ? $piece : esc_html((string) $piece);
|
||||
$title_html .= '<span class="' . esc_attr(trim($pack['class'])) . '"' . $pack['attr']
|
||||
. ' style="--delay:' . esc_attr((string) ($i * 0.05)) . 's;">' . $inner . '</span>';
|
||||
}
|
||||
} elseif ($title !== '') {
|
||||
$title_html = $left_html . esc_html($title) . $right_html;
|
||||
}
|
||||
|
||||
$desc_html = '';
|
||||
if ($desc !== '') {
|
||||
if ($anim !== '' && function_exists('aksrt_obs_animate_attrs')) {
|
||||
$pack = aksrt_obs_animate_attrs($anim, $anim_repeat, 0.6);
|
||||
$desc_html = '<div class="aksrt-text-title__desc' . esc_attr($pack['class']) . '"' . $pack['attr']
|
||||
. ' style="--delay:.6s;">' . esc_html($desc) . '</div>';
|
||||
} else {
|
||||
$desc_html = '<div class="aksrt-text-title__desc">' . esc_html($desc) . '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
$affix = function_exists('aksrt_widget_affix_attrs') ? aksrt_widget_affix_attrs($instance) : ['class' => '', 'attr' => ''];
|
||||
$box = 'aksrt-text-title text-title align-' . esc_attr($align) . $affix['class'];
|
||||
echo '<div class="' . esc_attr($box) . '" style="' . esc_attr($style) . '"' . $affix['attr'] . '>';
|
||||
if ($title_html !== '') {
|
||||
echo '<div class="aksrt-text-title__main">' . $title_html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
echo $desc_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '</div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function form($instance): void {
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('副标题 / 描述', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="2" name="<?php echo esc_attr($this->get_field_name('desc')); ?>"><?php echo esc_textarea((string) ($instance['desc'] ?? '')); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('对齐', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('align')); ?>">
|
||||
<option value="center" <?php selected(($instance['align'] ?? 'center'), 'center'); ?>><?php esc_html_e('居中', 'aksrt'); ?></option>
|
||||
<option value="left" <?php selected(($instance['align'] ?? ''), 'left'); ?>><?php esc_html_e('左', 'aksrt'); ?></option>
|
||||
<option value="right" <?php selected(($instance['align'] ?? ''), 'right'); ?>><?php esc_html_e('右', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('左图标 class', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title_icon_left')); ?>" value="<?php echo esc_attr((string) ($instance['title_icon_left'] ?? '')); ?>" placeholder="fas fa-star">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('右图标 class', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title_icon_right')); ?>" value="<?php echo esc_attr((string) ($instance['title_icon_right'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题字号', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="12" max="48" name="<?php echo esc_attr($this->get_field_name('title_size')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['title_size'] ?? 22))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('描述字号', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="11" max="24" name="<?php echo esc_attr($this->get_field_name('desc_size')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['desc_size'] ?? 14))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('入场动画', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('obs_animation')); ?>">
|
||||
<option value="" <?php selected(($instance['obs_animation'] ?? ''), ''); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
|
||||
<option value="fade" <?php selected(($instance['obs_animation'] ?? ''), 'fade'); ?>><?php esc_html_e('淡入', 'aksrt'); ?></option>
|
||||
<option value="slideup" <?php selected(($instance['obs_animation'] ?? ''), 'slideup'); ?>><?php esc_html_e('上滑', 'aksrt'); ?></option>
|
||||
<option value="zoomin" <?php selected(($instance['obs_animation'] ?? ''), 'zoomin'); ?>><?php esc_html_e('放大', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题色 #hex', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title_color')); ?>" value="<?php echo esc_attr((string) ($instance['title_color'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('描述色 #hex', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('desc_color')); ?>" value="<?php echo esc_attr((string) ($instance['desc_color'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<?php $this->render_common_form_section($instance); ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $new_instance
|
||||
* @param array<string,mixed> $old_instance
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return $this->update_common_fields($new_instance, $old_instance, [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'desc' => sanitize_text_field((string) ($new_instance['desc'] ?? '')),
|
||||
'align' => in_array(($new_instance['align'] ?? ''), ['left', 'center', 'right'], true) ? $new_instance['align'] : 'center',
|
||||
'title_icon_left' => sanitize_text_field((string) ($new_instance['title_icon_left'] ?? '')),
|
||||
'title_icon_right' => sanitize_text_field((string) ($new_instance['title_icon_right'] ?? '')),
|
||||
'title_size' => max(12, min(48, (int) ($new_instance['title_size'] ?? 22))),
|
||||
'desc_size' => max(11, min(24, (int) ($new_instance['desc_size'] ?? 14))),
|
||||
'title_color' => sanitize_hex_color((string) ($new_instance['title_color'] ?? '')) ?: '',
|
||||
'desc_color' => sanitize_hex_color((string) ($new_instance['desc_color'] ?? '')) ?: '',
|
||||
'obs_animation' => sanitize_key((string) ($new_instance['obs_animation'] ?? '')),
|
||||
'animation_repeat' => !empty($new_instance['animation_repeat']) ? 1 : 0,
|
||||
'title_icon_width' => max(0, min(64, (int) ($new_instance['title_icon_width'] ?? 0))),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
/**
|
||||
* Tilt cases / showcase intro (zibll zib_widget_ui_tilt_cases lite, no JS tilt).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetTiltCases extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_tilt_cases',
|
||||
esc_html__('AKSRT 案例展示', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('左文案 + 右侧叠层图片案例展示。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_tilt_cases',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$title = trim((string) ($instance['intro_title'] ?? ''));
|
||||
$subtitle = trim((string) ($instance['intro_subtitle'] ?? ''));
|
||||
$badge = trim((string) ($instance['intro_badge_text'] ?? ''));
|
||||
$images = $this->normalize_images($instance['intro_showcase_images'] ?? []);
|
||||
$features = $this->normalize_features($instance['intro_features'] ?? []);
|
||||
$buttons = $this->normalize_buttons($instance['intro_buttons'] ?? []);
|
||||
|
||||
if ($title === '' && $subtitle === '' && !$images) {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('请配置标题或案例图片', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<p class="widget-empty">' . esc_html__('请配置标题或案例图片', 'aksrt') . '</p>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
|
||||
echo '<div class="aksrt-tilt">';
|
||||
$pack = function_exists('aksrt_obs_animate_attrs') ? aksrt_obs_animate_attrs('slideup', false, 0) : ['class' => '', 'attr' => ''];
|
||||
echo '<div class="aksrt-tilt__text' . esc_attr($pack['class']) . '"' . $pack['attr'] . '>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
if ($badge !== '') {
|
||||
$icon = sanitize_text_field((string) ($instance['intro_badge_icon'] ?? ''));
|
||||
echo '<div class="aksrt-tilt__badge">';
|
||||
if ($icon !== '') {
|
||||
echo '<i class="' . esc_attr($icon) . '" aria-hidden="true"></i> ';
|
||||
}
|
||||
echo esc_html($badge) . '</div>';
|
||||
}
|
||||
if ($title !== '') {
|
||||
$highlight = trim((string) ($instance['intro_title_highlight'] ?? ''));
|
||||
$title_html = esc_html($title);
|
||||
if ($highlight !== '') {
|
||||
$h = esc_html($highlight);
|
||||
$title_html = str_replace($h, '<em class="aksrt-tilt__hl">' . $h . '</em>', $title_html);
|
||||
}
|
||||
echo '<h2 class="aksrt-tilt__title">' . $title_html . '</h2>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
if ($subtitle !== '') {
|
||||
echo '<p class="aksrt-tilt__subtitle">' . esc_html($subtitle) . '</p>';
|
||||
}
|
||||
if ($features) {
|
||||
echo '<ul class="aksrt-tilt__features">';
|
||||
foreach ($features as $f) {
|
||||
echo '<li>';
|
||||
if ($f['icon'] !== '') {
|
||||
echo '<i class="' . esc_attr($f['icon']) . '" aria-hidden="true"></i> ';
|
||||
}
|
||||
echo esc_html($f['text']) . '</li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
}
|
||||
if ($buttons) {
|
||||
echo '<div class="aksrt-tilt__actions">';
|
||||
foreach ($buttons as $b) {
|
||||
$url = $b['url'];
|
||||
if ($url !== '' && !empty($instance['go_link']) && function_exists('aksrt_go_link')) {
|
||||
$url = aksrt_go_link($url, true);
|
||||
}
|
||||
echo '<a class="aksrt-tilt__btn" href="' . esc_url($url !== '' ? $url : '#') . '">' . esc_html($b['text']) . '</a>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo '<div class="aksrt-tilt__showcase" data-aksrt-tilt data-tilt-max="12" data-tilt-glare="1">';
|
||||
if ($images) {
|
||||
foreach ($images as $i => $img) {
|
||||
$cls = $i === 0 ? ' is-main' : '';
|
||||
$placeholder = get_template_directory_uri() . '/assets/img/placeholder.svg';
|
||||
echo '<div class="aksrt-tilt__img' . esc_attr($cls) . '"><img class="aksrt-lazy lazyload" data-src="' . esc_url($img) . '" src="' . esc_url($placeholder) . '" alt="" loading="lazy" /></div>';
|
||||
}
|
||||
} else {
|
||||
echo '<div class="aksrt-tilt__img is-main is-empty"></div>';
|
||||
}
|
||||
echo '</div></div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/** @param mixed $raw @return array<int,string> */
|
||||
private function normalize_images($raw): array {
|
||||
if (is_string($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
if (is_array($decoded)) {
|
||||
$raw = $decoded;
|
||||
} else {
|
||||
$parts = preg_split('/\r\n|\r|\n|,/', $raw) ?: [];
|
||||
$raw = $parts;
|
||||
}
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($raw as $row) {
|
||||
if (is_array($row)) {
|
||||
$url = esc_url_raw(trim((string) ($row['image'] ?? $row['url'] ?? '')));
|
||||
} else {
|
||||
$url = esc_url_raw(trim((string) $row));
|
||||
}
|
||||
if ($url !== '') {
|
||||
$out[] = $url;
|
||||
}
|
||||
if (count($out) >= 5) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** @param mixed $raw @return array<int, array{text:string,icon:string}> */
|
||||
private function normalize_features($raw): array {
|
||||
if (is_string($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
$raw = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($raw as $row) {
|
||||
if (is_string($row)) {
|
||||
$text = sanitize_text_field($row);
|
||||
$icon = '';
|
||||
} elseif (is_array($row)) {
|
||||
$text = sanitize_text_field((string) ($row['text'] ?? ''));
|
||||
$icon = sanitize_text_field((string) ($row['icon'] ?? ''));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
if ($text === '') {
|
||||
continue;
|
||||
}
|
||||
$out[] = ['text' => $text, 'icon' => $icon];
|
||||
if (count($out) >= 8) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** @param mixed $raw @return array<int, array{text:string,url:string}> */
|
||||
private function normalize_buttons($raw): array {
|
||||
if (is_string($raw)) {
|
||||
$decoded = json_decode($raw, true);
|
||||
$raw = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach ($raw as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$text = sanitize_text_field((string) ($row['text'] ?? $row['btn']['text'] ?? ''));
|
||||
$url = esc_url_raw(trim((string) ($row['url'] ?? $row['btn']['url'] ?? '')));
|
||||
if ($text === '') {
|
||||
continue;
|
||||
}
|
||||
$out[] = ['text' => $text, 'url' => $url];
|
||||
if (count($out) >= 4) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$images = is_string($instance['intro_showcase_images'] ?? null)
|
||||
? (string) $instance['intro_showcase_images']
|
||||
: wp_json_encode($this->normalize_images($instance['intro_showcase_images'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
$features = is_string($instance['intro_features'] ?? null)
|
||||
? (string) $instance['intro_features']
|
||||
: wp_json_encode($this->normalize_features($instance['intro_features'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
$buttons = is_string($instance['intro_buttons'] ?? null)
|
||||
? (string) $instance['intro_buttons']
|
||||
: wp_json_encode($this->normalize_buttons($instance['intro_buttons'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('角标文案', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('intro_badge_text')); ?>" value="<?php echo esc_attr((string) ($instance['intro_badge_text'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('角标图标 class', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('intro_badge_icon')); ?>" value="<?php echo esc_attr((string) ($instance['intro_badge_icon'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="2" name="<?php echo esc_attr($this->get_field_name('intro_title')); ?>"><?php echo esc_textarea((string) ($instance['intro_title'] ?? '')); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题高亮词', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('intro_title_highlight')); ?>" value="<?php echo esc_attr((string) ($instance['intro_title_highlight'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('副标题', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="2" name="<?php echo esc_attr($this->get_field_name('intro_subtitle')); ?>"><?php echo esc_textarea((string) ($instance['intro_subtitle'] ?? '')); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('特性 JSON', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="3" name="<?php echo esc_attr($this->get_field_name('intro_features')); ?>"><?php echo esc_textarea((string) $features); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('按钮 JSON', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="3" name="<?php echo esc_attr($this->get_field_name('intro_buttons')); ?>"><?php echo esc_textarea((string) $buttons); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('展示图 JSON/URL 列表', 'aksrt'); ?>
|
||||
<textarea class="widefat" rows="4" name="<?php echo esc_attr($this->get_field_name('intro_showcase_images')); ?>"><?php echo esc_textarea((string) $images); ?></textarea>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('go_link')); ?>" value="1" <?php checked(!empty($instance['go_link'])); ?>> <?php esc_html_e('外链跳转页', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$business = [
|
||||
'intro_badge_text' => sanitize_text_field((string) ($new_instance['intro_badge_text'] ?? '')),
|
||||
'intro_badge_icon' => sanitize_text_field((string) ($new_instance['intro_badge_icon'] ?? '')),
|
||||
'intro_title' => sanitize_textarea_field((string) ($new_instance['intro_title'] ?? '')),
|
||||
'intro_title_highlight' => sanitize_text_field((string) ($new_instance['intro_title_highlight'] ?? '')),
|
||||
'intro_subtitle' => sanitize_textarea_field((string) ($new_instance['intro_subtitle'] ?? '')),
|
||||
'intro_features' => $this->normalize_features($new_instance['intro_features'] ?? []),
|
||||
'intro_buttons' => $this->normalize_buttons($new_instance['intro_buttons'] ?? []),
|
||||
'intro_showcase_images' => $this->normalize_images($new_instance['intro_showcase_images'] ?? []),
|
||||
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,383 @@
|
||||
<?php
|
||||
/**
|
||||
* Current-user / guest identity card (zibll widget_ui_user parity).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetUserCard extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_user_card',
|
||||
__('用户卡片', 'aksrt'),
|
||||
[
|
||||
'description' => __('未登录显示登录入口;登录后显示当前用户卡片、签到、快捷按钮与更多文章。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_user_card',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$show_cover = !isset($instance['show_img_bg']) || !empty($instance['show_img_bg']);
|
||||
$show_stats = !empty($instance['show_stats']) || !isset($instance['show_stats']);
|
||||
$show_actions = !empty($instance['show_button']) || !isset($instance['show_button']);
|
||||
$show_checkin = !empty($instance['show_checkin']) || !isset($instance['show_checkin']);
|
||||
// zibll user widget forces show_posts=false; author widget enables it.
|
||||
// AKSRT keeps optional show_posts for current-user card (default off).
|
||||
$show_posts = !empty($instance['show_posts']);
|
||||
$affix = function_exists('aksrt_widget_affix_attrs') ? aksrt_widget_affix_attrs($instance) : ['class' => '', 'attr' => ''];
|
||||
$loged_title = (string) ($instance['loged_title'] ?? __('Hi!请登录', 'aksrt'));
|
||||
$btn1_text = (string) ($instance['button_1_text'] ?? __('发布文章', 'aksrt'));
|
||||
$btn2_text = (string) ($instance['button_2_text'] ?? __('用户中心', 'aksrt'));
|
||||
// Zibll button color palettes (button_1_class / button_2_class).
|
||||
$btn1_class = sanitize_html_class((string) ($instance['button_1_class'] ?? 'jb-pink')) ?: 'jb-pink';
|
||||
$btn2_class = sanitize_html_class((string) ($instance['button_2_class'] ?? 'jb-blue')) ?: 'jb-blue';
|
||||
// Zibll button types: button_1 = 'post' (compose); button_2 ∈ {home,center}.
|
||||
$btn2_type = (string) ($instance['button_2'] ?? 'center');
|
||||
$post_style = sanitize_key((string) ($instance['post_style'] ?? 'mini'));
|
||||
if (!in_array($post_style, ['card', 'mini'], true)) {
|
||||
$post_style = 'mini';
|
||||
}
|
||||
$limit = max(0, min(12, (int) ($instance['limit'] ?? 6)));
|
||||
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
|
||||
|
||||
// Fixed user id wins; else current user.
|
||||
$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();
|
||||
}
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
if ($affix['class'] !== '') {
|
||||
echo '<div class="is-affix"' . $affix['attr'] . '>';
|
||||
}
|
||||
|
||||
if ($user_id <= 0) {
|
||||
$this->render_guest($show_cover, $loged_title);
|
||||
if ($affix['class'] !== '') {
|
||||
echo '</div>';
|
||||
}
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
return;
|
||||
}
|
||||
|
||||
$user = get_userdata($user_id);
|
||||
if (!$user) {
|
||||
if ($affix['class'] !== '') {
|
||||
echo '</div>';
|
||||
}
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
return;
|
||||
}
|
||||
|
||||
$is_self = is_user_logged_in() && (int) get_current_user_id() === $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);
|
||||
$center_url = function_exists('aksrt_get_user_center_url')
|
||||
? aksrt_get_user_center_url()
|
||||
: $profile_url;
|
||||
// Zibll button_2 type: home → profile, center → user center.
|
||||
$btn2_url = $btn2_type === 'home' ? $profile_url : $center_url;
|
||||
$compose_url = home_url('/');
|
||||
if (function_exists('get_pages')) {
|
||||
$pages = get_pages(['meta_key' => '_wp_page_template', 'meta_value' => 'templates/pages/page-submit.php', 'number' => 1]);
|
||||
if (!empty($pages[0])) {
|
||||
$compose_url = get_permalink($pages[0]);
|
||||
}
|
||||
}
|
||||
if ($compose_url === home_url('/') && function_exists('aksrt_get_user_center_url')) {
|
||||
$compose_url = aksrt_get_user_center_url('posts');
|
||||
}
|
||||
$bio = get_user_meta($user_id, 'description', true);
|
||||
if (!is_string($bio) || $bio === '') {
|
||||
$bio = (string) $user->description;
|
||||
}
|
||||
$cover = (string) get_user_meta($user_id, 'aksrt_cover', true);
|
||||
if ($cover === '') {
|
||||
$cover = (string) get_user_meta($user_id, 'custom_avatar_cover', true);
|
||||
}
|
||||
|
||||
$post_count = count_user_posts($user_id, 'post', 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);
|
||||
}
|
||||
|
||||
$name_html = function_exists('aksrt_get_user_name')
|
||||
? aksrt_get_user_name(['id' => $user_id, 'follow' => !$is_self, 'class' => 'flex ac jc'])
|
||||
: '<a href="' . esc_url($profile_url) . '">' . esc_html($user->display_name) . '</a>';
|
||||
|
||||
echo '<div class="aksrt-user-card user-card">';
|
||||
if ($show_cover) {
|
||||
if ($cover !== '') {
|
||||
echo '<div class="aksrt-user-card__cover user-cover graphic" style="background-image:url(' . esc_url($cover) . ')"></div>';
|
||||
} else {
|
||||
// Prefer site default cover image when available.
|
||||
$default_cover = '';
|
||||
if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_user_cover_default')) {
|
||||
$default_cover = (string) \AKSRT\Settings\Settings::get_user_cover_default();
|
||||
}
|
||||
if ($default_cover === '' && function_exists('get_theme_mod')) {
|
||||
$default_cover = (string) get_theme_mod('aksrt_user_cover', '');
|
||||
}
|
||||
if ($default_cover !== '') {
|
||||
echo '<div class="aksrt-user-card__cover user-cover graphic" style="background-image:url(' . esc_url($default_cover) . ')"></div>';
|
||||
} else {
|
||||
echo '<div class="aksrt-user-card__cover user-cover graphic is-empty"></div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
echo '<div class="aksrt-user-card__body card-content">';
|
||||
if ($show_checkin && $is_self && function_exists('aksrt_header_user_checkin_btn')) {
|
||||
$btn = aksrt_header_user_checkin_btn($user_id);
|
||||
if ($btn !== '') {
|
||||
echo '<div class="aksrt-user-card__checkin">' . $btn . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
}
|
||||
echo '<div class="aksrt-user-card__avatar user-avatar">';
|
||||
$ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
|
||||
echo '<a href="' . esc_url($profile_url) . '"><img class="aksrt-lazy lazyload" data-src="' . esc_url($avatar_url) . '" src="' . esc_url($ph) . '" alt="' . esc_attr($user->display_name) . '" loading="lazy" width="72" height="72" /></a>';
|
||||
echo '</div>';
|
||||
echo '<h4 class="aksrt-user-card__name user-name">' . $name_html . '</h4>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
if ($bio !== '') {
|
||||
echo '<p class="aksrt-user-card__bio user-desc">' . esc_html(wp_trim_words($bio, 24)) . '</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">' . esc_html(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">' . esc_html(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">' . esc_html(number_format_i18n($following_count)) . '</span><span class="aksrt-user-card__stat-label">' . esc_html__('关注', 'aksrt') . '</span></div>';
|
||||
echo '</div>';
|
||||
}
|
||||
if ($show_actions && $is_self) {
|
||||
echo '<div class="aksrt-user-card__actions user-btns">';
|
||||
echo '<a class="aksrt-user-card__btn aksrt-user-card__btn--primary but ' . esc_attr($btn1_class) . '" href="' . esc_url($compose_url) . '">' . esc_html($btn1_text) . '</a>';
|
||||
echo '<a class="aksrt-user-card__btn aksrt-user-card__btn--ghost but ' . esc_attr($btn2_class) . '" href="' . esc_url($btn2_url) . '">' . esc_html($btn2_text) . '</a>';
|
||||
echo '</div>';
|
||||
} elseif ($show_actions) {
|
||||
echo '<div class="aksrt-user-card__actions"><a class="aksrt-user-card__btn aksrt-user-card__btn--primary" href="' . esc_url($profile_url) . '">' . esc_html__('查看主页', 'aksrt') . '</a></div>';
|
||||
}
|
||||
|
||||
if ($show_posts && $limit > 0 && function_exists('aksrt_render_user_card_posts')) {
|
||||
echo aksrt_render_user_card_posts([ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
'user_id' => $user_id,
|
||||
'limit' => $limit,
|
||||
'orderby' => $orderby,
|
||||
'post_style' => $post_style,
|
||||
'post_type' => 'post',
|
||||
'show_empty' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
echo '</div></div>';
|
||||
|
||||
if ($affix['class'] !== '') {
|
||||
echo '</div>';
|
||||
}
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
private function render_guest(bool $show_cover, string $loged_title): void {
|
||||
$login = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url();
|
||||
$register = function_exists('aksrt_get_register_url') ? aksrt_get_register_url() : wp_registration_url();
|
||||
$default_avatar = function_exists('get_avatar_url')
|
||||
? get_avatar_url(0, ['size' => 144, 'force_default' => true])
|
||||
: includes_url('images/blank.gif');
|
||||
|
||||
$oauth_html = '';
|
||||
if (class_exists('\AKSRT\Auth\OAuth')) {
|
||||
$oauth = \AKSRT\Auth\OAuth::get_instance();
|
||||
if (method_exists($oauth, 'render_social_buttons')) {
|
||||
$oauth_html = (string) $oauth->render_social_buttons(__('社交账号登录', 'aksrt'));
|
||||
} elseif (method_exists($oauth, 'get_login_buttons')) {
|
||||
$oauth_html = (string) $oauth->get_login_buttons();
|
||||
}
|
||||
}
|
||||
|
||||
echo '<div class="aksrt-user-card user-card is-guest">';
|
||||
if ($show_cover) {
|
||||
$default_cover = '';
|
||||
if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_user_cover_default')) {
|
||||
$default_cover = (string) \AKSRT\Settings\Settings::get_user_cover_default();
|
||||
}
|
||||
if ($default_cover !== '') {
|
||||
echo '<div class="aksrt-user-card__cover user-cover graphic" style="background-image:url(' . esc_url($default_cover) . ')"></div>';
|
||||
} else {
|
||||
echo '<div class="aksrt-user-card__cover user-cover graphic is-empty"></div>';
|
||||
}
|
||||
}
|
||||
echo '<div class="aksrt-user-card__body card-content">';
|
||||
echo '<div class="aksrt-user-card__avatar user-avatar"><img src="' . esc_url($default_avatar) . '" alt="" width="72" height="72" loading="lazy" /></div>';
|
||||
echo '<div class="aksrt-user-card__guest-title">' . esc_html($loged_title) . '</div>';
|
||||
echo '<div class="aksrt-user-card__actions">';
|
||||
echo '<a class="aksrt-user-card__btn aksrt-user-card__btn--primary signin-loader" href="' . esc_url($login) . '">' . esc_html__('登录', 'aksrt') . '</a>';
|
||||
if ($register) {
|
||||
echo '<a class="aksrt-user-card__btn aksrt-user-card__btn--ghost signup-loader" href="' . esc_url($register) . '">' . esc_html__('注册', 'aksrt') . '</a>';
|
||||
}
|
||||
echo '</div>';
|
||||
if ($oauth_html !== '') {
|
||||
echo '<div class="aksrt-user-card__oauth social_loginbar">' . $oauth_html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
echo '</div></div>';
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$post_style = sanitize_key((string) ($instance['post_style'] ?? 'mini'));
|
||||
if (!in_array($post_style, ['card', 'mini'], true)) {
|
||||
$post_style = 'mini';
|
||||
}
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题(可选)', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('固定用户 ID(0=当前用户)', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('user_id')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['user_id'] ?? 0))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('未登录文案', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('loged_title')); ?>" value="<?php echo esc_attr((string) ($instance['loged_title'] ?? __('Hi!请登录', 'aksrt'))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('按钮1文案', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('button_1_text')); ?>" value="<?php echo esc_attr((string) ($instance['button_1_text'] ?? __('发布文章', 'aksrt'))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('按钮1颜色', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('button_1_class')); ?>">
|
||||
<?php foreach (self::palette_opts() as $k => $label) : ?>
|
||||
<option value="<?php echo esc_attr($k); ?>" <?php selected(($instance['button_1_class'] ?? 'jb-pink'), $k); ?>><?php echo esc_html($label); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('按钮2类型', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('button_2')); ?>">
|
||||
<option value="home" <?php selected(($instance['button_2'] ?? 'center'), 'home'); ?>><?php esc_html_e('个人主页', 'aksrt'); ?></option>
|
||||
<option value="center" <?php selected(($instance['button_2'] ?? 'center'), 'center'); ?>><?php esc_html_e('用户中心', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
<label><?php esc_html_e('按钮2文案', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('button_2_text')); ?>" value="<?php echo esc_attr((string) ($instance['button_2_text'] ?? __('用户中心', 'aksrt'))); ?>">
|
||||
</label>
|
||||
<label><?php esc_html_e('按钮2颜色', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('button_2_class')); ?>">
|
||||
<?php foreach (self::palette_opts() as $k => $label) : ?>
|
||||
<option value="<?php echo esc_attr($k); ?>" <?php selected(($instance['button_2_class'] ?? 'jb-blue'), $k); ?>><?php echo esc_html($label); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_img_bg')); ?>" value="1" <?php checked(!isset($instance['show_img_bg']) || !empty($instance['show_img_bg'])); ?>> <?php esc_html_e('显示封面', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_stats')); ?>" value="1" <?php checked(!isset($instance['show_stats']) || !empty($instance['show_stats'])); ?>> <?php esc_html_e('显示统计', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_button')); ?>" value="1" <?php checked(!isset($instance['show_button']) || !empty($instance['show_button'])); ?>> <?php esc_html_e('显示操作按钮', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_checkin')); ?>" value="1" <?php checked(!isset($instance['show_checkin']) || !empty($instance['show_checkin'])); ?>> <?php esc_html_e('显示签到(本人)', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_posts')); ?>" value="1" <?php checked(!empty($instance['show_posts'])); ?>> <?php esc_html_e('显示更多文章', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('文章数量', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="0" max="12" name="<?php echo esc_attr($this->get_field_name('limit')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['limit'] ?? 6))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('文章样式', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('post_style')); ?>">
|
||||
<option value="mini" <?php selected($post_style, 'mini'); ?>><?php esc_html_e('迷你列表', 'aksrt'); ?></option>
|
||||
<option value="card" <?php selected($post_style, 'card'); ?>><?php esc_html_e('卡片横滑', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('排序', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
|
||||
<option value="date" <?php selected(($instance['orderby'] ?? 'date'), 'date'); ?>><?php esc_html_e('最新', 'aksrt'); ?></option>
|
||||
<option value="views" <?php selected(($instance['orderby'] ?? ''), 'views'); ?>><?php esc_html_e('阅读量', 'aksrt'); ?></option>
|
||||
<option value="comment_count" <?php selected(($instance['orderby'] ?? ''), 'comment_count'); ?>><?php esc_html_e('评论数', 'aksrt'); ?></option>
|
||||
<option value="rand" <?php selected(($instance['orderby'] ?? ''), 'rand'); ?>><?php esc_html_e('随机', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<?php $this->render_common_form_section($instance); ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$post_style = sanitize_key((string) ($new_instance['post_style'] ?? 'mini'));
|
||||
if (!in_array($post_style, ['card', 'mini'], true)) {
|
||||
$post_style = 'mini';
|
||||
}
|
||||
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'date'));
|
||||
if (!in_array($orderby, ['date', 'views', 'comment_count', 'modified', 'rand'], true)) {
|
||||
$orderby = 'date';
|
||||
}
|
||||
return $this->update_common_fields($new_instance, $old_instance, [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'user_id' => max(0, (int) ($new_instance['user_id'] ?? 0)),
|
||||
'loged_title' => sanitize_text_field((string) ($new_instance['loged_title'] ?? '')),
|
||||
'button_1' => 'post',
|
||||
'button_1_text' => sanitize_text_field((string) ($new_instance['button_1_text'] ?? '')),
|
||||
'button_1_class'=> self::sanitize_palette((string) ($new_instance['button_1_class'] ?? 'jb-pink'), 'jb-pink'),
|
||||
'button_2' => in_array((string) ($new_instance['button_2'] ?? 'center'), ['home', 'center'], true) ? $new_instance['button_2'] : 'center',
|
||||
'button_2_text' => sanitize_text_field((string) ($new_instance['button_2_text'] ?? '')),
|
||||
'button_2_class'=> self::sanitize_palette((string) ($new_instance['button_2_class'] ?? 'jb-blue'), 'jb-blue'),
|
||||
'show_img_bg' => !empty($new_instance['show_img_bg']) ? 1 : 0,
|
||||
'show_stats' => !empty($new_instance['show_stats']) ? 1 : 0,
|
||||
'show_button' => !empty($new_instance['show_button']) ? 1 : 0,
|
||||
'show_checkin' => !empty($new_instance['show_checkin']) ? 1 : 0,
|
||||
'show_posts' => !empty($new_instance['show_posts']) ? 1 : 0,
|
||||
'limit' => max(0, min(12, (int) ($new_instance['limit'] ?? 6))),
|
||||
'post_style' => $post_style,
|
||||
'orderby' => $orderby,
|
||||
]);
|
||||
}
|
||||
|
||||
/** Zibll CFS_Module::zib_palette() gradient/solid keys (c/b/jb families). */
|
||||
public static function palette_opts(): array {
|
||||
return [
|
||||
'jb-red' => __('红', 'aksrt'),
|
||||
'jb-yellow' => __('橙', 'aksrt'),
|
||||
'jb-pink' => __('粉', 'aksrt'),
|
||||
'jb-cyan' => __('青', 'aksrt'),
|
||||
'jb-blue' => __('蓝', 'aksrt'),
|
||||
'jb-green' => __('绿', 'aksrt'),
|
||||
'jb-purple' => __('紫', 'aksrt'),
|
||||
'jb-vip1' => __('会员金', 'aksrt'),
|
||||
'jb-vip2' => __('会员灰', 'aksrt'),
|
||||
'b-red' => __('红(纯色)', 'aksrt'),
|
||||
'b-yellow' => __('橙(纯色)', 'aksrt'),
|
||||
'b-cyan' => __('青(纯色)', 'aksrt'),
|
||||
'b-blue' => __('蓝(纯色)', 'aksrt'),
|
||||
'b-green' => __('绿(纯色)', 'aksrt'),
|
||||
'b-purple' => __('紫(纯色)', 'aksrt'),
|
||||
'b-black' => __('黑(纯色)', 'aksrt'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function sanitize_palette(string $value, string $fallback): string {
|
||||
$value = sanitize_html_class($value);
|
||||
return array_key_exists($value, self::palette_opts()) ? $value : $fallback;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
/**
|
||||
* Registered user list (zibll widget_ui_user_lists → zib_author_card).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetUserLists extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_user_lists',
|
||||
esc_html__('AKSRT 用户列表', 'aksrt'),
|
||||
[
|
||||
'description' => esc_html__('展示站点用户迷你名片(头像/身份/关注/简介)。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_user_lists',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/** @return array<int,int> */
|
||||
public static function parse_ids_static(string $raw): array {
|
||||
if ($raw === '') {
|
||||
return [];
|
||||
}
|
||||
$ids = [];
|
||||
foreach (preg_split('/[\s,]+/', $raw) ?: [] as $p) {
|
||||
$id = (int) $p;
|
||||
if ($id > 0) {
|
||||
$ids[] = $id;
|
||||
}
|
||||
}
|
||||
return array_values(array_unique($ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public static function render_html(array $instance): string {
|
||||
$number = max(1, min(50, (int) ($instance['number'] ?? 8)));
|
||||
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'user_registered'));
|
||||
$order = strtoupper((string) ($instance['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC';
|
||||
$include = self::parse_ids_static((string) ($instance['include'] ?? ''));
|
||||
$exclude = self::parse_ids_static((string) ($instance['exclude'] ?? ''));
|
||||
$hide_box = !empty($instance['hide_box']);
|
||||
$blur_bg = !empty($instance['blur_bg']);
|
||||
|
||||
$query = [
|
||||
'number' => $number,
|
||||
'order' => $order,
|
||||
'fields' => ['ID', 'display_name', 'user_nicename'],
|
||||
];
|
||||
if ($include) {
|
||||
$query['include'] = $include;
|
||||
$query['orderby'] = 'include';
|
||||
} else {
|
||||
switch ($orderby) {
|
||||
case 'display_name':
|
||||
$query['orderby'] = 'display_name';
|
||||
break;
|
||||
case 'post_count':
|
||||
$query['orderby'] = 'post_count';
|
||||
break;
|
||||
case 'last_login':
|
||||
$query['meta_key'] = 'aksrt_last_login';
|
||||
$query['orderby'] = 'meta_value';
|
||||
break;
|
||||
case 'followed-user-count':
|
||||
$query['meta_key'] = 'aksrt_follower_count';
|
||||
$query['orderby'] = 'meta_value_num';
|
||||
break;
|
||||
default:
|
||||
$query['orderby'] = 'user_registered';
|
||||
}
|
||||
}
|
||||
if ($exclude) {
|
||||
$query['exclude'] = $exclude;
|
||||
}
|
||||
|
||||
$users = get_users($query);
|
||||
if (!$users) {
|
||||
return function_exists('aksrt_get_ajax_null')
|
||||
? aksrt_get_ajax_null(__('暂无用户', 'aksrt'))
|
||||
: '<p class="widget-empty ajax-item">' . esc_html__('暂无用户', 'aksrt') . '</p>';
|
||||
}
|
||||
|
||||
if ($orderby === 'followed-user-count' && !$include && class_exists('\AKSRT\User\Follow')) {
|
||||
$has_meta = false;
|
||||
foreach ($users as $u) {
|
||||
if ((int) get_user_meta($u->ID, 'aksrt_follower_count', true) > 0) {
|
||||
$has_meta = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$has_meta) {
|
||||
usort($users, static function ($a, $b) {
|
||||
$fa = \AKSRT\User\Follow::get_instance()->get_follower_count((int) $a->ID);
|
||||
$fb = \AKSRT\User\Follow::get_instance()->get_follower_count((int) $b->ID);
|
||||
return $fb <=> $fa;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ob_start();
|
||||
// zibll: optional zib-widget + text-center user_lists of author-minicard.
|
||||
$outer = $hide_box ? 'aksrt-user-lists is-bare ajax-item' : 'aksrt-user-lists zib-widget ajax-item';
|
||||
echo '<div class="' . esc_attr($outer) . '">';
|
||||
echo '<div class="text-center user_lists">';
|
||||
foreach ($users as $user) {
|
||||
$uid = (int) $user->ID;
|
||||
if (function_exists('aksrt_author_minicard')) {
|
||||
echo aksrt_author_minicard($uid, 'mb10', $blur_bg); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
} else {
|
||||
$avatar = function_exists('aksrt_get_avatar_url')
|
||||
? aksrt_get_avatar_url($uid, ['size' => 96])
|
||||
: get_avatar_url($uid, ['size' => 96]);
|
||||
$home = function_exists('aksrt_get_user_home_url')
|
||||
? aksrt_get_user_home_url($uid)
|
||||
: get_author_posts_url($uid);
|
||||
$name = $user->display_name ?: $user->user_nicename;
|
||||
echo '<a class="author-minicard radius8" href="' . esc_url($home) . '">';
|
||||
echo '<img class="avatar" src="' . esc_url($avatar) . '" alt="" loading="lazy" width="42" height="42" /> ';
|
||||
echo esc_html($name);
|
||||
echo '</a>';
|
||||
}
|
||||
}
|
||||
echo '</div></div>';
|
||||
return (string) ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $args
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function widget($args, $instance): void {
|
||||
$html = self::render_html($instance);
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
|
||||
if ($title !== '') {
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function form($instance): void {
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('显示数量', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="1" max="50" name="<?php echo esc_attr($this->get_field_name('number')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['number'] ?? 8))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('包含用户 ID(逗号)', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('include')); ?>" value="<?php echo esc_attr((string) ($instance['include'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('排除用户 ID(逗号)', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('exclude')); ?>" value="<?php echo esc_attr((string) ($instance['exclude'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('排序', 'aksrt'); ?>
|
||||
<select class="widefat" name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
|
||||
<option value="user_registered" <?php selected(($instance['orderby'] ?? 'user_registered'), 'user_registered'); ?>><?php esc_html_e('注册时间', 'aksrt'); ?></option>
|
||||
<option value="display_name" <?php selected(($instance['orderby'] ?? ''), 'display_name'); ?>><?php esc_html_e('昵称', 'aksrt'); ?></option>
|
||||
<option value="post_count" <?php selected(($instance['orderby'] ?? ''), 'post_count'); ?>><?php esc_html_e('文章数', 'aksrt'); ?></option>
|
||||
<option value="last_login" <?php selected(($instance['orderby'] ?? ''), 'last_login'); ?>><?php esc_html_e('最后登录', 'aksrt'); ?></option>
|
||||
<option value="followed-user-count" <?php selected(($instance['orderby'] ?? ''), 'followed-user-count'); ?>><?php esc_html_e('粉丝数', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('顺序', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('order')); ?>">
|
||||
<option value="DESC" <?php selected(($instance['order'] ?? 'DESC'), 'DESC'); ?>><?php esc_html_e('降序', 'aksrt'); ?></option>
|
||||
<option value="ASC" <?php selected(($instance['order'] ?? ''), 'ASC'); ?>><?php esc_html_e('升序', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hide_box')); ?>" value="1" <?php checked(!empty($instance['hide_box'])); ?>> <?php esc_html_e('不显示背景盒子', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('blur_bg')); ?>" value="1" <?php checked(!empty($instance['blur_bg'])); ?>> <?php esc_html_e('头像模糊背景', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $new_instance
|
||||
* @param array<string,mixed> $old_instance
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'user_registered'));
|
||||
$allowed = ['user_registered', 'display_name', 'post_count', 'last_login', 'followed-user-count'];
|
||||
if (!in_array($orderby, $allowed, true)) {
|
||||
$orderby = 'user_registered';
|
||||
}
|
||||
$business = [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'number' => max(1, min(50, (int) ($new_instance['number'] ?? 8))),
|
||||
'include' => sanitize_text_field((string) ($new_instance['include'] ?? '')),
|
||||
'exclude' => sanitize_text_field((string) ($new_instance['exclude'] ?? '')),
|
||||
'orderby' => $orderby,
|
||||
'order' => strtoupper((string) ($new_instance['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC',
|
||||
'hide_box' => !empty($new_instance['hide_box']) ? 1 : 0,
|
||||
'blur_bg' => !empty($new_instance['blur_bg']) ? 1 : 0,
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
<?php
|
||||
/**
|
||||
* User ranking lists (zibll zib_user_ranking_lists parity).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetUserRanking extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_user_ranking',
|
||||
__('用户排行榜', 'aksrt'),
|
||||
[
|
||||
'description' => __('按积分、文章数或粉丝数排行的用户列表。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_user_ranking',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$title = !empty($instance['title']) ? (string) $instance['title'] : __('排行榜', 'aksrt');
|
||||
// Zibll zib_widget_ui_user_ranking: `orderby` (default checkin_all_day) + `number`.
|
||||
// Accept AKSRT legacy `rank_by`/`count` as aliases.
|
||||
$rank_by = (string) ($instance['orderby'] ?? $instance['rank_by'] ?? 'checkin_all_day');
|
||||
// Map Zibll `points` → AKSRT `credits`; `followed-user-count` → `followers`.
|
||||
$rank_aliases = [
|
||||
'points' => 'credits',
|
||||
'followed-user-count' => 'followers',
|
||||
'checkin_all_day' => 'checkin_all_day',
|
||||
'checkin_continuous_day' => 'checkin_continuous_day',
|
||||
'credits' => 'credits',
|
||||
'posts' => 'posts',
|
||||
'followers' => 'followers',
|
||||
];
|
||||
$rank_by = $rank_aliases[$rank_by] ?? 'checkin_all_day';
|
||||
$count = max(1, min(20, (int) ($instance['number'] ?? $instance['count'] ?? 6)));
|
||||
$show_avatar = !isset($instance['show_avatar']) || !empty($instance['show_avatar']);
|
||||
$desc_mode = sanitize_key((string) ($instance['desc'] ?? 'desc'));
|
||||
$top_badge = !empty($instance['top_badge']);
|
||||
$affix = function_exists('aksrt_widget_affix_attrs') ? aksrt_widget_affix_attrs($instance) : ['class' => '', 'attr' => ''];
|
||||
$users = $this->get_ranking($rank_by, $count, $desc_mode);
|
||||
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
|
||||
$box_cls = 'zib-widget user-ranking-box aksrt-user-ranking-box' . $affix['class'];
|
||||
echo '<div class="' . esc_attr($box_cls) . '"' . $affix['attr'] . '>';
|
||||
if (!empty($instance['checkin_btn']) && is_user_logged_in() && function_exists('aksrt_header_user_checkin_btn')) {
|
||||
$cbtn = aksrt_header_user_checkin_btn(get_current_user_id());
|
||||
if ($cbtn !== '') {
|
||||
echo '<div class="aksrt-user-ranking__checkin mb10">' . $cbtn . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($users)) {
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('暂无排行数据', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<p class="aksrt-user-ranking__empty muted-2-color">' . esc_html__('暂无排行数据', 'aksrt') . '</p>';
|
||||
echo '</div>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
return;
|
||||
}
|
||||
|
||||
$ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
|
||||
$rank = 0;
|
||||
foreach ($users as $user) {
|
||||
$rank++;
|
||||
$uid = (int) $user['id'];
|
||||
$user_url = function_exists('aksrt_get_user_home_url')
|
||||
? aksrt_get_user_home_url($uid)
|
||||
: get_author_posts_url($uid);
|
||||
|
||||
$top_bagd = '';
|
||||
if ($top_badge && $rank <= 3) {
|
||||
$tone = $rank === 1 ? 'jb-red' : ($rank === 2 ? 'jb-yellow' : 'b-gray');
|
||||
$top_bagd = '<badge class="img-badge left hot ' . esc_attr($tone) . '"><i>TOP' . (int) $rank . '</i></badge>';
|
||||
}
|
||||
|
||||
$name_html = function_exists('aksrt_get_user_name')
|
||||
? aksrt_get_user_name([
|
||||
'id' => $uid,
|
||||
'class' => 'flex ac flex1',
|
||||
'follow' => false,
|
||||
'medal' => true,
|
||||
'level' => true,
|
||||
])
|
||||
: '<a class="font-bold" href="' . esc_url($user_url) . '">' . esc_html((string) $user['name']) . '</a>';
|
||||
|
||||
$avatar_html = '';
|
||||
if ($show_avatar) {
|
||||
$avatar_url = function_exists('aksrt_get_avatar_url')
|
||||
? aksrt_get_avatar_url($uid, ['size' => 42])
|
||||
: get_avatar_url($uid, ['size' => 42]);
|
||||
$avatar_html = '<a class="avatar-img" href="' . esc_url($user_url) . '"><img class="avatar aksrt-lazy lazyload" data-src="' . esc_url($avatar_url) . '" src="' . esc_url($ph) . '" alt="" loading="lazy" width="36" height="36" /></a>';
|
||||
}
|
||||
|
||||
$desc = (string) ($user['desc'] ?? '');
|
||||
$value_label = (string) ($user['value_label'] ?? $user['value'] ?? '');
|
||||
$value_icon = (string) ($user['value_icon'] ?? 'fa-trophy');
|
||||
$right_badge = '<span class="badg" title="' . esc_attr($value_label) . '"><i class="fa ' . esc_attr($value_icon) . '" aria-hidden="true"></i><span class="ml6">' . esc_html((string) ($user['value_num'] ?? $user['value'] ?? '')) . '</span></span>';
|
||||
|
||||
$mt = $rank > 1 ? ' mt20' : '';
|
||||
echo '<div class="user-ranking-item aksrt-user-ranking__item relative' . esc_attr($mt) . ($rank <= 3 ? ' is-top' : '') . '">';
|
||||
echo $top_bagd; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '<div class="user-info flex ac">';
|
||||
echo $avatar_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '<div class="user-right flex flex1 ac jsb ml10">';
|
||||
echo '<div class="flex1 min-w0">';
|
||||
echo '<div class="font-bold aksrt-user-ranking__name">' . $name_html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
if ($desc !== '') {
|
||||
echo '<div class="mt3 em09 muted-color text-ellipsis aksrt-user-ranking__desc">' . esc_html($desc) . '</div>';
|
||||
}
|
||||
echo '</div>';
|
||||
echo '<div class="ml20 flex0 aksrt-user-ranking__value">' . $right_badge . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '</div></div></div>';
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{id:int,name:string,value:string,value_num:string,value_label:string,value_icon:string,desc:string}>
|
||||
*/
|
||||
private function get_ranking(string $rank_by, int $count, string $desc_mode = 'desc'): 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) {
|
||||
$num = (int) $row['credits'];
|
||||
$users[] = [
|
||||
'id' => (int) $row['user_id'],
|
||||
'name' => $user->display_name,
|
||||
'value' => number_format_i18n($num) . ' 积分',
|
||||
'value_num' => (string) number_format_i18n($num),
|
||||
'value_label' => sprintf(__('积分:%s', 'aksrt'), number_format_i18n($num)),
|
||||
'value_icon' => 'fa-diamond',
|
||||
'desc' => $this->user_extra_desc((int) $row['user_id'], $user, $desc_mode),
|
||||
];
|
||||
}
|
||||
}
|
||||
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) . ' 篇',
|
||||
'value_num' => (string) number_format_i18n($post_count),
|
||||
'value_label' => sprintf(__('共%d篇文章', 'aksrt'), $post_count),
|
||||
'value_icon' => 'fa-file-text-o',
|
||||
'desc' => $this->user_extra_desc($user->ID, $user, $desc_mode),
|
||||
];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'checkin_all_day':
|
||||
$users = $this->meta_rank('aksrt_checkin_all_day', ['checkin_all_day'], $count, $desc_mode, 'fa-calendar-check-o', __('累计签到%d天', 'aksrt'), ' 天');
|
||||
break;
|
||||
|
||||
case 'checkin_continuous_day':
|
||||
$users = $this->meta_rank('aksrt_checkin_continuous_day', ['checkin_continuous_day'], $count, $desc_mode, 'fa-calendar-check-o', __('连续签到%d天', 'aksrt'), ' 天');
|
||||
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) {
|
||||
$num = (int) $row['cnt'];
|
||||
$users[] = [
|
||||
'id' => (int) $row['following_id'],
|
||||
'name' => $user->display_name,
|
||||
'value' => number_format_i18n($num) . ' 粉丝',
|
||||
'value_num' => (string) number_format_i18n($num),
|
||||
'value_label' => sprintf(__('共%d个粉丝', 'aksrt'), $num),
|
||||
'value_icon' => 'fa-heart',
|
||||
'desc' => $this->user_extra_desc((int) $row['following_id'], $user, $desc_mode),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $alt_keys
|
||||
* @return list<array{id:int,name:string,value:string,value_num:string,value_label:string,value_icon:string,desc:string}>
|
||||
*/
|
||||
private function meta_rank(string $primary_key, array $alt_keys, int $count, string $desc_mode, string $icon, string $label_tpl, string $suffix): array {
|
||||
global $wpdb;
|
||||
$keys = array_merge([$primary_key], $alt_keys);
|
||||
$in = implode(',', array_fill(0, count($keys), '%s'));
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
||||
$sql = $wpdb->prepare(
|
||||
"SELECT user_id, meta_value as val FROM {$wpdb->usermeta}
|
||||
WHERE meta_key IN ($in)
|
||||
ORDER BY CAST(meta_value AS UNSIGNED) DESC
|
||||
LIMIT %d",
|
||||
...array_merge($keys, [$count * 2])
|
||||
);
|
||||
$rows = $wpdb->get_results($sql, ARRAY_A);
|
||||
$seen = [];
|
||||
$users = [];
|
||||
foreach ((array) $rows as $row) {
|
||||
$uid = (int) $row['user_id'];
|
||||
if (isset($seen[$uid])) {
|
||||
continue;
|
||||
}
|
||||
$seen[$uid] = true;
|
||||
$user = get_userdata($uid);
|
||||
if (!$user) {
|
||||
continue;
|
||||
}
|
||||
$num = (int) $row['val'];
|
||||
$users[] = [
|
||||
'id' => $uid,
|
||||
'name' => $user->display_name,
|
||||
'value' => number_format_i18n($num) . $suffix,
|
||||
'value_num' => (string) number_format_i18n($num),
|
||||
'value_label' => sprintf($label_tpl, $num),
|
||||
'value_icon' => $icon,
|
||||
'desc' => $this->user_extra_desc($uid, $user, $desc_mode),
|
||||
];
|
||||
if (count($users) >= $count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $users;
|
||||
}
|
||||
|
||||
private function user_extra_desc(int $user_id, $user, string $mode): string {
|
||||
switch ($mode) {
|
||||
case 'user_registered':
|
||||
if ($user && !empty($user->user_registered)) {
|
||||
$days = max(0, (int) floor((time() - strtotime($user->user_registered)) / DAY_IN_SECONDS));
|
||||
return sprintf(__('加入 %d 天', 'aksrt'), $days);
|
||||
}
|
||||
return '';
|
||||
case 'last_login':
|
||||
$t = (string) get_user_meta($user_id, 'aksrt_last_login', true);
|
||||
if ($t === '') {
|
||||
$t = (string) get_user_meta($user_id, 'last_login', true);
|
||||
}
|
||||
return $t !== '' ? sprintf(__('最近登录 %s', 'aksrt'), $t) : '';
|
||||
case 'last_checkin':
|
||||
$t = (string) get_user_meta($user_id, 'aksrt_last_checkin', true);
|
||||
if ($t === '') {
|
||||
$t = (string) get_user_meta($user_id, 'last_checkin', true);
|
||||
}
|
||||
return $t !== '' ? sprintf(__('最近签到 %s', 'aksrt'), $t) : '';
|
||||
case 'desc':
|
||||
default:
|
||||
$bio = get_user_meta($user_id, 'description', true);
|
||||
if (!is_string($bio) || $bio === '') {
|
||||
$bio = $user ? (string) $user->description : '';
|
||||
}
|
||||
return $bio !== '' ? wp_trim_words($bio, 12) : '';
|
||||
}
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
$title = !empty($instance['title']) ? $instance['title'] : __('排行榜', 'aksrt');
|
||||
// Zibll field is `orderby` (default checkin_all_day); accept legacy `rank_by`.
|
||||
$rank_by = (string) ($instance['orderby'] ?? $instance['rank_by'] ?? 'checkin_all_day');
|
||||
$count = !empty($instance['number']) ? (int) $instance['number'] : (!empty($instance['count']) ? (int) $instance['count'] : 6);
|
||||
$show_avatar = !isset($instance['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('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="checkin_all_day" <?php selected($rank_by, 'checkin_all_day'); ?>><?php esc_html_e('累计签到天数', 'aksrt'); ?></option>
|
||||
<option value="checkin_continuous_day" <?php selected($rank_by, 'checkin_continuous_day'); ?>><?php esc_html_e('连续签到天数', 'aksrt'); ?></option>
|
||||
<option value="points" <?php selected($rank_by, 'points'); ?>><?php esc_html_e('用户积分', 'aksrt'); ?></option>
|
||||
<option value="followed-user-count" <?php selected($rank_by, 'followed-user-count'); ?>><?php esc_html_e('粉丝数量', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('number')); ?>"><?php esc_html_e('最大显示数量:', 'aksrt'); ?></label>
|
||||
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('number')); ?>" name="<?php echo esc_attr($this->get_field_name('number')); ?>" 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>
|
||||
<p>
|
||||
<label for="<?php echo esc_attr($this->get_field_id('desc')); ?>"><?php esc_html_e('附加显示:', 'aksrt'); ?></label>
|
||||
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('desc')); ?>" name="<?php echo esc_attr($this->get_field_name('desc')); ?>">
|
||||
<option value="desc" <?php selected(($instance['desc'] ?? 'desc'), 'desc'); ?>><?php esc_html_e('用户签名', 'aksrt'); ?></option>
|
||||
<option value="user_registered" <?php selected(($instance['desc'] ?? ''), 'user_registered'); ?>><?php esc_html_e('加入本站多少天', 'aksrt'); ?></option>
|
||||
<option value="last_login" <?php selected(($instance['desc'] ?? ''), 'last_login'); ?>><?php esc_html_e('最后登录时间', 'aksrt'); ?></option>
|
||||
<option value="last_checkin" <?php selected(($instance['desc'] ?? ''), 'last_checkin'); ?>><?php esc_html_e('最后签到时间', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
<input type="checkbox" name="<?php echo esc_attr($this->get_field_name('top_badge')); ?>" value="1" <?php checked(!empty($instance['top_badge'])); ?>>
|
||||
<?php esc_html_e('显示排行徽章', 'aksrt'); ?>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>
|
||||
<input type="checkbox" name="<?php echo esc_attr($this->get_field_name('checkin_btn')); ?>" value="1" <?php checked(!empty($instance['checkin_btn'])); ?>>
|
||||
<?php esc_html_e('显示签到按钮', 'aksrt'); ?>
|
||||
</label>
|
||||
</p>
|
||||
<?php $this->render_common_form_section($instance); ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$rank_by = (string) ($new_instance['orderby'] ?? $new_instance['rank_by'] ?? 'checkin_all_day');
|
||||
$allowed = ['checkin_all_day', 'checkin_continuous_day', 'points', 'followed-user-count', 'credits', 'posts', 'followers'];
|
||||
if (!in_array($rank_by, $allowed, true)) {
|
||||
$rank_by = 'checkin_all_day';
|
||||
}
|
||||
return $this->update_common_fields($new_instance, $old_instance, [
|
||||
'title' => sanitize_text_field($new_instance['title'] ?? ''),
|
||||
'orderby' => $rank_by,
|
||||
'rank_by' => $rank_by, // keep alias for any legacy readers.
|
||||
'number' => max(1, min(20, (int) ($new_instance['number'] ?? $new_instance['count'] ?? 6))),
|
||||
'count' => max(1, min(20, (int) ($new_instance['number'] ?? $new_instance['count'] ?? 6))),
|
||||
'show_avatar' => !empty($new_instance['show_avatar']) ? 1 : 0,
|
||||
'checkin_btn' => !empty($new_instance['checkin_btn']) ? 1 : 0,
|
||||
'top_badge' => !empty($new_instance['top_badge']) ? 1 : 0,
|
||||
'desc' => in_array($new_instance['desc'] ?? '', ['desc', 'user_registered', 'last_login', 'last_checkin'], true) ? $new_instance['desc'] : 'desc',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
/**
|
||||
* Video player widget (zibll dplayer lite via data-aksrt-video + embed).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetVideoPlayer extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
'aksrt_video_player',
|
||||
__('视频播放器', 'aksrt'),
|
||||
[
|
||||
'description' => __('侧栏/流体区嵌入视频(共享 aksrt-video 播放器)。', 'aksrt'),
|
||||
'classname' => 'widget_aksrt_video_player',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function widget($args, $instance): void {
|
||||
$title = (string) ($instance['title'] ?? '');
|
||||
$video_url = trim((string) ($instance['video_url'] ?? $instance['url'] ?? ''));
|
||||
$poster = trim((string) ($instance['poster'] ?? $instance['pic'] ?? ''));
|
||||
$autoplay = !empty($instance['autoplay']);
|
||||
$loop = !empty($instance['loop']);
|
||||
$muted = $autoplay || !empty($instance['muted']);
|
||||
$hide_controller = !empty($instance['hide_controller']);
|
||||
$scale = max(20, min(100, (int) ($instance['scale_height'] ?? 56)));
|
||||
$aspect = (string) ($instance['aspect_ratio'] ?? '');
|
||||
|
||||
if ($video_url === '') {
|
||||
if (current_user_can('manage_options')) {
|
||||
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo function_exists('aksrt_get_widget_null')
|
||||
? aksrt_get_widget_null(__('请配置视频 URL', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
: '<div class="c-red muted-box mb20"><b>' . esc_html__('视频播放器:', 'aksrt') . '</b>' . esc_html__('请配置视频 URL 或移出该模块', 'aksrt') . '</div>';
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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 ($aspect !== '' && str_contains($aspect, ':')) {
|
||||
$parts = explode(':', $aspect);
|
||||
if (count($parts) === 2 && (int) $parts[0] > 0) {
|
||||
$scale = (int) round(((int) $parts[1] / (int) $parts[0]) * 100);
|
||||
}
|
||||
}
|
||||
|
||||
$embed = $this->get_embed_url($video_url);
|
||||
$cls = 'aksrt-video-widget relative-h radius8' . ($hide_controller ? ' controller-hide' : '');
|
||||
echo '<div class="' . esc_attr($cls) . '" style="padding-bottom:' . esc_attr((string) $scale) . '%">';
|
||||
|
||||
if ($embed !== '') {
|
||||
$src = $embed;
|
||||
if ($autoplay) {
|
||||
$src = add_query_arg(['autoplay' => '1', 'mute' => '1'], $src);
|
||||
}
|
||||
echo '<iframe class="aksrt-video-widget__frame" src="' . esc_url($src) . '" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen loading="lazy"></iframe>';
|
||||
} else {
|
||||
// Shared player stack (assets/js/video-player.js).
|
||||
$type = 'mp4';
|
||||
if (preg_match('/\.m3u8(\?|$)/i', $video_url)) {
|
||||
$type = 'hls';
|
||||
} elseif (preg_match('/\.flv(\?|$)/i', $video_url)) {
|
||||
$type = 'flv';
|
||||
}
|
||||
printf(
|
||||
'<div class="aksrt-video aksrt-video-widget__player" data-aksrt-video data-src="%s" data-type="%s"%s%s%s%s></div>',
|
||||
esc_url($video_url),
|
||||
esc_attr($type),
|
||||
$poster !== '' ? ' data-poster="' . esc_url($poster) . '"' : '',
|
||||
$autoplay ? ' data-autoplay="true"' : '',
|
||||
$loop ? ' data-loop="true"' : '',
|
||||
$muted ? ' data-muted="true"' : ''
|
||||
);
|
||||
}
|
||||
echo '</div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
private function get_embed_url(string $url): string {
|
||||
if (preg_match('#(?:youtube\.com/watch\?v=|youtu\.be/)([A-Za-z0-9_-]+)#', $url, $m)) {
|
||||
return 'https://www.youtube.com/embed/' . rawurlencode($m[1]);
|
||||
}
|
||||
if (preg_match('#bilibili\.com/video/(BV[\w]+)#', $url, $m)) {
|
||||
return 'https://player.bilibili.com/player.html?bvid=' . rawurlencode($m[1]) . '&high_quality=1';
|
||||
}
|
||||
if (preg_match('#v\.qq\.com/.+?/([A-Za-z0-9]+)\.html#', $url, $m)) {
|
||||
return 'https://v.qq.com/txp/iframe/player.html?vid=' . rawurlencode($m[1]);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
public function form($instance): void {
|
||||
?>
|
||||
<p>
|
||||
<label><?php esc_html_e('标题', 'aksrt'); ?>
|
||||
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('视频 URL(mp4/m3u8/flv 或 YouTube/B站/腾讯)', 'aksrt'); ?>
|
||||
<input class="widefat" type="url" name="<?php echo esc_attr($this->get_field_name('video_url')); ?>" value="<?php echo esc_attr((string) ($instance['video_url'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('封面图 URL', 'aksrt'); ?>
|
||||
<input class="widefat" type="url" name="<?php echo esc_attr($this->get_field_name('poster')); ?>" value="<?php echo esc_attr((string) ($instance['poster'] ?? '')); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><?php esc_html_e('高度占比 %', 'aksrt'); ?>
|
||||
<input class="tiny-text" type="number" min="20" max="100" name="<?php echo esc_attr($this->get_field_name('scale_height')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['scale_height'] ?? 56))); ?>">
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('autoplay')); ?>" value="1" <?php checked(!empty($instance['autoplay'])); ?>> <?php esc_html_e('自动播放', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('loop')); ?>" value="1" <?php checked(!empty($instance['loop'])); ?>> <?php esc_html_e('循环', 'aksrt'); ?></label><br>
|
||||
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hide_controller')); ?>" value="1" <?php checked(!empty($instance['hide_controller'])); ?>> <?php esc_html_e('隐藏控制条样式', 'aksrt'); ?></label>
|
||||
</p>
|
||||
<?php
|
||||
$this->render_common_form_section($instance);
|
||||
}
|
||||
|
||||
public function update($new_instance, $old_instance): array {
|
||||
$business = [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'video_url' => esc_url_raw(trim((string) ($new_instance['video_url'] ?? ''))),
|
||||
'poster' => esc_url_raw(trim((string) ($new_instance['poster'] ?? ''))),
|
||||
'scale_height' => max(0, min(200, (int) ($new_instance['scale_height'] ?? 56))),
|
||||
'aspect_ratio' => sanitize_text_field((string) ($new_instance['aspect_ratio'] ?? '')),
|
||||
'autoplay' => !empty($new_instance['autoplay']) ? 1 : 0,
|
||||
'loop' => !empty($new_instance['loop']) ? 1 : 0,
|
||||
'muted' => !empty($new_instance['muted']) ? 1 : 0,
|
||||
'hide_controller' => !empty($new_instance['hide_controller']) ? 1 : 0,
|
||||
]; return $this->update_common_fields($new_instance, $old_instance, $business);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
/**
|
||||
* Yiyan / Hitokoto widget (zibll widget_ui_yiyan shell + aksrt_render_yiyan_block).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Widgets;
|
||||
|
||||
use AKSRT\Widgets\Traits\CommonWidgetFields;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class WidgetYiyan extends \WP_Widget {
|
||||
use CommonWidgetFields;
|
||||
|
||||
/**
|
||||
* @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'),
|
||||
];
|
||||
}
|
||||
|
||||
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'),
|
||||
'classname' => 'widget_aksrt_yiyan yiyan-box',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $args
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function widget($args, $instance): void {
|
||||
$title = !empty($instance['title']) ? (string) $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']) ? (string) $instance['icon'] : 'fas fa-quote-left';
|
||||
$affix = function_exists('aksrt_widget_affix_attrs') ? aksrt_widget_affix_attrs($instance) : ['class' => '', 'attr' => ''];
|
||||
$anim = sanitize_key((string) ($instance['obs_animation'] ?? ''));
|
||||
$anim_repeat = !empty($instance['animation_repeat']);
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// zibll shell: theme-box + yiyan-box main-bg text-center box-body radius8 main-shadow.
|
||||
// NOTE: obs_animation is this widget's own per-widget animation business
|
||||
// field (kept as-is); the shared common `animation_in` field added via
|
||||
// render_common_form_section() is a separate additional control.
|
||||
$pack = function_exists('aksrt_obs_animate_attrs')
|
||||
? aksrt_obs_animate_attrs($anim, $anim_repeat, 0)
|
||||
: ['class' => '', 'attr' => ''];
|
||||
$box_class = 'theme-box yiyan-box-wrap' . $pack['class'];
|
||||
|
||||
echo '<div' . $affix['attr'] . ' class="' . esc_attr(trim($box_class)) . '"' . $pack['attr'] . '>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '<div class="yiyan-box main-bg text-center box-body radius8 main-shadow">';
|
||||
|
||||
if (function_exists('aksrt_render_yiyan_block')) {
|
||||
aksrt_render_yiyan_block([
|
||||
'class' => 'aksrt-yiyan yiyan',
|
||||
'type' => $type,
|
||||
'count' => $count,
|
||||
'interval' => $interval,
|
||||
'icon' => $icon,
|
||||
]);
|
||||
} else {
|
||||
?>
|
||||
<div class="aksrt-yiyan 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 '</div></div>';
|
||||
|
||||
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $instance
|
||||
*/
|
||||
public function form($instance): void {
|
||||
$title = !empty($instance['title']) ? (string) $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']) ? (string) $instance['icon'] : 'fas fa-quote-left';
|
||||
$labels = self::type_labels();
|
||||
$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>
|
||||
<p>
|
||||
<label><?php esc_html_e('入场动画', 'aksrt'); ?>
|
||||
<select name="<?php echo esc_attr($this->get_field_name('obs_animation')); ?>">
|
||||
<option value="" <?php selected(($instance['obs_animation'] ?? ''), ''); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
|
||||
<option value="fade" <?php selected(($instance['obs_animation'] ?? ''), 'fade'); ?>><?php esc_html_e('淡入', 'aksrt'); ?></option>
|
||||
<option value="slideup" <?php selected(($instance['obs_animation'] ?? ''), 'slideup'); ?>><?php esc_html_e('上滑', 'aksrt'); ?></option>
|
||||
<option value="zoomin" <?php selected(($instance['obs_animation'] ?? ''), 'zoomin'); ?>><?php esc_html_e('放大', 'aksrt'); ?></option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
<?php $this->render_common_form_section($instance); ?>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $new_instance
|
||||
* @param array<string,mixed> $old_instance
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function update($new_instance, $old_instance): array {
|
||||
return $this->update_common_fields($new_instance, $old_instance, [
|
||||
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
|
||||
'count' => max(1, min(10, absint($new_instance['count'] ?? 5))),
|
||||
'interval' => max(3, min(30, absint($new_instance['interval'] ?? 5))),
|
||||
'type' => self::sanitize_type((string) ($new_instance['type'] ?? '')),
|
||||
'icon' => sanitize_text_field((string) ($new_instance['icon'] ?? 'fas fa-quote-left')),
|
||||
'obs_animation' => sanitize_key((string) ($new_instance['obs_animation'] ?? '')),
|
||||
'animation_repeat' => !empty($new_instance['animation_repeat']) ? 1 : 0,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user