# king-addons/51.1.44/includes/extensions/Theme_Builder/Theme_Builder.php

King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder, version 51.1.44. 1,392 lines.

- Page: https://pluginprobe.com/plugins/king-addons/51.1.44/code/includes/extensions/Theme_Builder/Theme_Builder.php
- Raw: https://pluginprobe.com/plugins/king-addons/51.1.44/raw/includes/extensions/Theme_Builder/Theme_Builder.php
- Modified: 2025-12-27T05:19:20+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/king-addons/51.1.44/code/includes/extensions/Theme_Builder/Theme_Builder.php#L10-L20`.

```php
<?php
/**
 * Theme Builder feature bootstrap.
 *
 * @package King_Addons
 */

namespace King_Addons;

use Elementor\Controls_Manager;
use Elementor\Plugin as Elementor_Plugin;
use King_Addons\Theme_Builder\Admin_List_Table;
use King_Addons\Theme_Builder\Conditions;
use King_Addons\Theme_Builder\Context;
use King_Addons\Theme_Builder\Meta_Keys;
use King_Addons\Theme_Builder\Repository;
use King_Addons\Theme_Builder\Resolver;
use King_Addons\Woo_Builder\Context as Woo_Context;
use WP_Post;

if (!defined('ABSPATH')) {
    exit;
}

/**
 * Registers Theme Builder feature and integrates with WordPress and Elementor.
 */
class Theme_Builder
{
    /**
     * Admin menu slug.
     *
     * @var string
     */
    private string $menu_slug = 'king-addons-theme-builder';

    /**
     * Repository instance.
     *
     * @var Repository
     */
    private Repository $repository;

    /**
     * Resolver instance.
     *
     * @var Resolver
     */
    private Resolver $resolver;

    /**
     * Resolved template ID for current request.
     *
     * @var int|null
     */
    private ?int $current_template_id = null;

    /**
     * Constructor.
     */
    public function __construct()
    {
        $this->require_helpers();

        $this->repository = new Repository();
        $this->resolver = new Resolver($this->repository);

        // Use priority 15 to ensure the parent menu exists before adding this submenu
        add_action('admin_menu', [$this, 'register_admin_menu'], 15);
        add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']);
        add_action('add_meta_boxes', [$this, 'register_meta_boxes']);
        add_action('save_post', [$this, 'handle_save_post'], 10, 2);
        add_action('delete_post', [$this, 'handle_delete_post']);
        add_action('transition_post_status', [$this, 'handle_status_transition'], 10, 3);
        add_action('admin_post_ka_theme_builder_create', [$this, 'handle_create_template']);
        add_action('admin_post_ka_theme_builder_quick_update', [$this, 'handle_quick_update']);
        add_action('admin_post_ka_theme_builder_duplicate', [$this, 'handle_duplicate_template']);
        add_action('elementor/preview/enqueue_scripts', [$this, 'enqueue_preview_handler']);
        add_action('elementor/documents/register_controls', [$this, 'register_document_controls']);
        add_action('elementor/document/after_save', [$this, 'handle_document_after_save'], 10, 2);
        add_filter('template_include', [$this, 'handle_template_include'], 99);
        add_filter('king_addons/theme_builder/current_template_id', [$this, 'filter_current_template_id']);
        add_filter('body_class', [$this, 'filter_body_class']);
    }

    /**
     * Require helper files for Theme Builder.
     *
     * @return void
     */
    private function require_helpers(): void
    {
        require_once KING_ADDONS_PATH . 'includes/helpers/Theme_Builder/Meta_Keys.php';
        require_once KING_ADDONS_PATH . 'includes/helpers/Theme_Builder/Context.php';
        require_once KING_ADDONS_PATH . 'includes/helpers/Theme_Builder/Conditions.php';
        require_once KING_ADDONS_PATH . 'includes/helpers/Theme_Builder/Repository.php';
        require_once KING_ADDONS_PATH . 'includes/helpers/Theme_Builder/Resolver.php';
        require_once KING_ADDONS_PATH . 'includes/helpers/Theme_Builder/Admin_List_Table.php';
    }

    /**
     * Register admin menu entry.
     *
     * @return void
     */
    public function register_admin_menu(): void
    {
        add_menu_page(
            esc_html__('Theme Builder', 'king-addons'),
            esc_html__('Theme Builder', 'king-addons'),
            'manage_options',
            $this->menu_slug,
            [$this, 'render_admin_page'],
            'dashicons-layout',
            54.6
        );
    }

    /**
     * Enqueue admin assets for Theme Builder pages.
     *
     * @param string $hook Current admin page hook.
     *
     * @return void
     */
    public function enqueue_admin_assets(string $hook): void
    {
        if (false === strpos($hook, $this->menu_slug)) {
            return;
        }

        wp_enqueue_style(
            KING_ADDONS_ASSETS_UNIQUE_KEY . '-theme-builder-admin',
            KING_ADDONS_URL . 'includes/extensions/Theme_Builder/style.css',
            [],
            KING_ADDONS_VERSION
        );

        wp_enqueue_script(
            KING_ADDONS_ASSETS_UNIQUE_KEY . '-theme-builder-admin',
            KING_ADDONS_URL . 'includes/extensions/Theme_Builder/script.js',
            ['jquery'],
            KING_ADDONS_VERSION,
            true
        );
    }

    /**
     * Register meta boxes for Elementor templates.
     *
     * @return void
     */
    public function register_meta_boxes(): void
    {
        add_meta_box(
            'king-addons-theme-builder',
            esc_html__('Theme Builder', 'king-addons'),
            [$this, 'render_meta_box'],
            'elementor_library',
            'side',
            'default'
        );
    }

    /**
     * Render Theme Builder meta box.
     *
     * @param WP_Post $post Current post.
     *
     * @return void
     */
    public function render_meta_box(WP_Post $post): void
    {
        wp_nonce_field('ka_theme_builder_meta_box', 'ka_theme_builder_meta_box_nonce');

        $location = get_post_meta($post->ID, Meta_Keys::LOCATION, true);
        $sub_location = get_post_meta($post->ID, Meta_Keys::SUB_LOCATION, true);
        $priority = (int) get_post_meta($post->ID, Meta_Keys::PRIORITY, true);
        $enabled = '1' === (string) get_post_meta($post->ID, Meta_Keys::ENABLED, true);

        echo '<p><strong>' . esc_html__('Location', 'king-addons') . ':</strong> ' . esc_html($sub_location ?: '-') . '</p>';
        echo '<p><strong>' . esc_html__('Type', 'king-addons') . ':</strong> ' . esc_html($location ?: '-') . '</p>';
        echo '<p><strong>' . esc_html__('Priority', 'king-addons') . ':</strong> ' . esc_html((string) ($priority ?: 10)) . '</p>';
        echo '<p><strong>' . esc_html__('Status', 'king-addons') . ':</strong> ' . ($enabled ? esc_html__('Enabled', 'king-addons') : esc_html__('Disabled', 'king-addons')) . '</p>';
        echo '<p><a href="' . esc_url(admin_url('admin.php?page=' . $this->menu_slug)) . '">' . esc_html__('Manage in Theme Builder', 'king-addons') . '</a></p>';
    }

    /**
     * Handle meta save for Elementor Library posts.
     *
     * @param int     $post_id Post ID.
     * @param WP_Post $post    Post object.
     *
     * @return void
     */
    public function handle_save_post(int $post_id, WP_Post $post): void
    {
        if ('elementor_library' !== $post->post_type) {
            return;
        }

        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
            return;
        }

        // Keep cache fresh if any relevant meta changes.
        $this->repository->clear_cache();
    }

    /**
     * Clear cache on delete.
     *
     * @return void
     */
    public function handle_delete_post(): void
    {
        $this->repository->clear_cache();
    }

    /**
     * Clear cache on status transition.
     *
     * @param string  $new_status New status.
     * @param string  $old_status Old status.
     * @param WP_Post $post       Post object.
     *
     * @return void
     */
    public function handle_status_transition(string $new_status, string $old_status, WP_Post $post): void
    {
        if ('elementor_library' !== $post->post_type || $new_status === $old_status) {
            return;
        }

        $this->repository->clear_cache();
    }

    /**
     * Render admin page for Theme Builder.
     *
     * @return void
     */
    public function render_admin_page(): void
    {
        if (!current_user_can('manage_options')) {
            return;
        }

        $this->handle_inline_actions();

        $templates = $this->prepare_admin_templates();
        $table = new Admin_List_Table($templates, admin_url('admin.php?page=' . $this->menu_slug));
        $table->prepare_items();

        // Enqueue shared V3 styles
        wp_enqueue_style(
            'king-addons-admin-v3',
            KING_ADDONS_URL . 'includes/admin/layouts/shared/admin-v3-styles.css',
            [],
            KING_ADDONS_VERSION
        );

        $this->render_modern_styles();

        // Theme mode is per-user
        $theme_mode = get_user_meta(get_current_user_id(), 'king_addons_theme_mode', true);
        $allowed_theme_modes = ['dark', 'light', 'auto'];
        if (!in_array($theme_mode, $allowed_theme_modes, true)) {
            $theme_mode = 'dark';
        }
        ?>
        <script>
        (function() {
            const mode = '<?php echo esc_js($theme_mode); ?>';
            const mql = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
            const isDark = mode === 'auto' ? !!(mql && mql.matches) : mode === 'dark';
            document.documentElement.classList.toggle('ka-v3-dark', isDark);
            document.body.classList.add('ka-admin-v3');
            document.body.classList.toggle('ka-v3-dark', isDark);
        })();
        </script>
        <div class="ka-admin-wrap ka-tb-wrap">
            <!-- Header -->
            <div class="ka-admin-header">
                <div class="ka-admin-header-left">
                    <div class="ka-admin-header-icon">
                        <span class="dashicons dashicons-layout"></span>
                    </div>
                    <div>
                        <h1 class="ka-admin-title"><?php esc_html_e('Theme Builder', 'king-addons'); ?></h1>
                        <p class="ka-admin-subtitle"><?php esc_html_e('Create custom templates for your site', 'king-addons'); ?></p>
                    </div>
                </div>
                <div class="ka-admin-header-actions">
                    <button type="button" id="ka-theme-builder-add-new" class="ka-btn ka-btn-primary">
                        <span class="dashicons dashicons-plus-alt2"></span>
                        <?php esc_html_e('Add New Template', 'king-addons'); ?>
                    </button>
                    <div class="ka-v3-segmented" id="ka-v3-theme-segment" role="radiogroup" aria-label="Theme" data-active="<?php echo esc_attr($theme_mode); ?>">
                        <span class="ka-v3-segmented-indicator" aria-hidden="true"></span>
                        <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>
                        <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>
                        <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>
                    </div>
                </div>
            </div>

            <?php $this->render_add_new_modal(); ?>
            <?php $this->render_quick_edit_modal(); ?>

            <div class="ka-card">
                <div class="ka-card-header">
                    <span class="dashicons dashicons-admin-page" style="color: #0071e3;"></span>
                    <h2><?php esc_html_e('Templates', 'king-addons'); ?></h2>
                </div>
                <div class="ka-card-body" style="padding: 0;">
                    <form method="post">
                        <?php $table->views(); ?>
                        <?php $table->display(); ?>
                    </form>
                </div>
            </div>
        </div>
        <script>
        (function() {
            const ajaxUrl = '<?php echo esc_url(admin_url('admin-ajax.php')); ?>';
            const nonce = '<?php echo esc_js(wp_create_nonce('king_addons_dashboard_ui')); ?>';
            const segment = document.getElementById('ka-v3-theme-segment');
            const mql = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
            let mode = (segment && segment.getAttribute('data-active') ? segment.getAttribute('data-active') : 'dark').toString();
            let mqlHandler = null;

            function saveUISetting(key, value) {
                const body = new URLSearchParams();
                body.set('action', 'king_addons_save_dashboard_ui');
                body.set('nonce', nonce);
                body.set('key', key);
                body.set('value', value);
                fetch(ajaxUrl, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
                    body: body.toString()
                });
            }

            function updateSegment(activeMode) {
                if (!segment) {
                    return;
                }
                segment.setAttribute('data-active', activeMode);
                segment.querySelectorAll('.ka-v3-segmented-btn').forEach((btn) => {
                    const theme = btn.getAttribute('data-theme');
                    btn.setAttribute('aria-pressed', theme === activeMode ? 'true' : 'false');
                });
            }

            function applyTheme(isDark) {
                document.body.classList.toggle('ka-v3-dark', isDark);
                document.documentElement.classList.toggle('ka-v3-dark', isDark);
            }

            function setThemeMode(nextMode, save) {
                mode = nextMode;
                updateSegment(nextMode);

                if (mqlHandler && mql) {
                    if (mql.removeEventListener) {
                        mql.removeEventListener('change', mqlHandler);
                    } else if (mql.removeListener) {
                        mql.removeListener(mqlHandler);
                    }
                    mqlHandler = null;
                }

                if (nextMode === 'auto') {
                    applyTheme(!!(mql && mql.matches));
                    mqlHandler = (e) => {
                        if (mode !== 'auto') {
                            return;
                        }
                        applyTheme(!!e.matches);
                    };
                    if (mql) {
                        if (mql.addEventListener) {
                            mql.addEventListener('change', mqlHandler);
                        } else if (mql.addListener) {
                            mql.addListener(mqlHandler);
                        }
                    }
                } else {
                    applyTheme(nextMode === 'dark');
                }

                if (save) {
                    saveUISetting('theme_mode', nextMode);
                }
            }

            window.kaV3ToggleDark = function() {
                const isDark = document.body.classList.contains('ka-v3-dark');
                setThemeMode(isDark ? 'light' : 'dark', true);
            };

            if (segment) {
                segment.addEventListener('click', function(e) {
                    const btn = e.target.closest('.ka-v3-segmented-btn');
                    if (!btn) return;
                    e.preventDefault();
                    setThemeMode((btn.getAttribute('data-theme') || 'dark').toString(), true);
                });
            }

            if (segment) {
                setThemeMode(mode, false);
            }
        })();
        </script>
        <?php
    }

    /**
     * Render modern CSS styles for Theme Builder admin page.
     *
     * @return void
     */
    private function render_modern_styles(): void
    {
        ?>
        <style>
        /* Theme Builder V3 Additional Styles */
        .ka-tb-wrap {
            max-width: 1400px;
        }

        /* Table overrides for V3 */
        .ka-tb-wrap .wp-list-table { 
            border: none; 
        }
        .ka-tb-wrap .wp-list-table th {
            background: rgba(0, 0, 0, 0.02);
            padding: 14px 20px;
            font-size: 11px;
            font-weight: 600;
            color: #86868b;
            text-transform: uppercase;
            letter-spacing: 0.5px;
            border-bottom: 1px solid rgba(0, 0, 0, 0.04);
        }
        body.ka-v3-dark .ka-tb-wrap .wp-list-table th {
            background: rgba(255, 255, 255, 0.02);
            color: #86868b;
            border-bottom-color: rgba(255, 255, 255, 0.04);
        }
        .ka-tb-wrap .wp-list-table td {
            padding: 14px 20px;
            border-bottom: 1px solid rgba(0, 0, 0, 0.04);
            font-size: 14px;
            color: #1d1d1f;
        }
        body.ka-v3-dark .ka-tb-wrap .wp-list-table td {
            color: #f5f5f7;
            border-bottom-color: rgba(255, 255, 255, 0.04);
        }
        .ka-tb-wrap .wp-list-table tbody tr:hover td { 
            background: rgba(0, 0, 0, 0.02); 
        }
        body.ka-v3-dark .ka-tb-wrap .wp-list-table tbody tr:hover td { 
            background: rgba(255, 255, 255, 0.02); 
        }
        .ka-tb-wrap .wp-list-table .row-actions { 
            font-size: 12px; 
        }
        .ka-tb-wrap .wp-list-table .row-actions a {
            color: #0071e3;
        }
        body.ka-v3-dark .ka-tb-wrap .wp-list-table .row-actions a {
            color: #2997ff;
        }
        .ka-tb-wrap .subsubsub { 
            margin: 16px 24px; 
        }
        .ka-tb-wrap .subsubsub a {
            color: #1d1d1f;
        }
        body.ka-v3-dark .ka-tb-wrap .subsubsub a {
            color: #f5f5f7;
        }
        .ka-tb-wrap .subsubsub a.current {
            color: #0071e3;
        }
        body.ka-v3-dark .ka-tb-wrap .subsubsub a.current {
            color: #2997ff;
        }
        .ka-tb-wrap .tablenav { 
            padding: 12px 24px; 
        }

        /* Modal V3 */
        .ka-theme-builder-modal { 
            position: fixed; 
            inset: 0; 
            z-index: 100000; 
            display: flex; 
            align-items: center; 
            justify-content: center; 
        }
        .ka-theme-builder-modal__backdrop { 
            position: absolute; 
            inset: 0; 
            background: rgba(0,0,0,0.5); 
            backdrop-filter: blur(20px);
            -webkit-backdrop-filter: blur(20px);
        }
        .ka-theme-builder-modal__content {
            position: relative; 
            background: #fff;
            border-radius: 20px; 
            padding: 32px;
            max-width: 500px; 
            width: 90%;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
            animation: kaModalIn 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
        }
        body.ka-v3-dark .ka-theme-builder-modal__content {
            background: #1c1c1e;
        }
        @keyframes kaModalIn {
            from { opacity: 0; transform: scale(0.95) translateY(-10px); }
            to { opacity: 1; transform: scale(1) translateY(0); }
        }
        .ka-theme-builder-modal__content h2 {
            margin: 0 0 24px; 
            font-size: 22px; 
            font-weight: 600; 
            color: #1d1d1f;
        }
        body.ka-v3-dark .ka-theme-builder-modal__content h2 {
            color: #f5f5f7;
        }
        .ka-theme-builder-modal__content p { 
            margin-bottom: 16px; 
            color: #86868b;
        }
        .ka-theme-builder-modal__content label {
            display: block; 
            font-size: 14px; 
            font-weight: 500;
            color: #1d1d1f; 
            margin-bottom: 8px;
        }
        body.ka-v3-dark .ka-theme-builder-modal__content label {
            color: #f5f5f7;
        }
        .ka-theme-builder-modal__content input[type="text"],
        .ka-theme-builder-modal__content input[type="number"],
        .ka-theme-builder-modal__content select {
            width: 100%; 
            padding: 12px 16px;
            border: 1px solid rgba(0, 0, 0, 0.1); 
            border-radius: 12px;
            font-size: 15px; 
            background: #fff;
            transition: border-color 0.2s, box-shadow 0.2s;
        }
        body.ka-v3-dark .ka-theme-builder-modal__content input[type="text"],
        body.ka-v3-dark .ka-theme-builder-modal__content input[type="number"],
        body.ka-v3-dark .ka-theme-builder-modal__content select {
            background: #2c2c2e;
            border-color: rgba(255, 255, 255, 0.1);
            color: #f5f5f7;
        }
        .ka-theme-builder-modal__content input:focus,
        .ka-theme-builder-modal__content select:focus {
            outline: none; 
            border-color: #0071e3;
            box-shadow: 0 0 0 4px rgba(0, 113, 227, 0.1);
        }
        body.ka-v3-dark .ka-theme-builder-modal__content input:focus,
        body.ka-v3-dark .ka-theme-builder-modal__content select:focus {
            border-color: #2997ff;
            box-shadow: 0 0 0 4px rgba(41, 151, 255, 0.2);
        }
        .ka-theme-builder-modal__content .submit {
            margin-top: 24px; 
            display: flex; 
            gap: 12px;
        }
        .ka-theme-builder-modal__content .button-primary {
            background: #0071e3;
            border: none; 
            padding: 12px 24px;
            font-size: 15px; 
            font-weight: 400;
            border-radius: 980px; 
            cursor: pointer; 
            color: #fff;
            transition: all 0.3s;
        }
        .ka-theme-builder-modal__content .button-primary:hover {
            background: #0077ed;
        }
        .ka-theme-builder-modal__content .button {
            padding: 12px 20px; 
            border-radius: 980px;
            font-size: 15px; 
            cursor: pointer;
            background: rgba(0, 0, 0, 0.06);
            border: none;
            color: #1d1d1f;
        }
        body.ka-v3-dark .ka-theme-builder-modal__content .button {
            background: rgba(255, 255, 255, 0.1);
            color: #f5f5f7;
        }
        .ka-theme-builder-modal__content input[type="checkbox"] {
            width: 18px; 
            height: 18px; 
            margin-right: 8px; 
            vertical-align: middle;
        }

        /* Responsive */
        @media (max-width: 782px) {
            .ka-tb-wrap .ka-admin-header { 
                flex-direction: column; 
                text-align: center; 
                gap: 16px; 
            }
        }
        </style>
        <?php
    }

    /**
     * Enqueue front-end assets placeholder.
     *
     * @return void
     */
    public function enqueue_front_assets(): void
    {
        // Reserved for future front-end assets; keep file reference valid.
    }

    /**
     * Enqueue preview handler for Elementor editor.
     *
     * @return void
     */
    public function enqueue_preview_handler(): void
    {
        $post_id = get_the_ID() ? (int) get_the_ID() : get_queried_object_id();

        if (!$post_id || 'elementor_library' !== get_post_type($post_id)) {
            return;
        }

        if (!get_post_meta($post_id, Meta_Keys::LOCATION, true)) {
            return;
        }

        wp_enqueue_script(
            KING_ADDONS_ASSETS_UNIQUE_KEY . '-theme-builder-preview',
                KING_ADDONS_URL . 'includes/extensions/Theme_Builder/preview-handler.js',
            ['jquery'],
            KING_ADDONS_VERSION,
            true
        );
    }

    /**
     * Register Elementor document controls for Theme Builder templates.
     *
     * @param mixed $document Document instance.
     *
     * @return void
     */
    public function register_document_controls($document): void
    {
        if (!is_object($document) || !method_exists($document, 'get_main_id')) {
            return;
        }

        $post_id = (int) $document->get_main_id();
        if ('elementor_library' !== get_post_type($post_id)) {
            return;
        }
        $location = get_post_meta($post_id, Meta_Keys::LOCATION, true);

        if (!$location) {
            return;
        }

        $sub_location = get_post_meta($post_id, Meta_Keys::SUB_LOCATION, true);
        $preview_post = (int) get_post_meta($post_id, Meta_Keys::PREVIEW_POST_ID, true);
        $preview_term = (int) get_post_meta($post_id, Meta_Keys::PREVIEW_TERM_ID, true);
        $preview_author = (int) get_post_meta($post_id, Meta_Keys::PREVIEW_AUTHOR_ID, true);
        $preview_query = get_post_meta($post_id, Meta_Keys::PREVIEW_QUERY, true);

        $document->start_controls_section(
            'ka_theme_builder_settings',
            [
                'label' => esc_html__('Theme Builder', 'king-addons'),
                'tab' => Controls_Manager::TAB_SETTINGS,
            ]
        );

        $document->add_control(
            'ka_tb_readonly_type',
            [
                'label' => esc_html__('Template Type', 'king-addons'),
                'type' => Controls_Manager::RAW_HTML,
                'raw' => '<strong>' . esc_html($location) . '</strong>',
                'content_classes' => 'ka-theme-builder-readonly',
            ]
        );

        $document->add_control(
            'ka_tb_readonly_location',
            [
                'label' => esc_html__('Location', 'king-addons'),
                'type' => Controls_Manager::RAW_HTML,
                'raw' => '<strong>' . esc_html($sub_location) . '</strong>',
                'content_classes' => 'ka-theme-builder-readonly',
            ]
        );

        $document->add_control(
            'ka_tb_preview_post',
            [
                'label' => esc_html__('Preview Post ID', 'king-addons'),
                'type' => Controls_Manager::NUMBER,
                'default' => $preview_post,
            ]
        );

        $document->add_control(
            'ka_tb_preview_term',
            [
                'label' => esc_html__('Preview Term ID', 'king-addons'),
                'type' => Controls_Manager::NUMBER,
                'default' => $preview_term,
            ]
        );

        $document->add_control(
            'ka_tb_preview_author',
            [
                'label' => esc_html__('Preview Author ID', 'king-addons'),
                'type' => Controls_Manager::NUMBER,
                'default' => $preview_author,
            ]
        );

        $document->add_control(
            'ka_tb_preview_query',
            [
                'label' => esc_html__('Preview Search Query', 'king-addons'),
                'type' => Controls_Manager::TEXT,
                'default' => $preview_query,
            ]
        );

        if ($this->is_pro_location($sub_location)) {
            $document->add_control(
                'ka_tb_pro_notice',
                [
                    'label' => esc_html__('Pro', 'king-addons'),
                    'type' => Controls_Manager::RAW_HTML,
                    'raw' => esc_html__('Advanced conditions require Pro.', 'king-addons'),
                    'content_classes' => 'ka-theme-builder-readonly',
                ]
            );
        }

        $document->end_controls_section();
    }

    /**
     * Persist preview meta from Elementor document settings.
     *
     * @param \Elementor\Core\Base\Document $document Elementor document.
     * @param array<string,mixed>           $data     Saved data.
     *
     * @return void
     */
    public function handle_document_after_save($document, array $data): void
    {
        if (!is_object($document) || !method_exists($document, 'get_main_id')) {
            return;
        }

        $post_id = (int) $document->get_main_id();
        if ('elementor_library' !== get_post_type($post_id)) {
            return;
        }

        if (!get_post_meta($post_id, Meta_Keys::LOCATION, true)) {
            return;
        }

        $preview_post = (int) $document->get_settings('ka_tb_preview_post');
        $preview_term = (int) $document->get_settings('ka_tb_preview_term');
        $preview_author = (int) $document->get_settings('ka_tb_preview_author');
        $preview_query = sanitize_text_field((string) $document->get_settings('ka_tb_preview_query'));

        update_post_meta($post_id, Meta_Keys::PREVIEW_POST_ID, $preview_post);
        update_post_meta($post_id, Meta_Keys::PREVIEW_TERM_ID, $preview_term);
        update_post_meta($post_id, Meta_Keys::PREVIEW_AUTHOR_ID, $preview_author);
        update_post_meta($post_id, Meta_Keys::PREVIEW_QUERY, $preview_query);

        $this->repository->clear_cache();
    }

    /**
     * Handle "Add New" submission.
     *
     * @return void
     */
    public function handle_create_template(): void
    {
        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('You do not have permission to perform this action.', 'king-addons'));
        }

        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
            wp_die(esc_html__('Invalid nonce.', 'king-addons'));
        }

        $title = isset($_POST['ka_tb_title']) ? sanitize_text_field(wp_unslash($_POST['ka_tb_title'])) : esc_html__('New Theme Template', 'king-addons');
        $type = isset($_POST['ka_tb_type']) ? sanitize_text_field(wp_unslash($_POST['ka_tb_type'])) : 'single';
        $sub_location = isset($_POST['ka_tb_location']) ? sanitize_text_field(wp_unslash($_POST['ka_tb_location'])) : '';
        $type = $this->normalize_type($type, $sub_location);

        // Check Free tier limit: 1 active template per primary type
        if (!$this->can_use_pro()) {
            $primary_type = $this->normalize_type($type, $sub_location);
            if ($this->count_enabled_templates_by_type($primary_type) >= 1) {
                wp_die(
                    sprintf(
                        /* translators: %s: template type name */
                        esc_html__('Free version allows only 1 active %s template. Disable an existing template or upgrade to Pro for unlimited templates.', 'king-addons'),
                        esc_html($primary_type)
                    )
                );
            }
        }

        $post_id = wp_insert_post([
            'post_type' => 'elementor_library',
            'post_status' => 'publish',
            'post_title' => $title,
        ]);

        if (is_wp_error($post_id)) {
            wp_die(esc_html__('Unable to create template.', 'king-addons'));
        }

        $conditions = $this->build_default_conditions($sub_location);

        $is_pro = Conditions::is_pro_only($conditions) || $this->is_pro_location($sub_location);
        $enabled = $is_pro && !$this->can_use_pro() ? '0' : '1';

        update_post_meta($post_id, Meta_Keys::ENABLED, $enabled);
        update_post_meta($post_id, Meta_Keys::LOCATION, $type);
        update_post_meta($post_id, Meta_Keys::SUB_LOCATION, $sub_location);
        update_post_meta($post_id, Meta_Keys::PRIORITY, 10);
        update_post_meta($post_id, Meta_Keys::IS_PRO_ONLY, $is_pro ? '1' : '0');
        update_post_meta($post_id, Meta_Keys::CONDITIONS, wp_json_encode($conditions));

        $this->repository->clear_cache();

        wp_safe_redirect(admin_url('post.php?post=' . $post_id . '&action=elementor'));
        exit;
    }

    /**
     * Handle quick update submission.
     *
     * @return void
     */
    public function handle_quick_update(): void
    {
        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('You do not have permission to perform this action.', 'king-addons'));
        }

        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
            wp_die(esc_html__('Invalid nonce.', 'king-addons'));
        }

        $template_id = isset($_POST['template_id']) ? (int) $_POST['template_id'] : 0;
        $priority = isset($_POST['priority']) ? (int) $_POST['priority'] : 10;
        $enabled = isset($_POST['enabled']) && '1' === $_POST['enabled']; // phpcs:ignore WordPress.Security.NonceVerification.Missing

        if ($template_id > 0) {
            update_post_meta($template_id, Meta_Keys::PRIORITY, $priority);
            update_post_meta($template_id, Meta_Keys::ENABLED, $enabled ? '1' : '0');
            $this->repository->clear_cache();
        }

        wp_safe_redirect(admin_url('admin.php?page=' . $this->menu_slug));
        exit;
    }

    /**
     * Handle template duplication (Pro feature).
     *
     * @return void
     */
    public function handle_duplicate_template(): void
    {
        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('You do not have permission to perform this action.', 'king-addons'));
        }

        if (!$this->can_use_pro()) {
            wp_die(esc_html__('Template duplication requires Pro.', 'king-addons'));
        }

        $template_id = isset($_GET['template_id']) ? (int) $_GET['template_id'] : 0;

        if (!$template_id || !wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'] ?? '')), 'ka_theme_builder_duplicate_' . $template_id)) {
            wp_die(esc_html__('Invalid request.', 'king-addons'));
        }

        $original = get_post($template_id);
        if (!$original || 'elementor_library' !== $original->post_type) {
            wp_die(esc_html__('Template not found.', 'king-addons'));
        }

        // Clone the post
        $new_post_id = wp_insert_post([
            'post_type' => 'elementor_library',
            'post_status' => 'draft',
            'post_title' => $original->post_title . ' ' . esc_html__('(Copy)', 'king-addons'),
            'post_content' => $original->post_content,
        ]);

        if (is_wp_error($new_post_id)) {
            wp_die(esc_html__('Failed to duplicate template.', 'king-addons'));
        }

        // Copy all meta data
        $meta_keys = [
            Meta_Keys::ENABLED,
            Meta_Keys::LOCATION,
            Meta_Keys::SUB_LOCATION,
            Meta_Keys::CONDITIONS,
            Meta_Keys::PRIORITY,
            Meta_Keys::IS_PRO_ONLY,
            Meta_Keys::PREVIEW_POST_ID,
            Meta_Keys::PREVIEW_TERM_ID,
            Meta_Keys::PREVIEW_AUTHOR_ID,
            Meta_Keys::PREVIEW_QUERY,
        ];

        foreach ($meta_keys as $key) {
            $value = get_post_meta($template_id, $key, true);
            if ('' !== $value) {
                update_post_meta($new_post_id, $key, $value);
            }
        }

        // Disable the duplicate by default to avoid conflicts
        update_post_meta($new_post_id, Meta_Keys::ENABLED, '0');

        // Copy Elementor data
        $elementor_data = get_post_meta($template_id, '_elementor_data', true);
        if ($elementor_data) {
            update_post_meta($new_post_id, '_elementor_data', $elementor_data);
        }

        $elementor_edit_mode = get_post_meta($template_id, '_elementor_edit_mode', true);
        if ($elementor_edit_mode) {
            update_post_meta($new_post_id, '_elementor_edit_mode', $elementor_edit_mode);
        }

        $this->repository->clear_cache();

        wp_safe_redirect(admin_url('admin.php?page=' . $this->menu_slug . '&duplicated=1'));
        exit;
    }

    /**
     * Handle template include override.
     *
     * @param string $template Current template path.
     *
     * @return string
     */
    public function handle_template_include(string $template): string
    {
        if (is_admin() || wp_doing_ajax() || (defined('REST_REQUEST') && REST_REQUEST)) {
            return $template;
        }

        if (!did_action('elementor/loaded')) {
            return $template;
        }

        if (is_singular('elementor_library')) {
            return $template;
        }

        if ($this->is_woo_context()) {
            return $template;
        }

        $context = Context::from_request();
        $resolved = $this->resolver->resolve($context);

        if (!$resolved) {
            return $template;
        }

        $this->current_template_id = $resolved;

        return KING_ADDONS_PATH . 'includes/extensions/Theme_Builder/templates/theme-builder.php';
    }

    /**
     * Provide current template ID to filters.
     *
     * @param int $template_id Existing template id.
     *
     * @return int
     */
    public function filter_current_template_id(int $template_id): int
    {
        if ($this->current_template_id) {
            return (int) $this->current_template_id;
        }

        return (int) $template_id;
    }

    /**
     * Append body class for resolved template.
     *
     * @param array<int,string> $classes Body classes.
     *
     * @return array<int,string>
     */
    public function filter_body_class(array $classes): array
    {
        if ($this->current_template_id) {
            $classes[] = 'king-addons-theme-builder-active';
        }

        return $classes;
    }

    /**
     * Render add-new modal markup.
     *
     * @return void
     */
    private function render_add_new_modal(): void
    {
        ?>
        <div id="ka-theme-builder-modal" class="ka-theme-builder-modal" style="display:none;">
            <div class="ka-theme-builder-modal__backdrop"></div>
            <div class="ka-theme-builder-modal__content">
                <h2><?php echo esc_html__('Create Theme Template', 'king-addons'); ?></h2>
                <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
                    <input type="hidden" name="action" value="ka_theme_builder_create" />
                    <?php wp_nonce_field('ka_theme_builder_create', 'ka_theme_builder_create_nonce'); ?>
                    <p>
                        <label for="ka-tb-title"><?php echo esc_html__('Template Name', 'king-addons'); ?></label>
                        <input type="text" id="ka-tb-title" name="ka_tb_title" class="regular-text" value="<?php echo esc_attr__('Theme Template', 'king-addons'); ?>" />
                    </p>
                    <p>
                        <label for="ka-tb-type"><?php echo esc_html__('Template Type', 'king-addons'); ?></label>
                        <select id="ka-tb-type" name="ka_tb_type">
                            <option value="single"><?php echo esc_html__('Single', 'king-addons'); ?></option>
                            <option value="archive"><?php echo esc_html__('Archive', 'king-addons'); ?></option>
                            <option value="author"><?php echo esc_html__('Author', 'king-addons'); ?></option>
                            <option value="search"><?php echo esc_html__('Search', 'king-addons'); ?></option>
                            <option value="not_found"><?php echo esc_html__('404', 'king-addons'); ?></option>
                        </select>
                    </p>
                    <p>
                        <label for="ka-tb-location"><?php echo esc_html__('Location', 'king-addons'); ?></label>
                        <select id="ka-tb-location" name="ka_tb_location">
                            <option value="single_post"><?php echo esc_html__('All Posts', 'king-addons'); ?></option>
                            <option value="single_page"><?php echo esc_html__('All Pages', 'king-addons'); ?></option>
                            <option value="single_cpt"><?php echo esc_html__('Custom Post Types (Pro)', 'king-addons'); ?></option>
                            <option value="archive_blog"><?php echo esc_html__('Blog / Posts Page', 'king-addons'); ?></option>
                            <option value="archive_category"><?php echo esc_html__('All Categories', 'king-addons'); ?></option>
                            <option value="archive_tag"><?php echo esc_html__('All Tags', 'king-addons'); ?></option>
                            <option value="archive_tax"><?php echo esc_html__('Custom Taxonomy (Pro)', 'king-addons'); ?></option>
                            <option value="author_all"><?php echo esc_html__('All Authors (Pro)', 'king-addons'); ?></option>
                            <option value="author_specific"><?php echo esc_html__('Specific Authors (Pro)', 'king-addons'); ?></option>
                            <option value="search_results"><?php echo esc_html__('Search Results', 'king-addons'); ?></option>
                            <option value="not_found"><?php echo esc_html__('404 Page', 'king-addons'); ?></option>
                        </select>
                    </p>
                    <p class="submit">
                        <button type="submit" class="button button-primary"><?php echo esc_html__('Create and Edit with Elementor', 'king-addons'); ?></button>
                        <button type="button" class="button ka-theme-builder-modal__close"><?php echo esc_html__('Cancel', 'king-addons'); ?></button>
                    </p>
                </form>
            </div>
        </div>
        <?php
    }

    /**
     * Render quick edit modal markup.
     *
     * @return void
     */
    private function render_quick_edit_modal(): void
    {
        ?>
        <div id="ka-theme-builder-quick-modal" class="ka-theme-builder-modal" style="display:none;">
            <div class="ka-theme-builder-modal__backdrop"></div>
            <div class="ka-theme-builder-modal__content">
                <h2><?php echo esc_html__('Quick Edit Template', 'king-addons'); ?></h2>
                <form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
                    <input type="hidden" name="action" value="ka_theme_builder_quick_update" />
                    <?php wp_nonce_field('ka_theme_builder_quick', 'ka_theme_builder_quick_nonce'); ?>
                    <input type="hidden" id="ka-tb-quick-template-id" name="template_id" value="" />
                    <p>
                        <label for="ka-tb-quick-priority"><?php echo esc_html__('Priority', 'king-addons'); ?></label>
                        <input type="number" id="ka-tb-quick-priority" name="priority" min="0" step="1" value="10" />
                    </p>
                    <p>
                        <label>
                            <input type="checkbox" id="ka-tb-quick-enabled" name="enabled" value="1" />
                            <?php echo esc_html__('Enabled', 'king-addons'); ?>
                        </label>
                    </p>
                    <p class="submit">
                        <button type="submit" class="button button-primary"><?php echo esc_html__('Save', 'king-addons'); ?></button>
                        <button type="button" class="button ka-theme-builder-modal__close"><?php echo esc_html__('Cancel', 'king-addons'); ?></button>
                    </p>
                </form>
            </div>
        </div>
        <?php
    }

    /**
     * Build default conditions for a selected location.
     *
     * @param string $sub_location Sub-location value.
     *
     * @return array<string,mixed>
     */
    private function build_default_conditions(string $sub_location): array
    {
        $groups = [];

        switch ($sub_location) {
            case 'single_post':
                $groups[] = [
                    'relation' => 'AND',
                    'rules' => [
                        [
                            'type' => 'include',
                            'target' => 'post_type',
                            'operator' => 'in',
                            'value' => ['post'],
                        ],
                    ],
                ];
                break;
            case 'single_page':
                $groups[] = [
                    'relation' => 'AND',
                    'rules' => [
                        [
                            'type' => 'include',
                            'target' => 'post_type',
                            'operator' => 'in',
                            'value' => ['page'],
                        ],
                    ],
                ];
                break;
            case 'search_results':
                $groups[] = [
                    'relation' => 'AND',
                    'rules' => [
                        [
                            'type' => 'include',
                            'target' => 'search',
                            'operator' => 'in',
                            'value' => ['search'],
                        ],
                    ],
                ];
                break;
            case 'not_found':
                $groups[] = [
                    'relation' => 'AND',
                    'rules' => [
                        [
                            'type' => 'include',
                            'target' => '404',
                            'operator' => 'in',
                            'value' => ['404'],
                        ],
                    ],
                ];
                break;
            default:
                $groups[] = [
                    'relation' => 'AND',
                    'rules' => [],
                ];
                break;
        }

        return ['groups' => $groups];
    }

    /**
     * Normalize template type based on selected sub-location.
     *
     * @param string $type         Base type from UI.
     * @param string $sub_location Selected sub-location.
     *
     * @return string
     */
    private function normalize_type(string $type, string $sub_location): string
    {
        if (strpos($sub_location, 'archive') === 0) {
            return 'archive';
        }

        if ('search_results' === $sub_location) {
            return 'search';
        }

        if ('not_found' === $sub_location) {
            return 'not_found';
        }

        if (strpos($sub_location, 'author') === 0) {
            return 'author';
        }

        return $type ?: 'single';
    }

    /**
     * Determine if sub-location is Pro-only.
     *
     * @param string $sub_location Sub-location identifier.
     *
     * @return bool
     */
    private function is_pro_location(string $sub_location): bool
    {
        if (in_array($sub_location, ['single_cpt', 'archive_tax', 'author_all', 'author_specific'], true)) {
            return true;
        }

        if (strpos($sub_location, 'single_') === 0 && !in_array($sub_location, ['single_post', 'single_page'], true)) {
            return true;
        }

        if (strpos($sub_location, 'archive_') === 0 && !in_array($sub_location, ['archive_blog', 'archive_category', 'archive_tag'], true)) {
            return true;
        }

        return false;
    }

    /**
     * Check whether Pro license is available.
     *
     * @return bool
     */
    private function can_use_pro(): bool
    {
        return function_exists('king_addons_freemius')
            && king_addons_freemius()->can_use_premium_code__premium_only();
    }

    /**
     * Count enabled templates of a given primary type.
     *
     * @param string $type Primary type (single, archive, search, not_found, author).
     *
     * @return int
     */
    private function count_enabled_templates_by_type(string $type): int
    {
        $templates = $this->repository->get_active_templates();
        $count = 0;

        foreach ($templates as $template) {
            if (empty($template['enabled'])) {
                continue;
            }

            $template_location = $template['location'] ?? '';

            if ($template_location === $type) {
                $count++;
            }
        }

        return $count;
    }

    /**
     * Prepare data for admin list table.
     *
     * @return array<int,array<string,mixed>>
     */
    private function prepare_admin_templates(): array
    {
        $templates = $this->repository->get_active_templates();
        $prepared = [];

        foreach ($templates as $template) {
            $post = get_post($template['id']);
            if (!$post instanceof WP_Post) {
                continue;
            }

            $prepared[] = [
                'id' => $template['id'],
                'title' => get_the_title($template['id']),
                'type' => $template['location'] ?? '',
                'sub_location' => $template['sub_location'] ?? '',
                'conditions' => $template['conditions'] ?? [],
                'priority' => $template['priority'] ?? 10,
                'enabled' => !empty($template['enabled']),
                'is_pro_only' => !empty($template['is_pro_only']),
            ];
        }

        return $prepared;
    }

    /**
     * Handle inline toggle/delete actions.
     *
     * @return void
     */
    private function handle_inline_actions(): void
    {
        $action = isset($_GET['action']) ? sanitize_text_field(wp_unslash($_GET['action'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        $template_id = isset($_GET['template_id']) ? (int) $_GET['template_id'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended

        if (!$template_id || empty($action)) {
            return;
        }

        if ('delete_template' === $action) {
            if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'] ?? '')), 'ka_theme_builder_delete_' . $template_id)) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
                return;
            }
            wp_trash_post($template_id);
            $this->repository->clear_cache();
        }

        if ('disable_template' === $action || 'enable_template' === $action) {
            if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'] ?? '')), 'ka_theme_builder_toggle_' . $template_id)) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
                return;
            }
            update_post_meta($template_id, Meta_Keys::ENABLED, 'enable_template' === $action ? '1' : '0');
            $this->repository->clear_cache();
        }
    }

    /**
     * Check if Woo Builder should own the context.
     *
     * @return bool
     */
    private function is_woo_context(): bool
    {
        if (!class_exists(Woo_Context::class)) {
            return false;
        }

        $context = Woo_Context::detect_context();

        return in_array($context, ['single_product', 'product_archive', 'cart', 'checkout', 'my_account'], true);
    }
}





```
