PluginProbe
WDesignKit – AI Templates, Widget Builder & MCP Workflow / 2.7.0
WDesignKit – AI Templates, Widget Builder & MCP Workflow v2.7.0
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 1.0.16 All 129 releases
wdesignkit / includes / abilities / code-snippets / wdesignkit-save-snippet.php

wdesignkit-save-snippet.php in WDesignKit – AI Templates, Widget Builder & MCP Workflow 2.7.0, at includes/abilities/code-snippets/wdesignkit-save-snippet.php

413 lines 20.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Abilities: Save/upload a snippet to the cloud, or update local snippet details.
4 */
5
6 declare(strict_types=1);
7
8 if (!defined('ABSPATH')) {
9 exit();
10 }
11
12 wp_register_ability('wdesignkit/save-snippet', [
13 'label' => __('Save / Upload WDesignKit Snippet to Cloud', 'wdesignkit'),
14 'description' => __(
15 'Reads a local nxt-code-snippet WordPress post by its post ID, packages all its meta fields, and uploads the snippet to the WDesignKit cloud marketplace. Use stype "new" for a first-time upload or "existing" with a snippet_id to update a previously uploaded snippet. Requires cloud login.',
16 'wdesignkit',
17 ),
18 'category' => 'wdesignkit',
19 'input_schema' => [
20 'type' => 'object',
21 'properties' => [
22 'post_id' => [
23 'type' => 'integer',
24 'description' => 'WordPress post ID of the local nxt-code-snippet to upload. Use for legacy post-based storage.',
25 ],
26 'file_id' => [
27 'type' => 'string',
28 'description' => 'File-based snippet key (from wdesignkit/list-local-snippets or wdesignkit/download-snippet). Use when Nexter Pro file-based storage is active. Provide either file_id or post_id.',
29 ],
30 'stype' => [
31 'type' => 'string',
32 'description' => '"new" to create a new cloud record, "existing" to update an already-uploaded snippet.',
33 'enum' => ['new', 'existing'],
34 ],
35 'title' => [
36 'type' => 'string',
37 'description' => 'Display title for the snippet. Required when stype is "new".',
38 ],
39 'description' => [
40 'type' => 'string',
41 'description' => 'Snippet description / notes. Overrides the local nxt-code-note value if provided.',
42 ],
43 'plugin' => [
44 'type' => 'string',
45 'description' => 'Comma-separated required-plugin IDs to associate with the snippet.',
46 ],
47 'category' => [
48 'type' => 'string',
49 'description' => 'Category term ID to assign to the snippet.',
50 ],
51 'snippet_id' => [
52 'type' => 'string',
53 'description' => 'Cloud snippet ID to update. Required when stype is "existing".',
54 ],
55 'w_unique' => [
56 'type' => 'string',
57 'description' => 'Unique identifier for the snippet. Used when stype is "new".',
58 ],
59 ],
60 'additionalProperties' => false,
61 ],
62 'output_schema' => [
63 'type' => 'object',
64 'properties' => [
65 'success' => ['type' => 'boolean'],
66 'message' => ['type' => 'string'],
67 'response' => ['type' => ['object', 'array']],
68 ],
69 ],
70 'execute_callback' => 'wdesignkit_mcp_save_snippet',
71 'permission_callback' => 'wdesignkit_mcp_permission_callback',
72 'meta' => [
73 'public' => true,
74 'show_in_rest' => true,
75 'mcp' => ['public' => true],
76 'annotations' => [
77 'instructions' => implode("\n", [
78 'Uploads a local snippet to the WDesignKit cloud marketplace. Requires cloud login.',
79 'Provide either file_id (Nexter Pro file-based storage) or post_id (legacy post-based storage).',
80 'Get file_id from wdesignkit/list-local-snippets or wdesignkit/download-snippet (file_id field).',
81 'stype "new": first-time upload. title is required.',
82 'stype "existing": update. snippet_id (cloud ID) is required — get it from wdesignkit/get-existing-snippet.',
83 'All snippet code and meta is read automatically from local storage (file-based or post-based).',
84 'After uploading, use wdesignkit/get-my-snippets to confirm the snippet appears in your library.',
85 ]),
86 'readonly' => false,
87 'destructive' => false,
88 'idempotent' => false,
89 ],
90 ],
91 ]);
92
93 wp_register_ability('wdesignkit/update-snippet-details', [
94 'label' => __('Update WDesignKit Local Snippet Details', 'wdesignkit'),
95 'description' => __(
96 'Updates the title and/or description of a local code snippet. Works with both Nexter Pro file-based storage (provide file_id) and legacy WordPress post-based storage (provide post_id). This is a local-only operation — no cloud call is made. To sync the updated details to the cloud, follow up with wdesignkit/save-snippet using stype "existing".',
97 'wdesignkit',
98 ),
99 'category' => 'wdesignkit',
100 'input_schema' => [
101 'type' => 'object',
102 'properties' => [
103 'post_id' => [
104 'type' => 'integer',
105 'description' => 'WordPress post ID of the local nxt-code-snippet to update. Use for legacy post-based storage.',
106 ],
107 'file_id' => [
108 'type' => 'string',
109 'description' => 'File-based snippet key (from wdesignkit/list-local-snippets). Use for Nexter Pro file-based storage.',
110 ],
111 'title' => [
112 'type' => 'string',
113 'description' => 'New title for the snippet.',
114 ],
115 'description' => [
116 'type' => 'string',
117 'description' => 'New description / notes for the snippet.',
118 ],
119 ],
120 'additionalProperties' => false,
121 ],
122 'output_schema' => [
123 'type' => 'object',
124 'properties' => [
125 'success' => ['type' => 'boolean'],
126 'message' => ['type' => 'string'],
127 'data' => ['type' => 'object'],
128 ],
129 ],
130 'execute_callback' => 'wdesignkit_mcp_update_snippet_details',
131 'permission_callback' => 'wdesignkit_mcp_permission_callback',
132 'meta' => [
133 'public' => true,
134 'show_in_rest' => true,
135 'mcp' => ['public' => true],
136 'annotations' => [
137 'instructions' => implode("\n", [
138 'Updates the title and/or description of a local snippet — no cloud call is made.',
139 'Provide either file_id (Nexter Pro file-based storage) or post_id (legacy post-based storage).',
140 'Get file_id from wdesignkit/list-local-snippets.',
141 'To push the updated details to the cloud, call wdesignkit/save-snippet with stype "existing" afterwards.',
142 ]),
143 'readonly' => false,
144 'destructive' => false,
145 'idempotent' => true,
146 ],
147 ],
148 ]);
149
150 function wdesignkit_mcp_save_snippet(array $input): array {
151 // Timeout guard: cloud snippet upload uses the default 60s HTTP timeout.
152 set_time_limit(90);
153
154 if (!defined('WDKIT_SERVER_API_URL') || !defined('WDKIT_VERSION')) {
155 return ['success' => false, 'message' => 'WDesignKit plugin is not active.'];
156 }
157
158 $auth = wdesignkit_mcp_template_get_auth();
159 if (empty($auth['logged_in'])) {
160 return ['success' => false, 'message' => $auth['message'] ?? 'Not logged in to WDesignKit cloud.'];
161 }
162
163 $post_id = (int) ($input['post_id'] ?? 0);
164 $file_id = sanitize_text_field((string) ($input['file_id'] ?? ''));
165 $stype = sanitize_text_field((string) ($input['stype'] ?? 'new'));
166 $title = sanitize_text_field((string) ($input['title'] ?? ''));
167 $description = sanitize_text_field((string) ($input['description'] ?? ''));
168 $plugin = sanitize_text_field((string) ($input['plugin'] ?? ''));
169 $category = sanitize_text_field((string) ($input['category'] ?? ''));
170 $snippet_id = sanitize_text_field((string) ($input['snippet_id'] ?? ''));
171 $w_unique = sanitize_text_field((string) ($input['w_unique'] ?? ''));
172
173 if ($post_id <= 0 && $file_id === '') {
174 return ['success' => false, 'message' => 'Provide either file_id (file-based/Nexter Pro) or post_id (post-based).'];
175 }
176
177 // Try file-based storage: prefer explicit file_id; fall back to string cast of post_id.
178 $file_code_list = null;
179 if (class_exists('Nexter_Code_Snippets_File_Based')) {
180 $file_based = new \Nexter_Code_Snippets_File_Based();
181 $lookup_key = ($file_id !== '') ? $file_id : (string) $post_id;
182 $file_code_list = $file_based->getSnippetData($lookup_key);
183
184 // If an explicit file_id was given but the snippet wasn't found, fail fast
185 // instead of silently falling through to a zero-post_id WP meta lookup.
186 if ($file_id !== '' && (empty($file_code_list) || !is_array($file_code_list))) {
187 return ['success' => false, 'message' => "No snippet found with file_id '{$file_id}'.", 'response' => (object) []];
188 }
189 }
190
191 if (!empty($file_code_list) && is_array($file_code_list)) {
192 $type = strtolower((string) ($file_code_list['type'] ?? ''));
193 $lang_code = (string) ($file_code_list['langCode'] ?? '');
194 if ($type === 'php' && $lang_code !== '' && !preg_match('/^\s*<\?php/i', $lang_code)) {
195 $lang_code = "<?php\n" . $lang_code;
196 }
197
198 $get_data = [
199 'id' => $post_id,
200 'name' => $file_code_list['name'] ?? '',
201 'description' => ($description !== '') ? $description : ($file_code_list['description'] ?? ''),
202 'type' => $type,
203 'post_type' => $file_code_list['post_type'] ?? '',
204 'tags' => $file_code_list['tags'] ?? '',
205 'codeExecute' => $file_code_list['codeExecute'] ?? '',
206 'status' => $file_code_list['status'] ?? '',
207 'langCode' => $lang_code,
208 'htmlHooks' => $file_code_list['htmlHooks'] ?? '',
209 'hooksPriority' => $file_code_list['hooksPriority'] ?? '',
210 'include_data' => $file_code_list['include_data'] ?? '',
211 'exclude_data' => $file_code_list['exclude_data'] ?? '',
212 'in_sub_data' => $file_code_list['in_sub_data'] ?? '',
213 'ex_sub_data' => $file_code_list['ex_sub_data'] ?? '',
214 'word_count' => $file_code_list['word_count'] ?? '',
215 'word_interval' => $file_code_list['word_interval'] ?? '',
216 'post_number' => $file_code_list['post_number'] ?? '',
217 'css_selector' => $file_code_list['css_selector'] ?? '',
218 'element_index' => $file_code_list['element_index'] ?? '',
219 'insertion' => $file_code_list['insertion'] ?? '',
220 'location' => $file_code_list['location'] ?? '',
221 'customname' => $file_code_list['customname'] ?? '',
222 'compresscode' => $file_code_list['compresscode'] ?? '',
223 'startDate' => $file_code_list['startDate'] ?? '',
224 'endDate' => $file_code_list['endDate'] ?? '',
225 'shortcodeattr' => $file_code_list['shortcodeattr'] ?? '',
226 'smart_conditional_logic' => $file_code_list['smart_conditional_logic'] ?? '',
227 'php_hidden_execute' => $file_code_list['php_hidden_execute'] ?? '',
228 ];
229 } else {
230 $type = (string) get_post_meta($post_id, 'nxt-code-type', true);
231 $get_data = [
232 'id' => $post_id,
233 'name' => get_the_title($post_id),
234 'description' => ($description !== '') ? $description : ((string) get_post_meta($post_id, 'nxt-code-note', true)),
235 'type' => $type,
236 'post_type' => (string) get_post_type($post_id),
237 'tags' => get_post_meta($post_id, 'nxt-code-tags', true),
238 'codeExecute' => (string) get_post_meta($post_id, 'nxt-code-execute', true),
239 'status' => (string) get_post_meta($post_id, 'nxt-code-status', true),
240 'langCode' => (string) get_post_meta($post_id, 'nxt-' . $type . '-code', true),
241 'htmlHooks' => (string) get_post_meta($post_id, 'nxt-code-html-hooks', true),
242 'hooksPriority' => (string) get_post_meta($post_id, 'nxt-code-hooks-priority', true),
243 'include_data' => (string) get_post_meta($post_id, 'nxt-add-display-rule', true),
244 'exclude_data' => (string) get_post_meta($post_id, 'nxt-exclude-display-rule', true),
245 'in_sub_data' => (string) get_post_meta($post_id, 'nxt-in-sub-rule', true),
246 'ex_sub_data' => (string) get_post_meta($post_id, 'nxt-ex-sub-rule', true),
247 'word_count' => (string) get_post_meta($post_id, 'nxt-insert-word-count', true),
248 'word_interval' => (string) get_post_meta($post_id, 'nxt-insert-word-interval', true),
249 'post_number' => (string) get_post_meta($post_id, 'nxt-post-number', true),
250 'css_selector' => (string) get_post_meta($post_id, 'nxt-css-selector', true),
251 'element_index' => (string) get_post_meta($post_id, 'nxt-element-index', true),
252 'insertion' => (string) get_post_meta($post_id, 'nxt-code-insertion', true),
253 'location' => (string) get_post_meta($post_id, 'nxt-code-location', true),
254 'customname' => (string) get_post_meta($post_id, 'nxt-code-customname', true),
255 'compresscode' => (string) get_post_meta($post_id, 'nxt-code-compresscode', true),
256 'startDate' => (string) get_post_meta($post_id, 'nxt-code-startdate', true),
257 'endDate' => (string) get_post_meta($post_id, 'nxt-code-enddate', true),
258 'shortcodeattr' => (string) get_post_meta($post_id, 'nxt-code-shortcodeattr', true),
259 'smart_conditional_logic' => (string) get_post_meta($post_id, 'nxt-smart-conditional-logic', true),
260 'php_hidden_execute' => (string) get_post_meta($post_id, 'nxt-code-php-hidden-execute', true),
261 ];
262 }
263
264 $save_type = ($stype === 'existing') ? 'exist' : 'new';
265
266 // Guard: updating an existing cloud snippet requires a snippet_id.
267 if ($save_type === 'exist' && $snippet_id === '') {
268 return ['success' => false, 'message' => 'snippet_id is required when stype is "existing". Get it from wdesignkit/get-my-snippets or wdesignkit/get-existing-snippet.'];
269 }
270
271 $array_data = [
272 'token' => $auth['token'],
273 'type' => $save_type, // Must match the URL path segment ('exist' or 'new').
274 'title' => $title,
275 'plugin_id' => $plugin,
276 'terms_id' => $category,
277 'data' => wp_json_encode([
278 'generator' => 'WDesignKit Export v' . WDKIT_VERSION,
279 'date_created' => current_time('Y-m-d H:i'),
280 'snippets' => [$get_data],
281 ]),
282 ];
283
284 if ($save_type === 'exist') {
285 $array_data['id'] = $snippet_id;
286 $array_data['post_content'] = $description; // Sync description on updates too.
287 }
288
289 if ($save_type === 'new') {
290 $array_data['w_unique'] = $w_unique;
291 $array_data['code_type'] = $type;
292 $array_data['post_content'] = $description;
293 }
294
295 $cloud = wdesignkit_mcp_template_cloud_call('snippet/save/' . $save_type, $array_data, 'form');
296
297 return [
298 'success' => !empty($cloud['success']),
299 'message' => $cloud['message'] ?? $cloud['massage'] ?? ($cloud['success'] ? 'Snippet saved.' : 'Failed to save snippet.'),
300 'response' => $cloud,
301 ];
302 }
303
304 function wdesignkit_mcp_update_snippet_details(array $input): array {
305 // Timeout guard: local file write + optional WP post update.
306 set_time_limit(30);
307
308 $post_id = (int) ($input['post_id'] ?? 0);
309 $file_id = sanitize_text_field((string) ($input['file_id'] ?? ''));
310 $new_title = isset($input['title']) ? sanitize_text_field((string) $input['title']) : null;
311 $description = isset($input['description']) ? sanitize_text_field((string) $input['description']) : null;
312
313 if ($post_id <= 0 && $file_id === '') {
314 return ['success' => false, 'message' => 'Provide either file_id (file-based/Nexter Pro) or post_id (post-based).'];
315 }
316
317 if ($new_title === null && $description === null) {
318 return ['success' => false, 'message' => 'Provide at least one of title or description to update.'];
319 }
320
321 // ── File-based path (Nexter Pro) ─────────────────────────────────────────
322 $lookup_key = ($file_id !== '') ? $file_id : (string) $post_id;
323 if (class_exists('Nexter_Code_Snippets_File_Based')) {
324 $file_based = new \Nexter_Code_Snippets_File_Based();
325 $snippet_data = $file_based->get_all_snippets([], $lookup_key, true);
326
327 if (!empty($snippet_data) && !empty($snippet_data['file']) && !empty($snippet_data['meta'])) {
328 $meta = $snippet_data['meta'];
329 $code = (string) ($snippet_data['code'] ?? '');
330 $file_path = $snippet_data['file'];
331
332 $changed = [];
333
334 if ($new_title !== null && $new_title !== '') {
335 $meta['name'] = $new_title;
336 $changed[] = 'title';
337 }
338 if ($description !== null) {
339 $meta['description'] = $description;
340 $changed[] = 'description';
341 }
342
343 $meta['updated_at'] = current_time('mysql');
344
345 // Reconstruct file with updated docblock.
346 // Format matches Nexter_Code_Snippets_Import_Data::parseInputMeta output.
347 $doc_block = '<?php' . PHP_EOL . '// <Internal Start>' . PHP_EOL . '/*' . PHP_EOL . '*';
348 foreach ($meta as $key => $value) {
349 if (is_array($value)) {
350 $value = wp_json_encode($value);
351 }
352 $doc_block .= PHP_EOL . '* @' . sanitize_key($key) . ': ' . $value;
353 }
354 $doc_block .= PHP_EOL . '*/' . PHP_EOL . '?>' . PHP_EOL . '<?php if (!defined("ABSPATH")) { return;} // <Internal End> ?>' . PHP_EOL;
355
356 if (@file_put_contents($file_path, $doc_block . $code) === false) {
357 return ['success' => false, 'message' => 'Failed to write updated snippet file.', 'data' => (object) []];
358 }
359
360 // Rebuild index so list-local-snippets sees the new metadata immediately.
361 $file_based->snippetIndexData();
362
363 return [
364 'success' => true,
365 'message' => 'Snippet details updated (file-based): ' . implode(', ', $changed),
366 'data' => [
367 'file_id' => $lookup_key,
368 'title' => $meta['name'] ?? '',
369 'description' => $meta['description'] ?? '',
370 ],
371 ];
372 }
373 }
374
375 // ── Post-based fallback ──────────────────────────────────────────────────
376 if ($post_id <= 0) {
377 return ['success' => false, 'message' => "No snippet found with file_id '{$file_id}'.", 'data' => (object) []];
378 }
379
380 $post = get_post($post_id);
381 if (!$post) {
382 $hint = class_exists('Nexter_Code_Snippets_File_Based')
383 ? " This site uses Nexter Pro file-based snippet storage — use file_id (from wdesignkit/list-local-snippets or wdesignkit/download-snippet) instead of post_id."
384 : '';
385 return ['success' => false, 'message' => "No post found with ID {$post_id}.{$hint}", 'data' => (object) []];
386 }
387
388 $changed = [];
389
390 if ($new_title !== null && $new_title !== '') {
391 $result = wp_update_post(['ID' => $post_id, 'post_title' => $new_title], true);
392 if (is_wp_error($result)) {
393 return ['success' => false, 'message' => $result->get_error_message(), 'data' => (object) []];
394 }
395 $changed[] = 'title';
396 }
397
398 if ($description !== null) {
399 update_post_meta($post_id, 'nxt-code-note', $description);
400 $changed[] = 'description';
401 }
402
403 return [
404 'success' => true,
405 'message' => 'Snippet details updated (post-based): ' . implode(', ', $changed),
406 'data' => [
407 'post_id' => $post_id,
408 'title' => get_the_title($post_id),
409 'description' => (string) get_post_meta($post_id, 'nxt-code-note', true),
410 ],
411 ];
412 }
413