$query, 'per_page' => $count, 'orientation' => $orientation, 'size' => $size, 'color' => $color ?: null, ]), 'https://api.pexels.com/v1/search'); $response = self::httpGet($endpoint, ['Authorization' => $key]); $photos = $response['photos'] ?? []; if (empty($photos)) return ['results' => [], 'total' => 0, 'query' => $query]; $results = []; foreach ($photos as $photo) { $item = [ 'pexels_id' => $photo['id'], 'pexels_url' => $photo['url'], 'photographer' => $photo['photographer'], 'photographer_url' => $photo['photographer_url'], 'avg_color' => $photo['avg_color'] ?? '', 'alt' => $photo['alt'] ?? $query, 'src' => $photo['src'], 'attachment_id' => null, 'wp_url' => null, ]; if ($import) { $img_url = $photo['src']['large2x'] ?? $photo['src']['large'] ?? $photo['src']['original']; $att = self::importUrlToMedia($img_url, $item['alt'], "Pexels: {$photo['photographer']}"); if ($att) { $item['attachment_id'] = $att['id']; $item['wp_url'] = $att['url']; } } $results[] = $item; } return ['query' => $query, 'total' => $response['total_results'] ?? count($results), 'results' => $results, 'attribution' => 'Photos provided by Pexels']; } public static function pexelsSearchVideos(array $args): array { $key = get_option(self::OPT_PEXELS_KEY, ''); if (empty($key)) throw new \RuntimeException('Pexels API key not configured. Go to WP Admin → AskTam MCP → Settings → AI Media API Keys.'); $query = sanitize_text_field($args['query'] ?? ''); $count = max(1, min(5, (int)($args['count'] ?? 2))); $orientation = sanitize_key($args['orientation'] ?? 'landscape'); $min_duration = (int)($args['min_duration'] ?? 0); $max_duration = (int)($args['max_duration'] ?? 60); if (empty($query)) throw new \RuntimeException('query is required.'); $endpoint = add_query_arg(array_filter([ 'query' => $query, 'per_page' => $count, 'orientation' => $orientation, 'min_duration' => $min_duration ?: null, 'max_duration' => $max_duration ?: null, ]), 'https://api.pexels.com/v1/videos/search'); $response = self::httpGet($endpoint, ['Authorization' => $key]); $videos = $response['videos'] ?? []; if (empty($videos)) return ['results' => [], 'total' => 0, 'query' => $query]; $results = []; foreach ($videos as $video) { $hd_file = null; $sd_file = null; foreach ($video['video_files'] ?? [] as $file) { if ($file['file_type'] !== 'video/mp4') continue; if ($file['quality'] === 'hd' && (!$hd_file || ($file['width'] ?? 0) > ($hd_file['width'] ?? 0))) $hd_file = $file; if ($file['quality'] === 'sd' && (!$sd_file || ($file['width'] ?? 0) > ($sd_file['width'] ?? 0))) $sd_file = $file; } $best = $hd_file ?? $sd_file; $thumb_url = $video['image'] ?? ($video['video_pictures'][0]['picture'] ?? null); $thumb_att = null; if ($thumb_url) $thumb_att = self::importUrlToMedia($thumb_url, $query . ' video thumbnail', 'Pexels Video Thumbnail'); $results[] = [ 'pexels_id' => $video['id'], 'pexels_url' => $video['url'], 'videographer' => $video['user']['name'] ?? '', 'videographer_url' => $video['user']['url'] ?? '', 'duration' => $video['duration'] ?? 0, 'width' => $video['width'] ?? 0, 'height' => $video['height'] ?? 0, 'mp4_url' => $best['link'] ?? '', 'mp4_quality' => $best['quality'] ?? '', 'mp4_width' => $best['width'] ?? 0, 'mp4_height' => $best['height'] ?? 0, 'thumbnail_url' => $thumb_url, 'thumbnail_attachment_id' => $thumb_att['id'] ?? null, 'thumbnail_wp_url' => $thumb_att['url'] ?? null, ]; } return ['query' => $query, 'total' => $response['total_results'] ?? count($results), 'results' => $results, 'usage_note' => 'Use mp4_url directly in Elementor video widget background. thumbnail_attachment_id as poster image.', 'attribution' => 'Videos provided by Pexels']; } public static function generateGptImage(array $args): array { $key = get_option(self::OPT_OPENAI_KEY, ''); if (empty($key)) throw new \RuntimeException('OpenAI API key not configured. Go to WP Admin → AskTam MCP → Settings → AI Media API Keys.'); $prompt = sanitize_textarea_field($args['prompt'] ?? ''); $model = sanitize_key($args['model'] ?? 'gpt-image-1'); $quality = sanitize_key($args['quality'] ?? 'medium'); $size = sanitize_text_field($args['size'] ?? '1536x1024'); $n = max(1, min(4, (int)($args['n'] ?? 1))); $filename = sanitize_file_name($args['filename'] ?? 'gpt-generated'); $style = sanitize_key($args['style'] ?? ''); if (empty($prompt)) throw new \RuntimeException('prompt is required.'); $body = array_filter(['model' => $model, 'prompt' => $prompt, 'n' => $n, 'size' => $size, 'quality' => $quality, 'style' => $style ?: null]); $response = self::httpPost('https://api.openai.com/v1/images/generations', $body, ['Authorization' => "Bearer {$key}", 'Content-Type' => 'application/json']); if (!isset($response['data'])) throw new \RuntimeException("OpenAI API error: " . ($response['error']['message'] ?? json_encode($response))); $results = []; foreach ($response['data'] as $i => $img) { $fname = count($response['data']) > 1 ? "{$filename}-{$i}" : $filename; $att = !empty($img['b64_json']) ? self::importBase64ToMedia($img['b64_json'], "{$fname}.png", $prompt, 'GPT Image') : (!empty($img['url']) ? self::importUrlToMedia($img['url'], $prompt, 'GPT Image') : null); if ($att) $results[] = ['attachment_id' => $att['id'], 'url' => $att['url'], 'prompt' => $prompt, 'model' => $model, 'quality' => $quality, 'size' => $size, 'source' => 'openai']; } return ['generated' => count($results), 'results' => $results, 'model' => $model, 'prompt' => $prompt]; } public static function generateGeminiImage(array $args): array { $key = get_option(self::OPT_GEMINI_KEY, ''); if (empty($key)) throw new \RuntimeException('Google Gemini API key not configured. Go to WP Admin → AskTam MCP → Settings → AI Media API Keys.'); $prompt = sanitize_textarea_field($args['prompt'] ?? ''); $model = sanitize_key($args['model'] ?? 'gemini-2.0-flash-preview-image-generation'); $aspect_ratio = sanitize_text_field($args['aspect_ratio'] ?? '16:9'); $n = max(1, min(4, (int)($args['n'] ?? 1))); $filename = sanitize_file_name($args['filename'] ?? 'gemini-generated'); if (empty($prompt)) throw new \RuntimeException('prompt is required.'); $endpoint = "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent?key={$key}"; $body = ['contents' => [['parts' => [['text' => $prompt]]]], 'generationConfig' => ['responseModalities' => ['TEXT', 'IMAGE'], 'candidateCount' => $n]]; $response = self::httpPost($endpoint, $body, ['Content-Type' => 'application/json']); if (isset($response['error'])) throw new \RuntimeException("Gemini API error: " . ($response['error']['message'] ?? json_encode($response['error']))); $results = []; $i = 0; foreach ($response['candidates'] ?? [] as $candidate) { foreach ($candidate['content']['parts'] ?? [] as $part) { if (!isset($part['inlineData'])) continue; $mime = $part['inlineData']['mimeType'] ?? 'image/png'; $fname = ($n > 1) ? "{$filename}-{$i}." . self::mimeToExt($mime) : "{$filename}." . self::mimeToExt($mime); $att = self::importBase64ToMedia($part['inlineData']['data'], $fname, $prompt, 'Gemini Image'); if ($att) $results[] = ['attachment_id' => $att['id'], 'url' => $att['url'], 'prompt' => $prompt, 'model' => $model, 'source' => 'gemini']; $i++; } } return ['generated' => count($results), 'results' => $results, 'model' => $model, 'prompt' => $prompt]; } public static function getAiMediaStatus(array $args): array { return [ 'pexels_configured' => !empty(get_option(self::OPT_PEXELS_KEY, '')), 'openai_configured' => !empty(get_option(self::OPT_OPENAI_KEY, '')), 'gemini_configured' => !empty(get_option(self::OPT_GEMINI_KEY, '')), 'pexels_key_hint' => self::keyHint(get_option(self::OPT_PEXELS_KEY, '')), 'openai_key_hint' => self::keyHint(get_option(self::OPT_OPENAI_KEY, '')), 'gemini_key_hint' => self::keyHint(get_option(self::OPT_GEMINI_KEY, '')), 'configure_at' => admin_url('admin.php?page=asktam-mcp-settings'), ]; } private static function importUrlToMedia(string $url, string $alt = '', string $title = ''): ?array { require_once ABSPATH . 'wp-admin/includes/media.php'; require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/image.php'; $tmp = download_url($url); if (is_wp_error($tmp)) return null; $basename = basename(parse_url($url, PHP_URL_PATH) ?? 'image.jpg'); $id = media_handle_sideload(['name' => $basename, 'tmp_name' => $tmp], 0, $title ?: $alt); @unlink($tmp); if (is_wp_error($id)) return null; if ($alt) update_post_meta($id, '_wp_attachment_image_alt', sanitize_text_field($alt)); if ($title) wp_update_post(['ID' => $id, 'post_title' => sanitize_text_field($title), 'post_excerpt' => sanitize_text_field($alt)]); return ['id' => $id, 'url' => wp_get_attachment_url($id)]; } private static function importBase64ToMedia(string $b64, string $filename, string $alt = '', string $title = ''): ?array { require_once ABSPATH . 'wp-admin/includes/media.php'; require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/image.php'; $data = base64_decode($b64); if (!$data) return null; $uploads = wp_upload_dir(); $tmp_path = $uploads['path'] . '/' . sanitize_file_name($filename); if (file_put_contents($tmp_path, $data) === false) return null; $id = media_handle_sideload(['name' => sanitize_file_name($filename), 'tmp_name' => $tmp_path], 0, $title ?: $alt); if (is_wp_error($id)) { @unlink($tmp_path); return null; } if ($alt) update_post_meta($id, '_wp_attachment_image_alt', sanitize_text_field($alt)); if ($title) wp_update_post(['ID' => $id, 'post_title' => sanitize_text_field($title)]); return ['id' => $id, 'url' => wp_get_attachment_url($id)]; } private static function httpGet(string $url, array $headers = []): array { $r = wp_remote_get($url, ['timeout' => 30, 'headers' => $headers]); if (is_wp_error($r)) throw new \RuntimeException($r->get_error_message()); $d = json_decode(wp_remote_retrieve_body($r), true); return is_array($d) ? $d : []; } private static function httpPost(string $url, array $body, array $headers = []): array { $r = wp_remote_post($url, ['timeout' => 60, 'headers' => $headers, 'body' => json_encode($body)]); if (is_wp_error($r)) throw new \RuntimeException($r->get_error_message()); $d = json_decode(wp_remote_retrieve_body($r), true); return is_array($d) ? $d : []; } private static function mimeToExt(string $mime): string { return match($mime) { 'image/jpeg' => 'jpg', 'image/webp' => 'webp', 'image/gif' => 'gif', default => 'png' }; } private static function keyHint(string $key): string { if (strlen($key) < 12) return $key ? '***' : ''; return substr($key, 0, 6) . str_repeat('•', max(4, strlen($key) - 10)) . substr($key, -4); } }