← All changes
|
includes/abilities/widgets/wdesignkit-create-widget.php
+33
-9
2.6.3
→
2.7.0
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 | |
| @@ -551,8 +571,12 @@ | ||
| 551 | 571 | return [ |
| 552 | 572 | 'success' => false, |
| 553 | 573 | 'message' => 'Failed to write files: ' . implode(', ', $write_errors) . '. Check directory permissions.', |
| 554 | 574 | ]; |
| 575 | + } | |
| 576 | + | |
| 577 | + if (function_exists('wdesignkit_invalidate_widget_registry')) { | |
| 578 | + wdesignkit_invalidate_widget_registry($builder); | |
| 555 | 579 | } |
| 556 | 580 | |
| 557 | 581 | $created_files = [ |
| 558 | 582 | $file_name_full . '.php', |