PluginProbe
WDesignKit – AI Templates, Widget Builder & MCP Workflow / 2.7.0
WDesignKit – AI Templates, Widget Builder & MCP Workflow v2.7.0
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 1.0.16 All 129 releases
wdesignkit / includes / abilities / widgets / wdesignkit-widget-preview.php

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

222 lines 8.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Ability: Get or create a live preview URL for a local WDesignKit widget.
4 */
5
6 declare(strict_types=1);
7
8 if (!defined('ABSPATH')) {
9 exit();
10 }
11
12 wp_register_ability('wdesignkit/widget-preview', [
13 'label' => __('WDesignKit Widget Preview', 'wdesignkit'),
14 'description' => __(
15 'Creates (or reuses) a temporary WordPress page to preview a widget and returns its editor URL. For Elementor widgets the page opens in the Elementor canvas editor. For Gutenberg/gutenberg_core widgets the page opens in the block editor. No page is created for builder types other than those two.',
16 'wdesignkit',
17 ),
18 'category' => 'wdesignkit',
19 'input_schema' => [
20 'type' => 'object',
21 'properties' => [
22 'builder' => [
23 'type' => 'string',
24 'description' => 'Builder type.',
25 'enum' => ['elementor', 'gutenberg', 'gutenberg_core'],
26 ],
27 'folder' => [
28 'type' => 'string',
29 'description' => 'Widget folder name (from wdesignkit/list-widgets).',
30 ],
31 'page_data' => [
32 'type' => 'string',
33 'description' => 'Builder-specific page data token: for Elementor this is the widgetType slug; for Gutenberg this is the block name suffix (the part after "wdkit/").',
34 ],
35 ],
36 'required' => ['builder', 'folder'],
37 'additionalProperties' => false,
38 ],
39 'output_schema' => [
40 'type' => 'object',
41 'properties' => [
42 'success' => ['type' => 'boolean'],
43 'message' => ['type' => 'string'],
44 'preview_url' => ['type' => 'string'],
45 'page_id' => ['type' => 'integer'],
46 ],
47 ],
48 'execute_callback' => 'wdesignkit_mcp_widget_preview',
49 'permission_callback' => 'wdesignkit_mcp_permission_callback',
50 'meta' => [
51 'public' => true,
52 'show_in_rest' => true,
53 'mcp' => ['public' => true],
54 'annotations' => [
55 'instructions' => implode("\n", [
56 'Returns an admin URL where the user can preview the widget live.',
57 'No cloud login required — this is a local WP operation.',
58 'A temporary preview page is created once per widget_id (or reused if one already exists).',
59 'page_data is the widgetType string for Elementor and the block suffix for Gutenberg.',
60 'Get widget_id and widget_name from wdesignkit/get-widget (check widget.config.widget_id and widget.config.name).',
61 ]),
62 'readonly' => false,
63 'destructive' => false,
64 'idempotent' => true,
65 ],
66 ],
67 ]);
68
69 function wdesignkit_mcp_widget_preview(array $input): array {
70 if (!defined('WDKIT_BUILDER_PATH')) {
71 return ['success' => false, 'message' => 'WDesignKit plugin is not active.'];
72 }
73
74 $builder = sanitize_key((string) ($input['builder'] ?? ''));
75 $folder = sanitize_file_name((string) ($input['folder'] ?? ''));
76 $page_data = sanitize_key((string) ($input['page_data'] ?? ''));
77
78 if (!in_array($builder, ['elementor', 'gutenberg', 'gutenberg_core'], true)) {
79 return ['success' => false, 'message' => 'builder must be "elementor", "gutenberg", or "gutenberg_core".'];
80 }
81
82 if ($folder === '') {
83 return ['success' => false, 'message' => 'folder is required.'];
84 }
85
86 // gutenberg_core widgets live under their own gutenberg_core/ directory — create-widget,
87 // list-widgets, and the gutenberg_core loader all use the raw builder name. Resolve the
88 // folder under the actual builder dir; collapsing gutenberg_core → gutenberg made the
89 // lookup fail with "Widget folder not found" for every Core Gutenberg widget. (bug: 86d3rc7nf)
90 $fs_builder = $builder;
91
92 $widget_dir = WDKIT_BUILDER_PATH . '/' . $fs_builder . '/' . $folder;
93
94 if (!is_dir($widget_dir)) {
95 return ['success' => false, 'message' => "Widget folder not found: {$fs_builder}/{$folder}"];
96 }
97
98 // Realpath validation — ensure we're still inside WDKIT_BUILDER_PATH.
99 $real_widget = realpath($widget_dir);
100 $real_base = realpath(WDKIT_BUILDER_PATH);
101 if (!$real_widget || !$real_base || strpos($real_widget, $real_base . DIRECTORY_SEPARATOR) !== 0) {
102 return ['success' => false, 'message' => 'Invalid widget path.'];
103 }
104
105 // Read widget metadata from JSON
106 $widget_name = $folder;
107 $widget_id = '';
108 $files = array_diff(@scandir($widget_dir) ?: [], ['.', '..']);
109 foreach ($files as $f) {
110 if (pathinfo($f, PATHINFO_EXTENSION) !== 'json') {
111 continue;
112 }
113 $raw = @file_get_contents($widget_dir . '/' . $f);
114 $jd = ($raw !== false) ? json_decode($raw, true) : null;
115 if (is_array($jd)) {
116 $widget_name = $jd['widget_data']['widgetdata']['name'] ?? $folder;
117 $widget_id = $jd['widget_data']['widgetdata']['widget_id'] ?? '';
118 }
119 break;
120 }
121
122 if ($widget_id === '') {
123 return ['success' => false, 'message' => "Could not read widget_id from {$fs_builder}/{$folder}"];
124 }
125
126 if ($page_data === '') {
127 // Derive from widget_id as best-effort fallback
128 $page_data = $widget_id;
129 }
130
131 $preview_url = '';
132 $page_id = 0;
133
134 if ($builder === 'elementor') {
135 if (!class_exists('\Elementor\Plugin')) {
136 return ['success' => false, 'message' => 'Elementor is not active. Install and activate Elementor to preview Elementor widgets.'];
137 }
138
139 $existing = get_posts([
140 'post_type' => 'page',
141 'meta_key' => '_wkit_preview_widget_id',
142 'meta_value' => $widget_id,
143 'posts_per_page' => 1,
144 'fields' => 'ids',
145 ]);
146
147 if (!empty($existing)) {
148 $page_id = (int) $existing[0];
149 } else {
150 $page_id = (int) wp_insert_post([
151 'post_title' => $widget_name . ' - Preview',
152 'post_status' => 'publish',
153 'post_type' => 'page',
154 'post_name' => sanitize_title($widget_id . '-preview'),
155 'post_content' => '',
156 'meta_input' => [
157 '_is_preview_page' => true,
158 '_wkit_preview_widget_id' => $widget_id,
159 '_elementor_edit_mode' => 'builder',
160 '_wp_page_template' => 'elementor_canvas',
161 ],
162 ]);
163 }
164
165 if (!$page_id) {
166 return ['success' => false, 'message' => 'Failed to create preview page.'];
167 }
168
169 $preview_url = admin_url('post.php?post=' . $page_id . '&action=elementor');
170
171 } elseif (in_array($builder, ['gutenberg', 'gutenberg_core'], true)) {
172 $existing = get_posts([
173 'post_type' => 'page',
174 'meta_key' => '_wkit_preview_widget_id',
175 'meta_value' => $widget_id,
176 'posts_per_page' => 1,
177 'fields' => 'ids',
178 ]);
179
180 if (!empty($existing)) {
181 $page_id = (int) $existing[0];
182 } else {
183 $page_id = (int) wp_insert_post([
184 'post_title' => $widget_name . ' - Preview',
185 'post_status' => 'publish',
186 'post_type' => 'page',
187 'post_name' => sanitize_title(wp_unique_post_slug($widget_id . '-preview', 0, 'publish', 'page', 0)),
188 'post_content' => '',
189 'meta_input' => [
190 'gutenberg_preview' => true,
191 '_wkit_preview_widget_id' => $widget_id,
192 '_wp_page_template' => 'default',
193 ],
194 ]);
195 }
196
197 if (!$page_id) {
198 return ['success' => false, 'message' => 'Failed to create preview page.'];
199 }
200
201 if (function_exists('serialize_blocks')) {
202 // Generated Gutenberg widgets register their block as wdkit/wb-<widget_id>
203 // (see gutenberg_file.js / gutenberg_core_file.js). Ensure the wb- prefix so
204 // the inserted block matches the registered name and renders instead of
205 // showing WordPress's "invalid content" recovery error.
206 $block_suffix = (strpos($page_data, 'wb-') === 0) ? $page_data : 'wb-' . $page_data;
207 $blocks = [['blockName' => 'wdkit/' . $block_suffix, 'attrs' => [], 'innerHTML' => '', 'innerContent' => []]];
208 $updated_content = serialize_blocks($blocks);
209 wp_update_post(['ID' => $page_id, 'post_content' => $updated_content]);
210 }
211
212 $preview_url = admin_url('post.php?post=' . $page_id . '&action=edit');
213 }
214
215 return [
216 'success' => true,
217 'message' => 'Preview page ready.',
218 'preview_url' => $preview_url,
219 'page_id' => $page_id,
220 ];
221 }
222