diff --git a/assets/src/block-editor/helpers/index.js b/assets/src/block-editor/helpers/index.js index 517810e994a..76697361e28 100644 --- a/assets/src/block-editor/helpers/index.js +++ b/assets/src/block-editor/helpers/index.js @@ -8,7 +8,7 @@ import { ReactElement } from 'react'; * WordPress dependencies */ import { __, _x } from '@wordpress/i18n'; -import { cloneElement, RawHTML, render } from '@wordpress/element'; +import { cloneElement, render } from '@wordpress/element'; import { TextControl, SelectControl, ToggleControl, Notice, PanelBody, FontSizePicker } from '@wordpress/components'; import { InspectorControls } from '@wordpress/block-editor'; import { select } from '@wordpress/data'; @@ -92,15 +92,17 @@ const ampLayoutOptions = [ */ export const addAMPAttributes = ( settings, name ) => { // AMP Carousel settings. - if ( 'core/shortcode' === name || 'core/gallery' === name ) { + if ( 'core/gallery' === name ) { if ( ! settings.attributes ) { settings.attributes = {}; } settings.attributes.ampCarousel = { type: 'boolean', + default: ! select( 'amp/block-editor' ).hasThemeSupport(), // @todo We could just default this to false now even in Reader mode since block styles are loaded. }; settings.attributes.ampLightbox = { type: 'boolean', + default: false, }; } @@ -111,6 +113,7 @@ export const addAMPAttributes = ( settings, name ) => { } settings.attributes.ampLightbox = { type: 'boolean', + default: false, }; } @@ -122,6 +125,7 @@ export const addAMPAttributes = ( settings, name ) => { settings.attributes = {}; } settings.attributes.ampFitText = { + type: 'boolean', default: false, }; settings.attributes.minFont = { @@ -171,58 +175,12 @@ export const addAMPAttributes = ( settings, name ) => { * @return {Object} Output element. */ export const filterBlocksSave = ( element, blockType, attributes ) => { // eslint-disable-line complexity - let text = attributes.text || '', - content = ''; - const fitTextProps = { layout: 'fixed-height', }; - if ( 'core/shortcode' === blockType.name && isGalleryShortcode( attributes ) ) { - if ( ! attributes.ampLightbox ) { - if ( hasGalleryShortcodeLightboxAttribute( attributes.text || '' ) ) { - text = removeAmpLightboxFromShortcodeAtts( attributes.text ); - } - } - if ( attributes.ampCarousel ) { - // If the text contains amp-carousel or amp-lightbox, lets remove it. - if ( hasGalleryShortcodeCarouselAttribute( text ) ) { - text = removeAmpCarouselFromShortcodeAtts( text ); - } - - // If lightbox is not set, we can return here. - if ( ! attributes.ampLightbox ) { - if ( attributes.text !== text ) { - return ( - - { text } - - ); - } - - // Else lets return original. - return element; - } - } else if ( ! hasGalleryShortcodeCarouselAttribute( attributes.text || '' ) ) { - // Add amp-carousel=false attribute to the shortcode. - text = attributes.text.replace( '[gallery', '[gallery amp-carousel=false' ); - } else { - text = attributes.text; - } - - if ( attributes.ampLightbox && ! hasGalleryShortcodeLightboxAttribute( text ) ) { - text = text.replace( '[gallery', '[gallery amp-lightbox=true' ); - } - - if ( attributes.text !== text ) { - return ( - - { text } - - ); - } - } else if ( 'core/paragraph' === blockType.name && ! attributes.ampFitText ) { - content = getAmpFitTextContent( attributes.content ); + if ( 'core/paragraph' === blockType.name && ! attributes.ampFitText ) { + const content = getAmpFitTextContent( attributes.content ); if ( content !== attributes.content ) { return cloneElement( element, @@ -300,48 +258,6 @@ export const getLayoutOptions = ( block ) => { return layoutOptions; }; -/** - * Add extra data-amp-layout attribute to save to DB. - * - * @param {Object} props Properties. - * @param {Object} blockType Block type. - * @param {Object} blockType.name Block type name. - * @param {Object} attributes Attributes. - * - * @return {Object} Props. - */ -export const addAMPExtraProps = ( props, blockType, attributes ) => { - const ampAttributes = {}; - - // Shortcode props are handled differently. - if ( 'core/shortcode' === blockType.name ) { - return props; - } - - // AMP blocks handle layout and other props on their own. - if ( 'amp/' === blockType.name.substr( 0, 4 ) ) { - return props; - } - - if ( attributes.ampLayout ) { - ampAttributes[ 'data-amp-layout' ] = attributes.ampLayout; - } - if ( attributes.ampNoLoading ) { - ampAttributes[ 'data-amp-noloading' ] = attributes.ampNoLoading; - } - if ( attributes.ampLightbox ) { - ampAttributes[ 'data-amp-lightbox' ] = attributes.ampLightbox; - } - if ( attributes.ampCarousel ) { - ampAttributes[ 'data-amp-carousel' ] = attributes.ampCarousel; - } - - return { - ...ampAttributes, - ...props, - }; -}; - /** * Filters blocks edit function of all blocks. * @@ -351,26 +267,11 @@ export const addAMPExtraProps = ( props, blockType, attributes ) => { */ export const filterBlocksEdit = ( BlockEdit ) => { const EnhancedBlockEdit = function( props ) { - const { attributes: { text, ampLayout }, setAttributes, name } = props; + const { attributes: { ampLayout }, name } = props; let inspectorControls; - if ( 'core/shortcode' === name ) { - // Lets remove amp-carousel from edit view. - if ( hasGalleryShortcodeCarouselAttribute( text || '' ) ) { - setAttributes( { text: removeAmpCarouselFromShortcodeAtts( text ) } ); - } - // Lets remove amp-lightbox from edit view. - if ( hasGalleryShortcodeLightboxAttribute( text || '' ) ) { - setAttributes( { text: removeAmpLightboxFromShortcodeAtts( text ) } ); - } - - inspectorControls = setUpShortcodeInspectorControls( props ); - if ( '' === inspectorControls ) { - // Return original. - return ; - } - } else if ( 'core/gallery' === name ) { + if ( 'core/gallery' === name ) { inspectorControls = setUpGalleryInspectorControls( props ); } else if ( 'core/image' === name ) { inspectorControls = setUpImageInspectorControls( props ); @@ -411,6 +312,8 @@ export const filterBlocksEdit = ( BlockEdit ) => { * Set width and height in case of image block. * * @param {Object} props Props. + * @param {Function} props.setAttributes Callback to set attributes. + * @param {Object} props.attributes Attributes. * @param {string} layout Layout. */ export const setImageBlockLayoutAttributes = ( props, layout ) => { @@ -530,7 +433,7 @@ const AmpNoloadingToggle = ( props ) => { AmpNoloadingToggle.propTypes = { attributes: PropTypes.shape( { - ampNoLoading: PropTypes.string, + ampNoLoading: PropTypes.bool, } ), setAttributes: PropTypes.func.isRequired, }; @@ -541,6 +444,9 @@ AmpNoloadingToggle.propTypes = { * @todo Consider wrapping the render function to delete the original font size in text settings when ampFitText. * * @param {Object} props Props. + * @param {Function} props.setAttributes Callback to set attributes. + * @param {Object} props.attributes Attributes. + * @param {boolean} props.isSelected Is selected. * * @return {ReactElement} Inspector Controls. */ @@ -664,7 +570,7 @@ const setUpTextBlocksInspectorControls = ( props ) => { setUpTextBlocksInspectorControls.propTypes = { isSelected: PropTypes.bool, attributes: PropTypes.shape( { - ampFitText: PropTypes.string, + ampFitText: PropTypes.bool, minFont: PropTypes.number, maxFont: PropTypes.number, height: PropTypes.number, @@ -672,38 +578,6 @@ setUpTextBlocksInspectorControls.propTypes = { setAttributes: PropTypes.func.isRequired, }; -/** - * Set up inspector controls for shortcode block. - * Adds ampCarousel attribute in case of gallery shortcode. - * - * @param {Object} props Props. - * - * @return {ReactElement} Inspector controls. - */ -const setUpShortcodeInspectorControls = ( props ) => { - const { isSelected } = props; - - if ( ! isGalleryShortcode( props.attributes ) || ! isSelected ) { - return null; - } - - const hasThemeSupport = select( 'amp/block-editor' ).hasThemeSupport(); - - return ( - - - { hasThemeSupport && } - - - - ); -}; - -setUpShortcodeInspectorControls.propTypes = { - isSelected: PropTypes.bool, - attributes: PropTypes.object, -}; - /** * Get AMP Lightbox toggle control. * @@ -737,7 +611,7 @@ const AmpLightboxToggle = ( props ) => { AmpLightboxToggle.propTypes = { attributes: PropTypes.shape( { - ampLightbox: PropTypes.string, + ampLightbox: PropTypes.bool, ampLayout: PropTypes.string, linkTo: PropTypes.string, } ), @@ -768,7 +642,7 @@ const AmpCarouselToggle = ( props ) => { AmpCarouselToggle.propTypes = { attributes: PropTypes.shape( { - ampCarousel: PropTypes.string, + ampCarousel: PropTypes.bool, } ), setAttributes: PropTypes.func.isRequired, }; @@ -819,12 +693,10 @@ const setUpGalleryInspectorControls = ( props ) => { return null; } - const hasThemeSupport = select( 'amp/block-editor' ).hasThemeSupport(); - return ( - { hasThemeSupport && } + @@ -835,61 +707,6 @@ setUpGalleryInspectorControls.propTypes = { isSelected: PropTypes.bool, }; -/** - * Removes amp-carousel=false from shortcode attributes. - * - * @param {string} shortcode Shortcode text. - * - * @return {string} Modified shortcode. - */ -export const removeAmpCarouselFromShortcodeAtts = ( shortcode ) => { - return shortcode.replace( ' amp-carousel=false', '' ); -}; - -/** - * Removes amp-lightbox=true from shortcode attributes. - * - * @param {string} shortcode Shortcode text. - * - * @return {string} Modified shortcode. - */ -export const removeAmpLightboxFromShortcodeAtts = ( shortcode ) => { - return shortcode.replace( ' amp-lightbox=true', '' ); -}; - -/** - * Determines whether a shortcode includes the amp-carousel attribute. - * - * @param {string} text Shortcode. - * - * @return {boolean} Whether the shortcode includes the attribute. - */ -export const hasGalleryShortcodeCarouselAttribute = ( text ) => { - return -1 !== text.indexOf( 'amp-carousel=false' ); -}; - -/** - * Determines whether a shortcode includes the amp-lightbox attribute. - * - * @param {string} text Shortcode. - * - * @return {boolean} Whether the shortcode includes the attribute. - */ -export const hasGalleryShortcodeLightboxAttribute = ( text ) => { - return -1 !== text.indexOf( 'amp-lightbox=true' ); -}; - -/** - * Determines whether the current shortcode is a gallery shortcode. - * - * @param {Object} attributes Shortcode attributes. - * - * @return {boolean} Whether it is a gallery shortcode. - */ -export const isGalleryShortcode = ( attributes ) => { - return attributes.text && -1 !== attributes.text.indexOf( 'gallery' ); -}; - /** * Determines whether AMP is enabled for the current post or not. * diff --git a/assets/src/block-editor/index.js b/assets/src/block-editor/index.js index 88d4a197e7b..f625e6f2fdd 100644 --- a/assets/src/block-editor/index.js +++ b/assets/src/block-editor/index.js @@ -13,7 +13,7 @@ import domReady from '@wordpress/dom-ready'; import { withFeaturedImageNotice } from '../common/components'; import { getMinimumFeaturedImageDimensions } from '../common/helpers'; import { withMediaLibraryNotice, AMPPreview } from './components'; -import { addAMPAttributes, addAMPExtraProps, filterBlocksEdit, filterBlocksSave, renderPreviewButton } from './helpers'; +import { addAMPAttributes, filterBlocksEdit, filterBlocksSave, renderPreviewButton } from './helpers'; import './store'; const { @@ -31,7 +31,6 @@ plugins.keys().forEach( ( modulePath ) => { addFilter( 'blocks.registerBlockType', 'ampEditorBlocks/addAttributes', addAMPAttributes ); addFilter( 'blocks.getSaveElement', 'ampEditorBlocks/filterSave', filterBlocksSave ); addFilter( 'editor.BlockEdit', 'ampEditorBlocks/filterEdit', filterBlocksEdit, 20 ); -addFilter( 'blocks.getSaveContent.extraProps', 'ampEditorBlocks/addExtraAttributes', addAMPExtraProps ); addFilter( 'editor.PostFeaturedImage', 'ampEditorBlocks/withFeaturedImageNotice', withFeaturedImageNotice ); addFilter( 'editor.MediaUpload', 'ampEditorBlocks/withMediaLibraryNotice', ( InitialMediaUpload ) => withMediaLibraryNotice( InitialMediaUpload, getMinimumFeaturedImageDimensions() ) ); diff --git a/includes/embeds/class-amp-core-block-handler.php b/includes/embeds/class-amp-core-block-handler.php index 4712c637ebc..bd4e27430d1 100644 --- a/includes/embeds/class-amp-core-block-handler.php +++ b/includes/embeds/class-amp-core-block-handler.php @@ -79,6 +79,30 @@ public function filter_rendered_block( $block_content, $block ) { if ( ! isset( $block['blockName'] ) ) { return $block_content; } + + if ( isset( $block['attrs'] ) && 'core/shortcode' !== $block['blockName'] ) { + $injected_attributes = ''; + $prop_attribute_mapping = [ + 'ampCarousel' => 'data-amp-carousel', + 'ampLayout' => 'data-amp-layout', + 'ampLightbox' => 'data-amp-lightbox', + 'ampNoLoading' => 'data-amp-noloading', + ]; + foreach ( $prop_attribute_mapping as $prop => $attr ) { + if ( isset( $block['attrs'][ $prop ] ) ) { + $property_value = $block['attrs'][ $prop ]; + if ( is_bool( $property_value ) ) { + $property_value = $property_value ? 'true' : 'false'; + } + + $injected_attributes .= sprintf( ' %s="%s"', $attr, esc_attr( $property_value ) ); + } + } + if ( $injected_attributes ) { + $block_content = preg_replace( '/(<\w+)/', '$1' . $injected_attributes, $block_content, 1 ); + } + } + if ( isset( $this->block_ampify_methods[ $block['blockName'] ] ) ) { $method_name = $this->block_ampify_methods[ $block['blockName'] ]; $block_content = $this->{$method_name}( $block_content, $block ); diff --git a/includes/embeds/class-amp-gallery-embed-handler.php b/includes/embeds/class-amp-gallery-embed-handler.php index 875ad3e793a..8ebff60c3ce 100644 --- a/includes/embeds/class-amp-gallery-embed-handler.php +++ b/includes/embeds/class-amp-gallery-embed-handler.php @@ -5,9 +5,9 @@ * @package AMP */ +use AmpProject\AmpWP\Embed\HandlesGalleryEmbed; use AmpProject\Dom\Document; -use AmpProject\AmpWP\Dom\ElementList; -use AmpProject\AmpWP\Component\Carousel; +use AmpProject\Tag; /** * Class AMP_Gallery_Embed_Handler @@ -16,253 +16,188 @@ */ class AMP_Gallery_Embed_Handler extends AMP_Base_Embed_Handler { + use HandlesGalleryEmbed; + /** * Register embed. */ public function register_embed() { - add_filter( 'post_gallery', [ $this, 'maybe_override_gallery' ], 10, 2 ); + add_filter( 'post_gallery', [ $this, 'generate_gallery_markup' ], 10, 2 ); add_action( 'wp_print_styles', [ $this, 'print_styles' ] ); } /** - * Unregister embed. + * Override the output of gallery_shortcode(). + * + * @param string $html Markup to filter. + * @param array $attrs Shortcode attributes. + * @return string Markup for the gallery. */ - public function unregister_embed() {} + public function generate_gallery_markup( $html, $attrs ) { + static $recursing = false; + if ( ! $recursing ) { + $recursing = true; + $html = $this->filter_post_gallery_markup( $html, $attrs ); + $recursing = false; + } + return $html; + } /** - * Shortcode handler. + * Filter the output of gallery_shortcode(). * - * @param array $attr Shortcode attributes. - * @return string Rendered gallery. + * @param string $html Markup to filter. + * @param array $attrs Shortcode attributes. + * @return string Markup for the gallery. */ - public function shortcode( $attr ) { - $post = get_post(); + protected function filter_post_gallery_markup( $html, $attrs ) { + // Use for the gallery if requested via amp-carousel shortcode attribute, or use by default if in legacy Reader mode. + // In AMP_Gallery_Block_Sanitizer, this is referred to as carousel_required. + $is_carousel = isset( $attrs['amp-carousel'] ) + ? rest_sanitize_boolean( $attrs['amp-carousel'] ) + : amp_is_legacy(); - if ( ! empty( $attr['ids'] ) ) { - // 'ids' is explicitly ordered, unless you specify otherwise. - if ( empty( $attr['orderby'] ) ) { - $attr['orderby'] = 'post__in'; - } - $attr['include'] = $attr['ids']; - } + $is_lightbox = isset( $attrs['amp-lightbox'] ) && rest_sanitize_boolean( $attrs['amp-lightbox'] ); - $atts = shortcode_atts( - [ - 'order' => 'ASC', - 'orderby' => 'menu_order ID', - 'id' => $post ? $post->ID : 0, - 'include' => '', - 'exclude' => '', - 'size' => [ $this->args['width'], $this->args['height'] ], - 'link' => 'none', - ], - $attr, - 'gallery' - ); - - if ( ! empty( $attr['amp-lightbox'] ) ) { - $atts['lightbox'] = filter_var( $attr['amp-lightbox'], FILTER_VALIDATE_BOOLEAN ); + if ( ! $is_carousel && ! $is_lightbox ) { + return $html; } - $id = (int) $atts['id']; - - if ( ! empty( $atts['include'] ) ) { - $attachments = get_posts( - [ - 'include' => $atts['include'], - 'post_status' => 'inherit', - 'post_type' => 'attachment', - 'post_mime_type' => 'image', - 'order' => $atts['order'], - 'orderby' => $atts['orderby'], - 'fields' => 'ids', - ] - ); - } elseif ( ! empty( $atts['exclude'] ) ) { - $attachments = get_children( - [ - 'post_parent' => $id, - 'exclude' => $atts['exclude'], - 'post_status' => 'inherit', - 'post_type' => 'attachment', - 'post_mime_type' => 'image', - 'order' => $atts['order'], - 'orderby' => $atts['orderby'], - 'fields' => 'ids', - ] - ); - } else { - $attachments = get_children( - [ - 'post_parent' => $id, - 'post_status' => 'inherit', - 'post_type' => 'attachment', - 'post_mime_type' => 'image', - 'order' => $atts['order'], - 'orderby' => $atts['orderby'], - 'fields' => 'ids', - ] - ); + if ( $is_carousel ) { + $gallery_size = isset( $attrs['size'] ) ? $attrs['size'] : null; + + if ( $gallery_size && 'thumbnail' === $gallery_size ) { + /* + * If the 'gallery' shortcode has a `size` attribute of `thumbnail`, prevent outputting an . + * That will often get thumbnail images around 150 x 150, + * while the usually has a width of 600 and a height of 480. + * That often means very low-resolution images. + * So fall back to the normal 'gallery' shortcode callback, gallery_shortcode(). + */ + return $html; + } + + if ( ! $gallery_size ) { + // Default to `large` if no `size` attribute is specified. + $attrs['size'] = 'large'; + } } - if ( empty( $attachments ) ) { - return ''; + if ( $is_lightbox ) { + // Prevent wrapping the images in anchor tags if a lightbox is specified. If that is done the link will get + // preference over the lightbox when the image is clicked. + $attrs['link'] = 'none'; } - $urls = []; - foreach ( $attachments as $attachment_id ) { - list( $url, $width, $height ) = wp_get_attachment_image_src( $attachment_id, $atts['size'], true ); + // Use `data` attributes to indicate which options are configured for the embed. These indications are later + // processed during sanitization of the embed in `::sanitize_raw_embeds`. + $filter_gallery_style = static function ( $style ) use ( $is_carousel, $is_lightbox ) { + $data_attrs = []; - if ( ! $url ) { - continue; + if ( $is_lightbox ) { + $data_attrs[] = 'data-amp-lightbox="true"'; } - $href = null; - if ( empty( $atts['lightbox'] ) && ! empty( $atts['link'] ) ) { - if ( 'file' === $atts['link'] ) { - $href = $url; - } elseif ( 'post' === $atts['link'] ) { - $href = get_attachment_link( $attachment_id ); - } + if ( $is_carousel ) { + $data_attrs[] = 'data-amp-carousel="true"'; } - $urls[] = [ - 'href' => $href, - 'url' => $url, - 'srcset' => wp_get_attachment_image_srcset( $attachment_id, $atts['size'] ), - 'width' => $width, - 'height' => $height, - 'alt' => trim( wp_strip_all_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) ), // Logic from wp_get_attachment_image(). - 'id' => $attachment_id, - ]; - } + return preg_replace( + '/(?<= $urls, - 'lightbox' => ! empty( $atts['lightbox'] ), - ]; + add_filter( 'gallery_style', $filter_gallery_style ); + $gallery_html = gallery_shortcode( $attrs ); + remove_filter( 'gallery_style', $filter_gallery_style ); - return $this->render( $args ); + return $gallery_html; } /** - * Override the output of gallery_shortcode() if amp-carousel is not false. - * - * The 'Gallery' widget also uses this function. - * This ensures that it outputs an . + * Unregister embed. + */ + public function unregister_embed() { + remove_filter( 'post_gallery', [ $this, 'generate_gallery_markup' ] ); + remove_action( 'wp_print_styles', [ $this, 'print_styles' ] ); + } + + /** + * Sanitizes gallery raw embeds to become an amp-carousel and/or amp-image-lightbox, depending on configuration options. * - * @param string $html Markup to filter, possibly ''. - * @param array $attributes Shortcode attributes. - * @return string $html Markup for the gallery. + * @param Document $dom DOM. */ - public function maybe_override_gallery( $html, $attributes ) { - $is_lightbox = isset( $attributes['amp-lightbox'] ) && true === filter_var( $attributes['amp-lightbox'], FILTER_VALIDATE_BOOLEAN ); - if ( isset( $attributes['amp-carousel'] ) && false === filter_var( $attributes['amp-carousel'], FILTER_VALIDATE_BOOLEAN ) ) { - if ( true === $is_lightbox ) { - $add_lightbox_attribute = static function ( $attr ) { - $attr['lightbox'] = ''; - return $attr; - }; - - $set_link_attribute = static function ( $attributes ) { - $attributes['link'] = 'none'; - return $attributes; - }; - - remove_filter( 'post_gallery', [ $this, 'maybe_override_gallery' ], 10 ); - add_filter( 'wp_get_attachment_image_attributes', $add_lightbox_attribute ); - add_filter( 'shortcode_atts_gallery', $set_link_attribute, PHP_INT_MAX ); - - $html = gallery_shortcode( $attributes ); - - remove_filter( 'wp_get_attachment_image_attributes', $add_lightbox_attribute ); - add_filter( 'post_gallery', [ $this, 'maybe_override_gallery' ], 10, 2 ); - remove_filter( 'shortcode_atts_gallery', $set_link_attribute, PHP_INT_MAX ); - } + public function sanitize_raw_embeds( Document $dom ) { + $nodes = $dom->xpath->query( '//div[ contains( concat( " ", normalize-space( @class ), " " ), " gallery " ) and ( @data-amp-carousel or @data-amp-lightbox ) ]' ); - return $html; - } + /** @var DOMElement $node */ + foreach ( $nodes as $node ) { + $is_carousel = $node->hasAttribute( 'data-amp-carousel' ) && rest_sanitize_boolean( $node->getAttribute( 'data-amp-carousel' ) ); + $is_lightbox = $node->hasAttribute( 'data-amp-lightbox' ) && rest_sanitize_boolean( $node->getAttribute( 'data-amp-lightbox' ) ); + $img_elements = $node->getElementsByTagName( 'img' ); - if ( isset( $attributes['size'] ) && 'thumbnail' === $attributes['size'] ) { - /* - * If the 'gallery' shortcode has a 'size' attribute of 'thumbnail', prevent outputting an . - * That will often get thumbnail images around 150 x 150, - * while the usually has a width of 600 and a height of 480. - * That often means very low-resolution images. - * So fall back to the normal 'gallery' shortcode callback, gallery_shortcode(). - */ - return ''; + $this->process_gallery_embed( $is_carousel, $is_lightbox, $node, $img_elements ); } - - return $this->shortcode( $attributes ); } /** - * Render. + * Get the caption element for the specified image element. * - * @param array $args Args. - * @return string Rendered. + * @param DOMElement $img_element Image element. + * @return DOMElement|null The caption element, otherwise `null` if it could not be found. */ - public function render( $args ) { - $dom = new Document(); - $this->did_convert_elements = true; - - $args = wp_parse_args( - $args, - [ - 'images' => false, - ] - ); - - if ( empty( $args['images'] ) ) { - return ''; + protected function get_caption_element( DOMElement $img_element ) { + $parent_element = $this->get_parent_container_for_image( $img_element ); + + if ( ! $parent_element instanceof DOMElement ) { + return null; } - $images = new ElementList(); - foreach ( $args['images'] as $props ) { - $image_atts = [ - 'src' => $props['url'], - 'width' => $props['width'], - 'height' => $props['height'], - 'layout' => 'responsive', - 'alt' => $props['alt'], - ]; - if ( ! empty( $props['srcset'] ) ) { - $image_atts['srcset'] = $props['srcset']; - } + // The caption should be next immediate element located next to the parent container of the image. + $caption_element = $parent_element->nextSibling; - if ( ! empty( $args['lightbox'] ) ) { - $image_atts['lightbox'] = ''; - } - $image = AMP_DOM_Utils::create_node( - $dom, - 'img', - $image_atts - ); + while ( + $caption_element + && ( ! $caption_element instanceof DOMElement || ! AMP_DOM_Utils::has_class( $caption_element, 'wp-caption-text' ) ) + ) { + $caption_element = $caption_element->nextSibling; + } + + if ( $caption_element instanceof DOMElement && Tag::FIGCAPTION !== $caption_element->nodeName ) { + // Transform the caption element into a `figcaption`. This not only allows the `amp-lightbox` to correctly + // detect and display the caption, but it is also semantically correct as the parent element will be a `figure`. + $figcaption_element = AMP_DOM_Utils::create_node( Document::fromNode( $caption_element ), Tag::FIGCAPTION, [] ); - if ( ! empty( $props['href'] ) ) { - $previous_image = $image; - $image = AMP_DOM_Utils::create_node( - $dom, - 'a', - [ - 'href' => $props['href'], - ] - ); - $image->appendChild( $previous_image ); + while ( $caption_element->firstChild ) { + $figcaption_element->appendChild( $caption_element->firstChild ); } - $caption = isset( $props['id'] ) ? wp_get_attachment_caption( $props['id'] ) : ''; - $images = $images->add( $image, $caption ); + $caption_element = $figcaption_element; } - $amp_carousel = new Carousel( $dom, $images ); - $carousel_node = $amp_carousel->get_dom_element(); + return $caption_element instanceof DOMElement ? $caption_element : null; + } - // Prevent an error in get_content_from_dom_node() when it calls $node->parentNode->insertBefore(). - $dom->appendChild( $carousel_node ); + /** + * Get the parent container for the specified image element. + * + * @param DOMElement $image_element Image element. + * @return DOMElement|null The parent container, otherwise `null` if it could not be found. + */ + protected function get_parent_container_for_image( DOMElement $image_element ) { + $parent_element = $image_element->parentNode; + + while ( + $parent_element + && ( ! $parent_element instanceof DOMElement || ! AMP_DOM_Utils::has_class( $parent_element, 'gallery-icon' ) ) + ) { + $parent_element = $parent_element->parentNode; + } - return $dom->saveHTML( $carousel_node ); + return $parent_element; } /** @@ -273,8 +208,6 @@ public function render( $args ) { * This rule is copied exactly from block-library/style.css, but the selector here has amp-img >. * The image sanitizer normally converts the from that original stylesheet , * but that doesn't have the same effect as applying it to the . - * - * @return void */ public function print_styles() { ?> diff --git a/includes/sanitizers/class-amp-gallery-block-sanitizer.php b/includes/sanitizers/class-amp-gallery-block-sanitizer.php index cc66bf4745d..f8ea9abac1c 100644 --- a/includes/sanitizers/class-amp-gallery-block-sanitizer.php +++ b/includes/sanitizers/class-amp-gallery-block-sanitizer.php @@ -5,8 +5,8 @@ * @package AMP */ -use AmpProject\AmpWP\Dom\ElementList; -use AmpProject\AmpWP\Component\Carousel; +use AmpProject\AmpWP\Embed\HandlesGalleryEmbed; +use AmpProject\Tag; /** * Class AMP_Gallery_Block_Sanitizer @@ -15,6 +15,8 @@ */ class AMP_Gallery_Block_Sanitizer extends AMP_Base_Sanitizer { + use HandlesGalleryEmbed; + /** * Tag. * @@ -69,104 +71,62 @@ public function sanitize() { ] ) ); - $query = $this->dom->xpath->query( $expr ); - - $nodes = []; - foreach ( $query as $node ) { - $nodes[] = $node; - } + $nodes = $this->dom->xpath->query( $expr ); foreach ( $nodes as $node ) { + /** @var DOMElement $node */ + // In WordPress 5.3, the Gallery block's