Files
wordpress-theme-aksrt/src/Admin/NavMenuOptions.php
T
kate522 9375b1c30c refactor(assets): vendor 资产迁入标准目录并全仓去除 zibll 标识
- assets/vendor 全量迁移: fontawesome5→assets/fontawesome5, css→assets/css,
  js→assets/js/runtime(避开 14 个同名自有文件), img 合并去重→assets/img,
  fonts→assets/fonts; 删除 assets/vendor
- libs 五个与 widgets/third-party 字节相同的库不重复入库, loader paths 改指向
- 运行时切换: _win.uri=assets 根, loader baseUrl=/js/runtime, enlighter 注入
  路径同步; PHP 7 个引用文件切换并改名 $zibll_*→$runtime_*
- 去品牌: Qinver 头注释/品牌 console.log/演示链接清除, 默认文案中性化
- 功能标识符改名: zib__→aksrt__, zib_ajax→aksrt_ajax, window.zib→
  window.aksrtLegacy, zibllblock/*→aksrtblock/*, .zib-widget→.aksrt-panel,
  .zib-slider→.aksrt-slider, zib_menu_options meta 键等
- 自有 CSS 改名: legacy-utilities/compat-vars/footer shell
- admin SPA 重建; 契约测试同步(路径/断言/文件更名)
- 例外保留: tests/visual 对照环境与 fixtures 前缀(外部 Zibll 产品引用)

验证: php -l 133 个改动文件通过; runtime 86 个 JS node --check 通过;
契约测试 68/70(2 个失败经 stash 对照确认为存量问题)
2026-08-22 16:16:30 +08:00

271 lines
10 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
/**
* Native WP nav-menu item options using legacy's aksrt_menu_options meta contract.
*
* @package AKSRT
*/
namespace AKSRT\Admin;
defined('ABSPATH') || exit;
class NavMenuOptions {
public static function init(): void {
add_action('wp_nav_menu_item_custom_fields', [self::class, 'render_fields'], 10, 2);
add_action('wp_update_nav_menu_item', [self::class, 'save'], 10, 2);
add_action('admin_enqueue_scripts', [self::class, 'enqueue_admin']);
}
public static function enqueue_admin(string $hook): void {
if ($hook !== 'nav-menus.php') {
return;
}
$path = get_template_directory() . '/assets/js/admin-nav-menu-senior.js';
if (!file_exists($path)) {
return;
}
wp_enqueue_media();
wp_enqueue_script(
'aksrt-admin-nav-menu-senior',
get_template_directory_uri() . '/assets/js/admin-nav-menu-senior.js',
['jquery'],
(string) filemtime($path),
true
);
}
/**
* @param int $item_id
* @param \WP_Post $item
*/
public static function render_fields($item_id, $item): void {
$item_id = (int) $item_id;
$opts = get_post_meta($item_id, 'aksrt_menu_options', true);
$opts = is_array($opts) ? $opts : [];
$icon = (string) ($opts['icon'] ?? '');
$badge = (string) ($opts['badge'] ?? '');
$badge_class = (string) ($opts['badge_class'] ?? '');
$submenu_s = !empty($opts['submenu_s']);
$submenu_type = (string) ($opts['submenu_type'] ?? '');
$graphic_json = !empty($opts['graphic_card_opts'])
? wp_json_encode($opts['graphic_card_opts'], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
: self::sample_graphic_json();
$multi_json = !empty($opts['multi_column_links_opts'])
? wp_json_encode($opts['multi_column_links_opts'], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)
: self::sample_multi_json();
?>
<p class="field-aksrt-icon description description-wide">
<label for="edit-menu-item-aksrt-icon-<?php echo esc_attr((string) $item_id); ?>">
<?php esc_html_e('图标 class(如 fa fas fa-home)', 'aksrt'); ?><br>
<input type="text" id="edit-menu-item-aksrt-icon-<?php echo esc_attr((string) $item_id); ?>"
class="widefat" name="aksrt_menu_options[<?php echo esc_attr((string) $item_id); ?>][icon]"
value="<?php echo esc_attr($icon); ?>">
</label>
</p>
<p class="field-aksrt-badge description description-thin">
<label for="edit-menu-item-aksrt-badge-<?php echo esc_attr((string) $item_id); ?>">
<?php esc_html_e('徽章文字', 'aksrt'); ?><br>
<input type="text" id="edit-menu-item-aksrt-badge-<?php echo esc_attr((string) $item_id); ?>"
class="widefat" name="aksrt_menu_options[<?php echo esc_attr((string) $item_id); ?>][badge]"
value="<?php echo esc_attr($badge); ?>">
</label>
</p>
<p class="field-aksrt-badge-class description description-thin">
<label for="edit-menu-item-aksrt-badge-class-<?php echo esc_attr((string) $item_id); ?>">
<?php esc_html_e('徽章 class', 'aksrt'); ?><br>
<input type="text" id="edit-menu-item-aksrt-badge-class-<?php echo esc_attr((string) $item_id); ?>"
class="widefat" name="aksrt_menu_options[<?php echo esc_attr((string) $item_id); ?>][badge_class]"
value="<?php echo esc_attr($badge_class); ?>">
</label>
</p>
<p class="field-aksrt-submenu description description-wide">
<label>
<input type="checkbox" name="aksrt_menu_options[<?php echo esc_attr((string) $item_id); ?>][submenu_s]" value="1" <?php checked($submenu_s); ?>>
<?php esc_html_e('启用高级子菜单(仅一级菜单)', 'aksrt'); ?>
</label>
</p>
<p class="field-aksrt-submenu-type description description-wide">
<label for="edit-menu-item-aksrt-submenu-type-<?php echo esc_attr((string) $item_id); ?>">
<?php esc_html_e('高级子菜单类型', 'aksrt'); ?><br>
<select id="edit-menu-item-aksrt-submenu-type-<?php echo esc_attr((string) $item_id); ?>"
name="aksrt_menu_options[<?php echo esc_attr((string) $item_id); ?>][submenu_type]">
<option value="" <?php selected($submenu_type, ''); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
<option value="graphic_card" <?php selected($submenu_type, 'graphic_card'); ?>><?php esc_html_e('图文卡片', 'aksrt'); ?></option>
<option value="multi_column_links" <?php selected($submenu_type, 'multi_column_links'); ?>><?php esc_html_e('多列链接', 'aksrt'); ?></option>
</select>
</label>
</p>
<p class="field-aksrt-graphic description description-wide">
<label for="edit-menu-item-aksrt-graphic-<?php echo esc_attr((string) $item_id); ?>">
<?php esc_html_e('图文卡片 JSON(type=graphic_card)', 'aksrt'); ?><br>
<textarea rows="8" class="widefat code" id="edit-menu-item-aksrt-graphic-<?php echo esc_attr((string) $item_id); ?>"
name="aksrt_menu_options[<?php echo esc_attr((string) $item_id); ?>][graphic_card_opts_json]"><?php echo esc_textarea((string) $graphic_json); ?></textarea>
</label>
</p>
<p class="field-aksrt-multi description description-wide">
<label for="edit-menu-item-aksrt-multi-<?php echo esc_attr((string) $item_id); ?>">
<?php esc_html_e('多列链接 JSON(type=multi_column_links)', 'aksrt'); ?><br>
<textarea rows="8" class="widefat code" id="edit-menu-item-aksrt-multi-<?php echo esc_attr((string) $item_id); ?>"
name="aksrt_menu_options[<?php echo esc_attr((string) $item_id); ?>][multi_column_links_opts_json]"><?php echo esc_textarea((string) $multi_json); ?></textarea>
</label>
</p>
<?php
}
/**
* @param int $menu_id
* @param int $menu_item_db_id
*/
public static function save($menu_id, $menu_item_db_id): void {
$menu_item_db_id = (int) $menu_item_db_id;
// phpcs:ignore WordPress.Security.NonceVerification.Missing
if (empty($_POST['aksrt_menu_options'][ $menu_item_db_id ]) || !is_array($_POST['aksrt_menu_options'][ $menu_item_db_id ])) {
return;
}
// phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$raw = wp_unslash($_POST['aksrt_menu_options'][ $menu_item_db_id ]);
$clean = [
'icon' => sanitize_text_field((string) ($raw['icon'] ?? '')),
'badge' => sanitize_text_field((string) ($raw['badge'] ?? '')),
'badge_class' => sanitize_html_class((string) ($raw['badge_class'] ?? '')),
'submenu_s' => !empty($raw['submenu_s']),
'submenu_type' => sanitize_key((string) ($raw['submenu_type'] ?? '')),
];
$graphic = self::decode_json_field((string) ($raw['graphic_card_opts_json'] ?? ''));
if (is_array($graphic)) {
$clean['graphic_card_opts'] = self::sanitize_graphic_opts($graphic);
}
$multi = self::decode_json_field((string) ($raw['multi_column_links_opts_json'] ?? ''));
if (is_array($multi)) {
$clean['multi_column_links_opts'] = self::sanitize_multi_opts($multi);
}
update_post_meta($menu_item_db_id, 'aksrt_menu_options', $clean);
}
/**
* @return array<string,mixed>|null
*/
private static function decode_json_field(string $json): ?array {
$json = trim($json);
if ($json === '') {
return null;
}
$data = json_decode($json, true);
return is_array($data) ? $data : null;
}
/**
* @param array<string,mixed> $opts
* @return array<string,mixed>
*/
private static function sanitize_graphic_opts(array $opts): array {
$items_in = is_array($opts['items'] ?? null) ? $opts['items'] : [];
$items = [];
foreach ($items_in as $item) {
if (!is_array($item)) {
continue;
}
$items[] = [
'img' => esc_url_raw((string) ($item['img'] ?? '')),
'title' => sanitize_text_field((string) ($item['title'] ?? '')),
'badge' => sanitize_text_field((string) ($item['badge'] ?? '')),
'badge_class' => sanitize_html_class((string) ($item['badge_class'] ?? '')),
'link' => [
'url' => esc_url_raw((string) ($item['link']['url'] ?? '')),
'text' => sanitize_text_field((string) ($item['link']['text'] ?? '')),
'target' => sanitize_text_field((string) ($item['link']['target'] ?? '')),
],
];
}
return [
'arrangement' => sanitize_key((string) ($opts['arrangement'] ?? '')),
'align' => sanitize_key((string) ($opts['align'] ?? '')),
'size' => sanitize_key((string) ($opts['size'] ?? '')),
'img_scale' => max(0, min(200, (int) ($opts['img_scale'] ?? 100))),
'items' => $items,
];
}
/**
* @param array<string,mixed> $opts
* @return array<string,mixed>
*/
private static function sanitize_multi_opts(array $opts): array {
$columns_in = is_array($opts['columns'] ?? null) ? $opts['columns'] : [];
$columns = [];
foreach ($columns_in as $col) {
if (!is_array($col)) {
continue;
}
$items = [];
if (!empty($col['items']) && is_array($col['items'])) {
foreach ($col['items'] as $item) {
if (!is_array($item)) {
continue;
}
$items[] = [
'title' => sanitize_text_field((string) ($item['title'] ?? '')),
'icon' => sanitize_text_field((string) ($item['icon'] ?? '')),
'img_icon' => esc_url_raw((string) ($item['img_icon'] ?? '')),
'link' => [
'url' => esc_url_raw((string) ($item['link']['url'] ?? '')),
'text' => sanitize_text_field((string) ($item['link']['text'] ?? '')),
'target' => sanitize_text_field((string) ($item['link']['target'] ?? '')),
],
];
}
}
$columns[] = [
'title' => sanitize_text_field((string) ($col['title'] ?? '')),
'icon' => sanitize_text_field((string) ($col['icon'] ?? '')),
'img_icon' => esc_url_raw((string) ($col['img_icon'] ?? '')),
'link' => [
'url' => esc_url_raw((string) ($col['link']['url'] ?? '')),
'text' => sanitize_text_field((string) ($col['link']['text'] ?? '')),
'target' => sanitize_text_field((string) ($col['link']['target'] ?? '')),
],
'items' => $items,
];
}
return ['columns' => $columns];
}
private static function sample_graphic_json(): string {
$sample = [
'arrangement' => '',
'size' => 'md',
'img_scale' => 100,
'items' => [
[
'img' => '',
'title' => '卡片标题',
'badge' => '',
'badge_class' => 'jb-red',
'link' => ['url' => home_url('/'), 'text' => '', 'target' => ''],
],
],
];
return (string) wp_json_encode($sample, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
private static function sample_multi_json(): string {
$sample = [
'columns' => [
[
'title' => '栏目一',
'icon' => 'fa fas fa-folder',
'link' => ['url' => '', 'text' => '', 'target' => ''],
'items' => [
['title' => '链接 A', 'icon' => '', 'link' => ['url' => home_url('/'), 'text' => '', 'target' => '']],
],
],
],
];
return (string) wp_json_encode($sample, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
}