array( $this, 'render' ), ); if ( function_exists( 'register_block_type_from_metadata' ) ) { register_block_type_from_metadata( $metadata_path, $args ); return; } register_block_type( $metadata_path, $args ); } /** * Get common conditional attributes */ protected function get_conditional_attributes() { return array( 'conditional' => array( 'type' => 'array', 'default' => array() ), 'conditionalLogic' => array( 'type' => 'string', 'default' => 'and' ), // 'and' or 'or' 'conditionalEnabled' => array( 'type' => 'boolean', 'default' => false ), ); } /** * Get all common attributes including conditional */ protected function get_common_attributes() { return array_merge( $this->get_conditional_attributes(), array( 'labelPosition' => array( 'type' => 'string', 'default' => 'top' ), 'labelAnimation'=> array( 'type' => 'string', 'default' => '' ), 'helpText' => array( 'type' => 'string', 'default' => '' ), 'className' => array( 'type' => 'string', 'default' => '' ), 'width' => array( 'type' => 'number', 'default' => 100 ), 'padding' => array( 'type' => 'object', 'default' => null ), 'margin' => array( 'type' => 'object', 'default' => null ), 'style' => array( 'type' => 'object', 'default' => array() ), 'textColor' => array( 'type' => 'string', 'default' => '' ), 'labelColor' => array( 'type' => 'string', 'default' => '' ), 'borderColor' => array( 'type' => 'string', 'default' => '' ), 'borderWidth' => array( 'type' => 'number' ), 'borderStyle' => array( 'type' => 'string', 'default' => '' ), 'borderRadius' => array( 'type' => 'number' ), 'ariaLabel' => array( 'type' => 'string', 'default' => '' ), 'ariaDescribedby'=>array( 'type' => 'string', 'default' => '' ), 'labelTypography'=> array( 'type' => 'object', 'default' => null ), 'fieldTypography'=> array( 'type' => 'object', 'default' => null ), ) ); } /** * Check if field should be displayed based on conditional logic */ protected function should_display_field( $atts ) { if ( ! $this->is_conditional_active( $atts ) ) { return true; // No conditions = always show } $conditions = is_array( $atts['conditional'] ) ? $atts['conditional'] : array(); $logic = isset( $atts['conditionalLogic'] ) ? $atts['conditionalLogic'] : 'and'; if ( empty( $conditions ) ) { return true; } $results = array(); foreach ( $conditions as $condition ) { if ( ! isset( $condition['field'] ) || ! isset( $condition['operator'] ) ) { continue; } $field_name = $condition['field']; $operator = $condition['operator']; $expected_value = isset( $condition['value'] ) ? $condition['value'] : ''; // Get actual value from form submission or default $actual_value = $this->get_field_value( $field_name ); $result = $this->evaluate_condition( $actual_value, $operator, $expected_value ); $results[] = $result; } if ( empty( $results ) ) { return true; } // Apply logic (AND/OR) if ( $logic === 'or' ) { return in_array( true, $results, true ); } else { // 'and' return ! in_array( false, $results, true ); } } /** * Get field value for conditional evaluation */ protected function get_field_value( $field_name ) { // In edit mode, we don't have form data, so return empty // In frontend, this would get the actual submitted/current value // Read-only: compares a re-rendered form's submitted value with a display condition. // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( isset( $_POST[ $field_name ] ) ) { $value = map_deep( wp_unslash( $_POST[ $field_name ] ), 'sanitize_text_field' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( is_array( $value ) ) { $value = array_filter( $value, 'is_scalar' ); return implode( ',', array_map( 'strval', $value ) ); } return is_scalar( $value ) ? (string) $value : ''; } return ''; } /** * Evaluate a single condition */ protected function evaluate_condition( $actual_value, $operator, $expected_value ) { switch ( $operator ) { case 'equals': return $actual_value == $expected_value; case 'not_equals': return $actual_value != $expected_value; case 'contains': return strpos( $actual_value, $expected_value ) !== false; case 'not_contains': return strpos( $actual_value, $expected_value ) === false; case 'empty': return empty( $actual_value ); case 'not_empty': return ! empty( $actual_value ); case 'greater_than': return is_numeric( $actual_value ) && is_numeric( $expected_value ) && floatval( $actual_value ) > floatval( $expected_value ); case 'less_than': return is_numeric( $actual_value ) && is_numeric( $expected_value ) && floatval( $actual_value ) < floatval( $expected_value ); case 'starts_with': return strpos( $actual_value, $expected_value ) === 0; case 'ends_with': return substr( $actual_value, -strlen( $expected_value ) ) === $expected_value; default: return false; } } /** * Add conditional data attributes to field wrapper */ protected function get_conditional_data_attributes( $atts ) { if ( ! $this->is_conditional_active( $atts ) ) { return ''; } $conditional_data = array( 'conditions' => is_array( $atts['conditional'] ) ? $atts['conditional'] : array(), 'logic' => isset( $atts['conditionalLogic'] ) ? $atts['conditionalLogic'] : 'and', ); return 'data-conditional="' . esc_attr( wp_json_encode( $conditional_data ) ) . '"'; } /** * Add dynamic value data attributes to field wrapper */ protected function get_dynamic_data_attributes( $atts ) { if ( empty( $atts['dynamicEnabled'] ) ) { return ''; } $source = isset( $atts['dynamicSource'] ) ? sanitize_key( $atts['dynamicSource'] ) : ''; if ( '' === $source ) { return ''; } $payload = array( 'source' => $source, ); $target_name = ''; if ( isset( $atts['name'] ) && $atts['name'] !== '' ) { $target_name = sanitize_key( (string) $atts['name'] ); } elseif ( isset( $atts['group'] ) && $atts['group'] !== '' ) { $target_name = sanitize_key( (string) $atts['group'] ); } if ( '' !== $target_name ) { $payload['target'] = $target_name; } switch ( $source ) { case 'url': if ( isset( $atts['dynamicUrlParam'] ) && $atts['dynamicUrlParam'] !== '' ) { $payload['param'] = sanitize_text_field( (string) $atts['dynamicUrlParam'] ); } break; case 'current_user': if ( isset( $atts['dynamicUserKey'] ) && $atts['dynamicUserKey'] !== '' ) { $payload['key'] = sanitize_key( (string) $atts['dynamicUserKey'] ); } break; case 'current_post': if ( isset( $atts['dynamicPostKey'] ) && $atts['dynamicPostKey'] !== '' ) { $payload['key'] = sanitize_key( (string) $atts['dynamicPostKey'] ); } break; case 'post_meta': if ( isset( $atts['dynamicMetaKey'] ) && $atts['dynamicMetaKey'] !== '' ) { $payload['meta'] = sanitize_text_field( (string) $atts['dynamicMetaKey'] ); } break; case 'field': if ( isset( $atts['dynamicFieldName'] ) && $atts['dynamicFieldName'] !== '' ) { $payload['field'] = sanitize_key( (string) $atts['dynamicFieldName'] ); } break; } return 'data-stacklar-dynamic="' . esc_attr( wp_json_encode( $payload ) ) . '"'; } /** * Only block rendering on POST requests when conditions fail. * For GET requests always render so JS can control visibility. */ protected function allow_render_now( $atts ) { $is_post = ( isset( $_SERVER['REQUEST_METHOD'] ) && 'POST' === $_SERVER['REQUEST_METHOD'] ) || ! empty( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- only checks whether the request is a POST. if ( ! $is_post ) return true; return $this->should_display_field( $atts ); } /** * If conditional config exists, hide by default to avoid flash. */ protected function apply_hidden_if_conditional( $atts, array &$style_rules ) { if ( $this->is_conditional_active( $atts ) ) { $hasDisplay = false; foreach ( $style_rules as $r ) { if ( stripos($r, 'display:') !== false ) { $hasDisplay = true; break; } } if ( ! $hasDisplay ) { $style_rules[] = 'display:none;'; } } } /** * Build the opening wrapper tag and append style + data-conditional automatically */ protected function get_block_supports( $extra = array() ) { $defaults = array( 'typography' => array( 'fontSize' => true, 'lineHeight' => true, 'fontFamily' => true, 'fontStyle' => true, 'fontWeight' => true, 'textTransform' => true, 'letterSpacing' => true, 'textDecoration'=> true, 'textAlign' => true, 'textColumns' => true, 'writingMode' => true, '__experimentalToolsPanel' => false, '__experimentalFontFamily' => true, '__experimentalFontStyle' => true, '__experimentalFontWeight' => true, '__experimentalLetterSpacing' => true, '__experimentalTextTransform' => true, '__experimentalTextDecoration' => true, ), ); if ( empty( $extra ) ) { return $defaults; } return array_replace_recursive( $defaults, $extra ); } protected function get_typography_css( $atts ) { if ( empty( $atts['style'] ) || ! is_array( $atts['style'] ) || empty( $atts['style']['typography'] ) || ! function_exists( 'wp_style_engine_get_styles' ) ) { return ''; } $result = wp_style_engine_get_styles( array( 'typography' => $atts['style']['typography'] ) ); if ( empty( $result['css'] ) ) { return ''; } return $result['css']; } protected function normalize_responsive_device( $device ) { $device = strtolower( trim( (string) $device ) ); if ( strpos( $device, 'mobile' ) !== false ) { return 'mobile'; } if ( strpos( $device, 'tablet' ) !== false ) { return 'tablet'; } return 'desktop'; } protected function is_responsive_style_value( $value ) { return is_array( $value ) && ( array_key_exists( 'desktop', $value ) || array_key_exists( 'tablet', $value ) || array_key_exists( 'mobile', $value ) ); } protected function normalize_typography_value( $value ) { if ( isset( $value['typography'] ) && is_array( $value['typography'] ) ) { $value = $value['typography']; } if ( ! is_array( $value ) ) { return array(); } $normalized = array(); foreach ( $value as $key => $raw ) { if ( $raw === '' || $raw === null ) { continue; } $normalized[ $key ] = $raw; } return $normalized; } protected function normalize_box_value( $value ) { if ( ! is_array( $value ) ) { return array( 'top' => '', 'right' => '', 'bottom' => '', 'left' => '', 'unit' => 'px', ); } return array( 'top' => isset( $value['top'] ) ? $value['top'] : '', 'right' => isset( $value['right'] ) ? $value['right'] : '', 'bottom' => isset( $value['bottom'] ) ? $value['bottom'] : '', 'left' => isset( $value['left'] ) ? $value['left'] : '', 'unit' => ! empty( $value['unit'] ) ? trim( (string) $value['unit'] ) : 'px', ); } protected function resolve_responsive_style_value( $value, $device = 'desktop', $type = 'typography' ) { $normalizer = $type === 'box' ? 'normalize_box_value' : 'normalize_typography_value'; if ( $type === 'box' && is_string( $value ) ) { return trim( $value ); } if ( ! $this->is_responsive_style_value( $value ) ) { return $this->$normalizer( $value ); } $device = $this->normalize_responsive_device( $device ); $resolved = array(); $apply_branch = function( $branch ) use ( &$resolved, $value, $normalizer ) { if ( empty( $value[ $branch ] ) || ! is_array( $value[ $branch ] ) ) { return; } $resolved = array_merge( $resolved, $this->$normalizer( $value[ $branch ] ) ); }; $apply_branch( 'desktop' ); if ( $device === 'tablet' || $device === 'mobile' ) { $apply_branch( 'tablet' ); } if ( $device === 'mobile' ) { $apply_branch( 'mobile' ); } return $this->$normalizer( $resolved ); } protected function get_responsive_scalar_store( $atts ) { if ( empty( $atts['style'] ) || ! is_array( $atts['style'] ) || empty( $atts['style']['stacklarResponsive'] ) || ! is_array( $atts['style']['stacklarResponsive'] ) ) { return array(); } return $atts['style']['stacklarResponsive']; } protected function get_responsive_scalar_branches( $atts, $key ) { $store = $this->get_responsive_scalar_store( $atts ); if ( empty( $key ) || empty( $store[ $key ] ) || ! is_array( $store[ $key ] ) ) { return array(); } return $store[ $key ]; } protected function normalize_scalar_style_value( $value, $type = 'string' ) { if ( $type === 'number' ) { if ( is_numeric( $value ) ) { return floatval( $value ); } return null; } if ( is_scalar( $value ) ) { return trim( (string) $value ); } return ''; } protected function is_empty_scalar_style_value( $value, $type = 'string' ) { if ( $type === 'number' ) { return $value === null || $value === ''; } return trim( (string) $value ) === ''; } protected function has_responsive_scalar_style_value( $atts, $key, $type = 'string' ) { $branches = $this->get_responsive_scalar_branches( $atts, $key ); foreach ( array( 'tablet', 'mobile' ) as $device ) { if ( ! array_key_exists( $device, $branches ) ) { continue; } $normalized = $this->normalize_scalar_style_value( $branches[ $device ], $type ); if ( ! $this->is_empty_scalar_style_value( $normalized, $type ) ) { return true; } } return false; } protected function has_responsive_style_branch( $value, $device ) { $device = $this->normalize_responsive_device( $device ); if ( $device === 'desktop' || ! $this->is_responsive_style_value( $value ) || ! array_key_exists( $device, $value ) ) { return false; } $branch = $value[ $device ]; if ( is_array( $branch ) ) { foreach ( $branch as $item ) { if ( $item !== '' && $item !== null ) { return true; } } return false; } return trim( (string) $branch ) !== ''; } protected function has_responsive_scalar_style_branch( $atts, $key, $device, $type = 'string' ) { $device = $this->normalize_responsive_device( $device ); $branches = $this->get_responsive_scalar_branches( $atts, $key ); if ( $device === 'desktop' || ! array_key_exists( $device, $branches ) ) { return false; } $normalized = $this->normalize_scalar_style_value( $branches[ $device ], $type ); return ! $this->is_empty_scalar_style_value( $normalized, $type ); } protected function resolve_responsive_scalar_style_value( $atts, $key, $device = 'desktop', $type = 'string' ) { $resolved = $this->normalize_scalar_style_value( isset( $atts[ $key ] ) ? $atts[ $key ] : null, $type ); $branches = $this->get_responsive_scalar_branches( $atts, $key ); $device = $this->normalize_responsive_device( $device ); $apply_branch = function( $branch ) use ( &$resolved, $branches, $type ) { if ( ! array_key_exists( $branch, $branches ) ) { return; } $normalized = $this->normalize_scalar_style_value( $branches[ $branch ], $type ); if ( $this->is_empty_scalar_style_value( $normalized, $type ) ) { return; } $resolved = $normalized; }; $apply_branch( 'desktop' ); if ( $device === 'tablet' || $device === 'mobile' ) { $apply_branch( 'tablet' ); } if ( $device === 'mobile' ) { $apply_branch( 'mobile' ); } return $resolved; } protected function append_important_rules( array $rules ) { $important_rules = array(); foreach ( $rules as $rule ) { $rule = trim( (string) $rule ); if ( $rule === '' ) { continue; } if ( substr( $rule, -1 ) !== ';' ) { $rule .= ';'; } $important_rules[] = preg_replace( '/;$/', ' !important;', $rule ); } return $important_rules; } protected function combine_scoped_selectors( $scope_selector, $selector ) { $scope_selector = trim( (string) $scope_selector ); $selector = trim( (string) $selector ); if ( $scope_selector === '' || $selector === '' ) { return ''; } $parts = explode( ',', $selector ); $combined = array(); foreach ( $parts as $part ) { $part = trim( $part ); if ( $part === '' ) { continue; } if ( strpos( $part, '&' ) !== false ) { $combined[] = str_replace( '&', $scope_selector, $part ); continue; } if ( preg_match( '/^[>+~]/', $part ) ) { $combined[] = $scope_selector . $part; continue; } $combined[] = $scope_selector . ' ' . $part; } return implode( ', ', $combined ); } protected function build_responsive_selector_css( $selector, array $device_rules ) { $selector = trim( (string) $selector ); if ( $selector === '' ) { return ''; } $css = ''; $tablet_rules = stacklar_sanitize_css_declarations( isset( $device_rules['tablet'] ) ? $device_rules['tablet'] : array() ); $mobile_rules = stacklar_sanitize_css_declarations( isset( $device_rules['mobile'] ) ? $device_rules['mobile'] : array() ); if ( ! empty( $tablet_rules ) ) { $css .= '@media (min-width: 768px) and (max-width: 1024px){' . $selector . '{' . implode( '', $tablet_rules ) . '}}'; } if ( ! empty( $mobile_rules ) ) { $css .= '@media (max-width: 767px){' . $selector . '{' . implode( '', $mobile_rules ) . '}}'; } return $css; } protected function build_selector_rule_block( $selector, array $rules ) { $selector = trim( (string) $selector ); $rules = stacklar_sanitize_css_declarations( $rules ); if ( $selector === '' || empty( $rules ) ) { return ''; } return $selector . '{' . implode( '', $rules ) . '}'; } protected function custom_selector_has_responsive_value( $atts, array $config, $device ) { $attr = isset( $config['attr'] ) ? trim( (string) $config['attr'] ) : ''; $type = isset( $config['type'] ) ? strtolower( trim( (string) $config['type'] ) ) : 'string'; if ( $attr === '' ) { return false; } if ( $type === 'typography' || $type === 'box' ) { return $this->has_responsive_style_branch( isset( $atts[ $attr ] ) ? $atts[ $attr ] : null, $device ); } return $this->has_responsive_scalar_style_branch( $atts, $attr, $device, $type === 'number' ? 'number' : 'string' ); } protected function build_custom_selector_rules( $atts, array $config, $device = 'desktop' ) { $attr = isset( $config['attr'] ) ? trim( (string) $config['attr'] ) : ''; $type = isset( $config['type'] ) ? strtolower( trim( (string) $config['type'] ) ) : 'string'; $property = isset( $config['property'] ) ? trim( (string) $config['property'] ) : ''; $important = ! empty( $config['important'] ); if ( $attr === '' ) { return array(); } if ( $type === 'typography' ) { $rules = $this->get_typography_rules( isset( $atts[ $attr ] ) ? $atts[ $attr ] : null, $device ); return $important ? $this->append_important_rules( $rules ) : $rules; } if ( $type === 'box' ) { if ( $property === '' ) { return array(); } $value = isset( $atts[ $attr ] ) ? $this->css_box_value( $atts[ $attr ], $device ) : ''; if ( $value === '' ) { return array(); } $rules = array( $property . ':' . stacklar_sanitize_css_value( $value ) . ';' ); return $important ? $this->append_important_rules( $rules ) : $rules; } if ( $property === '' ) { return array(); } $scalar_type = $type === 'number' ? 'number' : 'string'; $value = $this->resolve_responsive_scalar_style_value( $atts, $attr, $device, $scalar_type ); if ( $scalar_type === 'number' ) { if ( $value === null ) { return array(); } $value = floatval( $value ); if ( isset( $config['min'] ) && is_numeric( $config['min'] ) ) { $value = max( floatval( $config['min'] ), $value ); } if ( isset( $config['max'] ) && is_numeric( $config['max'] ) ) { $value = min( floatval( $config['max'] ), $value ); } $value_css = (string) $value; if ( ! empty( $config['unit'] ) ) { $value_css .= trim( (string) $config['unit'] ); } } else { $value_css = trim( (string) $value ); if ( $value_css === '' ) { return array(); } } $rules = array( $property . ':' . stacklar_sanitize_css_value( $value_css ) . ';' ); return $important ? $this->append_important_rules( $rules ) : $rules; } protected function build_custom_selector_css( $wrapper_selector, $atts, array $rule_sets ) { $desktop_css = ''; $tablet_css = ''; $mobile_css = ''; foreach ( $rule_sets as $rule_set ) { if ( empty( $rule_set['selector'] ) || empty( $rule_set['attr'] ) ) { continue; } $selector = $this->combine_scoped_selectors( $wrapper_selector, $rule_set['selector'] ); if ( $selector === '' ) { continue; } if ( ! empty( $rule_set['include_desktop'] ) ) { $desktop_rules = $this->build_custom_selector_rules( $atts, $rule_set, 'desktop' ); if ( ! empty( $desktop_rules ) ) { $desktop_css .= $this->build_selector_rule_block( $selector, $desktop_rules ); } } if ( $this->custom_selector_has_responsive_value( $atts, $rule_set, 'tablet' ) ) { $tablet_rules = $this->build_custom_selector_rules( $atts, $rule_set, 'tablet' ); if ( ! empty( $tablet_rules ) ) { $tablet_css .= $this->build_selector_rule_block( $selector, $tablet_rules ); } } if ( $this->custom_selector_has_responsive_value( $atts, $rule_set, 'mobile' ) ) { $mobile_rules = $this->build_custom_selector_rules( $atts, $rule_set, 'mobile' ); if ( ! empty( $mobile_rules ) ) { $mobile_css .= $this->build_selector_rule_block( $selector, $mobile_rules ); } } } $css = $desktop_css; if ( $tablet_css !== '' ) { $css .= '@media (min-width: 768px) and (max-width: 1024px){' . $tablet_css . '}'; } if ( $mobile_css !== '' ) { $css .= '@media (max-width: 767px){' . $mobile_css . '}'; } return $css; } protected function build_field_responsive_css( $wrapper_selector, $atts, array $selectors ) { $css = ''; $wrapper_tablet_rules = array(); $wrapper_mobile_rules = array(); if ( $this->has_responsive_scalar_style_value( $atts, 'width', 'number' ) ) { $tablet_width = $this->resolve_responsive_scalar_style_value( $atts, 'width', 'tablet', 'number' ); $mobile_width = $this->resolve_responsive_scalar_style_value( $atts, 'width', 'mobile', 'number' ); if ( $tablet_width !== null ) { $wrapper_tablet_rules[] = '--stacklar-field-width:' . max( 1, min( 100, floatval( $tablet_width ) ) ) . '%;'; } if ( $mobile_width !== null ) { $wrapper_mobile_rules[] = '--stacklar-field-width:' . max( 1, min( 100, floatval( $mobile_width ) ) ) . '%;'; } } if ( $this->has_responsive_scalar_style_value( $atts, 'borderWidth', 'number' ) ) { $tablet_width = $this->resolve_responsive_scalar_style_value( $atts, 'borderWidth', 'tablet', 'number' ); $mobile_width = $this->resolve_responsive_scalar_style_value( $atts, 'borderWidth', 'mobile', 'number' ); if ( $tablet_width !== null ) { $wrapper_tablet_rules[] = '--stacklar-input-border-width:' . max( 0, floatval( $tablet_width ) ) . 'px;'; } if ( $mobile_width !== null ) { $wrapper_mobile_rules[] = '--stacklar-input-border-width:' . max( 0, floatval( $mobile_width ) ) . 'px;'; } } if ( $this->has_responsive_scalar_style_value( $atts, 'borderStyle', 'string' ) ) { $tablet_style = preg_replace( '/[^a-z-]/i', '', (string) $this->resolve_responsive_scalar_style_value( $atts, 'borderStyle', 'tablet', 'string' ) ); $mobile_style = preg_replace( '/[^a-z-]/i', '', (string) $this->resolve_responsive_scalar_style_value( $atts, 'borderStyle', 'mobile', 'string' ) ); if ( $tablet_style !== '' ) { $wrapper_tablet_rules[] = '--stacklar-input-border-style:' . stacklar_sanitize_css_value( $tablet_style ) . ';'; } if ( $mobile_style !== '' ) { $wrapper_mobile_rules[] = '--stacklar-input-border-style:' . stacklar_sanitize_css_value( $mobile_style ) . ';'; } } if ( $this->has_responsive_scalar_style_value( $atts, 'borderColor', 'string' ) ) { $tablet_color = trim( (string) $this->resolve_responsive_scalar_style_value( $atts, 'borderColor', 'tablet', 'string' ) ); $mobile_color = trim( (string) $this->resolve_responsive_scalar_style_value( $atts, 'borderColor', 'mobile', 'string' ) ); if ( $tablet_color !== '' ) { $wrapper_tablet_rules[] = '--stacklar-input-border-color:' . stacklar_sanitize_css_value( $tablet_color ) . ';'; } if ( $mobile_color !== '' ) { $wrapper_mobile_rules[] = '--stacklar-input-border-color:' . stacklar_sanitize_css_value( $mobile_color ) . ';'; } } if ( $this->has_responsive_scalar_style_value( $atts, 'borderRadius', 'number' ) ) { $tablet_radius = $this->resolve_responsive_scalar_style_value( $atts, 'borderRadius', 'tablet', 'number' ); $mobile_radius = $this->resolve_responsive_scalar_style_value( $atts, 'borderRadius', 'mobile', 'number' ); if ( $tablet_radius !== null ) { $wrapper_tablet_rules[] = '--stacklar-input-border-radius:' . max( 0, floatval( $tablet_radius ) ) . 'px;'; } if ( $mobile_radius !== null ) { $wrapper_mobile_rules[] = '--stacklar-input-border-radius:' . max( 0, floatval( $mobile_radius ) ) . 'px;'; } } $css .= $this->build_responsive_selector_css( $wrapper_selector, array( 'tablet' => $this->append_important_rules( $wrapper_tablet_rules ), 'mobile' => $this->append_important_rules( $wrapper_mobile_rules ), ) ); if ( ! empty( $selectors['label'] ) && ( $this->is_responsive_style_value( isset( $atts['labelTypography'] ) ? $atts['labelTypography'] : null ) || $this->has_responsive_scalar_style_value( $atts, 'labelColor', 'string' ) ) ) { $label_selector = $this->combine_scoped_selectors( $wrapper_selector, $selectors['label'] ); $label_tablet_rules = array(); $label_mobile_rules = array(); if ( $this->is_responsive_style_value( isset( $atts['labelTypography'] ) ? $atts['labelTypography'] : null ) ) { $label_tablet_rules = array_merge( $label_tablet_rules, $this->append_important_rules( $this->get_typography_rules( isset( $atts['labelTypography'] ) ? $atts['labelTypography'] : null, 'tablet' ) ) ); $label_mobile_rules = array_merge( $label_mobile_rules, $this->append_important_rules( $this->get_typography_rules( isset( $atts['labelTypography'] ) ? $atts['labelTypography'] : null, 'mobile' ) ) ); } if ( $this->has_responsive_scalar_style_value( $atts, 'labelColor', 'string' ) ) { $tablet_color = trim( (string) $this->resolve_responsive_scalar_style_value( $atts, 'labelColor', 'tablet', 'string' ) ); $mobile_color = trim( (string) $this->resolve_responsive_scalar_style_value( $atts, 'labelColor', 'mobile', 'string' ) ); if ( $tablet_color !== '' ) { $label_tablet_rules[] = 'color:' . stacklar_sanitize_css_value( $tablet_color ) . ' !important;'; } if ( $mobile_color !== '' ) { $label_mobile_rules[] = 'color:' . stacklar_sanitize_css_value( $mobile_color ) . ' !important;'; } } $label_css = $this->build_responsive_selector_css( $label_selector, array( 'tablet' => $label_tablet_rules, 'mobile' => $label_mobile_rules, ) ); $css .= $label_css; } if ( ! empty( $selectors['input'] ) ) { $input_selector = $this->combine_scoped_selectors( $wrapper_selector, $selectors['input'] ); $tablet_rules = array(); $mobile_rules = array(); if ( $this->is_responsive_style_value( isset( $atts['fieldTypography'] ) ? $atts['fieldTypography'] : null ) ) { $tablet_rules = array_merge( $tablet_rules, $this->append_important_rules( $this->get_typography_rules( $atts['fieldTypography'], 'tablet' ) ) ); $mobile_rules = array_merge( $mobile_rules, $this->append_important_rules( $this->get_typography_rules( $atts['fieldTypography'], 'mobile' ) ) ); } if ( $this->has_responsive_scalar_style_value( $atts, 'textColor', 'string' ) ) { $tablet_color = trim( (string) $this->resolve_responsive_scalar_style_value( $atts, 'textColor', 'tablet', 'string' ) ); $mobile_color = trim( (string) $this->resolve_responsive_scalar_style_value( $atts, 'textColor', 'mobile', 'string' ) ); if ( $tablet_color !== '' ) { $tablet_rules[] = 'color:' . stacklar_sanitize_css_value( $tablet_color ) . ' !important;'; } if ( $mobile_color !== '' ) { $mobile_rules[] = 'color:' . stacklar_sanitize_css_value( $mobile_color ) . ' !important;'; } } if ( $this->is_responsive_style_value( isset( $atts['padding'] ) ? $atts['padding'] : null ) ) { $tablet_padding = $this->css_box_value( $atts['padding'], 'tablet' ); $mobile_padding = $this->css_box_value( $atts['padding'], 'mobile' ); if ( $tablet_padding !== '' ) { $tablet_rules[] = 'padding:' . stacklar_sanitize_css_value( $tablet_padding ) . ' !important;'; } if ( $mobile_padding !== '' ) { $mobile_rules[] = 'padding:' . stacklar_sanitize_css_value( $mobile_padding ) . ' !important;'; } } if ( $this->is_responsive_style_value( isset( $atts['margin'] ) ? $atts['margin'] : null ) ) { $tablet_margin = $this->css_box_value( $atts['margin'], 'tablet' ); $mobile_margin = $this->css_box_value( $atts['margin'], 'mobile' ); if ( $tablet_margin !== '' ) { $tablet_rules[] = 'margin:' . stacklar_sanitize_css_value( $tablet_margin ) . ' !important;'; } if ( $mobile_margin !== '' ) { $mobile_rules[] = 'margin:' . stacklar_sanitize_css_value( $mobile_margin ) . ' !important;'; } } $css .= $this->build_responsive_selector_css( $input_selector, array( 'tablet' => $tablet_rules, 'mobile' => $mobile_rules, ) ); } return $css; } /** * Get typography CSS rules from typography object */ protected function get_typography_rules( $typography_obj, $device = 'desktop' ) { $typography_obj = $this->resolve_responsive_style_value( $typography_obj, $device, 'typography' ); if ( empty( $typography_obj ) || ! is_array( $typography_obj ) ) { return array(); } $rules = array(); // Font family if ( ! empty( $typography_obj['fontFamily'] ) ) { $rules[] = 'font-family:' . stacklar_sanitize_css_value( $typography_obj['fontFamily'] ) . ';'; } // Font size if ( ! empty( $typography_obj['fontSize'] ) ) { $rules[] = 'font-size:' . stacklar_sanitize_css_value( $typography_obj['fontSize'] ) . ';'; } // Font style if ( ! empty( $typography_obj['fontStyle'] ) ) { $rules[] = 'font-style:' . stacklar_sanitize_css_value( $typography_obj['fontStyle'] ) . ';'; } // Font weight if ( ! empty( $typography_obj['fontWeight'] ) ) { $rules[] = 'font-weight:' . stacklar_sanitize_css_value( $typography_obj['fontWeight'] ) . ';'; } // Line height if ( ! empty( $typography_obj['lineHeight'] ) ) { $rules[] = 'line-height:' . stacklar_sanitize_css_value( $typography_obj['lineHeight'] ) . ';'; } // Letter spacing if ( ! empty( $typography_obj['letterSpacing'] ) ) { $rules[] = 'letter-spacing:' . stacklar_sanitize_css_value( $typography_obj['letterSpacing'] ) . ';'; } // Text transform if ( ! empty( $typography_obj['textTransform'] ) ) { $rules[] = 'text-transform:' . stacklar_sanitize_css_value( $typography_obj['textTransform'] ) . ';'; } // Text decoration if ( ! empty( $typography_obj['textDecoration'] ) ) { $rules[] = 'text-decoration:' . stacklar_sanitize_css_value( $typography_obj['textDecoration'] ) . ';'; } // Text align if ( ! empty( $typography_obj['textAlign'] ) ) { $rules[] = 'text-align:' . stacklar_sanitize_css_value( $typography_obj['textAlign'] ) . ';'; } return $rules; } /** * Get label style attribute with typography */ protected function get_label_style( $atts ) { $rules = array(); // Add label color $labelCol = trim( (string) $this->resolve_responsive_scalar_style_value( $atts, 'labelColor', 'desktop', 'string' ) ); if ( $labelCol !== '' ) { $rules[] = 'color:' . stacklar_sanitize_css_value( $labelCol ) . ';'; } // Add label typography if ( ! empty( $atts['labelTypography'] ) ) { $typo_rules = $this->get_typography_rules( $atts['labelTypography'] ); $rules = array_merge( $rules, $typo_rules ); } return $this->build_style_attribute( $rules ); } /** * Get input style rules with typography */ protected function get_input_style_rules( $atts ) { $rules = array(); // Add field typography first if ( ! empty( $atts['fieldTypography'] ) ) { $typo_rules = $this->get_typography_rules( $atts['fieldTypography'] ); $rules = array_merge( $rules, $typo_rules ); } // Add text color (override typography color if exists) $textCol = trim( (string) $this->resolve_responsive_scalar_style_value( $atts, 'textColor', 'desktop', 'string' ) ); if ( $textCol !== '' ) { $rules[] = 'color:' . stacklar_sanitize_css_value( $textCol ) . ';'; } // Add spacing $spacing_rules = $this->get_input_spacing_rules( $atts ); if ( ! empty( $spacing_rules ) ) { $rules = array_merge( $rules, $spacing_rules ); } return $rules; } protected function build_field_wrapper_class( $modifier, $atts ) { $class = 'stacklar-field stacklar-field--' . sanitize_html_class( $modifier ); if ( ! empty( $atts['className'] ) ) { $class .= ' ' . sanitize_html_class( $atts['className'] ); } return $class; } protected function get_field_wrapper_style_rules( $atts ) { $width = isset( $atts['width'] ) ? intval( $atts['width'] ) : 100; $rules = array( '--stacklar-field-width:' . max( 1, min( 100, $width ) ) . '%;', ); if ( ! empty( $atts['hidden'] ) ) { $rules[] = 'display:none;'; } return $rules; } protected function get_field_label_position( $atts, $default = 'top' ) { return isset( $atts['labelPosition'] ) ? (string) $atts['labelPosition'] : $default; } protected function get_field_label_position_class( $atts ) { $position = isset( $atts['labelPosition'] ) ? sanitize_key( (string) $atts['labelPosition'] ) : 'top'; $allowed = array( 'top', 'left', 'right', 'hidden' ); if ( ! in_array( $position, $allowed, true ) ) { $position = 'top'; } return 'stacklar-field--label-position-' . sanitize_html_class( $position ); } protected function get_field_label_animation_classes( $atts ) { $animation = isset( $atts['labelAnimation'] ) ? sanitize_key( (string) $atts['labelAnimation'] ) : ''; $allowed = array( 'float', 'lift', 'shrink', 'fade', 'slide', 'border-cutout' ); if ( $animation === '' || ! in_array( $animation, $allowed, true ) ) { return ''; } return 'stacklar-field--label-animated stacklar-field--label-animation-' . sanitize_html_class( $animation ); } protected function get_field_boolean_attr( $atts, $key, $attribute = '' ) { if ( empty( $atts[ $key ] ) ) { return ''; } return $attribute !== '' ? $attribute : $key; } protected function get_field_aria_label_attr( $atts ) { return isset( $atts['ariaLabel'] ) && $atts['ariaLabel'] !== '' ? 'aria-label="' . esc_attr( $atts['ariaLabel'] ) . '"' : ''; } protected function get_field_help_context( $atts, $name, $prefix = 'help' ) { $help = isset( $atts['helpText'] ) ? esc_html( $atts['helpText'] ) : ''; $help_id = ''; if ( $help !== '' ) { $help_id = $prefix . '-' . $name . '-' . wp_generate_password( 6, false, false ); } $aria_describedby = ''; if ( $help_id !== '' ) { $aria_describedby = 'aria-describedby="' . esc_attr( $help_id ) . '"'; } if ( ! empty( $atts['ariaDescribedby'] ) ) { $aria_describedby = 'aria-describedby="' . esc_attr( $atts['ariaDescribedby'] . ( $help_id ? ' ' . $help_id : '' ) ) . '"'; } return array( 'help' => $help, 'help_id' => $help_id, 'aria_describedby' => $aria_describedby, 'help_html' => $help !== '' ? '' . $help . '' : '', ); } protected function render_field_label( $id, $label, $required, $label_position, $atts ) { if ( $label_position === 'hidden' ) { return ''; } return ''; } protected function render_input_wrap( $content ) { return '