true,
'labelsVisibility' => 'always',
'hoverAnimation' => 'icgb__fade',
'labelVPosition' => 'icgbv__center',
'labelHPosition' => 'icgbh__center',
'beforeLabel' => 'Before',
'afterLabel' => 'After',
)
);
$compare = wp_parse_args(
isset( $attributes['comparisonOptions'] ) && is_array( $attributes['comparisonOptions'] ) ? $attributes['comparisonOptions'] : array(),
array(
'disableSliding' => false,
'onlyHandleDraggable' => false,
'slideMode' => 'horizontal',
'swipeMode' => 'drag',
'initialPosition' => 50,
)
);
$pick = static function ( $key, $allowed, $fallback ) use ( $attributes ) {
return isset( $attributes[ $key ] ) && in_array( $attributes[ $key ], $allowed, true ) ? $attributes[ $key ] : $fallback;
};
// More comparisons (carousel).
$pairs = array();
if ( ! empty( $attributes['pairs'] ) && is_array( $attributes['pairs'] ) ) {
foreach ( $attributes['pairs'] as $pair ) {
$pair = array(
'before' => self::image( $pair['beforeImage'] ?? array() ),
'after' => self::image( $pair['afterImage'] ?? array() ),
'caption' => isset( $pair['caption'] ) ? (string) $pair['caption'] : '',
);
if ( '' !== $pair['before']['url'] && '' !== $pair['after']['url'] ) {
$pairs[] = $pair;
}
}
}
return array(
'uniqueId' => isset( $attributes['uniqueId'] ) ? (string) $attributes['uniqueId'] : '',
'before' => self::image( $attributes['beforeImage'] ?? array() ),
'after' => self::image( $attributes['afterImage'] ?? array() ),
'slider' => $slider,
'compare' => $compare,
'showCaption' => ! empty( $attributes['showCaption'] ),
'caption' => isset( $attributes['sliderCaption'] ) ? (string) $attributes['sliderCaption'] : '',
'captionTag' => $pick( 'captionTag', self::CAPTION_TAGS, 'span' ),
'design' => $pick( 'design', self::DESIGNS, '' ),
'ratio' => $pick( 'aspectRatio', self::RATIOS, '' ),
'imageSize' => $pick( 'imageSize', array( 'large', 'full' ), 'full' ),
'handleStyle' => $pick( 'handleStyle', self::HANDLE_STYLES, 'circle' ),
'handleText' => isset( $attributes['handleText'] ) ? (string) $attributes['handleText'] : '',
'handleIcon' => isset( $attributes['handleIcon'] ) ? (string) $attributes['handleIcon'] : '',
'mode' => $pick( 'compareMode', self::MODES, 'slide' ),
'introHint' => ! empty( $attributes['introHint'] ),
'autoPlay' => ! empty( $attributes['autoPlay'] ),
'fullscreen' => ! empty( $attributes['fullscreen'] ),
'ctaText' => isset( $attributes['ctaText'] ) ? (string) $attributes['ctaText'] : '',
'ctaUrl' => isset( $attributes['ctaUrl'] ) ? (string) $attributes['ctaUrl'] : '',
'ctaNewTab' => ! empty( $attributes['ctaNewTab'] ),
'ctaPosition' => $pick( 'ctaPosition', self::CTA_POSITIONS, 'bottom-center' ),
'video' => 'video' === ( $attributes['mediaType'] ?? 'image' ),
'beforeVideo' => self::image( $attributes['beforeVideo'] ?? array() ),
'afterVideo' => self::image( $attributes['afterVideo'] ?? array() ),
'pairs' => $pairs,
'pairThumbs' => ! isset( $attributes['pairThumbs'] ) || ! empty( $attributes['pairThumbs'] ),
);
}
/**
* The parts of a saved image (or video) the page needs: id, url, alt.
*
* @param mixed $image Saved image.
* @return array{id:int,url:string,alt:string}
*/
private static function image( $image ) {
$image = is_array( $image ) ? $image : array();
return array(
'id' => isset( $image['id'] ) ? absint( $image['id'] ) : 0,
'url' => isset( $image['url'] ) ? (string) $image['url'] : '',
'alt' => isset( $image['alt'] ) ? (string) $image['alt'] : '',
);
}
/**
* A label as shown: the untouched English default is translated.
*
* @param string $label Saved label.
* @param string $which before|after.
* @return string
*/
public static function label( $label, $which ) {
if ( 'Before' === $label && 'before' === $which ) {
return __( 'Before', 'slider-blocks' );
}
if ( 'After' === $label && 'after' === $which ) {
return __( 'After', 'slider-blocks' );
}
return (string) $label;
}
/**
* Block output.
*
* @param array $attributes Block attributes.
* @param string $content Saved markup (only its HTML anchor is used).
* @return string
*/
public static function render( $attributes, $content = '' ) {
$atts = self::normalize( $attributes );
// The first comparison (images or videos) must be complete.
$first = $atts['video']
? array( 'before' => $atts['beforeVideo'], 'after' => $atts['afterVideo'], 'caption' => '' )
: array( 'before' => $atts['before'], 'after' => $atts['after'], 'caption' => '' );
if ( '' === $first['before']['url'] || '' === $first['after']['url'] ) {
return '';
}
// Several comparisons make a carousel (images only).
$pairs = $atts['video'] ? array() : $atts['pairs'];
$classes = array( $atts['uniqueId'] );
if ( $atts['design'] ) {
$classes[] = 'gs-ba-design-' . $atts['design'];
}
if ( $pairs ) {
$classes[] = 'has-pairs';
}
$wrapper = array( 'class' => implode( ' ', array_filter( $classes ) ) );
// The HTML anchor is saved in the markup, not in the attributes.
if ( preg_match( '/^\s*
]*\bid="([^"]+)"/', (string) $content, $match ) ) {
$wrapper['id'] = $match[1];
}
$caption = $atts['showCaption'] ? $atts['caption'] : '';
if ( ! $pairs ) {
return sprintf(
'
%2$s
',
get_block_wrapper_attributes( $wrapper ),
self::figure( $first, $atts, $caption, true )
);
}
// Carousel: Swiper only loads for blocks that need it.
wp_enqueue_script( 'gutslider-swiper-script' );
wp_enqueue_style( 'gutslider-swiper-style' );
$all = array_merge( array( array_merge( $first, array( 'caption' => $caption ) ) ), $pairs );
$slides = '';
foreach ( $all as $index => $pair ) {
$slides .= sprintf(
'
%2$s
',
/* translators: 1: comparison number, 2: number of comparisons. */
esc_attr( sprintf( __( '%1$d of %2$d', 'slider-blocks' ), $index + 1, count( $all ) ) ),
self::figure( $pair, $atts, $pair['caption'], 0 === $index )
);
}
$thumbs = '';
if ( $atts['pairThumbs'] ) {
foreach ( $all as $index => $pair ) {
$thumb = $pair['after']['id'] ? wp_get_attachment_image_url( $pair['after']['id'], 'thumbnail' ) : '';
$thumbs .= sprintf(
'
',
0 === $index ? ' is-active' : '',
/* translators: %d: comparison number. */
esc_attr( sprintf( __( 'Show comparison %d', 'slider-blocks' ), $index + 1 ) ),
0 === $index ? ' aria-current="true"' : '',
esc_url( $thumb ? $thumb : $pair['after']['url'] )
);
}
$thumbs = '
' . $thumbs . '
';
}
return sprintf(
'
',
get_block_wrapper_attributes( $wrapper ),
esc_attr__( 'carousel', 'slider-blocks' ),
$slides,
esc_attr__( 'Previous comparison', 'slider-blocks' ),
esc_attr__( 'Next comparison', 'slider-blocks' ),
$thumbs
);
}
/**
* One comparison with its caption.
*
* @param array $pair before, after (id, url, alt).
* @param array $atts Normalised settings.
* @param string $caption Caption (HTML).
* @param bool $lead First comparison of the block.
* @return string HTML.
*/
private static function figure( $pair, $atts, $caption, $lead ) {
$slider = $atts['slider'];
$compare = $atts['compare'];
$mode = $atts['mode'];
$portrait = 'slide' === $mode && 'vertical' === $compare['slideMode'];
$position = max( 0, min( 100, (float) $compare['initialPosition'] ) );
if ( 'toggle' === $mode ) {
$position = 100;
}
// Only the page's first comparison may hold its main image.
if ( $lead ) {
++self::$count;
}
$eager = $lead && 1 === self::$count;
$root_classes = array(
'gutslider-compare-block',
'gs-ba',
$portrait ? 'is-vertical' : 'is-horizontal',
'is-' . $mode,
'has-handle-' . $atts['handleStyle'],
);
if ( $compare['disableSliding'] && 'toggle' !== $mode ) {
$root_classes[] = 'is-disabled';
}
if ( $compare['onlyHandleDraggable'] ) {
$root_classes[] = 'is-handle-only';
}
if ( $atts['ratio'] ) {
$root_classes[] = 'has-ratio';
}
if ( $atts['video'] ) {
$root_classes[] = 'is-video';
}
// Opacity of "after": fade follows the knob (right = after), toggle starts on "before".
$style = sprintf( '--gs-ba-pos:%1$s%%;--gs-ba-o:%2$s;', $position, 'fade' === $mode ? $position / 100 : 1 - $position / 100 );
if ( $atts['ratio'] ) {
$style .= 'aspect-ratio:' . $atts['ratio'] . ';';
}
$before_label = self::label( $slider['beforeLabel'], 'before' );
$after_label = self::label( $slider['afterLabel'], 'after' );
ob_start();
?>
%2$s%1$s>',
tag_escape( $atts['captionTag'] ),
wp_kses_post( $caption )
);
?>
' . esc_html( '' !== $atts['handleText'] ? $atts['handleText'] : __( 'Drag', 'slider-blocks' ) ) . '';
} elseif ( 'icon' === $atts['handleStyle'] && '' !== $atts['handleIcon'] && function_exists( 'gutslider_display_icon' ) ) {
$inner = '' . wp_kses( gutslider_display_icon( $atts['handleIcon'] ), gutslider_allowed_svg_tags() ) . '';
} else {
$inner = '';
}
return sprintf(
'',
$disabled ? '-1' : '0',
'fade' === $atts['mode'] ? esc_attr__( 'Fade between the before and after images', 'slider-blocks' ) : esc_attr__( 'Drag to compare the before and after images', 'slider-blocks' ),
$portrait ? 'vertical' : 'horizontal',
esc_attr( (string) round( $position ) ),
$disabled ? ' aria-disabled="true"' : '',
$inner
);
}
/**
* Buttons over the comparison: toggle, play/pause, fullscreen, call to action.
*
* @param array $atts Normalised settings.
* @param string $before_label Before label.
* @param string $after_label After label.
* @return string HTML.
*/
private static function ui( $atts, $before_label, $after_label ) {
$html = '';
if ( 'toggle' === $atts['mode'] ) {
$html .= sprintf(
'',
esc_attr__( 'Show', 'slider-blocks' ),
esc_html( '' !== $before_label ? $before_label : __( 'Before', 'slider-blocks' ) ),
esc_html( '' !== $after_label ? $after_label : __( 'After', 'slider-blocks' ) )
);
}
if ( $atts['video'] ) {
$html .= sprintf(
'',
esc_attr__( 'Pause videos', 'slider-blocks' ),
esc_attr__( 'Play videos', 'slider-blocks' )
);
}
if ( $atts['fullscreen'] ) {
$html .= sprintf(
'',
esc_attr__( 'View fullscreen', 'slider-blocks' ),
esc_attr__( 'Exit fullscreen', 'slider-blocks' )
);
}
if ( '' !== $atts['ctaText'] && '' !== $atts['ctaUrl'] ) {
$html .= sprintf(
'%4$s',
esc_attr( $atts['ctaPosition'] ),
esc_url( $atts['ctaUrl'] ),
$atts['ctaNewTab'] ? ' target="_blank" rel="noopener noreferrer"' : '',
esc_html( $atts['ctaText'] )
);
}
return $html;
}
/**
* One side: its label and image (or video).
*
* @param array $media id, url, alt.
* @param string $which before|after.
* @param string $label Label text.
* @param array $atts Normalised settings.
* @param bool $eager Load right away.
* @param bool $priority May be fetched with high priority.
* @return string HTML.
*/
private static function item( $media, $which, $label, $atts, $eager, $priority ) {
$slider = $atts['slider'];
$visibility = 'hover' === $slider['labelsVisibility'] ? 'hover' : 'always';
$portrait = 'slide' === $atts['mode'] && 'vertical' === $atts['compare']['slideMode'];
$label_classes = array( 'gutslider-label', $which . '-label', $portrait ? 'vertical' : 'horizontal' );
if ( 'hover' === $visibility && in_array( $slider['hoverAnimation'], self::ANIMATIONS, true ) ) {
$label_classes[] = $slider['hoverAnimation'];
}
$place = $portrait ? $slider['labelHPosition'] : $slider['labelVPosition'];
if ( in_array( $place, $portrait ? self::H_POSITIONS : self::V_POSITIONS, true ) ) {
$label_classes[] = $place;
}
if ( $atts['video'] ) {
$body = sprintf(
'',
esc_url( $media['url'] ),
esc_attr( wp_strip_all_tags( $label ) )
);
} else {
// The attachment's current alt text, else the saved one, else the label.
$alt = $media['id'] ? (string) get_post_meta( $media['id'], '_wp_attachment_image_alt', true ) : '';
if ( '' === $alt ) {
$alt = '' !== $media['alt'] ? $media['alt'] : wp_strip_all_tags( $label );
}
$loading = $eager ? 'eager' : 'lazy';
$body = $media['id'] && wp_attachment_is_image( $media['id'] )
? wp_get_attachment_image(
$media['id'],
$atts['imageSize'],
false,
array(
'class' => 'gs-ba__img wp-image-' . $media['id'],
'alt' => $alt,
'draggable' => 'false',
'decoding' => 'async',
'loading' => $loading,
'fetchpriority' => $priority ? 'high' : 'auto',
)
)
: '';
if ( '' === $body ) {
$body = sprintf(
'
',
esc_url( $media['url'] ),
esc_attr( $alt ),
esc_attr( $loading )
);
}
}
ob_start();
?>