PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.76
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.76
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 51.1.51 All 36 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.76, at includes/Core.php

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