diff --git a/projects/plugins/jetpack/changelog/add-jetpack-skip-photon-domain-filter b/projects/plugins/jetpack/changelog/add-jetpack-skip-photon-domain-filter
new file mode 100644
index 000000000000..5abbe7403352
--- /dev/null
+++ b/projects/plugins/jetpack/changelog/add-jetpack-skip-photon-domain-filter
@@ -0,0 +1,4 @@
+Significance: minor
+Type: enhancement
+
+Tiled Gallery: add a `jetpack_skip_photon_domain` filter to serve images from the origin host (keeping the resizing query args) instead of routing them through the Photon domain, for platforms with a built-in image service such as WordPress VIP.
diff --git a/projects/plugins/jetpack/class.jetpack-gutenberg.php b/projects/plugins/jetpack/class.jetpack-gutenberg.php
index 3fa75194bea9..84588032e779 100644
--- a/projects/plugins/jetpack/class.jetpack-gutenberg.php
+++ b/projects/plugins/jetpack/class.jetpack-gutenberg.php
@@ -881,6 +881,8 @@ public static function enqueue_block_editor_assets() {
'jetpack_plan' => array(
'data' => $jetpack_plan['product_slug'],
),
+ /** This filter is documented in modules/tiled-gallery/tiled-gallery/tiled-gallery-item.php */
+ 'skip_photon_domain' => (bool) apply_filters( 'jetpack_skip_photon_domain', false ),
/**
* Enable the RePublicize UI in the block editor context.
*
diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/tiled-gallery.php b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/tiled-gallery.php
index 850d417a4958..60ddc5cdc7da 100644
--- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/tiled-gallery.php
+++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/tiled-gallery.php
@@ -68,6 +68,11 @@ public static function render( $attr, $content ) {
$jetpack_plan = Jetpack_Plan::get();
wp_localize_script( 'jetpack-gallery-settings', 'jetpack_plan', array( 'data' => $jetpack_plan['product_slug'] ) );
+ /** This filter is documented in modules/tiled-gallery/tiled-gallery/tiled-gallery-item.php */
+ $skip_photon_domain = (bool) apply_filters( 'jetpack_skip_photon_domain', false );
+ // Expose the opt-out flag to the block's save-time JS (see photonizedImgProps() in utils/index.js).
+ wp_localize_script( 'jetpack-gallery-settings', 'jetpack_tiled_gallery_settings', array( 'skip_photon_domain' => $skip_photon_domain ) );
+
if ( preg_match_all( '/
]+>/', $content, $images ) ) {
/**
* This block processes all of the images that are found and builds $find and $replace.
@@ -103,6 +108,13 @@ public static function render( $attr, $content ) {
continue;
}
+ // When the site opts out of the Photon domain, rewrite any already-baked-in
+ // i0.wp.com URLs back to their origin host so the srcset (and main src, below)
+ // serve from origin.
+ if ( $skip_photon_domain ) {
+ $orig_src = self::dephotonize_url( $orig_src, $is_ssl );
+ }
+
$srcset_parts = array();
if ( $is_squareish_layout ) {
$min_width = min( self::IMG_SRCSET_WIDTH_MIN, $orig_width, $orig_height );
@@ -116,7 +128,8 @@ public static function render( $attr, $content ) {
),
$orig_src
);
- if ( $is_ssl ) {
+ // `ssl` is a Photon-only signal; skip it on origin URLs when opting out of the Photon domain.
+ if ( $is_ssl && ! $skip_photon_domain ) {
$srcset_src = add_query_arg( 'ssl', '1', $srcset_src );
}
$srcset_parts[] = esc_url( $srcset_src ) . ' ' . $w . 'w';
@@ -136,7 +149,8 @@ public static function render( $attr, $content ) {
),
$orig_src
);
- if ( $is_ssl ) {
+ // `ssl` is a Photon-only signal; skip it on origin URLs when opting out of the Photon domain.
+ if ( $is_ssl && ! $skip_photon_domain ) {
$srcset_src = add_query_arg( 'ssl', '1', $srcset_src );
}
$srcset_parts[] = esc_url( $srcset_src ) . ' ' . $w . 'w';
@@ -151,8 +165,20 @@ public static function render( $attr, $content ) {
if ( ! empty( $srcset_parts ) ) {
$srcset = 'srcset="' . esc_attr( implode( ',', $srcset_parts ) ) . '"';
+ $replacement = str_replace( '
/?. This restores
+ * the origin URL (:///?) so that, when a site opts out of
+ * the Photon domain via the `jetpack_skip_photon_domain` filter, tiled gallery images are served
+ * from the origin host while keeping their resizing query args. The Photon-specific `ssl`
+ * argument is dropped. URLs that are not Photon URLs are returned unchanged.
+ *
+ * @param string $url The (possibly Photon) image URL.
+ * @param bool $is_ssl Whether the origin URL should use https (detected from the Photon ssl arg).
+ * @return string The origin URL, or the original URL if it was not a Photon URL.
+ */
+ private static function dephotonize_url( $url, $is_ssl = true ) {
+ if ( ! preg_match( '#^(?:https?:)?//i[0-2]\.wp\.com/(.+)$#', $url, $matches ) ) {
+ return $url;
+ }
+
+ $origin = ( $is_ssl ? 'https://' : 'http://' ) . $matches[1];
+
+ return remove_query_arg( 'ssl', $origin );
+ }
+
/**
* Render tiled gallery block for email.
*
diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/utils/index.js b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/utils/index.js
index df914545e390..3bbea1ef5e5f 100644
--- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/utils/index.js
+++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/utils/index.js
@@ -59,7 +59,10 @@ export function photonizedImgProps( img, galleryAtts = {} ) {
const { height, width } = img;
const { layoutStyle } = galleryAtts;
- const photonImplementation = true === isVIP() || isSimpleSite() ? photonWpcomImage : photon;
+ // When a site opts out of the Photon domain (e.g. VIP with a platform image service), keep the
+ // origin host and just append the resizing query args — the behavior `photonWpcomImage` provides.
+ const photonImplementation =
+ skipPhotonDomain() || true === isVIP() || isSimpleSite() ? photonWpcomImage : photon;
/**
* Build the `src`
@@ -129,6 +132,25 @@ function isVIP() {
return jetpackPlan && jetpackPlan?.data === 'vip';
}
+/**
+ * Whether the site has opted out of routing tiled gallery images through the Photon domain via the
+ * `jetpack_skip_photon_domain` PHP filter. The value is exposed to the editor through the Jetpack
+ * block editor initial state, with a localized `jetpack_tiled_gallery_settings` fallback (both set
+ * in tiled-gallery.php / class.jetpack-gutenberg.php).
+ *
+ * @return {boolean} True if the Photon domain should be skipped in favor of origin URLs.
+ */
+function skipPhotonDomain() {
+ /*global jetpack_tiled_gallery_settings*/
+ if ( typeof window?.Jetpack_Editor_Initial_State?.jetpack?.skip_photon_domain !== 'undefined' ) {
+ return !! window.Jetpack_Editor_Initial_State.jetpack.skip_photon_domain;
+ }
+ if ( typeof jetpack_tiled_gallery_settings !== 'undefined' ) {
+ return !! jetpack_tiled_gallery_settings?.skip_photon_domain;
+ }
+ return false;
+}
+
/**
* Apply photon arguments to *.files.wordpress.com images
* or images on mapped domains on private simple sites.
diff --git a/projects/plugins/jetpack/modules/tiled-gallery/tiled-gallery.php b/projects/plugins/jetpack/modules/tiled-gallery/tiled-gallery.php
index 15485fdb88d7..29cd3ade03ab 100644
--- a/projects/plugins/jetpack/modules/tiled-gallery/tiled-gallery.php
+++ b/projects/plugins/jetpack/modules/tiled-gallery/tiled-gallery.php
@@ -241,8 +241,12 @@ public function gallery_shortcode( $val, $atts ) {
if ( $gallery_html && class_exists( 'Jetpack' ) && class_exists( Image_CDN::class ) ) {
// Tiled Galleries in Jetpack require that Photon be active.
- // If it's not active, run it just on the gallery output.
- if ( ! Image_CDN::is_enabled() && ! ( new Status() )->is_offline_mode() ) {
+ // If it's not active, run it just on the gallery output — unless the site has
+ // opted out of Photon for tiled galleries (e.g. VIP with a built-in image service),
+ // in which case we must not rewrite the origin URLs back onto i0.wp.com.
+ /** This filter is documented in modules/tiled-gallery/tiled-gallery/tiled-gallery-item.php */
+ if ( ! apply_filters( 'jetpack_skip_photon_domain', false )
+ && ! Image_CDN::is_enabled() && ! ( new Status() )->is_offline_mode() ) {
$gallery_html = Image_CDN::filter_the_content( $gallery_html );
}
}
diff --git a/projects/plugins/jetpack/modules/tiled-gallery/tiled-gallery/tiled-gallery-item.php b/projects/plugins/jetpack/modules/tiled-gallery/tiled-gallery/tiled-gallery-item.php
index ac8c0d953711..98361bfc2eb4 100644
--- a/projects/plugins/jetpack/modules/tiled-gallery/tiled-gallery/tiled-gallery-item.php
+++ b/projects/plugins/jetpack/modules/tiled-gallery/tiled-gallery/tiled-gallery-item.php
@@ -110,9 +110,33 @@ public function __construct( $attachment_image, $needs_attachment_link, $graysca
if ( $this->image->height === $this->image->width ) {
$img_args['crop'] = true;
}
- // The function will always photonoize the URL (even if Photon is
- // not active). We need to photonize the URL to set the width/height.
- $this->img_src = Image_CDN_Core::cdn_url( $this->orig_file, $img_args );
+ /**
+ * Allow sites to opt out of routing Tiled Gallery images through the Photon domain (the
+ * Jetpack Image CDN / i0.wp.com), while still appending the same resizing query args the
+ * tiled layout needs (w, h, crop, resize, strip).
+ *
+ * This is primarily intended for platforms such as WordPress VIP where a Photon-equivalent
+ * image service is built in and already understands those query args on the origin host, so
+ * rewriting URLs onto i0.wp.com is redundant or undesirable.
+ *
+ * When this returns true, image URLs keep their original domain and the args are simply
+ * added as query parameters instead of being sent through Image_CDN_Core::cdn_url().
+ *
+ * @module tiled-gallery
+ *
+ * @since $$next-version$$
+ *
+ * @param bool false Whether to skip the Photon domain and keep origin image URLs. Default false.
+ */
+ if ( apply_filters( 'jetpack_skip_photon_domain', false ) ) {
+ // Keep the origin URL, but append the same query args Photon would have used so a
+ // platform-level image service (e.g. VIP) can resize/crop from the same parameters.
+ $this->img_src = add_query_arg( $img_args, $this->orig_file );
+ } else {
+ // The function will always photonoize the URL (even if Photon is
+ // not active). We need to photonize the URL to set the width/height.
+ $this->img_src = Image_CDN_Core::cdn_url( $this->orig_file, $img_args );
+ }
$image_meta = wp_get_attachment_metadata( $attachment_image->ID );
$size_array = array( absint( $this->image->width ), absint( $this->image->height ) );