'object', 'properties' => array( 'presentation' => array( 'type' => 'string', 'enum' => array( 'radio', 'select' ), ), 'allow_none' => array( 'type' => 'boolean' ), 'options' => array( 'type' => 'array', 'maxItems' => self::MAX_OPTIONS, 'items' => array( 'type' => 'object', 'properties' => array( 'id' => array( 'type' => 'string' ), 'label' => array( 'type' => 'string' ), 'pricing' => array( 'type' => 'object' ), ), ), ), ), ); } /** * {@inheritDoc} * * Option pricing blocks are normalised afterwards by Core\Schema with the pricing registry. * * @param array $config Raw config. */ public function sanitize_config( array $config ): array { $options = array(); $seen = array(); foreach ( array_slice( (array) ( $config['options'] ?? array() ), 0, self::MAX_OPTIONS ) as $option ) { if ( ! is_array( $option ) ) { continue; } $label = sanitize_text_field( (string) ( $option['label'] ?? '' ) ); if ( '' === $label ) { continue; } $id = sanitize_key( (string) ( $option['id'] ?? '' ) ); if ( ! preg_match( '/^opt_[a-z0-9]{1,16}$/', $id ) || isset( $seen[ $id ] ) ) { $id = 'opt_' . bin2hex( random_bytes( 3 ) ); } $seen[ $id ] = true; $options[] = array( 'id' => $id, 'label' => $label, 'pricing' => is_array( $option['pricing'] ?? null ) ? $option['pricing'] : array(), ); } $presentation = (string) ( $config['presentation'] ?? 'radio' ); return array( 'presentation' => in_array( $presentation, array( 'radio', 'select' ), true ) ? $presentation : 'radio', 'allow_none' => rest_sanitize_boolean( $config['allow_none'] ?? true ), 'options' => $options, ); } /** * Options of the extra. * * @param Extra $extra Extra. * @return array */ private function options( Extra $extra ): array { $options = array(); foreach ( (array) ( $extra->type_config['options'] ?? array() ) as $option ) { if ( is_array( $option ) && ! empty( $option['id'] ) ) { $options[] = array( 'id' => (string) $option['id'], 'label' => (string) ( $option['label'] ?? '' ), 'pricing' => is_array( $option['pricing'] ?? null ) ? $option['pricing'] : array(), ); } } return $options; } /** * Finds an option. * * @param Extra $extra Extra. * @param mixed $id Option id. */ private function option( Extra $extra, mixed $id ): ?array { if ( ! is_string( $id ) ) { return null; } foreach ( $this->options( $extra ) as $option ) { if ( $option['id'] === $id ) { return $option; } } return null; } /** * {@inheritDoc} * * @param mixed $raw Raw value. * @param Extra $extra Extra. */ public function sanitize_value( mixed $raw, Extra $extra ): mixed { if ( ! is_string( $raw ) ) { return null; } $id = sanitize_key( $raw ); return null !== $this->option( $extra, $id ) ? $id : null; } /** * {@inheritDoc} * * @param Extra $extra Extra. * @param Selection $selection Selection. */ public function is_chargeable( Extra $extra, Selection $selection ): bool { return null !== $this->option( $extra, $selection->value ); } /** * {@inheritDoc} * * @param Extra $extra Extra. * @param Selection $selection Selection. */ public function pricing_for( Extra $extra, Selection $selection ): array { $option = $this->option( $extra, $selection->value ); if ( $option && array() !== $option['pricing'] && ! empty( $option['pricing']['method'] ) ) { return $option['pricing']; } return $extra->pricing; } /** * {@inheritDoc} * * @param Extra $extra Extra. * @param Selection $selection Selection. */ public function display_value( Extra $extra, Selection $selection ): string { $option = $this->option( $extra, $selection->value ); return $option ? $option['label'] : ''; } /** * {@inheritDoc} * * @param Extra $extra Extra. * @param Selection $selection Selection. */ public function fee_name( Extra $extra, Selection $selection ): string { $value = $this->display_value( $extra, $selection ); if ( '' === $value ) { return $extra->label; } return sprintf( /* translators: 1: extra label, 2: chosen option. */ __( '%1$s: %2$s', 'extrova' ), $extra->label, $value ); } /** * {@inheritDoc} * * @param Extra $extra Extra. * @param Context $context Context. */ public function to_client( Extra $extra, Context $context ): array { return array( 'presentation' => (string) ( $extra->type_config['presentation'] ?? 'radio' ), 'allow_none' => ! empty( $extra->type_config['allow_none'] ), 'options' => array_map( static fn( array $option ): array => array( 'id' => $option['id'], 'label' => $option['label'], ), $this->options( $extra ) ), ); } /** * {@inheritDoc} * * @param Extra $extra Extra. */ public function preview_selections( Extra $extra ): array { $previews = array(); foreach ( $this->options( $extra ) as $option ) { $previews[ $option['id'] ] = $option['id']; } return $previews; } /** * {@inheritDoc} * * @param Extra $extra Extra. * @param Selection|null $selection Selection. * @param Context $context Context. */ public function render_classic( Extra $extra, ?Selection $selection, Context $context ): string { $current = $selection ? $selection->value : null; $allow_none = ! empty( $extra->type_config['allow_none'] ) || ! $extra->required; $presentation = (string) ( $extra->type_config['presentation'] ?? 'radio' ); $options = $this->options( $extra ); if ( 'select' === $presentation ) { $id = $this->field_id( $extra ); $html = '
'; $html .= ''; $html .= $this->description_html( $extra ); $html .= '
'; return $html; } $html = '
describedby_attr( $extra ) . '>'; $html .= '' . esc_html( $extra->label ) . $this->required_marker_html( $extra ) . ''; $html .= $this->description_html( $extra ); $html .= '
'; return $html; } }