| 1 |
<?php |
| 2 |
/** |
| 3 |
* Hybrid++ Schemas utilities: normalize/validate/merge/extract. |
| 4 |
* |
| 5 |
* @package Booking Calendar. |
| 6 |
* @author wpdevelop, oplugins |
| 7 |
* @web-site https://wpbookingcalendar.com/ |
| 8 |
* @email info@wpbookingcalendar.com |
| 9 |
* |
| 10 |
* @since 11.0.0 |
| 11 |
* @modified 2025-08-29 |
| 12 |
* @version 1.0 |
| 13 |
*/ |
| 14 |
|
| 15 |
// --------------------------------------------------------------------------------------------------------------------- |
| 16 |
// == File bfb-schemas.php == Time point: 2025-08-29 12:25 |
| 17 |
// --------------------------------------------------------------------------------------------------------------------- |
| 18 |
if ( ! defined( 'ABSPATH' ) ) { |
| 19 |
exit; // Exit if accessed directly. |
| 20 |
} |
| 21 |
|
| 22 |
/** |
| 23 |
* Utilities for working with field/section/component packs. |
| 24 |
*/ |
| 25 |
class WPBC_BFB_Schemas_Util { |
| 26 |
|
| 27 |
/** |
| 28 |
* Normalize a single pack to a safe, known shape. |
| 29 |
* |
| 30 |
* @param array $pack Raw pack from a filter callback. |
| 31 |
* |
| 32 |
* @return array Normalized pack. |
| 33 |
*/ |
| 34 |
public static function normalize_pack( $pack ) { |
| 35 |
$pack = is_array( $pack ) ? $pack : array(); |
| 36 |
|
| 37 |
$pack['kind'] = isset( $pack['kind'] ) ? sanitize_key( $pack['kind'] ) : 'field'; |
| 38 |
$pack['type'] = isset( $pack['type'] ) ? sanitize_key( $pack['type'] ) : ''; |
| 39 |
$pack['schema'] = ( isset( $pack['schema'] ) && is_array( $pack['schema'] ) ) ? $pack['schema'] : array(); |
| 40 |
if ( ! isset( $pack['schema']['props'] ) || ! is_array( $pack['schema']['props'] ) ) { |
| 41 |
$pack['schema']['props'] = array(); |
| 42 |
} |
| 43 |
|
| 44 |
$pack['inspector_ui'] = ( isset( $pack['inspector_ui'] ) && is_array( $pack['inspector_ui'] ) ) ? $pack['inspector_ui'] : array(); |
| 45 |
|
| 46 |
if ( ! isset( $pack['inspector_ui']['groups'] ) || ! is_array( $pack['inspector_ui']['groups'] ) ) { |
| 47 |
$pack['inspector_ui']['groups'] = array(); |
| 48 |
} |
| 49 |
|
| 50 |
$pack['header_actions'] = ( isset( $pack['header_actions'] ) && is_array( $pack['header_actions'] ) ) ? array_values( $pack['header_actions'] ) : array(); |
| 51 |
|
| 52 |
$pack['templates_printer'] = ( isset( $pack['templates_printer'] ) && is_callable( $pack['templates_printer'] ) ) ? $pack['templates_printer'] : null; |
| 53 |
|
| 54 |
return $pack; |
| 55 |
} |
| 56 |
|
| 57 |
/** |
| 58 |
* Basic validation: keep only supported prop/control types and JSON-serializable data. |
| 59 |
* |
| 60 |
* @param array $pack Normalized pack. |
| 61 |
* |
| 62 |
* @return array Validated pack (unsafe bits removed). |
| 63 |
*/ |
| 64 |
public static function validate_pack( $pack ) { |
| 65 |
|
| 66 |
// Allow extra prop/control types used by the Inspector Factory. |
| 67 |
$allowed_prop_types = array( 'string', 'number', 'boolean', 'array', 'int', 'float', 'enum', 'color' ); |
| 68 |
// Expanded to support Factory extras and color control. |
| 69 |
$allowed_control_types = array( 'text', 'number', 'checkbox', 'textarea', 'select', 'range_number', 'len', 'color' ); |
| 70 |
|
| 71 |
// Validate prop meta. |
| 72 |
foreach ( $pack['schema']['props'] as $prop_key => $meta ) { |
| 73 |
|
| 74 |
if ( ! is_array( $meta ) ) { |
| 75 |
unset( $pack['schema']['props'][ $prop_key ] ); |
| 76 |
continue; |
| 77 |
} |
| 78 |
$meta['type'] = isset( $meta['type'] ) ? $meta['type'] : 'string'; |
| 79 |
if ( ! in_array( $meta['type'], $allowed_prop_types, true ) ) { |
| 80 |
unset( $pack['schema']['props'][ $prop_key ] ); |
| 81 |
continue; |
| 82 |
} |
| 83 |
// Drop non-serializable values. |
| 84 |
foreach ( $meta as $k => $v ) { |
| 85 |
if ( is_object( $v ) && ! ( $v instanceof JsonSerializable ) ) { |
| 86 |
unset( $meta[ $k ] ); |
| 87 |
} |
| 88 |
} |
| 89 |
$pack['schema']['props'][ $prop_key ] = $meta; |
| 90 |
} |
| 91 |
|
| 92 |
// Validate inspector groups/controls. |
| 93 |
if ( ! empty( $pack['inspector_ui']['groups'] ) ) { |
| 94 |
foreach ( $pack['inspector_ui']['groups'] as $g_i => $group ) { |
| 95 |
if ( ! is_array( $group ) ) { |
| 96 |
unset( $pack['inspector_ui']['groups'][ $g_i ] ); |
| 97 |
continue; |
| 98 |
} |
| 99 |
$group['controls'] = ( isset( $group['controls'] ) && is_array( $group['controls'] ) ) ? $group['controls'] : array(); |
| 100 |
|
| 101 |
foreach ( $group['controls'] as $c_i => $control ) { |
| 102 |
if ( ! is_array( $control ) ) { |
| 103 |
unset( $group['controls'][ $c_i ] ); |
| 104 |
continue; |
| 105 |
} |
| 106 |
if ( empty( $control['key'] ) || empty( $control['type'] ) || ! in_array( $control['type'], $allowed_control_types, true ) ) { |
| 107 |
unset( $group['controls'][ $c_i ] ); |
| 108 |
continue; |
| 109 |
} |
| 110 |
// Remove objects in control meta. |
| 111 |
foreach ( $control as $k => $v ) { |
| 112 |
if ( is_object( $v ) && ! ( $v instanceof JsonSerializable ) ) { |
| 113 |
unset( $control[ $k ] ); |
| 114 |
} |
| 115 |
} |
| 116 |
$group['controls'][ $c_i ] = $control; |
| 117 |
} |
| 118 |
$pack['inspector_ui']['groups'][ $g_i ] = $group; |
| 119 |
} |
| 120 |
} |
| 121 |
|
| 122 |
// Sanitize header action keys. |
| 123 |
if ( ! empty( $pack['header_actions'] ) ) { |
| 124 |
$san = array(); |
| 125 |
foreach ( $pack['header_actions'] as $act ) { |
| 126 |
$san[] = sanitize_key( $act ); |
| 127 |
} |
| 128 |
$pack['header_actions'] = array_values( array_unique( $san ) ); |
| 129 |
} |
| 130 |
|
| 131 |
return $pack; |
| 132 |
} |
| 133 |
|
| 134 |
/** |
| 135 |
* Merge a list of packs keyed by type. Later packs win on conflicts. |
| 136 |
* |
| 137 |
* @param array $packs Packs list. |
| 138 |
* |
| 139 |
* @return array Merged packs keyed by type. |
| 140 |
*/ |
| 141 |
public static function merge_packs( $packs ) { |
| 142 |
$merged = array(); |
| 143 |
foreach ( (array) $packs as $pack ) { |
| 144 |
$pack = self::normalize_pack( $pack ); |
| 145 |
$pack = self::validate_pack( $pack ); |
| 146 |
if ( empty( $pack['type'] ) ) { |
| 147 |
continue; |
| 148 |
} |
| 149 |
$merged[ $pack['type'] ] = $pack; // last writer wins. |
| 150 |
} |
| 151 |
|
| 152 |
return $merged; |
| 153 |
} |
| 154 |
|
| 155 |
/** |
| 156 |
* Extract only the JS-safe schema bits for localization. |
| 157 |
* |
| 158 |
* @param array $packs_by_type Merged packs keyed by type. |
| 159 |
* |
| 160 |
* @return array Array keyed by type for JS (small payload). |
| 161 |
*/ |
| 162 |
public static function extract_schemas_for_js( $packs_by_type ) { |
| 163 |
$out = array(); |
| 164 |
foreach ( (array) $packs_by_type as $type => $pack ) { |
| 165 |
$out[ $type ] = array( |
| 166 |
'kind' => isset( $pack['kind'] ) ? $pack['kind'] : 'field', |
| 167 |
'schema' => isset( $pack['schema'] ) ? $pack['schema'] : array( 'props' => array() ), |
| 168 |
'inspector_ui' => isset( $pack['inspector_ui'] ) ? $pack['inspector_ui'] : array( 'groups' => array() ), |
| 169 |
'header_actions' => isset( $pack['header_actions'] ) ? array_values( $pack['header_actions'] ) : array(), |
| 170 |
); |
| 171 |
} |
| 172 |
|
| 173 |
return $out; |
| 174 |
} |
| 175 |
} |
| 176 |
|