PluginProbe
WDesignKit – AI Templates, Widget Builder & MCP Workflow / 2.6.3
WDesignKit – AI Templates, Widget Builder & MCP Workflow v2.6.3
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 1.0.17 All 128 releases
wdesignkit / includes / abilities / widgets / wdesignkit-sync-widget-code.php

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

743 lines 32.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Ability: Sync and report drift between WDesignKit widget PHP code and JSON section_data.
4 */
5
6 declare(strict_types=1);
7
8 if (!defined('ABSPATH')) {
9 exit();
10 }
11
12 wp_register_ability('wdesignkit/sync-widget-code', [
13 'label' => __('Sync WDesignKit Widget Code', 'wdesignkit'),
14 'description' => __(
15 'Reports whether a widget\'s PHP register_controls() and JSON section_data are in sync, and on request regenerates section_data from PHP (code_to_section) or PHP register_controls() from section_data (section_to_code). Supports dry_run preview.',
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 (elementor, gutenberg, gutenberg_core, bricks).',
25 'enum' => ['elementor', 'gutenberg', 'gutenberg_core', 'bricks'],
26 ],
27 'folder' => [
28 'type' => 'string',
29 'description' => 'Widget folder name (from wdesignkit/list-widgets). Required if widget_id is omitted.',
30 ],
31 'widget_id' => [
32 'type' => 'string',
33 'description' => 'Widget unique ID. Used to locate the widget folder if folder is omitted.',
34 ],
35 'direction' => [
36 'type' => 'string',
37 'description' => 'Sync operation direction: "check_only" (report status), "code_to_section" (regenerate section_data from PHP), or "section_to_code" (regenerate PHP register_controls from section_data). Default "check_only".',
38 'enum' => ['check_only', 'code_to_section', 'section_to_code'],
39 ],
40 'dry_run' => [
41 'type' => 'boolean',
42 'description' => 'When true, returns preview of proposed changes without writing to disk. Default false.',
43 ],
44 ],
45 'additionalProperties' => false,
46 ],
47 'output_schema' => [
48 'type' => 'object',
49 'properties' => [
50 'success' => ['type' => 'boolean'],
51 'message' => ['type' => 'string'],
52 'in_sync' => ['type' => 'boolean'],
53 'direction' => ['type' => 'string'],
54 'dry_run' => ['type' => 'boolean'],
55 'builder' => ['type' => 'string'],
56 'folder' => ['type' => 'string'],
57 'widget_id' => ['type' => 'string'],
58 'php_controls_count' => ['type' => 'integer'],
59 'section_data_controls_count' => ['type' => 'integer'],
60 'diff' => ['type' => 'object'],
61 'preview' => ['type' => 'object'],
62 ],
63 ],
64 'execute_callback' => 'wdesignkit_mcp_sync_widget_code',
65 'permission_callback' => 'wdesignkit_mcp_permission_callback',
66 'meta' => [
67 'show_in_rest' => true,
68 'mcp' => ['public' => true],
69 'annotations' => [
70 'instructions' => implode("\n", [
71 'Reports drift between widget PHP register_controls() and JSON section_data.',
72 'Use direction="check_only" to inspect sync status.',
73 'Use direction="code_to_section" after updating raw php_code to update JSON section_data.',
74 'Use direction="section_to_code" to regenerate PHP register_controls() from JSON section_data.',
75 'Use dry_run=true to preview proposed updates before writing to disk.',
76 ]),
77 'readonly' => false,
78 'destructive' => false,
79 'idempotent' => false,
80 ],
81 ],
82 ]);
83
84 function wdesignkit_mcp_sync_widget_code(array $input): array {
85 set_time_limit(90);
86
87 if (!defined('WDKIT_BUILDER_PATH')) {
88 return ['success' => false, 'message' => 'WDesignKit plugin is not active.'];
89 }
90
91 // Ensure create-widget helper functions are loaded
92 if (!function_exists('wdesignkit_mcp_parse_php_section_data')) {
93 $cw_path = __DIR__ . '/wdesignkit-create-widget.php';
94 if (file_exists($cw_path)) {
95 require_once $cw_path;
96 }
97 }
98
99 $builder = sanitize_text_field((string) ($input['builder'] ?? ''));
100 $folder = sanitize_file_name((string) ($input['folder'] ?? ''));
101 $widget_id = sanitize_text_field((string) ($input['widget_id'] ?? ''));
102 $direction = sanitize_text_field((string) ($input['direction'] ?? 'check_only'));
103 $dry_run = !empty($input['dry_run']);
104
105 $allowed_builders = ['elementor', 'gutenberg', 'gutenberg_core', 'bricks'];
106 $allowed_directions = ['check_only', 'code_to_section', 'section_to_code'];
107
108 if (!in_array($direction, $allowed_directions, true)) {
109 $direction = 'check_only';
110 }
111
112 // Locate widget folder
113 $widget_dir = null;
114
115 if ($folder !== '') {
116 if ($builder !== '' && !in_array($builder, $allowed_builders, true)) {
117 return ['success' => false, 'message' => 'Invalid builder type.'];
118 }
119 $builders_to_check = ($builder !== '') ? [$builder] : $allowed_builders;
120 foreach ($builders_to_check as $b) {
121 $candidate_dir = WDKIT_BUILDER_PATH . '/' . $b . '/' . $folder;
122 if (is_dir($candidate_dir)) {
123 $widget_dir = $candidate_dir;
124 $builder = $b;
125 break;
126 }
127 }
128 } elseif ($widget_id !== '') {
129 $builders_to_check = ($builder !== '' && in_array($builder, $allowed_builders, true)) ? [$builder] : $allowed_builders;
130 foreach ($builders_to_check as $b) {
131 $b_dir = WDKIT_BUILDER_PATH . '/' . $b;
132 if (!is_dir($b_dir)) {
133 continue;
134 }
135 $subfolders = array_diff(@scandir($b_dir) ?: [], ['.', '..']);
136 foreach ($subfolders as $sub) {
137 $dir_path = $b_dir . '/' . $sub;
138 if (!is_dir($dir_path)) {
139 continue;
140 }
141 $files = array_diff(@scandir($dir_path) ?: [], ['.', '..']);
142 foreach ($files as $f) {
143 if (pathinfo($f, PATHINFO_EXTENSION) === 'json') {
144 $raw = @file_get_contents($dir_path . '/' . $f);
145 $data = ($raw !== false) ? json_decode($raw, true) : null;
146 $wid = $data['widget_data']['widgetdata']['widget_id'] ?? '';
147 if ($wid === $widget_id) {
148 $widget_dir = $dir_path;
149 $builder = $b;
150 $folder = $sub;
151 break 3;
152 }
153 }
154 }
155 }
156 }
157 }
158
159 if (!$widget_dir || !is_dir($widget_dir)) {
160 return [
161 'success' => false,
162 'message' => 'Widget folder not found. Specify a valid builder and folder or widget_id.',
163 ];
164 }
165
166 // Path safety check
167 $real_widget = realpath($widget_dir);
168 $real_base = realpath(WDKIT_BUILDER_PATH);
169 if (!$real_widget || !$real_base || strpos($real_widget, $real_base . DIRECTORY_SEPARATOR) !== 0) {
170 return ['success' => false, 'message' => 'Invalid widget path.'];
171 }
172
173 // Read JSON and PHP files
174 $files = array_diff(@scandir($widget_dir) ?: [], ['.', '..']);
175 $json_path = null;
176 $json_data = null;
177 $php_path = null;
178 $php_code = '';
179
180 foreach ($files as $f) {
181 $ext = strtolower(pathinfo($f, PATHINFO_EXTENSION));
182 if ($ext === 'json' && $json_path === null) {
183 $json_path = $widget_dir . '/' . $f;
184 $raw = @file_get_contents($json_path);
185 $json_data = ($raw !== false) ? json_decode($raw, true) : null;
186 } elseif ($ext === 'php' && $php_path === null) {
187 $php_path = $widget_dir . '/' . $f;
188 $raw = @file_get_contents($php_path);
189 $php_code = ($raw !== false) ? $raw : '';
190 }
191 }
192
193 if (!is_array($json_data) || !$json_path) {
194 return ['success' => false, 'message' => "Could not read JSON config in widget folder {$builder}/{$folder}."];
195 }
196
197 $wd_name = (string) ($json_data['widget_data']['widgetdata']['name'] ?? $folder);
198 if ($widget_id === '') {
199 $widget_id = (string) ($json_data['widget_data']['widgetdata']['widget_id'] ?? '');
200 }
201
202 $_slug = sanitize_title($wd_name);
203 $widget_css_class = 'wdkit-' . $_slug . (substr($_slug, -7) === '-widget' ? '' : '-widget');
204
205 $stored_section_data = $json_data['section_data'] ?? [];
206 if (!is_array($stored_section_data)) {
207 $stored_section_data = [];
208 }
209
210 // Parse PHP section data
211 $parsed_section_data = null;
212 if ($php_code !== '' && function_exists('wdesignkit_mcp_parse_php_section_data')) {
213 $parsed_section_data = wdesignkit_mcp_parse_php_section_data($php_code, $widget_css_class);
214 }
215
216 // Extract flat control lists for comparison
217 $php_flat = wdesignkit_mcp_extract_flat_controls($parsed_section_data ?: []);
218 $stored_flat = wdesignkit_mcp_extract_flat_controls($stored_section_data);
219
220 // Compute diff
221 $missing_in_section_data = [];
222 $missing_in_php = [];
223 $mismatched_controls = [];
224
225 $php_by_name = [];
226 foreach ($php_flat as $ctrl) {
227 $php_by_name[$ctrl['name']] = $ctrl;
228 }
229
230 $stored_by_name = [];
231 foreach ($stored_flat as $ctrl) {
232 $stored_by_name[$ctrl['name']] = $ctrl;
233 }
234
235 $checked_stored_names = [];
236 $php_count = count($php_flat);
237 $stored_count = count($stored_flat);
238 $max_count = max($php_count, $stored_count);
239
240 for ($i = 0; $i < $max_count; $i++) {
241 $p = $php_flat[$i] ?? null;
242 $s = $stored_flat[$i] ?? null;
243
244 if ($p !== null && $s !== null) {
245 if ($p['name'] === $s['name']) {
246 $checked_stored_names[$s['name']] = true;
247 if ($p['type'] !== $s['type'] || $p['section'] !== $s['section'] || $p['default'] !== $s['default']) {
248 $mismatched_controls[] = [
249 'name' => $p['name'],
250 'php_name' => $p['name'],
251 'section_data_name' => $s['name'],
252 'php_type' => $p['type'],
253 'section_data_type' => $s['type'],
254 'php_section' => $p['section'],
255 'section_data_section' => $s['section'],
256 'php_default' => $p['default'],
257 'section_data_default' => $s['default'],
258 ];
259 }
260 } else {
261 $p_in_stored = isset($stored_by_name[$p['name']]);
262 $s_in_php = isset($php_by_name[$s['name']]);
263
264 if (!$p_in_stored && !$s_in_php) {
265 $mismatched_controls[] = [
266 'name' => $p['name'] . ' vs ' . $s['name'],
267 'php_name' => $p['name'],
268 'section_data_name' => $s['name'],
269 'php_type' => $p['type'],
270 'section_data_type' => $s['type'],
271 'php_section' => $p['section'],
272 'section_data_section' => $s['section'],
273 'php_default' => $p['default'],
274 'section_data_default' => $s['default'],
275 ];
276 $checked_stored_names[$s['name']] = true;
277 } elseif ($p_in_stored) {
278 $matched_s = $stored_by_name[$p['name']];
279 $checked_stored_names[$matched_s['name']] = true;
280 if ($p['type'] !== $matched_s['type'] || $p['section'] !== $matched_s['section'] || $p['default'] !== $matched_s['default']) {
281 $mismatched_controls[] = [
282 'name' => $p['name'],
283 'php_name' => $p['name'],
284 'section_data_name' => $matched_s['name'],
285 'php_type' => $p['type'],
286 'section_data_type' => $matched_s['type'],
287 'php_section' => $p['section'],
288 'section_data_section' => $matched_s['section'],
289 'php_default' => $p['default'],
290 'section_data_default' => $matched_s['default'],
291 ];
292 }
293 } else {
294 $missing_in_section_data[] = [
295 'name' => $p['name'],
296 'label' => $p['label'],
297 'type' => $p['type'],
298 'section' => $p['section'],
299 ];
300 }
301 }
302 } elseif ($p !== null && $s === null) {
303 if (isset($stored_by_name[$p['name']])) {
304 $matched_s = $stored_by_name[$p['name']];
305 $checked_stored_names[$matched_s['name']] = true;
306 if ($p['type'] !== $matched_s['type'] || $p['section'] !== $matched_s['section'] || $p['default'] !== $matched_s['default']) {
307 $mismatched_controls[] = [
308 'name' => $p['name'],
309 'php_name' => $p['name'],
310 'section_data_name' => $matched_s['name'],
311 'php_type' => $p['type'],
312 'section_data_type' => $matched_s['type'],
313 'php_section' => $p['section'],
314 'section_data_section' => $matched_s['section'],
315 'php_default' => $p['default'],
316 'section_data_default' => $matched_s['default'],
317 ];
318 }
319 } else {
320 $missing_in_section_data[] = [
321 'name' => $p['name'],
322 'label' => $p['label'],
323 'type' => $p['type'],
324 'section' => $p['section'],
325 ];
326 }
327 } elseif ($p === null && $s !== null) {
328 if (!isset($checked_stored_names[$s['name']]) && !isset($php_by_name[$s['name']])) {
329 $missing_in_php[] = [
330 'name' => $s['name'],
331 'label' => $s['label'],
332 'type' => $s['type'],
333 'section' => $s['section'],
334 ];
335 }
336 }
337 }
338
339 $in_sync = empty($missing_in_section_data) && empty($missing_in_php) && empty($mismatched_controls);
340
341 $diff_summary = [
342 'missing_in_section_data' => $missing_in_section_data,
343 'missing_in_php' => $missing_in_php,
344 'mismatched_controls' => $mismatched_controls,
345 ];
346
347 // Handle check_only
348 if ($direction === 'check_only') {
349 return [
350 'success' => true,
351 'message' => $in_sync ? "Widget '{$wd_name}' PHP and section_data are in sync." : "Widget '{$wd_name}' PHP and section_data have drifted.",
352 'in_sync' => $in_sync,
353 'direction' => 'check_only',
354 'dry_run' => $dry_run,
355 'builder' => $builder,
356 'folder' => $folder,
357 'widget_id' => $widget_id,
358 'php_controls_count' => count($php_flat),
359 'section_data_controls_count' => count($stored_flat),
360 'diff' => $diff_summary,
361 ];
362 }
363
364 // Handle code_to_section
365 if ($direction === 'code_to_section') {
366 if (!is_array($parsed_section_data)) {
367 return [
368 'success' => false,
369 'message' => "Could not parse register_controls() from PHP code for widget '{$wd_name}'.",
370 ];
371 }
372
373 $file_name = pathinfo($json_path, PATHINFO_FILENAME);
374 $new_editor_html = '';
375 if (function_exists('wdesignkit_mcp_generate_editor_html_from_section_data')) {
376 $new_editor_html = wdesignkit_mcp_generate_editor_html_from_section_data(
377 $widget_id, $widget_css_class, $file_name, $parsed_section_data
378 );
379 }
380
381 if ($dry_run) {
382 return [
383 'success' => true,
384 'message' => "Dry run: Proposed update of section_data from PHP code for '{$wd_name}'.",
385 'in_sync' => false,
386 'direction' => 'code_to_section',
387 'dry_run' => true,
388 'builder' => $builder,
389 'folder' => $folder,
390 'widget_id' => $widget_id,
391 'php_controls_count' => count($php_flat),
392 'section_data_controls_count' => count($stored_flat),
393 'diff' => $diff_summary,
394 'preview' => [
395 'new_section_data' => $parsed_section_data,
396 'new_editor_html' => $new_editor_html,
397 ],
398 ];
399 }
400
401 $json_data['section_data'] = $parsed_section_data;
402 if (!isset($json_data['Editor_data']) || !is_array($json_data['Editor_data'])) {
403 $json_data['Editor_data'] = [];
404 }
405 $json_data['Editor_data']['html'] = $new_editor_html;
406
407 $written = @file_put_contents(
408 $json_path,
409 wp_json_encode($json_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
410 );
411
412 if ($written === false) {
413 return ['success' => false, 'message' => "Failed to write updated JSON config to disk for {$builder}/{$folder}."];
414 }
415
416 return [
417 'success' => true,
418 'message' => "Successfully synced section_data from PHP code for widget '{$wd_name}'.",
419 'in_sync' => true,
420 'direction' => 'code_to_section',
421 'dry_run' => false,
422 'builder' => $builder,
423 'folder' => $folder,
424 'widget_id' => $widget_id,
425 'php_controls_count' => count($php_flat),
426 'section_data_controls_count' => count($php_flat),
427 'diff' => [
428 'missing_in_section_data' => [],
429 'missing_in_php' => [],
430 'mismatched_controls' => [],
431 ],
432 ];
433 }
434
435 // Handle section_to_code
436 if ($direction === 'section_to_code') {
437 if (empty($stored_section_data)) {
438 return [
439 'success' => false,
440 'message' => "section_data is empty in JSON config for widget '{$wd_name}'. Cannot generate PHP code.",
441 ];
442 }
443
444 if (!$php_path || $php_code === '') {
445 return [
446 'success' => false,
447 'message' => "PHP file not found for widget '{$wd_name}'.",
448 ];
449 }
450
451 $new_controls_body = wdesignkit_mcp_generate_php_controls_from_section_data($stored_section_data);
452 $new_php_code = wdesignkit_mcp_replace_register_controls($php_code, $new_controls_body);
453
454 if ($dry_run) {
455 return [
456 'success' => true,
457 'message' => "Dry run: Proposed update of PHP register_controls() from section_data for '{$wd_name}'.",
458 'in_sync' => false,
459 'direction' => 'section_to_code',
460 'dry_run' => true,
461 'builder' => $builder,
462 'folder' => $folder,
463 'widget_id' => $widget_id,
464 'php_controls_count' => count($php_flat),
465 'section_data_controls_count' => count($stored_flat),
466 'diff' => $diff_summary,
467 'preview' => [
468 'new_php_code' => $new_php_code,
469 ],
470 ];
471 }
472
473 $written = @file_put_contents($php_path, $new_php_code);
474 if ($written === false) {
475 return ['success' => false, 'message' => "Failed to write updated PHP file to disk for {$builder}/{$folder}."];
476 }
477
478 return [
479 'success' => true,
480 'message' => "Successfully synced PHP register_controls() from section_data for widget '{$wd_name}'.",
481 'in_sync' => true,
482 'direction' => 'section_to_code',
483 'dry_run' => false,
484 'builder' => $builder,
485 'folder' => $folder,
486 'widget_id' => $widget_id,
487 'php_controls_count' => count($stored_flat),
488 'section_data_controls_count' => count($stored_flat),
489 'diff' => [
490 'missing_in_section_data' => [],
491 'missing_in_php' => [],
492 'mismatched_controls' => [],
493 ],
494 ];
495 }
496
497 return ['success' => false, 'message' => 'Invalid direction.'];
498 }
499
500 /**
501 * Extract a flat map of controls from section_data array.
502 */
503 function wdesignkit_mcp_extract_flat_controls(array $section_data): array {
504 // section_data's real shape is [{ "layout": [<sections>], "style": [<sections>] }] —
505 // each wrapper entry holds layout/style arrays of sections, NOT a section itself.
506 // Flatten to a plain list of sections (each with inner_sec) before extracting controls;
507 // this also tolerates a flat array of sections directly, for defensiveness.
508 $sections = [];
509 foreach ($section_data as $entry) {
510 if (!is_array($entry)) {
511 continue;
512 }
513 if (isset($entry['layout']) || isset($entry['style'])) {
514 foreach (['layout', 'style'] as $key) {
515 if (is_array($entry[$key] ?? null)) {
516 foreach ($entry[$key] as $sec) {
517 if (is_array($sec)) {
518 $sections[] = $sec;
519 }
520 }
521 }
522 }
523 } elseif (isset($entry['inner_sec'])) {
524 $sections[] = $entry;
525 }
526 }
527
528 $flat = [];
529 foreach ($sections as $group) {
530 $sec_title = (string) ($group['section'] ?? $group['name'] ?? 'Section');
531 $inner = $group['inner_sec'] ?? [];
532 if (!is_array($inner)) {
533 continue;
534 }
535
536 foreach ($inner as $ctrl) {
537 if (!is_array($ctrl)) {
538 continue;
539 }
540 $c_type = strtolower((string) ($ctrl['type'] ?? ''));
541 if ($c_type === 'normalhover') {
542 $tabs = $ctrl['inner_sec'] ?? $ctrl['fields'] ?? [];
543 if (is_array($tabs)) {
544 foreach ($tabs as $tab) {
545 $tab_ctrls = $tab['inner_sec'] ?? $tab['fields'] ?? [];
546 if (is_array($tab_ctrls)) {
547 foreach ($tab_ctrls as $tc) {
548 $c_name = (string) ($tc['name'] ?? '');
549 if ($c_name !== '') {
550 $flat[] = [
551 'name' => $c_name,
552 'label' => (string) ($tc['lable'] ?? $tc['label'] ?? $c_name),
553 'type' => strtolower((string) ($tc['type'] ?? 'text')),
554 'default' => (string) ($tc['defaultValue'] ?? $tc['default'] ?? ''),
555 'section' => $sec_title,
556 ];
557 }
558 }
559 }
560 }
561 }
562 } else {
563 $c_name = (string) ($ctrl['name'] ?? '');
564 if ($c_name !== '') {
565 $flat[] = [
566 'name' => $c_name,
567 'label' => (string) ($ctrl['lable'] ?? $ctrl['label'] ?? $c_name),
568 'type' => strtolower((string) ($ctrl['type'] ?? 'text')),
569 'default' => (string) ($ctrl['defaultValue'] ?? $ctrl['default'] ?? ''),
570 'section' => $sec_title,
571 ];
572 }
573 }
574 }
575 }
576 return $flat;
577 }
578
579 /**
580 * Generate PHP register_controls() code from section_data array.
581 */
582 function wdesignkit_mcp_generate_php_controls_from_section_data(array $section_data): string {
583 // section_data's real shape is [{ "layout": [<sections>], "style": [<sections>] }] —
584 // flatten to a plain list of sections first, tagging each with which wrapper it came
585 // from so the TAB_CONTENT/TAB_STYLE choice below doesn't have to guess from the name.
586 $sections = [];
587 foreach ($section_data as $entry) {
588 if (!is_array($entry)) {
589 continue;
590 }
591 if (isset($entry['layout']) || isset($entry['style'])) {
592 foreach (['layout' => false, 'style' => true] as $key => $is_style_wrapper) {
593 if (is_array($entry[$key] ?? null)) {
594 foreach ($entry[$key] as $sec) {
595 if (is_array($sec)) {
596 $sec['_is_style'] = $is_style_wrapper;
597 $sections[] = $sec;
598 }
599 }
600 }
601 }
602 } elseif (isset($entry['inner_sec'])) {
603 $sections[] = $entry;
604 }
605 }
606
607 $out = [];
608 foreach ($sections as $sec) {
609 $sec_name = (string) ($sec['name'] ?? 'section');
610 $sec_label = (string) ($sec['section'] ?? 'Section');
611 $is_style = isset($sec['_is_style'])
612 ? (bool) $sec['_is_style']
613 : (!empty($sec['is_style']) || (strpos($sec_name, 'style') !== false) || (strtolower($sec_label) === 'style' || strtolower($sec_label) === 'widget style'));
614 $tab_const = $is_style ? 'Controls_Manager::TAB_STYLE' : 'Controls_Manager::TAB_CONTENT';
615
616 $out[] = " \$this->start_controls_section(";
617 $out[] = " '" . addslashes($sec_name) . "',";
618 $out[] = " array(";
619 $out[] = " 'label' => esc_html__( '" . addslashes($sec_label) . "', 'wdesignkit' ),";
620 $out[] = " 'tab' => {$tab_const},";
621 $out[] = " )";
622 $out[] = " );";
623 $out[] = "";
624
625 $inner_sec = $sec['inner_sec'] ?? [];
626 if (is_array($inner_sec)) {
627 foreach ($inner_sec as $ctrl) {
628 if (!is_array($ctrl)) {
629 continue;
630 }
631 if (($ctrl['type'] ?? '') === 'normalhover') {
632 $out[] = " \$this->start_controls_tabs( '" . addslashes((string) ($ctrl['name'] ?? 'tabs')) . "' );";
633 $out[] = "";
634 $tabs = $ctrl['inner_sec'] ?? [];
635 if (is_array($tabs)) {
636 foreach ($tabs as $tab) {
637 $tab_type = (string) ($tab['type'] ?? 'normal');
638 $tab_label = ucfirst($tab_type);
639 $out[] = " \$this->start_controls_tab(";
640 $out[] = " '" . addslashes((string) ($tab['name'] ?? ('tab_' . $tab_type))) . "',";
641 $out[] = " array( 'label' => esc_html__( '" . $tab_label . "', 'wdesignkit' ) )";
642 $out[] = " );";
643 $out[] = "";
644 $tab_ctrls = $tab['inner_sec'] ?? [];
645 if (is_array($tab_ctrls)) {
646 foreach ($tab_ctrls as $tc) {
647 $out[] = wdesignkit_mcp_generate_single_control_php($tc);
648 }
649 }
650 $out[] = " \$this->end_controls_tab();";
651 $out[] = "";
652 }
653 }
654 $out[] = " \$this->end_controls_tabs();";
655 $out[] = "";
656 } else {
657 $out[] = wdesignkit_mcp_generate_single_control_php($ctrl);
658 }
659 }
660 }
661
662 $out[] = " \$this->end_controls_section();";
663 $out[] = "";
664 }
665 return implode("\n", $out);
666 }
667
668 /**
669 * Helper to generate PHP code for a single control definition.
670 */
671 function wdesignkit_mcp_generate_single_control_php(array $ctrl): string {
672 $c_type = strtolower((string) ($ctrl['type'] ?? 'text'));
673 $c_name = (string) ($ctrl['name'] ?? 'control');
674 $c_label = (string) ($ctrl['lable'] ?? $ctrl['label'] ?? ucfirst($c_name));
675
676 $type_map = [
677 'text' => 'Controls_Manager::TEXT',
678 'textarea' => 'Controls_Manager::TEXTAREA',
679 'wysiwyg' => 'Controls_Manager::WYSIWYG',
680 'number' => 'Controls_Manager::NUMBER',
681 'select' => 'Controls_Manager::SELECT',
682 'switcher' => 'Controls_Manager::SWITCHER',
683 'color' => 'Controls_Manager::COLOR',
684 'dimension' => 'Controls_Manager::DIMENSIONS',
685 'slider' => 'Controls_Manager::SLIDER',
686 'media' => 'Controls_Manager::MEDIA',
687 'choose' => 'Controls_Manager::CHOOSE',
688 'code' => 'Controls_Manager::CODE',
689 ];
690
691 if ($c_type === 'typography') {
692 $lines = [];
693 $lines[] = " \$this->add_group_control(";
694 $lines[] = " Group_Control_Typography::get_type(),";
695 $lines[] = " array(";
696 $lines[] = " 'name' => '" . addslashes($c_name) . "',";
697 $lines[] = " 'label' => esc_html__( '" . addslashes($c_label) . "', 'wdesignkit' ),";
698 if (!empty($ctrl['selector'])) {
699 $lines[] = " 'selector' => '{{WRAPPER}} " . addslashes((string) $ctrl['selector']) . "',";
700 }
701 $lines[] = " )";
702 $lines[] = " );";
703 $lines[] = "";
704 return implode("\n", $lines);
705 }
706
707 $cm_type = $type_map[$c_type] ?? 'Controls_Manager::TEXT';
708
709 $lines = [];
710 $lines[] = " \$this->add_control(";
711 $lines[] = " '" . addslashes($c_name) . "',";
712 $lines[] = " array(";
713 $lines[] = " 'label' => esc_html__( '" . addslashes($c_label) . "', 'wdesignkit' ),";
714 $lines[] = " 'type' => {$cm_type},";
715 if (isset($ctrl['defaultValue']) && $ctrl['defaultValue'] !== '') {
716 $lines[] = " 'default' => esc_html__( '" . addslashes((string) $ctrl['defaultValue']) . "', 'wdesignkit' ),";
717 }
718 if (!empty($ctrl['selector_value']) && !empty($ctrl['selectors'])) {
719 $sel_target = addslashes((string) $ctrl['selectors']);
720 $sel_prop = addslashes((string) $ctrl['selector_value']);
721 $lines[] = " 'selectors' => array(";
722 $lines[] = " '{{WRAPPER}} {$sel_target}' => '{$sel_prop}: {{VALUE}};',";
723 $lines[] = " ),";
724 }
725 $lines[] = " )";
726 $lines[] = " );";
727 $lines[] = "";
728
729 return implode("\n", $lines);
730 }
731
732 /**
733 * Replace register_controls() method body in PHP code.
734 */
735 function wdesignkit_mcp_replace_register_controls(string $php_code, string $new_controls_body): string {
736 $pattern = '/(protected|public)\s+function\s+register_controls\s*\(\s*\)\s*\{.*?\n\s*\}\n/s';
737 $replacement = " protected function register_controls() {\n" . $new_controls_body . " }\n";
738 if (preg_match($pattern, $php_code)) {
739 return (string) preg_replace($pattern, $replacement, $php_code, 1);
740 }
741 return $php_code;
742 }
743