PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.78
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.78
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.78, at includes/Core.php

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