PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.44
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.44
51.1.86 51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 All 40 releases
king-addons / includes / extensions / Theme_Builder / Theme_Builder.php

Theme_Builder.php in King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder 51.1.44, at includes/extensions/Theme_Builder/Theme_Builder.php

1,392 lines 49.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Theme Builder feature bootstrap.
4 *
5 * @package King_Addons
6 */
7
8 namespace King_Addons;
9
10 use Elementor\Controls_Manager;
11 use Elementor\Plugin as Elementor_Plugin;
12 use King_Addons\Theme_Builder\Admin_List_Table;
13 use King_Addons\Theme_Builder\Conditions;
14 use King_Addons\Theme_Builder\Context;
15 use King_Addons\Theme_Builder\Meta_Keys;
16 use King_Addons\Theme_Builder\Repository;
17 use King_Addons\Theme_Builder\Resolver;
18 use King_Addons\Woo_Builder\Context as Woo_Context;
19 use WP_Post;
20
21 if (!defined('ABSPATH')) {
22 exit;
23 }
24
25 /**
26 * Registers Theme Builder feature and integrates with WordPress and Elementor.
27 */
28 class Theme_Builder
29 {
30 /**
31 * Admin menu slug.
32 *
33 * @var string
34 */
35 private string $menu_slug = 'king-addons-theme-builder';
36
37 /**
38 * Repository instance.
39 *
40 * @var Repository
41 */
42 private Repository $repository;
43
44 /**
45 * Resolver instance.
46 *
47 * @var Resolver
48 */
49 private Resolver $resolver;
50
51 /**
52 * Resolved template ID for current request.
53 *
54 * @var int|null
55 */
56 private ?int $current_template_id = null;
57
58 /**
59 * Constructor.
60 */
61 public function __construct()
62 {
63 $this->require_helpers();
64
65 $this->repository = new Repository();
66 $this->resolver = new Resolver($this->repository);
67
68 // Use priority 15 to ensure the parent menu exists before adding this submenu
69 add_action('admin_menu', [$this, 'register_admin_menu'], 15);
70 add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']);
71 add_action('add_meta_boxes', [$this, 'register_meta_boxes']);
72 add_action('save_post', [$this, 'handle_save_post'], 10, 2);
73 add_action('delete_post', [$this, 'handle_delete_post']);
74 add_action('transition_post_status', [$this, 'handle_status_transition'], 10, 3);
75 add_action('admin_post_ka_theme_builder_create', [$this, 'handle_create_template']);
76 add_action('admin_post_ka_theme_builder_quick_update', [$this, 'handle_quick_update']);
77 add_action('admin_post_ka_theme_builder_duplicate', [$this, 'handle_duplicate_template']);
78 add_action('elementor/preview/enqueue_scripts', [$this, 'enqueue_preview_handler']);
79 add_action('elementor/documents/register_controls', [$this, 'register_document_controls']);
80 add_action('elementor/document/after_save', [$this, 'handle_document_after_save'], 10, 2);
81 add_filter('template_include', [$this, 'handle_template_include'], 99);
82 add_filter('king_addons/theme_builder/current_template_id', [$this, 'filter_current_template_id']);
83 add_filter('body_class', [$this, 'filter_body_class']);
84 }
85
86 /**
87 * Require helper files for Theme Builder.
88 *
89 * @return void
90 */
91 private function require_helpers(): void
92 {
93 require_once KING_ADDONS_PATH . 'includes/helpers/Theme_Builder/Meta_Keys.php';
94 require_once KING_ADDONS_PATH . 'includes/helpers/Theme_Builder/Context.php';
95 require_once KING_ADDONS_PATH . 'includes/helpers/Theme_Builder/Conditions.php';
96 require_once KING_ADDONS_PATH . 'includes/helpers/Theme_Builder/Repository.php';
97 require_once KING_ADDONS_PATH . 'includes/helpers/Theme_Builder/Resolver.php';
98 require_once KING_ADDONS_PATH . 'includes/helpers/Theme_Builder/Admin_List_Table.php';
99 }
100
101 /**
102 * Register admin menu entry.
103 *
104 * @return void
105 */
106 public function register_admin_menu(): void
107 {
108 add_menu_page(
109 esc_html__('Theme Builder', 'king-addons'),
110 esc_html__('Theme Builder', 'king-addons'),
111 'manage_options',
112 $this->menu_slug,
113 [$this, 'render_admin_page'],
114 'dashicons-layout',
115 54.6
116 );
117 }
118
119 /**
120 * Enqueue admin assets for Theme Builder pages.
121 *
122 * @param string $hook Current admin page hook.
123 *
124 * @return void
125 */
126 public function enqueue_admin_assets(string $hook): void
127 {
128 if (false === strpos($hook, $this->menu_slug)) {
129 return;
130 }
131
132 wp_enqueue_style(
133 KING_ADDONS_ASSETS_UNIQUE_KEY . '-theme-builder-admin',
134 KING_ADDONS_URL . 'includes/extensions/Theme_Builder/style.css',
135 [],
136 KING_ADDONS_VERSION
137 );
138
139 wp_enqueue_script(
140 KING_ADDONS_ASSETS_UNIQUE_KEY . '-theme-builder-admin',
141 KING_ADDONS_URL . 'includes/extensions/Theme_Builder/script.js',
142 ['jquery'],
143 KING_ADDONS_VERSION,
144 true
145 );
146 }
147
148 /**
149 * Register meta boxes for Elementor templates.
150 *
151 * @return void
152 */
153 public function register_meta_boxes(): void
154 {
155 add_meta_box(
156 'king-addons-theme-builder',
157 esc_html__('Theme Builder', 'king-addons'),
158 [$this, 'render_meta_box'],
159 'elementor_library',
160 'side',
161 'default'
162 );
163 }
164
165 /**
166 * Render Theme Builder meta box.
167 *
168 * @param WP_Post $post Current post.
169 *
170 * @return void
171 */
172 public function render_meta_box(WP_Post $post): void
173 {
174 wp_nonce_field('ka_theme_builder_meta_box', 'ka_theme_builder_meta_box_nonce');
175
176 $location = get_post_meta($post->ID, Meta_Keys::LOCATION, true);
177 $sub_location = get_post_meta($post->ID, Meta_Keys::SUB_LOCATION, true);
178 $priority = (int) get_post_meta($post->ID, Meta_Keys::PRIORITY, true);
179 $enabled = '1' === (string) get_post_meta($post->ID, Meta_Keys::ENABLED, true);
180
181 echo '<p><strong>' . esc_html__('Location', 'king-addons') . ':</strong> ' . esc_html($sub_location ?: '-') . '</p>';
182 echo '<p><strong>' . esc_html__('Type', 'king-addons') . ':</strong> ' . esc_html($location ?: '-') . '</p>';
183 echo '<p><strong>' . esc_html__('Priority', 'king-addons') . ':</strong> ' . esc_html((string) ($priority ?: 10)) . '</p>';
184 echo '<p><strong>' . esc_html__('Status', 'king-addons') . ':</strong> ' . ($enabled ? esc_html__('Enabled', 'king-addons') : esc_html__('Disabled', 'king-addons')) . '</p>';
185 echo '<p><a href="' . esc_url(admin_url('admin.php?page=' . $this->menu_slug)) . '">' . esc_html__('Manage in Theme Builder', 'king-addons') . '</a></p>';
186 }
187
188 /**
189 * Handle meta save for Elementor Library posts.
190 *
191 * @param int $post_id Post ID.
192 * @param WP_Post $post Post object.
193 *
194 * @return void
195 */
196 public function handle_save_post(int $post_id, WP_Post $post): void
197 {
198 if ('elementor_library' !== $post->post_type) {
199 return;
200 }
201
202 if (!isset($_POST['ka_theme_builder_meta_box_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['ka_theme_builder_meta_box_nonce'])), 'ka_theme_builder_meta_box')) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
203 return;
204 }
205
206 // Keep cache fresh if any relevant meta changes.
207 $this->repository->clear_cache();
208 }
209
210 /**
211 * Clear cache on delete.
212 *
213 * @return void
214 */
215 public function handle_delete_post(): void
216 {
217 $this->repository->clear_cache();
218 }
219
220 /**
221 * Clear cache on status transition.
222 *
223 * @param string $new_status New status.
224 * @param string $old_status Old status.
225 * @param WP_Post $post Post object.
226 *
227 * @return void
228 */
229 public function handle_status_transition(string $new_status, string $old_status, WP_Post $post): void
230 {
231 if ('elementor_library' !== $post->post_type || $new_status === $old_status) {
232 return;
233 }
234
235 $this->repository->clear_cache();
236 }
237
238 /**
239 * Render admin page for Theme Builder.
240 *
241 * @return void
242 */
243 public function render_admin_page(): void
244 {
245 if (!current_user_can('manage_options')) {
246 return;
247 }
248
249 $this->handle_inline_actions();
250
251 $templates = $this->prepare_admin_templates();
252 $table = new Admin_List_Table($templates, admin_url('admin.php?page=' . $this->menu_slug));
253 $table->prepare_items();
254
255 // Enqueue shared V3 styles
256 wp_enqueue_style(
257 'king-addons-admin-v3',
258 KING_ADDONS_URL . 'includes/admin/layouts/shared/admin-v3-styles.css',
259 [],
260 KING_ADDONS_VERSION
261 );
262
263 $this->render_modern_styles();
264
265 // Theme mode is per-user
266 $theme_mode = get_user_meta(get_current_user_id(), 'king_addons_theme_mode', true);
267 $allowed_theme_modes = ['dark', 'light', 'auto'];
268 if (!in_array($theme_mode, $allowed_theme_modes, true)) {
269 $theme_mode = 'dark';
270 }
271 ?>
272 <script>
273 (function() {
274 const mode = '<?php echo esc_js($theme_mode); ?>';
275 const mql = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
276 const isDark = mode === 'auto' ? !!(mql && mql.matches) : mode === 'dark';
277 document.documentElement.classList.toggle('ka-v3-dark', isDark);
278 document.body.classList.add('ka-admin-v3');
279 document.body.classList.toggle('ka-v3-dark', isDark);
280 })();
281 </script>
282 <div class="ka-admin-wrap ka-tb-wrap">
283 <!-- Header -->
284 <div class="ka-admin-header">
285 <div class="ka-admin-header-left">
286 <div class="ka-admin-header-icon">
287 <span class="dashicons dashicons-layout"></span>
288 </div>
289 <div>
290 <h1 class="ka-admin-title"><?php esc_html_e('Theme Builder', 'king-addons'); ?></h1>
291 <p class="ka-admin-subtitle"><?php esc_html_e('Create custom templates for your site', 'king-addons'); ?></p>
292 </div>
293 </div>
294 <div class="ka-admin-header-actions">
295 <button type="button" id="ka-theme-builder-add-new" class="ka-btn ka-btn-primary">
296 <span class="dashicons dashicons-plus-alt2"></span>
297 <?php esc_html_e('Add New Template', 'king-addons'); ?>
298 </button>
299 <div class="ka-v3-segmented" id="ka-v3-theme-segment" role="radiogroup" aria-label="Theme" data-active="<?php echo esc_attr($theme_mode); ?>">
300 <span class="ka-v3-segmented-indicator" aria-hidden="true"></span>
301 <button type="button" class="ka-v3-segmented-btn" data-theme="light" aria-pressed="<?php echo $theme_mode === 'light' ? 'true' : 'false'; ?>"><?php esc_html_e('Light', 'king-addons'); ?></button>
302 <button type="button" class="ka-v3-segmented-btn" data-theme="dark" aria-pressed="<?php echo $theme_mode === 'dark' ? 'true' : 'false'; ?>"><?php esc_html_e('Dark', 'king-addons'); ?></button>
303 <button type="button" class="ka-v3-segmented-btn" data-theme="auto" aria-pressed="<?php echo $theme_mode === 'auto' ? 'true' : 'false'; ?>"><?php esc_html_e('Auto', 'king-addons'); ?></button>
304 </div>
305 </div>
306 </div>
307
308 <?php $this->render_add_new_modal(); ?>
309 <?php $this->render_quick_edit_modal(); ?>
310
311 <div class="ka-card">
312 <div class="ka-card-header">
313 <span class="dashicons dashicons-admin-page" style="color: #0071e3;"></span>
314 <h2><?php esc_html_e('Templates', 'king-addons'); ?></h2>
315 </div>
316 <div class="ka-card-body" style="padding: 0;">
317 <form method="post">
318 <?php $table->views(); ?>
319 <?php $table->display(); ?>
320 </form>
321 </div>
322 </div>
323 </div>
324 <script>
325 (function() {
326 const ajaxUrl = '<?php echo esc_url(admin_url('admin-ajax.php')); ?>';
327 const nonce = '<?php echo esc_js(wp_create_nonce('king_addons_dashboard_ui')); ?>';
328 const segment = document.getElementById('ka-v3-theme-segment');
329 const mql = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
330 let mode = (segment && segment.getAttribute('data-active') ? segment.getAttribute('data-active') : 'dark').toString();
331 let mqlHandler = null;
332
333 function saveUISetting(key, value) {
334 const body = new URLSearchParams();
335 body.set('action', 'king_addons_save_dashboard_ui');
336 body.set('nonce', nonce);
337 body.set('key', key);
338 body.set('value', value);
339 fetch(ajaxUrl, {
340 method: 'POST',
341 headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
342 body: body.toString()
343 });
344 }
345
346 function updateSegment(activeMode) {
347 if (!segment) {
348 return;
349 }
350 segment.setAttribute('data-active', activeMode);
351 segment.querySelectorAll('.ka-v3-segmented-btn').forEach((btn) => {
352 const theme = btn.getAttribute('data-theme');
353 btn.setAttribute('aria-pressed', theme === activeMode ? 'true' : 'false');
354 });
355 }
356
357 function applyTheme(isDark) {
358 document.body.classList.toggle('ka-v3-dark', isDark);
359 document.documentElement.classList.toggle('ka-v3-dark', isDark);
360 }
361
362 function setThemeMode(nextMode, save) {
363 mode = nextMode;
364 updateSegment(nextMode);
365
366 if (mqlHandler && mql) {
367 if (mql.removeEventListener) {
368 mql.removeEventListener('change', mqlHandler);
369 } else if (mql.removeListener) {
370 mql.removeListener(mqlHandler);
371 }
372 mqlHandler = null;
373 }
374
375 if (nextMode === 'auto') {
376 applyTheme(!!(mql && mql.matches));
377 mqlHandler = (e) => {
378 if (mode !== 'auto') {
379 return;
380 }
381 applyTheme(!!e.matches);
382 };
383 if (mql) {
384 if (mql.addEventListener) {
385 mql.addEventListener('change', mqlHandler);
386 } else if (mql.addListener) {
387 mql.addListener(mqlHandler);
388 }
389 }
390 } else {
391 applyTheme(nextMode === 'dark');
392 }
393
394 if (save) {
395 saveUISetting('theme_mode', nextMode);
396 }
397 }
398
399 window.kaV3ToggleDark = function() {
400 const isDark = document.body.classList.contains('ka-v3-dark');
401 setThemeMode(isDark ? 'light' : 'dark', true);
402 };
403
404 if (segment) {
405 segment.addEventListener('click', function(e) {
406 const btn = e.target.closest('.ka-v3-segmented-btn');
407 if (!btn) return;
408 e.preventDefault();
409 setThemeMode((btn.getAttribute('data-theme') || 'dark').toString(), true);
410 });
411 }
412
413 if (segment) {
414 setThemeMode(mode, false);
415 }
416 })();
417 </script>
418 <?php
419 }
420
421 /**
422 * Render modern CSS styles for Theme Builder admin page.
423 *
424 * @return void
425 */
426 private function render_modern_styles(): void
427 {
428 ?>
429 <style>
430 /* Theme Builder V3 Additional Styles */
431 .ka-tb-wrap {
432 max-width: 1400px;
433 }
434
435 /* Table overrides for V3 */
436 .ka-tb-wrap .wp-list-table {
437 border: none;
438 }
439 .ka-tb-wrap .wp-list-table th {
440 background: rgba(0, 0, 0, 0.02);
441 padding: 14px 20px;
442 font-size: 11px;
443 font-weight: 600;
444 color: #86868b;
445 text-transform: uppercase;
446 letter-spacing: 0.5px;
447 border-bottom: 1px solid rgba(0, 0, 0, 0.04);
448 }
449 body.ka-v3-dark .ka-tb-wrap .wp-list-table th {
450 background: rgba(255, 255, 255, 0.02);
451 color: #86868b;
452 border-bottom-color: rgba(255, 255, 255, 0.04);
453 }
454 .ka-tb-wrap .wp-list-table td {
455 padding: 14px 20px;
456 border-bottom: 1px solid rgba(0, 0, 0, 0.04);
457 font-size: 14px;
458 color: #1d1d1f;
459 }
460 body.ka-v3-dark .ka-tb-wrap .wp-list-table td {
461 color: #f5f5f7;
462 border-bottom-color: rgba(255, 255, 255, 0.04);
463 }
464 .ka-tb-wrap .wp-list-table tbody tr:hover td {
465 background: rgba(0, 0, 0, 0.02);
466 }
467 body.ka-v3-dark .ka-tb-wrap .wp-list-table tbody tr:hover td {
468 background: rgba(255, 255, 255, 0.02);
469 }
470 .ka-tb-wrap .wp-list-table .row-actions {
471 font-size: 12px;
472 }
473 .ka-tb-wrap .wp-list-table .row-actions a {
474 color: #0071e3;
475 }
476 body.ka-v3-dark .ka-tb-wrap .wp-list-table .row-actions a {
477 color: #2997ff;
478 }
479 .ka-tb-wrap .subsubsub {
480 margin: 16px 24px;
481 }
482 .ka-tb-wrap .subsubsub a {
483 color: #1d1d1f;
484 }
485 body.ka-v3-dark .ka-tb-wrap .subsubsub a {
486 color: #f5f5f7;
487 }
488 .ka-tb-wrap .subsubsub a.current {
489 color: #0071e3;
490 }
491 body.ka-v3-dark .ka-tb-wrap .subsubsub a.current {
492 color: #2997ff;
493 }
494 .ka-tb-wrap .tablenav {
495 padding: 12px 24px;
496 }
497
498 /* Modal V3 */
499 .ka-theme-builder-modal {
500 position: fixed;
501 inset: 0;
502 z-index: 100000;
503 display: flex;
504 align-items: center;
505 justify-content: center;
506 }
507 .ka-theme-builder-modal__backdrop {
508 position: absolute;
509 inset: 0;
510 background: rgba(0,0,0,0.5);
511 backdrop-filter: blur(20px);
512 -webkit-backdrop-filter: blur(20px);
513 }
514 .ka-theme-builder-modal__content {
515 position: relative;
516 background: #fff;
517 border-radius: 20px;
518 padding: 32px;
519 max-width: 500px;
520 width: 90%;
521 box-shadow: 0 20px 60px rgba(0,0,0,0.3);
522 animation: kaModalIn 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
523 }
524 body.ka-v3-dark .ka-theme-builder-modal__content {
525 background: #1c1c1e;
526 }
527 @keyframes kaModalIn {
528 from { opacity: 0; transform: scale(0.95) translateY(-10px); }
529 to { opacity: 1; transform: scale(1) translateY(0); }
530 }
531 .ka-theme-builder-modal__content h2 {
532 margin: 0 0 24px;
533 font-size: 22px;
534 font-weight: 600;
535 color: #1d1d1f;
536 }
537 body.ka-v3-dark .ka-theme-builder-modal__content h2 {
538 color: #f5f5f7;
539 }
540 .ka-theme-builder-modal__content p {
541 margin-bottom: 16px;
542 color: #86868b;
543 }
544 .ka-theme-builder-modal__content label {
545 display: block;
546 font-size: 14px;
547 font-weight: 500;
548 color: #1d1d1f;
549 margin-bottom: 8px;
550 }
551 body.ka-v3-dark .ka-theme-builder-modal__content label {
552 color: #f5f5f7;
553 }
554 .ka-theme-builder-modal__content input[type="text"],
555 .ka-theme-builder-modal__content input[type="number"],
556 .ka-theme-builder-modal__content select {
557 width: 100%;
558 padding: 12px 16px;
559 border: 1px solid rgba(0, 0, 0, 0.1);
560 border-radius: 12px;
561 font-size: 15px;
562 background: #fff;
563 transition: border-color 0.2s, box-shadow 0.2s;
564 }
565 body.ka-v3-dark .ka-theme-builder-modal__content input[type="text"],
566 body.ka-v3-dark .ka-theme-builder-modal__content input[type="number"],
567 body.ka-v3-dark .ka-theme-builder-modal__content select {
568 background: #2c2c2e;
569 border-color: rgba(255, 255, 255, 0.1);
570 color: #f5f5f7;
571 }
572 .ka-theme-builder-modal__content input:focus,
573 .ka-theme-builder-modal__content select:focus {
574 outline: none;
575 border-color: #0071e3;
576 box-shadow: 0 0 0 4px rgba(0, 113, 227, 0.1);
577 }
578 body.ka-v3-dark .ka-theme-builder-modal__content input:focus,
579 body.ka-v3-dark .ka-theme-builder-modal__content select:focus {
580 border-color: #2997ff;
581 box-shadow: 0 0 0 4px rgba(41, 151, 255, 0.2);
582 }
583 .ka-theme-builder-modal__content .submit {
584 margin-top: 24px;
585 display: flex;
586 gap: 12px;
587 }
588 .ka-theme-builder-modal__content .button-primary {
589 background: #0071e3;
590 border: none;
591 padding: 12px 24px;
592 font-size: 15px;
593 font-weight: 400;
594 border-radius: 980px;
595 cursor: pointer;
596 color: #fff;
597 transition: all 0.3s;
598 }
599 .ka-theme-builder-modal__content .button-primary:hover {
600 background: #0077ed;
601 }
602 .ka-theme-builder-modal__content .button {
603 padding: 12px 20px;
604 border-radius: 980px;
605 font-size: 15px;
606 cursor: pointer;
607 background: rgba(0, 0, 0, 0.06);
608 border: none;
609 color: #1d1d1f;
610 }
611 body.ka-v3-dark .ka-theme-builder-modal__content .button {
612 background: rgba(255, 255, 255, 0.1);
613 color: #f5f5f7;
614 }
615 .ka-theme-builder-modal__content input[type="checkbox"] {
616 width: 18px;
617 height: 18px;
618 margin-right: 8px;
619 vertical-align: middle;
620 }
621
622 /* Responsive */
623 @media (max-width: 782px) {
624 .ka-tb-wrap .ka-admin-header {
625 flex-direction: column;
626 text-align: center;
627 gap: 16px;
628 }
629 }
630 </style>
631 <?php
632 }
633
634 /**
635 * Enqueue front-end assets placeholder.
636 *
637 * @return void
638 */
639 public function enqueue_front_assets(): void
640 {
641 // Reserved for future front-end assets; keep file reference valid.
642 }
643
644 /**
645 * Enqueue preview handler for Elementor editor.
646 *
647 * @return void
648 */
649 public function enqueue_preview_handler(): void
650 {
651 $post_id = get_the_ID() ? (int) get_the_ID() : get_queried_object_id();
652
653 if (!$post_id || 'elementor_library' !== get_post_type($post_id)) {
654 return;
655 }
656
657 if (!get_post_meta($post_id, Meta_Keys::LOCATION, true)) {
658 return;
659 }
660
661 wp_enqueue_script(
662 KING_ADDONS_ASSETS_UNIQUE_KEY . '-theme-builder-preview',
663 KING_ADDONS_URL . 'includes/extensions/Theme_Builder/preview-handler.js',
664 ['jquery'],
665 KING_ADDONS_VERSION,
666 true
667 );
668 }
669
670 /**
671 * Register Elementor document controls for Theme Builder templates.
672 *
673 * @param mixed $document Document instance.
674 *
675 * @return void
676 */
677 public function register_document_controls($document): void
678 {
679 if (!is_object($document) || !method_exists($document, 'get_main_id')) {
680 return;
681 }
682
683 $post_id = (int) $document->get_main_id();
684 if ('elementor_library' !== get_post_type($post_id)) {
685 return;
686 }
687 $location = get_post_meta($post_id, Meta_Keys::LOCATION, true);
688
689 if (!$location) {
690 return;
691 }
692
693 $sub_location = get_post_meta($post_id, Meta_Keys::SUB_LOCATION, true);
694 $preview_post = (int) get_post_meta($post_id, Meta_Keys::PREVIEW_POST_ID, true);
695 $preview_term = (int) get_post_meta($post_id, Meta_Keys::PREVIEW_TERM_ID, true);
696 $preview_author = (int) get_post_meta($post_id, Meta_Keys::PREVIEW_AUTHOR_ID, true);
697 $preview_query = get_post_meta($post_id, Meta_Keys::PREVIEW_QUERY, true);
698
699 $document->start_controls_section(
700 'ka_theme_builder_settings',
701 [
702 'label' => esc_html__('Theme Builder', 'king-addons'),
703 'tab' => Controls_Manager::TAB_SETTINGS,
704 ]
705 );
706
707 $document->add_control(
708 'ka_tb_readonly_type',
709 [
710 'label' => esc_html__('Template Type', 'king-addons'),
711 'type' => Controls_Manager::RAW_HTML,
712 'raw' => '<strong>' . esc_html($location) . '</strong>',
713 'content_classes' => 'ka-theme-builder-readonly',
714 ]
715 );
716
717 $document->add_control(
718 'ka_tb_readonly_location',
719 [
720 'label' => esc_html__('Location', 'king-addons'),
721 'type' => Controls_Manager::RAW_HTML,
722 'raw' => '<strong>' . esc_html($sub_location) . '</strong>',
723 'content_classes' => 'ka-theme-builder-readonly',
724 ]
725 );
726
727 $document->add_control(
728 'ka_tb_preview_post',
729 [
730 'label' => esc_html__('Preview Post ID', 'king-addons'),
731 'type' => Controls_Manager::NUMBER,
732 'default' => $preview_post,
733 ]
734 );
735
736 $document->add_control(
737 'ka_tb_preview_term',
738 [
739 'label' => esc_html__('Preview Term ID', 'king-addons'),
740 'type' => Controls_Manager::NUMBER,
741 'default' => $preview_term,
742 ]
743 );
744
745 $document->add_control(
746 'ka_tb_preview_author',
747 [
748 'label' => esc_html__('Preview Author ID', 'king-addons'),
749 'type' => Controls_Manager::NUMBER,
750 'default' => $preview_author,
751 ]
752 );
753
754 $document->add_control(
755 'ka_tb_preview_query',
756 [
757 'label' => esc_html__('Preview Search Query', 'king-addons'),
758 'type' => Controls_Manager::TEXT,
759 'default' => $preview_query,
760 ]
761 );
762
763 if ($this->is_pro_location($sub_location)) {
764 $document->add_control(
765 'ka_tb_pro_notice',
766 [
767 'label' => esc_html__('Pro', 'king-addons'),
768 'type' => Controls_Manager::RAW_HTML,
769 'raw' => esc_html__('Advanced conditions require Pro.', 'king-addons'),
770 'content_classes' => 'ka-theme-builder-readonly',
771 ]
772 );
773 }
774
775 $document->end_controls_section();
776 }
777
778 /**
779 * Persist preview meta from Elementor document settings.
780 *
781 * @param \Elementor\Core\Base\Document $document Elementor document.
782 * @param array<string,mixed> $data Saved data.
783 *
784 * @return void
785 */
786 public function handle_document_after_save($document, array $data): void
787 {
788 if (!is_object($document) || !method_exists($document, 'get_main_id')) {
789 return;
790 }
791
792 $post_id = (int) $document->get_main_id();
793 if ('elementor_library' !== get_post_type($post_id)) {
794 return;
795 }
796
797 if (!get_post_meta($post_id, Meta_Keys::LOCATION, true)) {
798 return;
799 }
800
801 $preview_post = (int) $document->get_settings('ka_tb_preview_post');
802 $preview_term = (int) $document->get_settings('ka_tb_preview_term');
803 $preview_author = (int) $document->get_settings('ka_tb_preview_author');
804 $preview_query = sanitize_text_field((string) $document->get_settings('ka_tb_preview_query'));
805
806 update_post_meta($post_id, Meta_Keys::PREVIEW_POST_ID, $preview_post);
807 update_post_meta($post_id, Meta_Keys::PREVIEW_TERM_ID, $preview_term);
808 update_post_meta($post_id, Meta_Keys::PREVIEW_AUTHOR_ID, $preview_author);
809 update_post_meta($post_id, Meta_Keys::PREVIEW_QUERY, $preview_query);
810
811 $this->repository->clear_cache();
812 }
813
814 /**
815 * Handle "Add New" submission.
816 *
817 * @return void
818 */
819 public function handle_create_template(): void
820 {
821 if (!current_user_can('manage_options')) {
822 wp_die(esc_html__('You do not have permission to perform this action.', 'king-addons'));
823 }
824
825 if (!isset($_POST['ka_theme_builder_create_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['ka_theme_builder_create_nonce'])), 'ka_theme_builder_create')) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
826 wp_die(esc_html__('Invalid nonce.', 'king-addons'));
827 }
828
829 $title = isset($_POST['ka_tb_title']) ? sanitize_text_field(wp_unslash($_POST['ka_tb_title'])) : esc_html__('New Theme Template', 'king-addons');
830 $type = isset($_POST['ka_tb_type']) ? sanitize_text_field(wp_unslash($_POST['ka_tb_type'])) : 'single';
831 $sub_location = isset($_POST['ka_tb_location']) ? sanitize_text_field(wp_unslash($_POST['ka_tb_location'])) : '';
832 $type = $this->normalize_type($type, $sub_location);
833
834 // Check Free tier limit: 1 active template per primary type
835 if (!$this->can_use_pro()) {
836 $primary_type = $this->normalize_type($type, $sub_location);
837 if ($this->count_enabled_templates_by_type($primary_type) >= 1) {
838 wp_die(
839 sprintf(
840 /* translators: %s: template type name */
841 esc_html__('Free version allows only 1 active %s template. Disable an existing template or upgrade to Pro for unlimited templates.', 'king-addons'),
842 esc_html($primary_type)
843 )
844 );
845 }
846 }
847
848 $post_id = wp_insert_post([
849 'post_type' => 'elementor_library',
850 'post_status' => 'publish',
851 'post_title' => $title,
852 ]);
853
854 if (is_wp_error($post_id)) {
855 wp_die(esc_html__('Unable to create template.', 'king-addons'));
856 }
857
858 $conditions = $this->build_default_conditions($sub_location);
859
860 $is_pro = Conditions::is_pro_only($conditions) || $this->is_pro_location($sub_location);
861 $enabled = $is_pro && !$this->can_use_pro() ? '0' : '1';
862
863 update_post_meta($post_id, Meta_Keys::ENABLED, $enabled);
864 update_post_meta($post_id, Meta_Keys::LOCATION, $type);
865 update_post_meta($post_id, Meta_Keys::SUB_LOCATION, $sub_location);
866 update_post_meta($post_id, Meta_Keys::PRIORITY, 10);
867 update_post_meta($post_id, Meta_Keys::IS_PRO_ONLY, $is_pro ? '1' : '0');
868 update_post_meta($post_id, Meta_Keys::CONDITIONS, wp_json_encode($conditions));
869
870 $this->repository->clear_cache();
871
872 wp_safe_redirect(admin_url('post.php?post=' . $post_id . '&action=elementor'));
873 exit;
874 }
875
876 /**
877 * Handle quick update submission.
878 *
879 * @return void
880 */
881 public function handle_quick_update(): void
882 {
883 if (!current_user_can('manage_options')) {
884 wp_die(esc_html__('You do not have permission to perform this action.', 'king-addons'));
885 }
886
887 if (!isset($_POST['ka_theme_builder_quick_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['ka_theme_builder_quick_nonce'])), 'ka_theme_builder_quick')) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
888 wp_die(esc_html__('Invalid nonce.', 'king-addons'));
889 }
890
891 $template_id = isset($_POST['template_id']) ? (int) $_POST['template_id'] : 0;
892 $priority = isset($_POST['priority']) ? (int) $_POST['priority'] : 10;
893 $enabled = isset($_POST['enabled']) && '1' === $_POST['enabled']; // phpcs:ignore WordPress.Security.NonceVerification.Missing
894
895 if ($template_id > 0) {
896 update_post_meta($template_id, Meta_Keys::PRIORITY, $priority);
897 update_post_meta($template_id, Meta_Keys::ENABLED, $enabled ? '1' : '0');
898 $this->repository->clear_cache();
899 }
900
901 wp_safe_redirect(admin_url('admin.php?page=' . $this->menu_slug));
902 exit;
903 }
904
905 /**
906 * Handle template duplication (Pro feature).
907 *
908 * @return void
909 */
910 public function handle_duplicate_template(): void
911 {
912 if (!current_user_can('manage_options')) {
913 wp_die(esc_html__('You do not have permission to perform this action.', 'king-addons'));
914 }
915
916 if (!$this->can_use_pro()) {
917 wp_die(esc_html__('Template duplication requires Pro.', 'king-addons'));
918 }
919
920 $template_id = isset($_GET['template_id']) ? (int) $_GET['template_id'] : 0;
921
922 if (!$template_id || !wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'] ?? '')), 'ka_theme_builder_duplicate_' . $template_id)) {
923 wp_die(esc_html__('Invalid request.', 'king-addons'));
924 }
925
926 $original = get_post($template_id);
927 if (!$original || 'elementor_library' !== $original->post_type) {
928 wp_die(esc_html__('Template not found.', 'king-addons'));
929 }
930
931 // Clone the post
932 $new_post_id = wp_insert_post([
933 'post_type' => 'elementor_library',
934 'post_status' => 'draft',
935 'post_title' => $original->post_title . ' ' . esc_html__('(Copy)', 'king-addons'),
936 'post_content' => $original->post_content,
937 ]);
938
939 if (is_wp_error($new_post_id)) {
940 wp_die(esc_html__('Failed to duplicate template.', 'king-addons'));
941 }
942
943 // Copy all meta data
944 $meta_keys = [
945 Meta_Keys::ENABLED,
946 Meta_Keys::LOCATION,
947 Meta_Keys::SUB_LOCATION,
948 Meta_Keys::CONDITIONS,
949 Meta_Keys::PRIORITY,
950 Meta_Keys::IS_PRO_ONLY,
951 Meta_Keys::PREVIEW_POST_ID,
952 Meta_Keys::PREVIEW_TERM_ID,
953 Meta_Keys::PREVIEW_AUTHOR_ID,
954 Meta_Keys::PREVIEW_QUERY,
955 ];
956
957 foreach ($meta_keys as $key) {
958 $value = get_post_meta($template_id, $key, true);
959 if ('' !== $value) {
960 update_post_meta($new_post_id, $key, $value);
961 }
962 }
963
964 // Disable the duplicate by default to avoid conflicts
965 update_post_meta($new_post_id, Meta_Keys::ENABLED, '0');
966
967 // Copy Elementor data
968 $elementor_data = get_post_meta($template_id, '_elementor_data', true);
969 if ($elementor_data) {
970 update_post_meta($new_post_id, '_elementor_data', $elementor_data);
971 }
972
973 $elementor_edit_mode = get_post_meta($template_id, '_elementor_edit_mode', true);
974 if ($elementor_edit_mode) {
975 update_post_meta($new_post_id, '_elementor_edit_mode', $elementor_edit_mode);
976 }
977
978 $this->repository->clear_cache();
979
980 wp_safe_redirect(admin_url('admin.php?page=' . $this->menu_slug . '&duplicated=1'));
981 exit;
982 }
983
984 /**
985 * Handle template include override.
986 *
987 * @param string $template Current template path.
988 *
989 * @return string
990 */
991 public function handle_template_include(string $template): string
992 {
993 if (is_admin() || wp_doing_ajax() || (defined('REST_REQUEST') && REST_REQUEST)) {
994 return $template;
995 }
996
997 if (!did_action('elementor/loaded')) {
998 return $template;
999 }
1000
1001 if (is_singular('elementor_library')) {
1002 return $template;
1003 }
1004
1005 if ($this->is_woo_context()) {
1006 return $template;
1007 }
1008
1009 $context = Context::from_request();
1010 $resolved = $this->resolver->resolve($context);
1011
1012 if (!$resolved) {
1013 return $template;
1014 }
1015
1016 $this->current_template_id = $resolved;
1017
1018 return KING_ADDONS_PATH . 'includes/extensions/Theme_Builder/templates/theme-builder.php';
1019 }
1020
1021 /**
1022 * Provide current template ID to filters.
1023 *
1024 * @param int $template_id Existing template id.
1025 *
1026 * @return int
1027 */
1028 public function filter_current_template_id(int $template_id): int
1029 {
1030 if ($this->current_template_id) {
1031 return (int) $this->current_template_id;
1032 }
1033
1034 return (int) $template_id;
1035 }
1036
1037 /**
1038 * Append body class for resolved template.
1039 *
1040 * @param array<int,string> $classes Body classes.
1041 *
1042 * @return array<int,string>
1043 */
1044 public function filter_body_class(array $classes): array
1045 {
1046 if ($this->current_template_id) {
1047 $classes[] = 'king-addons-theme-builder-active';
1048 }
1049
1050 return $classes;
1051 }
1052
1053 /**
1054 * Render add-new modal markup.
1055 *
1056 * @return void
1057 */
1058 private function render_add_new_modal(): void
1059 {
1060 ?>
1061 <div id="ka-theme-builder-modal" class="ka-theme-builder-modal" style="display:none;">
1062 <div class="ka-theme-builder-modal__backdrop"></div>
1063 <div class="ka-theme-builder-modal__content">
1064 <h2><?php echo esc_html__('Create Theme Template', 'king-addons'); ?></h2>
1065 <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
1066 <input type="hidden" name="action" value="ka_theme_builder_create" />
1067 <?php wp_nonce_field('ka_theme_builder_create', 'ka_theme_builder_create_nonce'); ?>
1068 <p>
1069 <label for="ka-tb-title"><?php echo esc_html__('Template Name', 'king-addons'); ?></label>
1070 <input type="text" id="ka-tb-title" name="ka_tb_title" class="regular-text" value="<?php echo esc_attr__('Theme Template', 'king-addons'); ?>" />
1071 </p>
1072 <p>
1073 <label for="ka-tb-type"><?php echo esc_html__('Template Type', 'king-addons'); ?></label>
1074 <select id="ka-tb-type" name="ka_tb_type">
1075 <option value="single"><?php echo esc_html__('Single', 'king-addons'); ?></option>
1076 <option value="archive"><?php echo esc_html__('Archive', 'king-addons'); ?></option>
1077 <option value="author"><?php echo esc_html__('Author', 'king-addons'); ?></option>
1078 <option value="search"><?php echo esc_html__('Search', 'king-addons'); ?></option>
1079 <option value="not_found"><?php echo esc_html__('404', 'king-addons'); ?></option>
1080 </select>
1081 </p>
1082 <p>
1083 <label for="ka-tb-location"><?php echo esc_html__('Location', 'king-addons'); ?></label>
1084 <select id="ka-tb-location" name="ka_tb_location">
1085 <option value="single_post"><?php echo esc_html__('All Posts', 'king-addons'); ?></option>
1086 <option value="single_page"><?php echo esc_html__('All Pages', 'king-addons'); ?></option>
1087 <option value="single_cpt"><?php echo esc_html__('Custom Post Types (Pro)', 'king-addons'); ?></option>
1088 <option value="archive_blog"><?php echo esc_html__('Blog / Posts Page', 'king-addons'); ?></option>
1089 <option value="archive_category"><?php echo esc_html__('All Categories', 'king-addons'); ?></option>
1090 <option value="archive_tag"><?php echo esc_html__('All Tags', 'king-addons'); ?></option>
1091 <option value="archive_tax"><?php echo esc_html__('Custom Taxonomy (Pro)', 'king-addons'); ?></option>
1092 <option value="author_all"><?php echo esc_html__('All Authors (Pro)', 'king-addons'); ?></option>
1093 <option value="author_specific"><?php echo esc_html__('Specific Authors (Pro)', 'king-addons'); ?></option>
1094 <option value="search_results"><?php echo esc_html__('Search Results', 'king-addons'); ?></option>
1095 <option value="not_found"><?php echo esc_html__('404 Page', 'king-addons'); ?></option>
1096 </select>
1097 </p>
1098 <p class="submit">
1099 <button type="submit" class="button button-primary"><?php echo esc_html__('Create and Edit with Elementor', 'king-addons'); ?></button>
1100 <button type="button" class="button ka-theme-builder-modal__close"><?php echo esc_html__('Cancel', 'king-addons'); ?></button>
1101 </p>
1102 </form>
1103 </div>
1104 </div>
1105 <?php
1106 }
1107
1108 /**
1109 * Render quick edit modal markup.
1110 *
1111 * @return void
1112 */
1113 private function render_quick_edit_modal(): void
1114 {
1115 ?>
1116 <div id="ka-theme-builder-quick-modal" class="ka-theme-builder-modal" style="display:none;">
1117 <div class="ka-theme-builder-modal__backdrop"></div>
1118 <div class="ka-theme-builder-modal__content">
1119 <h2><?php echo esc_html__('Quick Edit Template', 'king-addons'); ?></h2>
1120 <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
1121 <input type="hidden" name="action" value="ka_theme_builder_quick_update" />
1122 <?php wp_nonce_field('ka_theme_builder_quick', 'ka_theme_builder_quick_nonce'); ?>
1123 <input type="hidden" id="ka-tb-quick-template-id" name="template_id" value="" />
1124 <p>
1125 <label for="ka-tb-quick-priority"><?php echo esc_html__('Priority', 'king-addons'); ?></label>
1126 <input type="number" id="ka-tb-quick-priority" name="priority" min="0" step="1" value="10" />
1127 </p>
1128 <p>
1129 <label>
1130 <input type="checkbox" id="ka-tb-quick-enabled" name="enabled" value="1" />
1131 <?php echo esc_html__('Enabled', 'king-addons'); ?>
1132 </label>
1133 </p>
1134 <p class="submit">
1135 <button type="submit" class="button button-primary"><?php echo esc_html__('Save', 'king-addons'); ?></button>
1136 <button type="button" class="button ka-theme-builder-modal__close"><?php echo esc_html__('Cancel', 'king-addons'); ?></button>
1137 </p>
1138 </form>
1139 </div>
1140 </div>
1141 <?php
1142 }
1143
1144 /**
1145 * Build default conditions for a selected location.
1146 *
1147 * @param string $sub_location Sub-location value.
1148 *
1149 * @return array<string,mixed>
1150 */
1151 private function build_default_conditions(string $sub_location): array
1152 {
1153 $groups = [];
1154
1155 switch ($sub_location) {
1156 case 'single_post':
1157 $groups[] = [
1158 'relation' => 'AND',
1159 'rules' => [
1160 [
1161 'type' => 'include',
1162 'target' => 'post_type',
1163 'operator' => 'in',
1164 'value' => ['post'],
1165 ],
1166 ],
1167 ];
1168 break;
1169 case 'single_page':
1170 $groups[] = [
1171 'relation' => 'AND',
1172 'rules' => [
1173 [
1174 'type' => 'include',
1175 'target' => 'post_type',
1176 'operator' => 'in',
1177 'value' => ['page'],
1178 ],
1179 ],
1180 ];
1181 break;
1182 case 'search_results':
1183 $groups[] = [
1184 'relation' => 'AND',
1185 'rules' => [
1186 [
1187 'type' => 'include',
1188 'target' => 'search',
1189 'operator' => 'in',
1190 'value' => ['search'],
1191 ],
1192 ],
1193 ];
1194 break;
1195 case 'not_found':
1196 $groups[] = [
1197 'relation' => 'AND',
1198 'rules' => [
1199 [
1200 'type' => 'include',
1201 'target' => '404',
1202 'operator' => 'in',
1203 'value' => ['404'],
1204 ],
1205 ],
1206 ];
1207 break;
1208 default:
1209 $groups[] = [
1210 'relation' => 'AND',
1211 'rules' => [],
1212 ];
1213 break;
1214 }
1215
1216 return ['groups' => $groups];
1217 }
1218
1219 /**
1220 * Normalize template type based on selected sub-location.
1221 *
1222 * @param string $type Base type from UI.
1223 * @param string $sub_location Selected sub-location.
1224 *
1225 * @return string
1226 */
1227 private function normalize_type(string $type, string $sub_location): string
1228 {
1229 if (strpos($sub_location, 'archive') === 0) {
1230 return 'archive';
1231 }
1232
1233 if ('search_results' === $sub_location) {
1234 return 'search';
1235 }
1236
1237 if ('not_found' === $sub_location) {
1238 return 'not_found';
1239 }
1240
1241 if (strpos($sub_location, 'author') === 0) {
1242 return 'author';
1243 }
1244
1245 return $type ?: 'single';
1246 }
1247
1248 /**
1249 * Determine if sub-location is Pro-only.
1250 *
1251 * @param string $sub_location Sub-location identifier.
1252 *
1253 * @return bool
1254 */
1255 private function is_pro_location(string $sub_location): bool
1256 {
1257 if (in_array($sub_location, ['single_cpt', 'archive_tax', 'author_all', 'author_specific'], true)) {
1258 return true;
1259 }
1260
1261 if (strpos($sub_location, 'single_') === 0 && !in_array($sub_location, ['single_post', 'single_page'], true)) {
1262 return true;
1263 }
1264
1265 if (strpos($sub_location, 'archive_') === 0 && !in_array($sub_location, ['archive_blog', 'archive_category', 'archive_tag'], true)) {
1266 return true;
1267 }
1268
1269 return false;
1270 }
1271
1272 /**
1273 * Check whether Pro license is available.
1274 *
1275 * @return bool
1276 */
1277 private function can_use_pro(): bool
1278 {
1279 return function_exists('king_addons_freemius')
1280 && king_addons_freemius()->can_use_premium_code__premium_only();
1281 }
1282
1283 /**
1284 * Count enabled templates of a given primary type.
1285 *
1286 * @param string $type Primary type (single, archive, search, not_found, author).
1287 *
1288 * @return int
1289 */
1290 private function count_enabled_templates_by_type(string $type): int
1291 {
1292 $templates = $this->repository->get_active_templates();
1293 $count = 0;
1294
1295 foreach ($templates as $template) {
1296 if (empty($template['enabled'])) {
1297 continue;
1298 }
1299
1300 $template_location = $template['location'] ?? '';
1301
1302 if ($template_location === $type) {
1303 $count++;
1304 }
1305 }
1306
1307 return $count;
1308 }
1309
1310 /**
1311 * Prepare data for admin list table.
1312 *
1313 * @return array<int,array<string,mixed>>
1314 */
1315 private function prepare_admin_templates(): array
1316 {
1317 $templates = $this->repository->get_active_templates();
1318 $prepared = [];
1319
1320 foreach ($templates as $template) {
1321 $post = get_post($template['id']);
1322 if (!$post instanceof WP_Post) {
1323 continue;
1324 }
1325
1326 $prepared[] = [
1327 'id' => $template['id'],
1328 'title' => get_the_title($template['id']),
1329 'type' => $template['location'] ?? '',
1330 'sub_location' => $template['sub_location'] ?? '',
1331 'conditions' => $template['conditions'] ?? [],
1332 'priority' => $template['priority'] ?? 10,
1333 'enabled' => !empty($template['enabled']),
1334 'is_pro_only' => !empty($template['is_pro_only']),
1335 ];
1336 }
1337
1338 return $prepared;
1339 }
1340
1341 /**
1342 * Handle inline toggle/delete actions.
1343 *
1344 * @return void
1345 */
1346 private function handle_inline_actions(): void
1347 {
1348 $action = isset($_GET['action']) ? sanitize_text_field(wp_unslash($_GET['action'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1349 $template_id = isset($_GET['template_id']) ? (int) $_GET['template_id'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1350
1351 if (!$template_id || empty($action)) {
1352 return;
1353 }
1354
1355 if ('delete_template' === $action) {
1356 if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'] ?? '')), 'ka_theme_builder_delete_' . $template_id)) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1357 return;
1358 }
1359 wp_trash_post($template_id);
1360 $this->repository->clear_cache();
1361 }
1362
1363 if ('disable_template' === $action || 'enable_template' === $action) {
1364 if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'] ?? '')), 'ka_theme_builder_toggle_' . $template_id)) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1365 return;
1366 }
1367 update_post_meta($template_id, Meta_Keys::ENABLED, 'enable_template' === $action ? '1' : '0');
1368 $this->repository->clear_cache();
1369 }
1370 }
1371
1372 /**
1373 * Check if Woo Builder should own the context.
1374 *
1375 * @return bool
1376 */
1377 private function is_woo_context(): bool
1378 {
1379 if (!class_exists(Woo_Context::class)) {
1380 return false;
1381 }
1382
1383 $context = Woo_Context::detect_context();
1384
1385 return in_array($context, ['single_product', 'product_archive', 'cart', 'checkout', 'my_account'], true);
1386 }
1387 }
1388
1389
1390
1391
1392