diff --git a/assets/css/src/amp-default.css b/assets/css/src/amp-default.css
index e8aae6e8712..558ffde7d84 100644
--- a/assets/css/src/amp-default.css
+++ b/assets/css/src/amp-default.css
@@ -61,3 +61,19 @@ body amp-audio:not([controls]) {
.amp-wp-default-form-message[submit-success] > p:empty {
display: none;
}
+
+amp-carousel .amp-wp-gallery-caption {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ text-align: center;
+ background-color: rgba(0, 0, 0, 0.5);
+ color: #fff;
+ padding: 1rem;
+}
+
+.wp-block-gallery[data-amp-carousel="true"] {
+ display: block;
+ flex-wrap: unset;
+}
diff --git a/includes/sanitizers/class-amp-gallery-block-sanitizer.php b/includes/sanitizers/class-amp-gallery-block-sanitizer.php
index 84a4c7f0f90..25779386fb6 100644
--- a/includes/sanitizers/class-amp-gallery-block-sanitizer.php
+++ b/includes/sanitizers/class-amp-gallery-block-sanitizer.php
@@ -73,23 +73,44 @@ class AMP_Gallery_Block_Sanitizer extends AMP_Base_Sanitizer {
* @since 0.2
*/
public function sanitize() {
- $nodes = $this->dom->getElementsByTagName( self::$tag );
- $num_nodes = $nodes->length;
- if ( 0 === $num_nodes ) {
- return;
+ $xpath = new DOMXPath( $this->dom );
+ $class_query = 'contains( concat( " ", normalize-space( @class ), " " ), " wp-block-gallery " )';
+ $expr = sprintf(
+ '//ul[ %s ]',
+ implode(
+ ' or ',
+ [
+ sprintf( '( parent::figure[ %s ] )', $class_query ),
+ $class_query,
+ ]
+ )
+ );
+ $query = $xpath->query( $expr );
+
+ $nodes = [];
+ foreach ( $query as $node ) {
+ $nodes[] = $node;
}
- for ( $i = $num_nodes - 1; $i >= 0; $i-- ) {
- $node = $nodes->item( $i );
+ foreach ( $nodes as $node ) {
+ /**
+ * Element
+ *
+ * @var DOMElement $node
+ */
- // We're looking for
elements that have at least one child and the proper class.
- if ( 0 === count( $node->childNodes ) || false === strpos( $node->getAttribute( 'class' ), self::$class ) ) {
- continue;
- }
+ // In WordPress 5.3, the Gallery block's is wrapped in a , so look for that node also.
+ $gallery_node = isset( $node->parentNode ) && AMP_DOM_Utils::has_class( $node->parentNode, self::$class ) ? $node->parentNode : $node;
+ $attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $gallery_node );
- $attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node );
$is_amp_lightbox = isset( $attributes['data-amp-lightbox'] ) && true === filter_var( $attributes['data-amp-lightbox'], FILTER_VALIDATE_BOOLEAN );
- $is_amp_carousel = ! empty( $this->args['carousel_required'] ) || ( isset( $attributes['data-amp-carousel'] ) && true === filter_var( $attributes['data-amp-carousel'], FILTER_VALIDATE_BOOLEAN ) );
+ $is_amp_carousel = (
+ ! empty( $this->args['carousel_required'] )
+ ||
+ filter_var( $node->getAttribute( 'data-amp-carousel' ), FILTER_VALIDATE_BOOLEAN )
+ ||
+ filter_var( $node->parentNode->getAttribute( 'data-amp-carousel' ), FILTER_VALIDATE_BOOLEAN )
+ );
// We are only looking for elements which have amp-carousel / amp-lightbox true.
if ( ! $is_amp_carousel && ! $is_amp_lightbox ) {
@@ -140,11 +161,46 @@ public function sanitize() {
'layout' => 'responsive',
]
);
+
foreach ( $images as $image ) {
- $amp_carousel->appendChild( $image );
+ $slide = AMP_DOM_Utils::create_node(
+ $this->dom,
+ 'div',
+ [ 'class' => 'slide' ]
+ );
+
+ // Ensure the image fills the entire , so the possible caption looks right.
+ if ( 'amp-img' === $image->tagName ) {
+ $image->setAttribute( 'layout', 'fill' );
+ $image->setAttribute( 'object-fit', 'cover' );
+ } elseif ( isset( $image->firstChild->tagName ) && 'amp-img' === $image->firstChild->tagName ) {
+ // If the is wrapped in an .
+ $image->firstChild->setAttribute( 'layout', 'fill' );
+ $image->firstChild->setAttribute( 'object-fit', 'cover' );
+ }
+
+ $possible_caption_text = $this->possibly_get_caption_text( $image );
+ $slide->appendChild( $image );
+
+ // Wrap the caption in a and
, and append it to the slide.
+ if ( $possible_caption_text ) {
+ $caption_wrapper = AMP_DOM_Utils::create_node(
+ $this->dom,
+ 'div',
+ [ 'class' => 'amp-wp-gallery-caption' ]
+ );
+ $caption_span = AMP_DOM_Utils::create_node( $this->dom, 'span', [] );
+ $text_node = $this->dom->createTextNode( $possible_caption_text );
+
+ $caption_span->appendChild( $text_node );
+ $caption_wrapper->appendChild( $caption_span );
+ $slide->appendChild( $caption_wrapper );
+ }
+
+ $amp_carousel->appendChild( $slide );
}
- $node->parentNode->replaceChild( $amp_carousel, $node );
+ $gallery_node->parentNode->replaceChild( $amp_carousel, $gallery_node );
}
$this->did_convert_elements = true;
}
@@ -219,4 +275,24 @@ protected function add_lightbox_attributes_to_image_nodes( $element ) {
}
}
}
+
+ /**
+ * Gets the caption of an image, if it exists.
+ *
+ * @param DOMElement $element The element for which to search for a caption.
+ * @return string|null The caption for the image, or null.
+ */
+ public function possibly_get_caption_text( $element ) {
+ $caption_tag = 'figcaption';
+ if ( isset( $element->nextSibling->nodeName ) && $caption_tag === $element->nextSibling->nodeName ) {
+ return $element->nextSibling->textContent;
+ }
+
+ // If 'Link To' is selected, the image will be wrapped in an , so search for the sibling of the .
+ if ( isset( $element->parentNode->nextSibling->nodeName ) && $caption_tag === $element->parentNode->nextSibling->nodeName ) {
+ return $element->parentNode->nextSibling->textContent;
+ }
+
+ return null;
+ }
}
diff --git a/tests/php/test-class-amp-gallery-block-sanitizer.php b/tests/php/test-class-amp-gallery-block-sanitizer.php
index b1542fbceec..b2b8cfabfa4 100644
--- a/tests/php/test-class-amp-gallery-block-sanitizer.php
+++ b/tests/php/test-class-amp-gallery-block-sanitizer.php
@@ -37,19 +37,50 @@ public function get_data() {
'',
],
- 'data_amp_with_carousel' => [
+ 'data_amp_with_carousel_and_link' => [
'',
- ' ',
+ ' ',
+ ],
+
+ 'data_amp_with_carousel_and_caption' => [
+ '',
+ ' ',
+ ],
+
+ // WordPress 5.3 changed the markup for the Gallery block, wrapping it in a .
+ 'data_amp_with_carousel_caption_5_3' => [
+ ' ',
+ ' ',
],
'data_amp_with_lightbox' => [
+ '',
+ ' ',
+ ],
+
+ 'data_amp_with_lightbox_and_link' => [
'',
' ',
],
+ 'data_amp_with_lightbox_5_3' => [
+ ' ',
+ ' ',
+ ],
+
+ 'data_amp_with_lightbox_and_link_5_3' => [
+ ' ',
+ ' ',
+ ],
+
'data_amp_with_lightbox_and_carousel' => [
'',
- ' ',
+ ' ',
+ ],
+
+ 'data_amp_with_lightbox_carousel_5_3' => [
+ ' ',
+ ' ',
],
];
}
@@ -91,14 +122,24 @@ public function get_reader_mode_data() {
'',
],
+ 'data_amp_with_carousel_and_caption' => [
+ '',
+ ' ',
+ ],
+
'data_amp_with_lightbox' => [
+ '',
+ ' ',
+ ],
+
+ 'data_amp_with_lightbox_and_link' => [
'',
- ' ',
+ ' ',
],
- 'data_amp_with_lightbox_and_carousel' => [
+ 'data_amp_lightbox_carousel_and_link' => [
'',
- ' ',
+ ' ',
],
];
}
@@ -125,4 +166,60 @@ public function test_sanitizer_reader_mode( $source, $expected ) {
$content = preg_replace( '/(?<=>)\s+(?=<)/', '', $content );
$this->assertEquals( $expected, $content );
}
+
+ /**
+ * Gets the data for test_possibly_get_caption_text().
+ *
+ * @return array[] The source to use, the expected return value, and the tag type to pass as an argument.
+ */
+ public function get_caption_text_data() {
+ return [
+ 'no_amp_img_or_anchor' => [
+ '',
+ null,
+ 'img',
+ ],
+ 'amp_img_with_empty_caption' => [
+ ' ',
+ null,
+ 'amp-img',
+ ],
+ 'amp_img_with_caption' => [
+ 'This is a caption ',
+ 'This is a caption',
+ 'amp-img',
+ ],
+ 'amp_img_wrapped_in_anchor_with_caption_in_div' => [
+ 'This is a caption
',
+ null,
+ 'a',
+ ],
+ 'amp_img_wrapped_in_anchor_with_caption_in_figcaption' => [
+ 'This is a caption ',
+ 'This is a caption',
+ 'a',
+ ],
+ ];
+ }
+
+ /**
+ * Test possibly_get_caption_text.
+ *
+ * @covers \AMP_Gallery_Block_Sanitizer::possibly_get_caption_text()
+ *
+ * @dataProvider get_caption_text_data
+ * @param string $source The markup source to test.
+ * @param string|null $expected The expected return value of the tested method.
+ * @param string $element_name The name of the element to pass to the tested method.
+ */
+ public function test_possibly_get_caption_text( $source, $expected, $element_name ) {
+ $dom = AMP_DOM_Utils::get_dom_from_content( $source );
+ $element = $dom->getElementsByTagName( $element_name )->item( 0 );
+ $sanitizer = new AMP_Gallery_Block_Sanitizer(
+ $dom,
+ [ 'content_max_width' => 600 ]
+ );
+
+ $this->assertEquals( $expected, $sanitizer->possibly_get_caption_text( $element ) );
+ }
}