PluginProbe
WDesignKit – AI Templates, Widget Builder & MCP Workflow / 2.7.0
WDesignKit – AI Templates, Widget Builder & MCP Workflow v2.7.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-get-widget.php

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

215 lines 8.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Ability: Get full details of a specific WDesignKit widget including its code.
4 */
5
6 declare(strict_types=1);
7
8 if (!defined('ABSPATH')) {
9 exit();
10 }
11
12 wp_register_ability('wdesignkit/get-widget', [
13 'label' => __('Get WDesignKit Widget Details', 'wdesignkit'),
14 'description' => __(
15 'Gets full details of a specific widget including its JSON config, PHP code, CSS styles, and JS scripts. Use list-widgets first to find the folder name and builder type.',
16 'wdesignkit',
17 ),
18 'category' => 'wdesignkit',
19 'input_schema' => [
20 'type' => 'object',
21 'properties' => [
22 'builder' => [
23 'type' => 'string',
24 'description' => 'Builder type the widget belongs to.',
25 'enum' => ['elementor', 'gutenberg', 'gutenberg_core', 'bricks'],
26 ],
27 'folder' => [
28 'type' => 'string',
29 'description' => 'Widget folder name (from list-widgets output).',
30 ],
31 'include_code' => [
32 'type' => 'boolean',
33 'description' => 'Whether to include full file contents (PHP, CSS, JS). Default true. Set false for metadata only.',
34 ],
35 ],
36 'required' => ['builder', 'folder'],
37 'additionalProperties' => false,
38 ],
39 'output_schema' => [
40 'type' => 'object',
41 'properties' => [
42 'success' => ['type' => 'boolean'],
43 'widget' => ['type' => 'object'],
44 ],
45 ],
46 'execute_callback' => 'wdesignkit_mcp_get_widget',
47 'permission_callback' => 'wdesignkit_mcp_permission_callback',
48 'meta' => [
49 'public' => true,
50 'show_in_rest' => true,
51 'mcp' => ['public' => true],
52 'annotations' => [
53 'instructions' => implode("\n", [
54 'Gets full details of a WDesignKit widget.',
55 'Requires builder type and folder name (get these from wdesignkit/list-widgets).',
56 'By default returns all file contents (PHP, CSS, JS, JSON).',
57 'Set include_code to false to get metadata only (faster).',
58 'Widget files are stored in wp-content/uploads/wdesignkit/{builder}/{folder}/',
59 ]),
60 'readonly' => true,
61 'destructive' => false,
62 'idempotent' => true,
63 ],
64 ],
65 ]);
66
67 function wdesignkit_mcp_get_widget(array $input): array {
68 if (!defined('WDKIT_BUILDER_PATH')) {
69 return ['success' => false, 'message' => 'WDesignKit plugin is not active.'];
70 }
71
72 $builder = sanitize_text_field($input['builder'] ?? '');
73 $folder = sanitize_file_name($input['folder'] ?? '');
74 $include_code = $input['include_code'] ?? true;
75
76 if (empty($builder) || empty($folder)) {
77 return ['success' => false, 'message' => 'Both builder and folder parameters are required.'];
78 }
79
80 // Builder whitelist validation
81 $allowed_builders = ['elementor', 'gutenberg', 'gutenberg_core', 'bricks'];
82 if (!in_array($builder, $allowed_builders, true)) {
83 return ['success' => false, 'message' => 'Invalid builder type.'];
84 }
85
86 $widget_dir = WDKIT_BUILDER_PATH . '/' . $builder . '/' . $folder;
87
88 if (!is_dir($widget_dir)) {
89 return ['success' => false, 'message' => "Widget folder not found: {$builder}/{$folder}"];
90 }
91
92 // Realpath validation — ensure we're still inside WDKIT_BUILDER_PATH
93 $real_widget = realpath($widget_dir);
94 $real_base = realpath(WDKIT_BUILDER_PATH);
95 if (!$real_widget || !$real_base || strpos($real_widget, $real_base . DIRECTORY_SEPARATOR) !== 0) {
96 return ['success' => false, 'message' => 'Invalid widget path.'];
97 }
98
99 $files = @scandir($widget_dir);
100 if (!is_array($files)) {
101 return ['success' => false, 'message' => "Cannot read widget folder: {$builder}/{$folder}"];
102 }
103 $files = array_diff($files, ['.', '..']);
104
105 $widget = [
106 'folder' => $folder,
107 'builder' => $builder,
108 'files' => [],
109 'config' => null,
110 ];
111
112 foreach ($files as $file) {
113 $ext = pathinfo($file, PATHINFO_EXTENSION);
114 $filepath = $widget_dir . '/' . $file;
115 $size = @filesize($filepath) ?: 0;
116
117 $file_info = [
118 'name' => $file,
119 'type' => $ext,
120 'size' => $size,
121 'size_formatted' => size_format($size),
122 ];
123
124 // Read file content if needed (reuse for JSON parsing)
125 $raw_content = null;
126 if (in_array($ext, ['php', 'css', 'js', 'json'], true)) {
127 $raw_content = @file_get_contents($filepath);
128 }
129
130 if ($include_code && $raw_content !== null && $raw_content !== false) {
131 if (strlen($raw_content) > 50000) {
132 $file_info['content'] = substr($raw_content, 0, 50000);
133 $file_info['truncated'] = true;
134 $file_info['total_length'] = strlen($raw_content);
135 } else {
136 $file_info['content'] = $raw_content;
137 $file_info['truncated'] = false;
138 }
139 }
140
141 // Parse JSON config for metadata (reuse already-read content)
142 if ($ext === 'json' && $raw_content !== null && $raw_content !== false) {
143 $json_data = json_decode($raw_content, true);
144
145 if (json_last_error() === JSON_ERROR_NONE && !empty($json_data['widget_data']['widgetdata'])) {
146 $wd = $json_data['widget_data']['widgetdata'];
147
148 // If widget_version is absent (legacy / AI-generated widgets), write '1.0.0'
149 // back to the JSON file on disk so the fix is persistent, not just a runtime default.
150 $version_was_empty = (($wd['widget_version'] ?? '') === '');
151 if ($version_was_empty) {
152 $json_data['widget_data']['widgetdata']['widget_version'] = '1.0.0';
153 @file_put_contents(
154 $filepath,
155 wp_json_encode($json_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
156 );
157 $wd = $json_data['widget_data']['widgetdata']; // refresh from updated data
158 }
159
160 $meta_version = $wd['widget_version'];
161 $meta_description = $wd['description'] ?? '';
162 $meta_helper_link = $wd['helper_link'] ?? '';
163
164 $metadata_warnings = [];
165 if ($meta_description === '') {
166 $metadata_warnings[] = 'description is empty — add a description via update-widget';
167 }
168 if ($version_was_empty) {
169 $metadata_warnings[] = 'widget_version was missing in JSON config — automatically set to 1.0.0 and saved to disk';
170 }
171 $widget['config'] = [
172 'name' => $wd['name'] ?? '',
173 'widget_id' => $wd['widget_id'] ?? '',
174 'type' => $wd['type'] ?? '',
175 'version' => $meta_version,
176 'description' => $meta_description,
177 'w_image' => $wd['w_image'] ?? '',
178 'allow_push' => $wd['allow_push'] ?? false,
179 'helper_link' => $meta_helper_link,
180 'metadata_warnings' => $metadata_warnings,
181 ];
182 }
183
184 if (json_last_error() === JSON_ERROR_NONE && !empty($json_data['widget_data'])) {
185 $widget['widget_data_keys'] = array_keys($json_data['widget_data']);
186 }
187 }
188
189 $widget['files'][] = $file_info;
190 }
191
192 // Check activation status
193 $deactivated = get_option('wkit_deactivate_widgets', []);
194 $widget_id = $widget['config']['widget_id'] ?? '';
195 $is_deactivated = false;
196
197 if (is_array($deactivated) && !empty($widget_id)) {
198 foreach ($deactivated as $dw) {
199 if (($dw['w_unique'] ?? '') === $widget_id) {
200 $is_deactivated = true;
201 break;
202 }
203 }
204 }
205
206 $widget['status'] = $is_deactivated ? 'deactivated' : 'active';
207 $mtime = @filemtime($widget_dir);
208 $widget['last_modified'] = $mtime !== false ? wp_date('Y-m-d H:i:s', $mtime) : null;
209
210 return [
211 'success' => true,
212 'widget' => $widget,
213 ];
214 }
215