feat(widgets): complete phase 3 atomic integration
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace AKSRT\WidgetSystem;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/** Admin-only import, export, template application and preview. */
|
||||
final class TransferController {
|
||||
private const NONCE = 'aksrt_widgets_transfer';
|
||||
|
||||
public static function register_hooks(): void {
|
||||
add_action('admin_notices', [self::class, 'render_panel']);
|
||||
add_action('admin_post_aksrt_widgets_export', [self::class, 'handle_export']);
|
||||
add_action('admin_post_aksrt_widgets_import', [self::class, 'handle_import']);
|
||||
add_action('admin_post_aksrt_widgets_template', [self::class, 'handle_template']);
|
||||
add_action('wp_ajax_aksrt_widgets_template_preview', [self::class, 'ajax_preview']);
|
||||
}
|
||||
|
||||
public static function render_panel(): void {
|
||||
$screen = function_exists('get_current_screen') ? get_current_screen() : null;
|
||||
if (!$screen || $screen->id !== 'widgets' || !current_user_can('edit_theme_options')) {
|
||||
return;
|
||||
}
|
||||
$sidebars = $GLOBALS['wp_registered_sidebars'] ?? [];
|
||||
$templates = WidgetTransfer::templates();
|
||||
$notice = isset($_GET['aksrt_widgets_notice']) ? sanitize_key(wp_unslash($_GET['aksrt_widgets_notice'])) : '';
|
||||
if ($notice !== '') {
|
||||
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__('小工具配置已更新。', 'aksrt') . '</p></div>';
|
||||
}
|
||||
?>
|
||||
<div class="aksrt-widget-transfer postbox">
|
||||
<div class="postbox-header"><h2><?php esc_html_e('AKSRT 小工具模板与导入导出', 'aksrt'); ?></h2></div>
|
||||
<div class="inside aksrt-widget-transfer__grid">
|
||||
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
|
||||
<h3><?php esc_html_e('导出', 'aksrt'); ?></h3>
|
||||
<input type="hidden" name="action" value="aksrt_widgets_export">
|
||||
<?php wp_nonce_field(self::NONCE); ?>
|
||||
<?php self::sidebar_select($sidebars, 'sidebar', true); ?>
|
||||
<?php submit_button(__('下载 aksrt.widgets.v1', 'aksrt'), 'secondary', 'submit', false); ?>
|
||||
</form>
|
||||
<form method="post" enctype="multipart/form-data" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
|
||||
<h3><?php esc_html_e('导入', 'aksrt'); ?></h3>
|
||||
<input type="hidden" name="action" value="aksrt_widgets_import">
|
||||
<?php wp_nonce_field(self::NONCE); ?>
|
||||
<input type="file" name="widgets_file" accept="application/json,.json" required>
|
||||
<?php self::mode_select(); ?>
|
||||
<?php self::sidebar_select($sidebars, 'target_sidebar', true, __('沿用文件中的区域', 'aksrt')); ?>
|
||||
<?php submit_button(__('导入配置', 'aksrt'), 'secondary', 'submit', false); ?>
|
||||
</form>
|
||||
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
|
||||
<h3><?php esc_html_e('内置模板', 'aksrt'); ?></h3>
|
||||
<input type="hidden" name="action" value="aksrt_widgets_template">
|
||||
<?php wp_nonce_field(self::NONCE); ?>
|
||||
<select name="template" class="aksrt-widget-template-select">
|
||||
<?php foreach ($templates as $key => $template) : ?>
|
||||
<option value="<?php echo esc_attr($key); ?>"><?php echo esc_html((string) $template['label']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php self::mode_select(); ?>
|
||||
<?php self::sidebar_select($sidebars, 'target_sidebar', true, __('使用模板默认区域', 'aksrt')); ?>
|
||||
<div class="aksrt-widget-transfer__actions">
|
||||
<button type="button" class="button aksrt-widget-template-preview"><?php esc_html_e('预览模块', 'aksrt'); ?></button>
|
||||
<?php submit_button(__('应用模板', 'aksrt'), 'primary', 'submit', false); ?>
|
||||
</div>
|
||||
</form>
|
||||
<div class="aksrt-widget-template-preview-output" hidden></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
public static function handle_export(): void {
|
||||
self::authorize();
|
||||
$sidebar = isset($_POST['sidebar']) ? sanitize_key(wp_unslash($_POST['sidebar'])) : '';
|
||||
$payload = WidgetTransfer::export($sidebar);
|
||||
nocache_headers();
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Content-Disposition: attachment; filename="aksrt-widgets-' . gmdate('Ymd-His') . '.json"');
|
||||
echo wp_json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function handle_import(): void {
|
||||
self::authorize();
|
||||
$file = $_FILES['widgets_file'] ?? null;
|
||||
if (!is_array($file) || !empty($file['error']) || empty($file['tmp_name']) || (int) ($file['size'] ?? 0) > 2 * MB_IN_BYTES) {
|
||||
wp_die(esc_html__('导入文件无效或超过 2MB。', 'aksrt'));
|
||||
}
|
||||
$payload = json_decode((string) file_get_contents((string) $file['tmp_name']), true);
|
||||
if (!is_array($payload)) {
|
||||
wp_die(esc_html__('导入文件不是有效 JSON。', 'aksrt'));
|
||||
}
|
||||
self::run_import($payload);
|
||||
}
|
||||
|
||||
public static function handle_template(): void {
|
||||
self::authorize();
|
||||
$template = isset($_POST['template']) ? sanitize_key(wp_unslash($_POST['template'])) : '';
|
||||
try {
|
||||
$payload = WidgetTransfer::template_payload($template);
|
||||
} catch (\InvalidArgumentException $error) {
|
||||
wp_die(esc_html($error->getMessage()), '', ['response' => 400]);
|
||||
}
|
||||
self::run_import($payload);
|
||||
}
|
||||
|
||||
public static function ajax_preview(): void {
|
||||
check_ajax_referer(self::NONCE, 'nonce');
|
||||
if (!current_user_can('edit_theme_options')) {
|
||||
wp_send_json_error(['message' => __('权限不足。', 'aksrt')], 403);
|
||||
}
|
||||
$template = isset($_POST['template']) ? sanitize_key(wp_unslash($_POST['template'])) : '';
|
||||
try {
|
||||
wp_send_json_success(['html' => WidgetTransfer::preview($template)]);
|
||||
} catch (\InvalidArgumentException $error) {
|
||||
wp_send_json_error(['message' => $error->getMessage()], 400);
|
||||
}
|
||||
}
|
||||
|
||||
private static function authorize(): void {
|
||||
if (!current_user_can('edit_theme_options')) {
|
||||
wp_die(esc_html__('权限不足。', 'aksrt'), '', ['response' => 403]);
|
||||
}
|
||||
check_admin_referer(self::NONCE);
|
||||
}
|
||||
|
||||
private static function run_import(array $payload): void {
|
||||
$mode = isset($_POST['mode']) ? sanitize_key(wp_unslash($_POST['mode'])) : 'append';
|
||||
$target = isset($_POST['target_sidebar']) ? sanitize_key(wp_unslash($_POST['target_sidebar'])) : '';
|
||||
try {
|
||||
WidgetTransfer::import($payload, $mode, $target);
|
||||
} catch (\InvalidArgumentException $error) {
|
||||
wp_die(esc_html($error->getMessage()), '', ['response' => 400]);
|
||||
}
|
||||
wp_safe_redirect(add_query_arg('aksrt_widgets_notice', 'updated', admin_url('widgets.php')));
|
||||
exit;
|
||||
}
|
||||
|
||||
private static function sidebar_select(array $sidebars, string $name, bool $allow_empty, string $empty_label = ''): void {
|
||||
echo '<label><span class="screen-reader-text">' . esc_html__('小工具区域', 'aksrt') . '</span><select name="' . esc_attr($name) . '">';
|
||||
if ($allow_empty) {
|
||||
echo '<option value="">' . esc_html($empty_label !== '' ? $empty_label : __('全部区域', 'aksrt')) . '</option>';
|
||||
}
|
||||
foreach ($sidebars as $id => $sidebar) {
|
||||
echo '<option value="' . esc_attr((string) $id) . '">' . esc_html((string) ($sidebar['name'] ?? $id)) . '</option>';
|
||||
}
|
||||
echo '</select></label>';
|
||||
}
|
||||
|
||||
private static function mode_select(): void {
|
||||
echo '<select name="mode"><option value="append">' . esc_html__('追加', 'aksrt') . '</option><option value="replace">' . esc_html__('替换区域', 'aksrt') . '</option></select>';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user