From 3ab72635e0b2a7ae87c3db80d403f7e8d35d2002 Mon Sep 17 00:00:00 2001 From: Enej Bajgoric Date: Fri, 10 Jul 2026 14:28:15 -0700 Subject: [PATCH 01/11] Tiled Gallery: source Photon-domain check from block editor settings filter --- .../update-tiled-gallery-skip-photon-domain | 4 +++ .../deprecated/v2/utils/index.js | 9 ++--- .../deprecated/v3/utils/index.js | 9 ++--- .../deprecated/v4/utils/index.js | 9 ++--- .../deprecated/v6/utils/index.js | 9 ++--- .../blocks/tiled-gallery/tiled-gallery.php | 23 ++++++++++--- .../blocks/tiled-gallery/utils/index.js | 34 +++++++------------ 7 files changed, 43 insertions(+), 54 deletions(-) create mode 100644 projects/plugins/jetpack/changelog/update-tiled-gallery-skip-photon-domain diff --git a/projects/plugins/jetpack/changelog/update-tiled-gallery-skip-photon-domain b/projects/plugins/jetpack/changelog/update-tiled-gallery-skip-photon-domain new file mode 100644 index 000000000000..604195233be5 --- /dev/null +++ b/projects/plugins/jetpack/changelog/update-tiled-gallery-skip-photon-domain @@ -0,0 +1,4 @@ +Significance: patch +Type: other + +Tiled Gallery: source the Photon-domain decision from the block editor settings filter instead of an inline script, and dedupe the check across block versions. diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v2/utils/index.js b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v2/utils/index.js index 8fadc3c4ea5f..0f9f8802fc26 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v2/utils/index.js +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v2/utils/index.js @@ -1,5 +1,6 @@ import { isBlobURL } from '@wordpress/blob'; import photon from 'photon'; +import { skipPhotonDomain } from '../../../utils'; import { PHOTON_MAX_RESIZE } from '../constants'; export function isSquareishLayout( layout ) { @@ -39,7 +40,7 @@ export function photonizedImgProps( img, galleryAtts = {} ) { const { layoutStyle } = galleryAtts; const photonImplementation = - isWpcomFilesUrl( url ) || true === isVIP() ? photonWpcomImage : photon; + isWpcomFilesUrl( url ) || skipPhotonDomain() ? photonWpcomImage : photon; /** * Build the `src` @@ -97,12 +98,6 @@ export function photonizedImgProps( img, galleryAtts = {} ) { return Object.assign( { src }, srcSet && { srcSet } ); } -function isVIP() { - /*global jetpack_plan*/ - if ( typeof jetpack_plan !== 'undefined' && jetpack_plan.data === 'vip' ) { - return true; - } -} function isWpcomFilesUrl( url ) { const { host } = new URL( url, window.location.href ); return /\.files\.wordpress\.com$/.test( host ); diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v3/utils/index.js b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v3/utils/index.js index 327c61d005d8..ac865cc2c921 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v3/utils/index.js +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v3/utils/index.js @@ -1,5 +1,6 @@ import { isBlobURL } from '@wordpress/blob'; import photon from 'photon'; +import { skipPhotonDomain } from '../../../utils'; import { PHOTON_MAX_RESIZE } from '../constants'; export function isSquareishLayout( layout ) { @@ -40,7 +41,7 @@ export function photonizedImgProps( img, galleryAtts = {} ) { const { layoutStyle } = galleryAtts; const photonImplementation = - isWpcomFilesUrl( url ) || true === isVIP() ? photonWpcomImage : photon; + isWpcomFilesUrl( url ) || skipPhotonDomain() ? photonWpcomImage : photon; /** * Build the `src` @@ -98,12 +99,6 @@ export function photonizedImgProps( img, galleryAtts = {} ) { return Object.assign( { src }, srcSet && { srcSet } ); } -function isVIP() { - /*global jetpack_plan*/ - if ( typeof jetpack_plan !== 'undefined' && jetpack_plan.data === 'vip' ) { - return true; - } -} function isWpcomFilesUrl( url ) { const { host } = new URL( url, window.location.href ); return /\.files\.wordpress\.com$/.test( host ); diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v4/utils/index.js b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v4/utils/index.js index 77240e1999be..192e1149cbfc 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v4/utils/index.js +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v4/utils/index.js @@ -3,6 +3,7 @@ import { isPrivateSite } from '@automattic/jetpack-shared-extension-utils'; import { isBlobURL } from '@wordpress/blob'; import photon from 'photon'; import isOfflineMode from '../../../../../shared/is-offline-mode'; +import { skipPhotonDomain } from '../../../utils'; import { PHOTON_MAX_RESIZE } from '../constants'; export function isSquareishLayout( layout ) { @@ -48,7 +49,7 @@ export function photonizedImgProps( img, galleryAtts = {} ) { const { layoutStyle } = galleryAtts; const photonImplementation = - isWpcomFilesUrl( url ) || true === isVIP() ? photonWpcomImage : photon; + isWpcomFilesUrl( url ) || skipPhotonDomain() ? photonWpcomImage : photon; /** * Build the `src` @@ -106,12 +107,6 @@ export function photonizedImgProps( img, galleryAtts = {} ) { return Object.assign( { src }, srcSet && { srcSet } ); } -function isVIP() { - /*global jetpack_plan*/ - if ( typeof jetpack_plan !== 'undefined' && jetpack_plan.data === 'vip' ) { - return true; - } -} function isWpcomFilesUrl( url ) { const { host } = new URL( url, window.location.href ); return /\.files\.wordpress\.com$/.test( host ); diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v6/utils/index.js b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v6/utils/index.js index 77240e1999be..192e1149cbfc 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v6/utils/index.js +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v6/utils/index.js @@ -3,6 +3,7 @@ import { isPrivateSite } from '@automattic/jetpack-shared-extension-utils'; import { isBlobURL } from '@wordpress/blob'; import photon from 'photon'; import isOfflineMode from '../../../../../shared/is-offline-mode'; +import { skipPhotonDomain } from '../../../utils'; import { PHOTON_MAX_RESIZE } from '../constants'; export function isSquareishLayout( layout ) { @@ -48,7 +49,7 @@ export function photonizedImgProps( img, galleryAtts = {} ) { const { layoutStyle } = galleryAtts; const photonImplementation = - isWpcomFilesUrl( url ) || true === isVIP() ? photonWpcomImage : photon; + isWpcomFilesUrl( url ) || skipPhotonDomain() ? photonWpcomImage : photon; /** * Build the `src` @@ -106,12 +107,6 @@ export function photonizedImgProps( img, galleryAtts = {} ) { return Object.assign( { src }, srcSet && { srcSet } ); } -function isVIP() { - /*global jetpack_plan*/ - if ( typeof jetpack_plan !== 'undefined' && jetpack_plan.data === 'vip' ) { - return true; - } -} function isWpcomFilesUrl( url ) { const { host } = new URL( url, window.location.href ); return /\.files\.wordpress\.com$/.test( host ); 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..102bb242e5d4 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/tiled-gallery.php +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/tiled-gallery.php @@ -48,9 +48,28 @@ public static function register() { 'render_email_callback' => array( __CLASS__, 'render_email' ), ) ); + + add_filter( 'block_editor_settings_all', array( __CLASS__, 'add_block_editor_settings' ) ); } } + /** + * Expose whether the current site should skip the external Photon domain to the block editor. + * + * VIP sites serve images from a Photon-like host and must not be routed through the public + * photon.js domain. The value is read by skipPhotonDomain() in utils/index.js (Simple sites are + * handled separately there via isSimpleSite()). + * + * @param array $settings The block editor settings. + * @return array The filtered block editor settings. + */ + public static function add_block_editor_settings( $settings ) { + $jetpack_plan = Jetpack_Plan::get(); + $settings['skip_photon_domain'] = 'vip' === $jetpack_plan['product_slug']; + + return $settings; + } + /** * Tiled gallery block registration * @@ -63,10 +82,6 @@ public static function render( $attr, $content ) { Jetpack_Gutenberg::load_assets_as_required( __DIR__ ); $is_squareish_layout = self::is_squareish_layout( $attr ); - // For backward compatibility (ensuring Tiled Galleries using now deprecated versions of the block are not affected). - // See isVIP() in utils/index.js. - $jetpack_plan = Jetpack_Plan::get(); - wp_localize_script( 'jetpack-gallery-settings', 'jetpack_plan', array( 'data' => $jetpack_plan['product_slug'] ) ); if ( preg_match_all( '/]+>/', $content, $images ) ) { /** 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..57bdc326ab7b 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/utils/index.js +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/utils/index.js @@ -6,17 +6,6 @@ import photon from 'photon'; import isOfflineMode from '../../../shared/is-offline-mode'; import { PHOTON_MAX_RESIZE } from '../constants'; -let jetpackPlanFromState; - -window.addEventListener( 'load', function () { - const hasImageCompare = select( 'core/block-editor' ) - .getBlocks() - .some( block => block.name === 'jetpack/image-compare' ); - if ( hasImageCompare && ! jetpackPlanFromState ) { - jetpackPlanFromState = window?.Jetpack_Editor_Initial_State?.jetpack?.jetpack_plan; - } -} ); - export function isSquareishLayout( layout ) { return [ 'circle', 'square' ].includes( layout ); } @@ -59,7 +48,7 @@ export function photonizedImgProps( img, galleryAtts = {} ) { const { height, width } = img; const { layoutStyle } = galleryAtts; - const photonImplementation = true === isVIP() || isSimpleSite() ? photonWpcomImage : photon; + const photonImplementation = skipPhotonDomain() || isSimpleSite() ? photonWpcomImage : photon; /** * Build the `src` @@ -117,16 +106,17 @@ export function photonizedImgProps( img, galleryAtts = {} ) { return Object.assign( { src }, srcSet && { srcSet } ); } -function isVIP() { - /*global jetpack_plan*/ - // Use `jetpackPlanFromState` if available, otherwise fall back to `jetpack_plan` defined within the render function in tiled-gallery.php. - let jetpackPlan; - if ( typeof jetpackPlanFromState !== 'undefined' ) { - jetpackPlan = jetpackPlanFromState; - } else if ( typeof jetpack_plan !== 'undefined' ) { - jetpackPlan = jetpack_plan; - } - return jetpackPlan && jetpackPlan?.data === 'vip'; +/** + * Whether the current site should skip the external Photon (photon.js) domain and instead build + * files.wordpress.com-style URLs via photonWpcomImage. + * + * The value is provided by the `block_editor_settings_all` filter in tiled-gallery.php and read + * here from the block editor settings. + * + * @return {boolean} True when the external Photon domain should be skipped. + */ +export function skipPhotonDomain() { + return true === select( 'core/block-editor' )?.getSettings()?.skip_photon_domain; } /** From f6198343f1a8a0dfba3ffe5904fa34f6e9fda79a Mon Sep 17 00:00:00 2001 From: Enej Bajgoric Date: Fri, 10 Jul 2026 14:50:55 -0700 Subject: [PATCH 02/11] Tiled Gallery: add jetpack_skip_photon_domain filter for the Photon-domain decision --- .../update-tiled-gallery-skip-photon-domain | 2 +- .../blocks/tiled-gallery/tiled-gallery.php | 20 +++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/projects/plugins/jetpack/changelog/update-tiled-gallery-skip-photon-domain b/projects/plugins/jetpack/changelog/update-tiled-gallery-skip-photon-domain index 604195233be5..a9c70a9f8633 100644 --- a/projects/plugins/jetpack/changelog/update-tiled-gallery-skip-photon-domain +++ b/projects/plugins/jetpack/changelog/update-tiled-gallery-skip-photon-domain @@ -1,4 +1,4 @@ Significance: patch Type: other -Tiled Gallery: source the Photon-domain decision from the block editor settings filter instead of an inline script, and dedupe the check across block versions. +Tiled Gallery: source the Photon-domain decision from the block editor settings filter instead of an inline script, dedupe the check across block versions, and add a `jetpack_skip_photon_domain` filter to control it. 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 102bb242e5d4..59798a2f15bd 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/tiled-gallery.php +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/tiled-gallery.php @@ -64,8 +64,24 @@ public static function register() { * @return array The filtered block editor settings. */ public static function add_block_editor_settings( $settings ) { - $jetpack_plan = Jetpack_Plan::get(); - $settings['skip_photon_domain'] = 'vip' === $jetpack_plan['product_slug']; + $jetpack_plan = Jetpack_Plan::get(); + + /** + * Filter whether the Tiled Gallery and Image Compare blocks should skip the external + * Photon (photon.js) domain and build files.wordpress.com-style image URLs instead. + * + * Defaults to true on VIP sites only, and false everywhere else. + * + * @module tiled-gallery + * + * @since $$next-version$$ + * + * @param bool $skip_photon_domain Whether to skip the external Photon domain. + */ + $settings['skip_photon_domain'] = apply_filters( + 'jetpack_skip_photon_domain', + 'vip' === $jetpack_plan['product_slug'] + ); return $settings; } From da68c4f8be9d650af0a251b4103570af792e52ab Mon Sep 17 00:00:00 2001 From: Enej Bajgoric Date: Fri, 10 Jul 2026 20:18:14 -0700 Subject: [PATCH 03/11] Tiled Gallery: read skip_photon_domain from core/editor settings, not core/block-editor --- .../jetpack/extensions/blocks/tiled-gallery/utils/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 57bdc326ab7b..ef66ac5d564a 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/utils/index.js +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/utils/index.js @@ -116,7 +116,10 @@ export function photonizedImgProps( img, galleryAtts = {} ) { * @return {boolean} True when the external Photon domain should be skipped. */ export function skipPhotonDomain() { - return true === select( 'core/block-editor' )?.getSettings()?.skip_photon_domain; + // The value is injected via the `block_editor_settings_all` PHP filter, which surfaces on the + // `core/editor` editor settings. Note it does NOT propagate into `core/block-editor`'s + // getSettings() — that store only forwards a fixed allowlist of keys, so custom keys are dropped. + return true === select( 'core/editor' )?.getEditorSettings?.()?.skip_photon_domain; } /** From c2ecb4feddcdaad29b791940397cb70bffe14baf Mon Sep 17 00:00:00 2001 From: Enej Bajgoric Date: Wed, 29 Jul 2026 14:41:58 -0700 Subject: [PATCH 04/11] Tiled Gallery: deliver skip_photon_domain via the editor initial state so block validation sees it --- .../update-tiled-gallery-skip-photon-domain | 4 +-- .../jetpack/class.jetpack-gutenberg.php | 20 +++++++++++ .../blocks/tiled-gallery/tiled-gallery.php | 36 ------------------- .../blocks/tiled-gallery/utils/index.js | 16 ++++----- 4 files changed, 30 insertions(+), 46 deletions(-) diff --git a/projects/plugins/jetpack/changelog/update-tiled-gallery-skip-photon-domain b/projects/plugins/jetpack/changelog/update-tiled-gallery-skip-photon-domain index a9c70a9f8633..df440f8a5eb4 100644 --- a/projects/plugins/jetpack/changelog/update-tiled-gallery-skip-photon-domain +++ b/projects/plugins/jetpack/changelog/update-tiled-gallery-skip-photon-domain @@ -1,4 +1,4 @@ Significance: patch -Type: other +Type: bugfix -Tiled Gallery: source the Photon-domain decision from the block editor settings filter instead of an inline script, dedupe the check across block versions, and add a `jetpack_skip_photon_domain` filter to control it. +Tiled Gallery: fix images being routed through the external Photon domain on VIP sites, and add a `jetpack_skip_photon_domain` filter to control that decision. diff --git a/projects/plugins/jetpack/class.jetpack-gutenberg.php b/projects/plugins/jetpack/class.jetpack-gutenberg.php index 3fa75194bea9..374e08b00584 100644 --- a/projects/plugins/jetpack/class.jetpack-gutenberg.php +++ b/projects/plugins/jetpack/class.jetpack-gutenberg.php @@ -881,6 +881,26 @@ public static function enqueue_block_editor_assets() { 'jetpack_plan' => array( 'data' => $jetpack_plan['product_slug'], ), + /** + * Filter whether the Tiled Gallery and Image Compare blocks should skip the external + * Photon (i0.wp.com) domain and build files.wordpress.com-style image URLs instead. + * + * VIP sites serve images from a Photon-like host of their own, and images routed + * through the public Photon domain are not reachable there. Defaults to true on VIP + * sites only, and false everywhere else. + * + * The value is read by skipPhotonDomain() in the Tiled Gallery block. It is delivered + * through this initial state rather than the block editor settings because it is used + * by the blocks' save() output, which is regenerated during block validation before + * the editor stores are populated. + * + * @module tiled-gallery + * + * @since $$next-version$$ + * + * @param bool $skip_photon_domain Whether to skip the external Photon domain. + */ + 'skip_photon_domain' => (bool) apply_filters( 'jetpack_skip_photon_domain', 'vip' === $jetpack_plan['product_slug'] ), /** * 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 59798a2f15bd..54e2d01586bd 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/tiled-gallery.php +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/tiled-gallery.php @@ -11,7 +11,6 @@ namespace Automattic\Jetpack\Extensions; use Automattic\Jetpack\Blocks; -use Automattic\Jetpack\Current_Plan as Jetpack_Plan; use Automattic\Jetpack\Status; use Automattic\Jetpack\Status\Host; use Jetpack; @@ -48,44 +47,9 @@ public static function register() { 'render_email_callback' => array( __CLASS__, 'render_email' ), ) ); - - add_filter( 'block_editor_settings_all', array( __CLASS__, 'add_block_editor_settings' ) ); } } - /** - * Expose whether the current site should skip the external Photon domain to the block editor. - * - * VIP sites serve images from a Photon-like host and must not be routed through the public - * photon.js domain. The value is read by skipPhotonDomain() in utils/index.js (Simple sites are - * handled separately there via isSimpleSite()). - * - * @param array $settings The block editor settings. - * @return array The filtered block editor settings. - */ - public static function add_block_editor_settings( $settings ) { - $jetpack_plan = Jetpack_Plan::get(); - - /** - * Filter whether the Tiled Gallery and Image Compare blocks should skip the external - * Photon (photon.js) domain and build files.wordpress.com-style image URLs instead. - * - * Defaults to true on VIP sites only, and false everywhere else. - * - * @module tiled-gallery - * - * @since $$next-version$$ - * - * @param bool $skip_photon_domain Whether to skip the external Photon domain. - */ - $settings['skip_photon_domain'] = apply_filters( - 'jetpack_skip_photon_domain', - 'vip' === $jetpack_plan['product_slug'] - ); - - return $settings; - } - /** * Tiled gallery block registration * 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 ef66ac5d564a..7cf84216c5b5 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/utils/index.js +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/utils/index.js @@ -1,7 +1,6 @@ import { isWoASite, isSimpleSite } from '@automattic/jetpack-script-data'; -import { isPrivateSite } from '@automattic/jetpack-shared-extension-utils'; +import { getJetpackData, isPrivateSite } from '@automattic/jetpack-shared-extension-utils'; import { isBlobURL } from '@wordpress/blob'; -import { select } from '@wordpress/data'; import photon from 'photon'; import isOfflineMode from '../../../shared/is-offline-mode'; import { PHOTON_MAX_RESIZE } from '../constants'; @@ -110,16 +109,17 @@ export function photonizedImgProps( img, galleryAtts = {} ) { * Whether the current site should skip the external Photon (photon.js) domain and instead build * files.wordpress.com-style URLs via photonWpcomImage. * - * The value is provided by the `block_editor_settings_all` filter in tiled-gallery.php and read - * here from the block editor settings. + * The value is computed in PHP (true on VIP sites, overridable via the `jetpack_skip_photon_domain` + * filter) and delivered in the editor initial state. It is deliberately read from that global rather + * than from editor settings or any other store: this runs inside the blocks' save() output, which is + * regenerated during block validation while the post is parsed — before the editor stores hold any + * settings. Reading store state there yields the default value on the first pass and marks saved + * galleries as invalid. * * @return {boolean} True when the external Photon domain should be skipped. */ export function skipPhotonDomain() { - // The value is injected via the `block_editor_settings_all` PHP filter, which surfaces on the - // `core/editor` editor settings. Note it does NOT propagate into `core/block-editor`'s - // getSettings() — that store only forwards a fixed allowlist of keys, so custom keys are dropped. - return true === select( 'core/editor' )?.getEditorSettings?.()?.skip_photon_domain; + return true === getJetpackData()?.jetpack?.skip_photon_domain; } /** From e922bc6d6972f9c471dc28cb9083bc97477f43da Mon Sep 17 00:00:00 2001 From: Enej Bajgoric Date: Wed, 29 Jul 2026 14:42:10 -0700 Subject: [PATCH 05/11] Tiled Gallery: cover skipPhotonDomain and its effect on photonized image props --- .../blocks/tiled-gallery/utils/test/index.js | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 projects/plugins/jetpack/extensions/blocks/tiled-gallery/utils/test/index.js diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/utils/test/index.js b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/utils/test/index.js new file mode 100644 index 000000000000..92cbd354fbf1 --- /dev/null +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/utils/test/index.js @@ -0,0 +1,68 @@ +import { photonizedImgProps, skipPhotonDomain } from '..'; + +const IMAGE = { + url: 'https://example.com/wp-content/uploads/2026/01/cat.jpg', + width: 1200, + height: 800, +}; + +/** + * Set the editor initial state global, the way wp_localize_script() does on an editor page load. + * + * @param {object} jetpack - The `jetpack` section of the initial state. + */ +function setEditorInitialState( jetpack = {} ) { + window.Jetpack_Editor_Initial_State = { jetpack }; +} + +describe( 'skipPhotonDomain', () => { + afterEach( () => { + delete window.Jetpack_Editor_Initial_State; + } ); + + it( 'is false when the editor initial state is missing', () => { + expect( skipPhotonDomain() ).toBe( false ); + } ); + + it( 'is false when the initial state does not flag it', () => { + setEditorInitialState( { jetpack_plan: { data: 'jetpack_free' } } ); + + expect( skipPhotonDomain() ).toBe( false ); + } ); + + // The value has to come from the initial state global rather than an editor store: it feeds the + // blocks' save() output, which is regenerated during block validation before any store holds + // editor settings. + it( 'is true when the initial state flags it', () => { + setEditorInitialState( { skip_photon_domain: true } ); + + expect( skipPhotonDomain() ).toBe( true ); + } ); +} ); + +describe( 'photonizedImgProps', () => { + afterEach( () => { + delete window.Jetpack_Editor_Initial_State; + } ); + + it( 'routes images through the Photon domain by default', () => { + const { src, srcSet } = photonizedImgProps( IMAGE ); + + expect( src ).toBe( 'https://i0.wp.com/example.com/wp-content/uploads/2026/01/cat.jpg?ssl=1' ); + expect( srcSet ).toContain( + 'https://i0.wp.com/example.com/wp-content/uploads/2026/01/cat.jpg' + ); + } ); + + it( 'keeps images on the site host when the Photon domain is skipped', () => { + setEditorInitialState( { skip_photon_domain: true } ); + + const { src, srcSet } = photonizedImgProps( IMAGE ); + + expect( src ).toBe( 'https://example.com/wp-content/uploads/2026/01/cat.jpg' ); + expect( srcSet ).not.toContain( 'i0.wp.com' ); + expect( srcSet ).toContain( + 'https://example.com/wp-content/uploads/2026/01/cat.jpg?strip=info&w=600' + ); + } ); +} ); From 22198f20787d2c7f0f31b46236882191b42309e7 Mon Sep 17 00:00:00 2001 From: Enej Bajgoric Date: Tue, 4 Aug 2026 11:30:16 -0700 Subject: [PATCH 06/11] Tiled Gallery: deliver skip_photon_domain through the script data, not the deprecated editor initial state --- .../jetpack/class.jetpack-gutenberg.php | 20 ------ .../blocks/tiled-gallery/utils/index.js | 36 ++++++---- .../blocks/tiled-gallery/utils/test/index.js | 41 +++++++----- .../jetpack/src/class-jetpack-script-data.php | 40 +++++++++++ .../php/src/Jetpack_Script_Data_Test.php | 67 +++++++++++++++++++ 5 files changed, 155 insertions(+), 49 deletions(-) diff --git a/projects/plugins/jetpack/class.jetpack-gutenberg.php b/projects/plugins/jetpack/class.jetpack-gutenberg.php index 374e08b00584..3fa75194bea9 100644 --- a/projects/plugins/jetpack/class.jetpack-gutenberg.php +++ b/projects/plugins/jetpack/class.jetpack-gutenberg.php @@ -881,26 +881,6 @@ public static function enqueue_block_editor_assets() { 'jetpack_plan' => array( 'data' => $jetpack_plan['product_slug'], ), - /** - * Filter whether the Tiled Gallery and Image Compare blocks should skip the external - * Photon (i0.wp.com) domain and build files.wordpress.com-style image URLs instead. - * - * VIP sites serve images from a Photon-like host of their own, and images routed - * through the public Photon domain are not reachable there. Defaults to true on VIP - * sites only, and false everywhere else. - * - * The value is read by skipPhotonDomain() in the Tiled Gallery block. It is delivered - * through this initial state rather than the block editor settings because it is used - * by the blocks' save() output, which is regenerated during block validation before - * the editor stores are populated. - * - * @module tiled-gallery - * - * @since $$next-version$$ - * - * @param bool $skip_photon_domain Whether to skip the external Photon domain. - */ - 'skip_photon_domain' => (bool) apply_filters( 'jetpack_skip_photon_domain', 'vip' === $jetpack_plan['product_slug'] ), /** * Enable the RePublicize UI in the block editor context. * 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 7cf84216c5b5..3ed594d2bcbb 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/utils/index.js +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/utils/index.js @@ -1,5 +1,5 @@ -import { isWoASite, isSimpleSite } from '@automattic/jetpack-script-data'; -import { getJetpackData, isPrivateSite } from '@automattic/jetpack-shared-extension-utils'; +import { getScriptData, isWoASite, isSimpleSite } from '@automattic/jetpack-script-data'; +import { isPrivateSite } from '@automattic/jetpack-shared-extension-utils'; import { isBlobURL } from '@wordpress/blob'; import photon from 'photon'; import isOfflineMode from '../../../shared/is-offline-mode'; @@ -12,16 +12,24 @@ export function isSquareishLayout( layout ) { /** * Build src and srcSet properties which can be used on an * - * @param {object} img - Image - * @param {number} img.height - Image height - * @param {string} img.url - Image URL - * @param {number} img.width - Image width - * @param {object} galleryAtts - Gallery attributes relevant for image optimization. - * @param {string} galleryAtts.layoutStyle - Gallery layout. 'rectangular', 'circle', etc. - * @param {number} galleryAtts.columns - Gallery columns. Not applicable for all layouts. + * @param {object} img - Image + * @param {number} img.height - Image height + * @param {string} img.url - Image URL + * @param {number} img.width - Image width + * @param {object} galleryAtts - Gallery attributes relevant for image optimization. + * @param {string} galleryAtts.layoutStyle - Gallery layout. 'rectangular', 'circle', etc. + * @param {number} galleryAtts.columns - Gallery columns. Not applicable for all layouts. + * @param {object} options - Options. + * @param {boolean} options.skipPhotonDomain - Whether to skip the external Photon domain. Defaults + * to what the site asks for; deprecated block versions + * pass `false` to keep emitting the URLs they saved. * @return {object} Returns an object. If possible, the object will include `src` and `srcSet` properties {string} for use on an image. */ -export function photonizedImgProps( img, galleryAtts = {} ) { +export function photonizedImgProps( + img, + galleryAtts = {}, + { skipPhotonDomain: skipDomain = skipPhotonDomain() } = {} +) { if ( ! img.height || ! img.width ) { return img.url ? { src: img.url } : {}; } @@ -47,7 +55,7 @@ export function photonizedImgProps( img, galleryAtts = {} ) { const { height, width } = img; const { layoutStyle } = galleryAtts; - const photonImplementation = skipPhotonDomain() || isSimpleSite() ? photonWpcomImage : photon; + const photonImplementation = skipDomain || isSimpleSite() ? photonWpcomImage : photon; /** * Build the `src` @@ -110,8 +118,8 @@ export function photonizedImgProps( img, galleryAtts = {} ) { * files.wordpress.com-style URLs via photonWpcomImage. * * The value is computed in PHP (true on VIP sites, overridable via the `jetpack_skip_photon_domain` - * filter) and delivered in the editor initial state. It is deliberately read from that global rather - * than from editor settings or any other store: this runs inside the blocks' save() output, which is + * filter) and delivered in the script data. It is deliberately read from that global rather than from + * editor settings or any other store: this runs inside the blocks' save() output, which is * regenerated during block validation while the post is parsed — before the editor stores hold any * settings. Reading store state there yields the default value on the first pass and marks saved * galleries as invalid. @@ -119,7 +127,7 @@ export function photonizedImgProps( img, galleryAtts = {} ) { * @return {boolean} True when the external Photon domain should be skipped. */ export function skipPhotonDomain() { - return true === getJetpackData()?.jetpack?.skip_photon_domain; + return true === getScriptData()?.jetpack?.flags?.skipPhotonDomain; } /** diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/utils/test/index.js b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/utils/test/index.js index 92cbd354fbf1..75e0178add45 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/utils/test/index.js +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/utils/test/index.js @@ -7,34 +7,33 @@ const IMAGE = { }; /** - * Set the editor initial state global, the way wp_localize_script() does on an editor page load. + * Set the script data global, the way the PHP inline script does on a page load. * - * @param {object} jetpack - The `jetpack` section of the initial state. + * @param {object} jetpack - The `jetpack` section of the script data. */ -function setEditorInitialState( jetpack = {} ) { - window.Jetpack_Editor_Initial_State = { jetpack }; +function setScriptData( jetpack = {} ) { + window.JetpackScriptData = { jetpack }; } describe( 'skipPhotonDomain', () => { afterEach( () => { - delete window.Jetpack_Editor_Initial_State; + delete window.JetpackScriptData; } ); - it( 'is false when the editor initial state is missing', () => { + it( 'is false when the script data is missing', () => { expect( skipPhotonDomain() ).toBe( false ); } ); - it( 'is false when the initial state does not flag it', () => { - setEditorInitialState( { jetpack_plan: { data: 'jetpack_free' } } ); + it( 'is false when the script data does not flag it', () => { + setScriptData( { flags: { showJetpackBranding: true } } ); expect( skipPhotonDomain() ).toBe( false ); } ); - // The value has to come from the initial state global rather than an editor store: it feeds the - // blocks' save() output, which is regenerated during block validation before any store holds - // editor settings. - it( 'is true when the initial state flags it', () => { - setEditorInitialState( { skip_photon_domain: true } ); + // The value has to come from this global rather than an editor store: it feeds the blocks' save() + // output, which is regenerated during block validation before any store holds editor settings. + it( 'is true when the script data flags it', () => { + setScriptData( { flags: { skipPhotonDomain: true } } ); expect( skipPhotonDomain() ).toBe( true ); } ); @@ -42,7 +41,7 @@ describe( 'skipPhotonDomain', () => { describe( 'photonizedImgProps', () => { afterEach( () => { - delete window.Jetpack_Editor_Initial_State; + delete window.JetpackScriptData; } ); it( 'routes images through the Photon domain by default', () => { @@ -55,7 +54,7 @@ describe( 'photonizedImgProps', () => { } ); it( 'keeps images on the site host when the Photon domain is skipped', () => { - setEditorInitialState( { skip_photon_domain: true } ); + setScriptData( { flags: { skipPhotonDomain: true } } ); const { src, srcSet } = photonizedImgProps( IMAGE ); @@ -65,4 +64,16 @@ describe( 'photonizedImgProps', () => { 'https://example.com/wp-content/uploads/2026/01/cat.jpg?strip=info&w=600' ); } ); + + // Deprecated block versions pass this so they keep emitting the URLs they originally saved. + it( 'stays on the Photon domain when the caller overrides the site setting', () => { + setScriptData( { flags: { skipPhotonDomain: true } } ); + + const { src, srcSet } = photonizedImgProps( IMAGE, {}, { skipPhotonDomain: false } ); + + expect( src ).toBe( 'https://i0.wp.com/example.com/wp-content/uploads/2026/01/cat.jpg?ssl=1' ); + expect( srcSet ).toContain( + 'https://i0.wp.com/example.com/wp-content/uploads/2026/01/cat.jpg' + ); + } ); } ); diff --git a/projects/plugins/jetpack/src/class-jetpack-script-data.php b/projects/plugins/jetpack/src/class-jetpack-script-data.php index f57935611918..d8a777005fdb 100644 --- a/projects/plugins/jetpack/src/class-jetpack-script-data.php +++ b/projects/plugins/jetpack/src/class-jetpack-script-data.php @@ -9,6 +9,8 @@ namespace Automattic\Jetpack\Plugin; +use Automattic\Jetpack\Current_Plan as Jetpack_Plan; + /** * Jetpack_Script_Data class. */ @@ -40,9 +42,47 @@ public static function set_admin_script_data( $data ) { $data['jetpack'] = array( 'flags' => array( 'showJetpackBranding' => (bool) apply_filters( 'jetpack_show_editor_panel_branding', true ), + 'skipPhotonDomain' => self::should_skip_photon_domain(), ), ); return $data; } + + /** + * Whether image-serving blocks should skip the external Photon domain (i0.wp.com) and build + * URLs on the site's own Photon-like host instead. + * + * VIP sites serve images from a host of their own, and images routed through the public Photon + * domain are not reachable there. + * + * This is read by the Tiled Gallery block's skipPhotonDomain(), which runs inside the block's + * save() output — including when the editor regenerates that output to validate saved content. + * That regeneration happens while the post is being parsed, before any editor store holds + * settings, so the value has to travel in the script data rather than in editor settings. + * + * @since $$next-version$$ + * + * @return bool + */ + private static function should_skip_photon_domain() { + $jetpack_plan = Jetpack_Plan::get(); + + /** + * Filter whether the Tiled Gallery and Image Compare blocks should skip the external + * Photon (i0.wp.com) domain and build image URLs on the site's own host instead. + * + * Defaults to true on VIP sites only, and false everywhere else. + * + * Changing this changes the markup those blocks save, so existing galleries are re-serialized + * the next time they are saved. + * + * @module tiled-gallery + * + * @since $$next-version$$ + * + * @param bool $skip_photon_domain Whether to skip the external Photon domain. + */ + return (bool) apply_filters( 'jetpack_skip_photon_domain', 'vip' === $jetpack_plan['product_slug'] ); + } } diff --git a/projects/plugins/jetpack/tests/php/src/Jetpack_Script_Data_Test.php b/projects/plugins/jetpack/tests/php/src/Jetpack_Script_Data_Test.php index f405bfea9fe0..11fe197f74d6 100644 --- a/projects/plugins/jetpack/tests/php/src/Jetpack_Script_Data_Test.php +++ b/projects/plugins/jetpack/tests/php/src/Jetpack_Script_Data_Test.php @@ -5,6 +5,7 @@ * @package automattic/jetpack */ +use Automattic\Jetpack\Current_Plan as Jetpack_Plan; use Automattic\Jetpack\Plugin\Jetpack_Script_Data; use PHPUnit\Framework\Attributes\CoversClass; @@ -17,15 +18,53 @@ class Jetpack_Script_Data_Test extends WP_UnitTestCase { use \Automattic\Jetpack\PHPUnit\WP_UnitTestCase_Fix; + /** + * Test set up. + */ + public function set_up() { + parent::set_up(); + $this->reset_active_plan_cache(); + } + /** * Test tear down. */ public function tear_down() { remove_all_filters( 'jetpack_show_editor_panel_branding' ); remove_all_filters( 'jetpack_admin_js_script_data' ); + remove_all_filters( 'jetpack_skip_photon_domain' ); + remove_all_filters( 'pre_option_jetpack_active_plan' ); + $this->reset_active_plan_cache(); parent::tear_down(); } + /** + * Drop Current_Plan's per-request cache so each test sees the plan it sets up. + */ + private function reset_active_plan_cache() { + $prop = new ReflectionProperty( Jetpack_Plan::class, 'active_plan_cache' ); + // setAccessible() is a no-op (and deprecated) since PHP 8.1; only needed for older versions. + if ( PHP_VERSION_ID < 80100 ) { + $prop->setAccessible( true ); + } + $prop->setValue( null, null ); + } + + /** + * Pretend the site is on the given plan, the way a synced plan would look. + * + * @param string $product_slug Plan product slug. + */ + private function set_plan( $product_slug ) { + add_filter( + 'pre_option_jetpack_active_plan', + function () use ( $product_slug ) { + return array( 'product_slug' => $product_slug ); + } + ); + $this->reset_active_plan_cache(); + } + /** * Tests that configure() registers the filter. */ @@ -51,6 +90,34 @@ public function test_filter_disables_branding() { $this->assertFalse( $result['jetpack']['flags']['showJetpackBranding'] ); } + /** + * Tests that sites keep using the external Photon domain by default. + */ + public function test_default_skip_photon_domain_is_false() { + $this->set_plan( 'jetpack_free' ); + $result = Jetpack_Script_Data::set_admin_script_data( array() ); + $this->assertFalse( $result['jetpack']['flags']['skipPhotonDomain'] ); + } + + /** + * Tests that VIP sites skip the external Photon domain. + */ + public function test_skip_photon_domain_is_true_on_vip() { + $this->set_plan( 'vip' ); + $result = Jetpack_Script_Data::set_admin_script_data( array() ); + $this->assertTrue( $result['jetpack']['flags']['skipPhotonDomain'] ); + } + + /** + * Tests that the filter can flip the decision on a non-VIP site. + */ + public function test_filter_overrides_skip_photon_domain() { + $this->set_plan( 'jetpack_free' ); + add_filter( 'jetpack_skip_photon_domain', '__return_true' ); + $result = Jetpack_Script_Data::set_admin_script_data( array() ); + $this->assertTrue( $result['jetpack']['flags']['skipPhotonDomain'] ); + } + /** * Tests that existing data in the array is preserved. */ From 3d647ba4ae4d1e1c6dfc45993a642e03ecde7ed4 Mon Sep 17 00:00:00 2001 From: Enej Bajgoric Date: Tue, 4 Aug 2026 11:30:32 -0700 Subject: [PATCH 07/11] Tiled Gallery: keep deprecated versions on the Photon domain so existing galleries stay valid --- .../update-tiled-gallery-skip-photon-domain | 2 +- .../blocks/tiled-gallery/deprecated/index.js | 2 + .../deprecated/v2/utils/index.js | 7 +- .../deprecated/v3/utils/index.js | 7 +- .../deprecated/v4/utils/index.js | 7 +- .../deprecated/v6/utils/index.js | 7 +- .../deprecated/v8/layout/index.jsx | 4 +- .../tiled-gallery/deprecated/v9/index.js | 6 + .../tiled-gallery/deprecated/v9/save.jsx | 60 ++++++++ .../blocks/tiled-gallery/layout/index.jsx | 9 +- .../tiled-gallery/test/photon-domain.js | 130 ++++++++++++++++++ 11 files changed, 226 insertions(+), 15 deletions(-) create mode 100644 projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/index.js create mode 100644 projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/save.jsx create mode 100644 projects/plugins/jetpack/extensions/blocks/tiled-gallery/test/photon-domain.js diff --git a/projects/plugins/jetpack/changelog/update-tiled-gallery-skip-photon-domain b/projects/plugins/jetpack/changelog/update-tiled-gallery-skip-photon-domain index df440f8a5eb4..09c4828c0f0d 100644 --- a/projects/plugins/jetpack/changelog/update-tiled-gallery-skip-photon-domain +++ b/projects/plugins/jetpack/changelog/update-tiled-gallery-skip-photon-domain @@ -1,4 +1,4 @@ Significance: patch Type: bugfix -Tiled Gallery: fix images being routed through the external Photon domain on VIP sites, and add a `jetpack_skip_photon_domain` filter to control that decision. +Tiled Gallery: fix images being routed through the external Photon domain on VIP sites, keep existing galleries valid when that changes, and add a `jetpack_skip_photon_domain` filter to control it. diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/index.js b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/index.js index d58e782338b0..5118a027d4ee 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/index.js +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/index.js @@ -6,8 +6,10 @@ import * as deprecatedV5 from './v5'; import * as deprecatedV6 from './v6'; import * as deprecatedV7 from './v7'; import * as deprecatedV8 from './v8'; +import * as deprecatedV9 from './v9'; export default [ + deprecatedV9, deprecatedV8, deprecatedV7, deprecatedV6, diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v2/utils/index.js b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v2/utils/index.js index 0f9f8802fc26..4b31e500d1d6 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v2/utils/index.js +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v2/utils/index.js @@ -1,6 +1,5 @@ import { isBlobURL } from '@wordpress/blob'; import photon from 'photon'; -import { skipPhotonDomain } from '../../../utils'; import { PHOTON_MAX_RESIZE } from '../constants'; export function isSquareishLayout( layout ) { @@ -39,8 +38,10 @@ export function photonizedImgProps( img, galleryAtts = {} ) { const { height, width } = img; const { layoutStyle } = galleryAtts; - const photonImplementation = - isWpcomFilesUrl( url ) || skipPhotonDomain() ? photonWpcomImage : photon; + // Deprecated versions have to keep producing the URLs they originally saved, or content saved + // under them stops validating, so they always use the external Photon domain — regardless of what + // the site's Photon-domain setting says. Only the current version follows that setting. + const photonImplementation = isWpcomFilesUrl( url ) ? photonWpcomImage : photon; /** * Build the `src` diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v3/utils/index.js b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v3/utils/index.js index ac865cc2c921..e63b5aeaff34 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v3/utils/index.js +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v3/utils/index.js @@ -1,6 +1,5 @@ import { isBlobURL } from '@wordpress/blob'; import photon from 'photon'; -import { skipPhotonDomain } from '../../../utils'; import { PHOTON_MAX_RESIZE } from '../constants'; export function isSquareishLayout( layout ) { @@ -40,8 +39,10 @@ export function photonizedImgProps( img, galleryAtts = {} ) { const { height, width } = img; const { layoutStyle } = galleryAtts; - const photonImplementation = - isWpcomFilesUrl( url ) || skipPhotonDomain() ? photonWpcomImage : photon; + // Deprecated versions have to keep producing the URLs they originally saved, or content saved + // under them stops validating, so they always use the external Photon domain — regardless of what + // the site's Photon-domain setting says. Only the current version follows that setting. + const photonImplementation = isWpcomFilesUrl( url ) ? photonWpcomImage : photon; /** * Build the `src` diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v4/utils/index.js b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v4/utils/index.js index 192e1149cbfc..6abfb95b71f0 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v4/utils/index.js +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v4/utils/index.js @@ -3,7 +3,6 @@ import { isPrivateSite } from '@automattic/jetpack-shared-extension-utils'; import { isBlobURL } from '@wordpress/blob'; import photon from 'photon'; import isOfflineMode from '../../../../../shared/is-offline-mode'; -import { skipPhotonDomain } from '../../../utils'; import { PHOTON_MAX_RESIZE } from '../constants'; export function isSquareishLayout( layout ) { @@ -48,8 +47,10 @@ export function photonizedImgProps( img, galleryAtts = {} ) { const { height, width } = img; const { layoutStyle } = galleryAtts; - const photonImplementation = - isWpcomFilesUrl( url ) || skipPhotonDomain() ? photonWpcomImage : photon; + // Deprecated versions have to keep producing the URLs they originally saved, or content saved + // under them stops validating, so they always use the external Photon domain — regardless of what + // the site's Photon-domain setting says. Only the current version follows that setting. + const photonImplementation = isWpcomFilesUrl( url ) ? photonWpcomImage : photon; /** * Build the `src` diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v6/utils/index.js b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v6/utils/index.js index 192e1149cbfc..6abfb95b71f0 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v6/utils/index.js +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v6/utils/index.js @@ -3,7 +3,6 @@ import { isPrivateSite } from '@automattic/jetpack-shared-extension-utils'; import { isBlobURL } from '@wordpress/blob'; import photon from 'photon'; import isOfflineMode from '../../../../../shared/is-offline-mode'; -import { skipPhotonDomain } from '../../../utils'; import { PHOTON_MAX_RESIZE } from '../constants'; export function isSquareishLayout( layout ) { @@ -48,8 +47,10 @@ export function photonizedImgProps( img, galleryAtts = {} ) { const { height, width } = img; const { layoutStyle } = galleryAtts; - const photonImplementation = - isWpcomFilesUrl( url ) || skipPhotonDomain() ? photonWpcomImage : photon; + // Deprecated versions have to keep producing the URLs they originally saved, or content saved + // under them stops validating, so they always use the external Photon domain — regardless of what + // the site's Photon-domain setting says. Only the current version follows that setting. + const photonImplementation = isWpcomFilesUrl( url ) ? photonWpcomImage : photon; /** * Build the `src` diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v8/layout/index.jsx b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v8/layout/index.jsx index 4ea01568d389..fde24346e023 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v8/layout/index.jsx +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v8/layout/index.jsx @@ -37,7 +37,9 @@ export default class Layout extends Component { ); const Image = GalleryImageSave; - const { src, srcSet } = photonizedImgProps( img, { layoutStyle } ); + // This deprecated version has to keep producing the URLs it originally saved, so it always + // uses the external Photon domain. Only the current version follows the site's setting. + const { src, srcSet } = photonizedImgProps( img, { layoutStyle }, { skipPhotonDomain: false } ); return ( + + + ); +} diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/layout/index.jsx b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/layout/index.jsx index 837a377998cf..1429c7c31470 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/layout/index.jsx +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/layout/index.jsx @@ -28,6 +28,7 @@ export default class Layout extends Component { onSelectImage, selectedImage, setImageAttributes, + skipPhotonDomain, } = this.props; const ariaLabel = sprintf( @@ -38,7 +39,13 @@ export default class Layout extends Component { ); const Image = isSave ? GalleryImageSave : GalleryImageEdit; - const { src, srcSet } = photonizedImgProps( img, { layoutStyle } ); + // `skipPhotonDomain` is only passed by the deprecated version that has to keep emitting the + // external Photon domain; left undefined, the images follow whatever this site asks for. + const { src, srcSet } = photonizedImgProps( + img, + { layoutStyle }, + skipPhotonDomain === undefined ? undefined : { skipPhotonDomain } + ); return ( { + beforeEach( () => { + registerBlockType( metadata.name, { ...metadata, save, deprecated } ); + } ); + + afterEach( () => { + unregisterBlockType( metadata.name ); + delete window.JetpackScriptData; + } ); + + it( 'saves images on the external Photon domain by default', () => { + setSkipPhotonDomain( false ); + + expect( saveGallery() ).toContain( 'https://i0.wp.com/example.com/wp-content/uploads' ); + } ); + + it( 'saves images on the site host when the site skips the Photon domain', () => { + setSkipPhotonDomain( true ); + const markup = saveGallery(); + + expect( markup ).not.toContain( 'i0.wp.com' ); + expect( markup ).toContain( 'src="https://example.com/wp-content/uploads/2026/07/one.png"' ); + } ); + + // The regression this guards: a VIP site's galleries were saved with Photon-domain URLs before the + // site's setting was honoured, and every one of them was flagged invalid on the next editor load. + it( 'keeps galleries saved on the Photon domain valid once the site skips it', () => { + setSkipPhotonDomain( false ); + const markupSavedBefore = saveGallery(); + + setSkipPhotonDomain( true ); + const [ block ] = parse( markupSavedBefore ); + + expect( block.name ).toBe( metadata.name ); + expect( block.isValid ).toBe( true ); + expect( block.attributes.images ).toHaveLength( 2 ); + // Gutenberg informs when a deprecation matched, so this also pins down *how* it stayed valid. + expect( console ).toHaveInformed(); + } ); + + it( 're-serializes those galleries onto the site host', () => { + setSkipPhotonDomain( false ); + const markupSavedBefore = saveGallery(); + + setSkipPhotonDomain( true ); + const reSerialized = serialize( parse( markupSavedBefore ) ); + + expect( reSerialized ).not.toContain( 'i0.wp.com' ); + expect( console ).toHaveInformed(); + } ); + + it( 'keeps galleries saved on the site host valid while the site still skips the domain', () => { + setSkipPhotonDomain( true ); + const [ block ] = parse( saveGallery() ); + + expect( block.isValid ).toBe( true ); + } ); + + // The deprecation earns its keep by mirroring the current markup exactly, so that galleries saved + // by this version before the setting was honoured match it. The older v8 deprecation happens to + // match them too, but only because the validator forgives the stray whitespace node in its + // wrapper — so keep this in step with save.jsx rather than leaning on that. + it( 'has a deprecation mirroring the current markup, apart from the image host', () => { + setSkipPhotonDomain( false ); + const attributes = { + ...createBlock( metadata.name, { images: IMAGES, ids: [ 5, 6 ] } ).attributes, + }; + + expect( getSaveContent( { ...metadata, ...photonDomainDeprecation }, attributes ) ).toBe( + getSaveContent( { ...metadata, save }, attributes ) + ); + } ); +} ); From 5836382e8f172b696b2bb7eb05dd922651e282c2 Mon Sep 17 00:00:00 2001 From: Enej Bajgoric Date: Tue, 4 Aug 2026 16:07:43 -0700 Subject: [PATCH 08/11] Tiled Gallery: freeze the new deprecation and cover the case that makes it necessary --- .../update-tiled-gallery-skip-photon-domain | 4 + .../shared-extension-utils/declarations.d.ts | 1 + .../tiled-gallery/deprecated/v9/attributes.js | 84 +++++++++++++++++++ .../tiled-gallery/deprecated/v9/index.js | 7 +- .../tiled-gallery/deprecated/v9/save.jsx | 10 ++- .../tiled-gallery/deprecated/v9/supports.js | 15 ++++ .../blocks/tiled-gallery/layout/index.jsx | 10 +-- .../tiled-gallery/test/photon-domain.js | 33 ++++---- .../jetpack/src/class-jetpack-script-data.php | 4 + 9 files changed, 139 insertions(+), 29 deletions(-) create mode 100644 projects/js-packages/shared-extension-utils/changelog/update-tiled-gallery-skip-photon-domain create mode 100644 projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/attributes.js create mode 100644 projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/supports.js diff --git a/projects/js-packages/shared-extension-utils/changelog/update-tiled-gallery-skip-photon-domain b/projects/js-packages/shared-extension-utils/changelog/update-tiled-gallery-skip-photon-domain new file mode 100644 index 000000000000..d7dde041c416 --- /dev/null +++ b/projects/js-packages/shared-extension-utils/changelog/update-tiled-gallery-skip-photon-domain @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Add skipPhotonDomain to the typed Jetpack script data flags. diff --git a/projects/js-packages/shared-extension-utils/declarations.d.ts b/projects/js-packages/shared-extension-utils/declarations.d.ts index ccf9467d2d10..421e3c427229 100644 --- a/projects/js-packages/shared-extension-utils/declarations.d.ts +++ b/projects/js-packages/shared-extension-utils/declarations.d.ts @@ -9,6 +9,7 @@ declare module '@automattic/jetpack-script-data' { jetpack?: { flags: { showJetpackBranding: boolean; + skipPhotonDomain: boolean; }; }; } diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/attributes.js b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/attributes.js new file mode 100644 index 000000000000..4158fc6738d4 --- /dev/null +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/attributes.js @@ -0,0 +1,84 @@ +// Frozen snapshot of the attributes at the time this markup was current. Never update it: the +// deprecation exists to parse content already saved in that shape. +export default { + align: { + type: 'string', + default: 'center', + }, + className: { + type: 'string', + default: 'is-style-rectangular', + }, + columns: { + type: 'number', + }, + columnWidths: { + type: 'array', + default: [], + }, + ids: { + type: 'array', + default: [], + }, + imageFilter: { + type: 'string', + }, + images: { + type: 'array', + default: [], + source: 'query', + selector: '.tiled-gallery__item', + query: { + alt: { + attribute: 'alt', + default: '', + selector: 'img', + source: 'attribute', + }, + height: { + attribute: 'data-height', + type: 'number', + selector: 'img', + source: 'attribute', + }, + id: { + attribute: 'data-id', + selector: 'img', + source: 'attribute', + }, + link: { + attribute: 'data-link', + selector: 'img', + source: 'attribute', + }, + customLink: { + attribute: 'href', + selector: 'a', + source: 'attribute', + }, + url: { + attribute: 'data-url', + selector: 'img', + source: 'attribute', + }, + width: { + attribute: 'data-width', + selector: 'img', + source: 'attribute', + type: 'number', + }, + }, + }, + imageCrop: { + type: 'boolean', + default: true, + }, + linkTo: { + default: 'none', + type: 'string', + }, + roundedCorners: { + type: 'integer', + default: 0, + }, +}; diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/index.js b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/index.js index 5d566050d040..177863fd1b57 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/index.js +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/index.js @@ -1,6 +1,3 @@ -import metadata from '../../block.json'; - -// Same attributes and supports as the current version — only the saved image URLs differ. -export const attributes = metadata.attributes; -export const supports = metadata.supports; +export { default as attributes } from './attributes'; +export { default as supports } from './supports'; export { default as save } from './save'; diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/save.jsx b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/save.jsx index 9029b26ee3d4..87acfb3f587d 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/save.jsx +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/save.jsx @@ -16,9 +16,13 @@ import Layout from '../../layout'; * Attributes are unchanged, so there is nothing to migrate: matching here keeps the block valid, and * the next save re-serializes it with the URLs the site asks for. * - * This is deliberately kept even though the v8 deprecation also matches that content today: v8 does - * so only because block validation forgives the stray whitespace node in its wrapper, which is not - * something to rely on. Keep this file in step with ../../save.jsx. + * This is the only deprecation that reproduces the current markup. The much older v6 happens to match + * it too for plain galleries — its wrapper differs only by whitespace, which validation forgives — + * but v6 knows nothing about custom links, so galleries using `linkTo: 'custom'` match here and + * nowhere else. Removing this file invalidates exactly those. + * + * Never edit this file to follow ../../save.jsx. It has to keep emitting the markup that is already + * in the database; when the current markup changes, add a new deprecation instead. * * @param {object} props - Block props. * @param {object} props.attributes - Block attributes. diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/supports.js b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/supports.js new file mode 100644 index 000000000000..913b91d0ec4b --- /dev/null +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/supports.js @@ -0,0 +1,15 @@ +// Frozen snapshot of the supports at the time this markup was current. See ./attributes.js. +export default { + align: [ 'center', 'wide', 'full' ], + color: { + gradients: true, + text: false, + }, + customClassName: false, + html: false, + spacing: { + margin: true, + padding: true, + }, + __experimentalHideChildBlockControls: true, +}; diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/layout/index.jsx b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/layout/index.jsx index 1429c7c31470..652024fe0de7 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/layout/index.jsx +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/layout/index.jsx @@ -39,13 +39,9 @@ export default class Layout extends Component { ); const Image = isSave ? GalleryImageSave : GalleryImageEdit; - // `skipPhotonDomain` is only passed by the deprecated version that has to keep emitting the - // external Photon domain; left undefined, the images follow whatever this site asks for. - const { src, srcSet } = photonizedImgProps( - img, - { layoutStyle }, - skipPhotonDomain === undefined ? undefined : { skipPhotonDomain } - ); + // Only the deprecated version that has to keep emitting the external Photon domain passes this; + // left undefined, the images follow whatever this site asks for. + const { src, srcSet } = photonizedImgProps( img, { layoutStyle }, { skipPhotonDomain } ); return ( { expect( block.name ).toBe( metadata.name ); expect( block.isValid ).toBe( true ); expect( block.attributes.images ).toHaveLength( 2 ); - // Gutenberg informs when a deprecation matched, so this also pins down *how* it stayed valid. + // Gutenberg informs when a deprecation matched, so this also proves it was rescued rather than + // matching the current save() by accident. expect( console ).toHaveInformed(); } ); @@ -113,18 +112,24 @@ describe( 'Tiled Gallery Photon domain changes', () => { expect( block.isValid ).toBe( true ); } ); - // The deprecation earns its keep by mirroring the current markup exactly, so that galleries saved - // by this version before the setting was honoured match it. The older v8 deprecation happens to - // match them too, but only because the validator forgives the stray whitespace node in its - // wrapper — so keep this in step with save.jsx rather than leaning on that. - it( 'has a deprecation mirroring the current markup, apart from the image host', () => { + // Galleries using custom links match the deprecation added for this change and nothing else — the + // much older v6 covers plain galleries by whitespace luck, but knows nothing about custom links. + // This case is what keeps that deprecation from being deleted as redundant. + it( 'keeps galleries with custom links valid too', () => { setSkipPhotonDomain( false ); - const attributes = { - ...createBlock( metadata.name, { images: IMAGES, ids: [ 5, 6 ] } ).attributes, - }; - - expect( getSaveContent( { ...metadata, ...photonDomainDeprecation }, attributes ) ).toBe( - getSaveContent( { ...metadata, save }, attributes ) + const markupSavedBefore = serialize( + createBlock( metadata.name, { + images: IMAGES.map( image => ( { ...image, customLink: `${ image.link }custom/` } ) ), + ids: [ 5, 6 ], + linkTo: 'custom', + } ) ); + + setSkipPhotonDomain( true ); + const [ block ] = parse( markupSavedBefore ); + + expect( block.isValid ).toBe( true ); + expect( block.attributes.images[ 0 ].customLink ).toBe( 'https://example.com/one/custom/' ); + expect( console ).toHaveInformed(); } ); } ); diff --git a/projects/plugins/jetpack/src/class-jetpack-script-data.php b/projects/plugins/jetpack/src/class-jetpack-script-data.php index d8a777005fdb..1443efb4af42 100644 --- a/projects/plugins/jetpack/src/class-jetpack-script-data.php +++ b/projects/plugins/jetpack/src/class-jetpack-script-data.php @@ -61,6 +61,10 @@ public static function set_admin_script_data( $data ) { * That regeneration happens while the post is being parsed, before any editor store holds * settings, so the value has to travel in the script data rather than in editor settings. * + * The signal is the plan slug rather than Host::is_vip_site(): it is what the block's previous + * isVIP() check used, and switching to the WPCOM_IS_VIP_ENV constant would change which sites get + * site-host URLs — and so which existing galleries need a deprecation to stay valid. + * * @since $$next-version$$ * * @return bool From 7dd61ba73faa81693d241fe9e60257adc4b58702 Mon Sep 17 00:00:00 2001 From: Enej Bajgoric Date: Wed, 5 Aug 2026 09:10:32 -0700 Subject: [PATCH 09/11] Tiled Gallery: cover both image hosts with deprecations so flipping the setting is safe either way --- .../{v9 => image-host}/attributes.js | 0 .../deprecated/image-host/index.js | 8 ++ .../deprecated/image-host/save.jsx | 74 +++++++++++++++++++ .../deprecated/{v9 => image-host}/supports.js | 0 .../blocks/tiled-gallery/deprecated/index.js | 5 +- .../tiled-gallery/deprecated/v9/index.js | 3 - .../tiled-gallery/deprecated/v9/save.jsx | 64 ---------------- .../tiled-gallery/test/photon-domain.js | 15 ++++ .../jetpack/src/class-jetpack-script-data.php | 20 +++-- .../php/src/Jetpack_Script_Data_Test.php | 23 ++++++ 10 files changed, 137 insertions(+), 75 deletions(-) rename projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/{v9 => image-host}/attributes.js (100%) create mode 100644 projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/image-host/index.js create mode 100644 projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/image-host/save.jsx rename projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/{v9 => image-host}/supports.js (100%) delete mode 100644 projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/index.js delete mode 100644 projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/save.jsx diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/attributes.js b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/image-host/attributes.js similarity index 100% rename from projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/attributes.js rename to projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/image-host/attributes.js diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/image-host/index.js b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/image-host/index.js new file mode 100644 index 000000000000..b806623299b0 --- /dev/null +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/image-host/index.js @@ -0,0 +1,8 @@ +import attributes from './attributes'; +import { savePhotonDomain, saveSiteHost } from './save'; +import supports from './supports'; + +// Two deprecations carrying the current markup, one per image host — see ./save.jsx for why both +// exist and why neither may be edited to follow the current save(). +export const photonDomain = { attributes, supports, save: savePhotonDomain }; +export const siteHost = { attributes, supports, save: saveSiteHost }; diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/image-host/save.jsx b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/image-host/save.jsx new file mode 100644 index 000000000000..5d6cf4decbf9 --- /dev/null +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/image-host/save.jsx @@ -0,0 +1,74 @@ +import { useBlockProps } from '@wordpress/block-editor'; +import clsx from 'clsx'; +import { getActiveStyleName } from '../../../../shared/block-styles'; +import { LAYOUT_STYLES } from '../../constants'; +import { defaultColumnsNumber } from '../../edit'; +import Layout from '../../layout'; + +/** + * Build a save() with the current markup that always serves images from one particular host, + * whatever this site currently asks for. + * + * The block bakes photonized image URLs into its markup, so the site's Photon-domain setting decides + * what save() emits. Whenever that setting changes — a site moving on or off the VIP plan, or the + * `jetpack_skip_photon_domain` filter being added or removed — the galleries already in the database + * hold the other host's URLs, the current save() regenerates something different while the post is + * parsed, and every one of them is flagged as invalid on load. One of these two deprecations + * reproduces that markup, whichever direction the setting moved in. + * + * Attributes are unchanged, so there is nothing to migrate: matching keeps the block valid, and the + * next save re-serializes it with the URLs the site asks for now. + * + * These are the only deprecations that reproduce the current markup. The much older v6 happens to + * match plain galleries too — its wrapper differs only by whitespace, which validation forgives — but + * it knows nothing about custom links, so galleries using `linkTo: 'custom'` match here and nowhere + * else. + * + * Never edit this file to follow ../../save.jsx. It has to keep emitting markup that is already in + * the database; when the current markup changes, add a new deprecation instead. + * + * @param {boolean} skipPhotonDomain - Whether to build site-host URLs rather than Photon ones. + * @return {Function} A save component pinned to that host. + */ +function createSave( skipPhotonDomain ) { + return function TiledGallerySave( { attributes } ) { + const { imageFilter, images } = attributes; + + if ( ! images.length ) { + return null; + } + + const { + align, + className, + columns = defaultColumnsNumber( attributes ), + linkTo, + roundedCorners, + columnWidths, + } = attributes; + const blockProps = useBlockProps.save(); + + return ( +
+ +
+ ); + }; +} + +// Galleries saved while the site served images from the external Photon domain. +export const savePhotonDomain = createSave( false ); + +// Galleries saved while the site served images from its own host. +export const saveSiteHost = createSave( true ); diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/supports.js b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/image-host/supports.js similarity index 100% rename from projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/supports.js rename to projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/image-host/supports.js diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/index.js b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/index.js index 5118a027d4ee..4fa938ee2410 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/index.js +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/index.js @@ -1,3 +1,4 @@ +import { photonDomain, siteHost } from './image-host'; import * as deprecatedV1 from './v1'; import * as deprecatedV2 from './v2'; import * as deprecatedV3 from './v3'; @@ -6,10 +7,10 @@ import * as deprecatedV5 from './v5'; import * as deprecatedV6 from './v6'; import * as deprecatedV7 from './v7'; import * as deprecatedV8 from './v8'; -import * as deprecatedV9 from './v9'; export default [ - deprecatedV9, + photonDomain, + siteHost, deprecatedV8, deprecatedV7, deprecatedV6, diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/index.js b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/index.js deleted file mode 100644 index 177863fd1b57..000000000000 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export { default as attributes } from './attributes'; -export { default as supports } from './supports'; -export { default as save } from './save'; diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/save.jsx b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/save.jsx deleted file mode 100644 index 87acfb3f587d..000000000000 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v9/save.jsx +++ /dev/null @@ -1,64 +0,0 @@ -import { useBlockProps } from '@wordpress/block-editor'; -import clsx from 'clsx'; -import { getActiveStyleName } from '../../../../shared/block-styles'; -import { LAYOUT_STYLES } from '../../constants'; -import { defaultColumnsNumber } from '../../edit'; -import Layout from '../../layout'; - -/** - * Same markup as the current version, but always building external Photon (i0.wp.com) image URLs. - * - * Galleries saved before the site's Photon-domain setting was honoured have those URLs in their - * markup — on VIP sites in particular, where images are now kept on the site's own host. Without - * this deprecation the current save() regenerates different URLs while the post is parsed and every - * one of those galleries is flagged as invalid on load. - * - * Attributes are unchanged, so there is nothing to migrate: matching here keeps the block valid, and - * the next save re-serializes it with the URLs the site asks for. - * - * This is the only deprecation that reproduces the current markup. The much older v6 happens to match - * it too for plain galleries — its wrapper differs only by whitespace, which validation forgives — - * but v6 knows nothing about custom links, so galleries using `linkTo: 'custom'` match here and - * nowhere else. Removing this file invalidates exactly those. - * - * Never edit this file to follow ../../save.jsx. It has to keep emitting the markup that is already - * in the database; when the current markup changes, add a new deprecation instead. - * - * @param {object} props - Block props. - * @param {object} props.attributes - Block attributes. - * @return {import('react').ReactElement} The saved markup. - */ -export default function TiledGallerySave( { attributes } ) { - const { imageFilter, images } = attributes; - - if ( ! images.length ) { - return null; - } - - const { - align, - className, - columns = defaultColumnsNumber( attributes ), - linkTo, - roundedCorners, - columnWidths, - } = attributes; - const blockProps = useBlockProps.save(); - - return ( -
- -
- ); -} diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/test/photon-domain.js b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/test/photon-domain.js index 6fb7bab1d26a..e5775bd16c3b 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/test/photon-domain.js +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/test/photon-domain.js @@ -112,6 +112,21 @@ describe( 'Tiled Gallery Photon domain changes', () => { expect( block.isValid ).toBe( true ); } ); + // The mirror image of the case above, reachable by removing a `jetpack_skip_photon_domain` filter + // or by a site leaving the VIP plan: the markup holds site-host URLs and save() now emits Photon + // ones. + it( 'keeps galleries saved on the site host valid once the site stops skipping the domain', () => { + setSkipPhotonDomain( true ); + const markupSavedBefore = saveGallery(); + + setSkipPhotonDomain( false ); + const [ block ] = parse( markupSavedBefore ); + + expect( block.isValid ).toBe( true ); + expect( block.attributes.images ).toHaveLength( 2 ); + expect( console ).toHaveInformed(); + } ); + // Galleries using custom links match the deprecation added for this change and nothing else — the // much older v6 covers plain galleries by whitespace luck, but knows nothing about custom links. // This case is what keeps that deprecation from being deleted as redundant. diff --git a/projects/plugins/jetpack/src/class-jetpack-script-data.php b/projects/plugins/jetpack/src/class-jetpack-script-data.php index 1443efb4af42..f2e0cffba72a 100644 --- a/projects/plugins/jetpack/src/class-jetpack-script-data.php +++ b/projects/plugins/jetpack/src/class-jetpack-script-data.php @@ -42,7 +42,7 @@ public static function set_admin_script_data( $data ) { $data['jetpack'] = array( 'flags' => array( 'showJetpackBranding' => (bool) apply_filters( 'jetpack_show_editor_panel_branding', true ), - 'skipPhotonDomain' => self::should_skip_photon_domain(), + 'skipPhotonDomain' => self::should_skip_photon_domain( $data ), ), ); @@ -67,10 +67,17 @@ public static function set_admin_script_data( $data ) { * * @since $$next-version$$ * + * @param array $data The script data, which may already carry the site plan. * @return bool */ - private static function should_skip_photon_domain() { - $jetpack_plan = Jetpack_Plan::get(); + private static function should_skip_photon_domain( $data ) { + // Another package may have put the plan in the payload already (see Publicize_Script_Data); + // only look it up when it hasn't. + $product_slug = isset( $data['site']['plan']['product_slug'] ) ? $data['site']['plan']['product_slug'] : ''; + if ( '' === $product_slug ) { + $jetpack_plan = Jetpack_Plan::get(); + $product_slug = $jetpack_plan['product_slug']; + } /** * Filter whether the Tiled Gallery and Image Compare blocks should skip the external @@ -78,8 +85,9 @@ private static function should_skip_photon_domain() { * * Defaults to true on VIP sites only, and false everywhere else. * - * Changing this changes the markup those blocks save, so existing galleries are re-serialized - * the next time they are saved. + * Changing this changes the markup those blocks save. Galleries saved under the previous value + * stay valid — the block ships a deprecation for each image host, so flipping this either way is + * safe — and they are re-serialized with the new URLs the next time they are saved. * * @module tiled-gallery * @@ -87,6 +95,6 @@ private static function should_skip_photon_domain() { * * @param bool $skip_photon_domain Whether to skip the external Photon domain. */ - return (bool) apply_filters( 'jetpack_skip_photon_domain', 'vip' === $jetpack_plan['product_slug'] ); + return (bool) apply_filters( 'jetpack_skip_photon_domain', 'vip' === $product_slug ); } } diff --git a/projects/plugins/jetpack/tests/php/src/Jetpack_Script_Data_Test.php b/projects/plugins/jetpack/tests/php/src/Jetpack_Script_Data_Test.php index 11fe197f74d6..55c352e2b5f4 100644 --- a/projects/plugins/jetpack/tests/php/src/Jetpack_Script_Data_Test.php +++ b/projects/plugins/jetpack/tests/php/src/Jetpack_Script_Data_Test.php @@ -108,6 +108,29 @@ public function test_skip_photon_domain_is_true_on_vip() { $this->assertTrue( $result['jetpack']['flags']['skipPhotonDomain'] ); } + /** + * Tests that a plan already present in the script data is used instead of looking it up again. + */ + public function test_skip_photon_domain_reads_the_plan_from_the_script_data() { + // A plan lookup would report the free plan; the payload says otherwise. + $this->set_plan( 'jetpack_free' ); + $result = Jetpack_Script_Data::set_admin_script_data( + array( 'site' => array( 'plan' => array( 'product_slug' => 'vip' ) ) ) + ); + $this->assertTrue( $result['jetpack']['flags']['skipPhotonDomain'] ); + } + + /** + * Tests that an empty plan in the script data falls back to a plan lookup. + */ + public function test_skip_photon_domain_falls_back_when_the_script_data_has_no_plan() { + $this->set_plan( 'vip' ); + $result = Jetpack_Script_Data::set_admin_script_data( + array( 'site' => array( 'plan' => array( 'product_slug' => '' ) ) ) + ); + $this->assertTrue( $result['jetpack']['flags']['skipPhotonDomain'] ); + } + /** * Tests that the filter can flip the decision on a non-VIP site. */ From ee806367387c9116fdfda38ae987987b560b0fd5 Mon Sep 17 00:00:00 2001 From: Enej Bajgoric Date: Wed, 5 Aug 2026 09:28:06 -0700 Subject: [PATCH 10/11] Tiled Gallery: pin the v5 deprecation to the Photon domain too --- .../tiled-gallery/deprecated/v5/save.jsx | 3 +++ .../tiled-gallery/test/photon-domain.js | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v5/save.jsx b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v5/save.jsx index afb8f8cf8776..41a985a3b23a 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v5/save.jsx +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/deprecated/v5/save.jsx @@ -34,6 +34,9 @@ export default function TiledGallerySave( { attributes } ) { linkTo={ linkTo } roundedCorners={ roundedCorners } columnWidths={ columnWidths } + // This version borrows the current Layout, so it has to pin the image host itself or it + // would follow the site's setting and stop matching the content it was saved with. + skipPhotonDomain={ false } /> ); diff --git a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/test/photon-domain.js b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/test/photon-domain.js index e5775bd16c3b..edc366a766e0 100644 --- a/projects/plugins/jetpack/extensions/blocks/tiled-gallery/test/photon-domain.js +++ b/projects/plugins/jetpack/extensions/blocks/tiled-gallery/test/photon-domain.js @@ -7,6 +7,7 @@ */ import { createBlock, + getSaveContent, parse, registerBlockType, serialize, @@ -127,6 +128,24 @@ describe( 'Tiled Gallery Photon domain changes', () => { expect( console ).toHaveInformed(); } ); + // Only the current version may follow the site's setting. A deprecation that follows it stops + // reproducing the markup it was saved with, which is what invalidates existing galleries — and it is + // easy to reintroduce, since some deprecations borrow the current Layout. + it( 'pins every deprecation to one image host, whatever the site asks for', () => { + const attributes = createBlock( metadata.name, { images: IMAGES, ids: [ 5, 6 ] } ).attributes; + + deprecated.forEach( ( deprecation, index ) => { + const blockType = { ...metadata, ...deprecation }; + + setSkipPhotonDomain( false ); + const whenPhoton = getSaveContent( blockType, attributes ); + setSkipPhotonDomain( true ); + const whenSkipping = getSaveContent( blockType, attributes ); + + expect( { index, markup: whenSkipping } ).toEqual( { index, markup: whenPhoton } ); + } ); + } ); + // Galleries using custom links match the deprecation added for this change and nothing else — the // much older v6 covers plain galleries by whitespace luck, but knows nothing about custom links. // This case is what keeps that deprecation from being deleted as redundant. From 0d8bcde731b706524b7b09b05a4b0a75651798ca Mon Sep 17 00:00:00 2001 From: Enej Bajgoric Date: Wed, 5 Aug 2026 09:59:38 -0700 Subject: [PATCH 11/11] Tiled Gallery: use null coalescing for the plan slug lookup --- projects/plugins/jetpack/src/class-jetpack-script-data.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/plugins/jetpack/src/class-jetpack-script-data.php b/projects/plugins/jetpack/src/class-jetpack-script-data.php index f2e0cffba72a..8cfc22e3c6ce 100644 --- a/projects/plugins/jetpack/src/class-jetpack-script-data.php +++ b/projects/plugins/jetpack/src/class-jetpack-script-data.php @@ -73,7 +73,7 @@ public static function set_admin_script_data( $data ) { private static function should_skip_photon_domain( $data ) { // Another package may have put the plan in the payload already (see Publicize_Script_Data); // only look it up when it hasn't. - $product_slug = isset( $data['site']['plan']['product_slug'] ) ? $data['site']['plan']['product_slug'] : ''; + $product_slug = $data['site']['plan']['product_slug'] ?? ''; if ( '' === $product_slug ) { $jetpack_plan = Jetpack_Plan::get(); $product_slug = $jetpack_plan['product_slug'];