esc_url_raw( rest_url( AccessVane_REST::NAMESPACE_V1 . '/alt-text' ) ), 'nonce' => wp_create_nonce( 'wp_rest' ), 'strings' => array( 'saved' => __( 'Saved', 'accessvane' ), 'failed' => __( 'Could not save', 'accessvane' ), ), ) ); } /** * Count images with no alt text. * * @return int */ private function missing_count() { $query = new WP_Query( $this->query_args( 1, 1 ) ); return (int) $query->found_posts; } /** * Query arguments for attachments missing alt text. * * Uses a NOT EXISTS / empty-string pair because WordPress writes an empty * string for images that were saved with the field left blank, and no row * at all for images that were never touched. * * @param int $page Page number. * @param int $per_page Results per page. * @return array */ private function query_args( $page, $per_page ) { return array( 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_mime_type' => array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif' ), 'posts_per_page' => $per_page, 'paged' => $page, 'orderby' => 'date', 'order' => 'DESC', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- the whole purpose of this screen is to find images by a missing meta value. 'meta_query' => array( 'relation' => 'OR', array( 'key' => '_wp_attachment_image_alt', 'compare' => 'NOT EXISTS', ), array( 'key' => '_wp_attachment_image_alt', 'value' => '', 'compare' => '=', ), ), ); } /** * A suggested description, offered but never saved automatically. * * Caption first, then title — and never the file name, which describes * nothing. Returning an empty string means "we have nothing useful", which * is a more honest answer than inventing something. * * @param WP_Post $attachment Attachment. * @return string */ private function suggestion( $attachment ) { $caption = trim( (string) $attachment->post_excerpt ); if ( '' !== $caption ) { return $caption; } $title = trim( (string) $attachment->post_title ); if ( '' === $title ) { return ''; } if ( preg_match( '/\.(jpe?g|png|gif|webp|avif|svg)$/i', $title ) ) { return ''; } if ( preg_match( '/^(img|dsc|dscn|pxl|photo|image|screenshot|screen[ _-]?shot|untitled)[ _-]?\d*/i', $title ) ) { return ''; } if ( preg_match( '/^\d{6,}$/', $title ) ) { return ''; } return $title; } /** * Render the screen. * * @return void */ public function render_page() { if ( ! current_user_can( 'upload_files' ) ) { wp_die( esc_html__( 'You are not allowed to edit media.', 'accessvane' ), 403 ); } $paged = isset( $_GET['paged'] ) ? max( 1, absint( $_GET['paged'] ) ) : 1; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only pagination. $query = new WP_Query( $this->query_args( $paged, self::PER_PAGE ) ); $total = (int) $query->found_posts; echo '
' . esc_html__( 'Every image that carries meaning needs a short description of what it shows. Type it and it saves as you go — there is no Save button to remember.', 'accessvane' ) . '
'; ?>
' . esc_html__( 'Nothing in your media library is missing alt text. Worth re-checking after your next batch of uploads.', 'accessvane' ) . '
| ' . esc_html__( 'Image', 'accessvane' ) . ' | '; echo '' . esc_html__( 'Description (alt text)', 'accessvane' ) . ' | '; echo '' . esc_html__( 'Decorative', 'accessvane' ) . ' | '; echo '
|---|---|---|
| ';
if ( $thumb ) {
printf(
' | ';
echo '';
printf(
'',
esc_attr( $attachment->ID ),
esc_html__( 'Alt text for this image', 'accessvane' )
);
printf(
'',
esc_attr( $attachment->ID ),
esc_attr__( 'Describe what this image shows', 'accessvane' )
);
echo '';
if ( '' !== $suggestion ) {
printf(
' %3$s ', esc_attr( $suggestion ), esc_html__( 'Use the caption', 'accessvane' ), esc_html( '"' . $suggestion . '"' ) ); } if ( $edit_link ) { printf( '', esc_url( $edit_link ), esc_html__( 'Open in Media Library', 'accessvane' ) ); } echo ' | ';
echo ''; printf( '', esc_html__( 'Skip it', 'accessvane' ) ); echo ' | '; echo '