PluginProbe
WDesignKit – AI Templates, Widget Builder & MCP Workflow / 2.7.0
WDesignKit – AI Templates, Widget Builder & MCP Workflow v2.7.0
2.7.1 2.7.0 2.6.6 2.6.5 2.6.4 2.6.3 2.6.2 2.6.1 2.6.0 2.5.5 2.5.4 2.5.3 2.5.2 2.5.1 2.5.0 2.4.0 2.3.3 2.3.2 2.3.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 All 130 releases
wdesignkit / includes / abilities / templates / wdesignkit-save-template-image.php

wdesignkit-save-template-image.php in WDesignKit – AI Templates, Widget Builder & MCP Workflow 2.7.0, at includes/abilities/templates/wdesignkit-save-template-image.php

172 lines 7.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Ability: Set / upload a saved cloud template's preview thumbnail image.
4 *
5 * Wraps the save_images cloud endpoint (the manual "Upload image on server" feature,
6 * plugin handler wdkit_update_save_temp_image()). Works for ANY template_id — newly
7 * saved or already-existing — so it also covers updating an existing template's image.
8 */
9
10 declare(strict_types=1);
11
12 if (!defined('ABSPATH')) {
13 exit();
14 }
15
16 wp_register_ability('wdesignkit/save-template-image', [
17 'label' => __('Set WDesignKit Template Thumbnail', 'wdesignkit'),
18 'description' => __(
19 'Uploads/sets the preview thumbnail image for a saved WDesignKit cloud template (works for both newly-saved and existing templates). Provide the template_id plus the image either as a URL (fetched and uploaded) or inline base64. Wraps the same save_images cloud endpoint the manual "Upload image on server" UI uses. Requires cloud login and a paid plan (the cloud rejects image uploads on free/subscriber accounts).',
20 'wdesignkit',
21 ),
22 'category' => 'wdesignkit',
23 'input_schema' => [
24 'type' => 'object',
25 'properties' => [
26 'template_id' => [
27 'type' => 'integer',
28 'description' => 'The saved cloud template ID to set the thumbnail for (from wdesignkit/list-templates or the save-template response). New or existing.',
29 ],
30 'image_url' => [
31 'type' => 'string',
32 'description' => 'URL of the image to use as the thumbnail. Fetched server-side and uploaded. Used only when image_base64 is not provided.',
33 ],
34 'image_base64' => [
35 'type' => 'string',
36 'description' => 'Image supplied inline as base64 (raw base64 or a data:image/...;base64,... URI). Takes precedence over image_url.',
37 ],
38 'name' => [
39 'type' => 'string',
40 'description' => 'Optional image name. Defaults to "template-<template_id>" when omitted (the cloud requires a non-empty name).',
41 ],
42 'type' => [
43 'type' => 'string',
44 'description' => 'Asset type. Use "image" for a thumbnail (default). The cloud applies a different size cap per type and plan.',
45 'enum' => ['image', 'video', 'gif'],
46 ],
47 ],
48 'required' => ['template_id'],
49 'additionalProperties' => false,
50 ],
51 'output_schema' => [
52 'type' => 'object',
53 'properties' => [
54 'success' => ['type' => 'boolean'],
55 'message' => ['type' => 'string'],
56 'response' => ['type' => ['object', 'array']],
57 ],
58 ],
59 'execute_callback' => 'wdesignkit_mcp_save_template_image',
60 'permission_callback' => 'wdesignkit_mcp_permission_callback',
61 'meta' => [
62 'public' => true,
63 'show_in_rest' => true,
64 'mcp' => ['public' => true],
65 'annotations' => [
66 'instructions' => implode("\n", [
67 'Sets a saved template\'s preview thumbnail via the cloud save_images endpoint.',
68 'Requires WDesignKit cloud login AND a paid plan — the cloud rejects image uploads on free/subscriber accounts ("Image Upload Not Allowed").',
69 'The template must belong to the logged-in account (the cloud verifies ownership by template_id + user).',
70 'Provide the image as image_url (fetched + uploaded) or image_base64 (used directly). type defaults to "image".',
71 'Works for both new and existing templates — pass any owned template_id.',
72 ]),
73 'readonly' => false,
74 'destructive' => false,
75 'idempotent' => false,
76 ],
77 ],
78 ]);
79
80 function wdesignkit_mcp_save_template_image(array $input): array {
81 set_time_limit(90);
82
83 $auth = wdesignkit_mcp_template_get_auth();
84 if (empty($auth['logged_in'])) {
85 return ['success' => false, 'message' => $auth['message'] ?? 'Not logged in to WDesignKit cloud.'];
86 }
87
88 $template_id = (int) ($input['template_id'] ?? 0);
89 if ($template_id <= 0) {
90 return ['success' => false, 'message' => 'template_id is required and must be a positive integer.'];
91 }
92
93 // 'gif' is a distinct type server-side with its own size cap — coercing it to 'image'
94 // would measure it against the (smaller) image limit.
95 $type = sanitize_text_field((string) ($input['type'] ?? 'image'));
96 if (!in_array($type, ['image', 'video', 'gif'], true)) {
97 $type = 'image';
98 }
99
100 $name = sanitize_text_field((string) ($input['name'] ?? ''));
101 if ($name === '') {
102 $name = 'template-' . $template_id;
103 }
104
105 // Resolve the image bytes as base64 "content" — the cloud endpoint requires non-empty content.
106 $image_base64 = (string) ($input['image_base64'] ?? '');
107 $image_url = sanitize_url((string) ($input['image_url'] ?? ''));
108 $content = '';
109
110 if ($image_base64 !== '') {
111 $b64 = $image_base64;
112 if (preg_match('#^data:[^;]+;base64,(.*)$#s', $b64, $m)) {
113 $b64 = $m[1];
114 }
115 $b64 = preg_replace('/\s+/', '', (string) $b64);
116 if ($b64 === '' || base64_decode($b64, true) === false) {
117 return ['success' => false, 'message' => 'image_base64 is not valid base64.'];
118 }
119 $content = $b64;
120 } elseif ($image_url !== '') {
121 // wdesignkit_safe_remote_get, not wp_safe_remote_get: the URL can come from anywhere
122 // (including a page the model just read), and this fetch runs from inside the site's
123 // network. wp_safe_remote_get() only rejects a *literal* IP host and never resolves a
124 // hostname, so it does not stop a crafted image_url that points a DNS name at
125 // 169.254.169.254 or another internal address. wdesignkit_safe_remote_get() resolves
126 // the host first and blocks loopback/private/link-local (incl. the cloud metadata
127 // range) targets.
128 $img_resp = wdesignkit_safe_remote_get($image_url, ['timeout' => 30]);
129 if (is_wp_error($img_resp)) {
130 return ['success' => false, 'message' => 'Could not fetch image_url: ' . $img_resp->get_error_message()];
131 }
132
133 // Without a status check, a 404/403 HTML error page would be base64-encoded and
134 // uploaded as if it were the thumbnail.
135 $img_status = (int) wp_remote_retrieve_response_code($img_resp);
136 if (200 !== $img_status) {
137 return ['success' => false, 'message' => "image_url returned HTTP {$img_status} — expected an image."];
138 }
139
140 $body = (string) wp_remote_retrieve_body($img_resp);
141 if ($body === '') {
142 return ['success' => false, 'message' => 'image_url returned an empty response.'];
143 }
144
145 $img_mime = (string) wp_remote_retrieve_header($img_resp, 'content-type');
146 if ($img_mime !== '' && 0 !== strpos($img_mime, 'image/') && 0 !== strpos($img_mime, 'video/')) {
147 return ['success' => false, 'message' => "image_url returned '{$img_mime}', not an image or video."];
148 }
149
150 $content = base64_encode($body);
151 } else {
152 return ['success' => false, 'message' => 'Provide the image as image_url or image_base64.'];
153 }
154
155 // Mirrors wdkit_update_save_temp_image(): token, template_id, name, base64 content, type.
156 $args = [
157 'token' => $auth['token'],
158 'template_id' => $template_id,
159 'name' => $name,
160 'content' => $content,
161 'type' => $type,
162 ];
163
164 $response = wdesignkit_mcp_template_cloud_call('save_images', $args, 'form');
165
166 return [
167 'success' => !empty($response['success']),
168 'message' => $response['message'] ?? $response['massage'] ?? ($response['success'] ? 'Template image saved.' : 'Failed to save template image.'),
169 'response' => $response,
170 ];
171 }
172