post_type || 'trash' === $post->post_status || ! current_user_can( 'edit_post', $id ) ) { throw new \RuntimeException( 'image_access_denied' ); } /* translators: %d: Attachment ID. */ $title = $post->post_title ?: sprintf( __( 'Media #%d', 'affstackdev-image-manager' ), $id ); $meta = wp_get_attachment_metadata( $id ); $meta = is_array( $meta ) ? $meta : array(); $org = Library::organization_for_objects( array( $id ) ); $org = $org[ $id ] ?? array( 'folders' => array(), 'collections' => array(), ); $pdf = 'application/pdf' === (string) $post->post_mime_type; $row = $pdf ? null : Repository::attachment( $id ); $image = str_starts_with( (string) $post->post_mime_type, 'image/' ); $pdf_preview = $pdf ? Media_Display::pdf_preview( $id, $meta, true ) : array(); echo '

' . esc_html( $title ) . '

'; if ( $image ) { echo '
' . wp_kses( wp_get_attachment_image( $id, 'medium', false, array( 'sizes' => '(max-width: 600px) 90vw, 480px', 'decoding' => 'async', ) ), Admin_Layout::allowed_html() ) . '
'; } elseif ( $pdf_preview ) { echo '
' . esc_attr( sprintf( __( 'First page of %s', 'affstackdev-image-manager' ), $title ) )
				. '

' . esc_html__( 'Saved first-page preview', 'affstackdev-image-manager' ) . '

'; } elseif ( $pdf ) { echo '
' . esc_html__( 'PDF document', 'affstackdev-image-manager' ) . '

' . esc_html__( 'No suitable saved preview is available. You can still open and organize this PDF.', 'affstackdev-image-manager' ) . '

'; } echo '
'; $fields = array( __( 'Media ID', 'affstackdev-image-manager' ) => (string) $id, __( 'File type', 'affstackdev-image-manager' ) => (string) $post->post_mime_type, ( $pdf ? __( 'Preview dimensions', 'affstackdev-image-manager' ) : __( 'Dimensions', 'affstackdev-image-manager' ) ) => $pdf ? ( $pdf_preview ? $pdf_preview[1] . ' × ' . $pdf_preview[2] : __( 'Not available', 'affstackdev-image-manager' ) ) : ( ! empty( $meta['width'] ) && ! empty( $meta['height'] ) ? absint( $meta['width'] ) . ' × ' . absint( $meta['height'] ) : __( 'Not recorded', 'affstackdev-image-manager' ) ), __( 'Recorded file size', 'affstackdev-image-manager' ) => ! empty( $meta['filesize'] ) ? size_format( absint( $meta['filesize'] ), 1 ) : __( 'Not recorded', 'affstackdev-image-manager' ), __( 'Folders', 'affstackdev-image-manager' ) => implode( ', ', $org['folders'] ) ?: __( 'No folder', 'affstackdev-image-manager' ), __( 'Collections', 'affstackdev-image-manager' ) => implode( ', ', $org['collections'] ) ?: __( 'No collection', 'affstackdev-image-manager' ), ); foreach ( $fields as $label => $value ) { echo '
' . esc_html( $label ) . '
' . esc_html( $value ) . '
'; } echo '
'; if ( $image ) { echo '

' . esc_html__( 'Alt-text default', 'affstackdev-image-manager' ) . '

' . esc_html( get_post_meta( $id, '_wp_attachment_image_alt', true ) ?: __( 'No default description saved.', 'affstackdev-image-manager' ) ) . '

'; } if ( $post->post_excerpt ) { echo '

' . esc_html__( 'Caption', 'affstackdev-image-manager' ) . '

' . esc_html( $post->post_excerpt ) . '

'; } if ( $image ) { self::photo_details( $id, $meta, $post ); } echo '

' . esc_html( $pdf ? __( 'Document handling', 'affstackdev-image-manager' ) : __( 'Stored optimization result', 'affstackdev-image-manager' ) ) . '

'; if ( $pdf ) { echo '

' . esc_html__( 'This PDF can be organized in folders and collections and included in media backups. Create image from PDF can render one page as a separate image on supported hosts. PDF contents are not changed by image compression.', 'affstackdev-image-manager' ) . '

'; } elseif ( $row ) { $result = Results::row( $row ); echo '

' . esc_html( $result['label'] ) . '

' . esc_html( $result['reason'] ) . '

' . esc_html( $result['delivery'] ) . '

'; echo '' . esc_html__( 'Optimization details and delivery checks', 'affstackdev-image-manager' ) . ''; } else { echo '

' . esc_html__( 'No optimization record for this attachment yet.', 'affstackdev-image-manager' ) . '

'; } Media_Usage::render( $id, $back_url ); $parent = (int) $post->post_parent; if ( $parent && current_user_can( 'edit_post', $parent ) && get_post( $parent ) && 'trash' !== get_post_status( $parent ) ) { /* translators: %d: Parent content ID. */ echo '

' . esc_html__( 'Upload association:', 'affstackdev-image-manager' ) . ' ' . esc_html( get_the_title( $parent ) ?: sprintf( __( 'Content #%d', 'affstackdev-image-manager' ), $parent ) ) . '. ' . esc_html__( 'This attachment relationship alone does not establish current use.', 'affstackdev-image-manager' ) . '

'; } echo '

' . esc_html__( 'Earlier scanner observations', 'affstackdev-image-manager' ) . '

' . esc_html( $shown ? __( 'Up to 16 references previously recorded by the optimizer are shown. These have not been rechecked here; inspect current content before relying on them.', 'affstackdev-image-manager' ) : __( 'No accessible references are recorded here. This does not mean the file is unused.', 'affstackdev-image-manager' ) ) . '

'; $history = $image ? array_slice( array_reverse( Editor::history( $id ) ), 0, 5 ) : array(); if ( $history ) { echo '

' . esc_html__( 'Recent edited copies', 'affstackdev-image-manager' ) . '

'; } echo '
'; $pdf_url = $pdf ? wp_get_attachment_url( $id ) : false; if ( $pdf_url ) { echo '' . esc_html__( 'Open PDF', 'affstackdev-image-manager' ) . ''; } if ( $image ) { echo '' . esc_html__( 'Edit image', 'affstackdev-image-manager' ) . ''; } if ( $image || $pdf ) { echo '' . esc_html( $pdf ? __( 'Create image from PDF', 'affstackdev-image-manager' ) : __( 'Replace with undo', 'affstackdev-image-manager' ) ) . ''; } if ( $pdf && PDF_Copy::available() ) { echo '' . esc_html__( 'Make a smaller PDF', 'affstackdev-image-manager' ) . ''; } echo '' . esc_html__( 'Edit attachment', 'affstackdev-image-manager' ) . '' . esc_html__( 'Open backups', 'affstackdev-image-manager' ) . '' . esc_html__( 'Back to library', 'affstackdev-image-manager' ) . '
'; } /** * 1.9.0 (D-500 gap 13): the camera and photo details WordPress saved from the file at upload (image_meta), whether * the original still holds GPS coordinates, and a button that copies the photo's own caption and title into * fields that have none yet. * * @param int $id The attachment. * @param array $meta Its metadata. * @param \WP_Post $post The attachment post. * @return void */ private static function photo_details( int $id, array $meta, \WP_Post $post ): void { $m = isset( $meta['image_meta'] ) && is_array( $meta['image_meta'] ) ? $meta['image_meta'] : array(); $rows = array(); if ( '' !== trim( (string) ( $m['camera'] ?? '' ) ) ) { $rows[ __( 'Camera', 'affstackdev-image-manager' ) ] = (string) $m['camera']; } if ( ! empty( $m['created_timestamp'] ) && is_numeric( $m['created_timestamp'] ) ) { // The camera's own clock, as WordPress read it (no time zone). $rows[ __( 'Taken', 'affstackdev-image-manager' ) ] = wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), (int) $m['created_timestamp'], new \DateTimeZone( 'UTC' ) ); } if ( ! empty( $m['aperture'] ) && is_numeric( $m['aperture'] ) ) { $rows[ __( 'Aperture', 'affstackdev-image-manager' ) ] = 'f/' . number_format_i18n( (float) $m['aperture'], 1 ); } if ( ! empty( $m['shutter_speed'] ) && is_numeric( $m['shutter_speed'] ) && (float) $m['shutter_speed'] > 0 ) { $speed = (float) $m['shutter_speed']; /* translators: %s: a shutter speed such as 1/250 or 2. */ $rows[ __( 'Shutter speed', 'affstackdev-image-manager' ) ] = sprintf( __( '%s s', 'affstackdev-image-manager' ), $speed >= 1 ? number_format_i18n( $speed, 1 ) : '1/' . number_format_i18n( round( 1 / $speed ) ) ); } if ( ! empty( $m['iso'] ) && is_numeric( $m['iso'] ) ) { $rows[ __( 'ISO', 'affstackdev-image-manager' ) ] = number_format_i18n( (int) $m['iso'] ); } if ( ! empty( $m['focal_length'] ) && is_numeric( $m['focal_length'] ) ) { /* translators: %s: a focal length in millimetres, such as 50. */ $rows[ __( 'Focal length', 'affstackdev-image-manager' ) ] = sprintf( __( '%s mm', 'affstackdev-image-manager' ), number_format_i18n( (float) $m['focal_length'] ) ); } foreach ( array( 'title' => __( 'Title in the photo', 'affstackdev-image-manager' ), 'caption' => __( 'Caption in the photo', 'affstackdev-image-manager' ), 'credit' => __( 'Credit', 'affstackdev-image-manager' ), 'copyright' => __( 'Copyright', 'affstackdev-image-manager' ), ) as $key => $label ) { if ( '' !== trim( (string) ( $m[ $key ] ?? '' ) ) ) { $rows[ $label ] = (string) $m[ $key ]; } } if ( ! empty( $m['keywords'] ) && is_array( $m['keywords'] ) ) { $rows[ __( 'Keywords', 'affstackdev-image-manager' ) ] = implode( ', ', array_map( 'strval', $m['keywords'] ) ); } $gps = self::has_location( $id ); if ( ! $rows && ! $gps ) { return; } echo '

' . esc_html__( 'Photo details', 'affstackdev-image-manager' ) . '

'; if ( $rows ) { echo '
'; foreach ( $rows as $label => $value ) { echo '
' . esc_html( $label ) . '
' . esc_html( $value ) . '
'; } echo '
'; } if ( $gps ) { echo '

' . esc_html__( 'The original file holds GPS coordinates (where the photo was taken). Anyone who opens its address can read them. Image Manager’s copies leave them out when the compression choice removes GPS.', 'affstackdev-image-manager' ) . '

'; } $caption = '' !== trim( (string) ( $m['caption'] ?? '' ) ) && '' === trim( (string) $post->post_excerpt ); $title = '' !== trim( (string) ( $m['title'] ?? '' ) ) && Auto_Text::copied_title( $id, (string) $post->post_title ); if ( $caption || $title ) { echo '
'; wp_nonce_field( 'afsd_im_action' ); echo '
'; } } /** * Whether the original JPEG still holds GPS coordinates (PHP's exif extension; nothing is shown without it). * * @param int $id The attachment. * @return bool */ private static function has_location( int $id ): bool { $file = function_exists( 'wp_get_original_image_path' ) ? wp_get_original_image_path( $id ) : get_attached_file( $id ); if ( ! function_exists( 'exif_read_data' ) || ! is_string( $file ) || ! is_file( $file ) || filesize( $file ) > 67108864 || 'image/jpeg' !== wp_get_image_mime( $file ) ) { return false; } set_error_handler( static fn() => true ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler -- a damaged EXIF block only means "no location"; its warnings are not the page's. try { $exif = exif_read_data( $file, 'GPS', true ); } finally { restore_error_handler(); } return is_array( $exif ) && ! empty( $exif['GPS']['GPSLatitude'] ) && ! empty( $exif['GPS']['GPSLongitude'] ); } /** * Copy the photo's own caption (when the caption is empty) and title (when the title is the bare file name). * * @param int $id The attachment. * @return array The fields filled (caption, title). * @throws \RuntimeException When the attachment may not be changed or the change fails. */ public static function copy_photo_text( int $id ): array { $lock = Reviewed_Store::enter( 8 ); try { $post = get_post( $id ); if ( ! $post || 'attachment' !== $post->post_type || ! current_user_can( 'edit_post', $id ) ) { throw new \RuntimeException( 'image_access_denied' ); } $meta = wp_get_attachment_metadata( $id ); $m = is_array( $meta ) && isset( $meta['image_meta'] ) && is_array( $meta['image_meta'] ) ? $meta['image_meta'] : array(); $update = array( 'ID' => $id ); $done = array(); $text = sanitize_textarea_field( (string) ( $m['caption'] ?? '' ) ); if ( '' !== trim( $text ) && '' === trim( (string) $post->post_excerpt ) ) { $update['post_excerpt'] = $text; $done[] = 'caption'; } $text = sanitize_text_field( (string) ( $m['title'] ?? '' ) ); if ( '' !== trim( $text ) && Auto_Text::copied_title( $id, (string) $post->post_title ) ) { $update['post_title'] = $text; $done[] = 'title'; } if ( $done && is_wp_error( Reviewed_Store::wp_update_post( wp_slash( $update ), true ) ) ) { throw new \RuntimeException( 'media_action_changed' ); } return $done; } finally { Reviewed_Store::leave( $lock ); } } }