PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.74
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.74
51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / Core.php

Core.php in King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder 51.1.74, at includes/Core.php

1,734 lines 73.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Core class do all things at the start of the plugin
5 */
6
7 namespace King_Addons;
8
9 use Elementor\Plugin;
10 use Elementor\Widgets_Manager;
11 use Elementor\Controls_Manager;
12 use King_Addons\Wishlist\Wishlist_Module;
13
14 if (!defined('ABSPATH')) {
15 exit; // Exit if accessed directly.
16 }
17
18 final class Core
19 {
20 /**
21 * Instance
22 *
23 * @var Core|null The single instance of the class.
24 */
25 private static ?Core $_instance = null;
26
27 /**
28 * Wishlist module instance.
29 *
30 * @var Wishlist_Module|null
31 */
32 private ?Wishlist_Module $wishlist_module = null;
33
34 /**
35 * Instance
36 *
37 * Ensures only one instance of the class is loaded or can be loaded.
38 *
39 * @return Core An instance of the class.
40 * @since 1.0.0
41 */
42 public static function instance(): Core
43 {
44 if (is_null(self::$_instance)) {
45 self::$_instance = new self();
46 }
47 return self::$_instance;
48 }
49
50 /**
51 * Check if an extension is enabled.
52 *
53 * Checks database options first, defaults to enabled if not set (new installations).
54 * Falls back to constant check for backward compatibility.
55 *
56 * @param string $extension_id The extension ID (e.g., 'templates-catalog', 'popup-builder').
57 * @param string $constant_name The constant name to check as fallback (e.g., 'KING_ADDONS_EXT_POPUP_BUILDER').
58 * @return bool True if extension is enabled, false otherwise.
59 * @since 1.0.0
60 */
61 private function isExtensionEnabled(string $extension_id, string $constant_name): bool
62 {
63 // Dependency checks (extensions that require other plugins).
64 if ($extension_id === 'woo-builder' && (!class_exists('WooCommerce') || !function_exists('WC'))) {
65 return false;
66 }
67
68 // Get options from database
69 $options = get_option('king_addons_options', []);
70
71 // If a constant is defined and explicitly false, treat it as a hard disable.
72 // This is useful for extensions that are in development and should not be
73 // available/visible even if the database option is enabled.
74 if ($constant_name !== '' && defined($constant_name) && constant($constant_name) === false) {
75 return false;
76 }
77
78 // Check if option exists in database
79 $option_key = 'ext_' . $extension_id;
80 if (isset($options[$option_key])) {
81 // Option exists, use its value
82 return $options[$option_key] === 'enabled';
83 }
84
85 // Option doesn't exist (new installation), default to enabled
86 // But also check constant as fallback for backward compatibility
87 if ($constant_name !== '' && defined($constant_name)) {
88 return constant($constant_name);
89 }
90
91 // Default to enabled for new installations
92 return true;
93 }
94
95 /**
96 * Constructor
97 *
98 * Perform some compatibility checks to make sure basic requirements are meet.
99 * If all compatibility checks pass, initialize the functionality.
100 *
101 * @since 1.0.0
102 */
103 public function __construct()
104 {
105 require_once(KING_ADDONS_PATH . 'includes/ModulesMap.php');
106 require_once(KING_ADDONS_PATH . 'includes/LibrariesMap.php');
107
108 if ($this->hasElementorCompatibility()) {
109
110 // Initial requirements check
111 require_once(KING_ADDONS_PATH . 'includes/helpers/Check_Requirements/Check_Requirements.php');
112
113 // Templates Catalog
114 if ($this->isExtensionEnabled('templates-catalog', 'KING_ADDONS_EXT_TEMPLATES_CATALOG')) {
115 require_once(KING_ADDONS_PATH . 'includes/TemplatesMap.php');
116 require_once(KING_ADDONS_PATH . 'includes/extensions/Templates/CollectionsMap.php');
117 require_once(KING_ADDONS_PATH . 'includes/extensions/Templates/Templates.php');
118
119 // Template Catalog Button for Elementor Editor
120 require_once(KING_ADDONS_PATH . 'includes/extensions/Template_Catalog_Button/Template_Catalog_Button.php');
121 Template_Catalog_Button::instance();
122 }
123
124 // Header & Footer Builder
125 if ($this->isExtensionEnabled('header-footer-builder', 'KING_ADDONS_EXT_HEADER_FOOTER_BUILDER')) {
126 require_once(KING_ADDONS_PATH . 'includes/extensions/Header_Footer_Builder/Header_Footer_Builder.php');
127 Header_Footer_Builder::instance();
128 }
129
130 // Popup Builder
131 if ($this->isExtensionEnabled('popup-builder', 'KING_ADDONS_EXT_POPUP_BUILDER')) {
132 require_once(KING_ADDONS_PATH . 'includes/extensions/Popup_Builder/Popup_Builder.php');
133 Popup_Builder::instance();
134 }
135
136 // Cookie / Consent Bar
137 if ($this->isExtensionEnabled('cookie-consent', 'KING_ADDONS_EXT_COOKIE_CONSENT')) {
138 require_once(KING_ADDONS_PATH . 'includes/extensions/Cookie_Consent/Cookie_Consent.php');
139 Cookie_Consent::instance();
140 }
141
142 // WooCommerce Builder
143 if ($this->isExtensionEnabled('woo-builder', 'KING_ADDONS_EXT_WOO_BUILDER')) {
144 require_once(KING_ADDONS_PATH . 'includes/extensions/Woo_Builder/Woo_Builder.php');
145 if (class_exists('King_Addons\\Woo_Builder')) {
146 new Woo_Builder();
147 }
148 }
149
150 // Sticky Contact Bar
151 if ($this->isExtensionEnabled('sticky-contact-bar', 'KING_ADDONS_EXT_STICKY_CONTACT_BAR')) {
152 require_once(KING_ADDONS_PATH . 'includes/extensions/Sticky_Contact_Bar/Sticky_Contact_Bar.php');
153
154 $pro_loaded = false;
155 if (
156 function_exists('king_addons_freemius')
157 && king_addons_freemius()->can_use_premium_code__premium_only()
158 && defined('KING_ADDONS_PRO_PATH')
159 ) {
160 $pro_file_path = KING_ADDONS_PRO_PATH . 'includes/extensions/Sticky_Contact_Bar_Pro/Sticky_Contact_Bar_Pro.php';
161 if (file_exists($pro_file_path)) {
162 require_once $pro_file_path;
163 if (class_exists('King_Addons\\Sticky_Contact_Bar_Pro')) {
164 new Sticky_Contact_Bar_Pro();
165 $pro_loaded = true;
166 }
167 }
168 }
169
170 if (!$pro_loaded && class_exists('King_Addons\\Sticky_Contact_Bar')) {
171 new Sticky_Contact_Bar();
172 }
173 }
174
175 // Theme Builder
176 if ($this->isExtensionEnabled('theme-builder', 'KING_ADDONS_EXT_THEME_BUILDER')) {
177 require_once(KING_ADDONS_PATH . 'includes/extensions/Theme_Builder/Theme_Builder.php');
178
179 $pro_loaded = false;
180 if (
181 function_exists('king_addons_freemius')
182 && king_addons_freemius()->can_use_premium_code__premium_only()
183 && defined('KING_ADDONS_PRO_PATH')
184 ) {
185 $pro_file_path = KING_ADDONS_PRO_PATH . 'includes/extensions/Theme_Builder_Pro/Theme_Builder_Pro.php';
186 if (file_exists($pro_file_path)) {
187 require_once $pro_file_path;
188 if (class_exists('King_Addons\\Theme_Builder_Pro')) {
189 new Theme_Builder_Pro();
190 $pro_loaded = true;
191 }
192 }
193 }
194
195 if (!$pro_loaded && class_exists('King_Addons\\Theme_Builder')) {
196 new Theme_Builder();
197 }
198 }
199
200 // Custom Cursor
201 if ($this->isExtensionEnabled('custom-cursor', 'KING_ADDONS_EXT_CUSTOM_CURSOR')) {
202 require_once(KING_ADDONS_PATH . 'includes/extensions/Custom_Cursor/Custom_Cursor.php');
203 if (class_exists('King_Addons\\Custom_Cursor')) {
204 new Custom_Cursor();
205 }
206 }
207
208 // Age Gate
209 if ($this->isExtensionEnabled('age-gate', 'KING_ADDONS_EXT_AGE_GATE')) {
210 require_once(KING_ADDONS_PATH . 'includes/extensions/Age_Gate/Age_Gate.php');
211 if (defined('KING_ADDONS_PRO_PATH')) {
212 $age_gate_pro = KING_ADDONS_PRO_PATH . 'includes/extensions/Age_Gate_Pro/Age_Gate_Pro.php';
213 if (file_exists($age_gate_pro)) {
214 require_once $age_gate_pro;
215 }
216 }
217 Age_Gate::instance();
218 }
219
220 // Live Chat & Support Builder
221 if ($this->isExtensionEnabled('live-chat', 'KING_ADDONS_EXT_LIVE_CHAT')) {
222 require_once(KING_ADDONS_PATH . 'includes/extensions/Live_Chat/Live_Chat.php');
223 Live_Chat::instance();
224 }
225
226 // Docs & Knowledge Base
227 if ($this->isExtensionEnabled('docs-kb', 'KING_ADDONS_EXT_DOCS_KB')) {
228 require_once(KING_ADDONS_PATH . 'includes/extensions/Docs_KB/Docs_KB.php');
229 Docs_KB::instance();
230 }
231
232 // Pricing Table Builder
233 if ($this->isExtensionEnabled('pricing-table-builder', 'KING_ADDONS_EXT_PRICING_TABLE_BUILDER')) {
234 require_once(KING_ADDONS_PATH . 'includes/extensions/Pricing_Table_Builder/Pricing_Table_Builder.php');
235 Pricing_Table_Builder::instance();
236 }
237
238 // Custom Code Manager
239 if ($this->isExtensionEnabled('custom-code-manager', 'KING_ADDONS_EXT_CUSTOM_CODE_MANAGER')) {
240 require_once(KING_ADDONS_PATH . 'includes/extensions/Custom_Code_Manager/Custom_Code_Manager.php');
241 Custom_Code_Manager::getInstance();
242 }
243
244 // Fomo Notifications
245 if ($this->isExtensionEnabled('fomo-notifications', 'KING_ADDONS_EXT_FOMO_NOTIFICATIONS')) {
246 require_once(KING_ADDONS_PATH . 'includes/extensions/Fomo_Notifications/Fomo_Notifications.php');
247 Fomo_Notifications::instance();
248 }
249
250 // Smart Links
251 if ($this->isExtensionEnabled('smart-links', 'KING_ADDONS_EXT_SMART_LINKS')) {
252 require_once(KING_ADDONS_PATH . 'includes/extensions/Smart_Links/Smart_Links.php');
253 \King_Addons\Smart_Links\Smart_Links::instance();
254 }
255
256 // Activity Log
257 if ($this->isExtensionEnabled('activity-log', 'KING_ADDONS_EXT_ACTIVITY_LOG')) {
258 require_once(KING_ADDONS_PATH . 'includes/extensions/Activity_Log/Activity_Log.php');
259 \King_Addons\Activity_Log\Activity_Log::instance();
260 }
261
262 // Maintenance Mode
263 if ($this->isExtensionEnabled('maintenance-mode', 'KING_ADDONS_EXT_MAINTENANCE_MODE')) {
264 require_once(KING_ADDONS_PATH . 'includes/extensions/Maintenance_Mode/Maintenance_Mode.php');
265 \King_Addons\Maintenance_Mode\Maintenance_Mode::instance();
266 }
267
268 // Data Table Builder
269 if ($this->isExtensionEnabled('table-builder', 'KING_ADDONS_EXT_TABLE_BUILDER')) {
270 require_once(KING_ADDONS_PATH . 'includes/extensions/Data_Table_Builder/Data_Table_Builder.php');
271 Data_Table_Builder::instance();
272 }
273
274 // Site Preloader Animation
275 if ($this->isExtensionEnabled('site-preloader', 'KING_ADDONS_EXT_SITE_PRELOADER')) {
276 require_once(KING_ADDONS_PATH . 'includes/extensions/Site_Preloader/Site_Preloader.php');
277 Site_Preloader::instance();
278 }
279
280 // Image Optimizer
281 if ($this->isExtensionEnabled('image-optimizer', 'KING_ADDONS_EXT_IMAGE_OPTIMIZER')) {
282 require_once(KING_ADDONS_PATH . 'includes/extensions/Image_Optimizer/Image_Optimizer.php');
283 \King_Addons\Image_Optimizer\Image_Optimizer::instance();
284 }
285
286 // Admin
287 require_once(KING_ADDONS_PATH . 'includes/Admin.php');
288
289 // Rating Notice (admin only)
290 // Temporarily disabled
291 // if (is_admin()) {
292 // require_once(KING_ADDONS_PATH . 'includes/admin/notices/RatingNotice.php');
293 // \King_Addons\Admin\Notices\RatingNotice::instance();
294 // }
295
296 // Additional - Controls
297 require_once(KING_ADDONS_PATH . 'includes/controls/Ajax_Select2/Ajax_Select2.php');
298 require_once(KING_ADDONS_PATH . 'includes/controls/Ajax_Select2/Ajax_Select2_API.php');
299 require_once(KING_ADDONS_PATH . 'includes/controls/Animations/Animations.php');
300 require_once(KING_ADDONS_PATH . 'includes/controls/Animations/Button_Animations.php');
301
302 // Additional - Widgets
303 require_once(KING_ADDONS_PATH . 'includes/widgets/Search/Search_Ajax.php');
304 require_once(KING_ADDONS_PATH . 'includes/widgets/MailChimp/MailChimp_Ajax.php');
305
306 // Additional - Grids, Magazine Grid
307 require_once(KING_ADDONS_PATH . 'includes/helpers/Grid/Filter_Posts_Ajax.php');
308 require_once(KING_ADDONS_PATH . 'includes/helpers/Grid/Filter_WooCommerce_Products_Ajax.php');
309 require_once(KING_ADDONS_PATH . 'includes/helpers/Grid/Post_Likes_Ajax.php');
310
311 // Additional - Form Builder
312 if (KING_ADDONS_WGT_FORM_BUILDER) {
313 require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/Create_Submission.php');
314 require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/Send_Email.php');
315 require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/Send_Webhook.php');
316 require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/Subscribe_Mailchimp.php');
317 require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/Update_Action_Meta.php');
318 require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/Upload_Email_File.php');
319 require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/Verify_Google_Recaptcha.php');
320 require_once(KING_ADDONS_PATH . 'includes/widgets/Form_Builder/helpers/View_Submissions_Pro.php');
321 }
322
323 // ADDITIONAL CLASSES
324
325 // AI SEO Tools (Alt Text Generator + Auto Tagging)
326 if ($this->isExtensionEnabled('ai-seo-tools', 'KING_ADDONS_EXT_AI_SEO_TOOLS')) {
327 require_once(KING_ADDONS_PATH . 'includes/extensions/AI_SEO_Tools/AI_SEO_Tools.php');
328 if (class_exists('King_Addons\\AI_SEO_Tools\\AI_SEO_Tools')) {
329 \King_Addons\AI_SEO_Tools\AI_SEO_Tools::instance();
330 }
331 }
332
333 // Wishlist module - check extension toggle
334 if ($this->isExtensionEnabled('wishlist', 'KING_ADDONS_EXT_WISHLIST')) {
335 require_once KING_ADDONS_PATH . 'includes/wishlist/Wishlist_DB.php';
336 require_once KING_ADDONS_PATH . 'includes/wishlist/Wishlist_Session.php';
337 require_once KING_ADDONS_PATH . 'includes/wishlist/Wishlist_Settings.php';
338 require_once KING_ADDONS_PATH . 'includes/wishlist/Wishlist_Service.php';
339 require_once KING_ADDONS_PATH . 'includes/wishlist/Wishlist_Renderer.php';
340 require_once KING_ADDONS_PATH . 'includes/wishlist/Wishlist_Frontend.php';
341 require_once KING_ADDONS_PATH . 'includes/wishlist/Wishlist_WooCommerce.php';
342 require_once KING_ADDONS_PATH . 'includes/wishlist/Wishlist_Module.php';
343 $this->wishlist_module = new Wishlist_Module();
344 }
345
346 // Dynamic Posts Grid AJAX Helper - Initialize regardless of Elementor compatibility
347 // This is needed for AJAX functionality to work even when PRO version is disabled
348 require_once(KING_ADDONS_PATH . 'includes/helpers/Dynamic_Posts_Grid_Ajax.php');
349 \King_Addons\Dynamic_Posts_Grid_Ajax::get_instance();
350
351 // Screenshot Generator
352 // require_once(KING_ADDONS_PATH . 'includes/extensions/Templates/screenshot-generator.php');
353 // require_once(KING_ADDONS_PATH . 'includes/extensions/Templates/screenshot-admin-page.php');
354 // new King_Addons\KingAddons\ScreenshotAdmin();
355
356 // END: ADDITIONAL CLASSES
357
358 self::enableWidgetsByDefault();
359
360 add_action('elementor/init', [$this, 'initElementor']);
361
362 add_action('elementor/elements/categories_registered', [$this, 'addWidgetCategory']);
363 add_action('elementor/controls/controls_registered', [$this, 'registerControls']);
364
365 self::enableFeatures();
366
367 // Load and register AJAX handlers for Login Register Form widget
368 require_once(KING_ADDONS_PATH . 'includes/widgets/Login_Register_Form/Login_Register_Form_Ajax.php');
369 require_once(KING_ADDONS_PATH . 'includes/widgets/Login_Register_Form/User_Profile_Fields.php');
370 require_once(KING_ADDONS_PATH . 'includes/widgets/Login_Register_Form/Email_Handler.php');
371 require_once(KING_ADDONS_PATH . 'includes/widgets/Login_Register_Form/Social_Login_Handler.php');
372 add_action('wp_ajax_nopriv_king_addons_user_login', ['King_Addons\Widgets\Login_Register_Form\Login_Register_Form_Ajax', 'handle_login_ajax']);
373 add_action('wp_ajax_king_addons_user_login', ['King_Addons\Widgets\Login_Register_Form\Login_Register_Form_Ajax', 'handle_login_ajax']);
374 add_action('wp_ajax_nopriv_king_addons_user_register', ['King_Addons\Widgets\Login_Register_Form\Login_Register_Form_Ajax', 'handle_register_ajax']);
375 add_action('wp_ajax_king_addons_user_register', ['King_Addons\Widgets\Login_Register_Form\Login_Register_Form_Ajax', 'handle_register_ajax']);
376 add_action('wp_ajax_nopriv_king_addons_user_lostpassword', ['King_Addons\Widgets\Login_Register_Form\Login_Register_Form_Ajax', 'handle_lostpassword_ajax']);
377 add_action('wp_ajax_king_addons_user_lostpassword', ['King_Addons\Widgets\Login_Register_Form\Login_Register_Form_Ajax', 'handle_lostpassword_ajax']);
378
379 // Initialize user profile fields
380 \King_Addons\Widgets\Login_Register_Form\User_Profile_Fields::init();
381
382 // Initialize social login handler
383 \King_Addons\Widgets\Login_Register_Form\Social_Login_Handler::init();
384
385 // Initialize Security Dashboard for admins (only if Login Register Form widget is enabled)
386 if (is_admin()) {
387 $widget_options = get_option('king_addons_options', []);
388 $login_form_enabled = !isset($widget_options['login-register-form']) || $widget_options['login-register-form'] === 'enabled';
389 if ($login_form_enabled) {
390 require_once(KING_ADDONS_PATH . 'includes/widgets/Login_Register_Form/Security_Dashboard.php');
391 \King_Addons\Widgets\Login_Register_Form\Security_Dashboard::init();
392 }
393 }
394
395 new Admin();
396
397 add_action('wp_enqueue_scripts', [$this, 'enqueueFrontendStyles']);
398 add_action('wp_enqueue_scripts', [$this, 'enqueueLightboxDynamicStyles']);
399
400 // Notice - Upgrade Suggestion
401 if (!king_addons_freemius()->can_use_premium_code__premium_only()) {
402 add_action('wp_ajax_king_addons_premium_notice_dismiss', [$this, 'king_addons_premium_notice_dismiss_callback']);
403 add_action('admin_notices', [$this, 'showNoticeUpgrade']);
404 }
405
406 // Dashboard UI settings AJAX handler
407 add_action('wp_ajax_king_addons_save_dashboard_ui', [$this, 'king_addons_save_dashboard_ui_callback']);
408
409 // Conditionally enqueue AI text-field enhancement script and styles in Elementor editor
410 $ai_options = get_option('king_addons_ai_options', []);
411 $enable_ai_text_buttons = isset($ai_options['enable_ai_buttons']) ? (bool) $ai_options['enable_ai_buttons'] : true;
412 if ($enable_ai_text_buttons) {
413 // Enqueue AI text-field enhancement script
414 add_action('elementor/editor/after_enqueue_scripts', [$this, 'enqueueAiFieldScript']);
415 // Enqueue styles for AI prompt UI
416 add_action('elementor/editor/after_enqueue_styles', [$this, 'enqueueAiFieldStyles']);
417 // Enqueue AI page translator script
418 add_action('elementor/editor/after_enqueue_scripts', [$this, 'enqueueAiTranslatorScript']);
419 }
420
421 $enable_ai_image_generation_button = isset($ai_options['enable_ai_image_generation_button']) ? (bool) $ai_options['enable_ai_image_generation_button'] : true;
422 if ($enable_ai_image_generation_button) {
423 add_action('elementor/editor/after_enqueue_scripts', [$this, 'enqueueAiImageGenerationScript']);
424 // Enqueue styles for AI Image Generation controls
425 add_action('elementor/editor/after_enqueue_styles', [$this, 'enqueueAiImageFieldStyles']);
426 }
427 }
428 }
429
430 function king_addons_premium_notice_dismiss_callback()
431 {
432 // Check user capabilities
433 if (!current_user_can('manage_options')) {
434 wp_die();
435 }
436
437 check_ajax_referer('king_addons_premium_notice_dismiss', 'nonce');
438
439 $user_id = get_current_user_id();
440 // Save the current time as the last dismissal time for the premium notice
441 update_user_meta($user_id, 'king_addons_premium_notice_dismissed_time', time());
442 wp_die(); // End AJAX request
443 }
444
445 /**
446 * AJAX callback for saving dashboard UI settings (theme, view toggle)
447 *
448 * @since 1.0.0
449 */
450 function king_addons_save_dashboard_ui_callback()
451 {
452 // Check user capabilities
453 if (!current_user_can('manage_options')) {
454 wp_send_json_error(['message' => 'Unauthorized'], 403);
455 }
456
457 check_ajax_referer('king_addons_dashboard_ui', 'nonce');
458
459 $key = isset($_POST['key']) ? sanitize_text_field($_POST['key']) : '';
460
461 $user_id = get_current_user_id();
462
463 // Validate key
464 $allowed_keys = ['dark_theme', 'theme_mode', 'show_descriptions'];
465 if (!in_array($key, $allowed_keys, true)) {
466 wp_send_json_error(['message' => 'Invalid key'], 400);
467 }
468
469 // Theme preference is per-user.
470 if ($key === 'theme_mode') {
471 $mode = isset($_POST['value']) ? sanitize_key($_POST['value']) : '';
472 $allowed_modes = ['dark', 'light', 'auto'];
473 if (!in_array($mode, $allowed_modes, true)) {
474 wp_send_json_error(['message' => 'Invalid theme mode'], 400);
475 }
476
477 update_user_meta($user_id, 'king_addons_theme_mode', $mode);
478
479 // Also store as a global option so pages can fall back when user meta isn't set.
480 update_option('king_addons_theme_mode', $mode);
481
482 wp_send_json_success(['key' => $key, 'value' => $mode]);
483 }
484
485 // Backward compatibility: old boolean dark_theme maps to theme_mode.
486 if ($key === 'dark_theme') {
487 $is_dark = isset($_POST['value']) && $_POST['value'] === '1';
488 $mode = $is_dark ? 'dark' : 'light';
489 update_user_meta($user_id, 'king_addons_theme_mode', $mode);
490 wp_send_json_success(['key' => 'theme_mode', 'value' => $mode]);
491 }
492
493 // Remaining UI settings are still stored as site option (shared).
494 $value = isset($_POST['value']) && $_POST['value'] === '1';
495 $settings = get_option('king_addons_dashboard_ui', []);
496 $settings[$key] = $value;
497 update_option('king_addons_dashboard_ui', $settings);
498
499 wp_send_json_success(['key' => $key, 'value' => $value]);
500 }
501
502 function showNoticeUpgrade()
503 {
504 // Check user capabilities; show notice only to administrators as an example
505 if (!current_user_can('manage_options')) {
506 return;
507 }
508
509 $user_id = get_current_user_id();
510 $now = time();
511 // Retrieve the last time the premium notice was dismissed by the user
512 $last_dismissed = get_user_meta($user_id, 'king_addons_premium_notice_dismissed_time', true);
513
514 // If the premium notice was dismissed less than a week ago (604800 seconds), do not show it
515 if ($last_dismissed && ($now - $last_dismissed) < 604800) {
516 // if ($last_dismissed && ($now - $last_dismissed) < 60) {
517 return;
518 }
519 ?>
520 <div class="king-addons-upgrade-notice notice notice-info is-dismissible"
521 style="border-left: 4px solid #0071e3;padding: 10px 15px;">
522 <p style="font-size: 15px; margin:0; display: flex; align-items: center;">
523 <span>
524 Get <strong style="font-weight: 700;">4,000+</strong> premium templates and sections,
525 <strong style="font-weight: 700;">80+</strong> widgets,
526 <strong style="font-weight: 700;">200+</strong> advanced features,
527 and AI tools for Elementor.
528 From $<strong style="font-weight: 700;">4</strong>/mo, billed annually.
529 </span>
530 </p>
531 <p style="font-size: 14px; opacity: 0.6;">Trusted by 20,000+ users</p>
532 <p style="display: flex;">
533 <a href="https://kingaddons.com/pricing?utm_source=kng-notice-offer&amp;utm_medium=plugin&amp;utm_campaign=kng" target="_blank" class="ka-wb-btn ka-wb-btn-primary" style="
534 background: #0071e3;
535 color: #fff;
536 display: inline-flex;
537 align-items: center;
538 justify-content: center;
539 gap: 6px;
540 padding: 10px 18px;
541 font-size: 14px;
542 font-weight: 500;
543 text-decoration: none;
544 border-radius: 980px;
545 border: none;
546 cursor: pointer;
547 transition: all 0.3s cubic-bezier(0.25, 1, 0.5, 1);
548 white-space: nowrap;
549 font-family: inherit;
550 ">Upgrade to Pro<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="
551 width: 16px;
552 height: 16px;
553 "><path d="M5 12h14M12 5l7 7-7 7"></path></svg>
554 </a>
555 <a style="margin-left: 20px;display: flex;align-items: center;font-size: 14px;color: #0071e3;"
556 href="https://kingaddons.com/pricing?utm_source=kng-notice-offer&utm_medium=plugin&utm_campaign=kng"
557 class="link">Learn More</a>
558 </p>
559 </div>
560 <script>
561 (function ($) {
562 // Wait for the document to be ready
563 const kingAddonsPremiumNoticeNonce = '<?php echo esc_js(wp_create_nonce('king_addons_premium_notice_dismiss')); ?>';
564 $(document).ready(function () {
565 // Attach click handler to the dismiss button of the premium notice
566 $('.king-addons-upgrade-notice.notice.is-dismissible').on('click', '.notice-dismiss', function () {
567 $.post(ajaxurl, {
568 action: 'king_addons_premium_notice_dismiss',
569 nonce: kingAddonsPremiumNoticeNonce
570 });
571 });
572 });
573 })(jQuery);
574 </script>
575 <?php
576 }
577
578 function enqueueFrontendStyles()
579 {
580 /**
581 * It fixes the default Elementor SVG icon rendering feature (Settings -> Features -> Inline Font Icons)
582 * because sometimes Elementor still renders Font Awesome icons but doesn't load the corresponding Font Awesome styles.
583 * Therefore, we have to enqueue the styles.
584 */
585 wp_enqueue_style(
586 'font-awesome-5-all',
587 ELEMENTOR_ASSETS_URL . 'lib/font-awesome/css/all' . (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG ? '' : '.min') . '.css',
588 false,
589 KING_ADDONS_VERSION
590 );
591 }
592
593 function hasElementorCompatibility(): bool
594 {
595 // Check if Elementor installed and activated
596 if (!did_action('elementor/loaded')) {
597 add_action('admin_notices', [$this, 'showAdminNotice_ElementorRequired']);
598 return false;
599 }
600
601 // Check for required Elementor version
602 if (!version_compare(ELEMENTOR_VERSION, '3.19.0', '>=')) {
603 add_action('admin_notices', [$this, 'showAdminNotice_ElementorMinimumVersion']);
604 return false;
605 }
606
607 return true;
608 }
609
610 function showAdminNotice_ElementorRequired(): void
611 {
612 $screen = get_current_screen();
613 if (isset($screen->parent_file) && 'plugins.php' === $screen->parent_file && 'update' === $screen->id) {
614 return;
615 }
616
617 if (isset(get_plugins()['elementor/elementor.php'])) {
618 if (!current_user_can('activate_plugins') || is_plugin_active('elementor/elementor.php')) {
619 return;
620 }
621 $plugin = 'elementor/elementor.php';
622 $activation_url = wp_nonce_url('plugins.php?action=activate&amp;plugin=' . $plugin . '&amp;plugin_status=all&amp;paged=1&amp;s', 'activate-plugin_' . $plugin);
623 $message = '<div class="error"><p>' . esc_html__('King Addons plugin is not working because you need to activate the Elementor plugin.', 'king-addons') . '</p>';
624 /** @noinspection HtmlUnknownTarget */
625 $message .= '<p>' . sprintf('<a href="%s" class="button-primary">%s</a>', $activation_url, esc_html__('Activate Elementor now', 'king-addons')) . '</p></div>';
626 } else {
627 if (!current_user_can('install_plugins')) {
628 return;
629 }
630 $install_url = wp_nonce_url(self_admin_url('update.php?action=install-plugin&plugin=elementor'), 'install-plugin_elementor');
631 $message = '<div class="error"><p>' . esc_html__('King Addons plugin is not working because you need to install the Elementor plugin.', 'king-addons') . '</p>';
632 /** @noinspection HtmlUnknownTarget */
633 $message .= '<p>' . sprintf('<a href="%s" class="button-primary">%s</a>', $install_url, esc_html__('Install Elementor now', 'king-addons')) . '</p></div>';
634 }
635 echo $message;
636 }
637
638 function showAdminNotice_ElementorMinimumVersion(): void
639 {
640 $message = sprintf(
641 /* translators: 1: Plugin name 2: Elementor 3: Required Elementor version */
642 esc_html__('%1$s plugin requires %2$s plugin version %3$s or greater.', 'king-addons'),
643 esc_html__('King Addons', 'king-addons'),
644 esc_html__('Elementor', 'king-addons'),
645 '3.19.0'
646 );
647 echo '<div class="notice notice-error"><p>' . esc_html($message) . '</p></div>';
648 }
649
650 public function initElementor(): void
651 {
652 add_action('elementor/widgets/register', [$this, 'registerWidgets']);
653 add_action('elementor/editor/after_enqueue_styles', [$this, 'enqueueEditorStyles']);
654 add_action('elementor/editor/after_enqueue_scripts', [$this, 'enqueueEditorScripts']);
655 add_action('elementor/preview/enqueue_styles', [$this, 'enqueueEditorPreviewStyles']);
656 }
657
658 function enqueueEditorPreviewStyles(): void
659 {
660 wp_enqueue_style(
661 KING_ADDONS_ASSETS_UNIQUE_KEY . '-elementor-preview',
662 KING_ADDONS_URL . 'includes/admin/css/elementor-preview.css',
663 [],
664 KING_ADDONS_VERSION
665 );
666 }
667
668 function addWidgetCategory(): void
669 {
670 $elements_manager = Plugin::instance()->elements_manager;
671
672 // Add our categories
673 $elements_manager->add_category(
674 'king-addons',
675 [
676 'title' => esc_html__('King Addons', 'king-addons'),
677 'icon' => 'fa fa-plug'
678 ]
679 );
680
681 $elements_manager->add_category(
682 'king-addons-woo-builder',
683 [
684 'title' => esc_html__('King Addons Woo Builder', 'king-addons'),
685 'icon' => 'fa fa-shopping-cart'
686 ]
687 );
688
689 // Move our categories to the top of the panel
690 $this->reorderWidgetCategories($elements_manager);
691 }
692
693 /**
694 * Reorder widget categories so King Addons categories appear after Layout and Basic.
695 *
696 * @param \Elementor\Elements_Manager $elements_manager
697 * @return void
698 */
699 private function reorderWidgetCategories($elements_manager): void
700 {
701 try {
702 $reflection = new \ReflectionClass($elements_manager);
703 $categories_property = $reflection->getProperty('categories');
704 $categories_property->setAccessible(true);
705
706 $categories = $categories_property->getValue($elements_manager);
707 if (!is_array($categories)) {
708 return;
709 }
710
711 // Extract our categories
712 $our_categories = [];
713 if (isset($categories['king-addons'])) {
714 $our_categories['king-addons'] = $categories['king-addons'];
715 unset($categories['king-addons']);
716 }
717 if (isset($categories['king-addons-woo-builder'])) {
718 $our_categories['king-addons-woo-builder'] = $categories['king-addons-woo-builder'];
719 unset($categories['king-addons-woo-builder']);
720 }
721
722 // Insert our categories after Layout and Basic
723 $reordered = [];
724 $insert_after = ['layout', 'basic']; // Categories after which we insert ours
725 $inserted = false;
726
727 foreach ($categories as $key => $value) {
728 $reordered[$key] = $value;
729
730 // Insert our categories after the last target category
731 if (!$inserted && in_array($key, $insert_after, true)) {
732 // Check if next category is also in our target list
733 $keys = array_keys($categories);
734 $current_index = array_search($key, $keys, true);
735 $next_key = $keys[$current_index + 1] ?? null;
736
737 // Only insert if the next category is NOT in our target list
738 if ($next_key === null || !in_array($next_key, $insert_after, true)) {
739 $reordered = array_merge($reordered, $our_categories);
740 $inserted = true;
741 }
742 }
743 }
744
745 // If target categories weren't found, append at the end
746 if (!$inserted) {
747 $reordered = array_merge($reordered, $our_categories);
748 }
749
750 // Set back the reordered array
751 $categories_property->setValue($elements_manager, $reordered);
752 } catch (\ReflectionException $e) {
753 // Silently fail if reflection doesn't work (e.g., future Elementor changes)
754 }
755 }
756
757 /**
758 * Registers Elementor widgets with a mechanism to skip (and remember) broken widgets
759 * that caused a fatal error previously, and try them again if the plugin version is updated.
760 *
761 * @param Widgets_Manager $widgets_manager
762 * @return void
763 */
764 function registerWidgets(Widgets_Manager $widgets_manager): void
765 {
766 // Used to track which widget is currently being loaded when a fatal error occurs
767 static $currentlyLoadingWidgetId = null;
768
769 $currentPluginVersion = KING_ADDONS_VERSION;
770
771 // Get plugin options to check if a widget is enabled
772 $options = get_option('king_addons_options');
773 $options = is_array($options) ? $options : [];
774
775 // Extension toggles (used to prevent loading dependent widgets when extension is disabled).
776 $wishlist_extension_enabled = !isset($options['ext_wishlist']) || $options['ext_wishlist'] === 'enabled';
777 if (defined('KING_ADDONS_EXT_WISHLIST') && KING_ADDONS_EXT_WISHLIST === false) {
778 $wishlist_extension_enabled = false;
779 }
780
781 // Ensure Woo Builder base class is available for single product widgets.
782 $abstract_single_widget = KING_ADDONS_PATH . 'includes/helpers/Woo_Builder/Abstract_Single_Widget.php';
783 if (file_exists($abstract_single_widget)) {
784 require_once $abstract_single_widget;
785 }
786
787 // Ensure Woo Builder base class is available for archive widgets.
788 $abstract_archive_widget = KING_ADDONS_PATH . 'includes/helpers/Woo_Builder/Abstract_Archive_Widget.php';
789 if (file_exists($abstract_archive_widget)) {
790 require_once $abstract_archive_widget;
791 }
792
793 // Ensure Woo Builder base class is available for cart widgets.
794 $abstract_cart_widget = KING_ADDONS_PATH . 'includes/helpers/Woo_Builder/Abstract_Cart_Widget.php';
795 if (file_exists($abstract_cart_widget)) {
796 require_once $abstract_cart_widget;
797 }
798
799 // Ensure Woo Builder base class is available for checkout widgets.
800 $abstract_checkout_widget = KING_ADDONS_PATH . 'includes/helpers/Woo_Builder/Abstract_Checkout_Widget.php';
801 if (file_exists($abstract_checkout_widget)) {
802 require_once $abstract_checkout_widget;
803 }
804
805 // Ensure Woo Builder base class is available for My Account widgets.
806 $abstract_my_account_widget = KING_ADDONS_PATH . 'includes/helpers/Woo_Builder/Abstract_My_Account_Widget.php';
807 if (file_exists($abstract_my_account_widget)) {
808 require_once $abstract_my_account_widget;
809 }
810
811 /**
812 * Retrieve the array of broken widgets from the WordPress options.
813 * The structure is expected to be something like:
814 *
815 * 'widget_id' => [
816 * 'version' => '1.2.0',
817 * 'error' => 'Some fatal error message'
818 * ],
819 * ...
820 *
821 */
822 $brokenWidgets = get_option('king_addons_broken_widgets', []);
823
824 /**
825 * STEP 1: Clear out any "broken widgets" where the stored version is
826 * less than the current plugin version. This gives them a second chance
827 * after an update, assuming the issue may have been fixed.
828 */
829 foreach ($brokenWidgets as $brokenId => $brokenData) {
830 if (
831 isset($brokenData['version'])
832 && version_compare($currentPluginVersion, $brokenData['version'], '>')
833 ) {
834 // If the plugin version is now higher, we remove the widget from the blacklist
835 unset($brokenWidgets[$brokenId]);
836 }
837 }
838
839 // Update the option after cleaning up
840 update_option('king_addons_broken_widgets', $brokenWidgets);
841
842 /**
843 * STEP 2: Use register_shutdown_function to detect any fatal errors (E_ERROR, E_PARSE, etc.)
844 * that might occur during the loading of a widget. If an error is detected, store that widget
845 * in the "broken" list with the current plugin version and the error message.
846 */
847 register_shutdown_function(function () use (&$currentlyLoadingWidgetId, $currentPluginVersion) {
848 $error = error_get_last();
849 if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
850 // If a fatal error occurred while loading a specific widget
851 if (!empty($currentlyLoadingWidgetId)) {
852 $brokenWidgetsLocal = get_option('king_addons_broken_widgets', []);
853 $brokenWidgetsLocal[$currentlyLoadingWidgetId] = [
854 'version' => $currentPluginVersion,
855 'error' => $error['message'] ?? ''
856 ];
857 update_option('king_addons_broken_widgets', $brokenWidgetsLocal);
858 }
859 }
860 });
861
862 /**
863 * STEP 3: Now we iterate through all widgets in our modules map and try to load them.
864 * If a widget is in the broken list, we skip it to avoid repeated fatal errors.
865 */
866 foreach (ModulesMap::getModulesMapArray()['widgets'] as $widget_id => $widget) {
867 // Hard-disable via constant (used to QA/rollout new widgets).
868 $widget_constant = 'KING_ADDONS_WGT_' . strtoupper(str_replace('-', '_', (string) $widget_id));
869 if (defined($widget_constant) && constant($widget_constant) === false) {
870 continue;
871 }
872
873 // Check if the widget is enabled in the options
874 if (!isset($options[$widget_id]) || $options[$widget_id] !== 'enabled') {
875 continue;
876 }
877
878 // Skip Wishlist widgets when Wishlist extension is disabled.
879 // This prevents fatals when wishlist classes aren't loaded.
880 if (!$wishlist_extension_enabled && strpos((string) $widget_id, 'wishlist-') === 0) {
881 continue;
882 }
883
884 // If this widget is listed as broken, skip it
885 if (array_key_exists($widget_id, $brokenWidgets)) {
886 // Log something here if needed:
887 // error_log("Skipping widget {$widget_id}, it previously caused a fatal error.");
888 continue;
889 }
890
891 // Track which widget we're loading
892 $currentlyLoadingWidgetId = $widget_id;
893
894 // Include the base widget class
895 $widget_class = $widget['php-class'];
896 $path_widget_class = "King_Addons\\" . $widget_class;
897 $widget_file = KING_ADDONS_PATH . 'includes/widgets/' . $widget_class . '/' . $widget_class . '.php';
898 if (!file_exists($widget_file)) {
899 // Skip missing widget files to avoid fatal errors if registry is ahead of implementation.
900 $currentlyLoadingWidgetId = null;
901 continue;
902 }
903
904 require_once $widget_file;
905
906 // Check if we can load the Pro version
907 if (
908 function_exists('king_addons_freemius')
909 && king_addons_freemius()->can_use_premium_code__premium_only()
910 && defined('KING_ADDONS_PRO_PATH')
911 ) {
912 if (!empty($widget['has-pro'])) {
913 $pro_file_path = KING_ADDONS_PRO_PATH . 'includes/widgets/' . $widget_class . '_Pro/' . $widget_class . '_Pro.php';
914
915 if (file_exists($pro_file_path)) {
916 require_once($pro_file_path);
917 $path_widget_class_pro = "King_Addons\\" . $widget_class . '_Pro';
918 $widgets_manager->register(new $path_widget_class_pro);
919 } else {
920 // If Pro file doesn't exist, register the base widget
921 $widgets_manager->register(new $path_widget_class);
922 }
923 } else {
924 // No 'has-pro', register the base widget
925 $widgets_manager->register(new $path_widget_class);
926 }
927 } else {
928 // No Freemius Pro available, register the base widget
929 $widgets_manager->register(new $path_widget_class);
930 }
931
932 // Clear the tracking variable after successful load
933 $currentlyLoadingWidgetId = null;
934 }
935 }
936
937 function enableWidgetsByDefault(): void
938 {
939 $options = get_option('king_addons_options');
940
941 foreach (ModulesMap::getModulesMapArray()['widgets'] as $widget_id => $widget) {
942
943 // Hard-disable via constant (used to QA/rollout new widgets).
944 $widget_constant = 'KING_ADDONS_WGT_' . strtoupper(str_replace('-', '_', (string) $widget_id));
945 if (defined($widget_constant) && constant($widget_constant) === false) {
946 continue;
947 }
948
949 if (!($options[$widget_id] ?? null)) {
950 $options[$widget_id] = 'enabled';
951 update_option('king_addons_options', $options);
952 }
953 }
954 }
955
956 /**
957 * Enable and bootstrap registered features.
958 *
959 * Loads free feature classes and, when available and licensed, their Pro counterparts.
960 *
961 * @return void
962 */
963 public function enableFeatures(): void
964 {
965 $options = get_option('king_addons_options');
966
967 foreach (ModulesMap::getModulesMapArray()['features'] as $feature_id => $feature) {
968 // Hard-disable via constant (used to QA/rollout new features).
969 $feature_constant = 'KING_ADDONS_FEAT_' . strtoupper(str_replace('-', '_', (string) $feature_id));
970 if (defined($feature_constant) && constant($feature_constant) === false) {
971 continue;
972 }
973
974 if (!($options[$feature_id] ?? null)) {
975 $options[$feature_id] = 'enabled';
976 update_option('king_addons_options', $options);
977 }
978
979 if ($options[$feature_id] !== 'enabled') {
980 continue;
981 }
982
983 $feature_class = $feature['php-class'];
984 $path_feature_class = "King_Addons\\" . $feature_class;
985 $feature_file = KING_ADDONS_PATH . 'includes/features/' . $feature_class . '/' . $feature_class . '.php';
986
987 if (file_exists($feature_file)) {
988 require_once $feature_file;
989 }
990
991 $pro_loaded = false;
992
993 if (
994 !empty($feature['has-pro'])
995 && function_exists('king_addons_freemius')
996 && king_addons_freemius()->can_use_premium_code__premium_only()
997 && defined('KING_ADDONS_PRO_PATH')
998 ) {
999 $pro_file_path = KING_ADDONS_PRO_PATH . 'includes/features/' . $feature_class . '_Pro/' . $feature_class . '_Pro.php';
1000
1001 if (file_exists($pro_file_path)) {
1002 require_once $pro_file_path;
1003
1004 $path_feature_class_pro = "King_Addons\\" . $feature_class . '_Pro';
1005 if (class_exists($path_feature_class_pro)) {
1006 new $path_feature_class_pro();
1007 $pro_loaded = true;
1008 }
1009 }
1010 }
1011
1012 if (!$pro_loaded && class_exists($path_feature_class)) {
1013 new $path_feature_class();
1014 }
1015 }
1016 }
1017
1018 public function registerControls(Controls_Manager $controls_manager): void
1019 {
1020 $controls_manager->register(new AJAX_Select2\Ajax_Select2());
1021 $controls_manager->register(new Animations\Animations());
1022 $controls_manager->register(new Animations\Animations_Alternative());
1023 $controls_manager->register(new Button_Animations\Button_Animations());
1024 }
1025
1026 function enqueueEditorStyles(): void
1027 {
1028 wp_enqueue_style(KING_ADDONS_ASSETS_UNIQUE_KEY . '-elementor-editor', KING_ADDONS_URL . 'includes/admin/css/elementor-editor.css', '', KING_ADDONS_VERSION);
1029 }
1030
1031 function enqueueEditorScripts(): void
1032 {
1033 wp_enqueue_script(KING_ADDONS_ASSETS_UNIQUE_KEY . '-elementor-editor', KING_ADDONS_URL . 'includes/admin/js/elementor-editor.js', '', KING_ADDONS_VERSION);
1034
1035 // Localize script with PRO status
1036 wp_localize_script(KING_ADDONS_ASSETS_UNIQUE_KEY . '-elementor-editor', 'kingAddonsEditor', [
1037 'isPro' => king_addons_freemius()->can_use_premium_code__premium_only() ? true : false
1038 ]);
1039
1040 wp_enqueue_script(KING_ADDONS_ASSETS_UNIQUE_KEY . '-data-table-export', KING_ADDONS_URL . 'includes/widgets/Data_Table/preview-handler.js', '', KING_ADDONS_VERSION);
1041
1042 if (KING_ADDONS_WGT_FORM_BUILDER) {
1043 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);
1044 }
1045 }
1046
1047 public static function renderProFeaturesSection($module, $section, $type, $widget_name, $features): void
1048 {
1049 if (king_addons_freemius()->can_use_premium_code__premium_only()) {
1050 return;
1051 }
1052
1053 $module->start_controls_section(
1054 'king_addons_pro_features_section',
1055 [
1056 'label' => KING_ADDONS_ELEMENTOR_ICON_PRO . '<span class="king-addons-pro-features-heading">' . esc_html__('Pro Features', 'king-addons') . '</span>',
1057 'tab' => $section ?: null,
1058 ]
1059 );
1060
1061 $list_html = '<ul>' . implode('', array_map(fn($feature) => "<li>$feature</li>", $features)) . '</ul>';
1062
1063 $module->add_control(
1064 'king_addons_pro_features_list',
1065 [
1066 'type' => $type,
1067 'raw' => $list_html . '<a class="king-addons-pro-features-cta-btn" href="https://kingaddons.com/pricing/?utm_source=kng-module-' . $widget_name . '-upgrade-pro&utm_medium=plugin&utm_campaign=kng" target="_blank">' . esc_html__('Upgrade Now', 'king-addons') . '</a>',
1068 'content_classes' => 'king-addons-pro-features-list',
1069 ]
1070 );
1071
1072 $module->end_controls_section();
1073 }
1074
1075 public static function renderUpgradeProNotice($module, $controls_manager, $widget_name, $option, $condition = []): void
1076 {
1077 if (king_addons_freemius()->can_use_premium_code__premium_only()) {
1078 return;
1079 }
1080
1081 $module->add_control(
1082 $option . '_pro_notice_',
1083 [
1084 'raw' => 'Upgrade to the <strong><a href="https://kingaddons.com/pricing/?utm_source=kng-module-' . $widget_name . '-settings-upgrade-pro&utm_medium=plugin&utm_campaign=kng" target="_blank">Pro version</a></strong> now<br> and unlock this feature!',
1085 'type' => $controls_manager,
1086 'content_classes' => 'king-addons-pro-notice',
1087 'condition' => [
1088 $option => $condition,
1089 ]
1090 ]
1091 );
1092 }
1093
1094 public static function getCustomTypes($query, $exclude_defaults = true): array
1095 {
1096 $custom_types = $query === 'tax'
1097 ? get_taxonomies(['show_in_nav_menus' => true], 'objects')
1098 : get_post_types(['show_in_nav_menus' => true], 'objects');
1099
1100 return array_filter(
1101 array_map(fn($type) => $type->label, $custom_types),
1102 fn($label, $key) => !$exclude_defaults || !in_array($key, ['post', 'page', 'category', 'post_tag']),
1103 ARRAY_FILTER_USE_BOTH
1104 );
1105 }
1106
1107 public static function getShareIcon($args = []): string
1108 {
1109 $args = wp_parse_args($args, [
1110 'network' => '',
1111 'url' => '',
1112 'title' => '',
1113 'text' => '',
1114 'image' => '',
1115 'show_whatsapp_title' => 'no',
1116 'show_whatsapp_excerpt' => 'no',
1117 'tooltip' => 'no',
1118 'icons' => 'no',
1119 'labels' => 'no',
1120 'custom_label' => '',
1121 ]);
1122
1123 $url = esc_url($args['url']);
1124 $title = wp_strip_all_tags($args['title']);
1125 $text = wp_strip_all_tags($args['text']);
1126 $image = esc_url($args['image']);
1127 $network = $args['network'];
1128
1129 $get_whatsapp_url = function ($a) {
1130 if ('yes' === $a['show_whatsapp_title'] && 'yes' === $a['show_whatsapp_excerpt']) {
1131 return 'https://api.whatsapp.com/send?text=*' . $a['title'] . '*%0a' . $a['text'] . '%0a' . $a['url'];
1132 } elseif ('yes' === $a['show_whatsapp_title']) {
1133 return 'https://api.whatsapp.com/send?text=*' . $a['title'] . '*%0a' . $a['url'];
1134 } elseif ('yes' === $a['show_whatsapp_excerpt']) {
1135 return 'https://api.whatsapp.com/send?text=*' . $a['text'] . '%0a' . $a['url'];
1136 }
1137 return 'https://api.whatsapp.com/send?text=' . $a['url'];
1138 };
1139
1140 $networks_map = [
1141 'facebook-f' => [
1142 'url' => "https://www.facebook.com/sharer.php?u=$url",
1143 'title' => esc_html__('Facebook', 'king-addons'),
1144 'icon' => 'fab',
1145 ],
1146 'x-twitter' => [
1147 'url' => "https://twitter.com/intent/tweet?url=$url",
1148 'title' => esc_html__('X (Twitter)', 'king-addons'),
1149 'icon' => 'fab',
1150 ],
1151 'linkedin-in' => [
1152 'url' => "https://www.linkedin.com/shareArticle?mini=true&url=$url&title=$title&summary=$text&source=$url",
1153 'title' => esc_html__('LinkedIn', 'king-addons'),
1154 'icon' => 'fab',
1155 ],
1156 'pinterest-p' => [
1157 'url' => "https://www.pinterest.com/pin/create/button/?url=$url&media=$image",
1158 'title' => esc_html__('Pinterest', 'king-addons'),
1159 'icon' => 'fab',
1160 ],
1161 'reddit' => [
1162 'url' => "https://reddit.com/submit?url=$url&title=$title",
1163 'title' => esc_html__('Reddit', 'king-addons'),
1164 'icon' => 'fab',
1165 ],
1166 'tumblr' => [
1167 'url' => "https://tumblr.com/share/link?url=$url",
1168 'title' => esc_html__('Tumblr', 'king-addons'),
1169 'icon' => 'fab',
1170 ],
1171 'digg' => [
1172 'url' => "https://digg.com/submit?url=$url",
1173 'title' => esc_html__('Digg', 'king-addons'),
1174 'icon' => 'fab',
1175 ],
1176 'xing' => [
1177 'url' => "https://www.xing.com/app/user?op=share&url=$url",
1178 'title' => esc_html__('Xing', 'king-addons'),
1179 'icon' => 'fab',
1180 ],
1181 'vk' => [
1182 'url' => "https://vk.ru/share.php?url=$url&title=$title&description=" . wp_trim_words($text, 250) . "&image=$image/",
1183 'title' => esc_html__('VK', 'king-addons'),
1184 'icon' => 'fab',
1185 ],
1186 'odnoklassniki' => [
1187 'url' => "https://connect.ok.ru/offer?url=$url",
1188 'title' => esc_html__('OK', 'king-addons'),
1189 'icon' => 'fab',
1190 ],
1191 'get-pocket' => [
1192 'url' => "https://getpocket.com/edit?url=$url",
1193 'title' => esc_html__('Pocket', 'king-addons'),
1194 'icon' => 'fab',
1195 ],
1196 'skype' => [
1197 'url' => "https://web.skype.com/share?url=$url",
1198 'title' => esc_html__('Skype', 'king-addons'),
1199 'icon' => 'fab',
1200 ],
1201 'whatsapp' => [
1202 'url' => $get_whatsapp_url($args),
1203 'title' => esc_html__('WhatsApp', 'king-addons'),
1204 'icon' => 'fab',
1205 ],
1206 'telegram' => [
1207 'url' => "https://telegram.me/share/url?url=$url&text=$text",
1208 'title' => esc_html__('Telegram', 'king-addons'),
1209 'icon' => 'fab',
1210 ],
1211 'envelope' => [
1212 'url' => "mailto:?subject=$title&body=$url",
1213 'title' => esc_html__('Email', 'king-addons'),
1214 'icon' => 'fas',
1215 ],
1216 'print' => [
1217 'url' => "javascript:window.print()",
1218 'title' => esc_html__('Print', 'king-addons'),
1219 'icon' => 'fas',
1220 ],
1221 ];
1222
1223 if (!isset($networks_map[$network])) {
1224 return '';
1225 }
1226
1227 $share_url = $networks_map[$network]['url'];
1228 $network_title = $networks_map[$network]['title'];
1229 $icon_category = $networks_map[$network]['icon'];
1230
1231 $output = '<a href="' . esc_url($share_url) . '" class="king-addons-share-icon king-addons-share-' . esc_attr($network) . '" target="_blank">';
1232
1233 if ('yes' === $args['tooltip']) {
1234 $output .= '<span class="king-addons-share-tooltip king-addons-tooltip">' . esc_html($network_title) . '</span>';
1235 }
1236
1237 if ('yes' === $args['icons']) {
1238 $output .= '<i class="' . esc_attr($icon_category) . ' fa-' . esc_attr($network) . '"></i>';
1239 }
1240
1241 if ('yes' === $args['labels']) {
1242 $label = !empty($args['custom_label']) ? $args['custom_label'] : $network_title;
1243 $output .= '<span class="king-addons-share-label">' . esc_html($label) . '</span>';
1244 }
1245
1246 $output .= '</a>';
1247
1248 return $output;
1249 }
1250
1251 public static function validateHTMLTags($setting, $default, $tags_whitelist)
1252 {
1253 $value = $setting;
1254 if (!in_array($value, $tags_whitelist)) {
1255 $value = $default;
1256 }
1257 return $value;
1258 }
1259
1260 public static function getIcon($icon, $dir)
1261 {
1262 if (empty($icon) || strpos($icon, 'fa-') === false) {
1263 return '';
1264 }
1265
1266 $dir = $dir ? "-$dir" : '';
1267 return wp_kses(
1268 '<i class="' . esc_attr($icon . $dir) . '"></i>',
1269 ['i' => ['class' => []]]
1270 );
1271 }
1272
1273 public static function getPluginName()
1274 {
1275 return 'King Addons';
1276 }
1277
1278 public static function getAnimationTimings(): array
1279 {
1280 /** @noinspection DuplicatedCode */
1281 $timings = [
1282 'ease-default' => 'Default',
1283 'linear' => 'Linear',
1284 'ease-in' => 'Ease In',
1285 'ease-out' => 'Ease Out',
1286 'pro-eio' => 'EI Out (Pro)',
1287 'pro-eiqd' => 'EI Quad (Pro)',
1288 'pro-eicb' => 'EI Cubic (Pro)',
1289 'pro-eiqrt' => 'EI Quart (Pro)',
1290 'pro-eiqnt' => 'EI Quint (Pro)',
1291 'pro-eisn' => 'EI Sine (Pro)',
1292 'pro-eiex' => 'EI Expo (Pro)',
1293 'pro-eicr' => 'EI Circ (Pro)',
1294 'pro-eibk' => 'EI Back (Pro)',
1295 'pro-eoqd' => 'EO Quad (Pro)',
1296 'pro-eocb' => 'EO Cubic (Pro)',
1297 'pro-eoqrt' => 'EO Quart (Pro)',
1298 'pro-eoqnt' => 'EO Quint (Pro)',
1299 'pro-eosn' => 'EO Sine (Pro)',
1300 'pro-eoex' => 'EO Expo (Pro)',
1301 'pro-eocr' => 'EO Circ (Pro)',
1302 'pro-eobk' => 'EO Back (Pro)',
1303 'pro-eioqd' => 'EIO Quad (Pro)',
1304 'pro-eiocb' => 'EIO Cubic (Pro)',
1305 'pro-eioqrt' => 'EIO Quart (Pro)',
1306 'pro-eioqnt' => 'EIO Quint (Pro)',
1307 'pro-eiosn' => 'EIO Sine (Pro)',
1308 'pro-eioex' => 'EIO Expo (Pro)',
1309 'pro-eiocr' => 'EIO Circ (Pro)',
1310 'pro-eiobk' => 'EIO Back (Pro)',
1311 ];
1312
1313 if (king_addons_freemius()->can_use_premium_code__premium_only()) {
1314 /** @noinspection DuplicatedCode */
1315 $timings = [
1316 'ease-default' => 'Default',
1317 'linear' => 'Linear',
1318 'ease-in' => 'Ease In',
1319 'ease-out' => 'Ease Out',
1320 'ease-in-out' => 'Ease In Out',
1321 'ease-in-quad' => 'Ease In Quad',
1322 'ease-in-cubic' => 'Ease In Cubic',
1323 'ease-in-quart' => 'Ease In Quart',
1324 'ease-in-quint' => 'Ease In Quint',
1325 'ease-in-sine' => 'Ease In Sine',
1326 'ease-in-expo' => 'Ease In Expo',
1327 'ease-in-circ' => 'Ease In Circ',
1328 'ease-in-back' => 'Ease In Back',
1329 'ease-out-quad' => 'Ease Out Quad',
1330 'ease-out-cubic' => 'Ease Out Cubic',
1331 'ease-out-quart' => 'Ease Out Quart',
1332 'ease-out-quint' => 'Ease Out Quint',
1333 'ease-out-sine' => 'Ease Out Sine',
1334 'ease-out-expo' => 'Ease Out Expo',
1335 'ease-out-circ' => 'Ease Out Circ',
1336 'ease-out-back' => 'Ease Out Back',
1337 'ease-in-out-quad' => 'Ease In Out Quad',
1338 'ease-in-out-cubic' => 'Ease In Out Cubic',
1339 'ease-in-out-quart' => 'Ease In Out Quart',
1340 'ease-in-out-quint' => 'Ease In Out Quint',
1341 'ease-in-out-sine' => 'Ease In Out Sine',
1342 'ease-in-out-expo' => 'Ease In Out Expo',
1343 'ease-in-out-circ' => 'Ease In Out Circ',
1344 'ease-in-out-back' => 'Ease In Out Back',
1345 ];
1346 }
1347
1348 return $timings;
1349 }
1350
1351 public static function getAnimationTimingsConditionsPro()
1352 {
1353 return [
1354 'pro-eibk',
1355 'pro-eicb',
1356 'pro-eicr',
1357 'pro-eiex',
1358 'pro-eio',
1359 'pro-eiobk',
1360 'pro-eiocb',
1361 'pro-eiocr',
1362 'pro-eioex',
1363 'pro-eioqd',
1364 'pro-eioqnt',
1365 'pro-eioqrt',
1366 'pro-eiosn',
1367 'pro-eiqd',
1368 'pro-eiqnt',
1369 'pro-eiqrt',
1370 'pro-eisn',
1371 'pro-eobk',
1372 'pro-eocb',
1373 'pro-eocr',
1374 'pro-eoex',
1375 'pro-eoqd',
1376 'pro-eoqnt',
1377 'pro-eoqrt',
1378 'pro-eosn',
1379 ];
1380 }
1381
1382 public static function isBlogArchive()
1383 {
1384 return (
1385 is_home()
1386 && '0' === get_option('page_on_front')
1387 && '0' === get_option('page_for_posts')
1388 ) || (
1389 intval(get_option('page_for_posts')) === get_queried_object_id()
1390 && !is_404()
1391 );
1392 }
1393
1394 public static function filterOembedResults($html)
1395 {
1396 preg_match('/src="([^"]+)"/', $html, $m);
1397 return $m[1] . '&auto_play=true';
1398 }
1399
1400 public static function getWooCommerceTaxonomies()
1401 {
1402 $filtered = array_filter(get_object_taxonomies('product'), fn($t) => get_taxonomy($t)->show_ui);
1403 return array_combine($filtered, array_map(fn($t) => get_taxonomy($t)->label, $filtered));
1404 }
1405
1406 public static function getCustomMetaKeysTaxonomies()
1407 {
1408 $data = [];
1409 $tax_types = Core::getCustomTypes('tax', false);
1410
1411 foreach ($tax_types as $taxonomy_slug => $post_type_name) {
1412 $meta_keys = [];
1413 foreach (get_terms($taxonomy_slug) as $tax) {
1414 $keys = array_keys(get_term_meta($tax->term_id));
1415 $keys = array_filter($keys, fn($key) => '_' !== $key[0]);
1416 $meta_keys = array_merge($meta_keys, $keys);
1417 }
1418 $data[$taxonomy_slug] = array_unique($meta_keys);
1419 }
1420
1421
1422 $merged = call_user_func_array('array_merge', array_values($data));
1423 $merged_meta_keys = array_values(array_unique($merged));
1424
1425 $options = array_combine($merged_meta_keys, $merged_meta_keys);
1426
1427 return [$data, $options];
1428 }
1429
1430 public static function getMailchimpLists()
1431 {
1432 $api_key = get_option('king_addons_mailchimp_api_key', '');
1433 $mailchimp_list = ['def' => esc_html__('Select List', 'king-addons')];
1434
1435 if (!$api_key) {
1436 return $mailchimp_list;
1437 }
1438
1439 $url = 'https://' . explode('-', $api_key)[1] . '.api.mailchimp.com/3.0/lists/';
1440 $response = wp_remote_get($url, [
1441 'headers' => ['Authorization' => 'Basic ' . base64_encode('user:' . $api_key)]
1442 ]);
1443
1444 $body = json_decode(wp_remote_retrieve_body($response));
1445 if (!empty($body->lists)) {
1446 foreach ($body->lists as $list) {
1447 $mailchimp_list[$list->id] = $list->name . ' (' . $list->stats->member_count . ')';
1448 }
1449 }
1450
1451 return $mailchimp_list;
1452 }
1453
1454 public static function getMailchimpGroups()
1455 {
1456 $apiKey = get_option('king_addons_mailchimp_api_key');
1457 $domain = 'https://' . substr($apiKey, strpos($apiKey, '-') + 1) . '.api.mailchimp.com/3.0/';
1458 $authArgs = ['headers' => ['Authorization' => 'Basic ' . base64_encode('user:' . $apiKey)]];
1459 $groups = ['def' => 'Select Group'];
1460 $mailchimpIDs = Core::getMailchimpLists();
1461
1462 foreach ($mailchimpIDs as $audience => $ignore) {
1463 if ($audience === 'def') {
1464 continue;
1465 }
1466
1467 $cats = wp_remote_get("{$domain}lists/$audience/interest-categories", $authArgs);
1468 $cats = json_decode($cats['body'])->categories ?? [];
1469
1470 foreach ($cats as $cat) {
1471 $interests = wp_remote_get("{$domain}lists/$audience/interest-categories/$cat->id/interests", $authArgs);
1472 $interests = json_decode($interests['body'])->interests ?? [];
1473
1474 foreach ($interests as $int) {
1475 $groups[$int->id] = $int->name;
1476 }
1477 }
1478 }
1479
1480 return $groups;
1481 }
1482
1483 public static function getShopURL($settings)
1484 {
1485 global $wp;
1486 $url = ('' === get_option('permalink_structure'))
1487 ? remove_query_arg(['page', 'paged'], add_query_arg($wp->query_string, '', home_url($wp->request)))
1488 : preg_replace('%/page/[0-9]+%', '', home_url(trailingslashit($wp->request)));
1489 $url = add_query_arg('kingaddonsfilters', '', $url);
1490 $single_params = [
1491 'min_price' => true,
1492 'max_price' => true,
1493 'orderby' => false,
1494 'psearch' => false,
1495 'filter_product_cat' => false,
1496 'filter_product_tag' => false,
1497 'filter_rating' => false,
1498 ];
1499 foreach ($single_params as $param => $needs_clean) {
1500 if (isset($_GET[$param])) {
1501 $value = wp_unslash($_GET[$param]);
1502 $value = $needs_clean ? wc_clean($value) : $value;
1503 $url = add_query_arg($param, $value, $url);
1504 }
1505 }
1506 /** @noinspection DuplicatedCode */
1507 if ($chosen_attrs = WC()->query->get_layered_nav_chosen_attributes()) {
1508 foreach ($chosen_attrs as $name => $data) {
1509 $filter_name = wc_attribute_taxonomy_slug($name);
1510 if (!empty($data['terms'])) {
1511 $url = add_query_arg('filter_' . $filter_name, implode(',', $data['terms']), $url);
1512 }
1513 if (!empty($settings)) {
1514 if ('or' === $settings['tax_query_type'] || isset($_GET['query_type_' . $filter_name])) {
1515 $url = add_query_arg('query_type_' . $filter_name, 'or', $url);
1516 }
1517 }
1518 }
1519 }
1520 return $url;
1521 }
1522
1523 public static function getClientIP()
1524 {
1525 $server_ip_keys = [
1526 'HTTP_CLIENT_IP',
1527 'HTTP_X_FORWARDED_FOR',
1528 'HTTP_X_FORWARDED',
1529 'HTTP_X_CLUSTER_CLIENT_IP',
1530 'HTTP_FORWARDED_FOR',
1531 'HTTP_FORWARDED',
1532 'REMOTE_ADDR',
1533 ];
1534
1535 foreach ($server_ip_keys as $key) {
1536 if (isset($_SERVER[$key])) {
1537 $ip = wp_kses_post_deep(wp_unslash($_SERVER[$key]));
1538 if (filter_var($ip, FILTER_VALIDATE_IP)) {
1539 return $ip;
1540 }
1541 }
1542 }
1543
1544 return '127.0.0.1';
1545 }
1546
1547 public static function getCustomMetaKeys()
1548 {
1549 // Get all custom post types (slug => name).
1550 $post_types = Core::getCustomTypes('post', false);
1551
1552 // Build $data with each post type's unique custom meta keys (excluding keys beginning with "_").
1553 $data = array_combine(
1554 array_keys($post_types),
1555 array_map(function ($slug) {
1556 $keys = [];
1557 foreach (get_posts(['post_type' => $slug, 'posts_per_page' => -1]) as $post) {
1558 // get_post_custom_keys can return null, so cast to array:
1559 foreach ((array) get_post_custom_keys($post->ID) as $meta_key) {
1560 // Exclude protected keys (those beginning with "_").
1561 if ($meta_key[0] !== '_') {
1562 $keys[] = $meta_key;
1563 }
1564 }
1565 }
1566 return array_values(array_unique($keys));
1567 }, array_keys($post_types))
1568 );
1569
1570 // Flatten all meta keys across all post types, remove duplicates, and reindex.
1571 $merged_meta_keys = array_values(array_unique(array_merge([], ...$data)));
1572
1573 // Create an associative array where key == value (for convenient dropdowns, etc.).
1574 $options = array_combine($merged_meta_keys, $merged_meta_keys);
1575
1576 // Return both the per-post-type data and the merged, deduplicated options.
1577 return [$data, $options];
1578 }
1579
1580 public function enqueueLightboxDynamicStyles()
1581 {
1582 wp_register_style('king-addons-lightbox-dynamic-style', false);
1583 wp_enqueue_style('king-addons-lightbox-dynamic-style');
1584
1585 $bg = esc_html(get_option('king_addons_lightbox_bg_color', 'rgba(0,0,0,0.6)'));
1586 $toolbar = esc_html(get_option('king_addons_lightbox_toolbar_color', 'rgba(0,0,0,0.8)'));
1587 $caption = esc_html(get_option('king_addons_lightbox_caption_color', 'rgba(0,0,0,0.8)'));
1588 $gallery = esc_html(get_option('king_addons_lightbox_gallery_color', '#444444'));
1589 $progress_bar = esc_html(get_option('king_addons_lightbox_pb_color', '#8a8a8a'));
1590 $ui_color = esc_html(get_option('king_addons_lightbox_ui_color', '#efefef'));
1591 $icon_size = floatval(get_option('king_addons_lightbox_icon_size', 20));
1592 $icon_size_big = $icon_size + 4;
1593 $ui_hover = esc_html(get_option('king_addons_lightbox_ui_hover_color', '#ffffff'));
1594 $text_color = esc_html(get_option('king_addons_lightbox_text_color', '#efefef'));
1595 $text_size = esc_html(get_option('king_addons_lightbox_text_size', 14));
1596 $arrow_size = esc_html(get_option('king_addons_lightbox_arrow_size', 35));
1597
1598 $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; }";
1599
1600 wp_add_inline_style('king-addons-lightbox-dynamic-style', $custom_css);
1601 }
1602
1603 /**
1604 * Enqueues the AI button injection script in the Elementor editor panel.
1605 *
1606 * @return void
1607 */
1608 public function enqueueAiFieldScript(): void
1609 {
1610 wp_enqueue_script(
1611 'king-addons-ai-field',
1612 KING_ADDONS_URL . 'includes/admin/js/ai-textfield.js',
1613 ['jquery', 'elementor-editor'],
1614 KING_ADDONS_VERSION,
1615 true
1616 );
1617
1618 // Localize for AJAX
1619 wp_localize_script(
1620 'king-addons-ai-field',
1621 'KingAddonsAiField',
1622 [
1623 'ajax_url' => admin_url('admin-ajax.php'),
1624 'generate_nonce' => wp_create_nonce('king_addons_ai_generate_nonce'),
1625 'change_nonce' => wp_create_nonce('king_addons_ai_change_nonce'),
1626 'generate_action' => 'king_addons_ai_generate_text',
1627 'change_action' => 'king_addons_ai_change_text',
1628 'icon_url' => KING_ADDONS_URL . 'includes/admin/img/ai.svg',
1629 'rewrite_icon_url' => KING_ADDONS_URL . 'includes/admin/img/ai-refresh.svg',
1630 'settings_url' => admin_url('admin.php?page=king-addons-ai-settings'),
1631 'plugin_url' => KING_ADDONS_URL,
1632 'is_pro' => king_addons_freemius()->can_use_premium_code__premium_only() ? true : false,
1633 'premium_active' => king_addons_freemius()->can_use_premium_code__premium_only() ? true : false,
1634 'translator_enabled' => isset($ai_options['enable_ai_page_translator']) ? (bool) $ai_options['enable_ai_page_translator'] : true,
1635 ]
1636 );
1637 }
1638
1639 /**
1640 * Enqueues the AI image generation field script in the Elementor editor panel.
1641 *
1642 * @return void
1643 */
1644 public function enqueueAiImageGenerationScript(): void
1645 {
1646 // Retrieve AI options and ensure it's an array to prevent warnings.
1647 $ai_options = get_option('king_addons_ai_options', []);
1648 wp_enqueue_script(
1649 'king-addons-ai-image-field',
1650 KING_ADDONS_URL . 'includes/admin/js/ai-imagefield.js',
1651 ['jquery', 'elementor-editor'],
1652 KING_ADDONS_VERSION,
1653 true
1654 );
1655
1656 // Localize for AJAX
1657 wp_localize_script(
1658 'king-addons-ai-image-field',
1659 'KingAddonsAiImageField',
1660 [
1661 'ajax_url' => admin_url('admin-ajax.php'),
1662 'generate_nonce' => wp_create_nonce('king_addons_ai_generate_image_nonce'),
1663 'generate_action' => 'king_addons_ai_generate_image',
1664 'image_model' => sanitize_text_field($ai_options['openai_image_model'] ?? ''),
1665 'icon_url' => KING_ADDONS_URL . 'includes/admin/img/ai.svg',
1666 'rewrite_icon_url' => KING_ADDONS_URL . 'includes/admin/img/ai-refresh.svg',
1667 'settings_url' => admin_url('admin.php?page=king-addons-ai-settings'),
1668 'plugin_url' => KING_ADDONS_URL,
1669 ]
1670 );
1671 }
1672
1673 /**
1674 * Enqueues the styles for AI prompt UI in the Elementor editor panel.
1675 *
1676 * @return void
1677 */
1678 public function enqueueAiFieldStyles(): void
1679 {
1680 // Enqueue CSS for the AI prompt UI
1681 wp_enqueue_style(
1682 'king-addons-ai-field-css',
1683 KING_ADDONS_URL . 'includes/admin/css/ai-textfield.css',
1684 [],
1685 KING_ADDONS_VERSION
1686 );
1687 }
1688
1689 /**
1690 * Enqueues styles for AI Image Generation UI in the Elementor editor panel.
1691 *
1692 * @return void
1693 */
1694 public function enqueueAiImageFieldStyles(): void
1695 {
1696 wp_enqueue_style(
1697 'king-addons-ai-imagefield',
1698 KING_ADDONS_URL . 'includes/admin/css/ai-imagefield.css',
1699 [],
1700 KING_ADDONS_VERSION
1701 );
1702 }
1703
1704 /**
1705 * Enqueues the AI page translator script in the Elementor editor panel.
1706 *
1707 * @return void
1708 */
1709 public function enqueueAiTranslatorScript(): void
1710 {
1711 // Check if AI Page Translator is enabled in settings
1712 $ai_options = get_option('king_addons_ai_options', []);
1713 $translator_enabled = isset($ai_options['enable_ai_page_translator']) ? (bool) $ai_options['enable_ai_page_translator'] : true;
1714
1715 if (!$translator_enabled) {
1716 return; // Don't load script if translator is disabled
1717 }
1718
1719 wp_enqueue_script(
1720 'king-addons-ai-translator',
1721 KING_ADDONS_URL . 'includes/admin/js/ai-page-translator.js',
1722 ['jquery', 'elementor-editor'],
1723 KING_ADDONS_VERSION,
1724 true
1725 );
1726
1727 // Note: Using existing KingAddonsAiField localization
1728 // The translator script will use the same AJAX endpoints and settings
1729 // No need for separate localization as it reuses existing AI infrastructure
1730 }
1731 }
1732
1733 Core::instance();
1734