diff --git a/assets/css/frontend/widget-batch5.css b/assets/css/frontend/widget-batch5.css index bfcfd826..95fc7429 100644 --- a/assets/css/frontend/widget-batch5.css +++ b/assets/css/frontend/widget-batch5.css @@ -377,3 +377,79 @@ .aksrt-cover-card__bg.is-video video { width: 100%; height: 100%; object-fit: cover; display: block; } + +.widget-buttons { + display: flex; + flex-wrap: wrap; + gap: var(--btn-gap, 10px); + margin-bottom: 8px; +} +.widget-buttons--vertical { flex-direction: column; } +.widget-buttons.align-center { justify-content: center; } +.widget-buttons.align-left { justify-content: flex-start; } +.widget-buttons.align-right { justify-content: flex-end; } +.widget-buttons__btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + padding: 8px 16px; + border-radius: 8px; + text-decoration: none; + font-size: 13px; + font-weight: 600; + background: var(--color-primary, #3b82f6); + color: #fff !important; +} +.widget-buttons.is-radius .widget-buttons__btn { border-radius: 999px; } +.widget-buttons.is-hollow .widget-buttons__btn { + background: transparent; + color: var(--color-primary, #3b82f6) !important; + border: 1px solid currentColor; +} +.widget-buttons__btn--secondary { background: #64748b; } +.widget-buttons__btn--outline { + background: transparent; + color: inherit !important; + border: 1px solid var(--color-line, #ddd); +} +.widget-buttons__btn--text { + background: transparent; + color: var(--color-primary, #3b82f6) !important; + padding-left: 0; + padding-right: 0; +} +.widget-buttons__btn--pink { background: #ec4899; } +.widget-buttons__btn--blue { background: #3b82f6; } +.widget-buttons__btn--green { background: #22c55e; } + +.aksrt-video-widget { + position: relative; + width: 100%; + height: 0; + border-radius: 8px; + overflow: hidden; + background: #111; + margin-bottom: 10px; +} +.aksrt-video-widget iframe, +.aksrt-video-widget video { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + border: 0; + object-fit: cover; +} +.aksrt-video-widget.controller-hide video::-webkit-media-controls { + display: none !important; +} + +.widget-recent-comments__post { + margin-top: 2px; + font-size: 11px; +} +.widget-recent-comments__post a { + color: var(--muted-2-color, #999); + text-decoration: none; +} diff --git a/src/AKSRT/Widgets/ButtonsWidget.php b/src/AKSRT/Widgets/ButtonsWidget.php index cadfde31..20eb79b2 100644 --- a/src/AKSRT/Widgets/ButtonsWidget.php +++ b/src/AKSRT/Widgets/ButtonsWidget.php @@ -1,5 +1,7 @@ esc_html__('显示一组自定义按钮', 'aksrt'), - ]); - } + public function __construct() { + parent::__construct('aksrt_buttons', esc_html__('AKSRT 按钮组', 'aksrt'), [ + 'description' => esc_html__('显示一组自定义按钮,支持外链跳转与样式。', 'aksrt'), + ]); + } - public function widget($args, $instance): void { - echo $args['before_widget']; - $title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base); - $buttons = isset($instance['buttons']) && is_array($instance['buttons']) ? $instance['buttons'] : []; - $layout = sanitize_key($instance['layout'] ?? 'vertical'); + public function widget($args, $instance): void { + $buttons = isset($instance['buttons']) && is_array($instance['buttons']) ? $instance['buttons'] : []; + $layout = sanitize_key((string) ($instance['layout'] ?? 'horizontal')); + if (!in_array($layout, ['vertical', 'horizontal'], true)) { + $layout = 'horizontal'; + } + $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']); + $gap = max(4, min(30, (int) ($instance['gap'] ?? 10))); - if ($title) echo $args['before_title'] . esc_html($title) . $args['after_title']; + $items = []; + foreach ($buttons as $btn) { + if (!is_array($btn)) { + continue; + } + $text = sanitize_text_field((string) ($btn['text'] ?? '')); + $url = esc_url_raw(trim((string) ($btn['url'] ?? ''))); + if ($text === '') { + continue; + } + if ($url !== '' && $go_link && function_exists('aksrt_go_link')) { + $url = aksrt_go_link($url, true); + } + $items[] = [ + 'text' => $text, + 'url' => $url, + 'style' => sanitize_key((string) ($btn['style'] ?? 'primary')), + 'icon' => sanitize_text_field((string) ($btn['icon'] ?? $btn['icon_left'] ?? '')), + 'icon_right' => sanitize_text_field((string) ($btn['icon_right'] ?? '')), + 'new_window' => !empty($btn['new_window']) || !empty($btn['target']), + ]; + } + if (!$items) { + return; + } - if (!empty($buttons)) : ?> -
- - - - - - -
- id_base); + if ($title !== '') { + echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } - echo $args['after_widget']; - } + $wrap = 'widget-buttons widget-buttons--' . $layout . ' align-' . $align; + if ($radius) { + $wrap .= ' is-radius'; + } + if ($hollow) { + $wrap .= ' is-hollow'; + } + echo '
'; + foreach ($items as $btn) { + $style = in_array($btn['style'], ['primary', 'secondary', 'outline', 'text', 'pink', 'blue', 'green', 'yellow', 'purple'], true) + ? $btn['style'] + : 'primary'; + $cls = 'widget-buttons__btn widget-buttons__btn--' . $style; + $href = $btn['url'] !== '' ? $btn['url'] : 'javascript:void(0)'; + $tgt = $btn['new_window'] && $btn['url'] !== '' ? ' target="_blank" rel="noopener noreferrer"' : ''; + echo ''; + if ($btn['icon'] !== '') { + echo ' '; + } + echo esc_html($btn['text']); + if ($btn['icon_right'] !== '') { + echo ' '; + } + echo ''; + } + echo '
'; - public function form($instance): void { - $buttons = isset($instance['buttons']) && is_array($instance['buttons']) ? $instance['buttons'] : []; - while (count($buttons) < 3) { - $buttons[] = ['text' => '', 'url' => '', 'style' => 'primary', 'icon' => '', 'new_window' => false]; - } - ?> -

- - -

-

- - -

-
- $btn) : ?> -
-

- " value="" placeholder=""> -

-

- " value="" placeholder=""> -

-

- -

-

- " value="" placeholder=""> -

-

- -

-
- sanitize_text_field($btn['text'] ?? ''), - 'url' => esc_url_raw($btn['url'] ?? ''), - 'style' => $style, - 'icon' => sanitize_text_field($btn['icon'] ?? ''), - 'new_window' => !empty($btn['new_window']), - ]; - } - } - return [ - 'title' => sanitize_text_field($new_instance['title'] ?? ''), - 'layout' => in_array($new_instance['layout'] ?? '', ['vertical', 'horizontal'], true) ? $new_instance['layout'] : 'vertical', - 'buttons' => $buttons, - ]; - } + 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]; + } + ?> +

+ +

+

+ + +

+

+ + + +

+ $btn) : ?> +
+ " value="" placeholder=""> + " value="" placeholder=""> + " value="" placeholder=""> + " value="" placeholder=""> + + +
+ $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; + } + } + } + return [ + 'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')), + 'layout' => ($new_instance['layout'] ?? '') === 'vertical' ? 'vertical' : 'horizontal', + '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'] ?? 10))), + 'buttons' => $buttons, + ]; + } } diff --git a/src/AKSRT/Widgets/RecentCommentsWidget.php b/src/AKSRT/Widgets/RecentCommentsWidget.php index f24de874..5820e1aa 100644 --- a/src/AKSRT/Widgets/RecentCommentsWidget.php +++ b/src/AKSRT/Widgets/RecentCommentsWidget.php @@ -1,5 +1,7 @@ esc_html__('显示最新评论列表', 'aksrt'), - ]); - } + public function __construct() { + parent::__construct('aksrt_recent_comments', esc_html__('AKSRT 最新评论', 'aksrt'), [ + 'description' => esc_html__('显示最新评论列表', 'aksrt'), + ]); + } - public function widget($args, $instance): void { - echo $args['before_widget']; - $title = apply_filters('widget_title', $instance['title'] ?? __('最新评论', 'aksrt'), $instance, $this->id_base); - $count = max(1, min(20, (int) ($instance['count'] ?? 5))); - $show_avatar = !empty($instance['show_avatar']); + 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); + $count = max(1, min(20, (int) ($instance['count'] ?? $instance['limit'] ?? 5))); + $show_avatar = !isset($instance['show_avatar']) || !empty($instance['show_avatar']); + $outpost = max(0, (int) ($instance['outpost'] ?? 0)); + $outer = sanitize_text_field((string) ($instance['outer'] ?? '')); - if ($title) echo $args['before_title'] . esc_html($title) . $args['after_title']; + if ($title !== '') { + echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } - $comments = get_comments([ - 'number' => $count, - 'status' => 'approve', - 'post_status' => 'publish', - ]); + $query = [ + 'number' => $count, + 'status' => 'approve', + 'post_status' => 'publish', + ]; + if ($outpost > 0) { + $query['post__not_in'] = [$outpost]; + } + if ($outer !== '') { + // Exclude comments containing outer keywords (comma separated). + // Implemented post-filter below. + } + $comments = get_comments($query); + if ($outer !== '' && $comments) { + $keys = array_filter(array_map('trim', preg_split('/[,,]/', $outer) ?: [])); + if ($keys) { + $comments = array_values(array_filter($comments, static function ($c) use ($keys) { + $body = (string) $c->comment_content; + foreach ($keys as $k) { + if ($k !== '' && str_contains($body, $k)) { + return false; + } + } + return true; + })); + } + } - if ($comments) : ?> -
- comment_post_ID); - if (!$post) continue; - ?> -
- -
- -
- -
-
- comment_author); ?> - comment_date), current_time('timestamp'))); ?> -
-

- comment_content, 15, '...')); ?> -

- - ID)); ?> - -
-
- -
- - ' . esc_html__('暂无评论', 'aksrt') . '

'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> - '; + foreach ($comments as $comment) { + $post = get_post($comment->comment_post_ID); + if (!$post) { + continue; + } + echo '
'; + if ($show_avatar) { + echo '
' . get_avatar($comment, 32) . '
'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } + echo '
'; + echo '
'; + echo '' . esc_html($comment->comment_author) . ''; + echo '' . esc_html(human_time_diff(strtotime($comment->comment_date), current_time('timestamp'))) . ''; + echo '
'; + echo '

' . esc_html(wp_trim_words($comment->comment_content, 15, '...')) . '

'; + echo '
' . esc_html(get_the_title($post)) . '
'; + echo '
'; + } + echo ''; + } else { + echo '

' . esc_html__('暂无评论', 'aksrt') . '

'; + } - echo $args['after_widget']; - } + echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } - public function form($instance): void { - ?> -

- - -

-

- - -

-

- -

- +

+ +

+

+ +

+

+ +

+

+ +

+

+ +

+ sanitize_text_field($new_instance['title'] ?? ''), - 'count' => max(1, min(20, (int) ($new_instance['count'] ?? 5))), - 'show_avatar' => !empty($new_instance['show_avatar']), - ]; - } + public function update($new_instance, $old_instance): array { + return [ + 'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')), + 'count' => max(1, min(20, (int) ($new_instance['count'] ?? 5))), + 'outpost' => max(0, (int) ($new_instance['outpost'] ?? 0)), + 'outer' => sanitize_text_field((string) ($new_instance['outer'] ?? '')), + 'show_avatar' => !empty($new_instance['show_avatar']) ? 1 : 0, + ]; + } } diff --git a/src/AKSRT/Widgets/WidgetVideoPlayer.php b/src/AKSRT/Widgets/WidgetVideoPlayer.php index 40888ed5..d20a2d0f 100644 --- a/src/AKSRT/Widgets/WidgetVideoPlayer.php +++ b/src/AKSRT/Widgets/WidgetVideoPlayer.php @@ -1,5 +1,7 @@ __('在侧栏嵌入视频播放器。', 'aksrt')] - ); - } + public function __construct() { + parent::__construct( + 'aksrt_video_player', + __('视频播放器', 'aksrt'), + ['description' => __('侧栏/流体区嵌入视频播放器。', 'aksrt')] + ); + } - public function widget($args, $instance): void { - $title = !empty($instance['title']) ? $instance['title'] : ''; - $video_url = !empty($instance['video_url']) ? $instance['video_url'] : ''; - $poster = !empty($instance['poster']) ? $instance['poster'] : ''; - $autoplay = !empty($instance['autoplay']); - $aspect_ratio = !empty($instance['aspect_ratio']) ? $instance['aspect_ratio'] : '16:9'; + 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 (empty($video_url)) return; + if ($video_url === '') { + return; + } - // 解析视频类型 - $embed_url = $this->get_embed_url($video_url); - $is_embed = !empty($embed_url); + 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 $args['before_widget']; + 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); + } + } - if ($title) { - echo $args['before_title'] . esc_html($title) . $args['after_title']; - } + $embed = $this->get_embed_url($video_url); + $cls = 'aksrt-video-widget' . ($hide_controller ? ' controller-hide' : ''); + echo '
'; + if ($embed !== '') { + $src = $embed; + if ($autoplay) { + $src = add_query_arg(['autoplay' => '1', 'mute' => '1'], $src); + } + echo ''; + } else { + $attrs = ' controls playsinline'; + if ($autoplay) { + $attrs .= ' autoplay'; + } + if ($loop) { + $attrs .= ' loop'; + } + if ($muted) { + $attrs .= ' muted'; + } + if ($poster !== '') { + $attrs .= ' poster="' . esc_url($poster) . '"'; + } + echo ''; + } + echo '
'; - // 计算 padding-bottom - $ratio_parts = explode(':', $aspect_ratio); - if (count($ratio_parts) === 2 && (int) $ratio_parts[1] > 0) { - $padding = ((int) $ratio_parts[1] / (int) $ratio_parts[0]) * 100; - } else { - $padding = 56.25; // 16:9 - } + echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } - echo '
'; + 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 ''; + } - if ($is_embed) { - echo ''; - } else { - echo ''; - echo ''; - echo ''; - } + public function form($instance): void { + ?> +

+ +

+

+ +

+

+ +

+

+ +

+

+
+
+ +

+ '; - - echo $args['after_widget']; - } - - /** - * 解析 YouTube / Bilibili 链接为嵌入 URL - */ - private function get_embed_url(string $url): string { - // YouTube - if (preg_match('/(?:youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]+)/', $url, $m)) { - return 'https://www.youtube.com/embed/' . $m[1]; - } - - // Bilibili - if (preg_match('/bilibili\.com\/video\/(BV[a-zA-Z0-9]+)/', $url, $m)) { - return 'https://player.bilibili.com/player.html?bvid=' . $m[1] . '&high_quality=1'; - } - - // 腾讯视频 - if (preg_match('/v\.qq\.com\/x\/page\/([a-zA-Z0-9]+)/', $url, $m)) { - return 'https://v.qq.com/txp/iframe/player.html?vid=' . $m[1]; - } - - return ''; - } - - public function form($instance): void { - $title = !empty($instance['title']) ? $instance['title'] : ''; - $video_url = !empty($instance['video_url']) ? $instance['video_url'] : ''; - $poster = !empty($instance['poster']) ? $instance['poster'] : ''; - $autoplay = !empty($instance['autoplay']); - $aspect_ratio = !empty($instance['aspect_ratio']) ? $instance['aspect_ratio'] : '16:9'; - ?> -

- - -

-

- - - -

-

- - -

-

- - -

-

- -

- 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(20, min(100, (int) ($new_instance['scale_height'] ?? 56))), + 'autoplay' => !empty($new_instance['autoplay']) ? 1 : 0, + 'loop' => !empty($new_instance['loop']) ? 1 : 0, + 'hide_controller' => !empty($new_instance['hide_controller']) ? 1 : 0, + ]; + } }