PluginProbe
AI / 0.2.0
AI v0.2.0
1.3.0 1.2.0 1.1.0 1.0.2 1.0.1 1.0.0 0.9.0 trunk 0.1.1 0.2.0 0.2.1 0.3.0 0.3.1 0.4.0 0.4.1 0.5.0 0.6.0 0.7.0 0.8.0
ai / includes / Abilities / Image / Import_Base64_Image.php

Import_Base64_Image.php in AI 0.2.0, at includes/Abilities/Image/Import_Base64_Image.php

311 lines 8.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Base64 encoded image import WordPress Ability implementation.
4 *
5 * @package WordPress\AI
6 */
7
8 declare( strict_types=1 );
9
10 namespace WordPress\AI\Abilities\Image;
11
12 use Throwable;
13 use WP_Error;
14 use WordPress\AI\Abstracts\Abstract_Ability;
15 use WordPress\AiClient\Files\DTO\File;
16
17 /**
18 * Base64 encoded image import WordPress Ability.
19 *
20 * @since 0.2.0
21 */
22 class Import_Base64_Image extends Abstract_Ability {
23
24 /**
25 * {@inheritDoc}
26 *
27 * @since 0.2.0
28 */
29 protected function input_schema(): array {
30 return array(
31 'type' => 'object',
32 'properties' => array(
33 'data' => array(
34 'type' => 'string',
35 'sanitize_callback' => 'sanitize_text_field',
36 'description' => esc_html__( 'The base64 encoded image data to import into the media library.', 'ai' ),
37 ),
38 'filename' => array(
39 'type' => 'string',
40 'sanitize_callback' => 'sanitize_text_field',
41 'description' => esc_html__( 'The filename of the image.', 'ai' ),
42 ),
43 'title' => array(
44 'type' => 'string',
45 'sanitize_callback' => 'sanitize_text_field',
46 'description' => esc_html__( 'The title of the image.', 'ai' ),
47 ),
48 'description' => array(
49 'type' => 'string',
50 'sanitize_callback' => 'sanitize_text_field',
51 'description' => esc_html__( 'The description of the image.', 'ai' ),
52 ),
53 'alt_text' => array(
54 'type' => 'string',
55 'sanitize_callback' => 'sanitize_text_field',
56 'description' => esc_html__( 'The alt text of the image.', 'ai' ),
57 ),
58 'mime_type' => array(
59 'type' => 'string',
60 'sanitize_callback' => 'sanitize_text_field',
61 'description' => esc_html__( 'The MIME type of the image.', 'ai' ),
62 ),
63 ),
64 'required' => array( 'data' ),
65 );
66 }
67
68 /**
69 * {@inheritDoc}
70 *
71 * @since 0.2.0
72 */
73 protected function output_schema(): array {
74 return array(
75 'type' => 'object',
76 'properties' => array(
77 'image' => array(
78 'type' => 'object',
79 'description' => esc_html__( 'Imported image data.', 'ai' ),
80 'properties' => array(
81 'id' => array(
82 'type' => 'integer',
83 'description' => esc_html__( 'Attachment ID.', 'ai' ),
84 ),
85 'url' => array(
86 'type' => 'string',
87 'description' => esc_html__( 'Attachment URL.', 'ai' ),
88 ),
89 'filename' => array(
90 'type' => 'string',
91 'description' => esc_html__( 'Attachment filename.', 'ai' ),
92 ),
93 'title' => array(
94 'type' => 'string',
95 'description' => esc_html__( 'Attachment title.', 'ai' ),
96 ),
97 'description' => array(
98 'type' => 'string',
99 'description' => esc_html__( 'Attachment description.', 'ai' ),
100 ),
101 'alt_text' => array(
102 'type' => 'string',
103 'description' => esc_html__( 'Attachment alt text.', 'ai' ),
104 ),
105 ),
106 ),
107 ),
108 );
109 }
110
111 /**
112 * {@inheritDoc}
113 *
114 * @since 0.2.0
115 */
116 protected function execute_callback( $input ) {
117 // Default arguments.
118 $args = wp_parse_args(
119 $input,
120 array(
121 'filename' => 'ai-generated-image-' . time(),
122 'title' => '',
123 'description' => '',
124 'alt_text' => '',
125 'mime_type' => null,
126 ),
127 );
128
129 // Verify the data is a base64 encoded string.
130 try {
131 $file = new File( $input['data'], $args['mime_type'] );
132 } catch ( Throwable $t ) {
133 return new WP_Error(
134 'invalid_data',
135 esc_html__( 'The data is not a valid base64 encoded string.', 'ai' )
136 );
137 }
138
139 // Verify the data is a valid image.
140 if ( ! $file->isImage() ) {
141 return new WP_Error(
142 'invalid_data',
143 esc_html__( 'The data is not a valid image.', 'ai' )
144 );
145 }
146
147 // Get the base64 data.
148 $base64_data = $file->getBase64Data();
149
150 if ( empty( $base64_data ) ) {
151 return new WP_Error(
152 'no_base64_data',
153 esc_html__( 'No base64 data found in the provided input.', 'ai' )
154 );
155 }
156
157 // Try and import the image.
158 $result = $this->import_image(
159 $base64_data,
160 array(
161 'mime_type' => $file->getMimeType(),
162 'title' => $args['title'],
163 'description' => $args['description'],
164 'filename' => $args['filename'],
165 'alt_text' => $args['alt_text'],
166 )
167 );
168
169 // If we have an error, return it.
170 if ( is_wp_error( $result ) ) {
171 return $result;
172 }
173
174 // Return the image data in the format the Ability expects.
175 return array(
176 'image' => $result,
177 );
178 }
179
180 /**
181 * {@inheritDoc}
182 *
183 * @since 0.2.0
184 */
185 protected function permission_callback( $args ) {
186 // Ensure the user has permission to upload files.
187 if ( ! current_user_can( 'upload_files' ) ) {
188 return new WP_Error(
189 'insufficient_capabilities',
190 esc_html__( 'You do not have permission to import images.', 'ai' )
191 );
192 }
193
194 return true;
195 }
196
197 /**
198 * {@inheritDoc}
199 *
200 * @since 0.2.0
201 */
202 protected function meta(): array {
203 return array(
204 'show_in_rest' => true,
205 );
206 }
207
208 /**
209 * Imports an image from a base64 encoded string into the media library.
210 *
211 * @since 0.2.0
212 *
213 * @param string $data The base64 encoded image data to import into the media library.
214 * @param array<string, mixed> $args The arguments for the image import.
215 * - mime_type: The MIME type of the image (e.g., 'image/png', 'image/jpeg').
216 * - title: The title of the image.
217 * - description: The description of the image.
218 * - filename: The filename of the image.
219 * - alt_text: The alt text of the image.
220 * @return array<string, mixed>|\WP_Error The attachment data, or a WP_Error if there was an error.
221 */
222 protected function import_image( string $data, array $args = array() ) {
223 require_once ABSPATH . 'wp-admin/includes/file.php';
224 require_once ABSPATH . 'wp-admin/includes/media.php';
225 require_once ABSPATH . 'wp-admin/includes/image.php';
226
227 // Decode the base64 data.
228 $decoded_data = base64_decode( $data, true );
229
230 // Verify the data was decoded successfully.
231 if ( false === $decoded_data ) {
232 return new WP_Error(
233 'invalid_base64',
234 esc_html__( 'Failed to decode base64 image data.', 'ai' )
235 );
236 }
237
238 // Create a temporary file.
239 $temp_file = wp_tempnam( 'ai-image' );
240
241 // Write the decoded data to the temporary file.
242 $bytes_written = file_put_contents( $temp_file, $decoded_data ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_file_put_contents
243
244 if ( false === $bytes_written ) {
245 wp_delete_file( $temp_file );
246 return new WP_Error(
247 'write_failed',
248 esc_html__( 'Failed to write image data to temporary file.', 'ai' )
249 );
250 }
251
252 // Determine file extension from MIME type.
253 $extension = wp_get_default_extension_for_mime_type( $args['mime_type'] );
254
255 // Prepare file array for sideload.
256 $file_array = array(
257 'name' => sanitize_file_name( $args['filename'] ) . '.' . $extension,
258 'type' => $args['mime_type'],
259 'tmp_name' => $temp_file,
260 );
261
262 // Handle the sideload.
263 $attachment_id = media_handle_sideload(
264 $file_array,
265 0,
266 $args['description'],
267 array(
268 'post_title' => sanitize_text_field( $args['title'] ),
269 'post_content' => sanitize_text_field( $args['description'] ),
270 'post_mime_type' => $args['mime_type'],
271 'meta_input' => array(
272 '_wp_attachment_image_alt' => sanitize_text_field( $args['alt_text'] ),
273 ),
274 )
275 );
276
277 // Clean up temp file if it still exists.
278 if ( file_exists( $temp_file ) ) {
279 wp_delete_file( $temp_file );
280 }
281
282 // Ensure the import worked.
283 if ( is_wp_error( $attachment_id ) ) {
284 return $attachment_id;
285 }
286
287 // Get attachment data.
288 $attachment = get_post( $attachment_id );
289
290 if ( ! $attachment ) {
291 return new WP_Error(
292 'attachment_not_found',
293 esc_html__( 'Failed to retrieve attachment data.', 'ai' )
294 );
295 }
296
297 $attached_file = get_attached_file( $attachment_id );
298 $filename = $attached_file ? basename( $attached_file ) : '';
299
300 // Return attachment data.
301 return array(
302 'id' => $attachment_id,
303 'url' => wp_get_attachment_url( $attachment_id ),
304 'filename' => $filename,
305 'title' => $attachment->post_title,
306 'description' => $attachment->post_content,
307 'alt_text' => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ),
308 );
309 }
310 }
311