base-v2.php
2 months ago
base.php
2 years ago
get-from-db.php
2 months ago
get-from-rest-api.php
2 months ago
get-from-users.php
2 months ago
get-related-posts.php
2 months ago
legacy-parser.php
2 months ago
num-range-manual.php
2 months ago
num-range.php
2 months ago
registry.php
2 months ago
base-v2.php
443 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Enhanced base class for option generators with structured settings support. |
| 4 | * |
| 5 | * @package Jet_Form_Builder\Generators |
| 6 | */ |
| 7 | |
| 8 | namespace Jet_Form_Builder\Generators; |
| 9 | |
| 10 | // If this file is called directly, abort. |
| 11 | if ( ! defined( 'WPINC' ) ) { |
| 12 | die; |
| 13 | } |
| 14 | |
| 15 | /** |
| 16 | * Base_V2 class - Enhanced generator base with schema support. |
| 17 | * |
| 18 | * Extends the original Base class to add: |
| 19 | * - Structured settings schema for dynamic UI generation |
| 20 | * - Support for cascading/auto-update fields |
| 21 | * - Backward compatibility with legacy generators |
| 22 | */ |
| 23 | abstract class Base_V2 extends Base { |
| 24 | |
| 25 | /** |
| 26 | * Returns structured settings schema for this generator. |
| 27 | * Used for both editor UI generation and backend validation. |
| 28 | * |
| 29 | * Schema format: |
| 30 | * [ |
| 31 | * 'field_key' => [ |
| 32 | * 'type' => 'string|number|boolean|array', |
| 33 | * 'default' => mixed, |
| 34 | * 'label' => string, |
| 35 | * 'control' => 'text|number|select|toggle|textarea', |
| 36 | * 'help' => string (optional), |
| 37 | * 'placeholder' => string (optional), |
| 38 | * 'options' => array (for select control), |
| 39 | * 'min' => number (for number control), |
| 40 | * 'max' => number (for number control), |
| 41 | * 'step' => number (for number control), |
| 42 | * ], |
| 43 | * ] |
| 44 | * |
| 45 | * @return array Schema definition |
| 46 | */ |
| 47 | abstract public function get_settings_schema(): array; |
| 48 | |
| 49 | /** |
| 50 | * Returns block attributes definitions for this generator. |
| 51 | * Can be used to dynamically extend block.json attributes. |
| 52 | * |
| 53 | * @return array Attribute definitions compatible with block.json format |
| 54 | */ |
| 55 | public function get_block_attributes(): array { |
| 56 | $attributes = array(); |
| 57 | |
| 58 | foreach ( $this->get_settings_schema() as $key => $field ) { |
| 59 | $attr_name = $this->get_attribute_name( $key ); |
| 60 | |
| 61 | $attributes[ $attr_name ] = array( |
| 62 | 'type' => $this->map_schema_type_to_block_type( $field['type'] ?? 'string' ), |
| 63 | 'default' => $field['default'] ?? $this->get_default_for_type( $field['type'] ?? 'string' ), |
| 64 | ); |
| 65 | } |
| 66 | |
| 67 | return $attributes; |
| 68 | } |
| 69 | |
| 70 | /** |
| 71 | * Maps schema type to WordPress block attribute type. |
| 72 | * |
| 73 | * @param string $type Schema type. |
| 74 | * |
| 75 | * @return string Block attribute type |
| 76 | */ |
| 77 | protected function map_schema_type_to_block_type( string $type ): string { |
| 78 | $map = array( |
| 79 | 'string' => 'string', |
| 80 | 'number' => 'number', |
| 81 | 'boolean' => 'boolean', |
| 82 | 'array' => 'array', |
| 83 | 'object' => 'object', |
| 84 | ); |
| 85 | |
| 86 | return $map[ $type ] ?? 'string'; |
| 87 | } |
| 88 | |
| 89 | /** |
| 90 | * Returns default value for a given type. |
| 91 | * |
| 92 | * @param string $type Field type. |
| 93 | * |
| 94 | * @return mixed Default value |
| 95 | */ |
| 96 | protected function get_default_for_type( string $type ) { |
| 97 | $defaults = array( |
| 98 | 'string' => '', |
| 99 | 'number' => 0, |
| 100 | 'boolean' => false, |
| 101 | 'array' => array(), |
| 102 | 'object' => array(), |
| 103 | ); |
| 104 | |
| 105 | return $defaults[ $type ] ?? ''; |
| 106 | } |
| 107 | |
| 108 | /** |
| 109 | * Generates attribute name prefixed with generator ID to avoid collisions. |
| 110 | * |
| 111 | * @param string $key Setting key from schema. |
| 112 | * |
| 113 | * @return string Prefixed attribute name |
| 114 | */ |
| 115 | public function get_attribute_name( string $key ): string { |
| 116 | return 'gen_' . $this->get_id() . '_' . $key; |
| 117 | } |
| 118 | |
| 119 | /** |
| 120 | * Parses settings from block attributes. |
| 121 | * Extracts generator-specific settings from prefixed attributes. |
| 122 | * |
| 123 | * @param array $block_attrs All block attributes. |
| 124 | * |
| 125 | * @return array Parsed settings with original keys (without prefix) |
| 126 | */ |
| 127 | public function parse_settings( array $block_attrs ): array { |
| 128 | $settings = array(); |
| 129 | $prefix = 'gen_' . $this->get_id() . '_'; |
| 130 | $schema = $this->get_settings_schema(); |
| 131 | |
| 132 | foreach ( $schema as $key => $field_def ) { |
| 133 | $attr_name = $prefix . $key; |
| 134 | |
| 135 | if ( isset( $block_attrs[ $attr_name ] ) ) { |
| 136 | $settings[ $key ] = $block_attrs[ $attr_name ]; |
| 137 | } else { |
| 138 | // Use default from schema |
| 139 | $settings[ $key ] = $field_def['default'] ?? $this->get_default_for_type( $field_def['type'] ?? 'string' ); |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | return $settings; |
| 144 | } |
| 145 | |
| 146 | /** |
| 147 | * Validates settings against schema. |
| 148 | * |
| 149 | * @param array $settings Settings to validate. |
| 150 | * |
| 151 | * @return array Validation result ['valid' => bool, 'errors' => array] |
| 152 | */ |
| 153 | public function validate_settings( array $settings ): array { |
| 154 | $errors = array(); |
| 155 | $schema = $this->get_settings_schema(); |
| 156 | |
| 157 | foreach ( $schema as $key => $field_def ) { |
| 158 | // Check required fields |
| 159 | if ( ! empty( $field_def['required'] ) ) { |
| 160 | if ( ! isset( $settings[ $key ] ) || '' === $settings[ $key ] ) { |
| 161 | $errors[ $key ] = sprintf( |
| 162 | /* translators: %s: field label */ |
| 163 | __( '%s is required.', 'jet-form-builder' ), |
| 164 | $field_def['label'] ?? $key |
| 165 | ); |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | // Type validation |
| 170 | if ( isset( $settings[ $key ] ) && isset( $field_def['type'] ) ) { |
| 171 | $value = $settings[ $key ]; |
| 172 | $type = $field_def['type']; |
| 173 | |
| 174 | if ( 'number' === $type && ! is_numeric( $value ) && '' !== $value ) { |
| 175 | $errors[ $key ] = sprintf( |
| 176 | /* translators: %s: field label */ |
| 177 | __( '%s must be a number.', 'jet-form-builder' ), |
| 178 | $field_def['label'] ?? $key |
| 179 | ); |
| 180 | } |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | return array( |
| 185 | 'valid' => empty( $errors ), |
| 186 | 'errors' => $errors, |
| 187 | ); |
| 188 | } |
| 189 | |
| 190 | /** |
| 191 | * Whether this generator supports auto-update/cascading feature. |
| 192 | * Override in child classes to enable. |
| 193 | * |
| 194 | * @return bool |
| 195 | */ |
| 196 | public function supports_auto_update(): bool { |
| 197 | return false; |
| 198 | } |
| 199 | |
| 200 | /** |
| 201 | * Returns list of context fields this generator can use for auto-update. |
| 202 | * Override in child classes to specify which fields can be listened to. |
| 203 | * |
| 204 | * @return array Array of context descriptions |
| 205 | */ |
| 206 | public function get_auto_update_context_fields(): array { |
| 207 | return array(); |
| 208 | } |
| 209 | |
| 210 | /** |
| 211 | * Returns the value type supported by auto-update context. |
| 212 | * |
| 213 | * Supported values: |
| 214 | * - scalar: single-value fields only |
| 215 | * - scalar_or_array: single-value and multi-value fields |
| 216 | * |
| 217 | * @return string |
| 218 | */ |
| 219 | public function get_auto_update_value_type(): string { |
| 220 | return 'scalar'; |
| 221 | } |
| 222 | |
| 223 | /** |
| 224 | * Returns how the generator should behave when auto-update context is empty. |
| 225 | * |
| 226 | * Supported values: |
| 227 | * - clear_on_empty: always clear dependent options |
| 228 | * - fallback_to_static: always use static generator settings |
| 229 | * - fallback_to_static_if_configured: use static settings only when a valid fallback exists |
| 230 | * |
| 231 | * @return string |
| 232 | */ |
| 233 | public function get_auto_update_empty_context_policy(): string { |
| 234 | return 'clear_on_empty'; |
| 235 | } |
| 236 | |
| 237 | /** |
| 238 | * Whether the current generator settings provide a valid static fallback |
| 239 | * for empty auto-update context. |
| 240 | * |
| 241 | * @param array $settings Parsed generator settings. |
| 242 | * |
| 243 | * @return bool |
| 244 | */ |
| 245 | public function has_auto_update_static_fallback( array $settings ): bool { |
| 246 | return false; |
| 247 | } |
| 248 | |
| 249 | /** |
| 250 | * Whether the frontend should clear options immediately when auto-update |
| 251 | * context is empty for this field configuration. |
| 252 | * |
| 253 | * @param array $settings Parsed generator settings. |
| 254 | * |
| 255 | * @return bool |
| 256 | */ |
| 257 | public function should_clear_on_empty_auto_update_context( array $settings ): bool { |
| 258 | $policy = $this->get_auto_update_empty_context_policy(); |
| 259 | |
| 260 | if ( 'fallback_to_static' === $policy ) { |
| 261 | return false; |
| 262 | } |
| 263 | |
| 264 | if ( 'fallback_to_static_if_configured' === $policy ) { |
| 265 | return ! $this->has_auto_update_static_fallback( $settings ); |
| 266 | } |
| 267 | |
| 268 | return true; |
| 269 | } |
| 270 | |
| 271 | /** |
| 272 | * Generate options with context from dependent fields. |
| 273 | * Used when auto-update is triggered by another field's change. |
| 274 | * |
| 275 | * @param array $settings Parsed settings from block attributes. |
| 276 | * @param array $context Associative array ['field_name' => 'value'] from dependent fields. |
| 277 | * |
| 278 | * @return array Generated options |
| 279 | */ |
| 280 | public function generate_with_context( array $settings, array $context = array() ): array { |
| 281 | // Default implementation ignores context and calls regular generate |
| 282 | return $this->generate( $settings ); |
| 283 | } |
| 284 | |
| 285 | /** |
| 286 | * Override incoming_args to use schema-based parsing. |
| 287 | * For backward compatibility, this still works with the old system. |
| 288 | * |
| 289 | * @return array |
| 290 | */ |
| 291 | public function incoming_args() { |
| 292 | $args = array(); |
| 293 | $schema = $this->get_settings_schema(); |
| 294 | |
| 295 | foreach ( $schema as $key => $field_def ) { |
| 296 | $attr_name = $this->get_attribute_name( $key ); |
| 297 | $args[ $attr_name ] = $this->get_parse_callback_for_type( $field_def['type'] ?? 'string' ); |
| 298 | } |
| 299 | |
| 300 | return $args; |
| 301 | } |
| 302 | |
| 303 | /** |
| 304 | * Returns parse callback for a given type. |
| 305 | * |
| 306 | * @param string $type Field type. |
| 307 | * |
| 308 | * @return callable Parse callback |
| 309 | */ |
| 310 | protected function get_parse_callback_for_type( string $type ): callable { |
| 311 | switch ( $type ) { |
| 312 | case 'number': |
| 313 | return 'floatval'; |
| 314 | case 'boolean': |
| 315 | return function ( $value ) { |
| 316 | return filter_var( $value, FILTER_VALIDATE_BOOLEAN ); |
| 317 | }; |
| 318 | case 'array': |
| 319 | return function ( $value ) { |
| 320 | return is_array( $value ) ? $value : array(); |
| 321 | }; |
| 322 | default: |
| 323 | return function ( $value ) { |
| 324 | return (string) $value; |
| 325 | }; |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | /** |
| 330 | * Override get_values to work with new schema system. |
| 331 | * Supports multiple data sources in priority order: |
| 332 | * 1. generator_args (object) - new preferred format |
| 333 | * 2. Prefixed attributes (gen_<id>_<key>) - intermediate format |
| 334 | * 3. generator_field (pipe-delimited string) - legacy format |
| 335 | * |
| 336 | * @param array $args Block attributes. |
| 337 | * |
| 338 | * @return array Generated options |
| 339 | */ |
| 340 | public function get_values( $args ) { |
| 341 | |
| 342 | // Priority 1: Check for generator_args object (new format) |
| 343 | if ( ! empty( $args['generator_args'] ) && is_array( $args['generator_args'] ) ) { |
| 344 | $settings = $this->parse_generator_args( $args['generator_args'] ); |
| 345 | if ( $this->has_meaningful_values( $settings ) ) { |
| 346 | $result = $this->generate( $settings ); |
| 347 | return $result; |
| 348 | } |
| 349 | } |
| 350 | |
| 351 | // Priority 2: Try prefixed attributes |
| 352 | $settings = $this->parse_settings( $args ); |
| 353 | if ( $this->has_meaningful_values( $settings ) ) { |
| 354 | $result = $this->generate( $settings ); |
| 355 | return $result; |
| 356 | } |
| 357 | |
| 358 | // Priority 3: Fall back to legacy generator_field (pipe-delimited) |
| 359 | if ( ! empty( $args['generator_field'] ) ) { |
| 360 | $legacy_input = $this->enrich_legacy_field( $args['generator_field'], $args ); |
| 361 | $result = $this->generate( $legacy_input ); |
| 362 | return $result; |
| 363 | } |
| 364 | |
| 365 | // No settings found, return empty |
| 366 | $result = $this->generate( $settings ); |
| 367 | return $result; |
| 368 | } |
| 369 | |
| 370 | /** |
| 371 | * Enrich legacy generator_field with additional block attributes. |
| 372 | * Override in child classes to migrate legacy attrs into generate() input. |
| 373 | * |
| 374 | * @param string $generator_field Legacy pipe-delimited string. |
| 375 | * @param array $block_attrs All block attributes. |
| 376 | * |
| 377 | * @return mixed Enriched input for generate(). |
| 378 | */ |
| 379 | protected function enrich_legacy_field( string $generator_field, array $block_attrs ) { |
| 380 | return $generator_field; |
| 381 | } |
| 382 | |
| 383 | /** |
| 384 | * Parse settings from generator_args object. |
| 385 | * |
| 386 | * @param array $generator_args Generator args object from block attributes. |
| 387 | * |
| 388 | * @return array Parsed settings with defaults applied. |
| 389 | */ |
| 390 | public function parse_generator_args( array $generator_args ): array { |
| 391 | $settings = array(); |
| 392 | $schema = $this->get_settings_schema(); |
| 393 | |
| 394 | foreach ( $schema as $key => $field_def ) { |
| 395 | if ( isset( $generator_args[ $key ] ) ) { |
| 396 | $settings[ $key ] = $generator_args[ $key ]; |
| 397 | } else { |
| 398 | $settings[ $key ] = $field_def['default'] ?? $this->get_default_for_type( $field_def['type'] ?? 'string' ); |
| 399 | } |
| 400 | } |
| 401 | |
| 402 | return $settings; |
| 403 | } |
| 404 | |
| 405 | /** |
| 406 | * Check if settings contain meaningful (non-default) values. |
| 407 | * |
| 408 | * @param array $settings Settings array to check. |
| 409 | * |
| 410 | * @return bool True if has meaningful values. |
| 411 | */ |
| 412 | protected function has_meaningful_values( array $settings ): bool { |
| 413 | $schema = $this->get_settings_schema(); |
| 414 | |
| 415 | foreach ( $schema as $key => $field_def ) { |
| 416 | $default = $field_def['default'] ?? $this->get_default_for_type( $field_def['type'] ?? 'string' ); |
| 417 | if ( isset( $settings[ $key ] ) && $settings[ $key ] !== $default && '' !== $settings[ $key ] ) { |
| 418 | return true; |
| 419 | } |
| 420 | } |
| 421 | |
| 422 | return false; |
| 423 | } |
| 424 | |
| 425 | /** |
| 426 | * Returns schema for JS localization. |
| 427 | * Includes all necessary data for editor UI generation. |
| 428 | * |
| 429 | * @return array Schema data for JavaScript |
| 430 | */ |
| 431 | public function get_schema_for_js(): array { |
| 432 | return array( |
| 433 | 'id' => $this->get_id(), |
| 434 | 'name' => $this->get_name(), |
| 435 | 'schema' => $this->get_settings_schema(), |
| 436 | 'supports_update' => $this->supports_auto_update(), |
| 437 | 'update_context' => $this->get_auto_update_context_fields(), |
| 438 | 'update_value_type' => $this->get_auto_update_value_type(), |
| 439 | 'update_empty_context_policy' => $this->get_auto_update_empty_context_policy(), |
| 440 | ); |
| 441 | } |
| 442 | } |
| 443 |