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-import-widget.php +21 -8 2.6.3 → 2.7.1 View file →
@@ -56,8 +56,9 @@
56 56 ],
57 57 'execute_callback' => 'wdesignkit_mcp_import_widget',
58 58 'permission_callback' => 'wdesignkit_mcp_permission_callback',
59 59 'meta' => [
60 + 'public' => true,
60 61 'show_in_rest' => true,
61 62 'mcp' => ['public' => true],
62 63 'annotations' => [
63 64 'instructions' => implode("\n", [
@@ -106,12 +107,15 @@
106 107 return ['success' => false, 'message' => "Unsupported builder type: {$builder}."];
107 108 }
108 109
109 110 $overwrite = !empty($input['overwrite']);
110 - $safe_name = sanitize_file_name($widget_name);
111 - $safe_id = sanitize_file_name($widget_id);
112 - $folder_name = str_replace(' ', '-', $safe_name) . '_' . $safe_id;
113 - $file_name = str_replace(' ', '_', $safe_name) . '_' . $safe_id;
111 + // Canonical helpers: they replace spaces BEFORE sanitize_file_name(). Sanitising first
112 + // (as this did) already collapsed spaces to hyphens, so the underscore pass was a no-op
113 + // and a multi-word title produced "My-Widget_id.json" while the builder's own save path
114 + // writes "My_Widget_id.php". The registry resolves the JSON by swapping .php for .json,
115 + // so that pair never matched and the widget was skipped for good (ClickUp 86d41cck5).
116 + $folder_name = wdesignkit_widget_folder_name($widget_name, $widget_id);
117 + $file_name = wdesignkit_widget_file_name($widget_name, $widget_id);
114 118 $builder_dir = WDKIT_BUILDER_PATH . '/' . $builder;
115 119 $widget_dir = $builder_dir . '/' . $folder_name;
116 120
117 121 // Duplicate check / overwrite: scan for any existing folder sharing the same widget_id.
@@ -215,12 +219,17 @@
215 219 }
216 220 }
217 221
218 222 if ($img_bytes !== '') {
219 - $img_ext = preg_replace('/[^a-z0-9]/', '', strtolower($img_ext));
220 - if ($img_ext === '') {
221 - $img_ext = 'png';
222 - }
223 + // Both branches above take the extension from caller-supplied data — a data-URI subtype or
224 + // the remote URL. The old preg_replace() only stripped punctuation, so "php" survived and
225 + // this write could drop executable PHP into the builder directory (CWE-434,
226 + // ClickUp 86d41cczd). Verify against the decoded bytes instead; '' means not an image.
227 + // The synthetic "image.<ext>" filename lets the helper read either branch's extension.
228 + $img_ext = wdesignkit_safe_image_extension('image.' . strtolower((string) $img_ext), $img_bytes);
229 + }
230 +
231 + if ($img_bytes !== '' && $img_ext !== '') {
223 232 $img_path = $widget_dir . '/' . $file_name . '.' . $img_ext;
224 233 if (@file_put_contents($img_path, $img_bytes) !== false) {
225 234 $image_saved = true;
226 235 // Point the JSON at the local thumbnail so it renders in the library and push finds it.
@@ -234,8 +243,12 @@
234 243
235 244 $thumb_note = $image_saved
236 245 ? ($image_source === 'bundled' ? ' Bundled thumbnail imported.' : ' Thumbnail downloaded.')
237 246 : '';
247 +
248 + if (function_exists('wdesignkit_invalidate_widget_registry')) {
249 + wdesignkit_invalidate_widget_registry($builder);
250 + }
238 251
239 252 return [
240 253 'success' => true,
241 254 'message' => "Widget '{$widget_name}' imported successfully." . $thumb_note,