__( 'You do not have permission to do this.', 'fix-experts-ai-image-alt-tag-generator' ) ), 403 ); } check_ajax_referer( self::NONCE_ACTION, 'nonce' ); } public function handle_scan() { $this->check_request(); $items = AIATG_Scanner::scan(); wp_send_json_success( array( 'items' => $items, 'count' => count( $items ), ) ); } public function handle_generate() { $this->check_request(); $type = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : ''; $id = isset( $_POST['id'] ) ? absint( $_POST['id'] ) : 0; $post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0; $src = isset( $_POST['src'] ) ? esc_url_raw( wp_unslash( $_POST['src'] ) ) : ''; if ( ! in_array( $type, array( 'attachment', 'content' ), true ) || empty( $src ) ) { wp_send_json_error( array( 'message' => __( 'Invalid image reference.', 'fix-experts-ai-image-alt-tag-generator' ) ) ); } $image_data = $this->fetch_image_bytes( $type, $id, $src ); if ( is_wp_error( $image_data ) ) { wp_send_json_error( array( 'message' => $image_data->get_error_message() ) ); } $context = $this->build_context( $type, $id, $post_id ); $config = AIATG_Settings::get_active_provider_config(); $provider = AIATG_AI_Provider::get_provider( $config['provider'], $config['api_key'], $config['model'] ); if ( is_wp_error( $provider ) ) { wp_send_json_error( array( 'message' => $provider->get_error_message() ) ); } $alt_text = $provider->generate( $image_data['bytes'], $image_data['mime'], $context ); if ( is_wp_error( $alt_text ) ) { wp_send_json_error( array( 'message' => $alt_text->get_error_message() ) ); } wp_send_json_success( array( 'alt_text' => $alt_text ) ); } public function handle_save() { $this->check_request(); $type = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : ''; $id = isset( $_POST['id'] ) ? absint( $_POST['id'] ) : 0; $post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0; $index = isset( $_POST['index'] ) ? absint( $_POST['index'] ) : 0; $src = isset( $_POST['src'] ) ? esc_url_raw( wp_unslash( $_POST['src'] ) ) : ''; $alt_text = isset( $_POST['alt_text'] ) ? sanitize_text_field( wp_unslash( $_POST['alt_text'] ) ) : ''; if ( '' === trim( $alt_text ) ) { wp_send_json_error( array( 'message' => __( 'Alt text cannot be empty.', 'fix-experts-ai-image-alt-tag-generator' ) ) ); } if ( 'attachment' === $type ) { if ( ! $id || 'attachment' !== get_post_type( $id ) ) { wp_send_json_error( array( 'message' => __( 'Attachment not found.', 'fix-experts-ai-image-alt-tag-generator' ) ) ); } update_post_meta( $id, '_wp_attachment_image_alt', $alt_text ); wp_send_json_success( array( 'saved' => true ) ); } if ( 'content' === $type ) { $post = get_post( $post_id ); if ( ! $post ) { wp_send_json_error( array( 'message' => __( 'Post not found.', 'fix-experts-ai-image-alt-tag-generator' ) ) ); } if ( ! current_user_can( 'edit_post', $post_id ) ) { wp_send_json_error( array( 'message' => __( 'You cannot edit this post.', 'fix-experts-ai-image-alt-tag-generator' ) ), 403 ); } $updated_content = AIATG_Scanner::set_content_image_alt( $post->post_content, $index, $alt_text, $src ); if ( is_wp_error( $updated_content ) ) { wp_send_json_error( array( 'message' => $updated_content->get_error_message() ) ); } $result = wp_update_post( array( 'ID' => $post_id, 'post_content' => $updated_content, ), true ); if ( is_wp_error( $result ) ) { wp_send_json_error( array( 'message' => $result->get_error_message() ) ); } wp_send_json_success( array( 'saved' => true ) ); } wp_send_json_error( array( 'message' => __( 'Invalid save request.', 'fix-experts-ai-image-alt-tag-generator' ) ) ); } /** * Retrieves raw image bytes + mime type, either from local disk * (Media Library attachments) or via HTTP (content images, which may * be external or otherwise not registered as attachments). */ private function fetch_image_bytes( $type, $id, $src ) { if ( 'attachment' === $type && $id ) { $path = get_attached_file( $id ); if ( $path && file_exists( $path ) ) { $bytes = file_get_contents( $path ); $mime = get_post_mime_type( $id ); if ( false !== $bytes && $mime ) { return array( 'bytes' => $bytes, 'mime' => $mime ); } } } $response = wp_remote_get( $src, array( 'timeout' => 30, ) ); if ( is_wp_error( $response ) ) { return $response; } $code = wp_remote_retrieve_response_code( $response ); if ( $code < 200 || $code >= 300 ) { return new WP_Error( 'aiatg_fetch_failed', sprintf( __( 'Could not download image (HTTP %d).', 'fix-experts-ai-image-alt-tag-generator' ), $code ) ); } $bytes = wp_remote_retrieve_body( $response ); $mime = wp_remote_retrieve_header( $response, 'content-type' ); $mime = $mime ? explode( ';', $mime )[0] : 'image/jpeg'; if ( empty( $bytes ) ) { return new WP_Error( 'aiatg_fetch_empty', __( 'Downloaded image was empty.', 'fix-experts-ai-image-alt-tag-generator' ) ); } return array( 'bytes' => $bytes, 'mime' => trim( $mime ) ); } /** * Builds the site/page context passed to the AI prompt. */ private function build_context( $type, $id, $post_id ) { $context = array( 'site_name' => get_bloginfo( 'name' ), 'site_description' => get_bloginfo( 'description' ), 'site_context' => AIATG_Context::get_site_context(), 'max_length' => (int) AIATG_Settings::get( 'alt_max_length' ), ); if ( 'attachment' === $type && $id ) { $product = AIATG_Context::get_product_context( $id ); if ( $product ) { $context['product_name'] = $product['title']; $context['product_categories'] = $product['categories']; $context['product_description'] = $product['excerpt']; $context['page_title'] = $product['title']; } else { $parent_id = wp_get_post_parent_id( $id ); $title = get_the_title( $parent_id ? $parent_id : $id ); $caption = wp_get_attachment_caption( $id ); $context['page_title'] = $title; $context['excerpt'] = $caption ? $caption : ''; } } elseif ( 'content' === $type && $post_id ) { $post = get_post( $post_id ); if ( $post ) { $context['page_title'] = get_the_title( $post_id ); $excerpt = has_excerpt( $post_id ) ? get_the_excerpt( $post_id ) : wp_trim_words( wp_strip_all_tags( $post->post_content ), 40 ); $context['excerpt'] = $excerpt; } } return $context; } }