`, so the builder can tell which one was * clicked, and a block that would print nothing shows a note instead. * * @param list $blocks * @param array $values * @param array{style?:array,answers?:list,editor?:bool} $options */ public static function html( array $blocks, array $values, array $options = [] ): string { $style = EmailTemplates::read_style( $options['style'] ?? [] ); $editor = ! empty( $options['editor'] ); $font = EmailTemplates::FONTS[ $style['font'] ] ?? EmailTemplates::FONTS['system']; $rows = ''; foreach ( $blocks as $block ) { $cell = self::block( $block, $values, $style, $options['answers'] ?? [] ); if ( '' === $cell && $editor ) { $cell = self::placeholder( $block['type'] ); } if ( '' === $cell ) { continue; } $align = (string) ( $block['props']['align'] ?? 'left' ); $row = '' . $cell . ''; $rows .= $editor ? '' . $row . '' : $row; } if ( '' === $rows && $editor ) { $rows = '' . esc_html__( 'Add a block to start the email.', 'flexatech-table-reservations' ) . ''; } // A whole email document (sent by wp_mail(), or shown in the email // editor's preview frame), so a ' . '' . '' . '' . '
' . $rows . '
'; } /** * One block's markup, '' when it has nothing to show: no logo, no link, * no notes, no answers. * * @param Block $block * @param array $values * @param array $style * @param list $answers */ private static function block( array $block, array $values, array $style, array $answers ): string { $props = $block['props']; $text = static fn( string $key ): string => (string) ( $props[ $key ] ?? '' ); $muted = 'color:#6b7280;'; switch ( $block['type'] ) { case 'logo': $logo = (int) get_theme_mod( 'custom_logo', 0 ); $src = $logo > 0 ? (string) wp_get_attachment_image_url( $logo, 'medium' ) : ''; return '' === $src ? '' : '' . esc_attr( wp_specialchars_decode( (string) get_option( 'blogname', '' ), ENT_QUOTES ) ) . ''; case 'heading': return '

' . self::fill( $text( 'text' ), $values ) . '

'; case 'text': return wp_kses_post( self::fill( $text( 'html' ), $values ) ); case 'button': $url = esc_url( self::fill( $text( 'url' ), $values, false ) ); if ( '' === $url ) { return ''; } $color = '' !== $text( 'color' ) ? $text( 'color' ) : (string) $style['brand']; return '' . self::fill( $text( 'text' ), $values ) . ''; case 'details': return self::rows( [ [ __( 'Date', 'flexatech-table-reservations' ), $values['booking_date'] ?? '' ], [ __( 'Time', 'flexatech-table-reservations' ), $values['booking_time'] ?? '' ], [ __( 'Guests', 'flexatech-table-reservations' ), $values['party_size'] ?? '' ], [ __( 'Reference', 'flexatech-table-reservations' ), '#' . ( $values['reservation_id'] ?? '' ) ], ] ); case 'contact': return self::rows( [ [ __( 'Name', 'flexatech-table-reservations' ), $values['customer_name'] ?? '' ], [ __( 'Phone', 'flexatech-table-reservations' ), $values['customer_phone'] ?? '' ], [ __( 'Email', 'flexatech-table-reservations' ), $values['customer_email'] ?? '' ], ] ); case 'answers': return self::rows( $answers ); case 'notes': return self::rows( [ [ __( 'Notes', 'flexatech-table-reservations' ), $values['notes'] ?? '' ] ] ); case 'image': $src = esc_url( self::fill( $text( 'url' ), $values, false ) ); if ( '' === $src ) { return ''; } $width = (int) ( $props['width'] ?? 0 ); $img = '' . esc_attr( $text( 'alt' ) ) . ' 0 ? ' width="' . $width . '"' : '' ) . ' style="display:inline-block;border:0;height:auto;max-width:100%;' . ( $width > 0 ? 'width:' . $width . 'px;' : 'width:100%;' ) . '">'; $link = esc_url( self::fill( $text( 'link' ), $values, false ) ); return '' === $link ? $img : '' . $img . ''; case 'divider': $color = '' !== $text( 'color' ) ? $text( 'color' ) : '#e5e7eb'; return '
'; case 'spacer': return '
 
'; case 'footer': $html = '' !== $text( 'html' ) ? wp_kses_post( self::fill( $text( 'html' ), $values ) ) : esc_html( wp_specialchars_decode( (string) get_option( 'blogname', '' ), ENT_QUOTES ) ); return '' === $html ? '' : '
' . $html . '
'; } return ''; } /** * Label and value lines, a line without a value left out. * * @param list $lines */ private static function rows( array $lines ): string { $out = ''; foreach ( $lines as [ $label, $value ] ) { if ( '' !== $value && '#' !== $value ) { $out .= '' . esc_html( $label ) . ': ' . esc_html( $value ) . '
'; } } return '' === $out ? '' : '

' . substr( $out, 0, -4 ) . '

'; } /** What the editor shows for a block that has nothing to print yet. */ private static function placeholder( string $type ): string { $notes = [ 'logo' => __( 'Your site logo goes here. Add one under Appearance, Customize, Site Identity.', 'flexatech-table-reservations' ), 'button' => __( 'This button shows when its link has a value, such as a reservation page for {manage_link}.', 'flexatech-table-reservations' ), 'answers' => __( 'The guest\'s answers to your own booking form fields go here. None of them is set to show in emails.', 'flexatech-table-reservations' ), 'notes' => __( 'The guest\'s notes go here, when they wrote some.', 'flexatech-table-reservations' ), 'image' => __( 'Pick an image.', 'flexatech-table-reservations' ), 'footer' => __( 'Footer text.', 'flexatech-table-reservations' ), ]; return '
' . esc_html( $notes[ $type ] ?? $type ) . '
'; } /** * Puts the values in for their {tokens}. Escaped for HTML unless the * result is a subject or a link, which the caller escapes its own way. * An unknown token stays as it is. * * @param array $values */ public static function fill( string $text, array $values, bool $html = true ): string { return (string) preg_replace_callback( '/\{([a-z_]+)\}/', static function ( array $matches ) use ( $values, $html ): string { if ( ! array_key_exists( $matches[1], $values ) ) { return $matches[0]; } return $html ? esc_html( $values[ $matches[1] ] ) : $values[ $matches[1] ]; }, $text ); } }