tags already saved in post content (the class * WordPress core automatically adds when an image is inserted via the * block editor), post-processed via the_content * * AI-generated alt text (from an actual image description) is a separate, * richer feature tracked under M12. */ final class AltTextFallback { private FeaturesRepositoryInterface $features; public function __construct( FeaturesRepositoryInterface $features ) { $this->features = $features; } public function register(): void { add_filter( 'wp_get_attachment_image_attributes', array( $this, 'fill_alt_attributes' ), 10, 2 ); add_filter( 'the_content', array( $this, 'fill_alt_in_content' ) ); } /** * @param array $attr * * @return array */ public function fill_alt_attributes( array $attr, \WP_Post $attachment ): array { if ( ! $this->features->enabled( 'image_alt' ) || ! empty( $attr['alt'] ) ) { return $attr; } $fallback = self::fallback_text( $attachment->ID ); if ( '' !== $fallback ) { $attr['alt'] = $fallback; } return $attr; } public function fill_alt_in_content( string $content ): string { if ( ! $this->features->enabled( 'image_alt' ) ) { return $content; } if ( ! str_contains( $content, ']*class="[^"]*wp-image-(\d+)[^"]*"[^>]*>/i', array( $this, 'maybe_fill_alt_tag' ), $content ); } /** * @param string[] $matches */ private function maybe_fill_alt_tag( array $matches ): string { $tag = $matches[0]; if ( preg_match( '/\balt="[^"]+"/i', $tag ) ) { return $tag; // already has non-empty alt. } $fallback = self::fallback_text( (int) $matches[1] ); if ( '' === $fallback ) { return $tag; } if ( preg_match( '/\balt=""/i', $tag ) ) { return (string) preg_replace( '/\balt=""/i', 'alt="' . esc_attr( $fallback ) . '"', $tag, 1 ); } return (string) preg_replace( '/