PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.79
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.79
51.1.86 51.1.84 51.1.85 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 All 40 releases
king-addons / includes / Core.php

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

2,015 lines 86.7 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
406 /**
407 * The previous notice is kept untouched in showNoticeUpgrade() as a fallback.
408 * To go back to it, either define KING_ADDONS_UPGRADE_NOTICE_LEGACY as true
409 * in wp-config.php, or return true from this filter.
410 */
411 $use_legacy_notice = defined('KING_ADDONS_UPGRADE_NOTICE_LEGACY')
412 ? (bool) KING_ADDONS_UPGRADE_NOTICE_LEGACY
413 : false;
414 $use_legacy_notice = (bool) apply_filters('king_addons/upgrade_notice/use_legacy', $use_legacy_notice);
415
416 add_action(
417 'admin_notices',
418 [$this, $use_legacy_notice ? 'showNoticeUpgrade' : 'showNoticeUpgradeV2']
419 );
420 }
421
422 // Dashboard UI settings AJAX handler
423 add_action('wp_ajax_king_addons_save_dashboard_ui', [$this, 'king_addons_save_dashboard_ui_callback']);
424
425 // Conditionally enqueue AI text-field enhancement script and styles in Elementor editor
426 $ai_options = get_option('king_addons_ai_options', []);
427 $enable_ai_text_buttons = isset($ai_options['enable_ai_buttons']) ? (bool) $ai_options['enable_ai_buttons'] : true;
428 if ($enable_ai_text_buttons) {
429 // Enqueue AI text-field enhancement script
430 add_action('elementor/editor/after_enqueue_scripts', [$this, 'enqueueAiFieldScript']);
431 // Enqueue styles for AI prompt UI
432 add_action('elementor/editor/after_enqueue_styles', [$this, 'enqueueAiFieldStyles']);
433 // Enqueue AI page translator script
434 add_action('elementor/editor/after_enqueue_scripts', [$this, 'enqueueAiTranslatorScript']);
435 }
436
437 $enable_ai_image_generation_button = isset($ai_options['enable_ai_image_generation_button']) ? (bool) $ai_options['enable_ai_image_generation_button'] : true;
438 if ($enable_ai_image_generation_button) {
439 add_action('elementor/editor/after_enqueue_scripts', [$this, 'enqueueAiImageGenerationScript']);
440 // Enqueue styles for AI Image Generation controls
441 add_action('elementor/editor/after_enqueue_styles', [$this, 'enqueueAiImageFieldStyles']);
442 }
443 }
444 }
445
446 function king_addons_premium_notice_dismiss_callback()
447 {
448 // Check user capabilities
449 if (!current_user_can('manage_options')) {
450 wp_die();
451 }
452
453 check_ajax_referer('king_addons_premium_notice_dismiss', 'nonce');
454
455 $user_id = get_current_user_id();
456 // Save the current time as the last dismissal time for the premium notice
457 update_user_meta($user_id, 'king_addons_premium_notice_dismissed_time', time());
458 wp_die(); // End AJAX request
459 }
460
461 /**
462 * AJAX callback for saving dashboard UI settings (theme, view toggle)
463 *
464 * @since 1.0.0
465 */
466 function king_addons_save_dashboard_ui_callback()
467 {
468 // Check user capabilities
469 if (!current_user_can('manage_options')) {
470 wp_send_json_error(['message' => 'Unauthorized'], 403);
471 }
472
473 check_ajax_referer('king_addons_dashboard_ui', 'nonce');
474
475 $key = isset($_POST['key']) ? sanitize_text_field($_POST['key']) : '';
476
477 $user_id = get_current_user_id();
478
479 // Validate key
480 $allowed_keys = ['dark_theme', 'theme_mode', 'show_descriptions'];
481 if (!in_array($key, $allowed_keys, true)) {
482 wp_send_json_error(['message' => 'Invalid key'], 400);
483 }
484
485 // Theme preference is per-user.
486 if ($key === 'theme_mode') {
487 $mode = isset($_POST['value']) ? sanitize_key($_POST['value']) : '';
488 $allowed_modes = ['dark', 'light', 'auto'];
489 if (!in_array($mode, $allowed_modes, true)) {
490 wp_send_json_error(['message' => 'Invalid theme mode'], 400);
491 }
492
493 update_user_meta($user_id, 'king_addons_theme_mode', $mode);
494
495 // Also store as a global option so pages can fall back when user meta isn't set.
496 update_option('king_addons_theme_mode', $mode);
497
498 wp_send_json_success(['key' => $key, 'value' => $mode]);
499 }
500
501 // Backward compatibility: old boolean dark_theme maps to theme_mode.
502 if ($key === 'dark_theme') {
503 $is_dark = isset($_POST['value']) && $_POST['value'] === '1';
504 $mode = $is_dark ? 'dark' : 'light';
505 update_user_meta($user_id, 'king_addons_theme_mode', $mode);
506 wp_send_json_success(['key' => 'theme_mode', 'value' => $mode]);
507 }
508
509 // Remaining UI settings are still stored as site option (shared).
510 $value = isset($_POST['value']) && $_POST['value'] === '1';
511 $settings = get_option('king_addons_dashboard_ui', []);
512 $settings[$key] = $value;
513 update_option('king_addons_dashboard_ui', $settings);
514
515 wp_send_json_success(['key' => $key, 'value' => $value]);
516 }
517
518 function showNoticeUpgrade()
519 {
520 // Check user capabilities; show notice only to administrators as an example
521 if (!current_user_can('manage_options')) {
522 return;
523 }
524
525 $user_id = get_current_user_id();
526 $now = time();
527 // Retrieve the last time the premium notice was dismissed by the user
528 $last_dismissed = get_user_meta($user_id, 'king_addons_premium_notice_dismissed_time', true);
529
530 // If the premium notice was dismissed less than a week ago (604800 seconds), do not show it
531 if ($last_dismissed && ($now - $last_dismissed) < 604800) {
532 // if ($last_dismissed && ($now - $last_dismissed) < 60) {
533 return;
534 }
535 ?>
536 <div class="king-addons-upgrade-notice notice notice-info is-dismissible"
537 style="border-left: 4px solid #0071e3;padding: 10px 15px;">
538 <p style="font-size: 15px; margin:0; display: flex; align-items: center;">
539 <span>
540 Get <strong style="font-weight: 700;">4,000+</strong> premium templates and sections,
541 <strong style="font-weight: 700;">80+</strong> widgets,
542 <strong style="font-weight: 700;">200+</strong> advanced features,
543 and AI tools for Elementor.
544 From $<strong style="font-weight: 700;">4</strong>/mo, billed annually.
545 </span>
546 </p>
547 <p style="font-size: 14px; opacity: 0.6;">Trusted by 20,000+ users</p>
548 <p style="display: flex;">
549 <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="
550 background: #0071e3;
551 color: #fff;
552 display: inline-flex;
553 align-items: center;
554 justify-content: center;
555 gap: 6px;
556 padding: 10px 18px;
557 font-size: 14px;
558 font-weight: 500;
559 text-decoration: none;
560 border-radius: 980px;
561 border: none;
562 cursor: pointer;
563 transition: all 0.3s cubic-bezier(0.25, 1, 0.5, 1);
564 white-space: nowrap;
565 font-family: inherit;
566 ">Upgrade to Pro<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="
567 width: 16px;
568 height: 16px;
569 "><path d="M5 12h14M12 5l7 7-7 7"></path></svg>
570 </a>
571 <a style="margin-left: 20px;display: flex;align-items: center;font-size: 14px;color: #0071e3;"
572 href="https://kingaddons.com/pricing?utm_source=kng-notice-offer&utm_medium=plugin&utm_campaign=kng"
573 class="link">Learn More</a>
574 </p>
575 </div>
576 <script>
577 (function ($) {
578 // Wait for the document to be ready
579 const kingAddonsPremiumNoticeNonce = '<?php echo esc_js(wp_create_nonce('king_addons_premium_notice_dismiss')); ?>';
580 $(document).ready(function () {
581 // Attach click handler to the dismiss button of the premium notice
582 $('.king-addons-upgrade-notice.notice.is-dismissible').on('click', '.notice-dismiss', function () {
583 $.post(ajaxurl, {
584 action: 'king_addons_premium_notice_dismiss',
585 nonce: kingAddonsPremiumNoticeNonce
586 });
587 });
588 });
589 })(jQuery);
590 </script>
591 <?php
592 }
593
594 /**
595 * Upgrade notice, current design.
596 *
597 * Replaces showNoticeUpgrade(), which is kept above unchanged as a fallback —
598 * see the KING_ADDONS_UPGRADE_NOTICE_LEGACY constant and the
599 * king_addons/upgrade_notice/use_legacy filter.
600 *
601 * @return void
602 */
603 function showNoticeUpgradeV2()
604 {
605 if (!current_user_can('manage_options')) {
606 return;
607 }
608
609 $user_id = get_current_user_id();
610 $now = time();
611 $last_dismissed = get_user_meta($user_id, 'king_addons_premium_notice_dismissed_time', true);
612
613 // Stay hidden for a week after the notice is dismissed.
614 if ($last_dismissed && ($now - $last_dismissed) < WEEK_IN_SECONDS) {
615 return;
616 }
617
618 $pricing_url = 'https://kingaddons.com/pricing/?utm_source=kng-notice-offer&utm_medium=plugin&utm_campaign=kng';
619 // Same page, different utm_source: the two links stay separately
620 // measurable, which the previous notice could not do because both
621 // carried kng-notice-offer.
622 $explore_url = 'https://kingaddons.com/pricing/?utm_source=kng-notice-explore&utm_medium=plugin&utm_campaign=kng';
623 ?>
624 <style>
625 .king-addons-upgrade-notice-v2 {
626 border-left: 4px solid #5B03FF;
627 padding: 18px 20px 18px 22px;
628 }
629
630 .king-addons-upgrade-notice-v2 .king-addons-un-main {
631 /* Admin notices run the full width of the screen, so a second
632 column always ends up marooned in empty space. Everything
633 stays in one left-aligned stack instead, with the price sitting
634 in the action row where the decision is made. */
635 display: flex;
636 flex-direction: column;
637 gap: 10px;
638 }
639
640 .king-addons-upgrade-notice-v2 .king-addons-un-brand {
641 display: flex;
642 align-items: center;
643 gap: 7px;
644 font-size: 11px;
645 font-weight: 700;
646 letter-spacing: .09em;
647 text-transform: uppercase;
648 color: #5B03FF;
649 }
650
651 .king-addons-upgrade-notice-v2 .king-addons-un-brand svg {
652 width: 14px;
653 height: 14px;
654 display: block;
655 fill: currentColor;
656 }
657
658 .king-addons-upgrade-notice-v2 .king-addons-un-headline {
659 margin: 0;
660 font-size: 17px;
661 font-weight: 700;
662 line-height: 1.35;
663 color: #1d2327;
664 max-width: 46ch;
665 }
666
667 .king-addons-upgrade-notice-v2 .king-addons-un-sub {
668 margin: 0;
669 font-size: 14px;
670 line-height: 1.5;
671 color: #50575e;
672 max-width: 58ch;
673 }
674
675 .king-addons-upgrade-notice-v2 .king-addons-un-actions {
676 display: flex;
677 align-items: center;
678 gap: 20px;
679 flex-wrap: wrap;
680 margin-top: 2px;
681 }
682
683 .king-addons-upgrade-notice-v2 .king-addons-un-cta {
684 background: #5B03FF;
685 color: #fff;
686 display: inline-flex;
687 align-items: center;
688 gap: 7px;
689 padding: 9px 18px;
690 font-size: 14px;
691 font-weight: 600;
692 text-decoration: none;
693 border-radius: 8px;
694 transition: background-color .15s ease;
695 }
696
697 .king-addons-upgrade-notice-v2 .king-addons-un-cta:hover, .king-addons-upgrade-notice-v2 .king-addons-un-cta:focus {
698 background: #3D01B0;
699 color: #fff;
700 }
701
702 .king-addons-upgrade-notice-v2 .king-addons-un-cta:focus-visible {
703 outline: 2px solid #5B03FF;
704 outline-offset: 2px;
705 }
706
707 .king-addons-upgrade-notice-v2 .king-addons-un-cta svg {
708 width: 15px;
709 height: 15px;
710 display: block;
711 }
712
713 .king-addons-upgrade-notice-v2 .king-addons-un-secondary {
714 font-size: 14px;
715 font-weight: 500;
716 color: #50575e;
717 text-decoration: none;
718 border-bottom: 1px solid #c9c4d8;
719 padding-bottom: 1px;
720 }
721
722 .king-addons-upgrade-notice-v2 .king-addons-un-secondary:hover, .king-addons-upgrade-notice-v2 .king-addons-un-secondary:focus {
723 color: #5B03FF;
724 border-bottom-color: #5B03FF;
725 }
726
727 .king-addons-upgrade-notice-v2 .king-addons-un-trust {
728 margin: 4px 0 0;
729 font-size: 13px;
730 color: #50575e;
731 display: flex;
732 align-items: center;
733 gap: 8px 18px;
734 flex-wrap: wrap;
735 }
736
737 .king-addons-upgrade-notice-v2 .king-addons-un-trust-item {
738 display: inline-flex;
739 align-items: baseline;
740 gap: 6px;
741 }
742
743 .king-addons-upgrade-notice-v2 .king-addons-un-trust strong {
744 color: #1d2327;
745 font-weight: 700;
746 }
747
748 .king-addons-upgrade-notice-v2 .king-addons-un-check {
749 color: #0a6b45;
750 font-weight: 700;
751 font-size: 12px;
752 line-height: 1;
753 }
754
755 @media screen and (max-width: 782px) {
756 .king-addons-upgrade-notice-v2 .king-addons-un-actions {
757 align-items: flex-start;
758 gap: 12px;
759 }
760 }
761 </style>
762 <div class="king-addons-upgrade-notice king-addons-upgrade-notice-v2 notice notice-info is-dismissible">
763 <div class="king-addons-un-main">
764 <div class="king-addons-un-brand">
765 <svg viewBox="0 0 512 512" aria-hidden="true" focusable="false"><path d="M504.981,150.787c-6.048-5.163-14.583-6.251-21.736-2.769l-109.444,53.28L271.109,82.896C267.311,78.516,261.798,76,256,76c-5.798,0-11.31,2.516-15.109,6.896L138.199,201.297l-109.444-53.28c-7.153-3.481-15.687-2.394-21.737,2.769c-6.05,5.163-8.466,13.421-6.153,21.031l76,250C79.426,430.242,87.195,436,96,436h320c8.804,0,16.574-5.758,19.134-14.182l76-250C513.448,164.208,511.032,155.95,504.981,150.787z M401.175,396H110.823L52.472,204.052l82.022,39.931c8.144,3.964,17.93,1.962,23.863-4.878L256,126.525l97.644,112.58c5.932,6.841,15.721,8.841,23.862,4.878l82.022-39.931L401.175,396z"></path></svg>
766 <?php esc_html_e('King Addons Pro', 'king-addons'); ?>
767 </div>
768 <h2 class="king-addons-un-headline">
769 <?php esc_html_e('Mega Menu, Popup Builder, Theme Builder &amp; AI Tools', 'king-addons'); ?>
770 </h2>
771 <p class="king-addons-un-sub">
772 <?php esc_html_e('200+ Pro features, premium widgets and 4,000+ templates &amp; sections for Elementor.', 'king-addons'); ?>
773 </p>
774 <div class="king-addons-un-actions">
775 <a class="king-addons-un-cta" href="<?php echo esc_url($pricing_url); ?>" target="_blank" rel="noopener noreferrer">
776 <?php
777 printf(
778 /* translators: %s: monthly price, for example $6.99/mo */
779 esc_html__('Upgrade to Pro for %s', 'king-addons'),
780 esc_html__('$6.99/mo', 'king-addons')
781 );
782 ?>
783 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" aria-hidden="true" focusable="false"><path d="M5 12h14M12 5l7 7-7 7"></path></svg>
784 </a>
785 <a class="king-addons-un-secondary" href="<?php echo esc_url($explore_url); ?>" target="_blank" rel="noopener noreferrer">
786 <?php esc_html_e('Explore Pro features', 'king-addons'); ?>
787 </a>
788 </div>
789 <p class="king-addons-un-trust">
790 <span class="king-addons-un-trust-item">
791 <span class="king-addons-un-check" aria-hidden="true">&#10003;</span>
792 <?php esc_html_e('30-day money-back guarantee', 'king-addons'); ?>
793 </span>
794 <span class="king-addons-un-trust-item">
795 <span class="king-addons-un-check" aria-hidden="true">&#10003;</span>
796 <?php esc_html_e('Cancel anytime', 'king-addons'); ?>
797 </span>
798 <span class="king-addons-un-trust-item">
799 <span class="king-addons-un-check" aria-hidden="true">&#10003;</span>
800 <?php
801 printf(
802 /* translators: %s: number of users, wrapped in bold */
803 esc_html__('Trusted by %s users', 'king-addons'),
804 '<strong>' . esc_html__('20,000+', 'king-addons') . '</strong>'
805 );
806 ?>
807 </span>
808 </p>
809 </div>
810 </div>
811 <script>
812 (function ($) {
813 const kingAddonsPremiumNoticeNonce = '<?php echo esc_js(wp_create_nonce('king_addons_premium_notice_dismiss')); ?>';
814 $(document).ready(function () {
815 $('.king-addons-upgrade-notice.notice.is-dismissible').on('click', '.notice-dismiss', function () {
816 $.post(ajaxurl, {
817 action: 'king_addons_premium_notice_dismiss',
818 nonce: kingAddonsPremiumNoticeNonce
819 });
820 });
821 });
822 })(jQuery);
823 </script>
824 <?php
825 }
826
827 function enqueueFrontendStyles()
828 {
829 /**
830 * It fixes the default Elementor SVG icon rendering feature (Settings -> Features -> Inline Font Icons)
831 * because sometimes Elementor still renders Font Awesome icons but doesn't load the corresponding Font Awesome styles.
832 * Therefore, we have to enqueue the styles.
833 */
834 wp_enqueue_style(
835 'font-awesome-5-all',
836 ELEMENTOR_ASSETS_URL . 'lib/font-awesome/css/all' . (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG ? '' : '.min') . '.css',
837 false,
838 KING_ADDONS_VERSION
839 );
840 }
841
842 function hasElementorCompatibility(): bool
843 {
844 // Check if Elementor installed and activated
845 if (!did_action('elementor/loaded')) {
846 add_action('admin_notices', [$this, 'showAdminNotice_ElementorRequired']);
847 return false;
848 }
849
850 // Check for required Elementor version
851 if (!version_compare(ELEMENTOR_VERSION, '3.19.0', '>=')) {
852 add_action('admin_notices', [$this, 'showAdminNotice_ElementorMinimumVersion']);
853 return false;
854 }
855
856 return true;
857 }
858
859 function showAdminNotice_ElementorRequired(): void
860 {
861 $screen = get_current_screen();
862 if (isset($screen->parent_file) && 'plugins.php' === $screen->parent_file && 'update' === $screen->id) {
863 return;
864 }
865
866 if (isset(get_plugins()['elementor/elementor.php'])) {
867 if (!current_user_can('activate_plugins') || is_plugin_active('elementor/elementor.php')) {
868 return;
869 }
870 $plugin = 'elementor/elementor.php';
871 $activation_url = wp_nonce_url('plugins.php?action=activate&amp;plugin=' . $plugin . '&amp;plugin_status=all&amp;paged=1&amp;s', 'activate-plugin_' . $plugin);
872 $message = '<div class="error"><p>' . esc_html__('King Addons plugin is not working because you need to activate the Elementor plugin.', 'king-addons') . '</p>';
873 /** @noinspection HtmlUnknownTarget */
874 $message .= '<p>' . sprintf('<a href="%s" class="button-primary">%s</a>', $activation_url, esc_html__('Activate Elementor now', 'king-addons')) . '</p></div>';
875 } else {
876 if (!current_user_can('install_plugins')) {
877 return;
878 }
879 $install_url = wp_nonce_url(self_admin_url('update.php?action=install-plugin&plugin=elementor'), 'install-plugin_elementor');
880 $message = '<div class="error"><p>' . esc_html__('King Addons plugin is not working because you need to install the Elementor plugin.', 'king-addons') . '</p>';
881 /** @noinspection HtmlUnknownTarget */
882 $message .= '<p>' . sprintf('<a href="%s" class="button-primary">%s</a>', $install_url, esc_html__('Install Elementor now', 'king-addons')) . '</p></div>';
883 }
884 echo $message;
885 }
886
887 function showAdminNotice_ElementorMinimumVersion(): void
888 {
889 $message = sprintf(
890 /* translators: 1: Plugin name 2: Elementor 3: Required Elementor version */
891 esc_html__('%1$s plugin requires %2$s plugin version %3$s or greater.', 'king-addons'),
892 esc_html__('King Addons', 'king-addons'),
893 esc_html__('Elementor', 'king-addons'),
894 '3.19.0'
895 );
896 echo '<div class="notice notice-error"><p>' . esc_html($message) . '</p></div>';
897 }
898
899 public function initElementor(): void
900 {
901 add_action('elementor/widgets/register', [$this, 'registerWidgets']);
902 add_action('elementor/editor/after_enqueue_styles', [$this, 'enqueueEditorStyles']);
903 add_action('elementor/editor/after_enqueue_scripts', [$this, 'enqueueEditorScripts']);
904 add_action('elementor/preview/enqueue_styles', [$this, 'enqueueEditorPreviewStyles']);
905 }
906
907 function enqueueEditorPreviewStyles(): void
908 {
909 wp_enqueue_style(
910 KING_ADDONS_ASSETS_UNIQUE_KEY . '-elementor-preview',
911 KING_ADDONS_URL . 'includes/admin/css/elementor-preview.css',
912 [],
913 KING_ADDONS_VERSION
914 );
915 }
916
917 function addWidgetCategory(): void
918 {
919 $elements_manager = Plugin::instance()->elements_manager;
920
921 // Add our categories
922 $elements_manager->add_category(
923 'king-addons',
924 [
925 'title' => esc_html__('King Addons', 'king-addons'),
926 'icon' => 'fa fa-plug'
927 ]
928 );
929
930 $elements_manager->add_category(
931 'king-addons-woo-builder',
932 [
933 'title' => esc_html__('King Addons Woo Builder', 'king-addons'),
934 'icon' => 'fa fa-shopping-cart'
935 ]
936 );
937
938 // Move our categories to the top of the panel
939 $this->reorderWidgetCategories($elements_manager);
940 }
941
942 /**
943 * Reorder widget categories so King Addons categories appear after Layout and Basic.
944 *
945 * @param \Elementor\Elements_Manager $elements_manager
946 * @return void
947 */
948 private function reorderWidgetCategories($elements_manager): void
949 {
950 try {
951 $reflection = new \ReflectionClass($elements_manager);
952 $categories_property = $reflection->getProperty('categories');
953 $categories_property->setAccessible(true);
954
955 $categories = $categories_property->getValue($elements_manager);
956 if (!is_array($categories)) {
957 return;
958 }
959
960 // Extract our categories
961 $our_categories = [];
962 if (isset($categories['king-addons'])) {
963 $our_categories['king-addons'] = $categories['king-addons'];
964 unset($categories['king-addons']);
965 }
966 if (isset($categories['king-addons-woo-builder'])) {
967 $our_categories['king-addons-woo-builder'] = $categories['king-addons-woo-builder'];
968 unset($categories['king-addons-woo-builder']);
969 }
970
971 // Insert our categories after Layout and Basic
972 $reordered = [];
973 $insert_after = ['layout', 'basic']; // Categories after which we insert ours
974 $inserted = false;
975
976 foreach ($categories as $key => $value) {
977 $reordered[$key] = $value;
978
979 // Insert our categories after the last target category
980 if (!$inserted && in_array($key, $insert_after, true)) {
981 // Check if next category is also in our target list
982 $keys = array_keys($categories);
983 $current_index = array_search($key, $keys, true);
984 $next_key = $keys[$current_index + 1] ?? null;
985
986 // Only insert if the next category is NOT in our target list
987 if ($next_key === null || !in_array($next_key, $insert_after, true)) {
988 $reordered = array_merge($reordered, $our_categories);
989 $inserted = true;
990 }
991 }
992 }
993
994 // If target categories weren't found, append at the end
995 if (!$inserted) {
996 $reordered = array_merge($reordered, $our_categories);
997 }
998
999 // Set back the reordered array
1000 $categories_property->setValue($elements_manager, $reordered);
1001 } catch (\ReflectionException $e) {
1002 // Silently fail if reflection doesn't work (e.g., future Elementor changes)
1003 }
1004 }
1005
1006 /**
1007 * Registers Elementor widgets with a mechanism to skip (and remember) broken widgets
1008 * that caused a fatal error previously, and try them again if the plugin version is updated.
1009 *
1010 * @param Widgets_Manager $widgets_manager
1011 * @return void
1012 */
1013 function registerWidgets(Widgets_Manager $widgets_manager): void
1014 {
1015 // Used to track which widget is currently being loaded when a fatal error occurs
1016 static $currentlyLoadingWidgetId = null;
1017
1018 $currentPluginVersion = KING_ADDONS_VERSION;
1019
1020 // Get plugin options to check if a widget is enabled
1021 $options = get_option('king_addons_options');
1022 $options = is_array($options) ? $options : [];
1023
1024 // Extension toggles (used to prevent loading dependent widgets when extension is disabled).
1025 $wishlist_extension_enabled = !isset($options['ext_wishlist']) || $options['ext_wishlist'] === 'enabled';
1026 if (defined('KING_ADDONS_EXT_WISHLIST') && KING_ADDONS_EXT_WISHLIST === false) {
1027 $wishlist_extension_enabled = false;
1028 }
1029
1030 // Ensure Woo Builder base class is available for single product widgets.
1031 $abstract_single_widget = KING_ADDONS_PATH . 'includes/helpers/Woo_Builder/Abstract_Single_Widget.php';
1032 if (file_exists($abstract_single_widget)) {
1033 require_once $abstract_single_widget;
1034 }
1035
1036 // Ensure Woo Builder base class is available for archive widgets.
1037 $abstract_archive_widget = KING_ADDONS_PATH . 'includes/helpers/Woo_Builder/Abstract_Archive_Widget.php';
1038 if (file_exists($abstract_archive_widget)) {
1039 require_once $abstract_archive_widget;
1040 }
1041
1042 // Ensure Woo Builder base class is available for cart widgets.
1043 $abstract_cart_widget = KING_ADDONS_PATH . 'includes/helpers/Woo_Builder/Abstract_Cart_Widget.php';
1044 if (file_exists($abstract_cart_widget)) {
1045 require_once $abstract_cart_widget;
1046 }
1047
1048 // Ensure Woo Builder base class is available for checkout widgets.
1049 $abstract_checkout_widget = KING_ADDONS_PATH . 'includes/helpers/Woo_Builder/Abstract_Checkout_Widget.php';
1050 if (file_exists($abstract_checkout_widget)) {
1051 require_once $abstract_checkout_widget;
1052 }
1053
1054 // Ensure Woo Builder base class is available for My Account widgets.
1055 $abstract_my_account_widget = KING_ADDONS_PATH . 'includes/helpers/Woo_Builder/Abstract_My_Account_Widget.php';
1056 if (file_exists($abstract_my_account_widget)) {
1057 require_once $abstract_my_account_widget;
1058 }
1059
1060 /**
1061 * Retrieve the array of broken widgets from the WordPress options.
1062 * The structure is expected to be something like:
1063 *
1064 * 'widget_id' => [
1065 * 'version' => '1.2.0',
1066 * 'error' => 'Some fatal error message'
1067 * ],
1068 * ...
1069 *
1070 */
1071 $brokenWidgets = get_option('king_addons_broken_widgets', []);
1072
1073 /**
1074 * STEP 1: Clear out any "broken widgets" where the stored version is
1075 * less than the current plugin version. This gives them a second chance
1076 * after an update, assuming the issue may have been fixed.
1077 */
1078 foreach ($brokenWidgets as $brokenId => $brokenData) {
1079 if (
1080 isset($brokenData['version'])
1081 && version_compare($currentPluginVersion, $brokenData['version'], '>')
1082 ) {
1083 // If the plugin version is now higher, we remove the widget from the blacklist
1084 unset($brokenWidgets[$brokenId]);
1085 }
1086 }
1087
1088 // Update the option after cleaning up
1089 update_option('king_addons_broken_widgets', $brokenWidgets);
1090
1091 /**
1092 * STEP 2: Use register_shutdown_function to detect any fatal errors (E_ERROR, E_PARSE, etc.)
1093 * that might occur during the loading of a widget. If an error is detected, store that widget
1094 * in the "broken" list with the current plugin version and the error message.
1095 */
1096 register_shutdown_function(function () use (&$currentlyLoadingWidgetId, $currentPluginVersion) {
1097 $error = error_get_last();
1098 if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
1099 // If a fatal error occurred while loading a specific widget
1100 if (!empty($currentlyLoadingWidgetId)) {
1101 $brokenWidgetsLocal = get_option('king_addons_broken_widgets', []);
1102 $brokenWidgetsLocal[$currentlyLoadingWidgetId] = [
1103 'version' => $currentPluginVersion,
1104 'error' => $error['message'] ?? ''
1105 ];
1106 update_option('king_addons_broken_widgets', $brokenWidgetsLocal);
1107 }
1108 }
1109 });
1110
1111 /**
1112 * STEP 3: Now we iterate through all widgets in our modules map and try to load them.
1113 * If a widget is in the broken list, we skip it to avoid repeated fatal errors.
1114 */
1115 foreach (ModulesMap::getModulesMapArray()['widgets'] as $widget_id => $widget) {
1116 // Hard-disable via constant (used to QA/rollout new widgets).
1117 $widget_constant = 'KING_ADDONS_WGT_' . strtoupper(str_replace('-', '_', (string) $widget_id));
1118 if (defined($widget_constant) && constant($widget_constant) === false) {
1119 continue;
1120 }
1121
1122 // Check if the widget is enabled in the options
1123 if (!isset($options[$widget_id]) || $options[$widget_id] !== 'enabled') {
1124 continue;
1125 }
1126
1127 // Skip Wishlist widgets when Wishlist extension is disabled.
1128 // This prevents fatals when wishlist classes aren't loaded.
1129 if (!$wishlist_extension_enabled && strpos((string) $widget_id, 'wishlist-') === 0) {
1130 continue;
1131 }
1132
1133 // If this widget is listed as broken, skip it
1134 if (array_key_exists($widget_id, $brokenWidgets)) {
1135 // Log something here if needed:
1136 // error_log("Skipping widget {$widget_id}, it previously caused a fatal error.");
1137 continue;
1138 }
1139
1140 // Track which widget we're loading
1141 $currentlyLoadingWidgetId = $widget_id;
1142
1143 // Include the base widget class
1144 $widget_class = $widget['php-class'];
1145 $path_widget_class = "King_Addons\\" . $widget_class;
1146 $widget_file = KING_ADDONS_PATH . 'includes/widgets/' . $widget_class . '/' . $widget_class . '.php';
1147 if (!file_exists($widget_file)) {
1148 // Skip missing widget files to avoid fatal errors if registry is ahead of implementation.
1149 $currentlyLoadingWidgetId = null;
1150 continue;
1151 }
1152
1153 require_once $widget_file;
1154
1155 // Check if we can load the Pro version
1156 if (
1157 function_exists('king_addons_freemius')
1158 && king_addons_freemius()->can_use_premium_code__premium_only()
1159 && defined('KING_ADDONS_PRO_PATH')
1160 ) {
1161 if (!empty($widget['has-pro'])) {
1162 $pro_file_path = KING_ADDONS_PRO_PATH . 'includes/widgets/' . $widget_class . '_Pro/' . $widget_class . '_Pro.php';
1163
1164 if (file_exists($pro_file_path)) {
1165 require_once($pro_file_path);
1166 $path_widget_class_pro = "King_Addons\\" . $widget_class . '_Pro';
1167 $widgets_manager->register(new $path_widget_class_pro);
1168 } else {
1169 // If Pro file doesn't exist, register the base widget
1170 $widgets_manager->register(new $path_widget_class);
1171 }
1172 } else {
1173 // No 'has-pro', register the base widget
1174 $widgets_manager->register(new $path_widget_class);
1175 }
1176 } else {
1177 // No Freemius Pro available, register the base widget
1178 $widgets_manager->register(new $path_widget_class);
1179 }
1180
1181 // Clear the tracking variable after successful load
1182 $currentlyLoadingWidgetId = null;
1183 }
1184 }
1185
1186 function enableWidgetsByDefault(): void
1187 {
1188 $options = get_option('king_addons_options');
1189
1190 foreach (ModulesMap::getModulesMapArray()['widgets'] as $widget_id => $widget) {
1191
1192 // Hard-disable via constant (used to QA/rollout new widgets).
1193 $widget_constant = 'KING_ADDONS_WGT_' . strtoupper(str_replace('-', '_', (string) $widget_id));
1194 if (defined($widget_constant) && constant($widget_constant) === false) {
1195 continue;
1196 }
1197
1198 if (!($options[$widget_id] ?? null)) {
1199 $options[$widget_id] = 'enabled';
1200 update_option('king_addons_options', $options);
1201 }
1202 }
1203 }
1204
1205 /**
1206 * Enable and bootstrap registered features.
1207 *
1208 * Loads free feature classes and, when available and licensed, their Pro counterparts.
1209 *
1210 * @return void
1211 */
1212 public function enableFeatures(): void
1213 {
1214 $options = get_option('king_addons_options');
1215
1216 foreach (ModulesMap::getModulesMapArray()['features'] as $feature_id => $feature) {
1217 // Hard-disable via constant (used to QA/rollout new features).
1218 $feature_constant = 'KING_ADDONS_FEAT_' . strtoupper(str_replace('-', '_', (string) $feature_id));
1219 if (defined($feature_constant) && constant($feature_constant) === false) {
1220 continue;
1221 }
1222
1223 if (!($options[$feature_id] ?? null)) {
1224 $options[$feature_id] = 'enabled';
1225 update_option('king_addons_options', $options);
1226 }
1227
1228 if ($options[$feature_id] !== 'enabled') {
1229 continue;
1230 }
1231
1232 $feature_class = $feature['php-class'];
1233 $path_feature_class = "King_Addons\\" . $feature_class;
1234 $feature_file = KING_ADDONS_PATH . 'includes/features/' . $feature_class . '/' . $feature_class . '.php';
1235
1236 if (file_exists($feature_file)) {
1237 require_once $feature_file;
1238 }
1239
1240 $pro_loaded = false;
1241
1242 if (
1243 !empty($feature['has-pro'])
1244 && function_exists('king_addons_freemius')
1245 && king_addons_freemius()->can_use_premium_code__premium_only()
1246 && defined('KING_ADDONS_PRO_PATH')
1247 ) {
1248 $pro_file_path = KING_ADDONS_PRO_PATH . 'includes/features/' . $feature_class . '_Pro/' . $feature_class . '_Pro.php';
1249
1250 if (file_exists($pro_file_path)) {
1251 require_once $pro_file_path;
1252
1253 $path_feature_class_pro = "King_Addons\\" . $feature_class . '_Pro';
1254 if (class_exists($path_feature_class_pro)) {
1255 new $path_feature_class_pro();
1256 $pro_loaded = true;
1257 }
1258 }
1259 }
1260
1261 if (!$pro_loaded && class_exists($path_feature_class)) {
1262 new $path_feature_class();
1263 }
1264 }
1265 }
1266
1267 public function registerControls(Controls_Manager $controls_manager): void
1268 {
1269 $controls_manager->register(new AJAX_Select2\Ajax_Select2());
1270 $controls_manager->register(new Animations\Animations());
1271 $controls_manager->register(new Animations\Animations_Alternative());
1272 $controls_manager->register(new Button_Animations\Button_Animations());
1273 }
1274
1275 function enqueueEditorStyles(): void
1276 {
1277 wp_enqueue_style(KING_ADDONS_ASSETS_UNIQUE_KEY . '-elementor-editor', KING_ADDONS_URL . 'includes/admin/css/elementor-editor.css', '', KING_ADDONS_VERSION);
1278 }
1279
1280 function enqueueEditorScripts(): void
1281 {
1282 wp_enqueue_script(KING_ADDONS_ASSETS_UNIQUE_KEY . '-elementor-editor', KING_ADDONS_URL . 'includes/admin/js/elementor-editor.js', '', KING_ADDONS_VERSION);
1283
1284 // Localize script with PRO status
1285 wp_localize_script(KING_ADDONS_ASSETS_UNIQUE_KEY . '-elementor-editor', 'kingAddonsEditor', [
1286 'isPro' => king_addons_freemius()->can_use_premium_code__premium_only() ? true : false
1287 ]);
1288
1289 wp_enqueue_script(KING_ADDONS_ASSETS_UNIQUE_KEY . '-data-table-export', KING_ADDONS_URL . 'includes/widgets/Data_Table/preview-handler.js', '', KING_ADDONS_VERSION);
1290
1291 if (KING_ADDONS_WGT_FORM_BUILDER) {
1292 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);
1293 }
1294 }
1295
1296 public static function renderProFeaturesSection($module, $section, $type, $widget_name, $features): void
1297 {
1298 if (king_addons_freemius()->can_use_premium_code__premium_only()) {
1299 return;
1300 }
1301
1302 $module->start_controls_section(
1303 'king_addons_pro_features_section',
1304 [
1305 'label' => KING_ADDONS_ELEMENTOR_ICON_PRO . '<span class="king-addons-pro-features-heading">' . esc_html__('Pro Features', 'king-addons') . '</span>',
1306 'tab' => $section ?: null,
1307 ]
1308 );
1309
1310 $list_html = '<ul>' . implode('', array_map(fn($feature) => "<li>$feature</li>", $features)) . '</ul>';
1311
1312 $module->add_control(
1313 'king_addons_pro_features_list',
1314 [
1315 'type' => $type,
1316 '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>',
1317 'content_classes' => 'king-addons-pro-features-list',
1318 ]
1319 );
1320
1321 $module->end_controls_section();
1322 }
1323
1324 public static function renderUpgradeProNotice($module, $controls_manager, $widget_name, $option, $condition = []): void
1325 {
1326 if (king_addons_freemius()->can_use_premium_code__premium_only()) {
1327 return;
1328 }
1329
1330 $module->add_control(
1331 $option . '_pro_notice_',
1332 [
1333 '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!',
1334 'type' => $controls_manager,
1335 'content_classes' => 'king-addons-pro-notice',
1336 'condition' => [
1337 $option => $condition,
1338 ]
1339 ]
1340 );
1341 }
1342
1343 public static function getCustomTypes($query, $exclude_defaults = true): array
1344 {
1345 $custom_types = $query === 'tax'
1346 ? get_taxonomies(['show_in_nav_menus' => true], 'objects')
1347 : get_post_types(['show_in_nav_menus' => true], 'objects');
1348
1349 return array_filter(
1350 array_map(fn($type) => $type->label, $custom_types),
1351 fn($label, $key) => !$exclude_defaults || !in_array($key, ['post', 'page', 'category', 'post_tag']),
1352 ARRAY_FILTER_USE_BOTH
1353 );
1354 }
1355
1356 public static function getShareIcon($args = []): string
1357 {
1358 $args = wp_parse_args($args, [
1359 'network' => '',
1360 'url' => '',
1361 'title' => '',
1362 'text' => '',
1363 'image' => '',
1364 'show_whatsapp_title' => 'no',
1365 'show_whatsapp_excerpt' => 'no',
1366 'tooltip' => 'no',
1367 'icons' => 'no',
1368 'labels' => 'no',
1369 'custom_label' => '',
1370 ]);
1371
1372 $url = esc_url($args['url']);
1373 $title = wp_strip_all_tags($args['title']);
1374 $text = wp_strip_all_tags($args['text']);
1375 $image = esc_url($args['image']);
1376 $network = $args['network'];
1377
1378 $get_whatsapp_url = function ($a) {
1379 if ('yes' === $a['show_whatsapp_title'] && 'yes' === $a['show_whatsapp_excerpt']) {
1380 return 'https://api.whatsapp.com/send?text=*' . $a['title'] . '*%0a' . $a['text'] . '%0a' . $a['url'];
1381 } elseif ('yes' === $a['show_whatsapp_title']) {
1382 return 'https://api.whatsapp.com/send?text=*' . $a['title'] . '*%0a' . $a['url'];
1383 } elseif ('yes' === $a['show_whatsapp_excerpt']) {
1384 return 'https://api.whatsapp.com/send?text=*' . $a['text'] . '%0a' . $a['url'];
1385 }
1386 return 'https://api.whatsapp.com/send?text=' . $a['url'];
1387 };
1388
1389 $networks_map = [
1390 'facebook-f' => [
1391 'url' => "https://www.facebook.com/sharer.php?u=$url",
1392 'title' => esc_html__('Facebook', 'king-addons'),
1393 'icon' => 'fab',
1394 ],
1395 'x-twitter' => [
1396 'url' => "https://twitter.com/intent/tweet?url=$url",
1397 'title' => esc_html__('X (Twitter)', 'king-addons'),
1398 'icon' => 'fab',
1399 ],
1400 'linkedin-in' => [
1401 'url' => "https://www.linkedin.com/shareArticle?mini=true&url=$url&title=$title&summary=$text&source=$url",
1402 'title' => esc_html__('LinkedIn', 'king-addons'),
1403 'icon' => 'fab',
1404 ],
1405 'pinterest-p' => [
1406 'url' => "https://www.pinterest.com/pin/create/button/?url=$url&media=$image",
1407 'title' => esc_html__('Pinterest', 'king-addons'),
1408 'icon' => 'fab',
1409 ],
1410 'reddit' => [
1411 'url' => "https://reddit.com/submit?url=$url&title=$title",
1412 'title' => esc_html__('Reddit', 'king-addons'),
1413 'icon' => 'fab',
1414 ],
1415 'tumblr' => [
1416 'url' => "https://tumblr.com/share/link?url=$url",
1417 'title' => esc_html__('Tumblr', 'king-addons'),
1418 'icon' => 'fab',
1419 ],
1420 'digg' => [
1421 'url' => "https://digg.com/submit?url=$url",
1422 'title' => esc_html__('Digg', 'king-addons'),
1423 'icon' => 'fab',
1424 ],
1425 'xing' => [
1426 'url' => "https://www.xing.com/app/user?op=share&url=$url",
1427 'title' => esc_html__('Xing', 'king-addons'),
1428 'icon' => 'fab',
1429 ],
1430 'vk' => [
1431 'url' => "https://vk.ru/share.php?url=$url&title=$title&description=" . wp_trim_words($text, 250) . "&image=$image/",
1432 'title' => esc_html__('VK', 'king-addons'),
1433 'icon' => 'fab',
1434 ],
1435 'odnoklassniki' => [
1436 'url' => "https://connect.ok.ru/offer?url=$url",
1437 'title' => esc_html__('OK', 'king-addons'),
1438 'icon' => 'fab',
1439 ],
1440 'get-pocket' => [
1441 'url' => "https://getpocket.com/edit?url=$url",
1442 'title' => esc_html__('Pocket', 'king-addons'),
1443 'icon' => 'fab',
1444 ],
1445 'skype' => [
1446 'url' => "https://web.skype.com/share?url=$url",
1447 'title' => esc_html__('Skype', 'king-addons'),
1448 'icon' => 'fab',
1449 ],
1450 'whatsapp' => [
1451 'url' => $get_whatsapp_url($args),
1452 'title' => esc_html__('WhatsApp', 'king-addons'),
1453 'icon' => 'fab',
1454 ],
1455 'telegram' => [
1456 'url' => "https://telegram.me/share/url?url=$url&text=$text",
1457 'title' => esc_html__('Telegram', 'king-addons'),
1458 'icon' => 'fab',
1459 ],
1460 'envelope' => [
1461 'url' => "mailto:?subject=$title&body=$url",
1462 'title' => esc_html__('Email', 'king-addons'),
1463 'icon' => 'fas',
1464 ],
1465 'print' => [
1466 'url' => "javascript:window.print()",
1467 'title' => esc_html__('Print', 'king-addons'),
1468 'icon' => 'fas',
1469 ],
1470 ];
1471
1472 if (!isset($networks_map[$network])) {
1473 return '';
1474 }
1475
1476 $share_url = $networks_map[$network]['url'];
1477 $network_title = $networks_map[$network]['title'];
1478 $icon_category = $networks_map[$network]['icon'];
1479
1480 $output = '<a href="' . esc_url($share_url) . '" class="king-addons-share-icon king-addons-share-' . esc_attr($network) . '" target="_blank">';
1481
1482 if ('yes' === $args['tooltip']) {
1483 $output .= '<span class="king-addons-share-tooltip king-addons-tooltip">' . esc_html($network_title) . '</span>';
1484 }
1485
1486 if ('yes' === $args['icons']) {
1487 $output .= '<i class="' . esc_attr($icon_category) . ' fa-' . esc_attr($network) . '"></i>';
1488 }
1489
1490 if ('yes' === $args['labels']) {
1491 $label = !empty($args['custom_label']) ? $args['custom_label'] : $network_title;
1492 $output .= '<span class="king-addons-share-label">' . esc_html($label) . '</span>';
1493 }
1494
1495 $output .= '</a>';
1496
1497 return $output;
1498 }
1499
1500 public static function validateHTMLTags($setting, $default, $tags_whitelist)
1501 {
1502 $value = $setting;
1503 if (!in_array($value, $tags_whitelist)) {
1504 $value = $default;
1505 }
1506 return $value;
1507 }
1508
1509 public static function getIcon($icon, $dir)
1510 {
1511 if (empty($icon) || strpos($icon, 'fa-') === false) {
1512 return '';
1513 }
1514
1515 $dir = $dir ? "-$dir" : '';
1516 return wp_kses(
1517 '<i class="' . esc_attr($icon . $dir) . '"></i>',
1518 ['i' => ['class' => []]]
1519 );
1520 }
1521
1522 public static function getPluginName()
1523 {
1524 return 'King Addons';
1525 }
1526
1527 public static function getAnimationTimings(): array
1528 {
1529 /** @noinspection DuplicatedCode */
1530 $timings = [
1531 'ease-default' => 'Default',
1532 'linear' => 'Linear',
1533 'ease-in' => 'Ease In',
1534 'ease-out' => 'Ease Out',
1535 'pro-eio' => 'EI Out (Pro)',
1536 'pro-eiqd' => 'EI Quad (Pro)',
1537 'pro-eicb' => 'EI Cubic (Pro)',
1538 'pro-eiqrt' => 'EI Quart (Pro)',
1539 'pro-eiqnt' => 'EI Quint (Pro)',
1540 'pro-eisn' => 'EI Sine (Pro)',
1541 'pro-eiex' => 'EI Expo (Pro)',
1542 'pro-eicr' => 'EI Circ (Pro)',
1543 'pro-eibk' => 'EI Back (Pro)',
1544 'pro-eoqd' => 'EO Quad (Pro)',
1545 'pro-eocb' => 'EO Cubic (Pro)',
1546 'pro-eoqrt' => 'EO Quart (Pro)',
1547 'pro-eoqnt' => 'EO Quint (Pro)',
1548 'pro-eosn' => 'EO Sine (Pro)',
1549 'pro-eoex' => 'EO Expo (Pro)',
1550 'pro-eocr' => 'EO Circ (Pro)',
1551 'pro-eobk' => 'EO Back (Pro)',
1552 'pro-eioqd' => 'EIO Quad (Pro)',
1553 'pro-eiocb' => 'EIO Cubic (Pro)',
1554 'pro-eioqrt' => 'EIO Quart (Pro)',
1555 'pro-eioqnt' => 'EIO Quint (Pro)',
1556 'pro-eiosn' => 'EIO Sine (Pro)',
1557 'pro-eioex' => 'EIO Expo (Pro)',
1558 'pro-eiocr' => 'EIO Circ (Pro)',
1559 'pro-eiobk' => 'EIO Back (Pro)',
1560 ];
1561
1562 if (king_addons_freemius()->can_use_premium_code__premium_only()) {
1563 /** @noinspection DuplicatedCode */
1564 $timings = [
1565 'ease-default' => 'Default',
1566 'linear' => 'Linear',
1567 'ease-in' => 'Ease In',
1568 'ease-out' => 'Ease Out',
1569 'ease-in-out' => 'Ease In Out',
1570 'ease-in-quad' => 'Ease In Quad',
1571 'ease-in-cubic' => 'Ease In Cubic',
1572 'ease-in-quart' => 'Ease In Quart',
1573 'ease-in-quint' => 'Ease In Quint',
1574 'ease-in-sine' => 'Ease In Sine',
1575 'ease-in-expo' => 'Ease In Expo',
1576 'ease-in-circ' => 'Ease In Circ',
1577 'ease-in-back' => 'Ease In Back',
1578 'ease-out-quad' => 'Ease Out Quad',
1579 'ease-out-cubic' => 'Ease Out Cubic',
1580 'ease-out-quart' => 'Ease Out Quart',
1581 'ease-out-quint' => 'Ease Out Quint',
1582 'ease-out-sine' => 'Ease Out Sine',
1583 'ease-out-expo' => 'Ease Out Expo',
1584 'ease-out-circ' => 'Ease Out Circ',
1585 'ease-out-back' => 'Ease Out Back',
1586 'ease-in-out-quad' => 'Ease In Out Quad',
1587 'ease-in-out-cubic' => 'Ease In Out Cubic',
1588 'ease-in-out-quart' => 'Ease In Out Quart',
1589 'ease-in-out-quint' => 'Ease In Out Quint',
1590 'ease-in-out-sine' => 'Ease In Out Sine',
1591 'ease-in-out-expo' => 'Ease In Out Expo',
1592 'ease-in-out-circ' => 'Ease In Out Circ',
1593 'ease-in-out-back' => 'Ease In Out Back',
1594 ];
1595 }
1596
1597 return $timings;
1598 }
1599
1600 public static function getAnimationTimingsConditionsPro()
1601 {
1602 return [
1603 'pro-eibk',
1604 'pro-eicb',
1605 'pro-eicr',
1606 'pro-eiex',
1607 'pro-eio',
1608 'pro-eiobk',
1609 'pro-eiocb',
1610 'pro-eiocr',
1611 'pro-eioex',
1612 'pro-eioqd',
1613 'pro-eioqnt',
1614 'pro-eioqrt',
1615 'pro-eiosn',
1616 'pro-eiqd',
1617 'pro-eiqnt',
1618 'pro-eiqrt',
1619 'pro-eisn',
1620 'pro-eobk',
1621 'pro-eocb',
1622 'pro-eocr',
1623 'pro-eoex',
1624 'pro-eoqd',
1625 'pro-eoqnt',
1626 'pro-eoqrt',
1627 'pro-eosn',
1628 ];
1629 }
1630
1631 public static function isBlogArchive()
1632 {
1633 return (
1634 is_home()
1635 && '0' === get_option('page_on_front')
1636 && '0' === get_option('page_for_posts')
1637 ) || (
1638 intval(get_option('page_for_posts')) === get_queried_object_id()
1639 && !is_404()
1640 );
1641 }
1642
1643 public static function filterOembedResults($html)
1644 {
1645 preg_match('/src="([^"]+)"/', $html, $m);
1646 return $m[1] . '&auto_play=true';
1647 }
1648
1649 public static function getWooCommerceTaxonomies()
1650 {
1651 $filtered = array_filter(get_object_taxonomies('product'), fn($t) => get_taxonomy($t)->show_ui);
1652 return array_combine($filtered, array_map(fn($t) => get_taxonomy($t)->label, $filtered));
1653 }
1654
1655 public static function getCustomMetaKeysTaxonomies()
1656 {
1657 $data = [];
1658 $tax_types = Core::getCustomTypes('tax', false);
1659
1660 foreach ($tax_types as $taxonomy_slug => $post_type_name) {
1661 $meta_keys = [];
1662 foreach (get_terms($taxonomy_slug) as $tax) {
1663 $keys = array_keys(get_term_meta($tax->term_id));
1664 $keys = array_filter($keys, fn($key) => '_' !== $key[0]);
1665 $meta_keys = array_merge($meta_keys, $keys);
1666 }
1667 $data[$taxonomy_slug] = array_unique($meta_keys);
1668 }
1669
1670
1671 $merged = call_user_func_array('array_merge', array_values($data));
1672 $merged_meta_keys = array_values(array_unique($merged));
1673
1674 $options = array_combine($merged_meta_keys, $merged_meta_keys);
1675
1676 return [$data, $options];
1677 }
1678
1679 public static function getMailchimpLists()
1680 {
1681 $api_key = get_option('king_addons_mailchimp_api_key', '');
1682 $mailchimp_list = ['def' => esc_html__('Select List', 'king-addons')];
1683
1684 if (!$api_key) {
1685 return $mailchimp_list;
1686 }
1687
1688 $url = 'https://' . explode('-', $api_key)[1] . '.api.mailchimp.com/3.0/lists/';
1689 $response = wp_remote_get($url, [
1690 'headers' => ['Authorization' => 'Basic ' . base64_encode('user:' . $api_key)]
1691 ]);
1692
1693 $body = json_decode(wp_remote_retrieve_body($response));
1694 if (!empty($body->lists)) {
1695 foreach ($body->lists as $list) {
1696 $mailchimp_list[$list->id] = $list->name . ' (' . $list->stats->member_count . ')';
1697 }
1698 }
1699
1700 return $mailchimp_list;
1701 }
1702
1703 public static function getMailchimpGroups()
1704 {
1705 $apiKey = get_option('king_addons_mailchimp_api_key');
1706 $domain = 'https://' . substr($apiKey, strpos($apiKey, '-') + 1) . '.api.mailchimp.com/3.0/';
1707 $authArgs = ['headers' => ['Authorization' => 'Basic ' . base64_encode('user:' . $apiKey)]];
1708 $groups = ['def' => 'Select Group'];
1709 $mailchimpIDs = Core::getMailchimpLists();
1710
1711 foreach ($mailchimpIDs as $audience => $ignore) {
1712 if ($audience === 'def') {
1713 continue;
1714 }
1715
1716 $cats = wp_remote_get("{$domain}lists/$audience/interest-categories", $authArgs);
1717 $cats = json_decode($cats['body'])->categories ?? [];
1718
1719 foreach ($cats as $cat) {
1720 $interests = wp_remote_get("{$domain}lists/$audience/interest-categories/$cat->id/interests", $authArgs);
1721 $interests = json_decode($interests['body'])->interests ?? [];
1722
1723 foreach ($interests as $int) {
1724 $groups[$int->id] = $int->name;
1725 }
1726 }
1727 }
1728
1729 return $groups;
1730 }
1731
1732 public static function getShopURL($settings)
1733 {
1734 global $wp;
1735 $url = ('' === get_option('permalink_structure'))
1736 ? remove_query_arg(['page', 'paged'], add_query_arg($wp->query_string, '', home_url($wp->request)))
1737 : preg_replace('%/page/[0-9]+%', '', home_url(trailingslashit($wp->request)));
1738 $url = add_query_arg('kingaddonsfilters', '', $url);
1739 $single_params = [
1740 'min_price' => true,
1741 'max_price' => true,
1742 'orderby' => false,
1743 'psearch' => false,
1744 'filter_product_cat' => false,
1745 'filter_product_tag' => false,
1746 'filter_rating' => false,
1747 ];
1748 foreach ($single_params as $param => $needs_clean) {
1749 if (isset($_GET[$param])) {
1750 $value = wp_unslash($_GET[$param]);
1751 $value = $needs_clean ? wc_clean($value) : $value;
1752 $url = add_query_arg($param, $value, $url);
1753 }
1754 }
1755 /** @noinspection DuplicatedCode */
1756 if ($chosen_attrs = WC()->query->get_layered_nav_chosen_attributes()) {
1757 foreach ($chosen_attrs as $name => $data) {
1758 $filter_name = wc_attribute_taxonomy_slug($name);
1759 if (!empty($data['terms'])) {
1760 $url = add_query_arg('filter_' . $filter_name, implode(',', $data['terms']), $url);
1761 }
1762 if (!empty($settings)) {
1763 if ('or' === $settings['tax_query_type'] || isset($_GET['query_type_' . $filter_name])) {
1764 $url = add_query_arg('query_type_' . $filter_name, 'or', $url);
1765 }
1766 }
1767 }
1768 }
1769 return $url;
1770 }
1771
1772 public static function getClientIP()
1773 {
1774 $server_ip_keys = [
1775 'HTTP_CLIENT_IP',
1776 'HTTP_X_FORWARDED_FOR',
1777 'HTTP_X_FORWARDED',
1778 'HTTP_X_CLUSTER_CLIENT_IP',
1779 'HTTP_FORWARDED_FOR',
1780 'HTTP_FORWARDED',
1781 'REMOTE_ADDR',
1782 ];
1783
1784 foreach ($server_ip_keys as $key) {
1785 if (isset($_SERVER[$key])) {
1786 $ip = wp_kses_post_deep(wp_unslash($_SERVER[$key]));
1787 if (filter_var($ip, FILTER_VALIDATE_IP)) {
1788 return $ip;
1789 }
1790 }
1791 }
1792
1793 return '127.0.0.1';
1794 }
1795
1796 public static function getCustomMetaKeys()
1797 {
1798 // Get all custom post types (slug => name).
1799 $post_types = Core::getCustomTypes('post', false);
1800
1801 // Build $data with each post type's unique custom meta keys (excluding keys beginning with "_").
1802 $data = array_combine(
1803 array_keys($post_types),
1804 array_map(function ($slug) {
1805 $keys = [];
1806 foreach (get_posts(['post_type' => $slug, 'posts_per_page' => -1]) as $post) {
1807 // get_post_custom_keys can return null, so cast to array:
1808 foreach ((array) get_post_custom_keys($post->ID) as $meta_key) {
1809 // Exclude protected keys (those beginning with "_").
1810 if ($meta_key[0] !== '_') {
1811 $keys[] = $meta_key;
1812 }
1813 }
1814 }
1815 return array_values(array_unique($keys));
1816 }, array_keys($post_types))
1817 );
1818
1819 // Flatten all meta keys across all post types, remove duplicates, and reindex.
1820 $merged_meta_keys = array_values(array_unique(array_merge([], ...$data)));
1821
1822 // Create an associative array where key == value (for convenient dropdowns, etc.).
1823 $options = array_combine($merged_meta_keys, $merged_meta_keys);
1824
1825 // Return both the per-post-type data and the merged, deduplicated options.
1826 return [$data, $options];
1827 }
1828
1829 public function enqueueLightboxDynamicStyles()
1830 {
1831 wp_register_style('king-addons-lightbox-dynamic-style', false);
1832 wp_enqueue_style('king-addons-lightbox-dynamic-style');
1833
1834 $bg = esc_html(get_option('king_addons_lightbox_bg_color', 'rgba(0,0,0,0.6)'));
1835 $toolbar = esc_html(get_option('king_addons_lightbox_toolbar_color', 'rgba(0,0,0,0.8)'));
1836 $caption = esc_html(get_option('king_addons_lightbox_caption_color', 'rgba(0,0,0,0.8)'));
1837 $gallery = esc_html(get_option('king_addons_lightbox_gallery_color', '#444444'));
1838 $progress_bar = esc_html(get_option('king_addons_lightbox_pb_color', '#8a8a8a'));
1839 $ui_color = esc_html(get_option('king_addons_lightbox_ui_color', '#efefef'));
1840 $icon_size = floatval(get_option('king_addons_lightbox_icon_size', 20));
1841 $icon_size_big = $icon_size + 4;
1842 $ui_hover = esc_html(get_option('king_addons_lightbox_ui_hover_color', '#ffffff'));
1843 $text_color = esc_html(get_option('king_addons_lightbox_text_color', '#efefef'));
1844 $text_size = esc_html(get_option('king_addons_lightbox_text_size', 14));
1845 $arrow_size = esc_html(get_option('king_addons_lightbox_arrow_size', 35));
1846
1847 $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; }";
1848
1849 wp_add_inline_style('king-addons-lightbox-dynamic-style', $custom_css);
1850 }
1851
1852 /**
1853 * Enqueues the AI button injection script in the Elementor editor panel.
1854 *
1855 * @return void
1856 */
1857 public function enqueueAiFieldScript(): void
1858 {
1859 $ai_options = get_option('king_addons_ai_options', []);
1860
1861 wp_enqueue_script(
1862 'king-addons-ai-field',
1863 KING_ADDONS_URL . 'includes/admin/js/ai-textfield.js',
1864 ['jquery', 'elementor-editor'],
1865 KING_ADDONS_VERSION,
1866 true
1867 );
1868
1869 // Localize for AJAX
1870 wp_localize_script(
1871 'king-addons-ai-field',
1872 'KingAddonsAiField',
1873 [
1874 'ajax_url' => admin_url('admin-ajax.php'),
1875 'generate_nonce' => wp_create_nonce('king_addons_ai_generate_nonce'),
1876 'change_nonce' => wp_create_nonce('king_addons_ai_change_nonce'),
1877 'generate_action' => 'king_addons_ai_generate_text',
1878 'change_action' => 'king_addons_ai_change_text',
1879 'icon_url' => KING_ADDONS_URL . 'includes/admin/img/ai.svg',
1880 'rewrite_icon_url' => KING_ADDONS_URL . 'includes/admin/img/ai-refresh.svg',
1881 'settings_url' => admin_url('admin.php?page=king-addons-ai-settings'),
1882 'plugin_url' => KING_ADDONS_URL,
1883 'is_pro' => king_addons_freemius()->can_use_premium_code__premium_only() ? true : false,
1884 'premium_active' => king_addons_freemius()->can_use_premium_code__premium_only() ? true : false,
1885 'translator_enabled' => isset($ai_options['enable_ai_page_translator']) ? (bool) $ai_options['enable_ai_page_translator'] : true,
1886 // Editor prompts name the configured provider rather than always OpenAI.
1887 'provider' => \King_Addons\AI_Provider::getProvider(),
1888 'provider_label' => \King_Addons\AI_Provider::getLabel(),
1889 'api_keys_url' => \King_Addons\AI_Provider::getApiKeysUrl(),
1890 'api_keys_label' => \King_Addons\AI_Provider::isOpenRouter()
1891 ? esc_html__('OpenRouter Keys', 'king-addons')
1892 : esc_html__('OpenAI Platform', 'king-addons'),
1893 'setup_billing_note' => \King_Addons\AI_Provider::isOpenRouter()
1894 ? esc_html__('Free models work without adding any credit.', 'king-addons')
1895 : esc_html__('and top up your OpenAI account balance by at least $5', 'king-addons'),
1896 'setup_cost_note' => \King_Addons\AI_Provider::isOpenRouter()
1897 ? esc_html__('Free models cost nothing. Paid models cost pennies (about $0.01 per full page).', 'king-addons')
1898 : esc_html__('Processing a page costs pennies (about $0.01 per full page).', 'king-addons'),
1899 'missing_key_message' => sprintf(
1900 /* translators: %s: provider name */
1901 esc_html__('%s API key is missing or invalid. Please configure your API key in AI Settings.', 'king-addons'),
1902 \King_Addons\AI_Provider::getLabel()
1903 ),
1904 ]
1905 );
1906 }
1907
1908 /**
1909 * Enqueues the AI image generation field script in the Elementor editor panel.
1910 *
1911 * @return void
1912 */
1913 public function enqueueAiImageGenerationScript(): void
1914 {
1915 wp_enqueue_script(
1916 'king-addons-ai-image-field',
1917 KING_ADDONS_URL . 'includes/admin/js/ai-imagefield.js',
1918 ['jquery', 'elementor-editor'],
1919 KING_ADDONS_VERSION,
1920 true
1921 );
1922
1923 // Localize for AJAX
1924 wp_localize_script(
1925 'king-addons-ai-image-field',
1926 'KingAddonsAiImageField',
1927 [
1928 'ajax_url' => admin_url('admin-ajax.php'),
1929 'generate_nonce' => wp_create_nonce('king_addons_ai_generate_image_nonce'),
1930 'generate_action' => 'king_addons_ai_generate_image',
1931 'image_model' => \King_Addons\AI_Provider::getImageModel(),
1932 // The editor builds its model dropdown from this list, so the
1933 // options follow whichever provider is configured.
1934 'image_models' => array_map(
1935 static function ($model) {
1936 return ['value' => $model['id'], 'label' => $model['label']];
1937 },
1938 \King_Addons\AI_Provider::getModelsFor('image')
1939 ),
1940 'provider' => \King_Addons\AI_Provider::getProvider(),
1941 'icon_url' => KING_ADDONS_URL . 'includes/admin/img/ai.svg',
1942 'rewrite_icon_url' => KING_ADDONS_URL . 'includes/admin/img/ai-refresh.svg',
1943 'settings_url' => admin_url('admin.php?page=king-addons-ai-settings'),
1944 'plugin_url' => KING_ADDONS_URL,
1945 'missing_key_message' => sprintf(
1946 /* translators: %s: provider name */
1947 esc_html__('%s API key is missing or invalid. Please configure your API key in AI Settings.', 'king-addons'),
1948 \King_Addons\AI_Provider::getLabel()
1949 ),
1950 ]
1951 );
1952 }
1953
1954 /**
1955 * Enqueues the styles for AI prompt UI in the Elementor editor panel.
1956 *
1957 * @return void
1958 */
1959 public function enqueueAiFieldStyles(): void
1960 {
1961 // Enqueue CSS for the AI prompt UI
1962 wp_enqueue_style(
1963 'king-addons-ai-field-css',
1964 KING_ADDONS_URL . 'includes/admin/css/ai-textfield.css',
1965 [],
1966 KING_ADDONS_VERSION
1967 );
1968 }
1969
1970 /**
1971 * Enqueues styles for AI Image Generation UI in the Elementor editor panel.
1972 *
1973 * @return void
1974 */
1975 public function enqueueAiImageFieldStyles(): void
1976 {
1977 wp_enqueue_style(
1978 'king-addons-ai-imagefield',
1979 KING_ADDONS_URL . 'includes/admin/css/ai-imagefield.css',
1980 [],
1981 KING_ADDONS_VERSION
1982 );
1983 }
1984
1985 /**
1986 * Enqueues the AI page translator script in the Elementor editor panel.
1987 *
1988 * @return void
1989 */
1990 public function enqueueAiTranslatorScript(): void
1991 {
1992 // Check if AI Page Translator is enabled in settings
1993 $ai_options = get_option('king_addons_ai_options', []);
1994 $translator_enabled = isset($ai_options['enable_ai_page_translator']) ? (bool) $ai_options['enable_ai_page_translator'] : true;
1995
1996 if (!$translator_enabled) {
1997 return; // Don't load script if translator is disabled
1998 }
1999
2000 wp_enqueue_script(
2001 'king-addons-ai-translator',
2002 KING_ADDONS_URL . 'includes/admin/js/ai-page-translator.js',
2003 ['jquery', 'elementor-editor'],
2004 KING_ADDONS_VERSION,
2005 true
2006 );
2007
2008 // Note: Using existing KingAddonsAiField localization
2009 // The translator script will use the same AJAX endpoints and settings
2010 // No need for separate localization as it reuses existing AI infrastructure
2011 }
2012 }
2013
2014 Core::instance();
2015