slots = $this->overlay->slot_tokens(); $styles = []; foreach ( self::SECTIONS as $slug => $section ) { // A section the theme does not have (an older theme without the secondary button) answers with // the empty default instead of the color array, and is not offered. if ( ! is_array( $read( $section['prefix'] . 'color' ) ) ) { continue; } $styles[ $slug ] = [ 'label' => $this->label( $slug ), 'class' => $section['class'], 'values' => $this->values( $read, $section['prefix'], $set, $slots ), ]; } return $styles; } /** * The preset label of a section. * * @since 3.8.0 * * @param string $slug The unprefixed section slug. * * @return string */ private function label( string $slug ): string { return $slug === 'secondary' ? __( 'Theme Secondary', 'kadence-blocks' ) : __( 'Theme Base', 'kadence-blocks' ); } /** * The values the theme renders for a section, keyed by the Button's preset properties. A setting the * theme leaves empty is omitted, so the theme's static stylesheet stays the source of that value. * * @since 3.8.0 * * @param callable $read Returns kadence()->option( $key ). * @param string $prefix The section's setting prefix. * @param array $set Slot slug => color of the active palette set. * @param array $slots Slot slug => token id. * * @return array */ private function values( callable $read, string $prefix, array $set, array $slots ): array { $values = []; $colors = [ 'button-text' => [ 'color', 'color' ], 'button-text-hover' => [ 'color', 'hover' ], 'button-bg' => [ 'background', 'color' ], 'button-bg-hover' => [ 'background', 'hover' ], 'button-border-color' => [ 'border_colors', 'color' ], 'button-border-hover-color' => [ 'border_colors', 'hover' ], ]; foreach ( $colors as $property => [ $setting, $key ] ) { $option = $read( $prefix . $setting ); $color = Style_Guide_Mapper::theme_color_value( is_array( $option ) ? $option[ $key ] ?? null : null, $set, $slots ); if ( $color !== null ) { $values[ $property ] = $color; } } $radius = $this->range( $read( $prefix . 'border_radius' ) ); if ( $radius !== null ) { $values['button-radius'] = [ $radius, $radius, $radius, $radius ]; } $padding = $this->measure( $read( $prefix . 'padding' ) ); if ( $padding !== null ) { $values['button-padding'] = $padding; } $border = $this->border( $read( $prefix . 'border' ) ); if ( $border !== null ) { $values['button-border-width'] = $border['width']; $values['button-border-style'] = $border['style']; } $shadow = $this->shadow( $read( $prefix . 'shadow' ), $set, $slots ); if ( $shadow !== null ) { $values['button-shadow'] = $shadow; } $shadow_hover = $this->shadow( $read( $prefix . 'shadow_hover' ), $set, $slots ); if ( $shadow_hover !== null ) { $values['button-shadow-hover'] = $shadow_hover; } return $values; } /** * A range setting ({ size: { desktop… }, unit: { desktop… } }) as one length, or null when unset. * * @since 3.8.0 * * @param mixed $option The setting value. * * @return string|null */ private function range( $option ): ?string { if ( ! is_array( $option ) || ! is_array( $option['size'] ?? null ) ) { return null; } $size = $option['size'][ self::DEVICE ] ?? null; if ( ! is_numeric( $size ) ) { return null; } return Cast::to_string( $size ) . $this->unit( $option ); } /** * A measure setting ({ size: { desktop: [ top, right, bottom, left ] }, unit: { desktop… } }) as four * lengths, or null unless every side is set. * * @since 3.8.0 * * @param mixed $option The setting value. * * @return string[]|null */ private function measure( $option ): ?array { if ( ! is_array( $option ) || ! is_array( $option['size'] ?? null ) ) { return null; } $sides = $option['size'][ self::DEVICE ] ?? null; if ( ! is_array( $sides ) || count( $sides ) !== 4 ) { return null; } $unit = $this->unit( $option ); $lengths = []; foreach ( $sides as $side ) { if ( ! is_numeric( $side ) ) { return null; } $lengths[] = Cast::to_string( $side ) . $unit; } return $lengths; } /** * A border setting ({ desktop: { width, unit, style } }) as its width and style, or null when the theme * emits no border (no style set). * * @since 3.8.0 * * @param mixed $option The setting value. * * @return array{width: string, style: string}|null */ private function border( $option ): ?array { if ( ! is_array( $option ) || ! is_array( $option[ self::DEVICE ] ?? null ) ) { return null; } $border = $option[ self::DEVICE ]; $style = Cast::to_string( $border['style'] ?? '' ); if ( $style === '' ) { return null; } $width = Cast::to_string( $border['width'] ?? '' ); $unit = Cast::to_string( $border['unit'] ?? '' ); return [ 'width' => ( $width === '' ? '0' : $width ) . ( $unit === '' ? 'px' : $unit ), 'style' => $style, ]; } /** * A shadow setting ({ color, hOffset, vOffset, blur, spread, inset, disabled }) as a shadow composite, * or null when the theme renders none. * * @since 3.8.0 * * @param mixed $option The setting value. * @param array $set Slot slug => color of the active palette set. * @param array $slots Slot slug => token id. * * @return array|null */ private function shadow( $option, array $set, array $slots ): ?array { if ( ! is_array( $option ) || ( $option['disabled'] ?? false ) === true ) { return null; } foreach ( [ 'color', 'hOffset', 'vOffset', 'blur', 'spread' ] as $field ) { if ( ! isset( $option[ $field ] ) ) { return null; } } $color = Style_Guide_Mapper::theme_color_value( $option['color'], $set, $slots ); return [ 'color' => $color ?? 'rgba(0,0,0,0)', 'offsetX' => $this->px( $option['hOffset'] ), 'offsetY' => $this->px( $option['vOffset'] ), 'blur' => $this->px( $option['blur'] ), 'spread' => $this->px( $option['spread'] ), 'inset' => ( $option['inset'] ?? false ) === true, ]; } /** * A shadow offset as a pixel length; the theme renders every non-numeric or empty offset as 0. * * @since 3.8.0 * * @param mixed $value The offset. * * @return string */ private function px( $value ): string { return ( is_numeric( $value ) ? Cast::to_string( $value ) : '0' ) . 'px'; } /** * The desktop unit of a range or measure setting, px when unset. * * @since 3.8.0 * * @param array $option The setting value. * * @return string */ private function unit( array $option ): string { $unit = is_array( $option['unit'] ?? null ) ? Cast::to_string( $option['unit'][ self::DEVICE ] ?? '' ) : ''; return $unit === '' ? 'px' : $unit; } /** * A reader over the theme's option(), or null when the Kadence theme is not active or not yet loaded. * * @since 3.8.0 * * @return callable|null */ private function theme_option_reader(): ?callable { /** @var callable-string $accessor The theme's accessor; guarded by function_exists() below. */ $accessor = self::THEME_ACCESSOR; // The theme is not in the plugin's static-analysis scope, so its class and function are unknown // symbols here. Both checks are real at runtime; the indirection keeps the analyzer from folding // them to a constant false and reporting the rest of this method as dead. if ( ! class_exists( self::THEME_CLASS ) || ! function_exists( $accessor ) ) { return null; } $theme = call_user_func( $accessor ); $option = [ $theme, 'option' ]; return is_object( $theme ) && is_callable( $option ) ? $option : null; } }