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.
339 lines
13 KiB
PHP
339 lines
13 KiB
PHP
<?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;
|