Files
wordpress-theme-aksrt/inc/helpers/aksrt-shortcodes.php
T
kate522 8d31e65ccd feat(admin,forum): download logs, hidecontent, VIP tiers, users commerce
Add aksrt_download_logs workbench, zibll hidecontent alias + topic
partial-hide path, VIP1/VIP2 price matrix for topics, and users list
commerce snapshot column with deep links.
2026-07-15 11:13:48 +08:00

339 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* AKSRT Shortcodes Registry — 短代码统一注册入口
*
* Phase 9 收口:所有主题短代码集中在此注册,不再散落到
* `AKSRT_Content_Visibility` / `AKSRT_Templates` 等业务类的 init() 中。
* 业务渲染仍由各自类/模板实现,registry 只负责「注册 → 委派」。
*
* 详见 shortcode-registry.md。
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
if (!class_exists('AKSRT_Shortcodes')) :
class AKSRT_Shortcodes {
private static $instance = null;
public static function get_instance(): self {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* 注册全部短代码。在 `init` 上调用一次。
*/
public function register(): void {
// 隐藏内容(评论/登录/密码可见)→ AKSRT_Content_Visibility
add_shortcode('aksrt_hide', [$this, 'render_hide']);
// §3.8 用户友好短代码别名
add_shortcode('hide', [$this, 'render_hide_comment']);
add_shortcode('reply', [$this, 'render_reply']);
add_shortcode('vip', [$this, 'render_vip']);
add_shortcode('payshow', [$this, 'render_payshow']);
// Zibll-compatible hidecontent alias (type=payshow|reply|login|vip|password|…)
add_shortcode('hidecontent', [$this, 'render_hidecontent']);
add_shortcode('content', [$this, 'render_content_gate']);
// 文章归档 / 个人资料卡 → AKSRT_Templates 模板
add_shortcode('aksrt_archive', [$this, 'render_archive']);
add_shortcode('aksrt_profile', [$this, 'render_profile']);
// 文章列表(统一渲染器)
add_shortcode('aksrt_posts', [$this, 'render_posts']);
}
/**
* [aksrt_hide mode="comment|login|password"]...[/aksrt_hide]
*/
public function render_hide(array $atts, string $content = ''): string {
if (!class_exists('AKSRT_Content_Visibility')) {
return '';
}
return AKSRT_Content_Visibility::get_instance()->render_hide_shortcode($atts, $content);
}
/**
* [hide]...[/hide] — 评论后可见(默认 mode=comment)
*/
public function render_hide_comment(array $atts = [], string $content = ''): string {
$atts = shortcode_atts(['message' => ''], $atts, 'hide');
return $this->render_hide(['mode' => 'comment', 'message' => $atts['message']], $content);
}
/**
* [reply]...[/reply] — 回复(评论)后可见
*/
public function render_reply(array $atts = [], string $content = ''): string {
$atts = shortcode_atts(['message' => ''], $atts, 'reply');
$msg = $atts['message'] ?: __('回复本文后可查看隐藏内容', 'aksrt');
return $this->render_hide(['mode' => 'comment', 'message' => $msg], $content);
}
/**
* [vip]...[/vip] — VIP 用户可见
*/
public function render_vip(array $atts = [], string $content = ''): string {
$atts = shortcode_atts(['message' => ''], $atts, 'vip');
$uid = get_current_user_id();
$is_vip = $uid > 0 && function_exists('aksrt_is_vip') && aksrt_is_vip($uid);
if ($is_vip) {
return '<div class="aksrt-hidden-content aksrt-hidden-content--unlocked aksrt-hidden-content--vip">'
. do_shortcode($content) . '</div>';
}
$message = $atts['message'] ?: __('此内容仅 VIP 用户可见', 'aksrt');
$html = '<div class="aksrt-hidden-content aksrt-hidden-content--locked aksrt-hidden-content--vip">';
$html .= '<div class="aksrt-hidden-content__message">';
$html .= '<i class="fas fa-crown" aria-hidden="true"></i> ';
$html .= '<span>' . esc_html($message) . '</span>';
$html .= '</div>';
if ($uid <= 0) {
$html .= '<a href="' . esc_url(wp_login_url(get_permalink())) . '" class="aksrt-hidden-content__login-btn btn-primary btn-sm">';
$html .= esc_html__('登录后查看', 'aksrt') . '</a>';
}
$html .= '</div>';
return $html;
}
/**
* [payshow]...[/payshow] — 付费后可见(文章 content-pay 或论坛主题解锁)
*/
public function render_payshow(array $atts = [], string $content = ''): string {
$atts = shortcode_atts(['message' => ''], $atts, 'payshow');
$pid = get_the_ID();
$can_view = $this->viewer_can_see_paid_block($pid);
if ($can_view) {
return '<div class="aksrt-hidden-content aksrt-hidden-content--unlocked aksrt-hidden-content--payshow">'
. do_shortcode($content) . '</div>';
}
$message = $atts['message'] ?: __('此内容需付费后查看', 'aksrt');
$uid = get_current_user_id();
$html = '<div class="aksrt-hidden-content aksrt-hidden-content--locked aksrt-hidden-content--payshow">';
$html .= '<div class="aksrt-hidden-content__message">';
$html .= '<i class="fas fa-lock" aria-hidden="true"></i> ';
$html .= '<span>' . esc_html($message) . '</span>';
$html .= '</div>';
if ($uid <= 0) {
$html .= '<a href="' . esc_url(wp_login_url(get_permalink())) . '" class="aksrt-hidden-content__login-btn btn-primary btn-sm">';
$html .= esc_html__('登录后购买查看', 'aksrt') . '</a>';
}
$html .= '</div>';
return $html;
}
/**
* Zibll [hidecontent type="payshow|reply|login|…"]…[/hidecontent]
*/
public function render_hidecontent(array $atts = [], string $content = ''): string {
$atts = shortcode_atts([
'type' => 'payshow',
'message' => '',
'pass' => '',
], $atts, 'hidecontent');
$type = sanitize_key((string) $atts['type']);
$msg = (string) $atts['message'];
switch ($type) {
case 'reply':
case 'comment':
return $this->render_reply(['message' => $msg], $content);
case 'login':
case 'signin':
return $this->render_hide([
'mode' => 'login',
'message' => $msg !== '' ? $msg : __('登录后可查看隐藏内容', 'aksrt'),
], $content);
case 'password':
case 'pass':
return $this->render_hide([
'mode' => 'password',
'message' => $msg,
'pass' => (string) $atts['pass'],
], $content);
case 'vip':
return $this->render_vip(['message' => $msg], $content);
case 'payshow':
case 'pay':
case 'points':
default:
return $this->render_payshow(['message' => $msg], $content);
}
}
/**
* Whether current viewer may see a paid hide block on this post/topic.
*/
private function viewer_can_see_paid_block(int $pid): bool {
if ($pid <= 0) {
return false;
}
$post = get_post($pid);
if (!$post instanceof WP_Post) {
return false;
}
$uid = get_current_user_id();
if ($uid > 0 && ((int) $post->post_author === $uid || user_can($uid, 'manage_options'))) {
return true;
}
// Forum topics: paid/credits unlock list or orders.
if ($post->post_type === 'aksrt_forum_topic' && class_exists('AKSRT_Forum')) {
$forum = AKSRT_Forum::get_instance();
$visibility = sanitize_key((string) get_post_meta($pid, '_aksrt_visibility', true));
if ($visibility === 'public' || $visibility === '') {
return true;
}
if (method_exists($forum, 'has_user_unlocked_topic') && $forum->has_user_unlocked_topic($uid, $pid)) {
return true;
}
if (method_exists($forum, 'has_user_paid_topic') && $forum->has_user_paid_topic($uid, $pid)) {
return true;
}
// Free VIP price counts as paid for cash topics.
if ($visibility === 'pay' && method_exists($forum, 'resolve_topic_pay_price')) {
$price = $forum->resolve_topic_pay_price($pid, $uid);
if (!empty($price['is_free'])) {
return true;
}
}
return false;
}
if (class_exists('AKSRT_Content_Visibility')) {
return AKSRT_Content_Visibility::can_view_paid_content($pid);
}
return false;
}
/**
* [content type="admin|author|vip"]...[/content] — 按角色/身份控制可见性
*/
public function render_content_gate(array $atts = [], string $content = ''): string {
$atts = shortcode_atts(['type' => 'admin', 'message' => ''], $atts, 'content');
$type = sanitize_key((string) $atts['type']);
$uid = get_current_user_id();
$pid = get_the_ID();
$allowed = false;
switch ($type) {
case 'admin':
$allowed = $uid > 0 && user_can($uid, 'manage_options');
break;
case 'author':
if ($uid > 0 && $pid) {
$post = get_post($pid);
$allowed = $post && (int) $post->post_author === $uid;
}
break;
case 'vip':
$allowed = $uid > 0 && function_exists('aksrt_is_vip') && aksrt_is_vip($uid);
break;
case 'login':
$allowed = $uid > 0;
break;
}
if ($allowed) {
return '<div class="aksrt-hidden-content aksrt-hidden-content--unlocked aksrt-hidden-content--role">'
. do_shortcode($content) . '</div>';
}
$default_msgs = [
'admin' => __('此内容仅管理员可见', 'aksrt'),
'author' => __('此内容仅作者可见', 'aksrt'),
'vip' => __('此内容仅 VIP 用户可见', 'aksrt'),
'login' => __('此内容仅登录用户可见', 'aksrt'),
];
$message = $atts['message'] ?: ($default_msgs[$type] ?? __('此内容已隐藏', 'aksrt'));
$html = '<div class="aksrt-hidden-content aksrt-hidden-content--locked aksrt-hidden-content--role">';
$html .= '<div class="aksrt-hidden-content__message">';
$html .= '<i class="fas fa-eye-slash" aria-hidden="true"></i> ';
$html .= '<span>' . esc_html($message) . '</span>';
$html .= '</div>';
$html .= '</div>';
return $html;
}
/**
* [aksrt_archive] 文章归档
*/
public function render_archive(array $atts = [], string $content = ''): string {
if (!class_exists('AKSRT_Templates')) {
return '';
}
return AKSRT_Templates::get_instance()->render_archive_shortcode($atts, $content);
}
/**
* [aksrt_profile] 个人资料卡
*/
public function render_profile(array $atts = [], string $content = ''): string {
if (!class_exists('AKSRT_Templates')) {
return '';
}
return AKSRT_Templates::get_instance()->render_profile_shortcode($atts, $content);
}
/**
* [aksrt_posts count="6" cat="" tag="" orderby="date" order="DESC" mode="card"]
*
* 用统一渲染器输出文章列表,避免内容里手写卡片循环。
*/
public function render_posts(array $atts = [], string $content = ''): string {
if (!function_exists('aksrt_render_post_list')) {
return '';
}
$a = shortcode_atts([
'count' => 6,
'cat' => '',
'tag' => '',
'orderby' => 'date',
'order' => 'DESC',
'mode' => 'card',
], $atts, 'aksrt_posts');
$query_args = [
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => max(1, min(24, (int) $a['count'])),
'orderby' => sanitize_key((string) $a['orderby']) ?: 'date',
'order' => strtoupper((string) $a['order']) === 'ASC' ? 'ASC' : 'DESC',
'ignore_sticky_posts' => true,
'no_found_rows' => true,
];
$cat = sanitize_text_field((string) $a['cat']);
if ($cat !== '') {
$query_args[is_numeric($cat) ? 'cat' : 'category_name'] = $cat;
}
$tag = sanitize_text_field((string) $a['tag']);
if ($tag !== '') {
$query_args[is_numeric($tag) ? 'tag_id' : 'tag'] = $tag;
}
$mode = in_array($a['mode'], ['card', 'list', 'mini'], true) ? $a['mode'] : 'card';
$query = new WP_Query($query_args);
$html = aksrt_render_post_list($query, [
'context' => 'widget',
'mode' => $mode,
], false);
wp_reset_postdata();
return '<div class="aksrt-shortcode-posts">' . $html . '</div>';
}
}
endif;