PluginProbe
Instant AI Image Generator – Create & Import Images / trunk
Instant AI Image Generator – Create & Import Images vtrunk
2.1.6 2.1.5 2.1.4 trunk 1.5.1 1.5.10 1.5.11 1.5.2 1.5.3 1.5.4 1.5.5 1.5.7 1.5.8 1.5.9 2.0.0 2.0.1 2.1.0 2.1.1 2.1.2 2.1.3
ai-image / admin / api-proxy.php

api-proxy.php in Instant AI Image Generator – Create & Import Images trunk, at admin/api-proxy.php

504 lines 15.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * API Proxy Endpoints
4 *
5 * Proxies all external API calls through WordPress backend to hide API keys from frontend
6 *
7 * @package BdThemes\AiImage
8 */
9
10 namespace BDT_AI_IMG;
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit;
14 }
15
16 /**
17 * API Proxy REST Controller
18 * Handles proxying of external API requests to hide API keys from frontend
19 */
20 class API_Proxy_REST_Controller extends \WP_REST_Controller {
21
22 public function __construct() {
23 $this->namespace = 'bdthemes/v1';
24 $this->rest_base = 'proxy';
25 add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] );
26 }
27
28 /**
29 * Register REST API routes
30 */
31 public function register_rest_routes() {
32 $permission = [ $this, 'permission_check' ];
33
34 // Pexels proxy
35 register_rest_route( $this->namespace, $this->rest_base . '/pexels/search', [
36 'methods' => 'GET',
37 'callback' => [ $this, 'proxy_pexels_search' ],
38 'permission_callback' => $permission,
39 'args' => [
40 'query' => [ 'required' => true, 'type' => 'string' ],
41 'page' => [ 'default' => 1, 'type' => 'integer' ],
42 'per_page' => [ 'default' => 30, 'type' => 'integer' ],
43 ],
44 ] );
45
46 // Pixabay proxy
47 register_rest_route( $this->namespace, $this->rest_base . '/pixabay/search', [
48 'methods' => 'GET',
49 'callback' => [ $this, 'proxy_pixabay_search' ],
50 'permission_callback' => $permission,
51 'args' => [
52 'query' => [ 'required' => true, 'type' => 'string' ],
53 'page' => [ 'default' => 1, 'type' => 'integer' ],
54 'per_page' => [ 'default' => 30, 'type' => 'integer' ],
55 'order' => [ 'default' => '', 'type' => 'string', 'enum' => [ '', 'popular', 'latest' ] ],
56 'image_type' => [ 'default' => '', 'type' => 'string', 'enum' => [ '', 'all', 'photo', 'illustration', 'vector' ] ],
57 'category' => [ 'default' => '', 'type' => 'string' ],
58 'colors' => [ 'default' => '', 'type' => 'string' ],
59 'orientation' => [ 'default' => '', 'type' => 'string', 'enum' => [ '', 'all', 'horizontal', 'vertical' ] ],
60 ],
61 ] );
62
63 // Unsplash proxy
64 register_rest_route( $this->namespace, $this->rest_base . '/unsplash/search', [
65 'methods' => 'GET',
66 'callback' => [ $this, 'proxy_unsplash_search' ],
67 'permission_callback' => $permission,
68 'args' => [
69 'query' => [ 'required' => true, 'type' => 'string' ],
70 'page' => [ 'default' => 1, 'type' => 'integer' ],
71 'per_page' => [ 'default' => 30, 'type' => 'integer' ],
72 ],
73 ] );
74
75 // Giphy proxy
76 register_rest_route( $this->namespace, $this->rest_base . '/giphy/search', [
77 'methods' => 'GET',
78 'callback' => [ $this, 'proxy_giphy_search' ],
79 'permission_callback' => $permission,
80 'args' => [
81 'query' => [ 'required' => true, 'type' => 'string' ],
82 'offset' => [ 'default' => 0, 'type' => 'integer' ],
83 'limit' => [ 'default' => 30, 'type' => 'integer' ],
84 ],
85 ] );
86
87 // Wikimedia Commons proxy
88 register_rest_route( $this->namespace, $this->rest_base . '/wikimedia/search', [
89 'methods' => 'GET',
90 'callback' => [ $this, 'proxy_wikimedia_search' ],
91 'permission_callback' => $permission,
92 'args' => [
93 'query' => [ 'required' => true, 'type' => 'string' ],
94 'page' => [ 'default' => 1, 'type' => 'integer' ],
95 'per_page' => [ 'default' => 30, 'type' => 'integer' ],
96 'media_type' => [ 'default' => '', 'type' => 'string' ],
97 'sort' => [ 'default' => 'relevance', 'type' => 'string' ],
98 ],
99 ] );
100 }
101
102 /**
103 * Permission check for API proxy
104 */
105 public function permission_check( $request ) {
106 return current_user_can( 'edit_posts' );
107 }
108
109 /**
110 * Get API key for a provider
111 */
112 private function get_provider_api_key( $provider ) {
113 $key_option_map = [
114 'pexels' => 'bdthemes_pexels_api_key',
115 'pixabay' => 'bdthemes_pixabay_api_key',
116 'unsplash' => 'bdthemes_unsplash_access_key',
117 'giphy' => 'bdthemes_giphy_api_key',
118 ];
119
120 $default_key_map = [
121 'pexels' => AI_IMAGE_PEXELS_DEFAULT_KEY,
122 'pixabay' => AI_IMAGE_PIXABAY_DEFAULT_KEY,
123 'unsplash' => AI_IMAGE_UNSPLASH_DEFAULT_KEY,
124 'giphy' => AI_IMAGE_GIPHY_DEFAULT_KEY,
125 ];
126
127 if ( ! isset( $key_option_map[ $provider ] ) ) {
128 return '';
129 }
130
131 $custom_key = get_option( $key_option_map[ $provider ], '' );
132 $custom_key = is_string( $custom_key ) ? trim( $custom_key ) : '';
133
134 if ( ! empty( $custom_key ) ) {
135 return $custom_key;
136 }
137
138 // Use default encrypted key
139 if ( isset( $default_key_map[ $provider ] ) ) {
140 return decrypt_key( $default_key_map[ $provider ] );
141 }
142
143 return '';
144 }
145
146 /**
147 * Proxy Pexels search API
148 */
149 public function proxy_pexels_search( \WP_REST_Request $request ) {
150 $api_key = $this->get_provider_api_key( 'pexels' );
151
152 if ( empty( $api_key ) ) {
153 return new \WP_Error( 'no_api_key', 'Pexels API key not configured', [ 'status' => 400 ] );
154 }
155
156 $query = $request->get_param( 'query' );
157 $page = $request->get_param( 'page' );
158 $per_page = $request->get_param( 'per_page' );
159
160 // Use search endpoint if query provided, otherwise use curated endpoint
161 $has_query = ! empty( trim( $query ) );
162
163 if ( $has_query ) {
164 $api_url = add_query_arg( [
165 'query' => $query,
166 'page' => $page,
167 'per_page' => $per_page,
168 ], 'https://api.pexels.com/v1/search' );
169 } else {
170 $api_url = add_query_arg( [
171 'page' => $page,
172 'per_page' => $per_page,
173 ], 'https://api.pexels.com/v1/curated' );
174 }
175
176 $response = wp_remote_get( $api_url, [
177 'headers' => [
178 'Authorization' => $api_key,
179 ],
180 'timeout' => 30,
181 ] );
182
183 if ( is_wp_error( $response ) ) {
184 return new \WP_Error( 'api_error', $response->get_error_message(), [ 'status' => 500 ] );
185 }
186
187 $body = wp_remote_retrieve_body( $response );
188 $data = json_decode( $body, true );
189
190 return new \WP_REST_Response( $data, wp_remote_retrieve_response_code( $response ) );
191 }
192
193 /**
194 * Proxy Pixabay search API
195 */
196 public function proxy_pixabay_search( \WP_REST_Request $request ) {
197 $api_key = $this->get_provider_api_key( 'pixabay' );
198
199 if ( empty( $api_key ) ) {
200 return new \WP_Error( 'no_api_key', 'Pixabay API key not configured', [ 'status' => 400 ] );
201 }
202
203 $query = $request->get_param( 'query' );
204 $page = $request->get_param( 'page' );
205 $per_page = $request->get_param( 'per_page' );
206
207 $args = [
208 'key' => $api_key,
209 'q' => $query,
210 'page' => $page,
211 'per_page' => $per_page,
212 ];
213
214 // Pass the dashboard filters through to Pixabay. An empty value means
215 // "All", which Pixabay expresses by leaving the parameter out.
216 $categories = [
217 'backgrounds', 'fashion', 'nature', 'science', 'education', 'feelings',
218 'health', 'people', 'religion', 'places', 'animals', 'industry',
219 'computer', 'food', 'sports', 'transportation', 'travel', 'buildings',
220 'business', 'music',
221 ];
222 $colors = [
223 'grayscale', 'transparent', 'red', 'orange', 'yellow', 'green',
224 'turquoise', 'blue', 'lilac', 'pink', 'white', 'gray', 'black', 'brown',
225 ];
226
227 $order = $request->get_param( 'order' );
228 if ( ! empty( $order ) ) {
229 $args['order'] = $order;
230 }
231
232 $image_type = $request->get_param( 'image_type' );
233 if ( ! empty( $image_type ) ) {
234 $args['image_type'] = $image_type;
235 }
236
237 $category = $request->get_param( 'category' );
238 if ( in_array( $category, $categories, true ) ) {
239 $args['category'] = $category;
240 }
241
242 $color = $request->get_param( 'colors' );
243 if ( in_array( $color, $colors, true ) ) {
244 $args['colors'] = $color;
245 }
246
247 $orientation = $request->get_param( 'orientation' );
248 if ( ! empty( $orientation ) ) {
249 $args['orientation'] = $orientation;
250 }
251
252 // add_query_arg() URL-encodes each value, so $query must not be encoded here.
253 $api_url = add_query_arg( $args, 'https://pixabay.com/api/' );
254
255 $response = wp_remote_get( $api_url, [
256 'timeout' => 30,
257 ] );
258
259 if ( is_wp_error( $response ) ) {
260 return new \WP_Error( 'api_error', $response->get_error_message(), [ 'status' => 500 ] );
261 }
262
263 $body = wp_remote_retrieve_body( $response );
264 $data = json_decode( $body, true );
265
266 return new \WP_REST_Response( $data, wp_remote_retrieve_response_code( $response ) );
267 }
268
269 /**
270 * Proxy Unsplash search API
271 */
272 public function proxy_unsplash_search( \WP_REST_Request $request ) {
273 $api_key = $this->get_provider_api_key( 'unsplash' );
274
275 if ( empty( $api_key ) ) {
276 return new \WP_Error( 'no_api_key', 'Unsplash API key not configured', [ 'status' => 400 ] );
277 }
278
279 $query = $request->get_param( 'query' );
280 $page = $request->get_param( 'page' );
281 $per_page = $request->get_param( 'per_page' );
282
283 // Use search endpoint if query provided, otherwise use curated photos endpoint
284 $has_query = ! empty( trim( $query ) );
285
286 if ( $has_query ) {
287 $api_url = add_query_arg( [
288 'query' => $query,
289 'page' => $page,
290 'per_page' => $per_page,
291 ], 'https://api.unsplash.com/search/photos' );
292 } else {
293 $api_url = add_query_arg( [
294 'page' => $page,
295 'per_page' => $per_page,
296 ], 'https://api.unsplash.com/photos' );
297 }
298
299 $response = wp_remote_get( $api_url, [
300 'headers' => [
301 'Authorization' => 'Client-ID ' . $api_key,
302 ],
303 'timeout' => 30,
304 ] );
305
306 if ( is_wp_error( $response ) ) {
307 return new \WP_Error( 'api_error', $response->get_error_message(), [ 'status' => 500 ] );
308 }
309
310 $body = wp_remote_retrieve_body( $response );
311 $data = json_decode( $body, true );
312
313 // Normalize response format - curated endpoint returns array directly, search returns {results: []}
314 if ( ! $has_query && is_array( $data ) ) {
315 $data = [ 'results' => $data ];
316 }
317
318 return new \WP_REST_Response( $data, wp_remote_retrieve_response_code( $response ) );
319 }
320
321 /**
322 * Proxy Giphy search API
323 */
324 public function proxy_giphy_search( \WP_REST_Request $request ) {
325 $api_key = $this->get_provider_api_key( 'giphy' );
326
327 if ( empty( $api_key ) ) {
328 return new \WP_Error( 'no_api_key', 'Giphy API key not configured', [ 'status' => 400 ] );
329 }
330
331 $query = $request->get_param( 'query' );
332 $offset = $request->get_param( 'offset' );
333 $limit = $request->get_param( 'limit' );
334
335 // Use search endpoint if query provided, otherwise use trending endpoint
336 $has_query = ! empty( trim( $query ) );
337
338 if ( $has_query ) {
339 $api_url = add_query_arg( [
340 'api_key' => $api_key,
341 'q' => $query,
342 'offset' => $offset,
343 'limit' => $limit,
344 ], 'https://api.giphy.com/v1/gifs/search' );
345 } else {
346 $api_url = add_query_arg( [
347 'api_key' => $api_key,
348 'offset' => $offset,
349 'limit' => $limit,
350 ], 'https://api.giphy.com/v1/gifs/trending' );
351 }
352
353 $response = wp_remote_get( $api_url, [
354 'timeout' => 30,
355 ] );
356
357 if ( is_wp_error( $response ) ) {
358 return new \WP_Error( 'api_error', $response->get_error_message(), [ 'status' => 500 ] );
359 }
360
361 $body = wp_remote_retrieve_body( $response );
362 $data = json_decode( $body, true );
363
364 return new \WP_REST_Response( $data, wp_remote_retrieve_response_code( $response ) );
365 }
366
367 /**
368 * Proxy Wikimedia Commons search API
369 */
370 public function proxy_wikimedia_search( \WP_REST_Request $request ) {
371 $query = trim( (string) $request->get_param( 'query' ) );
372 $page = max( 1, (int) $request->get_param( 'page' ) );
373 $per_page = max( 1, min( 50, (int) $request->get_param( 'per_page' ) ) );
374 $offset = ( $page - 1 ) * $per_page;
375
376 $media_type = strtolower( trim( (string) $request->get_param( 'media_type' ) ) );
377 $sort = strtolower( trim( (string) $request->get_param( 'sort' ) ) );
378
379 $allowed_media_types = [ '', 'image', 'vector' ];
380 if ( ! in_array( $media_type, $allowed_media_types, true ) ) {
381 $media_type = '';
382 }
383
384 $allowed_sorts = [ 'relevance', 'newest', 'oldest' ];
385 if ( ! in_array( $sort, $allowed_sorts, true ) ) {
386 $sort = 'relevance';
387 }
388
389 $api_limit = min( 50, $per_page * 3 );
390 $gsr_search = $query;
391 if ( $media_type === 'vector' ) {
392 $gsr_search .= ' filetype:drawing';
393 } elseif ( $media_type === 'image' ) {
394 $gsr_search .= ' filetype:bitmap';
395 }
396
397 $api_url = add_query_arg( [
398 'action' => 'query',
399 'generator' => 'search',
400 'gsrsearch' => $gsr_search,
401 'gsrnamespace' => 6,
402 'gsrlimit' => $api_limit,
403 'gsroffset' => $offset,
404 'prop' => 'imageinfo|info',
405 'inprop' => 'url',
406 'iiprop' => 'url|user|mime|timestamp|extmetadata',
407 'iiurlwidth' => 1200,
408 'format' => 'json',
409 'formatversion' => 2,
410 'origin' => '*',
411 ], 'https://commons.wikimedia.org/w/api.php' );
412
413 $response = wp_remote_get( $api_url, [
414 'timeout' => 30,
415 ] );
416
417 if ( is_wp_error( $response ) ) {
418 return new \WP_Error( 'api_error', $response->get_error_message(), [ 'status' => 500 ] );
419 }
420
421 $body = wp_remote_retrieve_body( $response );
422 $data = json_decode( $body, true );
423
424 if ( ! is_array( $data ) ) {
425 return new \WP_Error( 'api_error', 'Invalid Wikimedia API response.', [ 'status' => 500 ] );
426 }
427
428 $pages = array();
429 if ( isset( $data['query']['pages'] ) && is_array( $data['query']['pages'] ) ) {
430 $pages = $data['query']['pages'];
431 }
432
433 $results = array();
434 foreach ( $pages as $page_item ) {
435 $image_info = null;
436 if ( isset( $page_item['imageinfo'][0] ) && is_array( $page_item['imageinfo'][0] ) ) {
437 $image_info = $page_item['imageinfo'][0];
438 }
439
440 if ( ! $image_info || empty( $image_info['url'] ) ) {
441 continue;
442 }
443
444 $title = isset( $page_item['title'] ) ? $page_item['title'] : '';
445 $title = preg_replace( '/^File:/', '', $title );
446
447 $author = isset( $image_info['user'] ) ? $image_info['user'] : '';
448 $mime = isset( $image_info['mime'] ) ? strtolower( (string) $image_info['mime'] ) : '';
449 $timestamp = isset( $image_info['timestamp'] ) ? (string) $image_info['timestamp'] : '';
450
451 if ( $media_type === 'vector' && $mime !== 'image/svg+xml' ) {
452 continue;
453 }
454
455 if ( $media_type === 'image' && ( strpos( $mime, 'image/' ) !== 0 || in_array( $mime, [ 'image/svg+xml', 'image/gif' ], true ) ) ) {
456 continue;
457 }
458
459 $description = '';
460 if ( isset( $image_info['extmetadata']['ImageDescription']['value'] ) ) {
461 $description = wp_strip_all_tags( $image_info['extmetadata']['ImageDescription']['value'] );
462 }
463
464 $results[] = array(
465 'id' => isset( $page_item['pageid'] ) ? (string) $page_item['pageid'] : md5( $image_info['url'] ),
466 'title' => $title,
467 'description' => $description,
468 'author' => $author,
469 'mime' => $mime,
470 'timestamp' => $timestamp,
471 'url' => $image_info['url'],
472 'thumbnail' => isset( $image_info['thumburl'] ) ? $image_info['thumburl'] : $image_info['url'],
473 'page_url' => isset( $page_item['fullurl'] ) ? $page_item['fullurl'] : '',
474 );
475 }
476
477 if ( $sort === 'newest' || $sort === 'oldest' ) {
478 usort( $results, static function ( $a, $b ) use ( $sort ) {
479 $time_a = isset( $a['timestamp'] ) ? strtotime( (string) $a['timestamp'] ) : 0;
480 $time_b = isset( $b['timestamp'] ) ? strtotime( (string) $b['timestamp'] ) : 0;
481
482 if ( $time_a === $time_b ) {
483 return 0;
484 }
485
486 if ( $sort === 'newest' ) {
487 return $time_b <=> $time_a;
488 }
489
490 return $time_a <=> $time_b;
491 } );
492 }
493
494 if ( count( $results ) > $per_page ) {
495 $results = array_slice( $results, 0, $per_page );
496 }
497
498 return new \WP_REST_Response( array( 'results' => $results ), 200 );
499 }
500 }
501
502 // Initialize the API Proxy controller
503 new API_Proxy_REST_Controller();
504