Skip to content

Commit d34ca53

Browse files
committed
1 parent a58028d commit d34ca53

2 files changed

Lines changed: 78 additions & 1 deletion

File tree

src/wp-admin/includes/image.php

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -813,7 +813,7 @@ function wp_exif_date2ts( $str ) {
813813
* created_timestamp, focal_length, shutter_speed, and title.
814814
*
815815
* The IPTC metadata that is retrieved is APP13, credit, byline, created date
816-
* and time, caption, copyright, and title. Also includes FNumber, Model,
816+
* and time, caption, copyright, alt, and title. Also includes FNumber, Model,
817817
* DateTimeDigitized, FocalLength, ISOSpeedRatings, and ExposureTime.
818818
*
819819
* @todo Try other exif libraries if available.
@@ -854,6 +854,7 @@ function wp_read_image_metadata( $file ) {
854854
'title' => '',
855855
'orientation' => 0,
856856
'keywords' => array(),
857+
'alt' => '',
857858
);
858859

859860
$iptc = array();
@@ -926,6 +927,8 @@ function wp_read_image_metadata( $file ) {
926927
}
927928
}
928929

930+
$meta['alt'] = wp_get_image_alttext( $file );
931+
929932
$exif = array();
930933

931934
/**
@@ -1074,6 +1077,71 @@ function wp_read_image_metadata( $file ) {
10741077
return apply_filters( 'wp_read_image_metadata', $meta, $file, $image_type, $iptc, $exif );
10751078
}
10761079

1080+
/**
1081+
* Get the alt text from image meta data.
1082+
*
1083+
* @param string $file File path to the image.
1084+
*
1085+
* @return string Embedded alternative text.
1086+
*/
1087+
function wp_get_image_alttext( $file ) {
1088+
1089+
$img_contents = file_get_contents( $file );
1090+
// Find the start and end positions of the XMP metadata.
1091+
$xmp_start = strpos( $img_contents, '<x:xmpmeta' );
1092+
$xmp_end = strpos( $img_contents, '</x:xmpmeta>');
1093+
1094+
if ( ! $xmp_start || ! $xmp_end ) {
1095+
// No XMP metadata found.
1096+
return '';
1097+
}
1098+
1099+
// Extract the XMP metadata from the JPEG contents
1100+
$xmp_data = substr( $img_contents, $xmp_start, $xmp_end - $xmp_start + 12 );
1101+
1102+
// Parse the XMP metadata using DOMDocument.
1103+
$doc = new DOMDocument();
1104+
if ( false === $doc->loadXML( $xmp_data ) ) {
1105+
// Invalid XML in metadata.
1106+
return '';
1107+
}
1108+
1109+
// Instantiate an XPath object, used to extract portions of the XMP.
1110+
$xpath = new DOMXPath( $doc );
1111+
1112+
// Register the relevant XML namespaces.
1113+
$xpath->registerNamespace( 'x', 'adobe:ns:meta/' );
1114+
$xpath->registerNamespace( 'rdf', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' );
1115+
$xpath->registerNamespace( 'Iptc4xmpCore', 'http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/' );
1116+
1117+
$node_list = $xpath->query( '/x:xmpmeta/rdf:RDF/rdf:Description/Iptc4xmpCore:AltTextAccessibility' );
1118+
if ( $node_list && $node_list->count() ) {
1119+
1120+
$node = $node_list->item( 0 );
1121+
1122+
// Get the site's locale.
1123+
$locale = get_locale();
1124+
1125+
// Get the alt text accessibility alternative most appropriate for the site language.
1126+
// There are 3 possibilities:
1127+
//
1128+
// 1. there is an rdf:li with an exact match on the site locale.
1129+
// 2. there is an rdf:li with a partial match on the site locale (e.g., site locale is en_US and rdf:li has @xml:lang="en").
1130+
// 3. there is an rdf:li with an "x-default" lang.
1131+
//
1132+
// Evaluate in that order, stopping when we have a match.
1133+
$value = $xpath->evaluate( "string( rdf:Alt/rdf:li[ @xml:lang = '{$locale}' ] )", $node );
1134+
if ( ! $value ) {
1135+
$value = $xpath->evaluate( 'string( rdf:Alt/rdf:li[ @xml:lang = "' . substr( $locale, 0, 2 ) . '" ] )', $node );
1136+
if ( ! $value ) {
1137+
$value = $xpath->evaluate( 'string( rdf:Alt/rdf:li[ @xml:lang = "x-default" ] )', $node );
1138+
}
1139+
}
1140+
}
1141+
1142+
return $value;
1143+
}
1144+
10771145
/**
10781146
* Validates that file is an image.
10791147
*

src/wp-admin/includes/media.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ function media_handle_upload( $file_id, $post_id, $post_data = array(), $overrid
319319
$title = sanitize_text_field( $name );
320320
$content = '';
321321
$excerpt = '';
322+
$alt = '';
322323

323324
if ( preg_match( '#^audio#', $type ) ) {
324325
$meta = wp_read_audio_metadata( $file );
@@ -399,6 +400,10 @@ function media_handle_upload( $file_id, $post_id, $post_data = array(), $overrid
399400
if ( trim( $image_meta['caption'] ) ) {
400401
$excerpt = $image_meta['caption'];
401402
}
403+
404+
if ( trim( $image_meta['alt'] ) ) {
405+
$alt = $image_meta['alt'];
406+
}
402407
}
403408
}
404409

@@ -421,6 +426,10 @@ function media_handle_upload( $file_id, $post_id, $post_data = array(), $overrid
421426
// Save the data.
422427
$attachment_id = wp_insert_attachment( $attachment, $file, $post_id, true );
423428

429+
if ( trim( $alt ) ) {
430+
update_post_meta( $attachment_id, '_wp_attachment_image_alt', $alt );
431+
}
432+
424433
if ( ! is_wp_error( $attachment_id ) ) {
425434
/*
426435
* Set a custom header with the attachment_id.

0 commit comments

Comments
 (0)