diff --git a/src/wp-admin/includes/image.php b/src/wp-admin/includes/image.php index 2553f68434659..177663d8ce5ba 100644 --- a/src/wp-admin/includes/image.php +++ b/src/wp-admin/includes/image.php @@ -813,7 +813,7 @@ function wp_exif_date2ts( $str ) { * created_timestamp, focal_length, shutter_speed, and title. * * The IPTC metadata that is retrieved is APP13, credit, byline, created date - * and time, caption, copyright, and title. Also includes FNumber, Model, + * and time, caption, copyright, alt, and title. Also includes FNumber, Model, * DateTimeDigitized, FocalLength, ISOSpeedRatings, and ExposureTime. * * @todo Try other exif libraries if available. @@ -854,6 +854,7 @@ function wp_read_image_metadata( $file ) { 'title' => '', 'orientation' => 0, 'keywords' => array(), + 'alt' => '', ); $iptc = array(); @@ -926,6 +927,8 @@ function wp_read_image_metadata( $file ) { } } + $meta['alt'] = wp_get_image_alttext( $file ); + $exif = array(); /** @@ -1074,6 +1077,72 @@ function wp_read_image_metadata( $file ) { return apply_filters( 'wp_read_image_metadata', $meta, $file, $image_type, $iptc, $exif ); } +/** + * Get the alt text from image meta data. + * + * @since x.x.x + * + * @param string $file File path to the image. + * @return string Embedded alternative text. + */ +function wp_get_image_alttext( $file ) { + + $img_contents = file_get_contents( $file ); + // Find the start and end positions of the XMP metadata. + $xmp_start = strpos( $img_contents, '' ); + + if ( ! $xmp_start || ! $xmp_end ) { + // No XMP metadata found. + return ''; + } + + // Extract the XMP metadata from the JPEG contents + $xmp_data = substr( $img_contents, $xmp_start, $xmp_end - $xmp_start + 12 ); + + // Parse the XMP metadata using DOMDocument. + $doc = new DOMDocument(); + if ( false === $doc->loadXML( $xmp_data ) ) { + // Invalid XML in metadata. + return ''; + } + + // Instantiate an XPath object, used to extract portions of the XMP. + $xpath = new DOMXPath( $doc ); + + // Register the relevant XML namespaces. + $xpath->registerNamespace( 'x', 'adobe:ns:meta/' ); + $xpath->registerNamespace( 'rdf', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' ); + $xpath->registerNamespace( 'Iptc4xmpCore', 'http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/' ); + + $node_list = $xpath->query( '/x:xmpmeta/rdf:RDF/rdf:Description/Iptc4xmpCore:AltTextAccessibility' ); + if ( $node_list && $node_list->count() ) { + + $node = $node_list->item( 0 ); + + // Get the site's locale. + $locale = get_locale(); + + // Get the alt text accessibility alternative most appropriate for the site language. + // There are 3 possibilities: + // + // 1. there is an rdf:li with an exact match on the site locale. + // 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"). + // 3. there is an rdf:li with an "x-default" lang. + // + // Evaluate in that order, stopping when we have a match. + $value = $xpath->evaluate( "string( rdf:Alt/rdf:li[ @xml:lang = '{$locale}' ] )", $node ); + if ( ! $value ) { + $value = $xpath->evaluate( 'string( rdf:Alt/rdf:li[ @xml:lang = "' . substr( $locale, 0, 2 ) . '" ] )', $node ); + if ( ! $value ) { + $value = $xpath->evaluate( 'string( rdf:Alt/rdf:li[ @xml:lang = "x-default" ] )', $node ); + } + } + } + + return $value; +} + /** * Validates that file is an image. * diff --git a/src/wp-admin/includes/media.php b/src/wp-admin/includes/media.php index 1d45224f5b7e4..cc728b0efd9bc 100644 --- a/src/wp-admin/includes/media.php +++ b/src/wp-admin/includes/media.php @@ -319,6 +319,7 @@ function media_handle_upload( $file_id, $post_id, $post_data = array(), $overrid $title = sanitize_text_field( $name ); $content = ''; $excerpt = ''; + $alt = ''; if ( preg_match( '#^audio#', $type ) ) { $meta = wp_read_audio_metadata( $file ); @@ -399,6 +400,10 @@ function media_handle_upload( $file_id, $post_id, $post_data = array(), $overrid if ( trim( $image_meta['caption'] ) ) { $excerpt = $image_meta['caption']; } + + if ( trim( $image_meta['alt'] ) ) { + $alt = $image_meta['alt']; + } } } @@ -421,6 +426,10 @@ function media_handle_upload( $file_id, $post_id, $post_data = array(), $overrid // Save the data. $attachment_id = wp_insert_attachment( $attachment, $file, $post_id, true ); + if ( trim( $alt ) ) { + update_post_meta( $attachment_id, '_wp_attachment_image_alt', $alt ); + } + if ( ! is_wp_error( $attachment_id ) ) { /* * Set a custom header with the attachment_id.