hasElementorCompatibility()) { // Initial requirements check require_once(KING_ADDONS_PATH . 'includes/helpers/Check_Requirements/Check_Requirements.php'); // Templates Catalog if ($this->isExtensionEnabled('templates-catalog', 'KING_ADDONS_EXT_TEMPLATES_CATALOG')) { require_once(KING_ADDONS_PATH . 'includes/TemplatesMap.php'); require_once(KING_ADDONS_PATH . 'includes/extensions/Templates/CollectionsMap.php'); require_once(KING_ADDONS_PATH . 'includes/extensions/Templates/Templates.php'); // Template Catalog Button for Elementor Editor require_once(KING_ADDONS_PATH . 'includes/extensions/Template_Catalog_Button/Template_Catalog_Button.php'); Template_Catalog_Button::instance(); } // Header & Footer Builder if ($this->isExtensionEnabled('header-footer-builder', 'KING_ADDONS_EXT_HEADER_FOOTER_BUILDER')) { require_once(KING_ADDONS_PATH . 'includes/extensions/Header_Footer_Builder/Header_Footer_Builder.php'); Header_Footer_Builder::instance(); } // Popup Builder if ($this->isExtensionEnabled('popup-builder', 'KING_ADDONS_EXT_POPUP_BUILDER')) { require_once(KING_ADDONS_PATH . 'includes/extensions/Popup_Builder/Popup_Builder.php'); Popup_Builder::instance(); } // Cookie / Consent Bar if ($this->isExtensionEnabled('cookie-consent', 'KING_ADDONS_EXT_COOKIE_CONSENT')) { require_once(KING_ADDONS_PATH . 'includes/extensions/Cookie_Consent/Cookie_Consent.php'); Cookie_Consent::instance(); } // Dynamic Tags // Loaded before the builders so their widgets can offer dynamic // values as soon as the editor asks Elementor for the tag list. if ($this->isExtensionEnabled('dynamic-tags', 'KING_ADDONS_EXT_DYNAMIC_TAGS')) { require_once(KING_ADDONS_PATH . 'includes/extensions/Dynamic_Tags/Dynamic_Tags.php'); if (class_exists('King_Addons\\Dynamic_Tags')) { new Dynamic_Tags(); } } // Loop Builder if ($this->isExtensionEnabled('loop-builder', 'KING_ADDONS_EXT_LOOP_BUILDER')) { require_once(KING_ADDONS_PATH . 'includes/extensions/Loop_Builder/Loop_Builder.php'); if (class_exists('King_Addons\\Loop_Builder')) { new Loop_Builder(); } } // WooCommerce Builder if ($this->isExtensionEnabled('woo-builder', 'KING_ADDONS_EXT_WOO_BUILDER')) { require_once(KING_ADDONS_PATH . 'includes/extensions/Woo_Builder/Woo_Builder.php'); if (class_exists('King_Addons\\Woo_Builder')) { new Woo_Builder(); } } // Sticky Contact Bar if ($this->isExtensionEnabled('sticky-contact-bar', 'KING_ADDONS_EXT_STICKY_CONTACT_BAR')) { require_once(KING_ADDONS_PATH . 'includes/extensions/Sticky_Contact_Bar/Sticky_Contact_Bar.php'); $pro_loaded = false; if ( function_exists('king_addons_freemius') && king_addons_freemius()->can_use_premium_code__premium_only() && defined('KING_ADDONS_PRO_PATH') ) { $pro_file_path = KING_ADDONS_PRO_PATH . 'includes/extensions/Sticky_Contact_Bar_Pro/Sticky_Contact_Bar_Pro.php'; if (file_exists($pro_file_path)) { require_once $pro_file_path; if (class_exists('King_Addons\\Sticky_Contact_Bar_Pro')) { new Sticky_Contact_Bar_Pro(); $pro_loaded = true; } } } if (!$pro_loaded && class_exists('King_Addons\\Sticky_Contact_Bar')) { new Sticky_Contact_Bar(); } } // Theme Builder if ($this->isExtensionEnabled('theme-builder', 'KING_ADDONS_EXT_THEME_BUILDER')) { require_once(KING_ADDONS_PATH . 'includes/extensions/Theme_Builder/Theme_Builder.php'); $pro_loaded = false; if ( function_exists('king_addons_freemius') && king_addons_freemius()->can_use_premium_code__premium_only() && defined('KING_ADDONS_PRO_PATH') ) { $pro_file_path = KING_ADDONS_PRO_PATH . 'includes/extensions/Theme_Builder_Pro/Theme_Builder_Pro.php'; if (file_exists($pro_file_path)) { require_once $pro_file_path; if (class_exists('King_Addons\\Theme_Builder_Pro')) { new Theme_Builder_Pro(); $pro_loaded = true; } } } if (!$pro_loaded && class_exists('King_Addons\\Theme_Builder')) { new Theme_Builder(); } } // Custom Cursor if ($this->isExtensionEnabled('custom-cursor', 'KING_ADDONS_EXT_CUSTOM_CURSOR')) { require_once(KING_ADDONS_PATH . 'includes/extensions/Custom_Cursor/Custom_Cursor.php'); if (class_exists('King_Addons\\Custom_Cursor')) { new Custom_Cursor(); } } // Age Gate if ($this->isExtensionEnabled('age-gate', 'KING_ADDONS_EXT_AGE_GATE')) { require_once(KING_ADDONS_PATH . 'includes/extensions/Age_Gate/Age_Gate.php'); if (defined('KING_ADDONS_PRO_PATH')) { $age_gate_pro = KING_ADDONS_PRO_PATH . 'includes/extensions/Age_Gate_Pro/Age_Gate_Pro.php'; if (file_exists($age_gate_pro)) { require_once $age_gate_pro; } } Age_Gate::instance(); } // Live Chat & Support Builder if ($this->isExtensionEnabled('live-chat', 'KING_ADDONS_EXT_LIVE_CHAT')) { require_once(KING_ADDONS_PATH . 'includes/extensions/Live_Chat/Live_Chat.php'); Live_Chat::instance(); } // Docs & Knowledge Base if ($this->isExtensionEnabled('docs-kb', 'KING_ADDONS_EXT_DOCS_KB')) { require_once(KING_ADDONS_PATH . 'includes/extensions/Docs_KB/Docs_KB.php'); Docs_KB::instance(); } // Pricing Table Builder if ($this->isExtensionEnabled('pricing-table-builder', 'KING_ADDONS_EXT_PRICING_TABLE_BUILDER')) { require_once(KING_ADDONS_PATH . 'includes/extensions/Pricing_Table_Builder/Pricing_Table_Builder.php'); Pricing_Table_Builder::instance(); } // Custom Code Manager if ($this->isExtensionEnabled('custom-code-manager', 'KING_ADDONS_EXT_CUSTOM_CODE_MANAGER')) { require_once(KING_ADDONS_PATH . 'includes/extensions/Custom_Code_Manager/Custom_Code_Manager.php'); Custom_Code_Manager::getInstance(); } // Fomo Notifications if ($this->isExtensionEnabled('fomo-notifications', 'KING_ADDONS_EXT_FOMO_NOTIFICATIONS')) { require_once(KING_ADDONS_PATH . 'includes/extensions/Fomo_Notifications/Fomo_Notifications.php'); Fomo_Notifications::instance(); } // Smart Links if ($this->isExtensionEnabled('smart-links', 'KING_ADDONS_EXT_SMART_LINKS')) { require_once(KING_ADDONS_PATH . 'includes/extensions/Smart_Links/Smart_Links.php'); \King_Addons\Smart_Links\Smart_Links::instance(); } // Activity Log if ($this->isExtensionEnabled('activity-log', 'KING_ADDONS_EXT_ACTIVITY_LOG')) { require_once(KING_ADDONS_PATH . 'includes/extensions/Activity_Log/Activity_Log.php'); \King_Addons\Activity_Log\Activity_Log::instance(); } // Maintenance Mode if ($this->isExtensionEnabled('maintenance-mode', 'KING_ADDONS_EXT_MAINTENANCE_MODE')) { require_once(KING_ADDONS_PATH . 'includes/extensions/Maintenance_Mode/Maintenance_Mode.php'); \King_Addons\Maintenance_Mode\Maintenance_Mode::instance(); } // Data Table Builder if ($this->isExtensionEnabled('table-builder', 'KING_ADDONS_EXT_TABLE_BUILDER')) { require_once(KING_ADDONS_PATH . 'includes/extensions/Data_Table_Builder/Data_Table_Builder.php'); Data_Table_Builder::instance(); } // Site Preloader Animation if ($this->isExtensionEnabled('site-preloader', 'KING_ADDONS_EXT_SITE_PRELOADER')) { require_once(KING_ADDONS_PATH . 'includes/extensions/Site_Preloader/Site_Preloader.php'); Site_Preloader::instance(); } // Free Shipping Bar if ($this->isExtensionEnabled('free-shipping-bar', 'KING_ADDONS_EXT_FREE_SHIPPING_BAR')) { require_once(KING_ADDONS_PATH . 'includes/extensions/Free_Shipping_Bar/Free_Shipping_Bar.php'); if (class_exists('King_Addons\\Free_Shipping_Bar')) { Free_Shipping_Bar::instance(); } } // Sticky Add To Cart if ($this->isExtensionEnabled('sticky-add-to-cart', 'KING_ADDONS_EXT_STICKY_ADD_TO_CART')) { require_once(KING_ADDONS_PATH . 'includes/extensions/Sticky_Add_To_Cart/Sticky_Add_To_Cart.php'); if (class_exists('King_Addons\\Sticky_Add_To_Cart')) { Sticky_Add_To_Cart::instance(); } } // Image Optimizer if ($this->isExtensionEnabled('image-optimizer', 'KING_ADDONS_EXT_IMAGE_OPTIMIZER')) { require_once(KING_ADDONS_PATH . 'includes/extensions/Image_Optimizer/Image_Optimizer.php'); \King_Addons\Image_Optimizer\Image_Optimizer::instance(); } // Admin require_once(KING_ADDONS_PATH . 'includes/Admin.php'); // Rating Notice (admin only) // Temporarily disabled // if (is_admin()) { // require_once(KING_ADDONS_PATH . 'includes/admin/notices/RatingNotice.php'); // \King_Addons\Admin\Notices\RatingNotice::instance(); // } // Additional - Controls require_once(KING_ADDONS_PATH . 'includes/controls/Ajax_Select2/Ajax_Select2.php'); require_once(KING_ADDONS_PATH . 'includes/controls/Ajax_Select2/Ajax_Select2_API.php'); require_once(KING_ADDONS_PATH . 'includes/controls/Animations/Animations.php'); require_once(KING_ADDONS_PATH . 'includes/controls/Animations/Button_Animations.php'); // Additional - Widgets require_once(KING_ADDONS_PATH . 'includes/widgets/Search/Search_Ajax.php'); require_once(KING_ADDONS_PATH . 'includes/widgets/MailChimp/MailChimp_Ajax.php'); // Additional - Grids, Magazine Grid require_once(KING_ADDONS_PATH . 'includes/helpers/Grid/Grid_Ajax_Security.php'); require_once(KING_ADDONS_PATH . 'includes/helpers/Grid/Filter_Posts_Ajax.php'); require_once(KING_ADDONS_PATH . 'includes/helpers/Grid/Filter_WooCommerce_Products_Ajax.php'); require_once(KING_ADDONS_PATH . 'includes/helpers/Grid/Post_Likes_Ajax.php'); // Additional - Form Builder if (KING_ADDONS_WGT_FORM_BUILDER) { require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/Extra_Fields.php'); require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/Conditional_Logic.php'); require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/Spam_Protection.php'); require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/Integrations.php'); new Form_Integrations(); require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/Partial_Entries.php'); new Form_Partial_Entries(); require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/Formula.php'); require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/Payments.php'); require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/Payment_Confirm.php'); new Form_Payments(); require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/Form_Builder_Security.php'); require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/Create_Submission.php'); require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/Send_Email.php'); require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/Send_Webhook.php'); require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/Subscribe_Mailchimp.php'); require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/Update_Action_Meta.php'); require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/Upload_Email_File.php'); require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/Verify_Google_Recaptcha.php'); require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/View_Submissions_Pro.php'); } // ADDITIONAL CLASSES // AI SEO Tools (Alt Text Generator + Auto Tagging) if ($this->isExtensionEnabled('ai-seo-tools', 'KING_ADDONS_EXT_AI_SEO_TOOLS')) { require_once(KING_ADDONS_PATH . 'includes/extensions/AI_SEO_Tools/AI_SEO_Tools.php'); if (class_exists('King_Addons\\AI_SEO_Tools\\AI_SEO_Tools')) { \King_Addons\AI_SEO_Tools\AI_SEO_Tools::instance(); } } // Wishlist module - check extension toggle if ($this->isExtensionEnabled('wishlist', 'KING_ADDONS_EXT_WISHLIST')) { require_once KING_ADDONS_PATH . 'includes/wishlist/Wishlist_DB.php'; require_once KING_ADDONS_PATH . 'includes/wishlist/Wishlist_Session.php'; require_once KING_ADDONS_PATH . 'includes/wishlist/Wishlist_Settings.php'; require_once KING_ADDONS_PATH . 'includes/wishlist/Wishlist_Service.php'; require_once KING_ADDONS_PATH . 'includes/wishlist/Wishlist_Renderer.php'; require_once KING_ADDONS_PATH . 'includes/wishlist/Wishlist_Frontend.php'; require_once KING_ADDONS_PATH . 'includes/wishlist/Wishlist_WooCommerce.php'; require_once KING_ADDONS_PATH . 'includes/wishlist/Wishlist_Module.php'; $this->wishlist_module = new Wishlist_Module(); } // Dynamic Posts Grid AJAX Helper - Initialize regardless of Elementor compatibility // This is needed for AJAX functionality to work even when PRO version is disabled require_once(KING_ADDONS_PATH . 'includes/helpers/Dynamic_Posts_Grid_Ajax.php'); \King_Addons\Dynamic_Posts_Grid_Ajax::get_instance(); // Screenshot Generator // require_once(KING_ADDONS_PATH . 'includes/extensions/Templates/screenshot-generator.php'); // require_once(KING_ADDONS_PATH . 'includes/extensions/Templates/screenshot-admin-page.php'); // new King_Addons\KingAddons\ScreenshotAdmin(); // END: ADDITIONAL CLASSES self::enableWidgetsByDefault(); add_action('elementor/init', [$this, 'initElementor']); add_action('elementor/elements/categories_registered', [$this, 'addWidgetCategory']); // Elementor promotes Atomic / Custom Widget / Pro after that hook, so the // panel order is applied again once categories are fully initialised. add_action('elementor/init', [$this, 'reorderWidgetCategoriesLate'], 100); add_action('elementor/editor/before_enqueue_scripts', [$this, 'reorderWidgetCategoriesLate'], 1); add_filter('elementor/document/config', [$this, 'filterDocumentPanelCategories'], 1, 2); add_action('elementor/controls/controls_registered', [$this, 'registerControls']); self::enableFeatures(); // Load and register AJAX handlers for Login Register Form widget require_once(KING_ADDONS_PATH . 'includes/widgets/Login_Register_Form/Login_Register_Form_Ajax.php'); require_once(KING_ADDONS_PATH . 'includes/widgets/Login_Register_Form/User_Profile_Fields.php'); require_once(KING_ADDONS_PATH . 'includes/widgets/Login_Register_Form/Email_Handler.php'); require_once(KING_ADDONS_PATH . 'includes/widgets/Login_Register_Form/Social_Login_Handler.php'); add_action('wp_ajax_nopriv_king_addons_user_login', ['King_Addons\Widgets\Login_Register_Form\Login_Register_Form_Ajax', 'handle_login_ajax']); add_action('wp_ajax_king_addons_user_login', ['King_Addons\Widgets\Login_Register_Form\Login_Register_Form_Ajax', 'handle_login_ajax']); add_action('wp_ajax_nopriv_king_addons_user_register', ['King_Addons\Widgets\Login_Register_Form\Login_Register_Form_Ajax', 'handle_register_ajax']); add_action('wp_ajax_king_addons_user_register', ['King_Addons\Widgets\Login_Register_Form\Login_Register_Form_Ajax', 'handle_register_ajax']); add_action('wp_ajax_nopriv_king_addons_user_lostpassword', ['King_Addons\Widgets\Login_Register_Form\Login_Register_Form_Ajax', 'handle_lostpassword_ajax']); add_action('wp_ajax_king_addons_user_lostpassword', ['King_Addons\Widgets\Login_Register_Form\Login_Register_Form_Ajax', 'handle_lostpassword_ajax']); // Initialize user profile fields \King_Addons\Widgets\Login_Register_Form\User_Profile_Fields::init(); // Initialize social login handler \King_Addons\Widgets\Login_Register_Form\Social_Login_Handler::init(); // Initialize Security Dashboard for admins (only if Login Register Form widget is enabled) if (is_admin()) { $widget_options = get_option('king_addons_options', []); $login_form_enabled = !isset($widget_options['login-register-form']) || $widget_options['login-register-form'] === 'enabled'; if ($login_form_enabled) { require_once(KING_ADDONS_PATH . 'includes/widgets/Login_Register_Form/Security_Dashboard.php'); \King_Addons\Widgets\Login_Register_Form\Security_Dashboard::init(); } } new Admin(); add_action('wp_enqueue_scripts', [$this, 'enqueueFrontendStyles']); add_action('wp_enqueue_scripts', [$this, 'enqueueLightboxDynamicStyles']); // Notice - Upgrade Suggestion if (!king_addons_freemius()->can_use_premium_code__premium_only()) { add_action('wp_ajax_king_addons_premium_notice_dismiss', [$this, 'king_addons_premium_notice_dismiss_callback']); /** * The previous notice is kept untouched in showNoticeUpgrade() as a fallback. * To go back to it, either define KING_ADDONS_UPGRADE_NOTICE_LEGACY as true * in wp-config.php, or return true from this filter. */ $use_legacy_notice = defined('KING_ADDONS_UPGRADE_NOTICE_LEGACY') ? (bool) KING_ADDONS_UPGRADE_NOTICE_LEGACY : false; $use_legacy_notice = (bool) apply_filters('king_addons/upgrade_notice/use_legacy', $use_legacy_notice); add_action( 'admin_notices', [$this, $use_legacy_notice ? 'showNoticeUpgrade' : 'showNoticeUpgradeV2'] ); } // Dashboard UI settings AJAX handler add_action('wp_ajax_king_addons_save_dashboard_ui', [$this, 'king_addons_save_dashboard_ui_callback']); // Conditionally enqueue AI text-field enhancement script and styles in Elementor editor $ai_options = get_option('king_addons_ai_options', []); $enable_ai_text_buttons = isset($ai_options['enable_ai_buttons']) ? (bool) $ai_options['enable_ai_buttons'] : true; if ($enable_ai_text_buttons) { // Enqueue AI text-field enhancement script add_action('elementor/editor/after_enqueue_scripts', [$this, 'enqueueAiFieldScript']); // Enqueue styles for AI prompt UI add_action('elementor/editor/after_enqueue_styles', [$this, 'enqueueAiFieldStyles']); // Enqueue AI page translator script add_action('elementor/editor/after_enqueue_scripts', [$this, 'enqueueAiTranslatorScript']); } $enable_ai_image_generation_button = isset($ai_options['enable_ai_image_generation_button']) ? (bool) $ai_options['enable_ai_image_generation_button'] : true; if ($enable_ai_image_generation_button) { add_action('elementor/editor/after_enqueue_scripts', [$this, 'enqueueAiImageGenerationScript']); // Enqueue styles for AI Image Generation controls add_action('elementor/editor/after_enqueue_styles', [$this, 'enqueueAiImageFieldStyles']); } } } function king_addons_premium_notice_dismiss_callback() { // Check user capabilities if (!current_user_can('manage_options')) { wp_die(); } check_ajax_referer('king_addons_premium_notice_dismiss', 'nonce'); $user_id = get_current_user_id(); // Save the current time as the last dismissal time for the premium notice update_user_meta($user_id, 'king_addons_premium_notice_dismissed_time', time()); wp_die(); // End AJAX request } /** * AJAX callback for saving dashboard UI settings (theme, view toggle) * * @since 1.0.0 */ function king_addons_save_dashboard_ui_callback() { // Check user capabilities if (!current_user_can('manage_options')) { wp_send_json_error(['message' => 'Unauthorized'], 403); } check_ajax_referer('king_addons_dashboard_ui', 'nonce'); $key = isset($_POST['key']) ? sanitize_text_field($_POST['key']) : ''; $user_id = get_current_user_id(); // Validate key $allowed_keys = ['dark_theme', 'theme_mode', 'show_descriptions']; if (!in_array($key, $allowed_keys, true)) { wp_send_json_error(['message' => 'Invalid key'], 400); } // Theme preference is per-user. if ($key === 'theme_mode') { $mode = isset($_POST['value']) ? sanitize_key($_POST['value']) : ''; $allowed_modes = ['dark', 'light', 'auto']; if (!in_array($mode, $allowed_modes, true)) { wp_send_json_error(['message' => 'Invalid theme mode'], 400); } update_user_meta($user_id, 'king_addons_theme_mode', $mode); // Also store as a global option so pages can fall back when user meta isn't set. update_option('king_addons_theme_mode', $mode); wp_send_json_success(['key' => $key, 'value' => $mode]); } // Backward compatibility: old boolean dark_theme maps to theme_mode. if ($key === 'dark_theme') { $is_dark = isset($_POST['value']) && $_POST['value'] === '1'; $mode = $is_dark ? 'dark' : 'light'; update_user_meta($user_id, 'king_addons_theme_mode', $mode); wp_send_json_success(['key' => 'theme_mode', 'value' => $mode]); } // Remaining UI settings are still stored as site option (shared). $value = isset($_POST['value']) && $_POST['value'] === '1'; $settings = get_option('king_addons_dashboard_ui', []); $settings[$key] = $value; update_option('king_addons_dashboard_ui', $settings); wp_send_json_success(['key' => $key, 'value' => $value]); } function showNoticeUpgrade() { // Check user capabilities; show notice only to administrators as an example if (!current_user_can('manage_options')) { return; } $user_id = get_current_user_id(); $now = time(); // Retrieve the last time the premium notice was dismissed by the user $last_dismissed = get_user_meta($user_id, 'king_addons_premium_notice_dismissed_time', true); // If the premium notice was dismissed less than a week ago (604800 seconds), do not show it if ($last_dismissed && ($now - $last_dismissed) < 604800) { // if ($last_dismissed && ($now - $last_dismissed) < 60) { return; } ?>

Get 4,000+ premium templates and sections, 80+ widgets, 200+ advanced features, and AI tools for Elementor. From $4/mo, billed annually.

Trusted by 20,000+ users

Upgrade to Pro Learn More

' . esc_html__('20,000+', 'king-addons') . '' ); ?>

Features -> Inline Font Icons) * because sometimes Elementor still renders Font Awesome icons but doesn't load the corresponding Font Awesome styles. * Therefore, we have to enqueue the styles. */ wp_enqueue_style( 'font-awesome-5-all', ELEMENTOR_ASSETS_URL . 'lib/font-awesome/css/all' . (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG ? '' : '.min') . '.css', false, KING_ADDONS_VERSION ); } function hasElementorCompatibility(): bool { // Check if Elementor installed and activated if (!did_action('elementor/loaded')) { add_action('admin_notices', [$this, 'showAdminNotice_ElementorRequired']); return false; } // Check for required Elementor version if (!version_compare(ELEMENTOR_VERSION, '3.19.0', '>=')) { add_action('admin_notices', [$this, 'showAdminNotice_ElementorMinimumVersion']); return false; } return true; } function showAdminNotice_ElementorRequired(): void { $screen = get_current_screen(); if (isset($screen->parent_file) && 'plugins.php' === $screen->parent_file && 'update' === $screen->id) { return; } if (isset(get_plugins()['elementor/elementor.php'])) { if (!current_user_can('activate_plugins') || is_plugin_active('elementor/elementor.php')) { return; } $plugin = 'elementor/elementor.php'; $activation_url = wp_nonce_url('plugins.php?action=activate&plugin=' . $plugin . '&plugin_status=all&paged=1&s', 'activate-plugin_' . $plugin); $message = '

' . esc_html__('King Addons plugin is not working because you need to activate the Elementor plugin.', 'king-addons') . '

'; /** @noinspection HtmlUnknownTarget */ $message .= '

' . sprintf('%s', $activation_url, esc_html__('Activate Elementor now', 'king-addons')) . '

'; } else { if (!current_user_can('install_plugins')) { return; } $install_url = wp_nonce_url(self_admin_url('update.php?action=install-plugin&plugin=elementor'), 'install-plugin_elementor'); $message = '

' . esc_html__('King Addons plugin is not working because you need to install the Elementor plugin.', 'king-addons') . '

'; /** @noinspection HtmlUnknownTarget */ $message .= '

' . sprintf('%s', $install_url, esc_html__('Install Elementor now', 'king-addons')) . '

'; } echo $message; } function showAdminNotice_ElementorMinimumVersion(): void { $message = sprintf( /* translators: 1: Plugin name 2: Elementor 3: Required Elementor version */ esc_html__('%1$s plugin requires %2$s plugin version %3$s or greater.', 'king-addons'), esc_html__('King Addons', 'king-addons'), esc_html__('Elementor', 'king-addons'), '3.19.0' ); echo '

' . esc_html($message) . '

'; } public function initElementor(): void { add_action('elementor/widgets/register', [$this, 'registerWidgets']); add_action('elementor/editor/after_enqueue_styles', [$this, 'enqueueEditorStyles']); add_action('elementor/editor/after_enqueue_scripts', [$this, 'enqueueEditorScripts']); add_action('elementor/preview/enqueue_styles', [$this, 'enqueueEditorPreviewStyles']); } function enqueueEditorPreviewStyles(): void { wp_enqueue_style( KING_ADDONS_ASSETS_UNIQUE_KEY . '-elementor-preview', KING_ADDONS_URL . 'includes/admin/css/elementor-preview.css', [], KING_ADDONS_VERSION ); } function addWidgetCategory(): void { $elements_manager = Plugin::instance()->elements_manager; $elements_manager->add_category( 'king-addons', [ 'title' => esc_html__('King Addons', 'king-addons'), 'icon' => 'fa fa-plug', ] ); if (class_exists('WooCommerce') && function_exists('WC')) { $elements_manager->add_category( 'king-addons-woo', [ 'title' => esc_html__('King Addons WooCommerce', 'king-addons'), 'icon' => 'fa fa-shopping-cart', 'hideIfEmpty' => true, ] ); $elements_manager->add_category( 'king-addons-woo-builder', [ 'title' => esc_html__('King Addons WooCommerce Builder', 'king-addons'), 'icon' => 'fa fa-shopping-cart', 'hideIfEmpty' => true, ] ); } $elements_manager->add_category( 'king-addons-theme-builder', [ 'title' => esc_html__('King Addons Theme Builder', 'king-addons'), 'icon' => 'fa fa-plug', 'hideIfEmpty' => true, ] ); } /** * Apply the panel category order after Elementor has finished registering its own. * * @return void */ public function reorderWidgetCategoriesLate(): void { if (!did_action('elementor/loaded')) { return; } $elements_manager = Plugin::instance()->elements_manager; $elements_manager->get_categories(); $this->reorderWidgetCategories($elements_manager); if (!$this->shouldApplyEditorPanelVisibility()) { return; } $post_id = $this->getCurrentEditorPostId(); if ($post_id > 0) { $this->applyContextCategoryVisibility($elements_manager, $post_id); } } /** * Overwrite the editor panel category snapshot after Elementor has built document config. * * @param array $additional_config * @param int $post_id * @return array */ public function filterDocumentPanelCategories($additional_config, $post_id = 0) { $post_id = (int) $post_id; if ($post_id <= 0) { $post_id = $this->getCurrentEditorPostId(); } if (!is_array($additional_config)) { $additional_config = []; } if (!did_action('elementor/loaded')) { return $additional_config; } $elements_manager = Plugin::instance()->elements_manager; $elements_manager->get_categories(); $this->reorderWidgetCategories($elements_manager); if ($post_id > 0) { $this->applyContextCategoryVisibility($elements_manager, $post_id); } $categories = \Elementor\Core\Base\Document::get_filtered_editor_panel_categories(); foreach ($this->getHiddenPanelCategoryKeys($post_id) as $hidden_key) { unset($categories[$hidden_key]); } $additional_config['panel']['elements_categories'] = $categories; foreach ($this->getHiddenPanelWidgetNames($post_id) as $widget_name) { $additional_config['widgets'][$widget_name]['show_in_panel'] = false; } return $additional_config; } /** * Reorder widget categories: Layout, then King Addons groups, then the rest of Elementor. * * @param \Elementor\Elements_Manager $elements_manager * @return void */ private function reorderWidgetCategories($elements_manager): void { try { $reflection = new \ReflectionClass($elements_manager); $categories_property = $reflection->getProperty('categories'); if (PHP_VERSION_ID < 80100) { $categories_property->setAccessible(true); } $categories = $categories_property->getValue($elements_manager); if (!is_array($categories) || $categories === []) { return; } $layout = []; $ours = []; $rest = []; foreach ($categories as $key => $value) { if ('layout' === $key) { $layout[$key] = $value; } elseif (str_starts_with((string) $key, 'king-addons')) { $ours[$key] = $value; } else { $rest[$key] = $value; } } $ours_sorted = []; foreach (['king-addons', 'king-addons-woo', 'king-addons-woo-builder', 'king-addons-theme-builder'] as $preferred) { if (isset($ours[$preferred])) { $ours_sorted[$preferred] = $ours[$preferred]; unset($ours[$preferred]); } } $ours_sorted += $ours; $categories_property->setValue($elements_manager, $layout + $ours_sorted + $rest); } catch (\ReflectionException $e) { // Silently fail if reflection doesn't work (e.g., future Elementor changes) } } /** * Drop context-only categories from the manager for the current editor document. * * @param \Elementor\Elements_Manager $elements_manager * @param int $post_id * @return void */ private function applyContextCategoryVisibility($elements_manager, int $post_id): void { $hidden = $this->getHiddenPanelCategoryKeys($post_id); if ($hidden === []) { return; } try { $reflection = new \ReflectionClass($elements_manager); $categories_property = $reflection->getProperty('categories'); if (PHP_VERSION_ID < 80100) { $categories_property->setAccessible(true); } $categories = $categories_property->getValue($elements_manager); if (!is_array($categories) || $categories === []) { return; } foreach ($hidden as $key) { unset($categories[$key]); } $categories_property->setValue($elements_manager, $categories); } catch (\ReflectionException $e) { // Silently fail if reflection doesn't work (e.g., future Elementor changes) } } /** * Category keys that should not appear in the current editor document. * * @param int $post_id * @return array */ private function getHiddenPanelCategoryKeys(int $post_id): array { $hidden = []; $is_woo_builder = $this->isWooBuilderDocument($post_id); $is_theme_builder = $this->isThemeBuilderDocument($post_id); $is_loop_item = $this->isLoopItemDocument($post_id); $woocommerce_active = class_exists('WooCommerce') && function_exists('WC'); if (!$woocommerce_active) { $hidden[] = 'king-addons-woo'; $hidden[] = 'king-addons-woo-builder'; } elseif (!$is_woo_builder && !$is_loop_item) { $hidden[] = 'king-addons-woo-builder'; } if (!$is_theme_builder && !$is_loop_item) { $hidden[] = 'king-addons-theme-builder'; } return $hidden; } /** * Widget names that belong to hidden context categories. * * @param int $post_id * @return array */ private function getHiddenPanelWidgetNames(int $post_id): array { $hidden_categories = $this->getHiddenPanelCategoryKeys($post_id); if ($hidden_categories === [] || !did_action('elementor/loaded')) { return []; } $hidden_lookup = array_fill_keys($hidden_categories, true); $names = []; foreach (Plugin::instance()->widgets_manager->get_widget_types() as $widget_name => $widget) { if (!is_object($widget) || !method_exists($widget, 'get_categories')) { continue; } foreach ((array) $widget->get_categories() as $category) { if (isset($hidden_lookup[$category])) { $names[] = (string) $widget_name; break; } } } return $names; } /** * Whether the current request is the Elementor editor (or its AJAX). * * @return bool */ private function shouldApplyEditorPanelVisibility(): bool { if (!empty($_GET['action']) && 'elementor' === $_GET['action']) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return true; } if (!empty($_REQUEST['editor_post_id'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return true; } return class_exists('\Elementor\Plugin') && Plugin::instance()->editor && Plugin::instance()->editor->is_edit_mode(); } /** * Current Elementor editor post ID, if any. * * @return int */ private function getCurrentEditorPostId(): int { foreach (['editor_post_id', 'post_id', 'post'] as $key) { if (!empty($_REQUEST[$key])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $post_id = absint(wp_unslash($_REQUEST[$key])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ($post_id > 0) { return $post_id; } } } $post = get_post(); return $post ? (int) $post->ID : 0; } /** * Whether the post is a King Addons Woo Builder template. * * @param int $post_id * @return bool */ private function isWooBuilderDocument(int $post_id): bool { if ($post_id <= 0) { return false; } $elementor_type = (string) get_post_meta($post_id, '_elementor_template_type', true); if ('king-addons-woo-builder' === $elementor_type) { return true; } return (string) get_post_meta($post_id, 'ka_woo_template_type', true) !== ''; } /** * Whether the post is a King Addons Theme Builder template. * * @param int $post_id * @return bool */ private function isThemeBuilderDocument(int $post_id): bool { if ($post_id <= 0) { return false; } $location_key = '_ka_theme_builder_location'; if (class_exists('\King_Addons\Theme_Builder\Meta_Keys')) { $location_key = \King_Addons\Theme_Builder\Meta_Keys::LOCATION; } return (string) get_post_meta($post_id, $location_key, true) !== ''; } /** * Whether the post is a King Addons Loop Item template. * * @param int $post_id * @return bool */ private function isLoopItemDocument(int $post_id): bool { if ($post_id <= 0) { return false; } return 'king-addons-loop-item' === (string) get_post_meta($post_id, '_elementor_template_type', true); } /** * Registers Elementor widgets with a mechanism to skip (and remember) broken widgets * that caused a fatal error previously, and try them again if the plugin version is updated. * * @param Widgets_Manager $widgets_manager * @return void */ function registerWidgets(Widgets_Manager $widgets_manager): void { // Used to track which widget is currently being loaded when a fatal error occurs static $currentlyLoadingWidgetId = null; $currentPluginVersion = KING_ADDONS_VERSION; // Get plugin options to check if a widget is enabled $options = get_option('king_addons_options'); $options = is_array($options) ? $options : []; // Extension toggles (used to prevent loading dependent widgets when extension is disabled). $wishlist_extension_enabled = !isset($options['ext_wishlist']) || $options['ext_wishlist'] === 'enabled'; if (defined('KING_ADDONS_EXT_WISHLIST') && KING_ADDONS_EXT_WISHLIST === false) { $wishlist_extension_enabled = false; } // Ensure Woo Builder base class is available for single product widgets. $abstract_single_widget = KING_ADDONS_PATH . 'includes/helpers/Woo_Builder/Abstract_Single_Widget.php'; if (file_exists($abstract_single_widget)) { require_once $abstract_single_widget; } // Ensure Woo Builder base class is available for archive widgets. $abstract_archive_widget = KING_ADDONS_PATH . 'includes/helpers/Woo_Builder/Abstract_Archive_Widget.php'; if (file_exists($abstract_archive_widget)) { require_once $abstract_archive_widget; } // Ensure Woo Builder base class is available for cart widgets. $abstract_cart_widget = KING_ADDONS_PATH . 'includes/helpers/Woo_Builder/Abstract_Cart_Widget.php'; if (file_exists($abstract_cart_widget)) { require_once $abstract_cart_widget; } // Ensure Woo Builder base class is available for checkout widgets. $abstract_checkout_widget = KING_ADDONS_PATH . 'includes/helpers/Woo_Builder/Abstract_Checkout_Widget.php'; if (file_exists($abstract_checkout_widget)) { require_once $abstract_checkout_widget; } // Ensure Woo Builder base class is available for My Account widgets. $abstract_my_account_widget = KING_ADDONS_PATH . 'includes/helpers/Woo_Builder/Abstract_My_Account_Widget.php'; if (file_exists($abstract_my_account_widget)) { require_once $abstract_my_account_widget; } /** * Retrieve the array of broken widgets from the WordPress options. * The structure is expected to be something like: * * 'widget_id' => [ * 'version' => '1.2.0', * 'error' => 'Some fatal error message' * ], * ... * */ $brokenWidgets = get_option('king_addons_broken_widgets', []); /** * STEP 1: Clear out any "broken widgets" where the stored version is * less than the current plugin version. This gives them a second chance * after an update, assuming the issue may have been fixed. */ foreach ($brokenWidgets as $brokenId => $brokenData) { if ( isset($brokenData['version']) && version_compare($currentPluginVersion, $brokenData['version'], '>') ) { // If the plugin version is now higher, we remove the widget from the blacklist unset($brokenWidgets[$brokenId]); } } // Update the option after cleaning up update_option('king_addons_broken_widgets', $brokenWidgets); /** * STEP 2: Use register_shutdown_function to detect any fatal errors (E_ERROR, E_PARSE, etc.) * that might occur during the loading of a widget. If an error is detected, store that widget * in the "broken" list with the current plugin version and the error message. */ register_shutdown_function(function () use (&$currentlyLoadingWidgetId, $currentPluginVersion) { $error = error_get_last(); if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) { // If a fatal error occurred while loading a specific widget if (!empty($currentlyLoadingWidgetId)) { $brokenWidgetsLocal = get_option('king_addons_broken_widgets', []); $brokenWidgetsLocal[$currentlyLoadingWidgetId] = [ 'version' => $currentPluginVersion, 'error' => $error['message'] ?? '' ]; update_option('king_addons_broken_widgets', $brokenWidgetsLocal); } } }); /** * STEP 3: Now we iterate through all widgets in our modules map and try to load them. * If a widget is in the broken list, we skip it to avoid repeated fatal errors. */ foreach (ModulesMap::getModulesMapArray()['widgets'] as $widget_id => $widget) { // Hard-disable via constant (used to QA/rollout new widgets). $widget_constant = 'KING_ADDONS_WGT_' . strtoupper(str_replace('-', '_', (string) $widget_id)); if (defined($widget_constant) && constant($widget_constant) === false) { continue; } // Check if the widget is enabled in the options if (!isset($options[$widget_id]) || $options[$widget_id] !== 'enabled') { continue; } // Skip Wishlist widgets when Wishlist extension is disabled. // This prevents fatals when wishlist classes aren't loaded. if (!$wishlist_extension_enabled && strpos((string) $widget_id, 'wishlist-') === 0) { continue; } // If this widget is listed as broken, skip it if (array_key_exists($widget_id, $brokenWidgets)) { // Log something here if needed: // error_log("Skipping widget {$widget_id}, it previously caused a fatal error."); continue; } // Track which widget we're loading $currentlyLoadingWidgetId = $widget_id; // Include the base widget class $widget_class = $widget['php-class']; $path_widget_class = "King_Addons\\" . $widget_class; $widget_file = KING_ADDONS_PATH . 'includes/widgets/' . $widget_class . '/' . $widget_class . '.php'; if (!file_exists($widget_file)) { // Skip missing widget files to avoid fatal errors if registry is ahead of implementation. $currentlyLoadingWidgetId = null; continue; } require_once $widget_file; // Check if we can load the Pro version if ( function_exists('king_addons_freemius') && king_addons_freemius()->can_use_premium_code__premium_only() && defined('KING_ADDONS_PRO_PATH') ) { if (!empty($widget['has-pro'])) { $pro_file_path = KING_ADDONS_PRO_PATH . 'includes/widgets/' . $widget_class . '_Pro/' . $widget_class . '_Pro.php'; if (file_exists($pro_file_path)) { require_once($pro_file_path); $path_widget_class_pro = "King_Addons\\" . $widget_class . '_Pro'; $widgets_manager->register(new $path_widget_class_pro); } else { // If Pro file doesn't exist, register the base widget $widgets_manager->register(new $path_widget_class); } } else { // No 'has-pro', register the base widget $widgets_manager->register(new $path_widget_class); } } else { // No Freemius Pro available, register the base widget $widgets_manager->register(new $path_widget_class); } // Clear the tracking variable after successful load $currentlyLoadingWidgetId = null; } } function enableWidgetsByDefault(): void { $options = get_option('king_addons_options'); foreach (ModulesMap::getModulesMapArray()['widgets'] as $widget_id => $widget) { // Hard-disable via constant (used to QA/rollout new widgets). $widget_constant = 'KING_ADDONS_WGT_' . strtoupper(str_replace('-', '_', (string) $widget_id)); if (defined($widget_constant) && constant($widget_constant) === false) { continue; } if (!($options[$widget_id] ?? null)) { $options[$widget_id] = 'enabled'; update_option('king_addons_options', $options); } } } /** * Enable and bootstrap registered features. * * Loads free feature classes and, when available and licensed, their Pro counterparts. * * @return void */ public function enableFeatures(): void { $options = get_option('king_addons_options'); foreach (ModulesMap::getModulesMapArray()['features'] as $feature_id => $feature) { // Hard-disable via constant (used to QA/rollout new features). $feature_constant = 'KING_ADDONS_FEAT_' . strtoupper(str_replace('-', '_', (string) $feature_id)); if (defined($feature_constant) && constant($feature_constant) === false) { continue; } if (!($options[$feature_id] ?? null)) { $options[$feature_id] = 'enabled'; update_option('king_addons_options', $options); } if ($options[$feature_id] !== 'enabled') { continue; } $feature_class = $feature['php-class']; $path_feature_class = "King_Addons\\" . $feature_class; $feature_file = KING_ADDONS_PATH . 'includes/features/' . $feature_class . '/' . $feature_class . '.php'; if (file_exists($feature_file)) { require_once $feature_file; } $pro_loaded = false; if ( !empty($feature['has-pro']) && function_exists('king_addons_freemius') && king_addons_freemius()->can_use_premium_code__premium_only() && defined('KING_ADDONS_PRO_PATH') ) { $pro_file_path = KING_ADDONS_PRO_PATH . 'includes/features/' . $feature_class . '_Pro/' . $feature_class . '_Pro.php'; if (file_exists($pro_file_path)) { require_once $pro_file_path; $path_feature_class_pro = "King_Addons\\" . $feature_class . '_Pro'; if (class_exists($path_feature_class_pro)) { new $path_feature_class_pro(); $pro_loaded = true; } } } if (!$pro_loaded && class_exists($path_feature_class)) { new $path_feature_class(); } } } public function registerControls(Controls_Manager $controls_manager): void { $controls_manager->register(new AJAX_Select2\Ajax_Select2()); $controls_manager->register(new Animations\Animations()); $controls_manager->register(new Animations\Animations_Alternative()); $controls_manager->register(new Button_Animations\Button_Animations()); } function enqueueEditorStyles(): void { wp_enqueue_style(KING_ADDONS_ASSETS_UNIQUE_KEY . '-elementor-editor', KING_ADDONS_URL . 'includes/admin/css/elementor-editor.css', '', KING_ADDONS_VERSION); } function enqueueEditorScripts(): void { wp_enqueue_script(KING_ADDONS_ASSETS_UNIQUE_KEY . '-elementor-editor', KING_ADDONS_URL . 'includes/admin/js/elementor-editor.js', '', KING_ADDONS_VERSION); // Localize script with PRO status wp_localize_script(KING_ADDONS_ASSETS_UNIQUE_KEY . '-elementor-editor', 'kingAddonsEditor', [ 'isPro' => king_addons_freemius()->can_use_premium_code__premium_only() ? true : false ]); wp_enqueue_script(KING_ADDONS_ASSETS_UNIQUE_KEY . '-data-table-export', KING_ADDONS_URL . 'includes/widgets/Data_Table/preview-handler.js', '', KING_ADDONS_VERSION); if (KING_ADDONS_WGT_FORM_BUILDER) { wp_enqueue_script(KING_ADDONS_ASSETS_UNIQUE_KEY . '-form-builder-editor-handler', KING_ADDONS_URL . 'includes/widgets/Form_Builder/editor-handler.js', '', KING_ADDONS_VERSION); } } /** * Widget settings with dynamic tags resolved, and nothing missing. * * get_settings() never parses dynamic tags, so a tag placed in such a * widget printed its placeholder instead of the value. get_settings_for_display() * does parse them, but it also drops every setting whose control condition * is currently false - and widgets written against the raw array index into * those keys without checking, which turns the switch into a batch of * "Trying to access array offset on null" warnings. * * A hidden control comes back as null rather than missing, so the parsed * value is preferred only when there is one, and the raw value fills in * everywhere else. * * @param \Elementor\Controls_Stack $widget Widget being rendered. * * @return array */ public static function displaySettings($widget): array { $raw = (array) $widget->get_settings(); $display = (array) $widget->get_settings_for_display(); foreach ($display as $key => $value) { if (null !== $value) { $raw[$key] = $value; } } return $raw; } public static function renderProFeaturesSection($module, $section, $type, $widget_name, $features): void { if (king_addons_freemius()->can_use_premium_code__premium_only()) { return; } $module->start_controls_section( 'king_addons_pro_features_section', [ 'label' => KING_ADDONS_ELEMENTOR_ICON_PRO . '' . esc_html__('Pro Features', 'king-addons') . '', 'tab' => $section ?: null, ] ); $list_html = '
    ' . implode('', array_map(fn($feature) => "
  • $feature
  • ", $features)) . '
'; $module->add_control( 'king_addons_pro_features_list', [ 'type' => $type, 'raw' => $list_html . '' . esc_html__('Upgrade Now', 'king-addons') . '', 'content_classes' => 'king-addons-pro-features-list', ] ); $module->end_controls_section(); } /** * Same upgrade notice as renderUpgradeProNotice(), but not tied to another * control's value. Use it where a whole section is Pro-only, so the free * build does not show a section header that opens onto nothing. * * @param mixed $module Widget or repeater the control belongs to. * @param string $controls_manager Control type to render the notice with. * @param string $widget_name Widget slug, used for the campaign link. * @param string $control_id Unique control id for the notice. */ public static function renderUpgradeProSection($module, $controls_manager, string $widget_name, string $control_id): void { if (king_addons_freemius()->can_use_premium_code__premium_only()) { return; } $module->add_control( $control_id, [ 'raw' => 'Upgrade to the Pro version now
and unlock this feature!', 'type' => $controls_manager, 'content_classes' => 'king-addons-pro-notice', ] ); } public static function renderUpgradeProNotice($module, $controls_manager, $widget_name, $option, $condition = []): void { if (king_addons_freemius()->can_use_premium_code__premium_only()) { return; } $module->add_control( $option . '_pro_notice_', [ 'raw' => 'Upgrade to the Pro version now
and unlock this feature!', 'type' => $controls_manager, 'content_classes' => 'king-addons-pro-notice', 'condition' => [ $option => $condition, ] ] ); } public static function getCustomTypes($query, $exclude_defaults = true): array { $custom_types = $query === 'tax' ? get_taxonomies(['show_in_nav_menus' => true], 'objects') : get_post_types(['show_in_nav_menus' => true], 'objects'); return array_filter( array_map(fn($type) => $type->label, $custom_types), fn($label, $key) => !$exclude_defaults || !in_array($key, ['post', 'page', 'category', 'post_tag']), ARRAY_FILTER_USE_BOTH ); } public static function getShareIcon($args = []): string { $args = wp_parse_args($args, [ 'network' => '', 'url' => '', 'title' => '', 'text' => '', 'image' => '', 'show_whatsapp_title' => 'no', 'show_whatsapp_excerpt' => 'no', 'tooltip' => 'no', 'icons' => 'no', 'labels' => 'no', 'custom_label' => '', ]); $url = esc_url($args['url']); $title = wp_strip_all_tags($args['title']); $text = wp_strip_all_tags($args['text']); $image = esc_url($args['image']); $network = $args['network']; $get_whatsapp_url = function ($a) { if ('yes' === $a['show_whatsapp_title'] && 'yes' === $a['show_whatsapp_excerpt']) { return 'https://api.whatsapp.com/send?text=*' . $a['title'] . '*%0a' . $a['text'] . '%0a' . $a['url']; } elseif ('yes' === $a['show_whatsapp_title']) { return 'https://api.whatsapp.com/send?text=*' . $a['title'] . '*%0a' . $a['url']; } elseif ('yes' === $a['show_whatsapp_excerpt']) { return 'https://api.whatsapp.com/send?text=*' . $a['text'] . '%0a' . $a['url']; } return 'https://api.whatsapp.com/send?text=' . $a['url']; }; $networks_map = [ 'facebook-f' => [ 'url' => "https://www.facebook.com/sharer.php?u=$url", 'title' => esc_html__('Facebook', 'king-addons'), 'icon' => 'fab', ], 'x-twitter' => [ 'url' => "https://twitter.com/intent/tweet?url=$url", 'title' => esc_html__('X (Twitter)', 'king-addons'), 'icon' => 'fab', ], 'linkedin-in' => [ 'url' => "https://www.linkedin.com/shareArticle?mini=true&url=$url&title=$title&summary=$text&source=$url", 'title' => esc_html__('LinkedIn', 'king-addons'), 'icon' => 'fab', ], 'pinterest-p' => [ 'url' => "https://www.pinterest.com/pin/create/button/?url=$url&media=$image", 'title' => esc_html__('Pinterest', 'king-addons'), 'icon' => 'fab', ], 'reddit' => [ 'url' => "https://reddit.com/submit?url=$url&title=$title", 'title' => esc_html__('Reddit', 'king-addons'), 'icon' => 'fab', ], 'tumblr' => [ 'url' => "https://tumblr.com/share/link?url=$url", 'title' => esc_html__('Tumblr', 'king-addons'), 'icon' => 'fab', ], 'digg' => [ 'url' => "https://digg.com/submit?url=$url", 'title' => esc_html__('Digg', 'king-addons'), 'icon' => 'fab', ], 'xing' => [ 'url' => "https://www.xing.com/app/user?op=share&url=$url", 'title' => esc_html__('Xing', 'king-addons'), 'icon' => 'fab', ], 'vk' => [ 'url' => "https://vk.ru/share.php?url=$url&title=$title&description=" . wp_trim_words($text, 250) . "&image=$image/", 'title' => esc_html__('VK', 'king-addons'), 'icon' => 'fab', ], 'odnoklassniki' => [ 'url' => "https://connect.ok.ru/offer?url=$url", 'title' => esc_html__('OK', 'king-addons'), 'icon' => 'fab', ], 'get-pocket' => [ 'url' => "https://getpocket.com/edit?url=$url", 'title' => esc_html__('Pocket', 'king-addons'), 'icon' => 'fab', ], 'skype' => [ 'url' => "https://web.skype.com/share?url=$url", 'title' => esc_html__('Skype', 'king-addons'), 'icon' => 'fab', ], 'whatsapp' => [ 'url' => $get_whatsapp_url($args), 'title' => esc_html__('WhatsApp', 'king-addons'), 'icon' => 'fab', ], 'telegram' => [ 'url' => "https://telegram.me/share/url?url=$url&text=$text", 'title' => esc_html__('Telegram', 'king-addons'), 'icon' => 'fab', ], 'envelope' => [ 'url' => "mailto:?subject=$title&body=$url", 'title' => esc_html__('Email', 'king-addons'), 'icon' => 'fas', ], 'print' => [ 'url' => "javascript:window.print()", 'title' => esc_html__('Print', 'king-addons'), 'icon' => 'fas', ], ]; if (!isset($networks_map[$network])) { return ''; } $share_url = $networks_map[$network]['url']; $network_title = $networks_map[$network]['title']; $icon_category = $networks_map[$network]['icon']; $output = ''; if ('yes' === $args['tooltip']) { $output .= '' . esc_html($network_title) . ''; } if ('yes' === $args['icons']) { $output .= ''; } if ('yes' === $args['labels']) { $label = !empty($args['custom_label']) ? $args['custom_label'] : $network_title; $output .= '' . esc_html($label) . ''; } $output .= ''; return $output; } public static function validateHTMLTags($setting, $default, $tags_whitelist) { $value = $setting; if (!in_array($value, $tags_whitelist)) { $value = $default; } return $value; } public static function getIcon($icon, $dir) { if (empty($icon) || strpos($icon, 'fa-') === false) { return ''; } $dir = $dir ? "-$dir" : ''; return wp_kses( '', ['i' => ['class' => []]] ); } public static function getPluginName() { return 'King Addons'; } public static function getAnimationTimings(): array { /** @noinspection DuplicatedCode */ $timings = [ 'ease-default' => 'Default', 'linear' => 'Linear', 'ease-in' => 'Ease In', 'ease-out' => 'Ease Out', 'pro-eio' => 'EI Out (Pro)', 'pro-eiqd' => 'EI Quad (Pro)', 'pro-eicb' => 'EI Cubic (Pro)', 'pro-eiqrt' => 'EI Quart (Pro)', 'pro-eiqnt' => 'EI Quint (Pro)', 'pro-eisn' => 'EI Sine (Pro)', 'pro-eiex' => 'EI Expo (Pro)', 'pro-eicr' => 'EI Circ (Pro)', 'pro-eibk' => 'EI Back (Pro)', 'pro-eoqd' => 'EO Quad (Pro)', 'pro-eocb' => 'EO Cubic (Pro)', 'pro-eoqrt' => 'EO Quart (Pro)', 'pro-eoqnt' => 'EO Quint (Pro)', 'pro-eosn' => 'EO Sine (Pro)', 'pro-eoex' => 'EO Expo (Pro)', 'pro-eocr' => 'EO Circ (Pro)', 'pro-eobk' => 'EO Back (Pro)', 'pro-eioqd' => 'EIO Quad (Pro)', 'pro-eiocb' => 'EIO Cubic (Pro)', 'pro-eioqrt' => 'EIO Quart (Pro)', 'pro-eioqnt' => 'EIO Quint (Pro)', 'pro-eiosn' => 'EIO Sine (Pro)', 'pro-eioex' => 'EIO Expo (Pro)', 'pro-eiocr' => 'EIO Circ (Pro)', 'pro-eiobk' => 'EIO Back (Pro)', ]; if (king_addons_freemius()->can_use_premium_code__premium_only()) { /** @noinspection DuplicatedCode */ $timings = [ 'ease-default' => 'Default', 'linear' => 'Linear', 'ease-in' => 'Ease In', 'ease-out' => 'Ease Out', 'ease-in-out' => 'Ease In Out', 'ease-in-quad' => 'Ease In Quad', 'ease-in-cubic' => 'Ease In Cubic', 'ease-in-quart' => 'Ease In Quart', 'ease-in-quint' => 'Ease In Quint', 'ease-in-sine' => 'Ease In Sine', 'ease-in-expo' => 'Ease In Expo', 'ease-in-circ' => 'Ease In Circ', 'ease-in-back' => 'Ease In Back', 'ease-out-quad' => 'Ease Out Quad', 'ease-out-cubic' => 'Ease Out Cubic', 'ease-out-quart' => 'Ease Out Quart', 'ease-out-quint' => 'Ease Out Quint', 'ease-out-sine' => 'Ease Out Sine', 'ease-out-expo' => 'Ease Out Expo', 'ease-out-circ' => 'Ease Out Circ', 'ease-out-back' => 'Ease Out Back', 'ease-in-out-quad' => 'Ease In Out Quad', 'ease-in-out-cubic' => 'Ease In Out Cubic', 'ease-in-out-quart' => 'Ease In Out Quart', 'ease-in-out-quint' => 'Ease In Out Quint', 'ease-in-out-sine' => 'Ease In Out Sine', 'ease-in-out-expo' => 'Ease In Out Expo', 'ease-in-out-circ' => 'Ease In Out Circ', 'ease-in-out-back' => 'Ease In Out Back', ]; } return $timings; } public static function getAnimationTimingsConditionsPro() { return [ 'pro-eibk', 'pro-eicb', 'pro-eicr', 'pro-eiex', 'pro-eio', 'pro-eiobk', 'pro-eiocb', 'pro-eiocr', 'pro-eioex', 'pro-eioqd', 'pro-eioqnt', 'pro-eioqrt', 'pro-eiosn', 'pro-eiqd', 'pro-eiqnt', 'pro-eiqrt', 'pro-eisn', 'pro-eobk', 'pro-eocb', 'pro-eocr', 'pro-eoex', 'pro-eoqd', 'pro-eoqnt', 'pro-eoqrt', 'pro-eosn', ]; } public static function isBlogArchive() { return ( is_home() && '0' === get_option('page_on_front') && '0' === get_option('page_for_posts') ) || ( intval(get_option('page_for_posts')) === get_queried_object_id() && !is_404() ); } public static function filterOembedResults($html) { preg_match('/src="([^"]+)"/', $html, $m); return $m[1] . '&auto_play=true'; } public static function getWooCommerceTaxonomies() { $filtered = array_filter(get_object_taxonomies('product'), fn($t) => get_taxonomy($t)->show_ui); return array_combine($filtered, array_map(fn($t) => get_taxonomy($t)->label, $filtered)); } public static function getCustomMetaKeysTaxonomies() { $data = []; $tax_types = Core::getCustomTypes('tax', false); foreach ($tax_types as $taxonomy_slug => $post_type_name) { $meta_keys = []; foreach (get_terms($taxonomy_slug) as $tax) { $keys = array_keys(get_term_meta($tax->term_id)); $keys = array_filter($keys, fn($key) => '_' !== $key[0]); $meta_keys = array_merge($meta_keys, $keys); } $data[$taxonomy_slug] = array_unique($meta_keys); } $merged = call_user_func_array('array_merge', array_values($data)); $merged_meta_keys = array_values(array_unique($merged)); $options = array_combine($merged_meta_keys, $merged_meta_keys); return [$data, $options]; } public static function getMailchimpLists() { $api_key = get_option('king_addons_mailchimp_api_key', ''); $mailchimp_list = ['def' => esc_html__('Select List', 'king-addons')]; if (!$api_key) { return $mailchimp_list; } $parts = explode('-', (string) $api_key); if (count($parts) < 2 || '' === $parts[1]) { return $mailchimp_list; } $url = 'https://' . $parts[1] . '.api.mailchimp.com/3.0/lists/'; $response = wp_remote_get($url, [ 'headers' => ['Authorization' => 'Basic ' . base64_encode('user:' . $api_key)] ]); $body = json_decode(wp_remote_retrieve_body($response)); if (!empty($body->lists)) { foreach ($body->lists as $list) { $mailchimp_list[$list->id] = $list->name . ' (' . $list->stats->member_count . ')'; } } return $mailchimp_list; } public static function getMailchimpGroups() { $apiKey = (string) get_option('king_addons_mailchimp_api_key', ''); $groups = ['def' => 'Select Group']; if ('' === $apiKey || false === strpos($apiKey, '-')) { return $groups; } $dc = substr($apiKey, strpos($apiKey, '-') + 1); if ('' === $dc) { return $groups; } $domain = 'https://' . $dc . '.api.mailchimp.com/3.0/'; $authArgs = ['headers' => ['Authorization' => 'Basic ' . base64_encode('user:' . $apiKey)]]; $mailchimpIDs = Core::getMailchimpLists(); foreach ($mailchimpIDs as $audience => $ignore) { if ($audience === 'def') { continue; } $cats_res = wp_remote_get("{$domain}lists/$audience/interest-categories", $authArgs); if (is_wp_error($cats_res)) { continue; } $cats = json_decode((string) wp_remote_retrieve_body($cats_res))->categories ?? []; foreach ($cats as $cat) { $interests_res = wp_remote_get("{$domain}lists/$audience/interest-categories/$cat->id/interests", $authArgs); if (is_wp_error($interests_res)) { continue; } $interests = json_decode((string) wp_remote_retrieve_body($interests_res))->interests ?? []; foreach ($interests as $int) { $groups[$int->id] = $int->name; } } } return $groups; } public static function getShopURL($settings) { global $wp; $url = ('' === get_option('permalink_structure')) ? remove_query_arg(['page', 'paged'], add_query_arg($wp->query_string, '', home_url($wp->request))) : preg_replace('%/page/[0-9]+%', '', home_url(trailingslashit($wp->request))); $url = add_query_arg('kingaddonsfilters', '', $url); $single_params = [ 'min_price' => true, 'max_price' => true, 'orderby' => false, 'psearch' => false, 'filter_product_cat' => false, 'filter_product_tag' => false, 'filter_rating' => false, ]; foreach ($single_params as $param => $needs_clean) { if (isset($_GET[$param])) { $value = wp_unslash($_GET[$param]); $value = $needs_clean ? wc_clean($value) : $value; $url = add_query_arg($param, $value, $url); } } /** @noinspection DuplicatedCode */ if ($chosen_attrs = WC()->query->get_layered_nav_chosen_attributes()) { foreach ($chosen_attrs as $name => $data) { $filter_name = wc_attribute_taxonomy_slug($name); if (!empty($data['terms'])) { $url = add_query_arg('filter_' . $filter_name, implode(',', $data['terms']), $url); } if (!empty($settings)) { if ('or' === $settings['tax_query_type'] || isset($_GET['query_type_' . $filter_name])) { $url = add_query_arg('query_type_' . $filter_name, 'or', $url); } } } } return $url; } public static function getClientIP() { $server_ip_keys = [ 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR', ]; foreach ($server_ip_keys as $key) { if (isset($_SERVER[$key])) { $ip = wp_kses_post_deep(wp_unslash($_SERVER[$key])); if (filter_var($ip, FILTER_VALIDATE_IP)) { return $ip; } } } return '127.0.0.1'; } public static function getCustomMetaKeys() { // Get all custom post types (slug => name). $post_types = Core::getCustomTypes('post', false); // Build $data with each post type's unique custom meta keys (excluding keys beginning with "_"). $data = array_combine( array_keys($post_types), array_map(function ($slug) { $keys = []; foreach (get_posts(['post_type' => $slug, 'posts_per_page' => -1]) as $post) { // get_post_custom_keys can return null, so cast to array: foreach ((array) get_post_custom_keys($post->ID) as $meta_key) { // Exclude protected keys (those beginning with "_"). if ($meta_key[0] !== '_') { $keys[] = $meta_key; } } } return array_values(array_unique($keys)); }, array_keys($post_types)) ); // Flatten all meta keys across all post types, remove duplicates, and reindex. $merged_meta_keys = array_values(array_unique(array_merge([], ...$data))); // Create an associative array where key == value (for convenient dropdowns, etc.). $options = array_combine($merged_meta_keys, $merged_meta_keys); // Return both the per-post-type data and the merged, deduplicated options. return [$data, $options]; } public function enqueueLightboxDynamicStyles() { wp_register_style('king-addons-lightbox-dynamic-style', false); wp_enqueue_style('king-addons-lightbox-dynamic-style'); $bg = esc_html(get_option('king_addons_lightbox_bg_color', 'rgba(0,0,0,0.6)')); $toolbar = esc_html(get_option('king_addons_lightbox_toolbar_color', 'rgba(0,0,0,0.8)')); $caption = esc_html(get_option('king_addons_lightbox_caption_color', 'rgba(0,0,0,0.8)')); $gallery = esc_html(get_option('king_addons_lightbox_gallery_color', '#444444')); $progress_bar = esc_html(get_option('king_addons_lightbox_pb_color', '#8a8a8a')); $ui_color = esc_html(get_option('king_addons_lightbox_ui_color', '#efefef')); $icon_size = floatval(get_option('king_addons_lightbox_icon_size', 20)); $icon_size_big = $icon_size + 4; $ui_hover = esc_html(get_option('king_addons_lightbox_ui_hover_color', '#ffffff')); $text_color = esc_html(get_option('king_addons_lightbox_text_color', '#efefef')); $text_size = esc_html(get_option('king_addons_lightbox_text_size', 14)); $arrow_size = esc_html(get_option('king_addons_lightbox_arrow_size', 35)); $custom_css = "#lg-counter { color: $text_color !important; font-size: {$text_size}px !important; opacity: 0.9; } .lg-backdrop { background-color: $bg !important; } .lg-dropdown:after { border-bottom-color: $toolbar !important; } .lg-icon { color: $ui_color !important; font-size: {$icon_size}px !important; background-color: transparent !important; } .lg-icon.lg-toogle-thumb { font-size: {$icon_size_big}px !important; } .lg-icon:hover, .lg-dropdown-text:hover { color: $ui_hover !important; } .lg-prev, .lg-next { font-size: {$arrow_size}px !important; } .lg-progress { background-color: $progress_bar !important; } .lg-sub-html { background-color: $caption !important; } .lg-sub-html, .lg-dropdown-text { color: $text_color !important; font-size: {$text_size}px !important; } .lg-thumb-item { border-radius: 0 !important; border: none !important; opacity: 0.5; } .lg-thumb-item.active { opacity: 1; } .lg-thumb-outer, .lg-progress-bar { background-color: $gallery !important; } .lg-thumb-outer { padding: 0 10px; } .lg-toolbar, .lg-dropdown { background-color: $toolbar !important; }"; wp_add_inline_style('king-addons-lightbox-dynamic-style', $custom_css); } /** * Enqueues the AI button injection script in the Elementor editor panel. * * @return void */ public function enqueueAiFieldScript(): void { $ai_options = get_option('king_addons_ai_options', []); wp_enqueue_script( 'king-addons-ai-field', KING_ADDONS_URL . 'includes/admin/js/ai-textfield.js', ['jquery', 'elementor-editor'], KING_ADDONS_VERSION, true ); // Localize for AJAX wp_localize_script( 'king-addons-ai-field', 'KingAddonsAiField', [ 'ajax_url' => admin_url('admin-ajax.php'), 'generate_nonce' => wp_create_nonce('king_addons_ai_generate_nonce'), 'change_nonce' => wp_create_nonce('king_addons_ai_change_nonce'), 'generate_action' => 'king_addons_ai_generate_text', 'change_action' => 'king_addons_ai_change_text', 'icon_url' => KING_ADDONS_URL . 'includes/admin/img/ai.svg', 'rewrite_icon_url' => KING_ADDONS_URL . 'includes/admin/img/ai-refresh.svg', 'settings_url' => admin_url('admin.php?page=king-addons-ai-settings'), 'plugin_url' => KING_ADDONS_URL, 'is_pro' => king_addons_freemius()->can_use_premium_code__premium_only() ? true : false, 'premium_active' => king_addons_freemius()->can_use_premium_code__premium_only() ? true : false, 'translator_enabled' => isset($ai_options['enable_ai_page_translator']) ? (bool) $ai_options['enable_ai_page_translator'] : true, // Editor prompts name the configured provider rather than always OpenAI. 'provider' => \King_Addons\AI_Provider::getProvider(), 'provider_label' => \King_Addons\AI_Provider::getLabel(), 'api_keys_url' => \King_Addons\AI_Provider::getApiKeysUrl(), 'api_keys_label' => \King_Addons\AI_Provider::isOpenRouter() ? esc_html__('OpenRouter Keys', 'king-addons') : esc_html__('OpenAI Platform', 'king-addons'), 'setup_billing_note' => \King_Addons\AI_Provider::isOpenRouter() ? esc_html__('Free models work without adding any credit.', 'king-addons') : esc_html__('and top up your OpenAI account balance by at least $5', 'king-addons'), 'setup_cost_note' => \King_Addons\AI_Provider::isOpenRouter() ? esc_html__('Free models cost nothing. Paid models cost pennies (about $0.01 per full page).', 'king-addons') : esc_html__('Processing a page costs pennies (about $0.01 per full page).', 'king-addons'), /** * Before either provider has a key there is no "configured * provider" to describe - the setting is just its default. The * setup dialog then has to present the choice instead of sending * everyone to OpenAI. */ 'has_any_key' => ( '' !== \King_Addons\AI_Provider::getApiKey(\King_Addons\AI_Provider::OPENAI) || '' !== \King_Addons\AI_Provider::getApiKey(\King_Addons\AI_Provider::OPENROUTER) ), 'setup_providers' => [ [ 'name' => esc_html__('OpenRouter', 'king-addons'), 'url' => \King_Addons\AI_Provider::getApiKeysUrl(\King_Addons\AI_Provider::OPENROUTER), 'note' => esc_html__('has free models, no credit needed to start', 'king-addons'), ], [ 'name' => esc_html__('OpenAI', 'king-addons'), 'url' => \King_Addons\AI_Provider::getApiKeysUrl(\King_Addons\AI_Provider::OPENAI), 'note' => esc_html__('needs at least $5 on your account balance', 'king-addons'), ], ], 'setup_cost_note_neutral' => esc_html__('With a free OpenRouter model, nothing. On a paid model it is pennies (about $0.01 per full page).', 'king-addons'), 'missing_key_message' => sprintf( /* translators: %s: provider name */ esc_html__('%s API key is missing or invalid. Please configure your API key in AI Settings.', 'king-addons'), \King_Addons\AI_Provider::getLabel() ), ] ); } /** * Enqueues the AI image generation field script in the Elementor editor panel. * * @return void */ public function enqueueAiImageGenerationScript(): void { wp_enqueue_script( 'king-addons-ai-image-field', KING_ADDONS_URL . 'includes/admin/js/ai-imagefield.js', ['jquery', 'elementor-editor'], KING_ADDONS_VERSION, true ); // Localize for AJAX wp_localize_script( 'king-addons-ai-image-field', 'KingAddonsAiImageField', [ 'ajax_url' => admin_url('admin-ajax.php'), 'generate_nonce' => wp_create_nonce('king_addons_ai_generate_image_nonce'), 'generate_action' => 'king_addons_ai_generate_image', 'image_model' => \King_Addons\AI_Provider::getImageModel(), // The editor builds its model dropdown from this list, so the // options follow whichever provider is configured. 'image_models' => array_map( static function ($model) { return ['value' => $model['id'], 'label' => $model['label']]; }, \King_Addons\AI_Provider::getModelsFor('image') ), 'provider' => \King_Addons\AI_Provider::getProvider(), 'icon_url' => KING_ADDONS_URL . 'includes/admin/img/ai.svg', 'rewrite_icon_url' => KING_ADDONS_URL . 'includes/admin/img/ai-refresh.svg', 'settings_url' => admin_url('admin.php?page=king-addons-ai-settings'), 'plugin_url' => KING_ADDONS_URL, 'missing_key_message' => sprintf( /* translators: %s: provider name */ esc_html__('%s API key is missing or invalid. Please configure your API key in AI Settings.', 'king-addons'), \King_Addons\AI_Provider::getLabel() ), ] ); } /** * Enqueues the styles for AI prompt UI in the Elementor editor panel. * * @return void */ public function enqueueAiFieldStyles(): void { // Enqueue CSS for the AI prompt UI wp_enqueue_style( 'king-addons-ai-field-css', KING_ADDONS_URL . 'includes/admin/css/ai-textfield.css', [], KING_ADDONS_VERSION ); } /** * Enqueues styles for AI Image Generation UI in the Elementor editor panel. * * @return void */ public function enqueueAiImageFieldStyles(): void { wp_enqueue_style( 'king-addons-ai-imagefield', KING_ADDONS_URL . 'includes/admin/css/ai-imagefield.css', [], KING_ADDONS_VERSION ); } /** * Enqueues the AI page translator script in the Elementor editor panel. * * @return void */ public function enqueueAiTranslatorScript(): void { // Check if AI Page Translator is enabled in settings $ai_options = get_option('king_addons_ai_options', []); $translator_enabled = isset($ai_options['enable_ai_page_translator']) ? (bool) $ai_options['enable_ai_page_translator'] : true; if (!$translator_enabled) { return; // Don't load script if translator is disabled } wp_enqueue_script( 'king-addons-ai-translator', KING_ADDONS_URL . 'includes/admin/js/ai-page-translator.js', ['jquery', 'elementor-editor'], KING_ADDONS_VERSION, true ); // Note: Using existing KingAddonsAiField localization // The translator script will use the same AJAX endpoints and settings // No need for separate localization as it reuses existing AI infrastructure } /** * Render an attachment through an Elementor image-size group control. * * Group_Control_Image_Size::get_attachment_image_html() takes a *key into * $settings* as its third argument, not an attachment ID. Handing it an ID * makes Elementor read $settings[], which raises two PHP warnings and * returns nothing, so callers silently lose the image. * * @param array $settings Widget settings; carries "<$size_key>_size" and friends. * @param string $size_key Name of the image-size group control. * @param int $attachment_id Attachment to render. * * @return string Image HTML, or an empty string when it cannot be rendered. */ /** * Show the person building the page why a widget rendered nothing. * * Only ever printed inside the Elementor editor, so the public page keeps * rendering exactly what it rendered before. * * @param string $message Plain text explaining what the widget still needs. */ public static function renderEditorHint(string $message): void { if (!Plugin::$instance->editor->is_edit_mode()) { return; } $style = 'display:block;padding:12px 14px;border:1px dashed #c3c4c7;border-radius:4px;' . 'background:#f6f7f7;color:#50575e;font-size:13px;line-height:1.5;'; echo '
' . esc_html($message) . '
'; } /** * A setting written straight into a JavaScript object literal has to be a * number. Elementor stores a number field the user cleared as an empty * string, which would emit `slidesPerView: ,` - a syntax error that stops * the whole carousel from initialising - or blow up in PHP when the value * is used in arithmetic. * * @param array $settings Widget settings. * @param string $key Setting to read. * @param int|float $fallback Value to use when the setting is empty or not a number. * * @return string Numeric string, safe to interpolate into JS. */ public static function jsNumber(array $settings, string $key, $fallback = 0): string { $value = $settings[$key] ?? null; // Slider controls keep their number under 'size'. if (is_array($value)) { $value = $value['size'] ?? null; } if (!is_numeric($value)) { $value = $fallback; } return (string) (0 + $value); } public static function getAttachmentImageHTML(array $settings, string $size_key, int $attachment_id): string { if ($attachment_id < 1) { return ''; } $image_key = '__king_addons_image'; $settings[$image_key] = [ 'id' => $attachment_id, 'url' => wp_get_attachment_image_url($attachment_id, 'full') ?: '', ]; return (string) Group_Control_Image_Size::get_attachment_image_html($settings, $size_key, $image_key); } } Core::instance();