): string */ private $cardRenderer; /** * @param callable $cardRenderer Card renderer. * * @phpstan-param callable(array): string $cardRenderer */ public function __construct( callable $cardRenderer ) { $this->cardRenderer = $cardRenderer; } /** * Render `[affilun_product_card]`. * * @param mixed $attributes Shortcode attributes. */ public function renderCardShortcode( mixed $attributes ): string { $renderer = $this->cardRenderer; return $renderer( $this->cardAttributes( is_array( $attributes ) ? $attributes : array() ) ); } /** * Convert card shortcode attributes to block attributes. * * @param array $attributes Shortcode attributes. * * @return array */ public function cardAttributes( array $attributes ): array { $parsed = array(); $id = self::positiveInt( $attributes['product_id'] ?? $attributes['id'] ?? null ); if ( $id !== null ) { $parsed['productId'] = $id; } $template = self::enumString( $attributes['template'] ?? null, array( 'single-default-card', 'single-compact-card', 'single-minimal-cta', 'single-fallback-card' ) ); if ( $template !== null ) { $parsed['template'] = $template; } $this->copyBool( $attributes, $parsed, 'show_price', 'showPrice' ); $this->copyBool( $attributes, $parsed, 'show_brand', 'showBrand' ); $this->copyBool( $attributes, $parsed, 'show_description', 'showDescription' ); $this->copyBool( $attributes, $parsed, 'show_provider_badge', 'showProviderBadge' ); $this->copyBool( $attributes, $parsed, 'open_in_new_tab', 'openInNewTab' ); $this->copyBool( $attributes, $parsed, 'nofollow', 'nofollow' ); $this->copyBool( $attributes, $parsed, 'sponsored', 'sponsored' ); $this->copyBool( $attributes, $parsed, 'tracking_enabled', 'trackingEnabled' ); $this->copyString( $attributes, $parsed, 'microcopy', 'microcopyText', 80 ); $this->copyEnum( $attributes, $parsed, 'button_layout', 'buttonLayout', array( 'wrap', 'stacked', 'full' ) ); $this->copyEnum( $attributes, $parsed, 'button_shape', 'buttonShape', array( 'square', 'rounded', 'pill' ) ); $this->copyEnum( $attributes, $parsed, 'button_tone', 'buttonTone', array( 'brand', 'muted', 'monochrome' ) ); return $parsed; } /** * Copy a boolean attribute when supplied. * * @param array $source Source attributes. * @param array $target Target attributes. * @param string $sourceKey Source key. * @param string $targetKey Target key. */ private function copyBool( array $source, array &$target, string $sourceKey, string $targetKey ): void { $value = self::boolValue( $source[ $sourceKey ] ?? $source[ $targetKey ] ?? null ); if ( $value !== null ) { $target[ $targetKey ] = $value; } } /** * Copy a short text attribute when supplied. * * @param array $source Source attributes. * @param array $target Target attributes. * @param string $sourceKey Source key. * @param string $targetKey Target key. * @param int $maxLength Maximum length. */ private function copyString( array $source, array &$target, string $sourceKey, string $targetKey, int $maxLength ): void { $value = self::shortString( $source[ $sourceKey ] ?? $source[ $targetKey ] ?? null, $maxLength ); if ( $value !== null ) { $target[ $targetKey ] = $value; } } /** * Copy an enum attribute when supplied. * * @param array $source Source attributes. * @param array $target Target attributes. * @param string $sourceKey Source key. * @param string $targetKey Target key. * @param list $allowed Allowed values. */ private function copyEnum( array $source, array &$target, string $sourceKey, string $targetKey, array $allowed ): void { $value = self::enumString( $source[ $sourceKey ] ?? $source[ $targetKey ] ?? null, $allowed ); if ( $value !== null ) { $target[ $targetKey ] = $value; } } /** * Parse a positive integer. * * @param mixed $value Value. */ private static function positiveInt( mixed $value ): ?int { if ( is_int( $value ) && $value > 0 ) { return $value; } if ( is_string( $value ) && preg_match( '/\A\d+\z/', trim( $value ) ) === 1 ) { $parsed = (int) trim( $value ); return $parsed > 0 ? $parsed : null; } return null; } /** * Parse a boolean. * * @param mixed $value Value. */ private static function boolValue( mixed $value ): ?bool { if ( is_bool( $value ) ) { return $value; } if ( ! is_scalar( $value ) ) { return null; } $normalized = strtolower( trim( (string) $value ) ); if ( in_array( $normalized, array( '1', 'true', 'yes', 'on' ), true ) ) { return true; } if ( in_array( $normalized, array( '0', 'false', 'no', 'off' ), true ) ) { return false; } return null; } /** * Parse an enum string. * * @param mixed $value Value. * @param list $allowed Allowed values. */ private static function enumString( mixed $value, array $allowed ): ?string { if ( ! is_scalar( $value ) ) { return null; } $normalized = strtolower( trim( (string) $value ) ); return in_array( $normalized, $allowed, true ) ? $normalized : null; } /** * Parse a short string. * * @param mixed $value Value. * @param int $maxLength Maximum length. */ private static function shortString( mixed $value, int $maxLength ): ?string { if ( ! is_scalar( $value ) ) { return null; } $normalized = trim( preg_replace( '/\s+/', ' ', (string) $value ) ?? (string) $value ); if ( $normalized === '' ) { return null; } if ( function_exists( 'mb_strlen' ) && function_exists( 'mb_substr' ) ) { return mb_strlen( $normalized ) > $maxLength ? mb_substr( $normalized, 0, $maxLength ) : $normalized; } return strlen( $normalized ) > $maxLength ? substr( $normalized, 0, $maxLength ) : $normalized; } }