Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 117 additions & 81 deletions src/wp-includes/media.php
Original file line number Diff line number Diff line change
Expand Up @@ -5248,15 +5248,16 @@ function get_media_embedded_in_content( $content, $types = null ) {
*
* @since 3.6.0
*
* @param int|WP_Post $post Post ID or object.
* @param bool $html Optional. Whether to return HTML or data in the array. Default true.
* @return array A list of arrays, each containing gallery data and srcs parsed
* from the expanded shortcode.
* @param int|WP_Post $post Post ID or object.
* @param bool|null $html Optional. Whether to return HTML or data in the array. Default true.
* @param int|null $max_galleries Optional. Only collect up to this many galleries. Default unlimited.
* @return array{ids?: string, src: string[]} A list of arrays, each containing gallery data and 'src'
* attributes parsed from the expanded shortcode.
*/
function get_post_galleries( $post, $html = true ) {
function get_post_galleries( $post, $html = true, $max_galleries = PHP_INT_MAX ): array {
$post = get_post( $post );

if ( ! $post ) {
if ( ! ( $post instanceof WP_Post ) ) {
return array();
}

Expand All @@ -5267,8 +5268,12 @@ function get_post_galleries( $post, $html = true ) {
$galleries = array();
if ( preg_match_all( '/' . get_shortcode_regex() . '/s', $post->post_content, $matches, PREG_SET_ORDER ) ) {
foreach ( $matches as $shortcode ) {
if ( count( $galleries ) >= $max_galleries ) {
return $galleries;
}

if ( 'gallery' === $shortcode[2] ) {
$srcs = array();
$sources = array();

$shortcode_attrs = shortcode_parse_atts( $shortcode[3] );

Expand All @@ -5284,120 +5289,151 @@ function get_post_galleries( $post, $html = true ) {
preg_match_all( '#src=([\'"])(.+?)\1#is', $gallery, $src, PREG_SET_ORDER );
if ( ! empty( $src ) ) {
foreach ( $src as $s ) {
$srcs[] = $s[2];
$sources[] = $s[2];
}
}

$galleries[] = array_merge(
$shortcode_attrs,
array(
'src' => array_values( array_unique( $srcs ) ),
'src' => array_values( array_unique( $sources ) ),
)
);
}
}
}
}

if ( has_block( 'gallery', $post->post_content ) ) {
$post_blocks = parse_blocks( $post->post_content );

while ( $block = array_shift( $post_blocks ) ) {
$has_inner_blocks = ! empty( $block['innerBlocks'] );

// Skip blocks with no blockName and no innerHTML.
if ( ! $block['blockName'] ) {
continue;
}
if ( count( $galleries ) > $max_galleries ) {
return array_slice( $galleries, 0, $max_galleries );
}

// Skip non-Gallery blocks.
if ( 'core/gallery' !== $block['blockName'] ) {
// Move inner blocks into the root array before skipping.
if ( $has_inner_blocks ) {
array_push( $post_blocks, ...$block['innerBlocks'] );
}
continue;
$processor = new WP_Block_Processor( $post->post_content );
while ( count( $galleries ) < $max_galleries && $processor->next_block( 'gallery' ) ) {
/*
* It’s not clear yet whether this will have inner blocks.
* Until then, start computing for both paths, then bail
* once the block vintage is known.
*/
$gallery_depth = $processor->get_depth();
$has_inner_blocks = false;
$gallery_ids = $processor->allocate_and_return_parsed_attributes()['ids'] ?? array();
$ids = array();
$sources = array();
$inner_html = '';

while ( $processor->next_token() && $processor->get_depth() > $gallery_depth ) {
if ( ! $processor->is_html() ) {
$has_inner_blocks = true;
break;
}

// New Gallery block format as HTML.
if ( $has_inner_blocks && $html ) {
$block_html = wp_list_pluck( $block['innerBlocks'], 'innerHTML' );
$galleries[] = '<figure>' . implode( ' ', $block_html ) . '</figure>';
continue;
if ( $processor->get_depth() === $gallery_depth + 1 ) {
$inner_html .= $processor->get_html_content();
}
}

$srcs = array();
// New Gallery block format, returning HTML.
if ( $has_inner_blocks && $html ) {
// Reset this, because the new format stores everything inside the inner blocks.
$nested_inner_html = '';

// New Gallery block format as an array.
if ( $has_inner_blocks ) {
$attrs = wp_list_pluck( $block['innerBlocks'], 'attrs' );
$ids = wp_list_pluck( $attrs, 'id' );
// Get the rest of the innerHTML of the Gallery’s direct children.
while ( $processor->next_token() && $processor->get_depth() > $gallery_depth ) {
if ( $processor->is_html() && $processor->get_depth() === $gallery_depth + 2 ) {
$nested_inner_html .= $processor->get_html_content();
}
}

foreach ( $ids as $id ) {
$url = wp_get_attachment_url( $id );
$galleries[] = "<figure>{$nested_inner_html}</figure>";
continue;
}

if ( is_string( $url ) && ! in_array( $url, $srcs, true ) ) {
$srcs[] = $url;
// New Gallery block format, returning an array.
if ( $has_inner_blocks ) {
/**
* There are inner blocks and this is the first one;
* the loop above aborted at its opening, so initially the processor
* is paused on the opening delimiter of the first inner block.
*
* @todo Could avoid computation by tracking seen ids and only looking up the
* attachment url if the id hasn’t already been resolved.
*/
do {
// Examine only the direct children of the gallery block.
if ( $processor->get_depth() === $gallery_depth + 1 ) {
/** @todo Perfect use-case for lazy parsing here — only the `id` is wanted. */
$id = $processor->allocate_and_return_parsed_attributes()['id'] ?? null;
if ( isset( $id ) ) {
$ids[] = $id;
$url = wp_get_attachment_url( $id );

if ( is_string( $url ) && ! in_array( $url, $sources, true ) ) {
$sources[] = $url;
}
}
}
} while ( $processor->next_block() && $processor->get_depth() > $gallery_depth );

$galleries[] = array(
'ids' => implode( ',', $ids ),
'src' => $srcs,
);
$galleries[] = array(
'ids' => implode( ',', $ids ),
'src' => $sources,
);

continue;
}
continue;
}

// Old Gallery block format as HTML.
if ( $html ) {
$galleries[] = $block['innerHTML'];
continue;
}
// Old Gallery block format, returning HTML.
if ( $html ) {
$galleries[] = $inner_html;
continue;
}

// Old Gallery block format as an array.
$ids = ! empty( $block['attrs']['ids'] ) ? $block['attrs']['ids'] : array();
// Old Gallery block format, returning an array.

// If present, use the image IDs from the JSON blob as canonical.
if ( ! empty( $ids ) ) {
foreach ( $ids as $id ) {
$url = wp_get_attachment_url( $id );
/*
* If present, use the image IDs from the JSON blob as canonical.
* This is sourced above when the Block Processor first reaches
* the outer `gallery` block and saved for now; this is because
* once the Processor moves on to the inner text or inner blocks,
* the attributes are no longer accessible.
*/
if ( ! empty( $gallery_ids ) ) {
foreach ( $gallery_ids as $id ) {
$url = wp_get_attachment_url( $id );

if ( is_string( $url ) && ! in_array( $url, $srcs, true ) ) {
$srcs[] = $url;
}
if ( is_string( $url ) && ! in_array( $url, $sources, true ) ) {
$sources[] = $url;
}

$galleries[] = array(
'ids' => implode( ',', $ids ),
'src' => $srcs,
);

continue;
}

// Otherwise, extract srcs from the innerHTML.
preg_match_all( '#src=([\'"])(.+?)\1#is', $block['innerHTML'], $found_srcs, PREG_SET_ORDER );
$galleries[] = array(
'ids' => implode( ',', $gallery_ids ),
'src' => $sources,
);

if ( ! empty( $found_srcs[0] ) ) {
foreach ( $found_srcs as $src ) {
if ( isset( $src[2] ) && ! in_array( $src[2], $srcs, true ) ) {
$srcs[] = $src[2];
}
}
}
continue;
}

$galleries[] = array( 'src' => $srcs );
// Otherwise, extract srcs from the innerHTML.
$src_finder = new WP_HTML_Tag_Processor( $inner_html );
while ( $src_finder->next_tag() ) {
$src = $src_finder->get_attribute( 'src' );
if ( is_string( $src ) && ! in_array( $src, $sources, true ) ) {
$sources[] = $src;
}
}

$galleries[] = array( 'src' => $sources );
}

/**
* Filters the list of all found galleries in the given post.
*
* @since 3.6.0
*
* @param array $galleries Associative array of all found post galleries.
* @param WP_Post $post Post object.
* @param array{ids?: string, src: string[]} $galleries Associative array of all found post galleries.
* @param WP_Post $post Post object.
*/
return apply_filters( 'get_post_galleries', $galleries, $post );
}
Expand All @@ -5412,8 +5448,8 @@ function get_post_galleries( $post, $html = true ) {
* @return string|array Gallery data and srcs parsed from the expanded shortcode.
*/
function get_post_gallery( $post = 0, $html = true ) {
$galleries = get_post_galleries( $post, $html );
$gallery = reset( $galleries );
$galleries = get_post_galleries( $post, $html, 1 );
$gallery = $galleries[0] ?? false;

/**
* Filters the first-found post gallery.
Expand Down
36 changes: 36 additions & 0 deletions tests/phpunit/tests/media/getPostGalleries.php
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,42 @@ public function test_returns_no_srcs_with_block_in_post_with_no_attached_images(
);
}

/**
* Ensures that the function only returns up to the requested count of galleries.
*
* @dataProvider data_unique_gallery_type_content
*
* @param $gallery
*/
public function tests_returns_requested_max_number_of_galleries( $gallery ) {
$post_id = self::factory()->post->create(
array( 'post_content' => str_repeat( "{$gallery}\n", 15 ) )
);

// Test negative counts, the zero count, and a max count above the total contained galleries.
foreach ( range( -5, 25 ) as $max_count ) {
$this->assertCount(
max( 0, min( 15, $max_count ) ),
get_post_galleries( $post_id, false, $max_count ),
'Failed to fetch up to the max requested number of galleries.'
);
}
}

/**
* Data provider.
*
* @return array[]
*/
public static function data_unique_gallery_type_content() {
return array(
'Shortcode with ids' => array( '[gallery ids="11,12,13"]' ),
'Shortcode without ids' => array( '[gallery]<figure><img src="image-15.jpg" data-id="15"></figure>[/gallery]' ),
'Block with ids' => array( '<!-- wp:gallery {"ids":[14, 15, 16]} /-->' ),
'Block with inner blocks' => array( '<!-- wp:gallery --><!-- wp:image {"id":11} --><img src="image-11.jpg" data-id="11"><!-- /wp:image --><!-- /wp:gallery -->' ),
);
}

/**
* Tests that no srcs are returned for a gallery block v2
* in a post with no attached images.
Expand Down
Loading