Files
wordpress-theme-aksrt/src/WidgetSystem/FieldSchema.php
T
kate522 b639d57c5b feat(widgets): replicate 12 reference widgets for full Zibll parity
- rewrite schemas for posts, tab_posts, oneline_posts, hot_posts,
  term_cards, term_aggregation, media_cards, combo_info, partners,
  recent_comments, posts_toc and slider_media_combo to mirror the
  modern reference modules field by field (options, defaults,
  dependencies, min/max/step, subheadings)
- port reference renderers: hot ranking with TOP badges, category
  aggregation card, graphic cover cards, partners CSS marquee,
  combo info cards with auto band palette, recent comment list
- rename 分类与标签聚合卡片 to 分类聚合卡片 and restrict it to
  categories (no tags/topics branch in this project)
- drop topics filter fields and the topics query branch; align
  orderby value domain with supported post meta
- add searchable sortable terms admin field type and spinner unit
  suffix rendering
- unify widget pagination and lazy loading on the REST client with
  the runtime-owned ias reveal trigger; remove the legacy
  post_ajax_trigger dual path
- align section-navs TOC with the reference contract (wznav ids,
  3-heading minimum, zib-widget box)
2026-09-05 11:28:00 +08:00

214 lines
7.7 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
namespace AKSRT\WidgetSystem;
defined('ABSPATH') || exit;
/**
* Field schema defaults and recursive value normalization.
*/
final class FieldSchema {
private const DISPLAY_ONLY = ['content', 'subheading', 'submessage'];
public static function supported_types(): array {
return [
'text', 'textarea', 'number', 'spinner', 'slider', 'switcher', 'radio',
'button_set', 'select', 'checkbox', 'color', 'palette', 'image_select', 'icon', 'upload',
'link', 'spacing', 'background', 'group', 'fieldset', 'accordion', 'terms',
'content', 'subheading', 'submessage',
];
}
public static function defaults(array $fields): array {
$defaults = [];
foreach ($fields as $field) {
$id = (string) ($field['id'] ?? '');
$type = (string) ($field['type'] ?? 'text');
if ($id === '' || in_array($type, self::DISPLAY_ONLY, true)) {
continue;
}
$defaults[$id] = self::normalize_value($field, $field['default'] ?? self::empty_value($type));
}
return $defaults;
}
public static function normalize(array $fields, array $input): array {
$output = [];
foreach ($fields as $field) {
$id = (string) ($field['id'] ?? '');
$type = (string) ($field['type'] ?? 'text');
if ($id === '' || in_array($type, self::DISPLAY_ONLY, true)) {
continue;
}
$value = array_key_exists($id, $input) ? $input[$id] : ($field['default'] ?? self::empty_value($type));
$output[$id] = self::normalize_value($field, $value);
}
return $output;
}
public static function normalize_value(array $field, $value) {
$type = (string) ($field['type'] ?? 'text');
switch ($type) {
case 'switcher':
return filter_var($value, FILTER_VALIDATE_BOOLEAN);
case 'number':
case 'spinner':
case 'slider':
return self::number($field, $value);
case 'radio':
case 'button_set':
case 'select':
case 'palette':
case 'image_select':
return self::choice($field, $value);
case 'checkbox':
return self::checkbox($field, $value);
case 'terms':
return self::terms($value);
case 'link':
return self::link($value);
case 'spacing':
return self::spacing($value);
case 'background':
return self::background($value);
case 'group':
return self::group($field, $value);
case 'fieldset':
case 'accordion':
return self::nested($field, $value);
case 'upload':
return esc_url_raw((string) $value);
case 'color':
return self::color((string) $value);
case 'icon':
return sanitize_text_field((string) $value);
case 'textarea':
return !empty($field['allow_html']) ? wp_kses_post((string) $value) : sanitize_textarea_field((string) $value);
case 'text':
default:
if (($field['sanitize'] ?? '') === 'url') {
return esc_url_raw((string) $value);
}
if (($field['sanitize'] ?? '') === 'ids') {
return implode(',', array_values(array_unique(array_filter(array_map('absint', preg_split('/[,,\s]+/', (string) $value))))));
}
if (($field['sanitize'] ?? '') === 'reference_ids') {
$tokens = preg_split('/[,,\s]+/', trim((string) $value), -1, PREG_SPLIT_NO_EMPTY);
$tokens = array_map(static fn($token): string => preg_match('/^-?\d+$/', (string) $token) ? (string) (int) $token : '', $tokens ?: []);
return implode(',', array_values(array_unique(array_filter($tokens, static fn($token): bool => $token !== ''))));
}
return !empty($field['allow_html']) ? wp_kses_post((string) $value) : sanitize_text_field((string) $value);
}
}
private static function empty_value(string $type) {
if ($type === 'switcher') {
return false;
}
if (in_array($type, ['group', 'checkbox', 'terms'], true)) {
return [];
}
if (in_array($type, ['link', 'spacing', 'background', 'fieldset', 'accordion'], true)) {
return [];
}
return '';
}
private static function number(array $field, $value) {
$number = is_numeric($value) ? (float) $value : (float) ($field['default'] ?? 0);
if (isset($field['min'])) {
$number = max((float) $field['min'], $number);
}
if (isset($field['max'])) {
$number = min((float) $field['max'], $number);
}
$step = (float) ($field['step'] ?? 1);
// Whole values stay int so JSON round trips keep normalize identity
// even for fractional-step fields (e.g. partners speed 0.1–10s).
return $step >= 1 || fmod($number, 1.0) === 0.0 ? (int) round($number) : $number;
}
private static function choice(array $field, $value) {
$options = (array) ($field['options'] ?? []);
if (!empty($field['multiple'])) {
$values = is_array($value) ? $value : [];
return array_values(array_filter(array_map('strval', $values), static fn($item) => array_key_exists($item, $options)));
}
$key = (string) $value;
return array_key_exists($key, $options) ? $key : (string) ($field['default'] ?? array_key_first($options) ?? '');
}
private static function checkbox(array $field, $value) {
$options = (array) ($field['options'] ?? []);
if (!$options) {
return filter_var($value, FILTER_VALIDATE_BOOLEAN);
}
$values = is_array($value) ? $value : [];
return array_values(array_filter(array_map('strval', $values), static fn($item) => array_key_exists($item, $options)));
}
private static function terms($value): array {
$values = is_array($value) ? $value : (preg_split('/[,,\s]+/', (string) $value) ?: []);
return array_values(array_unique(array_filter(array_map('absint', $values))));
}
private static function link($value): array {
$value = is_array($value) ? $value : [];
return [
'url' => esc_url_raw((string) ($value['url'] ?? '')),
'text' => sanitize_text_field((string) ($value['text'] ?? '')),
'target' => ($value['target'] ?? '') === '_blank' ? '_blank' : '',
];
}
private static function spacing($value): array {
$value = is_array($value) ? $value : [];
$result = [];
foreach (['top', 'right', 'bottom', 'left'] as $edge) {
$result[$edge] = isset($value[$edge]) && is_numeric($value[$edge]) ? (int) $value[$edge] : '';
}
return $result;
}
private static function background($value): array {
$value = is_array($value) ? $value : [];
return [
'color' => self::color((string) ($value['color'] ?? '')),
'image' => esc_url_raw((string) ($value['image'] ?? '')),
'position' => self::allow((string) ($value['position'] ?? 'center center'), ['center center', 'top center', 'bottom center', 'center left', 'center right'], 'center center'),
'repeat' => self::allow((string) ($value['repeat'] ?? 'no-repeat'), ['no-repeat', 'repeat', 'repeat-x', 'repeat-y'], 'no-repeat'),
'attachment' => self::allow((string) ($value['attachment'] ?? 'scroll'), ['scroll', 'fixed'], 'scroll'),
'size' => self::allow((string) ($value['size'] ?? 'cover'), ['cover', 'contain', 'auto'], 'cover'),
];
}
private static function group(array $field, $value): array {
$rows = is_array($value) ? array_values($value) : [];
$max = max(1, (int) ($field['max_items'] ?? 50));
$children = (array) ($field['fields'] ?? []);
return array_map(static fn($row) => self::normalize($children, is_array($row) ? $row : []), array_slice($rows, 0, $max));
}
private static function nested(array $field, $value): array {
$children = (array) ($field['fields'] ?? []);
return self::normalize($children, is_array($value) ? $value : []);
}
private static function color(string $value): string {
$value = trim($value);
if ($value === '') {
return '';
}
$hex = sanitize_hex_color($value);
if ($hex) {
return $hex;
}
return preg_match('/^rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}(?:\s*,\s*(?:0|1|0?\.\d+))?\s*\)$/', $value) ? $value : '';
}
private static function allow(string $value, array $allowed, string $fallback): string {
return in_array($value, $allowed, true) ? $value : $fallback;
}
}