PluginProbe
WDesignKit – AI Templates, Widget Builder & MCP Workflow / 2.7.1
WDesignKit – AI Templates, Widget Builder & MCP Workflow v2.7.1
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
← All changes | includes/abilities/widgets/wdesignkit-create-widget.php +34 -10 2.6.2 → 2.7.1 View file →
@@ -119,8 +119,9 @@
119 119 ],
120 120 'execute_callback' => 'wdesignkit_mcp_create_widget',
121 121 'permission_callback' => 'wdesignkit_mcp_permission_callback',
122 122 'meta' => [
123 + 'public' => true,
123 124 'show_in_rest' => true,
124 125 'mcp' => ['public' => true],
125 126 'annotations' => [
126 127 'instructions' => implode("\n", [
@@ -241,8 +242,18 @@
241 242 '### After creation',
242 243 '- Use wdesignkit/get-widget to read back the generated files.',
243 244 '- Use wdesignkit/update-widget to modify code.',
244 245 '- Does NOT require WDesignKit cloud login.',
246 + '',
247 + '### ⚠ Trust boundary — this ability executes PHP',
248 + 'The php_code written here is saved to disk and require_once\'d by the builder',
249 + 'loaders on subsequent requests, so calling this ability runs arbitrary PHP as the',
250 + 'web server user. That is what a widget builder is, and it is gated on',
251 + 'manage_options — the same capability WordPress requires for the plugin editor.',
252 + 'It does mean this ability, create-widget and update-widget, must never be exposed',
253 + 'to any automation context trusted below a full site administrator. Treat php_code',
254 + 'from an untrusted source exactly as you would treat pasting it into wp-admin.',
255 + '(ClickUp 86d41zavc)',
245 256 ]),
246 257 'readonly' => false,
247 258 'destructive' => false,
248 259 'idempotent' => false,
@@ -257,8 +268,20 @@
257 268 'message' => 'WDesignKit plugin is not active.',
258 269 ];
259 270 }
260 271
272 + // Site-level opt-out for generated widget PHP (ClickUp 86d41zavc). Defaults to allowed, so
273 + // this changes nothing unless an operator explicitly filters it off:
274 + // add_filter( 'wdesignkit_allow_widget_php_write', '__return_false' );
275 + // See wdesignkit_widget_php_write_allowed() for why this is a dedicated filter rather than a
276 + // DISALLOW_FILE_MODS / DISALLOW_FILE_EDIT check.
277 + if (function_exists('wdesignkit_widget_php_write_allowed') && !wdesignkit_widget_php_write_allowed()) {
278 + return [
279 + 'success' => false,
280 + 'message' => 'Widget creation is disabled on this site: generated widget PHP writes have been turned off via the wdesignkit_allow_widget_php_write filter.',
281 + ];
282 + }
283 +
261 284 // --- Validate name ---
262 285 $raw_name = $input['name'] ?? '';
263 286 if (!is_string($raw_name) || trim($raw_name) === '') {
264 287 return ['success' => false, 'message' => 'Widget name is required.'];
@@ -341,19 +364,16 @@
341 364 return ['success' => false, 'message' => 'Invalid version format. Use semver format like "1.0.0".'];
342 365 }
343 366
344 367 // --- Generate folder and file names matching WDesignKit convention ---
345 - // folder: kebab-case (e.g. "my-custom-card")
346 - // file: snake_case (e.g. "my_custom_card")
347 - // Normalize ALL separators: folder is pure kebab-case, file is pure snake_case.
348 - // Without this, a name like "My-Custom Widget" produces "my-custom_widget" (mixed).
349 - $folder_base = sanitize_file_name(strtolower(str_replace([' ', '_'], '-', $name)));
350 - $file_base = sanitize_file_name(strtolower(str_replace([' ', '-'], '_', $name)));
351 -
368 + // folder: "My-Custom-Card_<hash>" file: "My_Custom_Card_<hash>"
369 + // Case is PRESERVED. This used to lowercase both, which put widgets created here in a
370 + // different folder than the one the download/import/save paths write — the same widget
371 + // ended up in two directories differing only by case (ClickUp 86d3yk4yx).
352 372 // 8-character unique hex hash — matches the real WDesignKit widget format (e.g. "kmrd6l24")
353 373 $widget_hash = substr(md5(uniqid('wdkit', true)), 0, 8);
354 - $folder_name_full = $folder_base . '_' . $widget_hash;
355 - $file_name_full = $file_base . '_' . $widget_hash;
374 + $folder_name_full = wdesignkit_widget_folder_name($name, $widget_hash);
375 + $file_name_full = wdesignkit_widget_file_name($name, $widget_hash);
356 376
357 377 // widget_id = JUST the hash (matches the real widget JSON format: "widget_id":"20hzyv26")
358 378 $widget_id = $widget_hash;
359 379
@@ -553,8 +573,12 @@
553 573 'message' => 'Failed to write files: ' . implode(', ', $write_errors) . '. Check directory permissions.',
554 574 ];
555 575 }
556 576
577 + if (function_exists('wdesignkit_invalidate_widget_registry')) {
578 + wdesignkit_invalidate_widget_registry($builder);
579 + }
580 +
557 581 $created_files = [
558 582 $file_name_full . '.php',
559 583 $file_name_full . '.json',
560 584 $file_name_full . '.js',
@@ -2447,9 +2471,9 @@
2447 2471 if ($wdk_type === '') {
2448 2472 return null; // Unsupported type — skip
2449 2473 }
2450 2474
2451 - $json_name = $wdk_type . '_' . $php_name;
2475 + $json_name = $php_name;
2452 2476 $label = wdesignkit_mcp_php_get_string($args, 'label');
2453 2477
2454 2478 $ctrl = [
2455 2479 '_php_name' => $php_name, // temp — removed after map update