namespace = 'bdthemes/v1'; $this->rest_base = 'proxy'; add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] ); } /** * Register REST API routes */ public function register_rest_routes() { $permission = [ $this, 'permission_check' ]; // Pexels proxy register_rest_route( $this->namespace, $this->rest_base . '/pexels/search', [ 'methods' => 'GET', 'callback' => [ $this, 'proxy_pexels_search' ], 'permission_callback' => $permission, 'args' => [ 'query' => [ 'required' => true, 'type' => 'string' ], 'page' => [ 'default' => 1, 'type' => 'integer' ], 'per_page' => [ 'default' => 30, 'type' => 'integer' ], ], ] ); // Pixabay proxy register_rest_route( $this->namespace, $this->rest_base . '/pixabay/search', [ 'methods' => 'GET', 'callback' => [ $this, 'proxy_pixabay_search' ], 'permission_callback' => $permission, 'args' => [ 'query' => [ 'required' => true, 'type' => 'string' ], 'page' => [ 'default' => 1, 'type' => 'integer' ], 'per_page' => [ 'default' => 30, 'type' => 'integer' ], 'order' => [ 'default' => '', 'type' => 'string', 'enum' => [ '', 'popular', 'latest' ] ], 'image_type' => [ 'default' => '', 'type' => 'string', 'enum' => [ '', 'all', 'photo', 'illustration', 'vector' ] ], 'category' => [ 'default' => '', 'type' => 'string' ], 'colors' => [ 'default' => '', 'type' => 'string' ], 'orientation' => [ 'default' => '', 'type' => 'string', 'enum' => [ '', 'all', 'horizontal', 'vertical' ] ], ], ] ); // Unsplash proxy register_rest_route( $this->namespace, $this->rest_base . '/unsplash/search', [ 'methods' => 'GET', 'callback' => [ $this, 'proxy_unsplash_search' ], 'permission_callback' => $permission, 'args' => [ 'query' => [ 'required' => true, 'type' => 'string' ], 'page' => [ 'default' => 1, 'type' => 'integer' ], 'per_page' => [ 'default' => 30, 'type' => 'integer' ], ], ] ); // Giphy proxy register_rest_route( $this->namespace, $this->rest_base . '/giphy/search', [ 'methods' => 'GET', 'callback' => [ $this, 'proxy_giphy_search' ], 'permission_callback' => $permission, 'args' => [ 'query' => [ 'required' => true, 'type' => 'string' ], 'offset' => [ 'default' => 0, 'type' => 'integer' ], 'limit' => [ 'default' => 30, 'type' => 'integer' ], ], ] ); // Wikimedia Commons proxy register_rest_route( $this->namespace, $this->rest_base . '/wikimedia/search', [ 'methods' => 'GET', 'callback' => [ $this, 'proxy_wikimedia_search' ], 'permission_callback' => $permission, 'args' => [ 'query' => [ 'required' => true, 'type' => 'string' ], 'page' => [ 'default' => 1, 'type' => 'integer' ], 'per_page' => [ 'default' => 30, 'type' => 'integer' ], 'media_type' => [ 'default' => '', 'type' => 'string' ], 'sort' => [ 'default' => 'relevance', 'type' => 'string' ], ], ] ); } /** * Permission check for API proxy */ public function permission_check( $request ) { return current_user_can( 'edit_posts' ); } /** * Get API key for a provider */ private function get_provider_api_key( $provider ) { $key_option_map = [ 'pexels' => 'bdthemes_pexels_api_key', 'pixabay' => 'bdthemes_pixabay_api_key', 'unsplash' => 'bdthemes_unsplash_access_key', 'giphy' => 'bdthemes_giphy_api_key', ]; $default_key_map = [ 'pexels' => AI_IMAGE_PEXELS_DEFAULT_KEY, 'pixabay' => AI_IMAGE_PIXABAY_DEFAULT_KEY, 'unsplash' => AI_IMAGE_UNSPLASH_DEFAULT_KEY, 'giphy' => AI_IMAGE_GIPHY_DEFAULT_KEY, ]; if ( ! isset( $key_option_map[ $provider ] ) ) { return ''; } $custom_key = get_option( $key_option_map[ $provider ], '' ); $custom_key = is_string( $custom_key ) ? trim( $custom_key ) : ''; if ( ! empty( $custom_key ) ) { return $custom_key; } // Use default encrypted key if ( isset( $default_key_map[ $provider ] ) ) { return decrypt_key( $default_key_map[ $provider ] ); } return ''; } /** * Proxy Pexels search API */ public function proxy_pexels_search( \WP_REST_Request $request ) { $api_key = $this->get_provider_api_key( 'pexels' ); if ( empty( $api_key ) ) { return new \WP_Error( 'no_api_key', 'Pexels API key not configured', [ 'status' => 400 ] ); } $query = $request->get_param( 'query' ); $page = $request->get_param( 'page' ); $per_page = $request->get_param( 'per_page' ); // Use search endpoint if query provided, otherwise use curated endpoint $has_query = ! empty( trim( $query ) ); if ( $has_query ) { $api_url = add_query_arg( [ 'query' => $query, 'page' => $page, 'per_page' => $per_page, ], 'https://api.pexels.com/v1/search' ); } else { $api_url = add_query_arg( [ 'page' => $page, 'per_page' => $per_page, ], 'https://api.pexels.com/v1/curated' ); } $response = wp_remote_get( $api_url, [ 'headers' => [ 'Authorization' => $api_key, ], 'timeout' => 30, ] ); if ( is_wp_error( $response ) ) { return new \WP_Error( 'api_error', $response->get_error_message(), [ 'status' => 500 ] ); } $body = wp_remote_retrieve_body( $response ); $data = json_decode( $body, true ); return new \WP_REST_Response( $data, wp_remote_retrieve_response_code( $response ) ); } /** * Proxy Pixabay search API */ public function proxy_pixabay_search( \WP_REST_Request $request ) { $api_key = $this->get_provider_api_key( 'pixabay' ); if ( empty( $api_key ) ) { return new \WP_Error( 'no_api_key', 'Pixabay API key not configured', [ 'status' => 400 ] ); } $query = $request->get_param( 'query' ); $page = $request->get_param( 'page' ); $per_page = $request->get_param( 'per_page' ); $args = [ 'key' => $api_key, 'q' => $query, 'page' => $page, 'per_page' => $per_page, ]; // Pass the dashboard filters through to Pixabay. An empty value means // "All", which Pixabay expresses by leaving the parameter out. $categories = [ 'backgrounds', 'fashion', 'nature', 'science', 'education', 'feelings', 'health', 'people', 'religion', 'places', 'animals', 'industry', 'computer', 'food', 'sports', 'transportation', 'travel', 'buildings', 'business', 'music', ]; $colors = [ 'grayscale', 'transparent', 'red', 'orange', 'yellow', 'green', 'turquoise', 'blue', 'lilac', 'pink', 'white', 'gray', 'black', 'brown', ]; $order = $request->get_param( 'order' ); if ( ! empty( $order ) ) { $args['order'] = $order; } $image_type = $request->get_param( 'image_type' ); if ( ! empty( $image_type ) ) { $args['image_type'] = $image_type; } $category = $request->get_param( 'category' ); if ( in_array( $category, $categories, true ) ) { $args['category'] = $category; } $color = $request->get_param( 'colors' ); if ( in_array( $color, $colors, true ) ) { $args['colors'] = $color; } $orientation = $request->get_param( 'orientation' ); if ( ! empty( $orientation ) ) { $args['orientation'] = $orientation; } // add_query_arg() URL-encodes each value, so $query must not be encoded here. $api_url = add_query_arg( $args, 'https://pixabay.com/api/' ); $response = wp_remote_get( $api_url, [ 'timeout' => 30, ] ); if ( is_wp_error( $response ) ) { return new \WP_Error( 'api_error', $response->get_error_message(), [ 'status' => 500 ] ); } $body = wp_remote_retrieve_body( $response ); $data = json_decode( $body, true ); return new \WP_REST_Response( $data, wp_remote_retrieve_response_code( $response ) ); } /** * Proxy Unsplash search API */ public function proxy_unsplash_search( \WP_REST_Request $request ) { $api_key = $this->get_provider_api_key( 'unsplash' ); if ( empty( $api_key ) ) { return new \WP_Error( 'no_api_key', 'Unsplash API key not configured', [ 'status' => 400 ] ); } $query = $request->get_param( 'query' ); $page = $request->get_param( 'page' ); $per_page = $request->get_param( 'per_page' ); // Use search endpoint if query provided, otherwise use curated photos endpoint $has_query = ! empty( trim( $query ) ); if ( $has_query ) { $api_url = add_query_arg( [ 'query' => $query, 'page' => $page, 'per_page' => $per_page, ], 'https://api.unsplash.com/search/photos' ); } else { $api_url = add_query_arg( [ 'page' => $page, 'per_page' => $per_page, ], 'https://api.unsplash.com/photos' ); } $response = wp_remote_get( $api_url, [ 'headers' => [ 'Authorization' => 'Client-ID ' . $api_key, ], 'timeout' => 30, ] ); if ( is_wp_error( $response ) ) { return new \WP_Error( 'api_error', $response->get_error_message(), [ 'status' => 500 ] ); } $body = wp_remote_retrieve_body( $response ); $data = json_decode( $body, true ); // Normalize response format - curated endpoint returns array directly, search returns {results: []} if ( ! $has_query && is_array( $data ) ) { $data = [ 'results' => $data ]; } return new \WP_REST_Response( $data, wp_remote_retrieve_response_code( $response ) ); } /** * Proxy Giphy search API */ public function proxy_giphy_search( \WP_REST_Request $request ) { $api_key = $this->get_provider_api_key( 'giphy' ); if ( empty( $api_key ) ) { return new \WP_Error( 'no_api_key', 'Giphy API key not configured', [ 'status' => 400 ] ); } $query = $request->get_param( 'query' ); $offset = $request->get_param( 'offset' ); $limit = $request->get_param( 'limit' ); // Use search endpoint if query provided, otherwise use trending endpoint $has_query = ! empty( trim( $query ) ); if ( $has_query ) { $api_url = add_query_arg( [ 'api_key' => $api_key, 'q' => $query, 'offset' => $offset, 'limit' => $limit, ], 'https://api.giphy.com/v1/gifs/search' ); } else { $api_url = add_query_arg( [ 'api_key' => $api_key, 'offset' => $offset, 'limit' => $limit, ], 'https://api.giphy.com/v1/gifs/trending' ); } $response = wp_remote_get( $api_url, [ 'timeout' => 30, ] ); if ( is_wp_error( $response ) ) { return new \WP_Error( 'api_error', $response->get_error_message(), [ 'status' => 500 ] ); } $body = wp_remote_retrieve_body( $response ); $data = json_decode( $body, true ); return new \WP_REST_Response( $data, wp_remote_retrieve_response_code( $response ) ); } /** * Proxy Wikimedia Commons search API */ public function proxy_wikimedia_search( \WP_REST_Request $request ) { $query = trim( (string) $request->get_param( 'query' ) ); $page = max( 1, (int) $request->get_param( 'page' ) ); $per_page = max( 1, min( 50, (int) $request->get_param( 'per_page' ) ) ); $offset = ( $page - 1 ) * $per_page; $media_type = strtolower( trim( (string) $request->get_param( 'media_type' ) ) ); $sort = strtolower( trim( (string) $request->get_param( 'sort' ) ) ); $allowed_media_types = [ '', 'image', 'vector' ]; if ( ! in_array( $media_type, $allowed_media_types, true ) ) { $media_type = ''; } $allowed_sorts = [ 'relevance', 'newest', 'oldest' ]; if ( ! in_array( $sort, $allowed_sorts, true ) ) { $sort = 'relevance'; } $api_limit = min( 50, $per_page * 3 ); $gsr_search = $query; if ( $media_type === 'vector' ) { $gsr_search .= ' filetype:drawing'; } elseif ( $media_type === 'image' ) { $gsr_search .= ' filetype:bitmap'; } $api_url = add_query_arg( [ 'action' => 'query', 'generator' => 'search', 'gsrsearch' => $gsr_search, 'gsrnamespace' => 6, 'gsrlimit' => $api_limit, 'gsroffset' => $offset, 'prop' => 'imageinfo|info', 'inprop' => 'url', 'iiprop' => 'url|user|mime|timestamp|extmetadata', 'iiurlwidth' => 1200, 'format' => 'json', 'formatversion' => 2, 'origin' => '*', ], 'https://commons.wikimedia.org/w/api.php' ); $response = wp_remote_get( $api_url, [ 'timeout' => 30, ] ); if ( is_wp_error( $response ) ) { return new \WP_Error( 'api_error', $response->get_error_message(), [ 'status' => 500 ] ); } $body = wp_remote_retrieve_body( $response ); $data = json_decode( $body, true ); if ( ! is_array( $data ) ) { return new \WP_Error( 'api_error', 'Invalid Wikimedia API response.', [ 'status' => 500 ] ); } $pages = array(); if ( isset( $data['query']['pages'] ) && is_array( $data['query']['pages'] ) ) { $pages = $data['query']['pages']; } $results = array(); foreach ( $pages as $page_item ) { $image_info = null; if ( isset( $page_item['imageinfo'][0] ) && is_array( $page_item['imageinfo'][0] ) ) { $image_info = $page_item['imageinfo'][0]; } if ( ! $image_info || empty( $image_info['url'] ) ) { continue; } $title = isset( $page_item['title'] ) ? $page_item['title'] : ''; $title = preg_replace( '/^File:/', '', $title ); $author = isset( $image_info['user'] ) ? $image_info['user'] : ''; $mime = isset( $image_info['mime'] ) ? strtolower( (string) $image_info['mime'] ) : ''; $timestamp = isset( $image_info['timestamp'] ) ? (string) $image_info['timestamp'] : ''; if ( $media_type === 'vector' && $mime !== 'image/svg+xml' ) { continue; } if ( $media_type === 'image' && ( strpos( $mime, 'image/' ) !== 0 || in_array( $mime, [ 'image/svg+xml', 'image/gif' ], true ) ) ) { continue; } $description = ''; if ( isset( $image_info['extmetadata']['ImageDescription']['value'] ) ) { $description = wp_strip_all_tags( $image_info['extmetadata']['ImageDescription']['value'] ); } $results[] = array( 'id' => isset( $page_item['pageid'] ) ? (string) $page_item['pageid'] : md5( $image_info['url'] ), 'title' => $title, 'description' => $description, 'author' => $author, 'mime' => $mime, 'timestamp' => $timestamp, 'url' => $image_info['url'], 'thumbnail' => isset( $image_info['thumburl'] ) ? $image_info['thumburl'] : $image_info['url'], 'page_url' => isset( $page_item['fullurl'] ) ? $page_item['fullurl'] : '', ); } if ( $sort === 'newest' || $sort === 'oldest' ) { usort( $results, static function ( $a, $b ) use ( $sort ) { $time_a = isset( $a['timestamp'] ) ? strtotime( (string) $a['timestamp'] ) : 0; $time_b = isset( $b['timestamp'] ) ? strtotime( (string) $b['timestamp'] ) : 0; if ( $time_a === $time_b ) { return 0; } if ( $sort === 'newest' ) { return $time_b <=> $time_a; } return $time_a <=> $time_b; } ); } if ( count( $results ) > $per_page ) { $results = array_slice( $results, 0, $per_page ); } return new \WP_REST_Response( array( 'results' => $results ), 200 ); } } // Initialize the API Proxy controller new API_Proxy_REST_Controller();