@@ -207,6 +207,8 @@ function acf_blocks_download_external_image( $url ) {
207207
208208 // Already downloaded — return cached path.
209209 if ( file_exists ( $ target_path ) ) {
210+ // Ensure a WP attachment exists for srcset support.
211+ acf_blocks_ensure_localised_attachment ( $ target_path , $ target_url , $ filename );
210212 return $ target_url ;
211213 }
212214
@@ -246,9 +248,90 @@ function acf_blocks_download_external_image( $url ) {
246248 return false ;
247249 }
248250
251+ // Register as a WP attachment so WordPress generates image sizes for srcset.
252+ acf_blocks_ensure_localised_attachment ( $ target_path , $ target_url , $ filename );
253+
249254 return $ target_url ;
250255}
251256
257+ /**
258+ * Ensure a localized image file has a corresponding WordPress attachment.
259+ *
260+ * Creates an attachment post and generates image metadata (thumbnails)
261+ * if one does not already exist. This enables srcset/sizes support for
262+ * images downloaded by the localizer.
263+ *
264+ * @param string $file_path Absolute path to the image file.
265+ * @param string $file_url Public URL of the image file.
266+ * @param string $filename The filename (e.g. "md5hash.jpg").
267+ * @return int Attachment ID, or 0 on failure.
268+ */
269+ function acf_blocks_ensure_localised_attachment ( $ file_path , $ file_url , $ filename ) {
270+ // Check if an attachment already exists for this file.
271+ $ existing_id = acf_blocks_get_attachment_id_by_file ( $ file_path );
272+ if ( $ existing_id ) {
273+ return $ existing_id ;
274+ }
275+
276+ // Determine MIME type.
277+ $ mime_type = wp_check_filetype ( $ filename )['type ' ];
278+ if ( ! $ mime_type ) {
279+ return 0 ;
280+ }
281+
282+ // Build the relative path WordPress expects for _wp_attached_file meta.
283+ $ upload_dir = wp_upload_dir ();
284+ $ relative_path = str_replace ( trailingslashit ( $ upload_dir ['basedir ' ] ), '' , $ file_path );
285+
286+ $ attachment_data = array (
287+ 'post_title ' => sanitize_file_name ( pathinfo ( $ filename , PATHINFO_FILENAME ) ),
288+ 'post_mime_type ' => $ mime_type ,
289+ 'post_status ' => 'inherit ' ,
290+ );
291+
292+ $ attachment_id = wp_insert_attachment ( $ attachment_data , $ file_path );
293+
294+ if ( ! $ attachment_id || is_wp_error ( $ attachment_id ) ) {
295+ return 0 ;
296+ }
297+
298+ // Set the correct relative path so attachment_url_to_postid() can find it.
299+ update_post_meta ( $ attachment_id , '_wp_attached_file ' , $ relative_path );
300+
301+ // Generate image sizes and metadata.
302+ if ( ! function_exists ( 'wp_generate_attachment_metadata ' ) ) {
303+ require_once ABSPATH . 'wp-admin/includes/image.php ' ;
304+ }
305+
306+ $ metadata = wp_generate_attachment_metadata ( $ attachment_id , $ file_path );
307+ if ( ! empty ( $ metadata ) ) {
308+ wp_update_attachment_metadata ( $ attachment_id , $ metadata );
309+ }
310+
311+ return $ attachment_id ;
312+ }
313+
314+ /**
315+ * Find an existing attachment ID by its file path.
316+ *
317+ * @param string $file_path Absolute path to the image file.
318+ * @return int Attachment ID, or 0 if not found.
319+ */
320+ function acf_blocks_get_attachment_id_by_file ( $ file_path ) {
321+ $ upload_dir = wp_upload_dir ();
322+ $ relative_path = str_replace ( trailingslashit ( $ upload_dir ['basedir ' ] ), '' , $ file_path );
323+
324+ global $ wpdb ;
325+ $ attachment_id = $ wpdb ->get_var (
326+ $ wpdb ->prepare (
327+ "SELECT post_id FROM {$ wpdb ->postmeta } WHERE meta_key = '_wp_attached_file' AND meta_value = %s LIMIT 1 " ,
328+ $ relative_path
329+ )
330+ );
331+
332+ return $ attachment_id ? (int ) $ attachment_id : 0 ;
333+ }
334+
252335/**
253336 * Get (and create if needed) the local image storage directory.
254337 *
0 commit comments