<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
* Module: Jetpack Carousel
* @package automattic/jetpack
use Automattic\Jetpack\Assets;
use Automattic\Jetpack\Stats\Options as Stats_Options;
use Automattic\Jetpack\Status;
use Automattic\Jetpack\Status\Host;
if ( ! defined( 'ABSPATH' ) ) {
* Jetpack_Carousel class.
* @phan-constructor-used-for-side-effects
* Defines Carousel pre-built widths
public $prebuilt_widths = array( 370, 700, 1000, 1200, 1400, 2000 );
* Localization strings and other data for the JavaScript
public $localize_strings;
* Represents whether or not this is the first load of Carousel on a page. Default is true.
public $first_run = true;
* Determines whether or not to set in the gallery. Default is false.
public $in_gallery = false;
* Determines whether the module runs in the Jetpack plugin, as opposed to WP.com Simple site environment
public $in_jetpack = true;
* Determines whether or not a single image gallery is enabled. Default is false.
public $single_image_gallery_enabled = false;
* Determines whether images that link to themselves should be replaced with a one image gallery. Default is false.
public $single_image_gallery_enabled_media_file = false;
public function __construct() {
add_action( 'init', array( $this, 'init' ) );
if ( $this->maybe_disable_jp_carousel() ) {
$this->in_jetpack = ! ( new Host() )->is_wpcom_simple();
$this->single_image_gallery_enabled = ! $this->maybe_disable_jp_carousel_single_images();
$this->single_image_gallery_enabled_media_file = $this->maybe_enable_jp_carousel_single_images_media_file();
// Register the Carousel-related related settings.
add_action( 'admin_init', array( $this, 'register_settings' ), 5 );
if ( ! $this->in_jetpack ) {
if ( 0 === $this->test_1or0_option( get_option( 'carousel_enable_it' ), true ) ) {
return; // Carousel disabled, abort early, but still register setting so user can switch it back on.
// If in admin, register the ajax endpoints.
add_action( 'wp_ajax_get_attachment_comments', array( $this, 'get_attachment_comments' ) );
add_action( 'wp_ajax_nopriv_get_attachment_comments', array( $this, 'get_attachment_comments' ) );
add_action( 'wp_ajax_post_attachment_comment', array( $this, 'post_attachment_comment' ) );
add_action( 'wp_ajax_nopriv_post_attachment_comment', array( $this, 'post_attachment_comment' ) );
if ( ! $this->in_jetpack ) {
if ( 0 === $this->test_1or0_option( get_option( 'carousel_enable_it' ), true ) ) {
return; // Carousel disabled, abort early.
// If on front-end, do the Carousel thang.
* Filter the array of default prebuilt widths used in Carousel.
* @param array $this->prebuilt_widths Array of default widths.
$this->prebuilt_widths = apply_filters( 'jp_carousel_widths', $this->prebuilt_widths );
// below: load later than other callbacks hooked it (e.g. 3rd party plugins handling gallery shortcode).
add_filter( 'post_gallery', array( $this, 'check_if_shortcode_processed_and_enqueue_assets' ), 1000, 2 );
add_filter( 'post_gallery', array( $this, 'set_in_gallery' ), -1000 );
add_filter( 'gallery_style', array( $this, 'add_data_to_container' ) );
add_filter( 'wp_get_attachment_image_attributes', array( $this, 'add_data_to_images' ), 10, 2 );
add_filter( 'jetpack_tiled_galleries_block_content', array( $this, 'add_data_img_tags_and_enqueue_assets' ) );
if ( $this->single_image_gallery_enabled ) {
add_filter( 'the_content', array( $this, 'add_data_img_tags_and_enqueue_assets' ) );
add_filter( 'render_block_data', array( $this, 'remove_core_lightbox_in_gallery' ), 10, 3 );
// `is_amp_request()` can't be called until the 'wp' filter.
add_action( 'wp', array( $this, 'check_amp_support' ) );
if ( $this->in_jetpack ) {
Jetpack::enable_module_configurable( dirname( __DIR__ ) . '/carousel.php' );
* Check AMP and add filters.
public function check_amp_support() {
! class_exists( 'Jetpack_AMP_Support' )
|| ! Jetpack_AMP_Support::is_amp_request()
add_filter( 'render_block_core/gallery', array( $this, 'filter_gallery_block_render' ), 10, 2 );
add_filter( 'render_block_jetpack/tiled-gallery', array( $this, 'filter_gallery_block_render' ), 10, 2 );
* Returns the value of the applied jp_carousel_maybe_disable filter
* @return bool - Should Carousel be disabled? Default to false.
public function maybe_disable_jp_carousel() {
* Allow third-party plugins or themes to disable Carousel.
* @param bool false Should Carousel be disabled? Default to false.
return apply_filters( 'jp_carousel_maybe_disable', false );
* Returns the value of the applied jp_carousel_maybe_disable_single_images filter
* @return bool - Should Carousel be disabled for single images? Default to false.
public function maybe_disable_jp_carousel_single_images() {
* Allow third-party plugins or themes to disable Carousel for single images.
* @param bool false Should Carousel be disabled for single images? Default to false.
return apply_filters( 'jp_carousel_maybe_disable_single_images', false );
* Returns the value of the applied jp_carousel_load_for_images_linked_to_file filter
* @return bool - Should Carousel be enabled for single images linking to 'Media File'? Default to false.
public function maybe_enable_jp_carousel_single_images_media_file() {
* Allow third-party plugins or themes to enable Carousel
* for single images linking to 'Media File' (full size image).
* @param bool false Should Carousel be enabled for single images linking to 'Media File'? Default to false.
return apply_filters( 'jp_carousel_load_for_images_linked_to_file', false );
* Returns the value of the applied jp_carousel_asset_version filter
* @param string $version Asset version.
public function asset_version( $version ) {
* Filter the version string used when enqueuing Carousel assets.
* @param string $version Asset version.
return apply_filters( 'jp_carousel_asset_version', $version );
* Displays a message on top of gallery if carousel has bailed.
* @param string $output Gallery shortcode output.
* @return string Shortcode output with bail message prepended.
public function display_bail_message( $output = '' ) {
$message = '<div class="jp-carousel-msg"><p>';
$message .= __( 'Jetpack\'s Carousel has been disabled, because another plugin or your theme is overriding the [gallery] shortcode.', 'jetpack' );
$message .= '</p></div>';
// put before gallery output.
$output = $message . $output;
* Determine whether Carousel is enabled, and adjust filters and enqueue assets accordingly.
* If no other filter hook produced output for the gallery shortcode or something returns true for
* the `jp_carousel_force_enable` filter, Carousel is enabled and we queue our assets. Otherwise
* it's disabled and we remove some of our subsequent filter hooks.
* @param string $output Gallery shortcode output.
* @return string Gallery shortcode output.
public function check_if_shortcode_processed_and_enqueue_assets( $output ) {
class_exists( 'Jetpack_AMP_Support' )
&& Jetpack_AMP_Support::is_amp_request()
* Allow third-party plugins or themes to force-enable Carousel.
* @param bool false Should we force enable Carousel? Default to false.
! apply_filters( 'jp_carousel_force_enable', false )
// Bail because someone is overriding the [gallery] shortcode.
remove_filter( 'gallery_style', array( $this, 'add_data_to_container' ) );
remove_filter( 'wp_get_attachment_image_attributes', array( $this, 'add_data_to_images' ) );
remove_filter( 'the_content', array( $this, 'add_data_img_tags_and_enqueue_assets' ) );
// Display message that carousel has bailed, if user is super_admin, and if we're not on WordPress.com.
! ( defined( 'IS_WPCOM' ) && IS_WPCOM )
add_filter( 'post_gallery', array( $this, 'display_bail_message' ) );
* Fires when thumbnails are shown in Carousel.
do_action( 'jp_carousel_thumbnails_shown' );
* Check if the content of a post uses gallery blocks. To be used by 'the_content' filter.
* @deprecated since 11.3 We now hook into the 'block_render_{block_name}' hook to add markup.
* @param string $content Post content.
* @return string $content Post content.
public function check_content_for_blocks( $content ) {
_deprecated_function( __METHOD__, 'jetpack-11.3' );
class_exists( 'Jetpack_AMP_Support' )
&& Jetpack_AMP_Support::is_amp_request()
if ( has_block( 'gallery', $content ) || has_block( 'jetpack/tiled-gallery', $content ) ) {
$content = $this->add_data_to_container( $content );
* Remove core lightbox settings from images in a gallery, if Carousel is enabled.
* @param array $parsed_block An associative array of the block being rendered.
* @param array $source_block An un-modified copy of `$parsed_block`, as it appeared in the source content.
* @param WP_Block|null $parent_block If this is a nested block, a reference to the parent block.
* @return array The modified block data.
public function remove_core_lightbox_in_gallery( $parsed_block, $source_block, $parent_block ) {
! empty( $parsed_block['blockName'] ) &&
'core/image' === $parsed_block['blockName'] &&
! empty( $parent_block->name ) &&
'core/gallery' === $parent_block->name
unset( $parsed_block['attrs']['lightbox'] );
* Enrich the gallery block content using the render_block_{$this->name} filter.
* This function is triggered after block render to make sure we track galleries within
* @see https://developer.wordpress.org/reference/hooks/render_block_this-name/
* @param string $block_content The rendered HTML for the carousel or gallery block.
* @param array $block The parsed block details for the block.
* @return string The fully-processed HTML for the carousel or gallery block.
public function filter_gallery_block_render( $block_content, $block ) {
if ( empty( $block['blockName'] ) || ! in_array( $block['blockName'], array( 'core/gallery', 'jetpack/tiled-gallery' ), true ) ) {
if ( ! $post instanceof WP_Post ) {
$blog_id = (int) get_current_blog_id();
'data-carousel-extra' => array(
'permalink' => get_permalink( $post->ID ),
* Filter the data added to the Gallery container.
* @param array $extra_data Array of data about the site and the post.
$extra_data = apply_filters( 'jp_carousel_add_data_to_container', $extra_data );
$extra_data = (array) $extra_data;
if ( empty( $extra_data ) ) {
$extra_attributes = implode(
function ( $data_key, $data_values ) {
return esc_attr( $data_key ) . "='" . wp_json_encode( $data_values ) . "'";
array_keys( $extra_data ),
array_values( $extra_data )
// Add extra attributes to first HTML element (which may have leading whitespace)
'/^(\s*<(div|ul|figure))/',
'$1 ' . $extra_attributes . ' ',
* Enqueueing Carousel assets.
public function enqueue_assets() {
if ( $this->first_run ) {
Assets::get_file_url_for_environment(
'_inc/build/carousel/jetpack-carousel.min.js',
'modules/carousel/jetpack-carousel.js'
$this->asset_version( JETPACK__VERSION ),
$swiper_library_path = array(
'url' => plugins_url( '_inc/blocks/swiper.js', JETPACK__PLUGIN_FILE ),
wp_localize_script( 'jetpack-carousel', 'jetpackSwiperLibraryPath', $swiper_library_path );
// Note: using home_url() instead of admin_url() for ajaxurl to be sure to get same domain on wpcom when using mapped domains (also works on self-hosted).
// Also: not hardcoding path since there is no guarantee site is running on site root in self-hosted context.
$is_logged_in = is_user_logged_in();
$comment_registration = (int) get_option( 'comment_registration' );
$require_name_email = (int) get_option( 'require_name_email' );
$localize_strings = array(
'widths' => $this->prebuilt_widths,
'is_logged_in' => $is_logged_in,
'lang' => strtolower( substr( get_locale(), 0, 2 ) ),
'ajaxurl' => set_url_scheme( admin_url( 'admin-ajax.php' ) ),
'nonce' => wp_create_nonce( 'carousel_nonce' ),
'display_exif' => $this->test_1or0_option( Jetpack_Options::get_option_and_ensure_autoload( 'carousel_display_exif', true ) ),
'display_comments' => $this->test_1or0_option( Jetpack_Options::get_option_and_ensure_autoload( 'carousel_display_comments', true ) ),
'single_image_gallery' => $this->single_image_gallery_enabled,
'single_image_gallery_media_file' => $this->single_image_gallery_enabled_media_file,
'background_color' => $this->carousel_background_color_sanitize( Jetpack_Options::get_option_and_ensure_autoload( 'carousel_background_color', '' ) ),
'comment' => __( 'Comment', 'jetpack' ),
'post_comment' => __( 'Post Comment', 'jetpack' ),
'write_comment' => __( 'Write a Comment...', 'jetpack' ),
'loading_comments' => __( 'Loading Comments...', 'jetpack' ),
'image_label' => __( 'Open image in full-screen.', 'jetpack' ),
'download_original' => sprintf(
/* translators: %1s is the full-size image width, and %2s is the height. */
__( 'View full size <span class="photo-size">%1$s<span class="photo-size-times">×</span>%2$s</span>', 'jetpack' ),
'no_comment_text' => __( 'Please be sure to submit some text with your comment.', 'jetpack' ),
'no_comment_email' => __( 'Please provide an email address to comment.', 'jetpack' ),
'no_comment_author' => __( 'Please provide your name to comment.', 'jetpack' ),
'comment_post_error' => __( 'Sorry, but there was an error posting your comment. Please try again later.', 'jetpack' ),
'comment_approved' => __( 'Your comment was approved.', 'jetpack' ),
'comment_unapproved' => __( 'Your comment is in moderation.', 'jetpack' ),
'camera' => __( 'Camera', 'jetpack' ),
'aperture' => __( 'Aperture', 'jetpack' ),
'shutter_speed' => __( 'Shutter Speed', 'jetpack' ),
'focal_length' => __( 'Focal Length', 'jetpack' ),
'copyright' => __( 'Copyright', 'jetpack' ),
'comment_registration' => $comment_registration,
'require_name_email' => $require_name_email,
/** This action is documented in core/src/wp-includes/link-template.php */
'login_url' => wp_login_url( apply_filters( 'the_permalink', get_permalink() ) ),