Files
kate522andClaude Opus 5 35bb96e82f refactor: rename the theme brand and every identifier from AKSRT to Jio
Full-project rename. `git grep -i aksrt` is now empty apart from the
DBMigrator 3.3.0 changelog note that documents the rename itself.

Code identifiers
- PHP namespace AKSRT\ -> Jio\ (composer PSR-4 + 378 declarations)
- global constants AKSRT_* -> JIO_* (VERSION/DIR/URI, PAY_TYPE_*, CAPTCHA_*)
- procedural functions, hooks, options, meta keys, table names,
  cookies, query vars, ajax actions and nonces: aksrt_ -> jio_
- text domain aksrt -> jio; Theme Name -> Jio
- JS globals: aksrtData/aksrtAdminData/... -> jioData/jioAdminData/...,
  window.AKSRTCaptcha & friends -> window.JioCaptcha, Aksrt* -> Jio*
- Vue components Aks*.vue -> Jio*.vue (15 files renamed + imports)
- CSS classes/data-attrs/custom properties: aksrt- -> jio-
- REST namespace aksrt/v1 -> jio/v1 (incl. payment gateway callbacks)

Storage layer
- DB tables {prefix}aksrt_* -> {prefix}jio_*; SCHEMA_VERSION 3.2.0 -> 3.3.0
  so dbDelta creates the new names on install
- widget id_base aksrt_widget_* -> jio_widget_*
- uninstall.php drop list rebuilt from the actual migrator tables
  (dropped 3 stale entries, added the 17 missing ones)

Removed
- hardcoded https://api.aksrt.com/version.json default update endpoint;
  get_endpoint() now reads the 「更新端点」 setting and do_check() no-ops
  when it is empty

Files renamed: inc/helpers/aksrt-*.php -> jio-*.php (54),
assets/admin/src/components/form/Aks*.vue -> Jio*.vue (15).
Build artifacts regenerated: assets/css/build, assets/js/runtime,
assets/admin/build.

Verified: audit-psr4-paths errors=0, audit-admin-contracts errors=0
(warnings=56, unchanged baseline), 813/813 widget-system tests,
44/44 phpunit, php -l clean across all PHP.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-01 20:05:04 +08:00

602 lines
22 KiB
PHP
Raw Permalink 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
/**
* Jio Theme Core Hooks
*
* Central registration for the theme's WordPress hooks.
*
* @package Jio
* @version 3.2.0
*/
defined('ABSPATH') || exit;
// ============================================================
// 评论功能开关
// ============================================================
add_filter('comments_open', function ($open, $post_id) {
if (class_exists('\Jio\Settings\Settings')) {
$display = \Jio\Settings\Settings::get_comment_display_settings();
if (!empty($display['close_comments'])) {
return false;
}
}
return $open;
}, 10, 2);
add_filter('comment_text', function ($comment_text, $comment = null, $args = []) {
if (function_exists('jio_render_comment_content')) {
return jio_render_comment_content((string) $comment_text);
}
return $comment_text;
}, 20, 3);
// ============================================================
// 文章预置参数:新建文章按区间随机填入浏览/点赞(参考主题 post_default_mate)
// ============================================================
add_action('save_post', static function ($post_id, $post = null): void {
$post_id = (int) $post_id;
if ($post_id <= 0 || wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) {
return;
}
if (!($post instanceof \WP_Post) || $post->post_type !== 'post') {
return;
}
if (!class_exists('\Jio\Settings\Settings')) {
return;
}
$page = \Jio\Settings\Settings::get_post_page_settings();
$views_min = max(0, (int) ($page['default_views_min'] ?? 20));
$views_max = max($views_min, (int) ($page['default_views_max'] ?? 50));
$likes_min = max(0, (int) ($page['default_likes_min'] ?? 5));
$likes_max = max($likes_min, (int) ($page['default_likes_max'] ?? 15));
if ('' === (string) get_post_meta($post_id, \Jio\Core\DomainModel::POST_VIEW_COUNT_KEY, true)) {
update_post_meta($post_id, \Jio\Core\DomainModel::POST_VIEW_COUNT_KEY, wp_rand($views_min, $views_max));
}
if ('' === (string) get_post_meta($post_id, '_jio_like_count', true)) {
update_post_meta($post_id, '_jio_like_count', wp_rand($likes_min, $likes_max));
}
}, 10, 2);
// ============================================================
// 前端隐藏 WordPress Admin Bar:由 SystemTools 按「扩展&增强 → 系统工具」
// 的 hide_admin_bar 设置门控(默认开启),不再无条件注入。
// ============================================================
// ============================================================
// SMTP 邮件配置注入
// ============================================================
add_filter('wp_mail_from', function ($from) {
if (!class_exists('\Jio\Settings\Settings')) return $from;
$mail = \Jio\Settings\Settings::get_smtp();
if (empty($mail['enabled'])) return $from;
$configured = sanitize_email((string) ($mail['from_address'] ?? ''));
return $configured !== '' ? $configured : $from;
});
add_filter('wp_mail_from_name', function ($from_name) {
if (!class_exists('\Jio\Settings\Settings')) return $from_name;
$mail = \Jio\Settings\Settings::get_smtp();
if (empty($mail['enabled'])) return $from_name;
$configured = trim((string) ($mail['from_name'] ?? ''));
return $configured !== '' ? $configured : $from_name;
});
add_action('phpmailer_init', function ($phpmailer) {
$mail = class_exists('\\Jio\\Settings\\Settings')
? \Jio\Settings\Settings::get_smtp()
: [];
if (empty($mail['enabled']) || empty($mail['host'])) return;
$phpmailer->isSMTP();
$phpmailer->Host = $mail['host'];
$phpmailer->Port = (int) ($mail['port'] ?? 465);
$phpmailer->SMTPAuth = !empty($mail['auth']) && !empty($mail['username']);
$phpmailer->Username = (string) ($mail['username'] ?? '');
$phpmailer->Password = (string) ($mail['password'] ?? '');
$phpmailer->SMTPSecure = (string) ($mail['encryption'] ?? 'ssl');
$phpmailer->Timeout = 10;
$phpmailer->Timelimit = 15;
});
// mail_title_prefix is applied by the canonical renderer and by third-party wp_mail callers.
add_filter('wp_mail', function ($atts) {
if (!class_exists('\Jio\Settings\Settings') || empty($atts['subject'])) return $atts;
$branding = \Jio\Settings\Settings::get_mail_branding();
$subject = (string) $atts['subject'];
$site = wp_specialchars_decode(get_option('blogname'), ENT_QUOTES);
$subject = preg_replace('/^\[' . preg_quote($site, '/') . '\]\s*/u', '', $subject);
if ($branding['title_prefix'] !== '' && strpos($subject, $branding['title_prefix']) !== 0) {
$atts['subject'] = $branding['title_prefix'] . ' ' . $subject;
} else {
$atts['subject'] = $subject;
}
return $atts;
});
// ============================================================
// 分类 Term Meta 字段(封面图 + 推荐描述)
// ============================================================
/**
* 编辑分类页面 - 显示自定义字段
*/
function jio_category_meta_fields($term) {
$cover_id = (int) get_term_meta($term->term_id, 'jio_category_cover_id', true);
$cover_url = $cover_id > 0 ? wp_get_attachment_url($cover_id) : '';
$recommend_desc = (string) get_term_meta($term->term_id, 'jio_category_recommend_desc', true);
?>
<tr class="form-field">
<th scope="row"><label for="jio_category_cover_id"><?php esc_html_e('分类封面图', 'jio'); ?></label></th>
<td>
<div id="jio-cover-preview" style="margin-bottom:8px;">
<?php if ($cover_url) : ?>
<img src="<?php echo esc_url($cover_url); ?>" style="max-width:200px;height:auto;border-radius:6px;" alt="">
<?php endif; ?>
</div>
<input type="hidden" id="jio_category_cover_id" name="jio_category_cover_id" value="<?php echo esc_attr((string) $cover_id); ?>">
<button type="button" class="button" id="jio-pick-cover"><?php esc_html_e('选择图片', 'jio'); ?></button>
<button type="button" class="button" id="jio-remove-cover" <?php echo $cover_id > 0 ? '' : 'style="display:none;"'; ?>><?php esc_html_e('移除', 'jio'); ?></button>
<p class="description"><?php esc_html_e('分类推荐模块的封面图片。', 'jio'); ?></p>
</td>
</tr>
<tr class="form-field">
<th scope="row"><label for="jio_category_recommend_desc"><?php esc_html_e('推荐描述', 'jio'); ?></label></th>
<td>
<textarea id="jio_category_recommend_desc" name="jio_category_recommend_desc" rows="3" class="large-text"><?php echo esc_textarea($recommend_desc); ?></textarea>
<p class="description"><?php esc_html_e('分类推荐模块中显示的描述文字。留空则使用分类默认描述。', 'jio'); ?></p>
</td>
</tr>
<?php
}
/**
* 新增分类页面 - 显示自定义字段
*/
function jio_category_meta_fields_add() {
?>
<div class="form-field">
<label for="jio_category_cover_id"><?php esc_html_e('分类封面图', 'jio'); ?></label>
<div id="jio-cover-preview" style="margin-bottom:8px;"></div>
<input type="hidden" id="jio_category_cover_id" name="jio_category_cover_id" value="">
<button type="button" class="button" id="jio-pick-cover"><?php esc_html_e('选择图片', 'jio'); ?></button>
<button type="button" class="button" id="jio-remove-cover" style="display:none;"><?php esc_html_e('移除', 'jio'); ?></button>
<p class="description"><?php esc_html_e('分类推荐模块的封面图片。', 'jio'); ?></p>
</div>
<div class="form-field">
<label for="jio_category_recommend_desc"><?php esc_html_e('推荐描述', 'jio'); ?></label>
<textarea id="jio_category_recommend_desc" name="jio_category_recommend_desc" rows="3"></textarea>
<p class="description"><?php esc_html_e('分类推荐模块中显示的描述文字。留空则使用分类默认描述。', 'jio'); ?></p>
</div>
<?php
}
add_action('category_edit_form_fields', 'jio_category_meta_fields', 10, 1);
add_action('category_add_form_fields', 'jio_category_meta_fields_add', 10, 1);
/**
* 保存分类 term meta
*/
function jio_save_category_meta(int $term_id) {
if (!current_user_can('manage_categories')) return;
$cover_id = isset($_POST['jio_category_cover_id']) ? absint($_POST['jio_category_cover_id']) : 0;
$recommend_desc = isset($_POST['jio_category_recommend_desc']) ? sanitize_textarea_field(wp_unslash($_POST['jio_category_recommend_desc'])) : '';
if ($cover_id > 0) {
update_term_meta($term_id, 'jio_category_cover_id', $cover_id);
} else {
delete_term_meta($term_id, 'jio_category_cover_id');
}
if ($recommend_desc !== '') {
update_term_meta($term_id, 'jio_category_recommend_desc', $recommend_desc);
} else {
delete_term_meta($term_id, 'jio_category_recommend_desc');
}
if (class_exists('\Jio\Settings\Settings')) {
\Jio\Settings\Settings::flush_category_recommend_cache();
}
}
add_action('edited_category', 'jio_save_category_meta', 10, 1);
add_action('created_category', 'jio_save_category_meta', 10, 1);
/**
* 分类编辑页加载媒体库脚本(wp_enqueue_media 仅在 Jio 面板加载,此处补上)
*/
add_action('admin_enqueue_scripts', function (string $hook_suffix) {
if ($hook_suffix === 'edit-tags.php' || $hook_suffix === 'term.php') {
wp_enqueue_media();
}
});
/**
* 分类编辑页内联 JS - 媒体选择器
*/
function jio_category_meta_admin_js() {
$screen = get_current_screen();
if (!$screen || $screen->taxonomy !== 'category') return;
?>
<script>
jQuery(function($) {
var frame;
$(document).on('click', '#jio-pick-cover', function(e) {
e.preventDefault();
if (typeof wp === 'undefined' || !wp.media) return;
if (frame) { frame.open(); return; }
frame = wp.media({
title: '<?php echo esc_js(__('选择分类封面图', 'jio')); ?>',
button: { text: '<?php echo esc_js(__('使用此图片', 'jio')); ?>' },
multiple: false
});
frame.on('select', function() {
var attachment = frame.state().get('selection').first().toJSON();
$('#jio_category_cover_id').val(attachment.id);
$('#jio-cover-preview').html('<img src="' + attachment.url + '" style="max-width:200px;height:auto;border-radius:6px;" alt="">');
$('#jio-remove-cover').show();
});
frame.open();
});
$(document).on('click', '#jio-remove-cover', function(e) {
e.preventDefault();
$('#jio_category_cover_id').val('');
$('#jio-cover-preview').html('');
$(this).hide();
});
});
</script>
<?php
}
add_action('admin_footer', 'jio_category_meta_admin_js');
// ============================================================
// 资源列表:/resources 虚拟归档(post + _jio_is_resource)
// ============================================================
add_action('init', function () {
add_rewrite_rule('^resources/?$', 'index.php?jio_is_resource_archive=1', 'top');
add_rewrite_rule('^resources/page/([0-9]+)/?$', 'index.php?jio_is_resource_archive=1&paged=$matches[1]', 'top');
// 旧单篇路径 /resource/{slug} → 301 到 post 永久链接
add_rewrite_rule('^resource/([^/]+)/?$', 'index.php?jio_legacy_resource=$matches[1]', 'top');
});
add_filter('query_vars', function ($vars) {
$vars[] = 'jio_is_resource_archive';
$vars[] = 'jio_legacy_resource';
$vars[] = 'type';
$vars[] = 'trem';
return $vars;
});
add_action('template_redirect', function () {
$slug = get_query_var('jio_legacy_resource');
if (!$slug) {
return;
}
$page = get_page_by_path((string) $slug, OBJECT, 'post');
if ($page) {
wp_redirect(get_permalink($page->ID), 301);
exit;
}
});
add_action('pre_get_posts', function ($query) {
if (is_admin() || !$query->is_main_query()) {
return;
}
if ((bool) $query->get('jio_is_resource_archive')) {
$query->set('post_type', 'post');
$query->set('post_status', 'publish');
$orderby = isset($_GET['orderby']) ? sanitize_key((string) $_GET['orderby']) : 'date';
if ($orderby === 'downloads') {
$query->set('meta_key', \Jio\Core\DomainModel::RESOURCE_DOWNLOAD_COUNT_KEY);
$query->set('orderby', 'meta_value_num');
$query->set('order', 'DESC');
} elseif ($orderby === 'views') {
$query->set('meta_key', \Jio\Core\DomainModel::RESOURCE_VIEW_COUNT_KEY);
$query->set('orderby', 'meta_value_num');
$query->set('order', 'DESC');
}
$price_filter = isset($_GET['price']) ? sanitize_key((string) $_GET['price']) : '';
$price_min = isset($_GET['price_min']) && $_GET['price_min'] !== '' ? (float) $_GET['price_min'] : null;
$price_max = isset($_GET['price_max']) && $_GET['price_max'] !== '' ? (float) $_GET['price_max'] : null;
$meta_query = [
'relation' => 'AND',
[
'key' => \Jio\Core\DomainModel::RESOURCE_MARKER_KEY,
'value' => '1',
'compare' => '=',
],
];
if ($price_filter === 'free') {
$meta_query[] = [
'relation' => 'OR',
[
'key' => \Jio\Core\DomainModel::PAY_PRICE_KEY,
'value' => 0,
'compare' => '=',
'type' => 'NUMERIC',
],
[
'key' => \Jio\Core\DomainModel::PAY_PRICE_KEY,
'compare' => 'NOT EXISTS',
],
];
} elseif ($price_filter === 'paid') {
$meta_query[] = [
'key' => \Jio\Core\DomainModel::PAY_PRICE_KEY,
'value' => 0,
'compare' => '>',
'type' => 'NUMERIC',
];
}
if ($price_min !== null || $price_max !== null) {
if ($price_min !== null && $price_max !== null) {
$meta_query[] = [
'key' => \Jio\Core\DomainModel::PAY_PRICE_KEY,
'value' => [$price_min, $price_max],
'compare' => 'BETWEEN',
'type' => 'NUMERIC',
];
} elseif ($price_min !== null) {
$meta_query[] = [
'key' => \Jio\Core\DomainModel::PAY_PRICE_KEY,
'value' => $price_min,
'compare' => '>=',
'type' => 'NUMERIC',
];
} else {
$meta_query[] = [
'key' => \Jio\Core\DomainModel::PAY_PRICE_KEY,
'value' => $price_max,
'compare' => '<=',
'type' => 'NUMERIC',
];
}
}
$existing = $query->get('meta_query');
if (!is_array($existing)) {
$existing = [];
}
$query->set('meta_query', array_merge($existing, $meta_query));
$query->set('cat', 0);
$query->set('tag', '');
}
// 站内搜索:分类/标签名搜索(内容类型始终为 post)
if ($query->is_search() && empty($query->get('post_type'))) {
$search_type = sanitize_key((string) $query->get('type'));
$trem = absint($query->get('trem'));
if ($trem > 0) {
$query->set('cat', $trem);
}
if (in_array($search_type, ['category', 'tag'], true)) {
$taxonomy = $search_type === 'category' ? 'category' : 'post_tag';
$term_ids = get_terms([
'taxonomy' => $taxonomy,
'hide_empty' => false,
'fields' => 'ids',
'number' => 20,
'search' => (string) $query->get('s'),
]);
$query->set('s', '');
if (is_wp_error($term_ids) || empty($term_ids)) {
$query->set('post__in', [0]);
} else {
$query->set('tax_query', [[
'taxonomy' => $taxonomy,
'field' => 'term_id',
'terms' => array_map('absint', $term_ids),
]]);
}
}
}
});
add_filter('template_include', function ($template) {
if (!get_query_var('jio_is_resource_archive')) {
return $template;
}
$candidate = locate_template(['archive-resources.php']);
return $candidate ?: $template;
});
add_filter('document_title_parts', function ($parts) {
if (get_query_var('jio_is_resource_archive')) {
$parts['title'] = __('资源', 'jio');
}
return $parts;
});
// EnlighterJS 代码高亮 — 白名单 data-enlighter-* 属性
// ============================================================
add_filter('wp_kses_allowed_html', function ($allowed, $context) {
if ($context !== 'post') {
return $allowed;
}
// Allow data-enlighter-* attributes on <pre> and <code>
$enlighter_attrs = [
'data-enlighter-language' => true,
'data-enlighter-theme' => true,
'data-enlighter-group' => true,
'data-enlighter-title' => true,
'data-enlighter-linenumbers' => true,
'data-enlighter-highlight' => true,
'data-enlighter-lineoffset' => true,
];
if (isset($allowed['pre'])) {
$allowed['pre'] = array_merge($allowed['pre'], $enlighter_attrs);
}
if (isset($allowed['code'])) {
$allowed['code'] = array_merge($allowed['code'], $enlighter_attrs);
}
return $allowed;
}, 10, 2);
// ============================================================
// Gutenberg 自定义块注册
// ============================================================
// Register category server-side so it exists before JS registerBlockType runs.
// Keep untyped + defensive: some plugins pass unexpected shapes; typed params can 500 the editor.
add_filter('block_categories_all', static function ($categories, $context = null) {
if (!is_array($categories)) {
return $categories;
}
foreach ($categories as $category) {
$slug = '';
if (is_array($category)) {
$slug = (string) ($category['slug'] ?? '');
} elseif (is_object($category) && isset($category->slug)) {
$slug = (string) $category->slug;
}
if ($slug === 'jio-blocks') {
return $categories;
}
}
$categories[] = [
'slug' => 'jio-blocks',
'title' => 'Jio',
'icon' => null,
];
return $categories;
}, 10, 2);
add_action('enqueue_block_editor_assets', function () {
$base_uri = get_template_directory_uri();
$blocks_js = get_template_directory() . '/assets/js/gutenberg-blocks.js';
if (file_exists($blocks_js)) {
wp_enqueue_script(
'jio-gutenberg-blocks',
$base_uri . '/assets/js/gutenberg-blocks.js',
// wp-media-utils: MediaUpload for carousel block
['wp-blocks', 'wp-element', 'wp-components', 'wp-block-editor', 'wp-hooks', 'wp-media-utils', 'wp-data'],
(string) filemtime($blocks_js),
true
);
}
$blocks_css = get_template_directory() . '/assets/css/gutenberg-blocks.css';
if (file_exists($blocks_css)) {
wp_enqueue_style(
'jio-gutenberg-blocks',
$base_uri . '/assets/css/gutenberg-blocks.css',
[],
(string) filemtime($blocks_css)
);
}
});
// Front-end styles for Gutenberg blocks
add_action('enqueue_block_assets', function () {
if (is_admin()) return; // Already loaded in editor
$posts = [];
$queried = get_queried_object();
if ($queried instanceof \WP_Post) {
$posts[$queried->ID] = $queried;
}
global $wp_query;
if ($wp_query instanceof \WP_Query) {
foreach ((array) $wp_query->posts as $post) {
if ($post instanceof \WP_Post) {
$posts[$post->ID] = $post;
}
}
}
$has_carousel = false;
foreach ($posts as $post) {
if (has_block('jio/carousel', $post)) {
$has_carousel = true;
break;
}
}
if (!$has_carousel) return;
$base_uri = get_template_directory_uri();
$blocks_css = get_template_directory() . '/assets/css/gutenberg-blocks.css';
if (file_exists($blocks_css)) {
wp_enqueue_style(
'jio-gutenberg-blocks-front',
$base_uri . '/assets/css/gutenberg-blocks.css',
[],
(string) filemtime($blocks_css)
);
}
});
// ============================================================
// Gutenberg Carousel Block 渲染回调
// ============================================================
add_action('init', function () {
if (!function_exists('register_block_type')) return;
register_block_type('jio/carousel', [
'render_callback' => function (array $attributes): string {
$images = $attributes['images'] ?? [];
if (empty($images)) return '';
$autoplay = !empty($attributes['autoplay']) ? 'true' : 'false';
$delay = absint($attributes['delay'] ?? 3000);
$effect = sanitize_text_field($attributes['effect'] ?? 'slide');
$slides = '';
foreach ($images as $img) {
if (empty($img['url'])) continue;
$alt = esc_attr($img['alt'] ?? '');
$url = esc_url($img['url']);
$slides .= '<div class="swiper-slide"><img' . jio_get_lazy_attr('lazy_sider', (string) $url, '', jio_get_lazy_thumb('lg')) . ' alt="' . $alt . '"></div>';
}
if (empty($slides)) return '';
return '<div class="jio-carousel-block swiper" data-autoplay="' . $autoplay . '" data-delay="' . $delay . '" data-effect="' . $effect . '">'
. '<div class="swiper-wrapper">' . $slides . '</div>'
. '<div class="swiper-pagination"></div>'
. '<div class="swiper-button-prev"></div><div class="swiper-button-next"></div>'
. '</div>';
},
]);
});
// ============================================================
// 付费内容系统:the_content 过滤器
// ============================================================
/**
* 未购买的图库/视频正文只保留公开预览;购买卡由 single 模板渲染。
*/
add_filter('the_content', 'jio_paid_content_filter', 20);