class and primes the meta * cache before this filter runs, so the lookup costs no additional query. * * @param string $img_tag Full img tag, already processed by core. * @param string $context Filter context supplied by core (unused). * @param int $attachment_id Resolved attachment ID, 0 when core could not determine one. * @return string */ public function fill_missing_alt($img_tag, $context, $attachment_id) { $img_tag = (string) $img_tag; /** * Master switch for the render-time alt fill. * * Lets a site turn the behaviour off without deactivating the plugin, for example * where empty alt attributes are used deliberately to mark decorative images. * * @param bool $enabled Whether to fill empty alt attributes. Default true. */ if (!apply_filters('aigude_fill_missing_alt_enabled', true)) { return $img_tag; } if ((int) $attachment_id <= 0) { return $img_tag; } // Never override alt text an editor wrote. if (preg_match('/\salt\s*=\s*(["\'])(?!\s*\1).+?\1/is', $img_tag)) { return $img_tag; } // Strip any markup before escaping, so a stray tag in generated text does not // surface as literal <b> inside the attribute. $alt = trim(wp_strip_all_tags((string) get_post_meta((int) $attachment_id, '_wp_attachment_image_alt', true))); if ($alt === '') { return $img_tag; } // Built once and returned from the callbacks below. preg_replace_callback is used // instead of preg_replace because alt text is customer data and may contain "$1" // or backslashes, which preg_replace would expand as backreferences. $replacement = ' alt="' . esc_attr($alt) . '"'; // Case 1: an empty alt attribute is present, replace it in place. if (preg_match('/\salt\s*=\s*(["\'])\s*\1/i', $img_tag)) { return preg_replace_callback( '/(\salt\s*=\s*)(["\'])\s*\2/i', static function () use ($replacement): string { return $replacement; }, $img_tag, 1 ); } // Case 2: no alt attribute at all, as older classic-editor markup can be. return preg_replace_callback( '/^