always yields the correct ID without * any re-injection machinery on the JS side. Core places compat fields below the File * URL field; assets/js/attachment-button.js moves the block under Alternative Text. */ if (!defined('ABSPATH')) { exit; } class AIGUDE_Attachment_Button { /** * Attach the single filter this module needs. */ public function register(): void { add_filter('attachment_fields_to_edit', [$this, 'add_generate_field'], 10, 2); } /** * Append the generate block to the compat fields of an image attachment. * * Emits an , never an : core's AttachmentCompat view saves the whole * compat form on any input/select/textarea change event. * * @param array $fields Compat fields keyed by field name. * @param WP_Post $post The attachment being rendered. * @return array */ public function add_generate_field($fields, $post) { if (!is_array($fields)) { $fields = []; } if (!current_user_can('upload_files') || !wp_attachment_is_image($post)) { return $fields; } // get_translation_api_key() returns the active server's key, which is the same // credential the generation endpoint uses; without it the button cannot work. if (AIGUDE_Tools_Plugin::get_translation_api_key() === '') { return $fields; } $tpl = AIGUDE_Tools_Plugin::get_default_prompt_template(); $tpl_title = (string) ($tpl['title'] ?? ''); $prompts_url = admin_url('admin.php?page=aigude-tools-prompts'); $html = sprintf( '
' . '%2$s' . ' ' . '

%3$s

' . '
', (int) $post->ID, esc_html__('Generate alt text', 'aigude-tools'), wp_kses_post(sprintf( /* translators: 1: prompt name, 2: credit cost, 3: link to the Prompts screen */ __('Using %1$s, %2$d credits. Change the default prompt', 'aigude-tools'), '' . esc_html($tpl_title) . '', AIGUDE_Tools_Plugin::PER_IMAGE_CREDITS, esc_url($prompts_url) )) ); $fields['aigude_generate'] = [ 'label' => '', 'input' => 'html', 'html' => $html, 'show_in_edit' => true, 'show_in_modal' => true, ]; return $fields; } }