@@ -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 *
0 commit comments