PluginProbe
WDesignKit – AI Templates, Widget Builder & MCP Workflow / 2.5.0
WDesignKit – AI Templates, Widget Builder & MCP Workflow v2.5.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 / widgets / wdesignkit-toggle-widget.php

wdesignkit-toggle-widget.php in WDesignKit – AI Templates, Widget Builder & MCP Workflow 2.5.0, at includes/abilities/widgets/wdesignkit-toggle-widget.php

363 lines 13.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Abilities: Activate and deactivate WDesignKit widgets.
4 */
5
6 declare(strict_types=1);
7
8 if (!defined('ABSPATH')) {
9 exit();
10 }
11
12 wp_register_ability('wdesignkit/activate-widget', [
13 'label' => __('Activate WDesignKit Widget', 'sprout-mcp'),
14 'description' => __(
15 'Activates a deactivated widget so it gets loaded by the page builder. The widget files remain unchanged; this only updates the activation status.',
16 'sprout-mcp',
17 ),
18 'category' => 'wdesignkit',
19 'input_schema' => [
20 'type' => 'object',
21 'properties' => [
22 'widget_id' => [
23 'type' => 'string',
24 'description' => 'The widget_id (w_unique) to activate. Get this from list-widgets.',
25 'maxLength' => 128,
26 ],
27 ],
28 'required' => ['widget_id'],
29 'additionalProperties' => false,
30 ],
31 'output_schema' => [
32 'type' => 'object',
33 'properties' => [
34 'success' => ['type' => 'boolean'],
35 'message' => ['type' => 'string'],
36 'status' => ['type' => 'string'],
37 ],
38 ],
39 'execute_callback' => 'wdesignkit_mcp_activate_widget',
40 'permission_callback' => 'sprout_mcp_permission_callback',
41 'meta' => [
42 'show_in_rest' => true,
43 'mcp' => ['public' => true],
44 'annotations' => [
45 'instructions' => implode("\n", [
46 'Activates a deactivated WDesignKit widget.',
47 'Use wdesignkit/list-widgets to find deactivated widgets and their widget_id.',
48 'Returns success:false if the widget_id does not exist on the filesystem.',
49 'Returns status "already_active" if the widget exists but was not deactivated.',
50 'Returns status "activated" if the widget was successfully removed from the deactivated list.',
51 ]),
52 'readonly' => false,
53 'destructive' => false,
54 'idempotent' => true,
55 ],
56 ],
57 ]);
58
59 wp_register_ability('wdesignkit/deactivate-widget', [
60 'label' => __('Deactivate WDesignKit Widget', 'sprout-mcp'),
61 'description' => __(
62 'Deactivates a widget so it stops loading on the frontend. The widget files are preserved; this only updates the activation status. Useful for temporarily disabling a widget without deleting it.',
63 'sprout-mcp',
64 ),
65 'category' => 'wdesignkit',
66 'input_schema' => [
67 'type' => 'object',
68 'properties' => [
69 'widget_id' => [
70 'type' => 'string',
71 'description' => 'The widget_id (w_unique) to deactivate. Get this from list-widgets.',
72 'maxLength' => 128,
73 ],
74 'builder' => [
75 'type' => 'string',
76 'description' => 'Builder type (for internal tracking).',
77 'enum' => ['elementor', 'gutenberg', 'gutenberg_core', 'bricks'],
78 ],
79 'title' => [
80 'type' => 'string',
81 'description' => 'Widget title (for internal tracking).',
82 ],
83 ],
84 'required' => ['widget_id'],
85 'additionalProperties' => false,
86 ],
87 'output_schema' => [
88 'type' => 'object',
89 'properties' => [
90 'success' => ['type' => 'boolean'],
91 'message' => ['type' => 'string'],
92 'status' => ['type' => 'string'],
93 ],
94 ],
95 'execute_callback' => 'wdesignkit_mcp_deactivate_widget',
96 'permission_callback' => 'sprout_mcp_permission_callback',
97 'meta' => [
98 'show_in_rest' => true,
99 'mcp' => ['public' => true],
100 'annotations' => [
101 'instructions' => implode("\n", [
102 'Deactivates a WDesignKit widget without deleting its files.',
103 'The widget will stop loading on the frontend but can be reactivated later.',
104 'Returns success:false if the widget_id does not exist on the filesystem.',
105 'Returns status "already_deactivated" if the widget was already deactivated.',
106 'Returns status "builder_mismatch" if the provided builder does not match the widget\'s actual builder — omit the builder parameter to skip this check.',
107 'The builder parameter is optional; when omitted the correct builder is auto-detected from the filesystem.',
108 'Use wdesignkit/activate-widget to re-enable it.',
109 ]),
110 'readonly' => false,
111 'destructive' => false,
112 'idempotent' => true,
113 ],
114 ],
115 ]);
116
117 /**
118 * Validate widget_id format: only alphanumeric, hyphens, underscores, dots.
119 */
120 function wdesignkit_mcp_validate_widget_id(string $widget_id): bool {
121 return (bool) preg_match('/^[a-zA-Z0-9_\-\.]+$/', $widget_id);
122 }
123
124 /**
125 * Check whether a widget with the given widget_id exists in any builder's filesystem.
126 * Returns an array with 'builder' and 'folder' keys if found, or null if not found.
127 *
128 * Fast path: widget folders follow the {slug}_{widget_id} naming convention, so the
129 * hash can be extracted from the folder name with strrpos — no file reads needed.
130 * Slow path: JSON scan fallback for any folder that doesn't match the convention.
131 * Results are cached in a static map for the duration of the PHP request.
132 */
133 function wdesignkit_mcp_find_widget_by_id(string $widget_id): ?array {
134 static $cache = [];
135
136 if (array_key_exists($widget_id, $cache)) {
137 return $cache[$widget_id];
138 }
139
140 if (!defined('WDKIT_BUILDER_PATH')) {
141 return $cache[$widget_id] = null;
142 }
143
144 $builders = ['elementor', 'gutenberg', 'gutenberg_core', 'bricks'];
145
146 // Fast path: compare hash suffix of folder name — O(n folders), zero file reads.
147 foreach ($builders as $builder) {
148 $builder_dir = WDKIT_BUILDER_PATH . '/' . $builder;
149 if (!is_dir($builder_dir)) {
150 continue;
151 }
152 $folders = @scandir($builder_dir);
153 if (!is_array($folders)) {
154 continue;
155 }
156 foreach ($folders as $folder) {
157 if ($folder === '.' || $folder === '..') {
158 continue;
159 }
160 if (!is_dir($builder_dir . '/' . $folder)) {
161 continue;
162 }
163 $sep = strrpos($folder, '_');
164 if ($sep !== false && substr($folder, $sep + 1) === $widget_id) {
165 return $cache[$widget_id] = ['builder' => $builder, 'folder' => $folder];
166 }
167 }
168 }
169
170 // Slow path: read JSON configs for folders that don't follow the naming convention.
171 foreach ($builders as $builder) {
172 $builder_dir = WDKIT_BUILDER_PATH . '/' . $builder;
173 if (!is_dir($builder_dir)) {
174 continue;
175 }
176 $folders = @scandir($builder_dir);
177 if (!is_array($folders)) {
178 continue;
179 }
180 foreach ($folders as $folder) {
181 if ($folder === '.' || $folder === '..') {
182 continue;
183 }
184 $folder_path = $builder_dir . '/' . $folder;
185 if (!is_dir($folder_path)) {
186 continue;
187 }
188 // Skip folders already confirmed not to match in the fast pass above.
189 $sep = strrpos($folder, '_');
190 if ($sep !== false && substr($folder, $sep + 1) !== $widget_id) {
191 continue;
192 }
193 $sub_files = @scandir($folder_path);
194 if (!is_array($sub_files)) {
195 continue;
196 }
197 foreach ($sub_files as $sf) {
198 if (pathinfo($sf, PATHINFO_EXTENSION) !== 'json') {
199 continue;
200 }
201 $raw = @file_get_contents($folder_path . '/' . $sf);
202 if ($raw === false) {
203 continue;
204 }
205 $json = json_decode($raw, true);
206 if (json_last_error() !== JSON_ERROR_NONE) {
207 continue;
208 }
209 if (($json['widget_data']['widgetdata']['widget_id'] ?? '') === $widget_id) {
210 return $cache[$widget_id] = ['builder' => $builder, 'folder' => $folder];
211 }
212 break;
213 }
214 }
215 }
216
217 return $cache[$widget_id] = null;
218 }
219
220 function wdesignkit_mcp_activate_widget(array $input): array {
221 set_time_limit(30);
222
223 $widget_id = sanitize_text_field($input['widget_id'] ?? '');
224
225 if (empty($widget_id)) {
226 return ['success' => false, 'message' => 'widget_id is required.', 'status' => 'error'];
227 }
228
229 // Validate widget_id format to prevent injection
230 if (!wdesignkit_mcp_validate_widget_id($widget_id)) {
231 return [
232 'success' => false,
233 'message' => 'Invalid widget_id format. Only letters, numbers, hyphens, underscores, and dots are allowed.',
234 'status' => 'error',
235 ];
236 }
237
238 if (strlen($widget_id) > 128) {
239 return ['success' => false, 'message' => 'widget_id is too long.', 'status' => 'error'];
240 }
241
242 // Verify the widget actually exists on the filesystem before proceeding
243 $widget_location = wdesignkit_mcp_find_widget_by_id($widget_id);
244 if ($widget_location === null) {
245 return [
246 'success' => false,
247 'message' => "Widget '{$widget_id}' does not exist. Use list-widgets to get valid widget IDs.",
248 'status' => 'not_found',
249 ];
250 }
251
252 $deactivated = get_option('wkit_deactivate_widgets', []);
253
254 if (!is_array($deactivated)) {
255 $deactivated = [];
256 }
257
258 $found = false;
259 foreach ($deactivated as $idx => $dw) {
260 if (($dw['w_unique'] ?? '') === $widget_id) {
261 unset($deactivated[$idx]);
262 $found = true;
263 break;
264 }
265 }
266
267 if (!$found) {
268 return [
269 'success' => true,
270 'message' => "Widget '{$widget_id}' ({$widget_location['builder']}/{$widget_location['folder']}) is already active.",
271 'status' => 'already_active',
272 ];
273 }
274
275 update_option('wkit_deactivate_widgets', array_values($deactivated), false);
276
277 return [
278 'success' => true,
279 'message' => "Widget '{$widget_id}' ({$widget_location['builder']}/{$widget_location['folder']}) has been activated.",
280 'status' => 'activated',
281 ];
282 }
283
284 function wdesignkit_mcp_deactivate_widget(array $input): array {
285 set_time_limit(30);
286
287 $widget_id = sanitize_text_field($input['widget_id'] ?? '');
288 $builder = sanitize_text_field($input['builder'] ?? '');
289 $title = sanitize_text_field($input['title'] ?? $widget_id);
290
291 if (empty($widget_id)) {
292 return ['success' => false, 'message' => 'widget_id is required.', 'status' => 'error'];
293 }
294
295 // Validate widget_id format
296 if (!wdesignkit_mcp_validate_widget_id($widget_id)) {
297 return [
298 'success' => false,
299 'message' => 'Invalid widget_id format. Only letters, numbers, hyphens, underscores, and dots are allowed.',
300 'status' => 'error',
301 ];
302 }
303
304 if (strlen($widget_id) > 128) {
305 return ['success' => false, 'message' => 'widget_id is too long.', 'status' => 'error'];
306 }
307
308 // Verify the widget actually exists on the filesystem
309 $widget_location = wdesignkit_mcp_find_widget_by_id($widget_id);
310 if ($widget_location === null) {
311 return [
312 'success' => false,
313 'message' => "Widget '{$widget_id}' does not exist. Use list-widgets to get valid widget IDs.",
314 'status' => 'not_found',
315 ];
316 }
317
318 // Validate the provided builder against the filesystem-discovered builder.
319 // If a builder is supplied and it doesn't match, reject the call — deactivating
320 // with the wrong builder would store a corrupt tracking record.
321 // Always use the canonical filesystem value for the stored record regardless.
322 if (!empty($builder) && $builder !== $widget_location['builder']) {
323 return [
324 'success' => false,
325 'message' => "Builder mismatch: widget '{$widget_id}' belongs to '{$widget_location['builder']}', not '{$builder}'. Use the correct builder or omit the parameter.",
326 'status' => 'builder_mismatch',
327 ];
328 }
329 $builder = $widget_location['builder'];
330
331 $deactivated = get_option('wkit_deactivate_widgets', []);
332
333 if (!is_array($deactivated)) {
334 $deactivated = [];
335 }
336
337 // Check if already deactivated
338 foreach ($deactivated as $dw) {
339 if (($dw['w_unique'] ?? '') === $widget_id) {
340 return [
341 'success' => true,
342 'message' => "Widget '{$widget_id}' ({$widget_location['builder']}/{$widget_location['folder']}) is already deactivated.",
343 'status' => 'already_deactivated',
344 ];
345 }
346 }
347
348 $deactivated[] = [
349 'w_unique' => $widget_id,
350 'builder' => $builder,
351 'title' => $title,
352 'is_activated' => 'deactive',
353 ];
354
355 update_option('wkit_deactivate_widgets', $deactivated, false);
356
357 return [
358 'success' => true,
359 'message' => "Widget '{$widget_id}' ({$widget_location['builder']}/{$widget_location['folder']}) has been deactivated.",
360 'status' => 'deactivated',
361 ];
362 }
363