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