PluginProbe
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More / 2.3.4
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More v2.3.4
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.2 2.2.1 2.2.0 2.1.2 2.1.1 trunk 0.0.1 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.0.7 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 All 66 releases
better-payment / includes / AI / Services / ImageGenerator.php

ImageGenerator.php in Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More 2.3.4, at includes/AI/Services/ImageGenerator.php

151 lines 5.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Better_Payment\Lite\AI\Services;
4
5 use Better_Payment\Lite\AI\AIManager;
6
7 if ( ! defined( 'ABSPATH' ) ) {
8 exit;
9 }
10
11 /**
12 * AI image generation → WordPress Media Library.
13 *
14 * Flow: prompt → active provider->generate_image() → receive URL or base64 →
15 * persist as a real WP attachment → return the photo-ready shape the builder's
16 * `photo` element and `replace_image` operation expect.
17 *
18 * The returned { id, url, sizes, alt } maps directly onto the photo element's
19 * { src_id, src, src_sizes, alt } settings.
20 */
21 class ImageGenerator {
22
23 /**
24 * Generate an image and add it to the media library.
25 *
26 * @param string $prompt
27 * @param int $attach_to_post Optional parent post id.
28 * @return array|\WP_Error { id, url, sizes, alt }
29 */
30 public static function generate( string $prompt, int $attach_to_post = 0 ) {
31 $provider = AIManager::active_provider();
32 if ( null === $provider ) {
33 return new \WP_Error( 'ai_no_provider', __( 'No AI provider is configured.', 'better-payment' ), [ 'status' => 400 ] );
34 }
35 if ( ! $provider->supports_images() ) {
36 return new \WP_Error( 'ai_no_images', __( 'The selected AI provider cannot generate images. Choose OpenAI or Gemini under Settings → AI.', 'better-payment' ), [ 'status' => 400 ] );
37 }
38 if ( ! $provider->is_configured() ) {
39 return new \WP_Error( 'ai_not_configured', __( 'The selected AI provider is missing its API key.', 'better-payment' ), [ 'status' => 400 ] );
40 }
41
42 $image = $provider->generate_image( $prompt );
43 if ( ! empty( $image['error'] ) ) {
44 return new \WP_Error( 'ai_image_error', (string) $image['error'], [ 'status' => 502 ] );
45 }
46
47 $saved = null;
48 if ( ! empty( $image['b64'] ) ) {
49 $saved = self::sideload_bytes( base64_decode( $image['b64'] ), (string) ( $image['mime'] ?? 'image/png' ), $prompt, $attach_to_post );
50 } elseif ( ! empty( $image['url'] ) ) {
51 $saved = self::sideload_url( (string) $image['url'], $prompt, $attach_to_post );
52 }
53
54 if ( is_wp_error( $saved ) ) {
55 return $saved;
56 }
57
58 $attachment_id = (int) $saved;
59 if ( ! $attachment_id ) {
60 return new \WP_Error( 'ai_image_save_failed', __( 'Could not save the generated image.', 'better-payment' ), [ 'status' => 500 ] );
61 }
62
63 return self::attachment_payload( $attachment_id, $prompt );
64 }
65
66 /**
67 * Persist raw image bytes as an attachment.
68 *
69 * @return int|\WP_Error
70 */
71 private static function sideload_bytes( string $bytes, string $mime, string $prompt, int $parent ) {
72 self::require_media();
73
74 $ext = 'image/jpeg' === $mime ? 'jpg' : 'png';
75 $filename = 'bp-ai-' . gmdate( 'YmdHis' ) . '-' . wp_generate_password( 6, false ) . '.' . $ext;
76 $upload = wp_upload_bits( $filename, null, $bytes );
77
78 if ( ! empty( $upload['error'] ) ) {
79 return new \WP_Error( 'ai_image_upload_failed', (string) $upload['error'], [ 'status' => 500 ] );
80 }
81
82 $attachment = [
83 'post_mime_type' => $mime,
84 'post_title' => self::title_from_prompt( $prompt ),
85 'post_content' => '',
86 'post_status' => 'inherit',
87 ];
88 // wp_insert_attachment() returns 0 on failure (no $wp_error flag passed).
89 $attach_id = wp_insert_attachment( $attachment, $upload['file'], $parent );
90 if ( ! $attach_id ) {
91 return new \WP_Error( 'ai_image_insert_failed', __( 'Could not create the image attachment.', 'better-payment' ), [ 'status' => 500 ] );
92 }
93 $meta = wp_generate_attachment_metadata( $attach_id, $upload['file'] );
94 wp_update_attachment_metadata( $attach_id, $meta );
95
96 return (int) $attach_id;
97 }
98
99 /**
100 * Download a remote image URL into the media library.
101 *
102 * @return int|\WP_Error
103 */
104 private static function sideload_url( string $url, string $prompt, int $parent ) {
105 self::require_media();
106 $attach_id = media_sideload_image( $url, $parent, self::title_from_prompt( $prompt ), 'id' );
107 return is_wp_error( $attach_id ) ? $attach_id : (int) $attach_id;
108 }
109
110 /**
111 * Build the photo-ready payload from a saved attachment.
112 *
113 * @return array{ id: int, url: string, sizes: array, alt: string }
114 */
115 private static function attachment_payload( int $attachment_id, string $prompt ): array {
116 $alt = self::title_from_prompt( $prompt );
117 update_post_meta( $attachment_id, '_wp_attachment_image_alt', $alt );
118
119 $meta = wp_get_attachment_metadata( $attachment_id );
120 $sizes = [];
121 if ( is_array( $meta ) && ! empty( $meta['sizes'] ) ) {
122 foreach ( array_keys( $meta['sizes'] ) as $size ) {
123 $src = wp_get_attachment_image_src( $attachment_id, $size );
124 if ( $src ) {
125 $sizes[ $size ] = [ 'url' => $src[0], 'width' => $src[1], 'height' => $src[2] ];
126 }
127 }
128 }
129
130 return [
131 'id' => $attachment_id,
132 'url' => (string) wp_get_attachment_url( $attachment_id ),
133 'sizes' => $sizes,
134 'alt' => $alt,
135 ];
136 }
137
138 private static function title_from_prompt( string $prompt ): string {
139 $title = wp_trim_words( sanitize_text_field( $prompt ), 10, '' );
140 return '' !== $title ? $title : __( 'AI generated image', 'better-payment' );
141 }
142
143 private static function require_media(): void {
144 if ( ! function_exists( 'media_sideload_image' ) ) {
145 require_once ABSPATH . 'wp-admin/includes/media.php';
146 require_once ABSPATH . 'wp-admin/includes/file.php';
147 require_once ABSPATH . 'wp-admin/includes/image.php';
148 }
149 }
150 }
151