[ * 'type' => 'string|number|boolean|array', * 'default' => mixed, * 'label' => string, * 'control' => 'text|number|select|toggle|textarea', * 'help' => string (optional), * 'placeholder' => string (optional), * 'options' => array (for select control), * 'min' => number (for number control), * 'max' => number (for number control), * 'step' => number (for number control), * ], * ] * * @return array Schema definition */ abstract public function get_settings_schema(): array; /** * Returns block attributes definitions for this generator. * Can be used to dynamically extend block.json attributes. * * @return array Attribute definitions compatible with block.json format */ public function get_block_attributes(): array { $attributes = array(); foreach ( $this->get_settings_schema() as $key => $field ) { $attr_name = $this->get_attribute_name( $key ); $attributes[ $attr_name ] = array( 'type' => $this->map_schema_type_to_block_type( $field['type'] ?? 'string' ), 'default' => $field['default'] ?? $this->get_default_for_type( $field['type'] ?? 'string' ), ); } return $attributes; } /** * Maps schema type to WordPress block attribute type. * * @param string $type Schema type. * * @return string Block attribute type */ protected function map_schema_type_to_block_type( string $type ): string { $map = array( 'string' => 'string', 'number' => 'number', 'boolean' => 'boolean', 'array' => 'array', 'object' => 'object', ); return $map[ $type ] ?? 'string'; } /** * Returns default value for a given type. * * @param string $type Field type. * * @return mixed Default value */ protected function get_default_for_type( string $type ) { $defaults = array( 'string' => '', 'number' => 0, 'boolean' => false, 'array' => array(), 'object' => array(), ); return $defaults[ $type ] ?? ''; } /** * Generates attribute name prefixed with generator ID to avoid collisions. * * @param string $key Setting key from schema. * * @return string Prefixed attribute name */ public function get_attribute_name( string $key ): string { return 'gen_' . $this->get_id() . '_' . $key; } /** * Parses settings from block attributes. * Extracts generator-specific settings from prefixed attributes. * * @param array $block_attrs All block attributes. * * @return array Parsed settings with original keys (without prefix) */ public function parse_settings( array $block_attrs ): array { $settings = array(); $prefix = 'gen_' . $this->get_id() . '_'; $schema = $this->get_settings_schema(); foreach ( $schema as $key => $field_def ) { $attr_name = $prefix . $key; if ( isset( $block_attrs[ $attr_name ] ) ) { $settings[ $key ] = $block_attrs[ $attr_name ]; } else { // Use default from schema $settings[ $key ] = $field_def['default'] ?? $this->get_default_for_type( $field_def['type'] ?? 'string' ); } } return $settings; } /** * Validates settings against schema. * * @param array $settings Settings to validate. * * @return array Validation result ['valid' => bool, 'errors' => array] */ public function validate_settings( array $settings ): array { $errors = array(); $schema = $this->get_settings_schema(); foreach ( $schema as $key => $field_def ) { // Check required fields if ( ! empty( $field_def['required'] ) ) { if ( ! isset( $settings[ $key ] ) || '' === $settings[ $key ] ) { $errors[ $key ] = sprintf( /* translators: %s: field label */ __( '%s is required.', 'jet-form-builder' ), $field_def['label'] ?? $key ); } } // Type validation if ( isset( $settings[ $key ] ) && isset( $field_def['type'] ) ) { $value = $settings[ $key ]; $type = $field_def['type']; if ( 'number' === $type && ! is_numeric( $value ) && '' !== $value ) { $errors[ $key ] = sprintf( /* translators: %s: field label */ __( '%s must be a number.', 'jet-form-builder' ), $field_def['label'] ?? $key ); } } } return array( 'valid' => empty( $errors ), 'errors' => $errors, ); } /** * Whether this generator supports auto-update/cascading feature. * Override in child classes to enable. * * @return bool */ public function supports_auto_update(): bool { return false; } /** * Returns list of context fields this generator can use for auto-update. * Override in child classes to specify which fields can be listened to. * * @return array Array of context descriptions */ public function get_auto_update_context_fields(): array { return array(); } /** * Returns the value type supported by auto-update context. * * Supported values: * - scalar: single-value fields only * - scalar_or_array: single-value and multi-value fields * * @return string */ public function get_auto_update_value_type(): string { return 'scalar'; } /** * Returns how the generator should behave when auto-update context is empty. * * Supported values: * - clear_on_empty: always clear dependent options * - fallback_to_static: always use static generator settings * - fallback_to_static_if_configured: use static settings only when a valid fallback exists * * @return string */ public function get_auto_update_empty_context_policy(): string { return 'clear_on_empty'; } /** * Whether the current generator settings provide a valid static fallback * for empty auto-update context. * * @param array $settings Parsed generator settings. * * @return bool */ public function has_auto_update_static_fallback( array $settings ): bool { return false; } /** * Whether the frontend should clear options immediately when auto-update * context is empty for this field configuration. * * @param array $settings Parsed generator settings. * * @return bool */ public function should_clear_on_empty_auto_update_context( array $settings ): bool { $policy = $this->get_auto_update_empty_context_policy(); if ( 'fallback_to_static' === $policy ) { return false; } if ( 'fallback_to_static_if_configured' === $policy ) { return ! $this->has_auto_update_static_fallback( $settings ); } return true; } /** * Generate options with context from dependent fields. * Used when auto-update is triggered by another field's change. * * @param array $settings Parsed settings from block attributes. * @param array $context Associative array ['field_name' => 'value'] from dependent fields. * * @return array Generated options */ public function generate_with_context( array $settings, array $context = array() ): array { // Default implementation ignores context and calls regular generate return $this->generate( $settings ); } /** * Override incoming_args to use schema-based parsing. * For backward compatibility, this still works with the old system. * * @return array */ public function incoming_args() { $args = array(); $schema = $this->get_settings_schema(); foreach ( $schema as $key => $field_def ) { $attr_name = $this->get_attribute_name( $key ); $args[ $attr_name ] = $this->get_parse_callback_for_type( $field_def['type'] ?? 'string' ); } return $args; } /** * Returns parse callback for a given type. * * @param string $type Field type. * * @return callable Parse callback */ protected function get_parse_callback_for_type( string $type ): callable { switch ( $type ) { case 'number': return 'floatval'; case 'boolean': return function ( $value ) { return filter_var( $value, FILTER_VALIDATE_BOOLEAN ); }; case 'array': return function ( $value ) { return is_array( $value ) ? $value : array(); }; default: return function ( $value ) { return (string) $value; }; } } /** * Override get_values to work with new schema system. * Supports multiple data sources in priority order: * 1. generator_args (object) - new preferred format * 2. Prefixed attributes (gen__) - intermediate format * 3. generator_field (pipe-delimited string) - legacy format * * @param array $args Block attributes. * * @return array Generated options */ public function get_values( $args ) { // Priority 1: Check for generator_args object (new format) if ( ! empty( $args['generator_args'] ) && is_array( $args['generator_args'] ) ) { $settings = $this->parse_generator_args( $args['generator_args'] ); if ( $this->has_meaningful_values( $settings ) ) { $result = $this->generate( $settings ); return $result; } } // Priority 2: Try prefixed attributes $settings = $this->parse_settings( $args ); if ( $this->has_meaningful_values( $settings ) ) { $result = $this->generate( $settings ); return $result; } // Priority 3: Fall back to legacy generator_field (pipe-delimited) if ( ! empty( $args['generator_field'] ) ) { $legacy_input = $this->enrich_legacy_field( $args['generator_field'], $args ); $result = $this->generate( $legacy_input ); return $result; } // No settings found, return empty $result = $this->generate( $settings ); return $result; } /** * Enrich legacy generator_field with additional block attributes. * Override in child classes to migrate legacy attrs into generate() input. * * @param string $generator_field Legacy pipe-delimited string. * @param array $block_attrs All block attributes. * * @return mixed Enriched input for generate(). */ protected function enrich_legacy_field( string $generator_field, array $block_attrs ) { return $generator_field; } /** * Parse settings from generator_args object. * * @param array $generator_args Generator args object from block attributes. * * @return array Parsed settings with defaults applied. */ public function parse_generator_args( array $generator_args ): array { $settings = array(); $schema = $this->get_settings_schema(); foreach ( $schema as $key => $field_def ) { if ( isset( $generator_args[ $key ] ) ) { $settings[ $key ] = $generator_args[ $key ]; } else { $settings[ $key ] = $field_def['default'] ?? $this->get_default_for_type( $field_def['type'] ?? 'string' ); } } return $settings; } /** * Check if settings contain meaningful (non-default) values. * * @param array $settings Settings array to check. * * @return bool True if has meaningful values. */ protected function has_meaningful_values( array $settings ): bool { $schema = $this->get_settings_schema(); foreach ( $schema as $key => $field_def ) { $default = $field_def['default'] ?? $this->get_default_for_type( $field_def['type'] ?? 'string' ); if ( isset( $settings[ $key ] ) && $settings[ $key ] !== $default && '' !== $settings[ $key ] ) { return true; } } return false; } /** * Returns schema for JS localization. * Includes all necessary data for editor UI generation. * * @return array Schema data for JavaScript */ public function get_schema_for_js(): array { return array( 'id' => $this->get_id(), 'name' => $this->get_name(), 'schema' => $this->get_settings_schema(), 'supports_update' => $this->supports_auto_update(), 'update_context' => $this->get_auto_update_context_fields(), 'update_value_type' => $this->get_auto_update_value_type(), 'update_empty_context_policy' => $this->get_auto_update_empty_context_policy(), ); } }