Skip to content

Commit cb186b3

Browse files
committed
Media: Test for DOMDocument before checking embedded alt text.
`DOMDocument` is not a required library for WordPress, and all other uses have validation via `class_exists`. Add a check to verify the library is available before attempting to use. Props smerriman, therssoftware, siliconforks, jorbin, joedolson. Fixes #66221. git-svn-id: https://develop.svn.wordpress.org/trunk@64089 602fd350-edb4-49c9-b593-d223f7449a82
1 parent e0fa2c9 commit cb186b3

2 files changed

Lines changed: 209 additions & 2 deletions

File tree

‎src/wp-admin/includes/image.php‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,10 +1083,15 @@ function wp_read_image_metadata( $file ) {
10831083
* @since 7.0.0
10841084
*
10851085
* @param string $file File path to the image.
1086-
* @return string Embedded alternative text.
1086+
* @return string Embedded alternative text, empty when there is no alt text or DOM extension is not installed.
10871087
*/
10881088
function wp_get_image_alttext( $file ) {
1089-
$alt_text = '';
1089+
$alt_text = '';
1090+
1091+
if ( ! class_exists( 'DOMDocument', false ) ) {
1092+
return $alt_text;
1093+
}
1094+
10901095
$img_contents = file_get_contents( $file );
10911096

10921097
if ( false === $img_contents ) {
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
<?php
2+
3+
/**
4+
* Tests for wp_get_image_alttext().
5+
*
6+
* @group image
7+
* @group media
8+
* @ticket 66221
9+
*
10+
* @covers ::wp_get_image_alttext
11+
*/
12+
class Tests_Image_Alttext extends WP_UnitTestCase {
13+
14+
/**
15+
* Temporary files to delete in tear_down().
16+
*
17+
* @var string[]
18+
*/
19+
private $temp_files = array();
20+
21+
public function tear_down() {
22+
restore_current_locale();
23+
24+
foreach ( $this->temp_files as $file ) {
25+
if ( file_exists( $file ) ) {
26+
unlink( $file );
27+
}
28+
}
29+
$this->temp_files = array();
30+
31+
parent::tear_down();
32+
}
33+
34+
/**
35+
* Creates a temporary file containing XMP metadata with the given alt text entries.
36+
*
37+
* @param array<string, string> $alt_entries Associative array where key is xml:lang and value is the alt text.
38+
* @return string File path to the temporary file.
39+
*/
40+
private function create_temp_xmp_file( array $alt_entries ) {
41+
$li_tags = '';
42+
foreach ( $alt_entries as $lang => $text ) {
43+
$li_tags .= "\t\t\t\t" . '<rdf:li xml:lang="' . esc_attr( $lang ) . '">' . htmlspecialchars( $text, ENT_XML1, 'UTF-8' ) . "</rdf:li>\n";
44+
}
45+
46+
$content = "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\">\n"
47+
. "\t<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:Iptc4xmpCore=\"http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/\">\n"
48+
. "\t\t<rdf:Description>\n"
49+
. "\t\t\t<Iptc4xmpCore:AltTextAccessibility>\n"
50+
. "\t\t\t\t<rdf:Alt>\n"
51+
. $li_tags
52+
. "\t\t\t\t</rdf:Alt>\n"
53+
. "\t\t\t</Iptc4xmpCore:AltTextAccessibility>\n"
54+
. "\t\t</rdf:Description>\n"
55+
. "\t</rdf:RDF>\n"
56+
. '</x:xmpmeta>';
57+
58+
$file = wp_tempnam( 'alttext-xmp' );
59+
file_put_contents( $file, $content );
60+
$this->temp_files[] = $file;
61+
62+
return $file;
63+
}
64+
65+
/**
66+
* Asserts that the alt text matches the expected value when the DOM extension is available,
67+
* or is empty when the DOM extension is not installed.
68+
*
69+
* @param string $expected Expected alt text when DOM is available.
70+
* @param string $actual Actual alt text returned by wp_get_image_alttext().
71+
* @param string $message Optional failure message.
72+
*/
73+
private function assert_alttext_matches_or_empty_without_dom( $expected, $actual, $message = '' ) {
74+
if ( ! class_exists( 'DOMDocument', false ) ) {
75+
$this->assertSame( '', $actual, 'Expected empty string when DOM extension is not available.' );
76+
return;
77+
}
78+
79+
$this->assertSame( $expected, $actual, $message );
80+
}
81+
82+
/**
83+
* Tests reading alt text with a valid image containing XMP metadata.
84+
*/
85+
public function test_wp_get_image_alttext_with_valid_image() {
86+
$alt = wp_get_image_alttext( DIR_TESTDATA . '/images/IPTC-PhotometadataRef-Std2025.1.jpg' );
87+
88+
$this->assert_alttext_matches_or_empty_without_dom(
89+
'This is the Alt Text description to support accessibility in 2025.1',
90+
$alt
91+
);
92+
}
93+
94+
/**
95+
* Tests wp_get_image_alttext() returns empty string when image has no XMP metadata.
96+
*/
97+
public function test_wp_get_image_alttext_returns_empty_when_no_xmp_metadata() {
98+
$alt = wp_get_image_alttext( DIR_TESTDATA . '/images/test-image-upside-down.jpg' );
99+
100+
$this->assertSame( '', $alt );
101+
}
102+
103+
/**
104+
* Tests wp_get_image_alttext() returns empty string when XMP has no AltTextAccessibility node.
105+
*/
106+
public function test_wp_get_image_alttext_returns_empty_when_no_alttext_node() {
107+
$file = wp_tempnam( 'alttext-no-node' );
108+
$content = '<x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><rdf:Description rdf:about="" /></rdf:RDF></x:xmpmeta>';
109+
file_put_contents( $file, $content );
110+
$this->temp_files[] = $file;
111+
112+
$alt = wp_get_image_alttext( $file );
113+
114+
$this->assertSame( '', $alt );
115+
}
116+
117+
/**
118+
* Tests possibility 1: exact match on site locale.
119+
*/
120+
public function test_wp_get_image_alttext_exact_locale_match() {
121+
switch_to_locale( 'de_DE' );
122+
123+
$file = $this->create_temp_xmp_file(
124+
array(
125+
'de_DE' => 'Deutscher Alt-Text (exakt)',
126+
'de' => 'Deutscher Alt-Text (teilweise)',
127+
'x-default' => 'Default Alt Text',
128+
)
129+
);
130+
131+
$alt = wp_get_image_alttext( $file );
132+
133+
$this->assert_alttext_matches_or_empty_without_dom( 'Deutscher Alt-Text (exakt)', $alt );
134+
}
135+
136+
/**
137+
* Tests possibility 2: partial match on site locale (e.g. locale is es_ES and rdf:li has @xml:lang="es").
138+
*/
139+
public function test_wp_get_image_alttext_partial_locale_match() {
140+
switch_to_locale( 'es_ES' );
141+
142+
$file = $this->create_temp_xmp_file(
143+
array(
144+
'es' => 'Texto alternativo en espanol (parcial)',
145+
'x-default' => 'Default Alt Text',
146+
)
147+
);
148+
149+
$alt = wp_get_image_alttext( $file );
150+
151+
$this->assert_alttext_matches_or_empty_without_dom( 'Texto alternativo en espanol (parcial)', $alt );
152+
}
153+
154+
/**
155+
* Tests possibility 3: fallback to x-default when neither exact nor partial locale matches.
156+
*/
157+
public function test_wp_get_image_alttext_x_default_fallback() {
158+
switch_to_locale( 'ja_JP' );
159+
160+
$file = $this->create_temp_xmp_file(
161+
array(
162+
'de_DE' => 'Deutscher Alt-Text',
163+
'x-default' => 'Default Alt Text Fallback',
164+
)
165+
);
166+
167+
$alt = wp_get_image_alttext( $file );
168+
169+
$this->assert_alttext_matches_or_empty_without_dom( 'Default Alt Text Fallback', $alt );
170+
}
171+
172+
/**
173+
* Tests that wp_get_image_alttext() returns different alt text based on the site locale.
174+
*/
175+
public function test_wp_get_image_alttext_returns_different_alt_text_based_on_site_locale() {
176+
$file = $this->create_temp_xmp_file(
177+
array(
178+
'en_US' => 'English (US) description',
179+
'en_GB' => 'English (UK) description',
180+
'es_ES' => 'Descripcion en espanol',
181+
'de_DE' => 'Deutsche Beschreibung',
182+
'x-default' => 'Default description',
183+
)
184+
);
185+
186+
switch_to_locale( 'en_US' );
187+
$this->assert_alttext_matches_or_empty_without_dom( 'English (US) description', wp_get_image_alttext( $file ) );
188+
189+
switch_to_locale( 'en_GB' );
190+
$this->assert_alttext_matches_or_empty_without_dom( 'English (UK) description', wp_get_image_alttext( $file ) );
191+
192+
switch_to_locale( 'es_ES' );
193+
$this->assert_alttext_matches_or_empty_without_dom( 'Descripcion en espanol', wp_get_image_alttext( $file ) );
194+
195+
switch_to_locale( 'de_DE' );
196+
$this->assert_alttext_matches_or_empty_without_dom( 'Deutsche Beschreibung', wp_get_image_alttext( $file ) );
197+
198+
// Unmatched locale should fall back to x-default.
199+
switch_to_locale( 'ja_JP' );
200+
$this->assert_alttext_matches_or_empty_without_dom( 'Default description', wp_get_image_alttext( $file ) );
201+
}
202+
}

0 commit comments

Comments
 (0)