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
9 changes: 5 additions & 4 deletions src/wp-includes/media.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?php

Check warning on line 1 in src/wp-includes/media.php

View workflow job for this annotation

GitHub Actions / Check PHP compatibility / Run compatibility checks

File has mixed line endings; this may cause incorrect results
/**
* WordPress API for media display.
*
Expand Down Expand Up @@ -2599,8 +2599,8 @@
$describedby = '';

if ( $atts['id'] ) {
Comment thread
westonruter marked this conversation as resolved.
$atts['id'] = sanitize_html_class( $atts['id'] );
$id = 'id="' . esc_attr( $atts['id'] ) . '" ';
$unique_id_value = preg_replace( '/-1$/', '', wp_unique_prefixed_id( sanitize_html_class( $atts['id'] . '-' ) ) );
$id = 'id="' . esc_attr( $unique_id_value ) . '" ';
}

if ( $atts['caption_id'] ) {
Expand All @@ -2610,8 +2610,9 @@
}

if ( $atts['caption_id'] ) {
$caption_id = 'id="' . esc_attr( $atts['caption_id'] ) . '" ';
$describedby = 'aria-describedby="' . esc_attr( $atts['caption_id'] ) . '" ';
$caption_id_value = preg_replace( '/-1$/', '', wp_unique_id( $atts['caption_id'] . '-' ) );

Check failure on line 2613 in src/wp-includes/media.php

View workflow job for this annotation

GitHub Actions / Coding standards / PHP checks

Whitespace found at end of line
$caption_id = 'id="' . esc_attr( $caption_id_value ) . '" ';
$describedby = 'aria-describedby="' . esc_attr( $caption_id_value ) . '" ';
}

$class = trim( 'wp-caption ' . $atts['align'] . ' ' . $atts['class'] );
Expand Down
92 changes: 90 additions & 2 deletions tests/phpunit/tests/media.php
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ public function test_img_caption_shortcode_with_old_format_id_and_align() {
)
);
$this->assertSame( 1, substr_count( $result, 'wp-caption &amp;myAlignment' ) );
$this->assertSame( 1, substr_count( $result, 'id="myId"' ) );
$this->assertSame( 1, preg_match( '/id="myId[0-9]+"/', $result ) );
$this->assertSame( 1, substr_count( $result, self::CAPTION ) );
}

Expand Down Expand Up @@ -302,7 +302,95 @@ public function test_img_caption_shortcode_has_aria_describedby() {
self::IMG_CONTENT . self::HTML_CONTENT
);

$this->assertSame( 1, substr_count( $result, 'aria-describedby="caption-myId"' ) );
$this->assertSame( 1, preg_match( '/aria-describedby="caption-myId[0-9]+"/', $result ) );
}

/**
* Tests that both figure and figcaption IDs are unique for multiple caption instances.
*
* When the same image with the same or different captions appears multiple
* times on a page, each figure and figcaption should receive a unique ID to
* maintain HTML validity and accessibility.
*
* @ticket 65315
*/
public function test_img_caption_shortcode_unique_ids_per_instance(): void {
// First instance with caption "My caption"
$result_1 = img_caption_shortcode(
array(
'width' => 20,
'id' => 'attachment_123',
'caption' => 'My caption',
),
self::IMG_CONTENT . 'My caption'
);

// Second instance - identical to first
$result_2 = img_caption_shortcode(
array(
'width' => 20,
'id' => 'attachment_123',
'caption' => 'My caption',
),
self::IMG_CONTENT . 'My caption'
);

// Third instance - same image, different caption
$result_3 = img_caption_shortcode(
array(
'width' => 20,
'id' => 'attachment_123',
'caption' => 'Different caption',
),
self::IMG_CONTENT . 'Different caption'
);

// Extract the figure (caption wrapper) and caption text IDs from each instance.
$figure_id_1 = $this->get_id_of_first_tag_with_class( $result_1, 'wp-caption' );
$figure_id_2 = $this->get_id_of_first_tag_with_class( $result_2, 'wp-caption' );
$figure_id_3 = $this->get_id_of_first_tag_with_class( $result_3, 'wp-caption' );
$caption_id_1 = $this->get_id_of_first_tag_with_class( $result_1, 'wp-caption-text' );
$caption_id_2 = $this->get_id_of_first_tag_with_class( $result_2, 'wp-caption-text' );
$caption_id_3 = $this->get_id_of_first_tag_with_class( $result_3, 'wp-caption-text' );

// Figure IDs should all exist
$this->assertNotEmpty( $figure_id_1, 'First figure should have an ID' );
$this->assertNotEmpty( $figure_id_2, 'Second figure should have an ID' );
$this->assertNotEmpty( $figure_id_3, 'Third figure should have an ID' );

// Figure IDs should all be different (each instance gets unique ID)
$this->assertNotSame( $figure_id_1, $figure_id_2, 'First and second figures should have different IDs even with identical content' );
$this->assertNotSame( $figure_id_2, $figure_id_3, 'Second and third figures should have different IDs' );
$this->assertNotSame( $figure_id_1, $figure_id_3, 'First and third figures should have different IDs' );

// Caption IDs should all exist
$this->assertNotEmpty( $caption_id_1, 'First caption should have an ID' );
$this->assertNotEmpty( $caption_id_2, 'Second caption should have an ID' );
$this->assertNotEmpty( $caption_id_3, 'Third caption should have an ID' );

// Caption IDs should all be different (each instance gets unique ID)
$this->assertNotSame( $caption_id_1, $caption_id_2, 'First and second captions should have different IDs even with identical content' );
$this->assertNotSame( $caption_id_2, $caption_id_3, 'Second and third captions should have different IDs' );
$this->assertNotSame( $caption_id_1, $caption_id_3, 'First and third captions should have different IDs' );
}

/**
* Returns the `id` attribute of the first tag bearing the given class name.
*
* @param string $html Markup to search.
* @param string $class_name Class name to locate the tag by.
* @return string|null The tag's `id` value, or null if no matching tag or `id` is found.
*/
private function get_id_of_first_tag_with_class( string $html, string $class_name ): ?string {
$processor = new WP_HTML_Tag_Processor( $html );

if ( ! $processor->next_tag( array( 'class_name' => $class_name ) ) ) {
return null;
}

$id = $processor->get_attribute( 'id' );

return is_string( $id ) ? $id : null;
}

public function test_add_remove_oembed_provider() {
Expand Down
Loading