The post editor's download row only exposed address / button text / remark, leaving copy_key, copy_val and class as hidden inputs with no UI, so the reference copy area, custom button icon, text and color were unreachable. The submission popup already emitted those keys, which made the two write paths diverge from anything the editor could produce. Expose the full reference field set (地址/备注/复制名称/复制内容/图标/文案/ 颜色) as an accordion card with drag reordering, add the one genuinely new `icon` key through aksrt_sanitize_icon_value(), and teach both frontend download renderers to consume the fields they previously ignored. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
305 lines
12 KiB
PHP
305 lines
12 KiB
PHP
<?php
|
||
/**
|
||
* @package AKSRT
|
||
*/
|
||
|
||
namespace AKSRT\Core;
|
||
|
||
defined('ABSPATH') || exit;
|
||
|
||
class Setup {
|
||
use SetupPart2;
|
||
|
||
|
||
private static $instance = null;
|
||
|
||
private function __construct() {}
|
||
|
||
public static function get_instance(): self {
|
||
if (null === self::$instance) {
|
||
self::$instance = new self();
|
||
}
|
||
return self::$instance;
|
||
}
|
||
|
||
public function init(): void {
|
||
add_action('after_setup_theme', [$this, 'load_textdomain'], 1);
|
||
add_action('after_setup_theme', [$this, 'setup_theme']);
|
||
add_filter('dynamic_sidebar_params', [$this, 'filter_sidebar_widget_params']);
|
||
add_action('init', [$this, 'register_nav_menus'], 0);
|
||
add_action('init', [$this, 'register_post_types'], 1);
|
||
add_action('init', [$this, 'register_meta_fields'], 5);
|
||
// 文章浏览量统计(单次访问)
|
||
add_action('wp', 'aksrt_set_post_views');
|
||
// 隐藏默认WordPress管理菜单
|
||
add_action('admin_menu', [$this, 'hide_default_menus'], 999);
|
||
}
|
||
|
||
public function load_textdomain(): void {
|
||
load_theme_textdomain('aksrt', AKSRT_DIR . '/languages');
|
||
}
|
||
|
||
public function setup_theme(): void {
|
||
|
||
// HTML5 支持
|
||
add_theme_support('html5', [
|
||
'search-form', 'comment-form', 'comment-list',
|
||
'gallery', 'caption', 'style', 'script',
|
||
'navigation-widgets',
|
||
]);
|
||
|
||
add_theme_support('title-tag');
|
||
add_theme_support('post-thumbnails');
|
||
add_theme_support('custom-logo', [
|
||
'height' => 120,
|
||
'width' => 300,
|
||
'flex-height' => true,
|
||
'flex-width' => true,
|
||
'header-text' => true,
|
||
]);
|
||
add_theme_support('customize-selective-refresh-widgets');
|
||
add_theme_support('align-wide');
|
||
add_theme_support('wp-block-styles');
|
||
add_theme_support('responsive-embeds');
|
||
|
||
// 文章格式
|
||
add_theme_support('post-formats', ['aside', 'image', 'video', 'quote', 'link', 'gallery']);
|
||
|
||
// 图片尺寸
|
||
add_image_size('aksrt-cover', 1200, 675, true);
|
||
add_image_size('aksrt-card', 400, 260, true);
|
||
add_image_size('aksrt-thumb', 150, 150, true);
|
||
|
||
// 内容宽度(用于区块编辑器)
|
||
if (!isset($GLOBALS['content_width'])) {
|
||
$GLOBALS['content_width'] = 900;
|
||
}
|
||
}
|
||
|
||
public function register_nav_menus(): void {
|
||
register_nav_menus([
|
||
'primary' => __('主导航', 'aksrt'),
|
||
'mobile' => __('移动端菜单', 'aksrt'),
|
||
'footer' => __('页脚菜单', 'aksrt'),
|
||
'social' => __('社交链接', 'aksrt'),
|
||
'header_actions' => __('顶部右侧入口', 'aksrt'),
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 注册自定义文章类型
|
||
*
|
||
* 资源不是 CPT:元数据挂在普通 post 上,
|
||
* 通过 _aksrt_is_resource meta 标记是否为资源。
|
||
*/
|
||
public function register_post_types(): void {
|
||
// 项目展示
|
||
register_post_type('project', [
|
||
'labels' => [
|
||
'name' => __('项目展示', 'aksrt'),
|
||
'singular_name' => __('项目', 'aksrt'),
|
||
'add_new' => __('新建项目', 'aksrt'),
|
||
'add_new_item' => __('新建项目', 'aksrt'),
|
||
'edit_item' => __('编辑项目', 'aksrt'),
|
||
'new_item' => __('新项目', 'aksrt'),
|
||
'view_item' => __('查看项目', 'aksrt'),
|
||
'search_items' => __('搜索项目', 'aksrt'),
|
||
'not_found' => __('未找到项目', 'aksrt'),
|
||
'not_found_in_trash' => __('回收站中无项目', 'aksrt'),
|
||
'menu_name' => __('项目展示', 'aksrt'),
|
||
],
|
||
'public' => true,
|
||
'publicly_queryable' => true,
|
||
'show_ui' => true,
|
||
'show_in_menu' => false,
|
||
'show_in_nav_menus' => false,
|
||
'has_archive' => false,
|
||
'hierarchical' => false,
|
||
'menu_position' => 25,
|
||
'menu_icon' => 'dashicons-portfolio',
|
||
'supports' => ['title', 'editor', 'excerpt', 'thumbnail', 'author', 'revisions'],
|
||
'rewrite' => ['slug' => 'project'],
|
||
'capability_type' => 'post',
|
||
'map_meta_cap' => true,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 注册资源 post meta + 分类 term meta(aksrt_flow)
|
||
*
|
||
* 全部 show_in_rest,便于 Block Editor + Vue SPA 读写。
|
||
*
|
||
* v3.4: 资源元数据全部挂在 post 上,配合 _aksrt_is_resource 开关使用。
|
||
*/
|
||
public function register_meta_fields(): void {
|
||
$post_edit_auth = static function () {
|
||
return current_user_can('edit_posts');
|
||
};
|
||
|
||
// Core switch: mark a post as resource.
|
||
register_post_meta('post', DomainModel::RESOURCE_MARKER_KEY, [
|
||
'type' => 'boolean',
|
||
'description' => 'Whether this post is a resource post.',
|
||
'single' => true,
|
||
'default' => false,
|
||
'show_in_rest' => true,
|
||
'sanitize_callback' => static function ($v) { return (bool) $v; },
|
||
'auth_callback' => $post_edit_auth,
|
||
]);
|
||
|
||
register_post_meta('post', DomainModel::PAY_META_KEY, [
|
||
'type' => 'object',
|
||
'description' => 'Canonical paid content payload.',
|
||
'single' => true,
|
||
'default' => DomainModel::default_pay_meta(),
|
||
'show_in_rest' => [
|
||
'schema' => [
|
||
'type' => 'object',
|
||
'properties' => [
|
||
'pay_type' => [
|
||
'type' => 'string',
|
||
'enum' => array_keys(DomainModel::pay_type_labels()),
|
||
],
|
||
'pay_modo' => ['type' => 'string', 'enum' => ['0', 'points']],
|
||
'price' => ['type' => 'string', 'pattern' => '^(0|[1-9]\\d*)(?:\\.\\d{1,2})?$'],
|
||
'points_price' => ['type' => 'integer'],
|
||
'original_price' => ['type' => 'string', 'pattern' => '^(0|[1-9]\\d*)(?:\\.\\d{1,2})?$'],
|
||
'vip1_price' => ['type' => 'string', 'pattern' => '^(0|[1-9]\\d*)(?:\\.\\d{1,2})?$'],
|
||
'vip2_price' => ['type' => 'string', 'pattern' => '^(0|[1-9]\\d*)(?:\\.\\d{1,2})?$'],
|
||
'vip1_points' => ['type' => 'integer'],
|
||
'vip2_points' => ['type' => 'integer'],
|
||
'pay_limit' => ['type' => 'integer', 'enum' => [0, 1, 2]],
|
||
'version' => ['type' => 'string'],
|
||
'size' => ['type' => 'string'],
|
||
'license' => ['type' => 'string'],
|
||
'compat' => ['type' => 'string'],
|
||
'demo_url' => ['type' => 'string', 'format' => 'uri'],
|
||
'video_url' => ['type' => 'string', 'format' => 'uri'],
|
||
'downloads' => [
|
||
'type' => 'array',
|
||
'items' => [
|
||
'type' => 'object',
|
||
'properties' => [
|
||
'label' => ['type' => 'string'],
|
||
'url' => ['type' => 'string', 'format' => 'uri'],
|
||
'type' => ['type' => 'string'],
|
||
'extract_code' => ['type' => 'string'],
|
||
'copy_key' => ['type' => 'string'],
|
||
'copy_val' => ['type' => 'string'],
|
||
'icon' => ['type' => 'string'],
|
||
'class' => ['type' => 'string'],
|
||
],
|
||
],
|
||
],
|
||
'attributes' => [
|
||
'type' => 'array',
|
||
'items' => [
|
||
'type' => 'object',
|
||
'properties' => [
|
||
'key' => ['type' => 'string'],
|
||
'value' => ['type' => 'string'],
|
||
],
|
||
],
|
||
],
|
||
'gallery' => [
|
||
'type' => 'array',
|
||
'items' => ['type' => 'string', 'format' => 'uri'],
|
||
],
|
||
'require_login' => ['type' => 'boolean'],
|
||
'require_captcha' => ['type' => 'boolean'],
|
||
],
|
||
],
|
||
],
|
||
'sanitize_callback' => [DomainModel::class, 'sanitize_pay_meta'],
|
||
'auth_callback' => $post_edit_auth,
|
||
]);
|
||
|
||
register_post_meta('post', DomainModel::PAY_TYPE_KEY, [
|
||
'type' => 'string',
|
||
'description' => 'Query-friendly paid content type.',
|
||
'single' => true,
|
||
'default' => DomainModel::PAY_TYPE_NO,
|
||
'show_in_rest' => true,
|
||
'sanitize_callback' => [DomainModel::class, 'normalize_pay_type'],
|
||
'auth_callback' => $post_edit_auth,
|
||
]);
|
||
|
||
register_post_meta('post', DomainModel::PAY_PRICE_KEY, [
|
||
'type' => 'string',
|
||
'description' => 'Query-friendly paid content price.',
|
||
'single' => true,
|
||
'default' => '0.00',
|
||
'show_in_rest' => true,
|
||
'sanitize_callback' => static function ($value): string {
|
||
if (is_int($value)) {
|
||
return (string) max(0, $value);
|
||
}
|
||
if (!is_string($value)) {
|
||
return '0.00';
|
||
}
|
||
try {
|
||
return \AKSRT\Ledger\AssetAmount::balance($value)->decimal();
|
||
} catch (\InvalidArgumentException $e) {
|
||
return '0.00';
|
||
}
|
||
},
|
||
'auth_callback' => $post_edit_auth,
|
||
]);
|
||
|
||
foreach ([
|
||
DomainModel::RESOURCE_DOWNLOAD_COUNT_KEY => 'Resource download count',
|
||
DomainModel::RESOURCE_VIEW_COUNT_KEY => 'Resource view count',
|
||
DomainModel::POST_VIEW_COUNT_KEY => 'Generic post view count',
|
||
] as $meta_key => $description) {
|
||
register_post_meta('post', $meta_key, [
|
||
'type' => 'integer',
|
||
'description' => $description,
|
||
'single' => true,
|
||
'default' => 0,
|
||
'show_in_rest' => true,
|
||
'sanitize_callback' => 'absint',
|
||
'auth_callback' => $post_edit_auth,
|
||
]);
|
||
}
|
||
|
||
register_term_meta('category', 'aksrt_flow', [
|
||
'type' => 'string',
|
||
'description' => 'Category flow mode: inherit/blog/cms',
|
||
'single' => true,
|
||
'default' => 'inherit',
|
||
'show_in_rest' => true,
|
||
'sanitize_callback' => function ($v) {
|
||
$v = is_string($v) ? $v : 'inherit';
|
||
return in_array($v, ['inherit', 'blog', 'cms'], true) ? $v : 'inherit';
|
||
},
|
||
'auth_callback' => function () {
|
||
return current_user_can('manage_categories');
|
||
},
|
||
]);
|
||
}
|
||
public static function sanitize_downloads_meta($value): array {
|
||
return DomainModel::sanitize_downloads($value);
|
||
}
|
||
|
||
/**
|
||
* 为 Customizer 小工具实时预览补齐编辑态包裹。
|
||
*
|
||
* @param array $params Dynamic sidebar params.
|
||
* @return array
|
||
*/
|
||
public function filter_sidebar_widget_params(array $params): array {
|
||
if (!is_customize_preview() || empty($params[0]['before_widget']) || empty($params[0]['after_widget'])) {
|
||
return $params;
|
||
}
|
||
|
||
$params[0]['before_widget'] = '<div class="customize-preview-widget-box"><div class="customize-preview-widget-edit"></div>' . $params[0]['before_widget'];
|
||
$params[0]['after_widget'] = $params[0]['after_widget'] . '</div>';
|
||
|
||
return $params;
|
||
}
|
||
|
||
/**
|
||
* 注册侧边栏
|
||
*/
|
||
}
|