<?php
/**
 * Single Testimonial element.
 *
 * A customer quote with avatar, name, role and optional star rating. Eight
 * layout skins (chosen from a visual picker), an optional decorative quote
 * mark, and per-part styling for the card, stars, avatar and text. Not part
 * of WPBakery core.
 *
 * @package CraftAddonsForWPBakery
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class CAFW_Element_Single_Testimonial
 */
class CAFW_Element_Single_Testimonial extends CAFW_Element_Base {

	/**
	 * Valid layout skins.
	 *
	 * @var array
	 */
	private static $skins = array( 'classic', 'header', 'inline', 'header-rated', 'stacked', 'aside-left', 'minimal', 'minimal-rated' );

	/**
	 * {@inheritDoc}
	 */
	public function get_shortcode_tag() {
		return 'cafw_single_testimonial';
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_label() {
		return esc_html__( 'Single Testimonial', 'craft-addons-for-wpbakery' );
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_description() {
		return esc_html__( 'A quote with avatar and rating.', 'craft-addons-for-wpbakery' );
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_admin_icon() {
		return 'dashicons-format-quote';
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_editor_icon() {
		return 'cafw-vc-icon cafw-vc-icon-testimonial';
	}

	/**
	 * {@inheritDoc}
	 */
	protected function get_defaults() {
		return array(
			'layout'            => 'classic',
			'quote'             => esc_html__( 'This is exactly what we were looking for - simple, fast and beautifully made. Highly recommended.', 'craft-addons-for-wpbakery' ),
			'author'            => esc_html__( 'Jane Doe', 'craft-addons-for-wpbakery' ),
			'role'              => esc_html__( 'CEO, Acme Inc.', 'craft-addons-for-wpbakery' ),
			'image'             => '',
			'rating'            => '5',
			'star_pos'          => '',
			'role_pos'          => '',
			'align'             => 'left',
			'card_bg'           => '',
			'card_border'       => '',
			'card_radius'       => '',
			'card_shadow'       => 'soft',
			'star_color'        => '',
			'star_size'         => '',
			'avatar_size'       => '',
			'avatar_shape'      => 'circle',
			'mark_show'         => '',
			'mark_color'        => '',
			'mark_size'         => '',
			'role_color'        => '',
			'quote_font'        => '',
			'quote_google_font' => '',
			'name_font'         => '',
			'name_google_font'  => '',
		);
	}

	/* ----------------------------------------------------------------------
	 * Layout picker thumbnails (small wireframe SVGs, one per skin).
	 * ------------------------------------------------------------------- */

	/**
	 * An avatar circle for a thumbnail.
	 *
	 * @param int $cx Centre x.
	 * @param int $cy Centre y.
	 * @param int $r  Radius.
	 * @return string
	 */
	private function svg_avatar( $cx, $cy, $r = 5 ) {
		return sprintf( '<circle cx="%d" cy="%d" r="%d" fill="#7d8694"/>', $cx, $cy, $r );
	}

	/**
	 * A text line for a thumbnail.
	 *
	 * @param int   $x Left.
	 * @param int   $y Top.
	 * @param int   $w Width.
	 * @param float $h Height.
	 * @return string
	 */
	private function svg_line( $x, $y, $w, $h = 3 ) {
		return sprintf( '<rect x="%d" y="%d" width="%d" height="%s" rx="1.2" fill="#b6bcc6"/>', $x, $y, $w, $h );
	}

	/**
	 * A 5-star strip for a thumbnail.
	 *
	 * @param int $x Left.
	 * @param int $y Top.
	 * @return string
	 */
	private function svg_stars( $x, $y ) {
		$out = '';
		for ( $i = 0; $i < 5; $i++ ) {
			$out .= sprintf( '<rect x="%d" y="%d" width="3" height="3" rx="0.8" fill="#7d8694"/>', $x + ( $i * 5 ), $y );
		}
		return $out;
	}

	/**
	 * Name + role line pair for a thumbnail.
	 *
	 * @param int $x Left.
	 * @param int $y Top.
	 * @return string
	 */
	private function svg_meta( $x, $y ) {
		return $this->svg_line( $x, $y, 14, 2.5 ) . $this->svg_line( $x, $y + 5, 9, 2.5 );
	}

	/**
	 * Decorative quote mark for a thumbnail.
	 *
	 * @param int $x Left.
	 * @param int $y Baseline.
	 * @return string
	 */
	private function svg_mark( $x = 61, $y = 18 ) {
		return sprintf( '<text x="%d" y="%d" font-family="Georgia,serif" font-size="17" fill="#9aa1ac">&#8221;</text>', $x, $y );
	}

	/**
	 * Wrap thumbnail parts in the SVG canvas.
	 *
	 * @param string $parts Inner SVG markup.
	 * @return string
	 */
	private function svg_thumb( $parts ) {
		return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 76 56" aria-hidden="true" focusable="false">' . $parts . '</svg>';
	}

	/**
	 * The layout picker options: skin => label + wireframe thumbnail.
	 *
	 * @return array
	 */
	private function layout_options() {
		return array(
			'classic'     => array(
				'label' => esc_html__( 'Classic - stars on top', 'craft-addons-for-wpbakery' ),
				'thumb' => $this->svg_thumb(
					$this->svg_stars( 8, 9 ) . $this->svg_mark()
					. $this->svg_line( 8, 19, 60 ) . $this->svg_line( 8, 25, 52 )
					. $this->svg_avatar( 13, 43 ) . $this->svg_meta( 22, 39 )
				),
			),
			'header'      => array(
				'label' => esc_html__( 'Person on top', 'craft-addons-for-wpbakery' ),
				'thumb' => $this->svg_thumb(
					$this->svg_avatar( 13, 13 ) . $this->svg_meta( 22, 9 ) . $this->svg_mark()
					. $this->svg_line( 8, 27, 60 ) . $this->svg_line( 8, 33, 52 )
					. $this->svg_stars( 8, 43 )
				),
			),
			'inline'      => array(
				'label' => esc_html__( 'Compact header', 'craft-addons-for-wpbakery' ),
				'thumb' => $this->svg_thumb(
					$this->svg_avatar( 13, 13 ) . $this->svg_line( 22, 8, 14, 2.5 ) . $this->svg_stars( 22, 14 )
					. $this->svg_line( 8, 27, 60 ) . $this->svg_line( 8, 33, 52 )
				),
			),
			'header-rated' => array(
				'label' => esc_html__( 'Person top, stars right', 'craft-addons-for-wpbakery' ),
				'thumb' => $this->svg_thumb(
					$this->svg_avatar( 13, 13 ) . $this->svg_meta( 22, 9 ) . $this->svg_stars( 45, 9 )
					. $this->svg_line( 8, 27, 60 ) . $this->svg_line( 8, 33, 52 )
				),
			),
			'stacked'     => array(
				'label' => esc_html__( 'Stacked', 'craft-addons-for-wpbakery' ),
				'thumb' => $this->svg_thumb(
					$this->svg_avatar( 38, 11 ) . $this->svg_stars( 27, 20 )
					. $this->svg_line( 12, 29, 52 ) . $this->svg_line( 18, 35, 40 )
					. $this->svg_line( 31, 44, 14, 2.5 ) . $this->svg_line( 33, 49, 10, 2.5 )
				),
			),
			'aside-left'  => array(
				'label' => esc_html__( 'Avatar left', 'craft-addons-for-wpbakery' ),
				'thumb' => $this->svg_thumb(
					$this->svg_avatar( 15, 16, 6 )
					. $this->svg_line( 28, 10, 40 ) . $this->svg_line( 28, 16, 32 )
					. $this->svg_stars( 28, 26 ) . $this->svg_meta( 28, 38 )
				),
			),
			'minimal'     => array(
				'label' => esc_html__( 'Quote first', 'craft-addons-for-wpbakery' ),
				'thumb' => $this->svg_thumb(
					$this->svg_line( 8, 10, 60 ) . $this->svg_line( 8, 16, 52 ) . $this->svg_mark( 61, 34 )
					. $this->svg_avatar( 12, 42, 4 ) . $this->svg_line( 20, 37, 14, 2.5 ) . $this->svg_stars( 20, 43 )
				),
			),
			'minimal-rated' => array(
				'label' => esc_html__( 'Quote first, stars right', 'craft-addons-for-wpbakery' ),
				'thumb' => $this->svg_thumb(
					$this->svg_line( 8, 10, 60 ) . $this->svg_line( 8, 16, 52 ) . $this->svg_mark( 61, 30 )
					. $this->svg_avatar( 12, 42, 4 ) . $this->svg_meta( 20, 38 ) . $this->svg_stars( 45, 38 )
				),
			),
		);
	}

	/**
	 * {@inheritDoc}
	 */
	public function get_params() {
		$styles = esc_html__( 'Styles', 'craft-addons-for-wpbakery' );

		$mark_dep = array(
			'element' => 'mark_show',
			'value'   => 'yes',
		);

		return array_merge(
			array(
				array(
					'type'        => 'cafw_layout',
					'heading'     => esc_html__( 'Layout', 'craft-addons-for-wpbakery' ),
					'param_name'  => 'layout',
					'options'     => $this->layout_options(),
					'value'       => 'classic',
					'resets'      => array( 'star_pos', 'role_pos', 'align' ),
					'description' => esc_html__( 'Ready-made arrangements you can start from - picking one is optional, and everything (content, colors, star and role placement) stays fully editable afterwards.', 'craft-addons-for-wpbakery' ),
				),
				array(
					'type'        => 'textarea',
					'heading'     => esc_html__( 'Quote', 'craft-addons-for-wpbakery' ),
					'param_name'  => 'quote',
					'value'       => esc_html__( 'This is exactly what we were looking for - simple, fast and beautifully made. Highly recommended.', 'craft-addons-for-wpbakery' ),
					'admin_label' => true,
				),
				array(
					'type'       => 'attach_image',
					'heading'    => esc_html__( 'Avatar', 'craft-addons-for-wpbakery' ),
					'param_name' => 'image',
				),
				array(
					'type'       => 'textfield',
					'heading'    => esc_html__( 'Author name', 'craft-addons-for-wpbakery' ),
					'param_name' => 'author',
					'value'      => esc_html__( 'Jane Doe', 'craft-addons-for-wpbakery' ),
				),
				array(
					'type'        => 'textfield',
					'heading'     => esc_html__( 'Author role', 'craft-addons-for-wpbakery' ),
					'param_name'  => 'role',
					'value'       => esc_html__( 'CEO, Acme Inc.', 'craft-addons-for-wpbakery' ),
					'description' => esc_html__( 'e.g. "CEO, Acme Inc."', 'craft-addons-for-wpbakery' ),
				),
				array(
					'type'             => 'dropdown',
					'heading'          => esc_html__( 'Rating', 'craft-addons-for-wpbakery' ),
					'param_name'       => 'rating',
					'value'            => array(
						esc_html__( '5 stars', 'craft-addons-for-wpbakery' ) => '5',
						esc_html__( '4 stars', 'craft-addons-for-wpbakery' ) => '4',
						esc_html__( '3 stars', 'craft-addons-for-wpbakery' ) => '3',
						esc_html__( '2 stars', 'craft-addons-for-wpbakery' ) => '2',
						esc_html__( '1 star', 'craft-addons-for-wpbakery' )  => '1',
						esc_html__( 'No rating', 'craft-addons-for-wpbakery' ) => '0',
					),
					'std'              => '5',
					'edit_field_class' => 'vc_col-sm-6 vc_column',
				),
				array(
					'type'             => 'dropdown',
					'heading'          => esc_html__( 'Star position', 'craft-addons-for-wpbakery' ),
					'param_name'       => 'star_pos',
					'value'            => array(
						esc_html__( 'Where the layout puts them', 'craft-addons-for-wpbakery' ) => '',
						esc_html__( 'Under the author name', 'craft-addons-for-wpbakery' )      => 'meta',
					),
					'std'              => '',
					'edit_field_class' => 'vc_col-sm-6 vc_column',
				),
				array(
					'type'             => 'dropdown',
					'heading'          => esc_html__( 'Author role position', 'craft-addons-for-wpbakery' ),
					'param_name'       => 'role_pos',
					'value'            => array(
						esc_html__( 'Where the layout puts it', 'craft-addons-for-wpbakery' )  => '',
						esc_html__( 'Below the author name', 'craft-addons-for-wpbakery' )    => 'below',
						esc_html__( 'Next to the author name', 'craft-addons-for-wpbakery' )  => 'inline',
					),
					'std'              => '',
					'edit_field_class' => 'vc_col-sm-6 vc_column',
				),
				array(
					'type'             => 'dropdown',
					'heading'          => esc_html__( 'Content alignment', 'craft-addons-for-wpbakery' ),
					'param_name'       => 'align',
					'value'            => array(
						esc_html__( 'Left', 'craft-addons-for-wpbakery' )   => 'left',
						esc_html__( 'Center', 'craft-addons-for-wpbakery' ) => 'center',
						esc_html__( 'Right', 'craft-addons-for-wpbakery' )  => 'right',
					),
					'std'              => 'left',
					'edit_field_class' => 'vc_col-sm-6 vc_column',
				),
				// --- Card (Styles) ---
				array(
					'type'             => 'colorpicker',
					'heading'          => esc_html__( 'Card background', 'craft-addons-for-wpbakery' ),
					'param_name'       => 'card_bg',
					'group'            => $styles,
					'edit_field_class' => 'vc_col-sm-6 vc_column',
				),
				array(
					'type'             => 'colorpicker',
					'heading'          => esc_html__( 'Card border color', 'craft-addons-for-wpbakery' ),
					'param_name'       => 'card_border',
					'group'            => $styles,
					'edit_field_class' => 'vc_col-sm-6 vc_column',
				),
				array(
					'type'             => 'textfield',
					'heading'          => esc_html__( 'Card radius (px)', 'craft-addons-for-wpbakery' ),
					'param_name'       => 'card_radius',
					'group'            => $styles,
					'edit_field_class' => 'vc_col-sm-6 vc_column',
				),
				array(
					'type'             => 'dropdown',
					'heading'          => esc_html__( 'Card shadow', 'craft-addons-for-wpbakery' ),
					'param_name'       => 'card_shadow',
					'value'            => array(
						esc_html__( 'Soft', 'craft-addons-for-wpbakery' ) => 'soft',
						esc_html__( 'None', 'craft-addons-for-wpbakery' ) => 'none',
					),
					'std'              => 'soft',
					'group'            => $styles,
					'edit_field_class' => 'vc_col-sm-6 vc_column',
				),
				// --- Stars (Styles) ---
				array(
					'type'             => 'colorpicker',
					'heading'          => esc_html__( 'Star color', 'craft-addons-for-wpbakery' ),
					'param_name'       => 'star_color',
					'group'            => $styles,
					'edit_field_class' => 'vc_col-sm-6 vc_column cafw-bg-sep',
				),
				array(
					'type'             => 'textfield',
					'heading'          => esc_html__( 'Star size (px)', 'craft-addons-for-wpbakery' ),
					'param_name'       => 'star_size',
					'group'            => $styles,
					'edit_field_class' => 'vc_col-sm-6 vc_column cafw-bg-sep',
				),
				// --- Avatar (Styles) ---
				array(
					'type'             => 'textfield',
					'heading'          => esc_html__( 'Avatar size (px)', 'craft-addons-for-wpbakery' ),
					'param_name'       => 'avatar_size',
					'group'            => $styles,
					'edit_field_class' => 'vc_col-sm-6 vc_column cafw-bg-sep',
				),
				array(
					'type'             => 'dropdown',
					'heading'          => esc_html__( 'Avatar shape', 'craft-addons-for-wpbakery' ),
					'param_name'       => 'avatar_shape',
					'value'            => array(
						esc_html__( 'Circle', 'craft-addons-for-wpbakery' )  => 'circle',
						esc_html__( 'Rounded', 'craft-addons-for-wpbakery' ) => 'rounded',
						esc_html__( 'Square', 'craft-addons-for-wpbakery' )  => 'square',
					),
					'std'              => 'circle',
					'group'            => $styles,
					'edit_field_class' => 'vc_col-sm-6 vc_column cafw-bg-sep',
				),
				// --- Quote mark (Styles) ---
				array(
					'type'             => 'checkbox',
					'heading'          => esc_html__( 'Quote mark', 'craft-addons-for-wpbakery' ),
					'param_name'       => 'mark_show',
					'value'            => array( esc_html__( 'Show a decorative quote mark', 'craft-addons-for-wpbakery' ) => 'yes' ),
					'group'            => $styles,
					'edit_field_class' => 'vc_col-xs-12 vc_column cafw-bg-sep',
				),
				array(
					'type'             => 'colorpicker',
					'heading'          => esc_html__( 'Quote mark color', 'craft-addons-for-wpbakery' ),
					'param_name'       => 'mark_color',
					'group'            => $styles,
					'dependency'       => $mark_dep,
					'edit_field_class' => 'vc_col-sm-6 vc_column',
				),
				array(
					'type'             => 'textfield',
					'heading'          => esc_html__( 'Quote mark size (px)', 'craft-addons-for-wpbakery' ),
					'param_name'       => 'mark_size',
					'group'            => $styles,
					'dependency'       => $mark_dep,
					'edit_field_class' => 'vc_col-sm-6 vc_column',
				),
			),
			$this->typography_params( 'quote', esc_html__( 'Quote', 'craft-addons-for-wpbakery' ), array( 'sep' => true ) ),
			$this->typography_params( 'name', esc_html__( 'Author name', 'craft-addons-for-wpbakery' ), array( 'sep' => true ) ),
			array(
				array(
					'type'             => 'colorpicker',
					'heading'          => esc_html__( 'Role color', 'craft-addons-for-wpbakery' ),
					'param_name'       => 'role_color',
					'group'            => $styles,
					'edit_field_class' => 'vc_col-sm-6 vc_column',
				),
			)
		);
	}

	/**
	 * {@inheritDoc}
	 */
	public function render( $atts, $content, $el_id ) {
		$skin   = in_array( $atts['layout'], self::$skins, true ) ? $atts['layout'] : 'classic';
		$align  = in_array( $atts['align'], array( 'left', 'center', 'right' ), true ) ? $atts['align'] : 'left';
		$rating = max( 0, min( 5, absint( $atts['rating'] ) ) );

		$quote_type = $this->part_typography( $atts, 'quote' );
		$name_type  = $this->part_typography( $atts, 'name' );

		// Skin/style knobs are emitted as CSS custom properties on the root so
		// the stylesheet stays the single source of layout truth.
		$vars = array();
		$bg   = sanitize_hex_color( $atts['card_bg'] );
		if ( $bg ) {
			$vars['--cafw-t-bg'] = $bg;
		}
		$border = sanitize_hex_color( $atts['card_border'] );
		if ( $border ) {
			$vars['--cafw-t-border'] = $border;
		}
		if ( '' !== trim( (string) $atts['card_radius'] ) ) {
			$vars['--cafw-t-radius'] = absint( $atts['card_radius'] ) . 'px';
		}
		$star = sanitize_hex_color( $atts['star_color'] );
		if ( $star ) {
			$vars['--cafw-t-star'] = $star;
		}
		if ( absint( $atts['star_size'] ) ) {
			$vars['--cafw-t-star-size'] = absint( $atts['star_size'] ) . 'px';
		}
		if ( absint( $atts['avatar_size'] ) ) {
			$vars['--cafw-t-avatar'] = absint( $atts['avatar_size'] ) . 'px';
		}
		if ( 'rounded' === $atts['avatar_shape'] ) {
			$vars['--cafw-t-avatar-radius'] = '10px';
		} elseif ( 'square' === $atts['avatar_shape'] ) {
			$vars['--cafw-t-avatar-radius'] = '0';
		}
		$mark_color = sanitize_hex_color( $atts['mark_color'] );
		if ( $mark_color ) {
			$vars['--cafw-t-mark-color'] = $mark_color;
		}
		if ( absint( $atts['mark_size'] ) ) {
			$vars['--cafw-t-mark-size'] = absint( $atts['mark_size'] ) . 'px';
		}
		$role_color = sanitize_hex_color( $atts['role_color'] );
		if ( $role_color ) {
			$vars['--cafw-t-role'] = $role_color;
		}

		$classes = 'cafw-testimonial cafw-testimonial--' . $skin . ' cafw-testimonial--align-' . $align;
		if ( 'none' === $atts['card_shadow'] ) {
			$classes .= ' cafw-testimonial--noshadow';
		}

		// --- Build the pieces; each is fully escaped here. -------------------
		$stars_html = '';
		if ( $rating > 0 ) {
			$stars_html = '<div class="cafw-testimonial__rating" aria-label="' . esc_attr( sprintf( /* translators: %d: number of stars. */ _n( '%d star', '%d stars', $rating, 'craft-addons-for-wpbakery' ), $rating ) ) . '">';
			for ( $i = 1; $i <= 5; $i++ ) {
				$stars_html .= '<span class="cafw-testimonial__star ' . ( $i <= $rating ? 'is-filled' : '' ) . '" aria-hidden="true">&#9733;</span>';
			}
			$stars_html .= '</div>';
		}

		$quote_html = '';
		if ( '' !== $atts['quote'] ) {
			$quote_html = '<blockquote class="cafw-testimonial__quote"' . $this->style_attr( $quote_type['styles'] )
				. '>' . wp_kses_post( wpautop( $atts['quote'] ) ) . '</blockquote>';
		}

		if ( '' !== $atts['image'] ) {
			$avatar_html = wp_get_attachment_image(
				absint( $atts['image'] ),
				'thumbnail',
				false,
				array( 'class' => 'cafw-testimonial__avatar' )
			);
		} else {
			// Show a person placeholder (like the Team Member element) so a freshly
			// dropped testimonial already has its photo area.
			$avatar_html = '<span class="cafw-testimonial__avatar cafw-testimonial__avatar--placeholder" aria-hidden="true">'
				. '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path d="M12 12a5 5 0 1 0 0-10 5 5 0 0 0 0 10Zm0 2c-4.42 0-8 2.5-8 5.5V22h16v-2.5c0-3-3.58-5.5-8-5.5Z"/></svg>'
				. '</span>';
		}

		$has_meta = ( '' !== $atts['author'] || '' !== $atts['role'] );

		$author_span = '' === $atts['author'] ? ''
			: '<span class="cafw-testimonial__author"' . $this->style_attr( $name_type['styles'] ) . '>' . esc_html( $atts['author'] ) . '</span>';
		$role_span   = '' === $atts['role'] ? ''
			: '<span class="cafw-testimonial__role">' . esc_html( $atts['role'] ) . '</span>';
		$role_inline = ( 'inline' === $atts['role_pos'] && '' !== $author_span && '' !== $role_span );

		// The "rated bar" skins lay the person row out themselves (stars pushed
		// to the right of the name line), so they opt out of the generic flow.
		$bar_skin = in_array( $skin, array( 'header-rated', 'minimal-rated' ), true );

		// Stars live inside the meta block, right under the name, when the user
		// asks for it - and by default in the skins designed around that look.
		// In that arrangement the role hides by default; an explicit "Below the
		// author name" slots it between the name and the stars, and
		// "Next to the author name" keeps it on the name line.
		$stars_in_meta = ( '' !== $stars_html && '' !== $atts['author'] && ! $bar_skin
			&& ( 'meta' === $atts['star_pos'] || in_array( $skin, array( 'inline', 'minimal' ), true ) ) );

		$meta_html = '';
		if ( $has_meta ) {
			$meta_html = '<div class="cafw-testimonial__meta">';
			if ( $role_inline ) {
				// Role sits on the same line as the name.
				$meta_html .= '<div class="cafw-testimonial__namerow">' . $author_span . $role_span . '</div>';
				$meta_html .= $stars_in_meta ? $stars_html : '';
			} elseif ( $stars_in_meta ) {
				$meta_html .= $author_span;
				$meta_html .= 'below' === $atts['role_pos'] ? $role_span : '';
				$meta_html .= $stars_html;
			} else {
				$meta_html .= $author_span . $role_span;
			}
			$meta_html .= '</div>';
		}

		if ( $stars_in_meta ) {
			$stars_html = ''; // already placed; keep it out of the layout slot.
		}

		$mark_html = '';
		if ( false !== strpos( (string) $atts['mark_show'], 'yes' ) ) {
			$mark_html = '<span class="cafw-testimonial__mark" aria-hidden="true">&#8221;</span>';
		}

		// --- Assemble the skin. ----------------------------------------------
		$person = function ( $inner, $modifier = '' ) {
			return '' === $inner ? '' : '<div class="cafw-testimonial__person' . $modifier . '">' . $inner . '</div>';
		};

		switch ( $skin ) {
			case 'header':
				$inner = $mark_html . $person( $avatar_html . $meta_html ) . $quote_html . $stars_html;
				break;
			case 'inline':
				$inner = $mark_html . $person( $avatar_html . $meta_html . $stars_html, ' cafw-testimonial__person--spread' ) . $quote_html;
				break;
			case 'stacked':
				$inner = $mark_html . $avatar_html . $stars_html . $quote_html . $meta_html;
				break;
			case 'aside-left':
				$inner = $mark_html . $avatar_html
					. '<div class="cafw-testimonial__body">' . $stars_html . $quote_html . $meta_html . '</div>';
				break;
			case 'minimal':
				$inner = $mark_html . $quote_html . $person( $avatar_html . $meta_html . $stars_html );
				break;
			case 'header-rated':
			case 'minimal-rated':
				// Avatar + name (role below it) on the left, stars at the far
				// right of the name line.
				$bar_meta = '';
				if ( $has_meta || '' !== $stars_html ) {
					$bar_meta = '<div class="cafw-testimonial__meta cafw-testimonial__meta--bar">'
						. '<div class="cafw-testimonial__namerow cafw-testimonial__namerow--bar">'
						. $author_span . ( $role_inline ? $role_span : '' ) . $stars_html . '</div>'
						. ( $role_inline ? '' : $role_span )
						. '</div>';
				}
				$person_html = $person( $avatar_html . $bar_meta, ' cafw-testimonial__person--spread' );
				$inner       = ( 'header-rated' === $skin )
					? $mark_html . $person_html . $quote_html
					: $mark_html . $quote_html . $person_html;
				break;
			default: // classic.
				$inner = $mark_html . $stars_html . $quote_html . $person( $avatar_html . $meta_html );
				break;
		}

		return $this->open_root( $atts, $el_id, $classes, $vars ) . $inner . '</div>';
	}
}
