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>
214 lines
7.7 KiB
PHP
214 lines
7.7 KiB
PHP
<?php
|
|
|
|
namespace Jio\WidgetSystem;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
/** Import/export contract for schema widgets. */
|
|
final class WidgetTransfer {
|
|
public const FORMAT = 'jio.widgets.v1';
|
|
|
|
/** @return array<string, array<string,mixed>> */
|
|
public static function templates(): array {
|
|
return [
|
|
'article_home' => [
|
|
'title' => __('文章首页', 'jio'),
|
|
'desc' => __('公告、幻灯片、多栏目文章与热榜侧栏。', 'jio'),
|
|
'remind' => __('导入到首页主内容区域,侧栏模块会一并导入。', 'jio'),
|
|
'sidebars' => [
|
|
'home_top_content' => [
|
|
['widget' => 'notice', 'settings' => ['title' => '站点公告']],
|
|
['widget' => 'slider', 'settings' => ['title' => '精选内容']],
|
|
['widget' => 'tab_posts', 'settings' => ['title' => '热门栏目']],
|
|
],
|
|
'home_sidebar' => [['widget' => 'hot_posts', 'settings' => ['title' => '热门文章']]],
|
|
],
|
|
],
|
|
'article_list' => [
|
|
'title' => __('文章列表', 'jio'),
|
|
'desc' => __('标题、分类卡片与可分页文章列表。', 'jio'),
|
|
'remind' => __('需已有文章与分类内容。', 'jio'),
|
|
'sidebars' => [
|
|
'home_top_content' => [
|
|
['widget' => 'text_title', 'settings' => ['heading' => '最新知识内容', 'description' => '持续更新的课程、文章与资料']],
|
|
['widget' => 'term_cards', 'settings' => ['title' => '内容分类']],
|
|
['widget' => 'posts', 'settings' => ['title' => '最新发布', 'paginate' => 'number']],
|
|
],
|
|
],
|
|
],
|
|
'links_navigation' => [
|
|
'title' => __('链接导航', 'jio'),
|
|
'desc' => __('搜索、分类入口与链接列表。', 'jio'),
|
|
'remind' => __('需已在链接管理中添加链接。', 'jio'),
|
|
'sidebars' => [
|
|
'home_top_content' => [
|
|
['widget' => 'icon_cover_cards', 'settings' => ['title' => '常用入口']],
|
|
['widget' => 'links', 'settings' => ['title' => '链接导航']],
|
|
],
|
|
'home_sidebar' => [['widget' => 'search', 'settings' => ['title' => '快速查找']]],
|
|
],
|
|
],
|
|
'article_sidebar' => [
|
|
'title' => __('文章侧栏', 'jio'),
|
|
'desc' => __('目录、作者、付费授权与热榜文章。', 'jio'),
|
|
'remind' => __('只能导入到文章页侧边栏。', 'jio'),
|
|
'sidebars' => [
|
|
'single_sidebar' => [
|
|
['widget' => 'posts_toc', 'settings' => ['title' => '文章目录']],
|
|
['widget' => 'author_card', 'settings' => ['title' => '作者']],
|
|
['widget' => 'post_pay', 'settings' => []],
|
|
['widget' => 'hot_posts', 'settings' => ['title' => '热门文章']],
|
|
],
|
|
],
|
|
],
|
|
'knowledge_landing' => [
|
|
'title' => __('知识付费落地页', 'jio'),
|
|
'desc' => __('亮点、方案、评价与常见问题。', 'jio'),
|
|
'remind' => __('适合放置在全宽度或主内容容器中。', 'jio'),
|
|
'sidebars' => [
|
|
'home_top_content' => [
|
|
['widget' => 'feature_intro', 'settings' => ['title' => '系统化学习路径']],
|
|
['widget' => 'highlights', 'settings' => ['title' => '内容亮点']],
|
|
['widget' => 'pricing_plans', 'settings' => ['title' => '选择学习方案']],
|
|
['widget' => 'reviews', 'settings' => ['title' => '学员评价']],
|
|
['widget' => 'faq', 'settings' => ['title' => '常见问题']],
|
|
],
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
public static function template_payload(string $template): array {
|
|
$templates = self::templates();
|
|
if (!isset($templates[$template])) {
|
|
throw new \InvalidArgumentException(__('未知的小工具模板。', 'jio'));
|
|
}
|
|
return [
|
|
'format' => self::FORMAT,
|
|
'template' => $template,
|
|
'sidebars' => $templates[$template]['sidebars'],
|
|
];
|
|
}
|
|
|
|
public static function export(string $sidebar = ''): array {
|
|
$assignments = wp_get_sidebars_widgets();
|
|
$sidebars = [];
|
|
foreach ($assignments as $sidebar_id => $widget_ids) {
|
|
if ($sidebar !== '' && $sidebar_id !== $sidebar) {
|
|
continue;
|
|
}
|
|
if (!is_array($widget_ids)) {
|
|
continue;
|
|
}
|
|
$items = [];
|
|
foreach ($widget_ids as $widget_id) {
|
|
$item = self::export_widget((string) $widget_id);
|
|
if ($item !== null) {
|
|
$items[] = $item;
|
|
}
|
|
}
|
|
if ($items || $sidebar !== '') {
|
|
$sidebars[$sidebar_id] = $items;
|
|
}
|
|
}
|
|
return [
|
|
'format' => self::FORMAT,
|
|
'exported_at' => gmdate('c'),
|
|
'sidebars' => $sidebars,
|
|
];
|
|
}
|
|
|
|
/** @return array{sidebars:int,widgets:int} */
|
|
public static function import(array $payload, string $mode = 'append', string $target_sidebar = ''): array {
|
|
if (($payload['format'] ?? '') !== self::FORMAT || !isset($payload['sidebars']) || !is_array($payload['sidebars'])) {
|
|
throw new \InvalidArgumentException(__('仅接受 jio.widgets.v1 格式。', 'jio'));
|
|
}
|
|
$mode = $mode === 'replace' ? 'replace' : 'append';
|
|
$registered = array_keys($GLOBALS['wp_registered_sidebars'] ?? []);
|
|
$assignments = wp_get_sidebars_widgets();
|
|
$sources = array_slice($payload['sidebars'], 0, 100, true);
|
|
if ($target_sidebar !== '') {
|
|
$combined = [];
|
|
foreach ($sources as $items) {
|
|
if (is_array($items)) {
|
|
$combined = array_merge($combined, $items);
|
|
}
|
|
}
|
|
$sources = [$target_sidebar => $combined];
|
|
}
|
|
$changed = 0;
|
|
$created = 0;
|
|
|
|
foreach ($sources as $source_sidebar => $items) {
|
|
$sidebar_id = $target_sidebar !== '' ? sanitize_key($target_sidebar) : sanitize_key((string) $source_sidebar);
|
|
if (!in_array($sidebar_id, $registered, true) || !is_array($items)) {
|
|
continue;
|
|
}
|
|
$current = isset($assignments[$sidebar_id]) && is_array($assignments[$sidebar_id]) ? $assignments[$sidebar_id] : [];
|
|
if ($mode === 'replace') {
|
|
$current = [];
|
|
}
|
|
foreach (array_slice($items, 0, 100) as $item) {
|
|
if (!is_array($item)) {
|
|
continue;
|
|
}
|
|
$widget_id = self::create_widget($item);
|
|
if ($widget_id !== '') {
|
|
$current[] = $widget_id;
|
|
$created++;
|
|
}
|
|
}
|
|
$assignments[$sidebar_id] = array_values(array_unique($current));
|
|
$changed++;
|
|
}
|
|
wp_set_sidebars_widgets($assignments);
|
|
return ['sidebars' => $changed, 'widgets' => $created];
|
|
}
|
|
|
|
/** @return list<array{id_base:string,title:string}> */
|
|
public static function template_modules(string $template): array {
|
|
$modules = [];
|
|
foreach (self::template_payload($template)['sidebars'] as $items) {
|
|
foreach ((array) $items as $item) {
|
|
$definition = WidgetRegistry::get((string) ($item['widget'] ?? ''));
|
|
if ($definition !== null) {
|
|
$modules[] = ['id_base' => $definition->id_base(), 'title' => $definition->name()];
|
|
}
|
|
}
|
|
}
|
|
return $modules;
|
|
}
|
|
|
|
private static function export_widget(string $widget_id): ?array {
|
|
if (!preg_match('/^(jio_widget_[a-z0-9_]+)-([1-9][0-9]*)$/', $widget_id, $matches)) {
|
|
return null;
|
|
}
|
|
$definition = WidgetRegistry::get_by_id_base($matches[1]);
|
|
if ($definition === null) {
|
|
return null;
|
|
}
|
|
$settings = get_option('widget_' . $definition->id_base(), []);
|
|
$number = (int) $matches[2];
|
|
if (!is_array($settings) || !isset($settings[$number]) || !is_array($settings[$number])) {
|
|
return null;
|
|
}
|
|
return ['widget' => $definition->slug(), 'settings' => $definition->normalize($settings[$number])];
|
|
}
|
|
|
|
private static function create_widget(array $item): string {
|
|
$definition = WidgetRegistry::get(sanitize_key((string) ($item['widget'] ?? '')));
|
|
if ($definition === null) {
|
|
return '';
|
|
}
|
|
$option_name = 'widget_' . $definition->id_base();
|
|
$settings = get_option($option_name, []);
|
|
$settings = is_array($settings) ? $settings : [];
|
|
$numbers = array_filter(array_map('intval', array_keys($settings)), static fn(int $number): bool => $number > 0);
|
|
$number = $numbers ? max($numbers) + 1 : 1;
|
|
$settings[$number] = $definition->normalize((array) ($item['settings'] ?? []));
|
|
$settings['_multiwidget'] = 1;
|
|
update_option($option_name, $settings);
|
|
return $definition->id_base() . '-' . $number;
|
|
}
|
|
}
|