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>
329 lines
18 KiB
PHP
329 lines
18 KiB
PHP
<?php
|
||
|
||
namespace Jio\WidgetSystem;
|
||
|
||
defined('ABSPATH') || exit;
|
||
|
||
/**
|
||
* Server-rendered, schema-driven widget form.
|
||
*/
|
||
final class FormRenderer {
|
||
public static function render(SchemaWidget $widget, array $fields, array $instance): void {
|
||
echo '<div class="jio-widget-form" data-jio-widget-form="1">';
|
||
foreach ($fields as $field) {
|
||
self::field($widget, $field, $instance[(string) ($field['id'] ?? '')] ?? ($field['default'] ?? ''), '');
|
||
}
|
||
echo '</div>';
|
||
}
|
||
|
||
private static function field(SchemaWidget $widget, array $field, $value, string $parent): void {
|
||
$type = (string) ($field['type'] ?? 'text');
|
||
$id = (string) ($field['id'] ?? '');
|
||
if (in_array($type, ['content', 'subheading', 'submessage'], true)) {
|
||
self::display_field($field, $type);
|
||
return;
|
||
}
|
||
if ($id === '') {
|
||
return;
|
||
}
|
||
|
||
$key = $parent === '' ? $id : $parent . '[' . $id . ']';
|
||
$field_id = $widget->get_field_id(str_replace(['[', ']'], ['-', ''], $key));
|
||
$field_name = $widget->get_field_name($key);
|
||
$dependency = !empty($field['dependency']) ? wp_json_encode($field['dependency']) : '';
|
||
|
||
echo '<div class="' . esc_attr(self::field_class($field, $type)) . '" data-field-id="' . esc_attr($id) . '"';
|
||
if ($dependency !== '') {
|
||
echo ' data-dependency="' . esc_attr($dependency) . '"';
|
||
}
|
||
echo '>';
|
||
if (!empty($field['label'])) {
|
||
echo '<label class="jio-widget-field__label" for="' . esc_attr($field_id) . '">' . esc_html((string) $field['label']) . '</label>';
|
||
}
|
||
if (!empty($field['subtitle'])) {
|
||
echo '<p class="jio-widget-field__subtitle">' . esc_html((string) $field['subtitle']) . '</p>';
|
||
}
|
||
if (!empty($field['description'])) {
|
||
echo '<p class="description">' . wp_kses_post((string) $field['description']) . '</p>';
|
||
}
|
||
|
||
switch ($type) {
|
||
case 'switcher':
|
||
self::switcher($field_id, $field_name, (bool) $value);
|
||
break;
|
||
case 'radio':
|
||
case 'button_set':
|
||
case 'palette':
|
||
case 'image_select':
|
||
self::radios($field_id, $field_name, (array) ($field['options'] ?? []), (string) $value, $type);
|
||
break;
|
||
case 'select':
|
||
self::select($field_id, $field_name, $field, $value);
|
||
break;
|
||
case 'checkbox':
|
||
self::checkboxes($field_id, $field_name, $field, $value);
|
||
break;
|
||
case 'terms':
|
||
self::terms($field_id, $field_name, $field, $value);
|
||
break;
|
||
case 'number':
|
||
case 'spinner':
|
||
case 'slider':
|
||
self::number($field_id, $field_name, $field, $value, $type);
|
||
break;
|
||
case 'textarea':
|
||
$rows = max(1, (int) ($field['attributes']['rows'] ?? 4));
|
||
echo '<textarea class="widefat" rows="' . $rows . '" id="' . esc_attr($field_id) . '" name="' . esc_attr($field_name) . '">' . esc_textarea((string) $value) . '</textarea>';
|
||
break;
|
||
case 'upload':
|
||
self::upload($field_id, $field_name, (string) $value, (string) ($field['library'] ?? ''));
|
||
break;
|
||
case 'color':
|
||
echo '<input class="widefat jio-widget-color" type="text" id="' . esc_attr($field_id) . '" name="' . esc_attr($field_name) . '" value="' . esc_attr((string) $value) . '">';
|
||
break;
|
||
case 'icon':
|
||
echo jio_icon_picker_field($field_name, $field_id, (string) $value);
|
||
break;
|
||
case 'link':
|
||
self::link($widget, $key, is_array($value) ? $value : []);
|
||
break;
|
||
case 'spacing':
|
||
self::spacing($widget, $key, is_array($value) ? $value : [], (array) ($field['edges'] ?? []));
|
||
break;
|
||
case 'background':
|
||
self::background($widget, $key, is_array($value) ? $value : []);
|
||
break;
|
||
case 'group':
|
||
self::group($widget, $field, is_array($value) ? $value : [], $key);
|
||
break;
|
||
case 'fieldset':
|
||
case 'accordion':
|
||
self::nested($widget, $field, is_array($value) ? $value : [], $key);
|
||
break;
|
||
case 'text':
|
||
default:
|
||
echo '<input class="widefat" type="text" id="' . esc_attr($field_id) . '" name="' . esc_attr($field_name) . '" value="' . esc_attr((string) $value) . '">';
|
||
}
|
||
echo '</div>';
|
||
}
|
||
|
||
private static function display_field(array $field, string $type): void {
|
||
$content = (string) ($field['content'] ?? $field['label'] ?? '');
|
||
$class = self::field_class($field, $type);
|
||
if ($type === 'submessage') {
|
||
$style = in_array($field['style'] ?? '', ['success', 'info', 'warning', 'danger'], true) ? $field['style'] : 'normal';
|
||
$class .= ' jio-widget-field--submessage-' . $style;
|
||
}
|
||
echo '<div class="' . esc_attr($class) . '">' . wp_kses_post($content) . '</div>';
|
||
}
|
||
|
||
/** Reference CSF row classes: type modifier plus schema `class` (e.g. compact) and `inline`. */
|
||
private static function field_class(array $field, string $type): string {
|
||
$classes = ['jio-widget-field', 'jio-widget-field--' . $type];
|
||
foreach (preg_split('/\s+/', trim((string) ($field['class'] ?? ''))) ?: [] as $extra) {
|
||
if ($extra !== '') {
|
||
$classes[] = 'is-' . sanitize_html_class($extra);
|
||
}
|
||
}
|
||
if (!empty($field['inline'])) {
|
||
$classes[] = 'is-inline';
|
||
}
|
||
return implode(' ', $classes);
|
||
}
|
||
|
||
private static function switcher(string $id, string $name, bool $value): void {
|
||
echo '<input type="hidden" name="' . esc_attr($name) . '" value="0">';
|
||
echo '<label class="jio-widget-switch"><input type="checkbox" id="' . esc_attr($id) . '" name="' . esc_attr($name) . '" value="1" ' . checked($value, true, false) . '><span aria-hidden="true"></span></label>';
|
||
}
|
||
|
||
private static function radios(string $id, string $name, array $options, string $value, string $type): void {
|
||
echo '<div class="jio-widget-choices jio-widget-choices--' . esc_attr($type) . '">';
|
||
foreach ($options as $key => $label) {
|
||
$option_id = $id . '-' . sanitize_html_class((string) $key);
|
||
$image = is_array($label) ? (string) ($label['image'] ?? $label['url'] ?? '') : (string) $label;
|
||
$text = is_array($label) ? (string) ($label['label'] ?? $label['title'] ?? $key) : (string) $label;
|
||
if ($type === 'image_select' && $text === $image) $text = (string) $key;
|
||
if ($type === 'image_select' && filter_var($image, FILTER_VALIDATE_URL)) {
|
||
$body = '<img src="' . esc_url($image) . '" alt="' . esc_attr($text) . '"><small>' . esc_html($text) . '</small>';
|
||
} elseif ($type === 'palette') {
|
||
$body = '<span class="jio-widget-palette-choice"><i aria-hidden="true" style="background:' . esc_attr(self::palette_color((string) $key)) . '"></i><small>' . esc_html($text) . '</small></span>';
|
||
} else {
|
||
$body = esc_html($text);
|
||
}
|
||
echo '<label for="' . esc_attr($option_id) . '"><input type="radio" id="' . esc_attr($option_id) . '" name="' . esc_attr($name) . '" value="' . esc_attr((string) $key) . '" ' . checked($value, (string) $key, false) . '><span>' . $body . '</span></label>';
|
||
}
|
||
echo '</div>';
|
||
}
|
||
|
||
private static function palette_color(string $key): string {
|
||
$colors = [
|
||
'' => '#fff', 'c-red' => 'rgba(255,84,115,.4)', 'c-red-2' => 'rgba(194,41,46,.4)',
|
||
'c-yellow' => 'rgba(255,111,6,.4)', 'c-yellow-2' => 'rgba(179,103,8,.4)', 'c-cyan' => 'rgba(8,196,193,.4)',
|
||
'c-blue' => 'rgba(41,151,247,.4)', 'c-blue-2' => 'rgba(77,130,249,.4)', 'c-green' => 'rgba(18,185,40,.4)',
|
||
'c-green-2' => 'rgba(72,135,24,.4)', 'c-purple' => 'rgba(213,72,245,.4)', 'c-purple-2' => 'rgba(154,72,245,.4)',
|
||
'b-red' => '#f74b3d', 'b-yellow' => '#f3920a', 'b-cyan' => '#08c4c1', 'b-blue' => '#0a8cf3',
|
||
'b-green' => '#1fd05a', 'b-purple' => '#c133f5', 'b-black' => '#121517',
|
||
'jb-red' => 'linear-gradient(135deg,#ffbeb4 10%,#f61a1a 100%)', 'jb-pink' => 'linear-gradient(135deg,#ff5e7f 30%,#ff967e 100%)',
|
||
'jb-yellow' => 'linear-gradient(135deg,#ffd6b2 10%,#ff651c 100%)', 'jb-cyan' => 'linear-gradient(140deg,#039ab3 10%,#58dbcf 90%)',
|
||
'jb-blue' => 'linear-gradient(135deg,#b6e6ff 10%,#198aff 100%)', 'jb-green' => 'linear-gradient(135deg,#ccffcd 10%,#52bb51 100%)',
|
||
'jb-purple' => 'linear-gradient(135deg,#fec2ff 10%,#d000de 100%)', 'jb-vip1' => 'linear-gradient(25deg,#eab869 10%,#fbecd4 60%,#ffe0ae 100%)',
|
||
'jb-vip2' => 'linear-gradient(317deg,#4d4c4c 30%,#878787 70%,#5f5c5c 100%)',
|
||
'focus-color' => 'linear-gradient(135deg,#74d5f6,#9866ff)', 'key-color' => '#333', 'muted-color' => '#777',
|
||
'muted-2-color' => '#999', 'muted-3-color' => '#b1b1b1',
|
||
];
|
||
return $colors[$key] ?? '#dcdcde';
|
||
}
|
||
|
||
private static function select(string $id, string $name, array $field, $value): void {
|
||
$multiple = !empty($field['multiple']);
|
||
echo '<select class="widefat" id="' . esc_attr($id) . '" name="' . esc_attr($name) . ($multiple ? '[]' : '') . '"' . ($multiple ? ' multiple' : '') . '>';
|
||
$values = $multiple ? array_map('strval', (array) $value) : [(string) $value];
|
||
foreach ((array) ($field['options'] ?? []) as $key => $label) {
|
||
echo '<option value="' . esc_attr((string) $key) . '" ' . selected(in_array((string) $key, $values, true), true, false) . '>' . esc_html((string) $label) . '</option>';
|
||
}
|
||
echo '</select>';
|
||
}
|
||
|
||
private static function checkboxes(string $id, string $name, array $field, $value): void {
|
||
$options = (array) ($field['options'] ?? []);
|
||
if (!$options) {
|
||
self::switcher($id, $name, (bool) $value);
|
||
return;
|
||
}
|
||
$values = array_map('strval', (array) $value);
|
||
foreach ($options as $key => $label) {
|
||
echo '<label><input type="checkbox" name="' . esc_attr($name) . '[]" value="' . esc_attr((string) $key) . '" ' . checked(in_array((string) $key, $values, true), true, false) . '> ' . esc_html((string) $label) . '</label> ';
|
||
}
|
||
}
|
||
|
||
private static function number(string $id, string $name, array $field, $value, string $type): void {
|
||
$min = isset($field['min']) ? ' min="' . esc_attr((string) $field['min']) . '"' : '';
|
||
$max = isset($field['max']) ? ' max="' . esc_attr((string) $field['max']) . '"' : '';
|
||
$step = ' step="' . esc_attr((string) ($field['step'] ?? 1)) . '"';
|
||
$input_type = $type === 'slider' ? 'range' : 'number';
|
||
$unit = trim((string) ($field['unit'] ?? ''));
|
||
echo '<div class="jio-widget-number"><input class="widefat" type="' . esc_attr($input_type) . '" id="' . esc_attr($id) . '" name="' . esc_attr($name) . '" value="' . esc_attr((string) $value) . '"' . $min . $max . $step . '><output>' . esc_html((string) $value) . '</output>' . ($unit !== '' ? '<span class="jio-widget-number__unit">' . esc_html($unit) . '</span>' : '') . '</div>';
|
||
}
|
||
|
||
private static function terms(string $id, string $name, array $field, $value): void {
|
||
$taxonomy = (string) ($field['taxonomy'] ?? 'category');
|
||
$terms = get_terms(['taxonomy' => $taxonomy, 'hide_empty' => false, 'number' => 500, 'orderby' => 'name']);
|
||
if (is_wp_error($terms)) {
|
||
$terms = [];
|
||
}
|
||
$selected = array_values(array_filter(array_map('absint', (array) $value)));
|
||
$by_id = [];
|
||
foreach ($terms as $term) {
|
||
$by_id[(int) $term->term_id] = $term;
|
||
}
|
||
echo '<div class="jio-widget-terms" data-jio-terms="' . esc_attr($taxonomy) . '">';
|
||
echo '<input type="hidden" class="jio-widget-terms__value" id="' . esc_attr($id) . '" name="' . esc_attr($name) . '" value="' . esc_attr(implode(',', $selected)) . '">';
|
||
echo '<div class="jio-widget-terms__selected">';
|
||
foreach ($selected as $term_id) {
|
||
if (!isset($by_id[$term_id])) {
|
||
continue;
|
||
}
|
||
echo '<span class="jio-widget-terms__chip" data-term-id="' . esc_attr((string) $term_id) . '"><span class="dashicons dashicons-move" aria-hidden="true"></span>' . esc_html((string) $by_id[$term_id]->name) . '<button type="button" class="button-link-delete jio-widget-terms__remove" aria-label="' . esc_attr__('移除', 'jio') . '">×</button></span>';
|
||
}
|
||
echo '</div>';
|
||
echo '<input type="search" class="widefat jio-widget-terms__search" placeholder="' . esc_attr__('输入关键词筛选', 'jio') . '" aria-label="' . esc_attr__('筛选可选分类', 'jio') . '">';
|
||
echo '<div class="jio-widget-terms__options">';
|
||
foreach ($terms as $term) {
|
||
$term_id = (int) $term->term_id;
|
||
echo '<label class="jio-widget-terms__option" data-term-name="' . esc_attr((string) $term->name) . '"><input type="checkbox" value="' . esc_attr((string) $term_id) . '" ' . checked(in_array($term_id, $selected, true), true, false) . '> ' . esc_html((string) $term->name) . '</label>';
|
||
}
|
||
echo '</div></div>';
|
||
}
|
||
|
||
private static function upload(string $id, string $name, string $value, string $library = ''): void {
|
||
$library = in_array($library, ['image', 'video'], true) ? $library : '';
|
||
echo '<div class="jio-widget-upload" data-media-library="' . esc_attr($library) . '">';
|
||
// 参考主题:视频上传字段不展示预览图(视频 URL 作为 img src 会得到破图)。
|
||
if ($value !== '' && $library !== 'video') {
|
||
echo '<img src="' . esc_url($value) . '" alt="">';
|
||
}
|
||
echo '<input class="widefat" type="url" id="' . esc_attr($id) . '" name="' . esc_attr($name) . '" value="' . esc_attr($value) . '">';
|
||
echo '<button type="button" class="button jio-widget-upload__choose">' . esc_html($library === 'video' ? __('选择视频', 'jio') : __('选择图片', 'jio')) . '</button> <button type="button" class="button-link-delete jio-widget-upload__remove">' . esc_html__('移除', 'jio') . '</button></div>';
|
||
}
|
||
|
||
private static function link(SchemaWidget $widget, string $key, array $value): void {
|
||
echo '<div class="jio-widget-link">';
|
||
self::plain_input($widget, $key . '[url]', (string) ($value['url'] ?? ''), 'url', 'URL');
|
||
self::plain_input($widget, $key . '[text]', (string) ($value['text'] ?? ''), 'text', '链接文字');
|
||
$name = $widget->get_field_name($key . '[target]');
|
||
echo '<label><input type="checkbox" name="' . esc_attr($name) . '" value="_blank" ' . checked(($value['target'] ?? '') === '_blank', true, false) . '> ' . esc_html__('新窗口打开', 'jio') . '</label></div>';
|
||
}
|
||
|
||
private static function spacing(SchemaWidget $widget, string $key, array $value, array $edges = []): void {
|
||
$all = ['top' => 'dashicons-arrow-up-alt', 'right' => 'dashicons-arrow-right-alt', 'bottom' => 'dashicons-arrow-down-alt', 'left' => 'dashicons-arrow-left-alt'];
|
||
$visible = $edges ? array_intersect_key($all, array_flip($edges)) : $all;
|
||
echo '<div class="jio-widget-spacing jio-widget-spacing--' . count($visible) . '">';
|
||
foreach ($visible as $edge => $icon) {
|
||
echo '<label><span class="dashicons ' . esc_attr($icon) . '" aria-hidden="true"></span>';
|
||
self::plain_input($widget, $key . '[' . $edge . ']', (string) ($value[$edge] ?? ''), 'number', 'px');
|
||
echo '</label>';
|
||
}
|
||
echo '</div>';
|
||
}
|
||
|
||
private static function background(SchemaWidget $widget, string $key, array $value): void {
|
||
echo '<div class="jio-widget-background">';
|
||
self::plain_input($widget, $key . '[color]', (string) ($value['color'] ?? ''), 'text', '背景颜色', 'widefat jio-widget-color');
|
||
self::plain_input($widget, $key . '[image]', (string) ($value['image'] ?? ''), 'url', '背景图片 URL');
|
||
foreach ([
|
||
'position' => ['center center' => '居中', 'top center' => '顶部', 'bottom center' => '底部', 'center left' => '左侧', 'center right' => '右侧'],
|
||
'repeat' => ['no-repeat' => '不重复', 'repeat' => '平铺', 'repeat-x' => '水平平铺', 'repeat-y' => '垂直平铺'],
|
||
'attachment' => ['scroll' => '滚动', 'fixed' => '固定'],
|
||
'size' => ['cover' => '覆盖', 'contain' => '包含', 'auto' => '原始尺寸'],
|
||
] as $part => $options) {
|
||
$name = $widget->get_field_name($key . '[' . $part . ']');
|
||
echo '<select name="' . esc_attr($name) . '">';
|
||
foreach ($options as $option => $label) {
|
||
echo '<option value="' . esc_attr($option) . '" ' . selected((string) ($value[$part] ?? ''), $option, false) . '>' . esc_html($label) . '</option>';
|
||
}
|
||
echo '</select>';
|
||
}
|
||
echo '</div>';
|
||
}
|
||
|
||
private static function group(SchemaWidget $widget, array $field, array $rows, string $key): void {
|
||
$token = '__index_' . substr(md5($key), 0, 8) . '__';
|
||
$button_title = (string) ($field['button_title'] ?? __('添加项目', 'jio'));
|
||
echo '<div class="jio-widget-repeater" data-index-token="' . esc_attr($token) . '" data-item-label="' . esc_attr((string) ($field['item_label'] ?? $field['label'] ?? __('项目', 'jio'))) . '" data-min-items="' . esc_attr((string) ($field['min_items'] ?? 0)) . '" data-max-items="' . esc_attr((string) ($field['max_items'] ?? 50)) . '"><div class="jio-widget-repeater__rows">';
|
||
foreach ($rows as $index => $row) {
|
||
self::group_row($widget, $field, is_array($row) ? $row : [], $key, (string) $index);
|
||
}
|
||
echo '</div><template class="jio-widget-repeater__template">';
|
||
self::group_row($widget, $field, [], $key, $token);
|
||
echo '</template><button type="button" class="button jio-widget-repeater__add">' . esc_html($button_title) . '</button></div>';
|
||
}
|
||
|
||
private static function group_row(SchemaWidget $widget, array $field, array $row, string $key, string $index): void {
|
||
$prefix = (string) ($field['item_label'] ?? $field['label'] ?? __('项目', 'jio'));
|
||
$number = ctype_digit($index) ? ((int) $index + 1) : 0;
|
||
$title = $prefix . $number;
|
||
if (!empty($row['title'])) $title .= ':' . strip_tags((string) $row['title']);
|
||
echo '<details class="jio-widget-repeater__row" data-row-index="' . esc_attr(ctype_digit($index) ? $index : '0') . '"><summary class="jio-widget-repeater__bar"><span class="dashicons dashicons-move" aria-hidden="true"></span><strong class="jio-widget-repeater__title" data-title-prefix="' . esc_attr($prefix) . '">' . esc_html($title) . '</strong><button type="button" class="button-link-delete jio-widget-repeater__remove">' . esc_html__('删除', 'jio') . '</button></summary><div class="jio-widget-repeater__fields">';
|
||
foreach ((array) ($field['fields'] ?? []) as $child) {
|
||
$child_id = (string) ($child['id'] ?? '');
|
||
self::field($widget, $child, $row[$child_id] ?? ($child['default'] ?? ''), $key . '[' . $index . ']');
|
||
}
|
||
echo '</div></details>';
|
||
}
|
||
|
||
private static function nested(SchemaWidget $widget, array $field, array $value, string $key): void {
|
||
echo '<details class="jio-widget-nested"><summary>' . esc_html((string) ($field['label'] ?? '更多设置')) . '</summary><div class="jio-widget-nested__body">';
|
||
foreach ((array) ($field['fields'] ?? []) as $child) {
|
||
$child_id = (string) ($child['id'] ?? '');
|
||
self::field($widget, $child, $value[$child_id] ?? ($child['default'] ?? ''), $key);
|
||
}
|
||
echo '</div></details>';
|
||
}
|
||
|
||
private static function plain_input(SchemaWidget $widget, string $key, string $value, string $type, string $placeholder, string $class = 'widefat'): void {
|
||
$name = $widget->get_field_name($key);
|
||
$id = $widget->get_field_id(str_replace(['[', ']'], ['-', ''], $key));
|
||
echo '<input class="' . esc_attr($class) . '" type="' . esc_attr($type) . '" id="' . esc_attr($id) . '" name="' . esc_attr($name) . '" value="' . esc_attr($value) . '" placeholder="' . esc_attr($placeholder) . '">';
|
||
}
|
||
}
|