PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.86
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.86
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 / extensions / Sticky_Contact_Bar / Sticky_Contact_Bar.php

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

1,639 lines 82.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Sticky Contact Bar feature.
4 *
5 * @package King_Addons
6 */
7
8 namespace King_Addons;
9
10 if (!defined('ABSPATH')) {
11 exit;
12 }
13
14 /**
15 * Renders a global sticky contact bar with quick actions.
16 */
17 class Sticky_Contact_Bar
18 {
19 /**
20 * Option key for storing settings.
21 */
22 public const OPTION_KEY = 'king_addons_sticky_contact_bar_settings';
23
24 /**
25 * Constructor.
26 */
27 public function __construct()
28 {
29 add_action('wp_enqueue_scripts', [$this, 'register_assets']);
30 add_action('wp_footer', [$this, 'render_bar']);
31
32 add_action('admin_init', [$this, 'register_settings']);
33 // Use priority 15 to ensure the parent menu exists before adding this submenu
34 add_action('admin_menu', [$this, 'register_settings_page'], 15);
35 add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']);
36 }
37
38 /**
39 * Enqueue admin styles on settings page.
40 *
41 * All admin styles are now inline in render_settings_page() for the modern design.
42 *
43 * @param string $hook Current admin page hook.
44 *
45 * @return void
46 */
47 public function enqueue_admin_assets(string $hook): void
48 {
49 // Styles are now inline in render_settings_page()
50 // Keeping method for future extensibility
51 }
52
53 /**
54 * Register frontend assets.
55 *
56 * @return void
57 */
58 public function register_assets(): void
59 {
60 $style_handle = KING_ADDONS_ASSETS_UNIQUE_KEY . '-sticky-contact-bar';
61 $script_handle = KING_ADDONS_ASSETS_UNIQUE_KEY . '-sticky-contact-bar';
62
63 wp_register_style(
64 $style_handle,
65 KING_ADDONS_URL . 'includes/extensions/Sticky_Contact_Bar/style.css',
66 [],
67 KING_ADDONS_VERSION
68 );
69
70 wp_register_script(
71 $script_handle,
72 KING_ADDONS_URL . 'includes/extensions/Sticky_Contact_Bar/script.js',
73 ['jquery'],
74 KING_ADDONS_VERSION,
75 true
76 );
77 }
78
79 /**
80 * Register settings.
81 *
82 * @return void
83 */
84 public function register_settings(): void
85 {
86 register_setting(
87 'king_addons_sticky_contact_bar',
88 self::OPTION_KEY,
89 [
90 'type' => 'array',
91 'sanitize_callback' => [$this, 'sanitize_settings'],
92 'default' => $this->get_settings_defaults(),
93 ]
94 );
95 }
96
97 /**
98 * Register settings page in King Addons menu.
99 *
100 * @return void
101 */
102 public function register_settings_page(): void
103 {
104 add_submenu_page(
105 'king-addons',
106 esc_html__('Sticky Contact Bar', 'king-addons'),
107 esc_html__('Sticky Contact Bar', 'king-addons'),
108 'manage_options',
109 'king-addons-sticky-contact-bar',
110 [$this, 'render_settings_page']
111 );
112 }
113
114 /**
115 * Render admin settings page.
116 *
117 * @return void
118 */
119 public function render_settings_page(): void
120 {
121 if (!current_user_can('manage_options')) {
122 return;
123 }
124
125 $is_pro = $this->can_use_pro();
126 $settings = $this->get_settings();
127
128 // Theme mode is per-user
129 $theme_mode = get_user_meta(get_current_user_id(), 'king_addons_theme_mode', true);
130 $allowed_theme_modes = ['dark', 'light', 'auto'];
131 if (!in_array($theme_mode, $allowed_theme_modes, true)) {
132 $theme_mode = 'auto';
133 }
134
135 // Enqueue shared V3 styles
136 wp_enqueue_style(
137 'king-addons-admin-v3',
138 KING_ADDONS_URL . 'includes/admin/layouts/shared/admin-v3-styles.css',
139 [],
140 KING_ADDONS_VERSION
141 );
142
143 ?>
144 <script>
145 (function() {
146 const mode = '<?php echo esc_js($theme_mode); ?>';
147 const mql = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
148 const isDark = mode === 'auto' ? !!(mql && mql.matches) : mode === 'dark';
149 document.documentElement.classList.toggle('ka-v3-dark', isDark);
150 document.body.classList.add('ka-admin-v3');
151 document.body.classList.toggle('ka-v3-dark', isDark);
152 })();
153 </script>
154 <style>
155 /* Sticky Contact Bar V3 Additional Styles */
156 .ka-scb-v3 .ka-grid-2 {
157 display: grid;
158 grid-template-columns: 1fr 1fr;
159 gap: 0 30px;
160 }
161
162 /* Button Items */
163 .ka-scb-v3 .ka-scb-item {
164 background: rgba(0, 0, 0, 0.02);
165 border: 1px solid rgba(0, 0, 0, 0.04);
166 border-radius: 16px;
167 padding: 20px 24px;
168 margin-bottom: 16px;
169 transition: all 0.3s;
170 }
171 body.ka-v3-dark .ka-scb-v3 .ka-scb-item {
172 background: rgba(255, 255, 255, 0.04);
173 border-color: rgba(255, 255, 255, 0.06);
174 }
175 .ka-scb-v3 .ka-scb-item:hover {
176 border-color: #22c55e;
177 }
178 body.ka-v3-dark .ka-scb-v3 .ka-scb-item:hover {
179 border-color: #4ade80;
180 }
181 .ka-scb-v3 .ka-scb-item-header {
182 display: flex;
183 align-items: center;
184 justify-content: space-between;
185 margin-bottom: 16px;
186 padding-bottom: 16px;
187 border-bottom: 1px solid rgba(0, 0, 0, 0.04);
188 }
189 body.ka-v3-dark .ka-scb-v3 .ka-scb-item-header {
190 border-bottom-color: rgba(255, 255, 255, 0.04);
191 }
192 .ka-scb-v3 .ka-scb-item-title {
193 font-weight: 600;
194 color: #1d1d1f;
195 font-size: 15px;
196 }
197 body.ka-v3-dark .ka-scb-v3 .ka-scb-item-title {
198 color: #f5f5f7;
199 }
200 .ka-scb-v3 .ka-scb-item-grid {
201 display: grid;
202 grid-template-columns: 1fr 1fr;
203 gap: 16px 24px;
204 }
205 .ka-scb-v3 .ka-scb-item-row {
206 display: flex;
207 flex-direction: column;
208 gap: 6px;
209 }
210 .ka-scb-v3 .ka-scb-item-label {
211 font-size: 13px;
212 font-weight: 500;
213 color: #86868b;
214 }
215 .ka-scb-v3 .ka-scb-item-row input,
216 .ka-scb-v3 .ka-scb-item-row select {
217 padding: 10px 14px;
218 border: 1px solid rgba(0, 0, 0, 0.1);
219 border-radius: 10px;
220 font-size: 14px;
221 background: #fff;
222 }
223 body.ka-v3-dark .ka-scb-v3 .ka-scb-item-row input,
224 body.ka-v3-dark .ka-scb-v3 .ka-scb-item-row select {
225 background: #2c2c2e;
226 border-color: rgba(255, 255, 255, 0.1);
227 color: #f5f5f7;
228 }
229 .ka-scb-v3 .ka-scb-remove-btn {
230 background: rgba(239, 68, 68, 0.1);
231 color: #ef4444;
232 border: none;
233 border-radius: 980px;
234 padding: 8px 16px;
235 font-size: 13px;
236 font-weight: 400;
237 cursor: pointer;
238 transition: all 0.2s;
239 }
240 .ka-scb-v3 .ka-scb-remove-btn:hover {
241 background: rgba(239, 68, 68, 0.2);
242 }
243 .ka-scb-v3 .ka-scb-add-btn {
244 background: #22c55e;
245 color: #fff;
246 border: none;
247 border-radius: 980px;
248 padding: 12px 24px;
249 font-size: 15px;
250 font-weight: 400;
251 cursor: pointer;
252 transition: all 0.3s;
253 }
254 .ka-scb-v3 .ka-scb-add-btn:hover {
255 background: #16a34a;
256 }
257
258 /* Checkbox group */
259 .ka-scb-v3 .ka-checkbox-group {
260 display: flex;
261 flex-wrap: wrap;
262 gap: 20px;
263 }
264 .ka-scb-v3 .ka-checkbox-item {
265 display: flex;
266 align-items: center;
267 gap: 8px;
268 font-size: 14px;
269 color: #1d1d1f;
270 }
271 body.ka-v3-dark .ka-scb-v3 .ka-checkbox-item {
272 color: #f5f5f7;
273 }
274
275 /* Shadow builder */
276 .ka-scb-v3 .ka-shadow-builder {
277 display: flex;
278 flex-wrap: wrap;
279 gap: 16px;
280 align-items: flex-end;
281 background: rgba(0, 0, 0, 0.02);
282 padding: 20px;
283 border-radius: 16px;
284 border: 1px solid rgba(0, 0, 0, 0.04);
285 }
286 body.ka-v3-dark .ka-scb-v3 .ka-shadow-builder {
287 background: rgba(255, 255, 255, 0.04);
288 border-color: rgba(255, 255, 255, 0.06);
289 }
290 .ka-scb-v3 .ka-shadow-field {
291 display: flex;
292 flex-direction: column;
293 gap: 6px;
294 }
295 .ka-scb-v3 .ka-shadow-field label {
296 font-size: 12px;
297 font-weight: 500;
298 color: #86868b;
299 text-transform: uppercase;
300 }
301 .ka-scb-v3 .ka-shadow-field input[type="number"] {
302 width: 80px;
303 padding: 10px 12px;
304 border: 1px solid rgba(0, 0, 0, 0.1);
305 border-radius: 10px;
306 font-size: 14px;
307 background: #fff;
308 }
309 body.ka-v3-dark .ka-scb-v3 .ka-shadow-field input[type="number"] {
310 background: #2c2c2e;
311 border-color: rgba(255, 255, 255, 0.1);
312 color: #f5f5f7;
313 }
314 .ka-scb-v3 .ka-shadow-preview {
315 width: 60px;
316 height: 40px;
317 background: #fff;
318 border-radius: 10px;
319 margin-left: auto;
320 }
321 body.ka-v3-dark .ka-scb-v3 .ka-shadow-preview {
322 background: #2c2c2e;
323 }
324
325 /* Status badge */
326 .ka-status-badge {
327 display: inline-flex;
328 align-items: center;
329 gap: 8px;
330 padding: 6px 14px;
331 border-radius: 980px;
332 font-size: 13px;
333 font-weight: 500;
334 }
335 .ka-status-badge-dot {
336 width: 8px;
337 height: 8px;
338 border-radius: 50%;
339 }
340 .ka-status-badge.enabled {
341 background: rgba(34, 197, 94, 0.1);
342 color: #22c55e;
343 }
344 .ka-status-badge.enabled .ka-status-badge-dot {
345 background: #22c55e;
346 }
347 .ka-status-badge.disabled {
348 background: rgba(239, 68, 68, 0.1);
349 color: #ef4444;
350 }
351 .ka-status-badge.disabled .ka-status-badge-dot {
352 background: #ef4444;
353 }
354
355 /* Color picker overrides */
356 .ka-scb-v3 .ka-color-wrap {
357 display: flex;
358 align-items: center;
359 gap: 12px;
360 }
361 .ka-scb-v3 .wp-picker-container .wp-color-result {
362 height: 36px;
363 border-radius: 8px;
364 }
365
366 /* Toggle override for green */
367 .ka-scb-v3 .ka-toggle input:checked + .ka-toggle-slider {
368 background: #22c55e !important;
369 }
370
371 /* Save button */
372 .ka-scb-v3 .ka-card-footer {
373 padding: 20px 28px;
374 background: rgba(34, 197, 94, 0.04);
375 border: 1px solid rgba(34, 197, 94, 0.1);
376 }
377 body.ka-v3-dark .ka-scb-v3 .ka-card-footer {
378 background: rgba(34, 197, 94, 0.08);
379 border-color: rgba(34, 197, 94, 0.2);
380 }
381 .ka-scb-v3 .ka-save-btn {
382 display: inline-flex;
383 align-items: center;
384 gap: 10px;
385 background: #22c55e;
386 color: #fff;
387 border: none;
388 padding: 14px 28px;
389 border-radius: 980px;
390 font-size: 15px;
391 font-weight: 400;
392 cursor: pointer;
393 transition: all 0.3s;
394 }
395 .ka-scb-v3 .ka-save-btn:hover {
396 background: #16a34a;
397 transform: scale(1.02);
398 }
399 .ka-scb-v3 .ka-save-btn .dashicons {
400 font-size: 18px;
401 width: 18px;
402 height: 18px;
403 }
404
405 /* Responsive */
406 @media (max-width: 900px) {
407 .ka-scb-v3 .ka-grid-2,
408 .ka-scb-v3 .ka-scb-item-grid {
409 grid-template-columns: 1fr;
410 }
411 }
412 </style>
413
414 <div class="ka-admin-wrap ka-scb-v3">
415 <!-- Header -->
416 <div class="ka-admin-header">
417 <div class="ka-admin-header-left">
418 <div class="ka-admin-header-icon green">
419 <span class="dashicons dashicons-phone"></span>
420 </div>
421 <div>
422 <h1 class="ka-admin-title"><?php esc_html_e('Sticky Contact Bar', 'king-addons'); ?></h1>
423 <p class="ka-admin-subtitle"><?php esc_html_e('Configure floating contact buttons', 'king-addons'); ?></p>
424 </div>
425 </div>
426 <div class="ka-admin-header-actions">
427 <span class="ka-status-badge <?php echo !empty($settings['enabled']) ? 'enabled' : 'disabled'; ?>">
428 <span class="ka-status-badge-dot"></span>
429 <?php echo !empty($settings['enabled']) ? esc_html__('Enabled', 'king-addons') : esc_html__('Disabled', 'king-addons'); ?>
430 </span>
431 <div class="ka-v3-segmented" id="ka-v3-theme-segment" role="radiogroup" aria-label="Theme" data-active="<?php echo esc_attr($theme_mode); ?>">
432 <span class="ka-v3-segmented-indicator" aria-hidden="true"></span>
433 <button type="button" class="ka-v3-segmented-btn" data-theme="light" aria-pressed="<?php echo $theme_mode === 'light' ? 'true' : 'false'; ?>">
434 <span class="ka-v3-segmented-icon" aria-hidden="true">☀︎</span>
435 <?php esc_html_e('Light', 'king-addons'); ?>
436 </button>
437 <button type="button" class="ka-v3-segmented-btn" data-theme="dark" aria-pressed="<?php echo $theme_mode === 'dark' ? 'true' : 'false'; ?>">
438 <span class="ka-v3-segmented-icon" aria-hidden="true">☾</span>
439 <?php esc_html_e('Dark', 'king-addons'); ?>
440 </button>
441 <button type="button" class="ka-v3-segmented-btn" data-theme="auto" aria-pressed="<?php echo $theme_mode === 'auto' ? 'true' : 'false'; ?>">
442 <span class="ka-v3-segmented-icon" aria-hidden="true">◐</span>
443 <?php esc_html_e('Auto', 'king-addons'); ?>
444 </button>
445 </div>
446 </div>
447 </div>
448
449 <!-- Tabs -->
450 <div class="ka-tabs">
451 <button type="button" class="ka-tab active" data-tab="general"><?php esc_html_e('General', 'king-addons'); ?></button>
452 <button type="button" class="ka-tab" data-tab="buttons"><?php esc_html_e('Buttons', 'king-addons'); ?></button>
453 <button type="button" class="ka-tab" data-tab="advanced">
454 <?php esc_html_e('Advanced', 'king-addons'); ?>
455 <?php if (!$is_pro): ?><span class="ka-pro-badge">Pro</span><?php endif; ?>
456 </button>
457 </div>
458
459 <form action="<?php echo esc_url(admin_url('options.php')); ?>" method="post">
460 <?php settings_fields('king_addons_sticky_contact_bar'); ?>
461
462 <!-- General Tab -->
463 <div class="ka-tab-content active" data-tab="general">
464 <!-- General Settings -->
465 <div class="ka-card">
466 <div class="ka-card-header">
467 <span class="dashicons dashicons-admin-settings"></span>
468 <h2><?php esc_html_e('General Settings', 'king-addons'); ?></h2>
469 </div>
470 <div class="ka-card-body">
471 <div class="ka-row">
472 <div class="ka-row-label"><?php esc_html_e('Enable', 'king-addons'); ?></div>
473 <div class="ka-row-field">
474 <label class="ka-toggle">
475 <input type="checkbox" name="<?php echo esc_attr(self::OPTION_KEY); ?>[enabled]" value="1" <?php checked(!empty($settings['enabled'])); ?> />
476 <span class="ka-toggle-slider"></span>
477 <span class="ka-toggle-label"><?php esc_html_e('Show sticky contact bar', 'king-addons'); ?></span>
478 </label>
479 </div>
480 </div>
481 <div class="ka-grid-2">
482 <div class="ka-row">
483 <div class="ka-row-label"><?php esc_html_e('Position', 'king-addons'); ?></div>
484 <div class="ka-row-field">
485 <select name="<?php echo esc_attr(self::OPTION_KEY); ?>[position]">
486 <option value="bottom" <?php selected($settings['position'], 'bottom'); ?>><?php esc_html_e('Bottom', 'king-addons'); ?></option>
487 <option value="left" <?php selected($settings['position'], 'left'); ?> <?php disabled(!$is_pro); ?>><?php esc_html_e('Left (Pro)', 'king-addons'); ?></option>
488 <option value="right" <?php selected($settings['position'], 'right'); ?> <?php disabled(!$is_pro); ?>><?php esc_html_e('Right (Pro)', 'king-addons'); ?></option>
489 </select>
490 </div>
491 </div>
492 <div class="ka-row">
493 <div class="ka-row-label"><?php esc_html_e('Alignment', 'king-addons'); ?></div>
494 <div class="ka-row-field">
495 <select name="<?php echo esc_attr(self::OPTION_KEY); ?>[alignment]">
496 <option value="center" <?php selected($settings['alignment'], 'center'); ?>><?php esc_html_e('Center', 'king-addons'); ?></option>
497 <option value="left" <?php selected($settings['alignment'], 'left'); ?>><?php esc_html_e('Left', 'king-addons'); ?></option>
498 <option value="right" <?php selected($settings['alignment'], 'right'); ?>><?php esc_html_e('Right', 'king-addons'); ?></option>
499 </select>
500 </div>
501 </div>
502 </div>
503 <div class="ka-row">
504 <div class="ka-row-label"><?php esc_html_e('Devices', 'king-addons'); ?></div>
505 <div class="ka-row-field">
506 <div class="ka-checkbox-group">
507 <label class="ka-checkbox-item">
508 <input type="checkbox" name="<?php echo esc_attr(self::OPTION_KEY); ?>[devices][]" value="desktop" <?php checked(in_array('desktop', (array) $settings['devices'], true)); ?> />
509 <?php esc_html_e('Desktop', 'king-addons'); ?>
510 </label>
511 <label class="ka-checkbox-item">
512 <input type="checkbox" name="<?php echo esc_attr(self::OPTION_KEY); ?>[devices][]" value="mobile" <?php checked(in_array('mobile', (array) $settings['devices'], true)); ?> />
513 <?php esc_html_e('Mobile', 'king-addons'); ?>
514 </label>
515 </div>
516 </div>
517 </div>
518 <div class="ka-grid-2">
519 <div class="ka-row">
520 <div class="ka-row-label"><?php esc_html_e('Trigger', 'king-addons'); ?></div>
521 <div class="ka-row-field">
522 <select name="<?php echo esc_attr(self::OPTION_KEY); ?>[trigger]">
523 <option value="always" <?php selected($settings['trigger'], 'always'); ?>><?php esc_html_e('Always visible', 'king-addons'); ?></option>
524 <option value="scroll" <?php selected($settings['trigger'], 'scroll'); ?>><?php esc_html_e('After scroll', 'king-addons'); ?></option>
525 <option value="delay" <?php selected($settings['trigger'], 'delay'); ?> <?php disabled(!$is_pro); ?>><?php esc_html_e('After delay (Pro)', 'king-addons'); ?></option>
526 </select>
527 </div>
528 </div>
529 <div class="ka-row">
530 <div class="ka-row-label"><?php esc_html_e('Scroll Offset', 'king-addons'); ?></div>
531 <div class="ka-row-field">
532 <input type="number" min="0" name="<?php echo esc_attr(self::OPTION_KEY); ?>[trigger_scroll]" value="<?php echo esc_attr((int) $settings['trigger_scroll']); ?>" style="max-width:100px" />
533 <span style="color:#64748b;margin-left:6px">px</span>
534 </div>
535 </div>
536 </div>
537 </div>
538 </div>
539
540 <!-- Appearance -->
541 <?php
542 // Parse shadow settings
543 $shadow_x = isset($settings['shadow_x']) ? (int) $settings['shadow_x'] : 0;
544 $shadow_y = isset($settings['shadow_y']) ? (int) $settings['shadow_y'] : 8;
545 $shadow_blur = isset($settings['shadow_blur']) ? (int) $settings['shadow_blur'] : 24;
546 $shadow_spread = isset($settings['shadow_spread']) ? (int) $settings['shadow_spread'] : 0;
547 $shadow_color = isset($settings['shadow_color']) ? $settings['shadow_color'] : 'rgba(0,0,0,0.15)';
548 ?>
549 <div class="ka-card">
550 <div class="ka-card-header">
551 <span class="dashicons dashicons-art"></span>
552 <h2><?php esc_html_e('Appearance', 'king-addons'); ?></h2>
553 </div>
554 <div class="ka-card-body">
555 <div class="ka-grid-2">
556 <div class="ka-row">
557 <div class="ka-row-label"><?php esc_html_e('Background', 'king-addons'); ?></div>
558 <div class="ka-row-field">
559 <div class="ka-color-wrap">
560 <input type="text" name="<?php echo esc_attr(self::OPTION_KEY); ?>[background]" value="<?php echo esc_attr($settings['background']); ?>" data-alpha-enabled="true" data-default-color="#0a0a0a" class="ka-color-picker" />
561 </div>
562 </div>
563 </div>
564 <div class="ka-row">
565 <div class="ka-row-label"><?php esc_html_e('Border Radius', 'king-addons'); ?></div>
566 <div class="ka-row-field">
567 <input type="number" min="0" name="<?php echo esc_attr(self::OPTION_KEY); ?>[border_radius]" value="<?php echo esc_attr((int) str_replace('px', '', $settings['border_radius'])); ?>" style="max-width:80px" />
568 <span style="color:#64748b;margin-left:6px">px</span>
569 </div>
570 </div>
571 <div class="ka-row">
572 <div class="ka-row-label"><?php esc_html_e('Z-index', 'king-addons'); ?></div>
573 <div class="ka-row-field">
574 <input type="number" name="<?php echo esc_attr(self::OPTION_KEY); ?>[z_index]" value="<?php echo esc_attr((int) $settings['z_index']); ?>" style="max-width:100px" />
575 </div>
576 </div>
577 <div class="ka-row">
578 <div class="ka-row-label"><?php esc_html_e('Item Shape', 'king-addons'); ?></div>
579 <div class="ka-row-field">
580 <select name="<?php echo esc_attr(self::OPTION_KEY); ?>[item_shape]">
581 <option value="circle" <?php selected($settings['item_shape'], 'circle'); ?>><?php esc_html_e('Circle', 'king-addons'); ?></option>
582 <option value="rounded" <?php selected($settings['item_shape'], 'rounded'); ?>><?php esc_html_e('Rounded', 'king-addons'); ?></option>
583 <option value="square" <?php selected($settings['item_shape'], 'square'); ?>><?php esc_html_e('Square', 'king-addons'); ?></option>
584 </select>
585 </div>
586 </div>
587 <div class="ka-row">
588 <div class="ka-row-label"><?php esc_html_e('Item Size', 'king-addons'); ?></div>
589 <div class="ka-row-field">
590 <input type="number" min="32" name="<?php echo esc_attr(self::OPTION_KEY); ?>[item_size]" value="<?php echo esc_attr((int) $settings['item_size']); ?>" style="max-width:100px" />
591 <span style="color:#64748b;margin-left:6px">px</span>
592 </div>
593 </div>
594 <div class="ka-row">
595 <div class="ka-row-label"><?php esc_html_e('Item Spacing', 'king-addons'); ?></div>
596 <div class="ka-row-field">
597 <input type="number" min="0" name="<?php echo esc_attr(self::OPTION_KEY); ?>[item_spacing]" value="<?php echo esc_attr((int) $settings['item_spacing']); ?>" style="max-width:100px" />
598 <span style="color:#64748b;margin-left:6px">px</span>
599 </div>
600 </div>
601 </div>
602 <!-- Shadow Builder -->
603 <div class="ka-row" style="flex-direction:column;gap:10px">
604 <div class="ka-row-label" style="padding-top:0"><?php esc_html_e('Box Shadow', 'king-addons'); ?></div>
605 <div class="ka-shadow-builder">
606 <div class="ka-shadow-field">
607 <label><?php esc_html_e('X', 'king-addons'); ?></label>
608 <input type="number" name="<?php echo esc_attr(self::OPTION_KEY); ?>[shadow_x]" value="<?php echo esc_attr($shadow_x); ?>" id="ka-shadow-x" />
609 </div>
610 <div class="ka-shadow-field">
611 <label><?php esc_html_e('Y', 'king-addons'); ?></label>
612 <input type="number" name="<?php echo esc_attr(self::OPTION_KEY); ?>[shadow_y]" value="<?php echo esc_attr($shadow_y); ?>" id="ka-shadow-y" />
613 </div>
614 <div class="ka-shadow-field">
615 <label><?php esc_html_e('Blur', 'king-addons'); ?></label>
616 <input type="number" min="0" name="<?php echo esc_attr(self::OPTION_KEY); ?>[shadow_blur]" value="<?php echo esc_attr($shadow_blur); ?>" id="ka-shadow-blur" />
617 </div>
618 <div class="ka-shadow-field">
619 <label><?php esc_html_e('Spread', 'king-addons'); ?></label>
620 <input type="number" name="<?php echo esc_attr(self::OPTION_KEY); ?>[shadow_spread]" value="<?php echo esc_attr($shadow_spread); ?>" id="ka-shadow-spread" />
621 </div>
622 <div class="ka-shadow-field">
623 <label><?php esc_html_e('Color', 'king-addons'); ?></label>
624 <input type="text" name="<?php echo esc_attr(self::OPTION_KEY); ?>[shadow_color]" value="<?php echo esc_attr($shadow_color); ?>" data-alpha-enabled="true" data-default-color="rgba(0,0,0,0.15)" class="ka-color-picker" id="ka-shadow-color" />
625 </div>
626 <div class="ka-shadow-preview" id="ka-shadow-preview"></div>
627 </div>
628 </div>
629 <!-- Keep legacy shadow field as hidden for backward compatibility -->
630 <input type="hidden" name="<?php echo esc_attr(self::OPTION_KEY); ?>[shadow]" id="ka-shadow-combined" value="<?php echo esc_attr($settings['shadow']); ?>" />
631 </div>
632 </div>
633 </div><!-- /General Tab -->
634
635 <!-- Buttons Tab -->
636 <div class="ka-tab-content" data-tab="buttons">
637 <div class="ka-card">
638 <div class="ka-card-header">
639 <span class="dashicons dashicons-admin-links"></span>
640 <h2><?php esc_html_e('Contact Buttons', 'king-addons'); ?></h2>
641 </div>
642 <div class="ka-card-body">
643 <p class="ka-row-desc" style="margin:0 0 16px"><?php esc_html_e('Add contact buttons. Fill only the fields relevant to each button type.', 'king-addons'); ?></p>
644 <div id="ka-scbar-items">
645 <?php
646 $items = is_array($settings['items']) ? $settings['items'] : [];
647 if (empty($items)) {
648 $items[] = [];
649 }
650 foreach ($items as $index => $item) {
651 $this->render_item_row($index, $item, $is_pro);
652 }
653 ?>
654 </div>
655 <button type="button" class="ka-scb-add-btn" id="ka-scbar-add-item">
656 <span class="dashicons dashicons-plus-alt2" style="margin-right:6px"></span>
657 <?php esc_html_e('Add Button', 'king-addons'); ?>
658 </button>
659 </div>
660 </div>
661 </div><!-- /Buttons Tab -->
662
663 <!-- Advanced Tab -->
664 <div class="ka-tab-content" data-tab="advanced">
665 <!-- Advanced Settings (Pro) -->
666 <div class="ka-card">
667 <div class="ka-card-header">
668 <span class="dashicons dashicons-admin-generic"></span>
669 <h2><?php esc_html_e('Advanced Settings', 'king-addons'); ?></h2>
670 <?php if (!$is_pro): ?><span class="ka-pro-badge">Pro</span><?php endif; ?>
671 </div>
672 <div class="ka-card-body">
673 <div class="ka-grid-2">
674 <div class="ka-row">
675 <div class="ka-row-label"><?php esc_html_e('Delay (ms)', 'king-addons'); ?></div>
676 <div class="ka-row-field">
677 <input type="number" min="0" name="<?php echo esc_attr(self::OPTION_KEY); ?>[trigger_delay]" value="<?php echo esc_attr((int) ($settings['trigger_delay'] ?? 0)); ?>" style="max-width:100px" <?php disabled(!$is_pro); ?> />
678 <p class="ka-row-desc"><?php esc_html_e('Show bar after this delay when trigger is set to "After delay".', 'king-addons'); ?></p>
679 </div>
680 </div>
681 <div class="ka-row">
682 <div class="ka-row-label"><?php esc_html_e('Hide on Scroll Down', 'king-addons'); ?></div>
683 <div class="ka-row-field">
684 <label class="ka-toggle">
685 <input type="checkbox" name="<?php echo esc_attr(self::OPTION_KEY); ?>[hide_on_scroll_down]" value="1" <?php checked(!empty($settings['hide_on_scroll_down'])); ?> <?php disabled(!$is_pro); ?> />
686 <span class="ka-toggle-slider"></span>
687 </label>
688 </div>
689 </div>
690 <div class="ka-row">
691 <div class="ka-row-label"><?php esc_html_e('Show on Scroll Up', 'king-addons'); ?></div>
692 <div class="ka-row-field">
693 <label class="ka-toggle">
694 <input type="checkbox" name="<?php echo esc_attr(self::OPTION_KEY); ?>[show_on_scroll_up]" value="1" <?php checked(!empty($settings['show_on_scroll_up'])); ?> <?php disabled(!$is_pro); ?> />
695 <span class="ka-toggle-slider"></span>
696 </label>
697 </div>
698 </div>
699 <div class="ka-row">
700 <div class="ka-row-label"><?php esc_html_e('Enable Analytics', 'king-addons'); ?></div>
701 <div class="ka-row-field">
702 <label class="ka-toggle">
703 <input type="checkbox" name="<?php echo esc_attr(self::OPTION_KEY); ?>[analytics_enabled]" value="1" <?php checked(!empty($settings['analytics_enabled'])); ?> <?php disabled(!$is_pro); ?> />
704 <span class="ka-toggle-slider"></span>
705 <span class="ka-toggle-label"><?php esc_html_e('Track clicks via dataLayer/gtag', 'king-addons'); ?></span>
706 </label>
707 </div>
708 </div>
709 </div>
710 </div>
711 </div>
712
713 <!-- Display Conditions (Pro) -->
714 <div class="ka-card">
715 <div class="ka-card-header">
716 <span class="dashicons dashicons-visibility"></span>
717 <h2><?php esc_html_e('Display Conditions', 'king-addons'); ?></h2>
718 <?php if (!$is_pro): ?><span class="ka-pro-badge">Pro</span><?php endif; ?>
719 </div>
720 <div class="ka-card-body">
721 <?php if (!$is_pro): ?>
722 <p class="ka-row-desc"><?php esc_html_e('Upgrade to Pro to add display conditions and control where the bar appears.', 'king-addons'); ?></p>
723 <?php else: ?>
724 <p class="ka-row-desc" style="margin:0 0 16px"><?php esc_html_e('Add conditions to control where the bar appears. All conditions must match.', 'king-addons'); ?></p>
725 <div id="ka-scbar-conditions">
726 <?php
727 $conditions = is_array($settings['conditions'] ?? null) ? $settings['conditions'] : [];
728 if (empty($conditions)) {
729 echo '<p class="ka-row-desc">' . esc_html__('No conditions set. Bar will appear on all pages.', 'king-addons') . '</p>';
730 }
731 foreach ($conditions as $cond_index => $condition) {
732 $this->render_condition_row($cond_index, $condition);
733 }
734 ?>
735 </div>
736 <button type="button" class="ka-scb-add-btn" id="ka-scbar-add-condition" style="margin-top:12px">
737 <span class="dashicons dashicons-plus-alt2" style="margin-right:6px"></span>
738 <?php esc_html_e('Add Condition', 'king-addons'); ?>
739 </button>
740 <?php endif; ?>
741 </div>
742 </div>
743 </div><!-- /Advanced Tab -->
744
745 <!-- Save Button -->
746 <div class="ka-card ka-card-footer">
747 <button type="submit" class="ka-save-btn">
748 <span class="dashicons dashicons-saved"></span>
749 <?php esc_html_e('Save Settings', 'king-addons'); ?>
750 </button>
751 </div>
752 </form>
753 </div>
754
755 <script>
756 (function() {
757 const ajaxUrl = '<?php echo esc_url(admin_url('admin-ajax.php')); ?>';
758 const nonce = '<?php echo esc_js(wp_create_nonce('king_addons_dashboard_ui')); ?>';
759 const segment = document.getElementById('ka-v3-theme-segment');
760 const mql = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
761 let mode = (segment && segment.getAttribute('data-active') ? segment.getAttribute('data-active') : 'dark').toString();
762 let mqlHandler = null;
763
764 function saveUISetting(key, value) {
765 const body = new URLSearchParams();
766 body.set('action', 'king_addons_save_dashboard_ui');
767 body.set('nonce', nonce);
768 body.set('key', key);
769 body.set('value', value);
770 fetch(ajaxUrl, {
771 method: 'POST',
772 headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
773 body: body.toString()
774 });
775 }
776
777 function updateSegment(activeMode) {
778 if (!segment) {
779 return;
780 }
781 segment.setAttribute('data-active', activeMode);
782 segment.querySelectorAll('.ka-v3-segmented-btn').forEach((btn) => {
783 const theme = btn.getAttribute('data-theme');
784 btn.setAttribute('aria-pressed', theme === activeMode ? 'true' : 'false');
785 });
786 }
787
788 function applyTheme(isDark) {
789 document.body.classList.toggle('ka-v3-dark', isDark);
790 document.documentElement.classList.toggle('ka-v3-dark', isDark);
791 }
792
793 function setThemeMode(nextMode, save) {
794 mode = nextMode;
795 updateSegment(nextMode);
796
797 if (mqlHandler && mql) {
798 if (mql.removeEventListener) {
799 mql.removeEventListener('change', mqlHandler);
800 } else if (mql.removeListener) {
801 mql.removeListener(mqlHandler);
802 }
803 mqlHandler = null;
804 }
805
806 if (nextMode === 'auto') {
807 applyTheme(!!(mql && mql.matches));
808 mqlHandler = (e) => {
809 if (mode !== 'auto') {
810 return;
811 }
812 applyTheme(!!e.matches);
813 };
814 if (mql) {
815 if (mql.addEventListener) {
816 mql.addEventListener('change', mqlHandler);
817 } else if (mql.addListener) {
818 mql.addListener(mqlHandler);
819 }
820 }
821 } else {
822 applyTheme(nextMode === 'dark');
823 }
824
825 if (save) {
826 saveUISetting('theme_mode', nextMode);
827 }
828 }
829
830 window.kaV3ToggleDark = function() {
831 const isDark = document.body.classList.contains('ka-v3-dark');
832 setThemeMode(isDark ? 'light' : 'dark', true);
833 };
834
835 if (segment) {
836 segment.addEventListener('click', function(e) {
837 const btn = e.target.closest('.ka-v3-segmented-btn');
838 if (!btn) return;
839 e.preventDefault();
840 setThemeMode((btn.getAttribute('data-theme') || 'dark').toString(), true);
841 });
842 }
843
844 if (segment) {
845 setThemeMode(mode, false);
846 }
847 })();
848
849 // Tab navigation
850 (function() {
851 const tabs = document.querySelectorAll('.ka-tab');
852 const contents = document.querySelectorAll('.ka-tab-content');
853
854 tabs.forEach(tab => {
855 tab.addEventListener('click', function(e) {
856 e.preventDefault();
857 const target = this.dataset.tab;
858 tabs.forEach(t => t.classList.remove('active'));
859 contents.forEach(c => c.classList.remove('active'));
860 this.classList.add('active');
861 document.querySelector('.ka-tab-content[data-tab="' + target + '"]').classList.add('active');
862 });
863 });
864 })();
865 </script>
866
867 <template id="ka-scbar-item-template">
868 <?php $this->render_item_row('__i__', [], $is_pro); ?>
869 </template>
870 <?php if ($is_pro): ?>
871 <template id="ka-scbar-condition-template">
872 <?php $this->render_condition_row('__c__', []); ?>
873 </template>
874 <?php endif; ?>
875 <?php
876 // Enqueue color picker
877 wp_enqueue_style('wp-color-picker');
878 wp_enqueue_script('wp-color-picker');
879 ?>
880 <script>
881 (function($) {
882 'use strict';
883
884 /**
885 * Initialize color pickers in a given container.
886 *
887 * @param {HTMLElement} container The container to search for color pickers.
888 */
889 function initColorPickers(container) {
890 if (!$.fn.wpColorPicker) return;
891 $(container).find('.ka-color-picker').each(function() {
892 if ($(this).closest('.wp-picker-container').length) return;
893 $(this).wpColorPicker({
894 defaultColor: $(this).data('default-color') || '',
895 change: function() {
896 updateShadowPreview();
897 }
898 });
899 });
900 }
901
902 /**
903 * Update shadow preview box.
904 */
905 function updateShadowPreview() {
906 const x = $('#ka-shadow-x').val() || 0;
907 const y = $('#ka-shadow-y').val() || 0;
908 const blur = $('#ka-shadow-blur').val() || 0;
909 const spread = $('#ka-shadow-spread').val() || 0;
910 const colorInput = $('#ka-shadow-color');
911 let color = colorInput.val() || 'rgba(0,0,0,0.15)';
912
913 // Get color from wp-color-picker if initialized
914 if (colorInput.closest('.wp-picker-container').length) {
915 color = colorInput.wpColorPicker('color') || color;
916 }
917
918 const shadow = x + 'px ' + y + 'px ' + blur + 'px ' + spread + 'px ' + color;
919 $('#ka-shadow-preview').css('box-shadow', shadow);
920 $('#ka-shadow-combined').val(shadow);
921 }
922
923 $(document).ready(function() {
924 // Initialize color pickers on page load
925 initColorPickers(document);
926
927 // Shadow builder - update preview on any change
928 $('#ka-shadow-x, #ka-shadow-y, #ka-shadow-blur, #ka-shadow-spread').on('input change', updateShadowPreview);
929
930 // Initial shadow preview
931 setTimeout(updateShadowPreview, 500);
932
933 // Button items repeater
934 const container = document.getElementById('ka-scbar-items');
935 const addBtn = document.getElementById('ka-scbar-add-item');
936 const tpl = document.getElementById('ka-scbar-item-template');
937 if (container && addBtn && tpl) {
938 addBtn.addEventListener('click', function() {
939 const idx = container.querySelectorAll('.ka-scb-item').length;
940 const html = tpl.innerHTML.replace(/__i__/g, idx);
941 const wrap = document.createElement('div');
942 wrap.innerHTML = html;
943 const row = wrap.firstElementChild;
944 container.appendChild(row);
945 // Initialize color pickers in new row
946 initColorPickers(row);
947 });
948
949 container.addEventListener('click', function(e) {
950 const btn = e.target.closest('.ka-scb-remove-btn');
951 if (!btn) return;
952 e.preventDefault();
953 const row = btn.closest('.ka-scb-item');
954 if (row) row.remove();
955 });
956 }
957
958 // Conditions (Pro)
959 const condContainer = document.getElementById('ka-scbar-conditions');
960 const condAddBtn = document.getElementById('ka-scbar-add-condition');
961 const condTpl = document.getElementById('ka-scbar-condition-template');
962 if (condContainer && condAddBtn && condTpl) {
963 condAddBtn.addEventListener('click', function() {
964 const idx = condContainer.querySelectorAll('.ka-scb-condition').length;
965 const html = condTpl.innerHTML.replace(/__c__/g, idx);
966 const wrap = document.createElement('div');
967 wrap.innerHTML = html;
968 const row = wrap.firstElementChild;
969 condContainer.appendChild(row);
970 });
971
972 condContainer.addEventListener('click', function(e) {
973 const btn = e.target.closest('.ka-scb-remove-condition');
974 if (!btn) return;
975 e.preventDefault();
976 const row = btn.closest('.ka-scb-condition');
977 if (row) row.remove();
978 });
979 }
980 });
981 })(jQuery);
982 </script>
983 <?php
984 }
985
986 /**
987 * Render single item row for admin repeater.
988 *
989 * @param int|string $index Item index.
990 * @param array $item Item data.
991 * @param bool $is_pro Whether pro is active.
992 *
993 * @return void
994 */
995 protected function render_item_row($index, array $item, bool $is_pro): void
996 {
997 $prefix = self::OPTION_KEY . '[items][' . $index . ']';
998 $type = $item['type'] ?? 'phone';
999 $type_labels = [
1000 'phone' => esc_html__('Phone', 'king-addons'),
1001 'email' => esc_html__('Email', 'king-addons'),
1002 'link' => esc_html__('URL', 'king-addons'),
1003 'whatsapp' => esc_html__('WhatsApp', 'king-addons'),
1004 'telegram' => esc_html__('Telegram', 'king-addons'),
1005 ];
1006 $label_text = $item['label'] ?? ($type_labels[$type] ?? ucfirst($type));
1007 ?>
1008 <div class="ka-scb-item">
1009 <div class="ka-scb-item-header">
1010 <span class="ka-scb-item-title"><?php echo esc_html($label_text); ?></span>
1011 <button type="button" class="ka-scb-remove-btn"><?php esc_html_e('Remove', 'king-addons'); ?></button>
1012 </div>
1013 <div class="ka-scb-item-grid">
1014 <div class="ka-scb-item-row">
1015 <span class="ka-scb-item-label"><?php esc_html_e('Type', 'king-addons'); ?></span>
1016 <select name="<?php echo esc_attr($prefix); ?>[type]">
1017 <option value="phone" <?php selected($type, 'phone'); ?>><?php esc_html_e('Phone', 'king-addons'); ?></option>
1018 <option value="email" <?php selected($type, 'email'); ?>><?php esc_html_e('Email', 'king-addons'); ?></option>
1019 <option value="link" <?php selected($type, 'link'); ?>><?php esc_html_e('Custom URL', 'king-addons'); ?></option>
1020 <option value="whatsapp" <?php selected($type, 'whatsapp'); ?>><?php esc_html_e('WhatsApp', 'king-addons'); ?></option>
1021 <option value="telegram" <?php selected($type, 'telegram'); ?>><?php esc_html_e('Telegram', 'king-addons'); ?></option>
1022 <option value="popup" <?php selected($type, 'popup'); ?> <?php disabled(!$is_pro); ?>><?php esc_html_e('Popup (Pro)', 'king-addons'); ?></option>
1023 <option value="messenger" <?php selected($type, 'messenger'); ?> <?php disabled(!$is_pro); ?>><?php esc_html_e('Messenger (Pro)', 'king-addons'); ?></option>
1024 <option value="viber" <?php selected($type, 'viber'); ?> <?php disabled(!$is_pro); ?>><?php esc_html_e('Viber (Pro)', 'king-addons'); ?></option>
1025 </select>
1026 </div>
1027 <div class="ka-scb-item-row">
1028 <span class="ka-scb-item-label"><?php esc_html_e('Label', 'king-addons'); ?></span>
1029 <input type="text" name="<?php echo esc_attr($prefix); ?>[label]" value="<?php echo esc_attr($item['label'] ?? ''); ?>" placeholder="<?php esc_attr_e('Tooltip text', 'king-addons'); ?>" />
1030 </div>
1031 <div class="ka-scb-item-row">
1032 <span class="ka-scb-item-label"><?php esc_html_e('Icon Class', 'king-addons'); ?></span>
1033 <input type="text" name="<?php echo esc_attr($prefix); ?>[icon]" value="<?php echo esc_attr($item['icon'] ?? ''); ?>" placeholder="fa-brands fa-whatsapp" />
1034 </div>
1035 <div class="ka-scb-item-row">
1036 <span class="ka-scb-item-label"><?php esc_html_e('Phone', 'king-addons'); ?></span>
1037 <input type="text" name="<?php echo esc_attr($prefix); ?>[phone]" value="<?php echo esc_attr($item['phone'] ?? ''); ?>" placeholder="+123456789" />
1038 </div>
1039 <div class="ka-scb-item-row">
1040 <span class="ka-scb-item-label"><?php esc_html_e('Email', 'king-addons'); ?></span>
1041 <input type="email" name="<?php echo esc_attr($prefix); ?>[email]" value="<?php echo esc_attr($item['email'] ?? ''); ?>" />
1042 </div>
1043 <div class="ka-scb-item-row">
1044 <span class="ka-scb-item-label"><?php esc_html_e('URL', 'king-addons'); ?></span>
1045 <input type="text" name="<?php echo esc_attr($prefix); ?>[url]" value="<?php echo esc_attr($item['url'] ?? ''); ?>" placeholder="https://" />
1046 </div>
1047 <div class="ka-scb-item-row">
1048 <span class="ka-scb-item-label"><?php esc_html_e('Username', 'king-addons'); ?></span>
1049 <input type="text" name="<?php echo esc_attr($prefix); ?>[username]" value="<?php echo esc_attr($item['username'] ?? ''); ?>" placeholder="@username" />
1050 </div>
1051 <div class="ka-scb-item-row">
1052 <span class="ka-scb-item-label"><?php esc_html_e('Background', 'king-addons'); ?></span>
1053 <input type="text" name="<?php echo esc_attr($prefix); ?>[color]" value="<?php echo esc_attr($item['color'] ?? ''); ?>" data-alpha-enabled="true" data-default-color="#25D366" class="ka-color-picker" />
1054 </div>
1055 <div class="ka-scb-item-row">
1056 <span class="ka-scb-item-label"><?php esc_html_e('Hover BG', 'king-addons'); ?></span>
1057 <input type="text" name="<?php echo esc_attr($prefix); ?>[color_hover]" value="<?php echo esc_attr($item['color_hover'] ?? ''); ?>" data-alpha-enabled="true" data-default-color="#1da851" class="ka-color-picker" />
1058 </div>
1059 <div class="ka-scb-item-row">
1060 <span class="ka-scb-item-label"><?php esc_html_e('Icon Color', 'king-addons'); ?></span>
1061 <input type="text" name="<?php echo esc_attr($prefix); ?>[icon_color]" value="<?php echo esc_attr($item['icon_color'] ?? ''); ?>" data-alpha-enabled="true" data-default-color="#ffffff" class="ka-color-picker" />
1062 </div>
1063 <div class="ka-scb-item-row">
1064 <span class="ka-scb-item-label"><?php esc_html_e('Target', 'king-addons'); ?></span>
1065 <select name="<?php echo esc_attr($prefix); ?>[target]">
1066 <option value="_self" <?php selected($item['target'] ?? '', '_self'); ?>><?php esc_html_e('Same Tab', 'king-addons'); ?></option>
1067 <option value="_blank" <?php selected($item['target'] ?? '', '_blank'); ?>><?php esc_html_e('New Tab', 'king-addons'); ?></option>
1068 </select>
1069 </div>
1070 <div class="ka-scb-item-row">
1071 <span class="ka-scb-item-label"><?php esc_html_e('Order', 'king-addons'); ?></span>
1072 <input type="number" name="<?php echo esc_attr($prefix); ?>[order]" value="<?php echo esc_attr($item['order'] ?? 0); ?>" style="max-width:80px" />
1073 </div>
1074 </div>
1075 <!-- Hidden fields for full data preservation -->
1076 <input type="hidden" name="<?php echo esc_attr($prefix); ?>[rel]" value="<?php echo esc_attr($item['rel'] ?? 'noopener noreferrer'); ?>" />
1077 <input type="hidden" name="<?php echo esc_attr($prefix); ?>[icon_color_hover]" value="<?php echo esc_attr($item['icon_color_hover'] ?? ''); ?>" />
1078 <input type="hidden" name="<?php echo esc_attr($prefix); ?>[popup_id]" value="<?php echo esc_attr($item['popup_id'] ?? ''); ?>" />
1079 <input type="hidden" name="<?php echo esc_attr($prefix); ?>[offcanvas_id]" value="<?php echo esc_attr($item['offcanvas_id'] ?? ''); ?>" />
1080 <input type="hidden" name="<?php echo esc_attr($prefix); ?>[scroll_target]" value="<?php echo esc_attr($item['scroll_target'] ?? ''); ?>" />
1081 <input type="hidden" name="<?php echo esc_attr($prefix); ?>[show_label]" value="<?php echo !empty($item['show_label']) ? '1' : ''; ?>" />
1082 <input type="hidden" name="<?php echo esc_attr($prefix); ?>[badge_text]" value="<?php echo esc_attr($item['badge_text'] ?? ''); ?>" />
1083 <input type="hidden" name="<?php echo esc_attr($prefix); ?>[badge_color]" value="<?php echo esc_attr($item['badge_color'] ?? ''); ?>" />
1084 <input type="hidden" name="<?php echo esc_attr($prefix); ?>[pulse]" value="<?php echo !empty($item['pulse']) ? '1' : ''; ?>" />
1085 <input type="hidden" name="<?php echo esc_attr($prefix); ?>[desktop_only]" value="<?php echo !empty($item['desktop_only']) ? '1' : ''; ?>" />
1086 <input type="hidden" name="<?php echo esc_attr($prefix); ?>[mobile_only]" value="<?php echo !empty($item['mobile_only']) ? '1' : ''; ?>" />
1087 </div>
1088 <?php
1089 }
1090
1091 /**
1092 * Render a single condition row for admin.
1093 *
1094 * @param int|string $index Row index.
1095 * @param array<string,string> $condition Condition data.
1096 *
1097 * @return void
1098 */
1099 protected function render_condition_row($index, array $condition): void
1100 {
1101 $prefix = self::OPTION_KEY . '[conditions][' . $index . ']';
1102 $type = $condition['type'] ?? 'page_type';
1103 $operator = $condition['operator'] ?? 'is';
1104 $value = $condition['value'] ?? '';
1105 ?>
1106 <div class="ka-scb-condition" style="display:flex;gap:10px;align-items:center;margin-bottom:10px;padding:12px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;">
1107 <select name="<?php echo esc_attr($prefix); ?>[type]" style="flex:1;padding:6px 10px;border:1px solid #e2e8f0;border-radius:6px;">
1108 <option value="page_type" <?php selected($type, 'page_type'); ?>><?php esc_html_e('Page Type', 'king-addons'); ?></option>
1109 <option value="user_role" <?php selected($type, 'user_role'); ?>><?php esc_html_e('User Role', 'king-addons'); ?></option>
1110 <option value="url_contains" <?php selected($type, 'url_contains'); ?>><?php esc_html_e('URL Contains', 'king-addons'); ?></option>
1111 <option value="post_id" <?php selected($type, 'post_id'); ?>><?php esc_html_e('Post ID', 'king-addons'); ?></option>
1112 </select>
1113 <select name="<?php echo esc_attr($prefix); ?>[operator]" style="width:100px;padding:6px 10px;border:1px solid #e2e8f0;border-radius:6px;">
1114 <option value="is" <?php selected($operator, 'is'); ?>><?php esc_html_e('Is', 'king-addons'); ?></option>
1115 <option value="is_not" <?php selected($operator, 'is_not'); ?>><?php esc_html_e('Is Not', 'king-addons'); ?></option>
1116 </select>
1117 <input type="text" name="<?php echo esc_attr($prefix); ?>[value]" value="<?php echo esc_attr($value); ?>" placeholder="<?php esc_attr_e('Value', 'king-addons'); ?>" style="flex:1;padding:6px 10px;border:1px solid #e2e8f0;border-radius:6px;" />
1118 <button type="button" class="ka-scb-remove-condition" style="background:#fee2e2;color:#dc2626;border:none;border-radius:6px;padding:6px 12px;cursor:pointer;">&times;</button>
1119 </div>
1120 <?php
1121 }
1122
1123 /**
1124 * Render the bar on frontend.
1125 *
1126 * @return void
1127 */
1128 public function render_bar(): void
1129 {
1130 $settings = $this->get_settings();
1131
1132 if (empty($settings['enabled'])) {
1133 return;
1134 }
1135
1136 $settings = $this->apply_license_restrictions($settings);
1137
1138 if (!$this->check_display_conditions($settings)) {
1139 return;
1140 }
1141
1142 $items = is_array($settings['items']) ? $settings['items'] : [];
1143 if (empty($items)) {
1144 return;
1145 }
1146
1147 wp_enqueue_style(KING_ADDONS_ASSETS_UNIQUE_KEY . '-sticky-contact-bar');
1148 wp_enqueue_script(KING_ADDONS_ASSETS_UNIQUE_KEY . '-sticky-contact-bar');
1149
1150 echo $this->get_bar_html($settings, $items); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1151 }
1152
1153 /**
1154 * Check simple display conditions (devices).
1155 *
1156 * @param array<string,mixed> $settings Settings.
1157 *
1158 * @return bool
1159 */
1160 protected function check_display_conditions(array $settings): bool
1161 {
1162 $devices = isset($settings['devices']) && is_array($settings['devices']) ? $settings['devices'] : ['desktop', 'mobile'];
1163 $is_mobile = wp_is_mobile();
1164
1165 if (in_array('mobile', $devices, true) && $is_mobile) {
1166 return true;
1167 }
1168
1169 if (in_array('desktop', $devices, true) && !$is_mobile) {
1170 return true;
1171 }
1172
1173 return false;
1174 }
1175
1176 /**
1177 * Build bar HTML.
1178 *
1179 * @param array<string,mixed> $settings Settings.
1180 * @param array<int,array> $items Items.
1181 *
1182 * @return string
1183 */
1184 protected function get_bar_html(array $settings, array $items): string
1185 {
1186 $position = $settings['position'] ?? 'bottom';
1187 $alignment = $settings['alignment'] ?? 'center';
1188
1189 $classes = [
1190 'ka-sticky-contact-bar',
1191 'ka-position-' . esc_attr($position),
1192 'ka-align-' . esc_attr($alignment),
1193 'ka-shape-' . esc_attr($settings['item_shape'] ?? 'circle'),
1194 'is-hidden',
1195 ];
1196
1197 $style_parts = [];
1198 if (!empty($settings['background'])) {
1199 $style_parts[] = '--ka-scbar-bg:' . $settings['background'];
1200 }
1201 if (!empty($settings['border_radius'])) {
1202 $style_parts[] = '--ka-scbar-radius:' . absint($settings['border_radius']) . 'px';
1203 }
1204 if (!empty($settings['shadow'])) {
1205 $style_parts[] = '--ka-scbar-shadow:' . $settings['shadow'];
1206 }
1207 if (!empty($settings['item_size'])) {
1208 $style_parts[] = '--ka-scbar-size:' . absint($settings['item_size']) . 'px';
1209 }
1210 if (isset($settings['item_spacing'])) {
1211 $style_parts[] = '--ka-scbar-gap:' . absint($settings['item_spacing']) . 'px';
1212 }
1213 if (isset($settings['z_index'])) {
1214 $style_parts[] = '--ka-scbar-z:' . (int) $settings['z_index'];
1215 }
1216
1217 $data_attrs = [
1218 'data-trigger="' . esc_attr($settings['trigger'] ?? 'always') . '"',
1219 'data-trigger-scroll="' . esc_attr((int) ($settings['trigger_scroll'] ?? 0)) . '"',
1220 'data-trigger-delay="' . esc_attr((int) ($settings['trigger_delay'] ?? 0)) . '"',
1221 'data-hide-on-scroll-down="' . (!empty($settings['hide_on_scroll_down']) ? 'yes' : 'no') . '"',
1222 'data-show-on-scroll-up="' . (!empty($settings['show_on_scroll_up']) ? 'yes' : 'no') . '"',
1223 'data-analytics-enabled="' . (!empty($settings['analytics_enabled']) ? 'yes' : 'no') . '"',
1224 ];
1225
1226 usort(
1227 $items,
1228 static function ($a, $b) {
1229 return (int) ($a['order'] ?? 0) <=> (int) ($b['order'] ?? 0);
1230 }
1231 );
1232
1233 $items_html = '';
1234 foreach ($items as $item) {
1235 $item_html = $this->build_item_html($item);
1236 if ($item_html) {
1237 $items_html .= $item_html;
1238 }
1239 }
1240
1241 if ('' === $items_html) {
1242 return '';
1243 }
1244
1245 $style_attr = !empty($style_parts) ? ' style="' . esc_attr(implode(';', $style_parts)) . '"' : '';
1246
1247 return '<div class="' . esc_attr(implode(' ', $classes)) . '" ' . implode(' ', $data_attrs) . $style_attr . '><div class="ka-sticky-contact-bar__inner">' . $items_html . '</div></div>';
1248 }
1249
1250 /**
1251 * Build single item HTML.
1252 *
1253 * @param array<string,mixed> $item Item data.
1254 *
1255 * @return string
1256 */
1257 protected function build_item_html(array $item): string
1258 {
1259 $type = $item['type'] ?? 'phone';
1260 $free_types = ['phone', 'email', 'link', 'whatsapp', 'telegram'];
1261 $is_pro = $this->can_use_pro();
1262
1263 if (!$is_pro && !in_array($type, $free_types, true)) {
1264 return '';
1265 }
1266
1267 $href = $this->get_item_href($type, $item);
1268 if ('' === $href) {
1269 return '';
1270 }
1271
1272 $classes = ['ka-sticky-contact-item', 'ka-type-' . esc_attr($type)];
1273 if (!empty($item['pulse']) && $is_pro) {
1274 $classes[] = 'ka-sticky-contact-item--pulse';
1275 }
1276 if (!empty($item['show_label']) && $is_pro) {
1277 $classes[] = 'ka-sticky-contact-item--label-visible';
1278 }
1279 if (!empty($item['desktop_only'])) {
1280 $classes[] = 'ka-desktop-only';
1281 }
1282 if (!empty($item['mobile_only'])) {
1283 $classes[] = 'ka-mobile-only';
1284 }
1285
1286 $style_parts = [];
1287 if (!empty($item['color'])) {
1288 $style_parts[] = '--ka-scbar-item-bg:' . $item['color'];
1289 }
1290 if (!empty($item['color_hover'])) {
1291 $style_parts[] = '--ka-scbar-item-bg-hover:' . $item['color_hover'];
1292 }
1293 if (!empty($item['icon_color'])) {
1294 $style_parts[] = '--ka-scbar-icon-color:' . $item['icon_color'];
1295 }
1296 if (!empty($item['icon_color_hover'])) {
1297 $style_parts[] = '--ka-scbar-icon-color-hover:' . $item['icon_color_hover'];
1298 }
1299
1300 $aria = !empty($item['label']) ? $item['label'] : ucfirst($type);
1301
1302 $data = [];
1303 if (in_array($type, ['popup', 'offcanvas', 'scroll'], true)) {
1304 $data[] = 'data-action="' . esc_attr($type) . '"';
1305 }
1306 if ('popup' === $type && !empty($item['popup_id'])) {
1307 $data[] = 'data-popup-id="' . esc_attr($item['popup_id']) . '"';
1308 }
1309 if ('offcanvas' === $type && !empty($item['offcanvas_id'])) {
1310 $data[] = 'data-offcanvas-id="' . esc_attr($item['offcanvas_id']) . '"';
1311 }
1312 if ('scroll' === $type && !empty($item['scroll_target'])) {
1313 $data[] = 'data-scroll-target="' . esc_attr($item['scroll_target']) . '"';
1314 }
1315
1316 $label_html = '';
1317 if (!empty($item['show_label']) && $is_pro && !empty($item['label'])) {
1318 $label_html = '<span class="ka-sticky-contact-item__label">' . esc_html($item['label']) . '</span>';
1319 }
1320
1321 $badge_html = '';
1322 if (!empty($item['badge_text']) && $is_pro) {
1323 $badge_style = '';
1324 if (!empty($item['badge_color'])) {
1325 $badge_style = ' style="background:' . esc_attr($item['badge_color']) . ';"';
1326 }
1327 $badge_html = '<span class="ka-sticky-contact-item__badge"' . $badge_style . '>' . esc_html($item['badge_text']) . '</span>';
1328 }
1329
1330 $icon_html = '';
1331 if (!empty($item['icon'])) {
1332 $icon_html = '<span class="ka-sticky-contact-icon"><i class="' . esc_attr($item['icon']) . '" aria-hidden="true"></i></span>';
1333 } else {
1334 // Use default SVG icon based on type
1335 $default_svg = $this->get_default_icon_svg($type);
1336 if ($default_svg) {
1337 $icon_html = '<span class="ka-sticky-contact-icon">' . $default_svg . '</span>';
1338 }
1339 }
1340
1341 $style_attr = !empty($style_parts) ? ' style="' . esc_attr(implode(';', $style_parts)) . '"' : '';
1342
1343 $target = !empty($item['target']) ? $item['target'] : '_self';
1344 $rel = !empty($item['rel']) ? $item['rel'] : 'noopener noreferrer';
1345
1346 return '<a class="' . esc_attr(implode(' ', $classes)) . '" href="' . esc_url($href) . '" aria-label="' . esc_attr($aria) . '" target="' . esc_attr($target) . '" rel="' . esc_attr($rel) . '"' . $style_attr . ' ' . implode(' ', $data) . '>' . $icon_html . $label_html . $badge_html . '</a>';
1347 }
1348
1349 /**
1350 * Get href per item type.
1351 *
1352 * @param string $type Item type.
1353 * @param array<string,mixed> $item Item data.
1354 *
1355 * @return string
1356 */
1357 protected function get_item_href(string $type, array $item): string
1358 {
1359 switch ($type) {
1360 case 'phone':
1361 return !empty($item['phone']) ? 'tel:' . preg_replace('/\s+/', '', $item['phone']) : '';
1362 case 'email':
1363 return !empty($item['email']) ? 'mailto:' . sanitize_email($item['email']) : '';
1364 case 'whatsapp':
1365 if (!empty($item['url'])) {
1366 return esc_url_raw($item['url']);
1367 }
1368 if (!empty($item['phone'])) {
1369 $num = preg_replace('/\D+/', '', $item['phone']);
1370 return $num ? 'https://wa.me/' . $num : '';
1371 }
1372 return '';
1373 case 'telegram':
1374 if (!empty($item['url'])) {
1375 return esc_url_raw($item['url']);
1376 }
1377 if (!empty($item['username'])) {
1378 return 'https://t.me/' . ltrim($item['username'], '@');
1379 }
1380 return '';
1381 case 'link':
1382 return !empty($item['url']) ? esc_url_raw($item['url']) : '';
1383 case 'messenger':
1384 if (!empty($item['url'])) {
1385 return esc_url_raw($item['url']);
1386 }
1387 if (!empty($item['username'])) {
1388 return 'https://m.me/' . ltrim($item['username'], '@');
1389 }
1390 return '';
1391 case 'viber':
1392 if (!empty($item['url'])) {
1393 return esc_url_raw($item['url']);
1394 }
1395 if (!empty($item['phone'])) {
1396 $num = preg_replace('/\D+/', '', $item['phone']);
1397 return $num ? 'viber://chat?number=' . $num : '';
1398 }
1399 return '';
1400 case 'popup':
1401 case 'offcanvas':
1402 case 'scroll':
1403 return 'javascript:void(0)';
1404 default:
1405 return '';
1406 }
1407 }
1408
1409 /**
1410 * Get default SVG icon for a button type.
1411 *
1412 * @param string $type Button type.
1413 *
1414 * @return string SVG markup or empty string.
1415 */
1416 protected function get_default_icon_svg(string $type): string
1417 {
1418 $icons = [
1419 'phone' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M6.62 10.79c1.44 2.83 3.76 5.15 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.24.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z"/></svg>',
1420 'email' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4-8 5-8-5V6l8 5 8-5v2z"/></svg>',
1421 'whatsapp' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/></svg>',
1422 'telegram' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M11.944 0A12 12 0 000 12a12 12 0 0012 12 12 12 0 0012-12A12 12 0 0012 0a12 12 0 00-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 01.171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.015 3.333-1.386 4.025-1.627 4.476-1.635z"/></svg>',
1423 'messenger' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 0C5.373 0 0 4.974 0 11.111c0 3.497 1.745 6.616 4.472 8.652V24l4.086-2.242c1.09.301 2.246.464 3.442.464 6.627 0 12-4.974 12-11.111S18.627 0 12 0zm1.193 14.963l-3.056-3.259-5.963 3.259 6.559-6.963 3.13 3.259 5.889-3.259-6.559 6.963z"/></svg>',
1424 'viber' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M11.4 0C9.473.028 5.333.344 3.02 2.467 1.302 4.187.712 6.692.622 9.836c-.09 3.144-.198 9.037 5.533 10.618v2.428s-.037.978.609 1.178c.778.243 1.235-.501 1.98-1.302.409-.44.972-1.088 1.397-1.583 3.85.323 6.813-.416 7.15-.527.776-.257 5.17-.815 5.884-6.651.735-6.016-.357-9.818-2.362-11.553C18.986.751 16.13.233 12.021.035 11.827.023 11.613.003 11.4 0zm.077 1.932c.096.002.2.007.313.015 3.758.18 6.282.61 7.952 2.094 1.7 1.468 2.611 4.753 1.974 9.992-.59 4.788-4.01 5.262-4.655 5.475-.279.092-2.905.744-6.212.52 0 0-2.464 2.971-3.233 3.742-.12.121-.268.166-.363.146-.134-.028-.171-.163-.17-.36l.012-4.053c-4.917-1.359-4.832-6.418-4.755-9.083.077-2.664.558-4.79 2.015-6.238 1.902-1.76 5.434-2.29 6.81-2.24.087-.004.19-.006.312-.01z"/><path d="M11.941 4.006c-.287 0-.287.446 0 .449 2.93.022 5.457 2.109 5.479 5.783.002.289.446.287.444 0-.024-3.965-2.723-6.21-5.923-6.232zm.007 2.119c-.285 0-.283.443.003.443 1.564.02 2.888 1.074 2.903 2.955.001.287.444.285.443-.002-.017-2.137-1.5-3.376-3.349-3.396zm.033 2.113c-.287-.001-.288.443 0 .445.663.01 1.244.561 1.252 1.297.001.287.443.284.442-.003-.01-.981-.71-1.728-1.694-1.739zm-4.165.02c.271.036.458.194.533.302.302.428.587.867.862 1.311.226.368.152.742-.167 1.011l-.57.507c-.119.115-.194.283-.082.479.856 1.484 1.811 2.459 3.456 3.474.18.104.36.095.494-.028.478-.435.941-.89 1.392-1.353.21-.217.55-.215.799-.042.484.341.959.694 1.427 1.055.295.227.326.618.103.887-.497.599-.996 1.196-1.49 1.796-.317.385-.767.464-1.214.356-.91-.221-1.773-.577-2.587-1.032-1.82-1.01-3.422-2.291-4.715-3.953-.6-.77-1.101-1.594-1.424-2.52-.17-.487.012-.851.355-1.177.407-.384.823-.759 1.234-1.14.175-.163.376-.25.594-.233z"/></svg>',
1425 'link' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"/></svg>',
1426 'popup' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 14H5V8h14v10z"/></svg>',
1427 'offcanvas' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/></svg>',
1428 'scroll' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z"/></svg>',
1429 ];
1430
1431 return $icons[$type] ?? '';
1432 }
1433
1434 /**
1435 * Get settings merged with defaults.
1436 *
1437 * @return array<string,mixed>
1438 */
1439 public function get_settings(): array
1440 {
1441 $saved = get_option(self::OPTION_KEY, []);
1442 if (!is_array($saved)) {
1443 $saved = [];
1444 }
1445 return wp_parse_args($saved, $this->get_settings_defaults());
1446 }
1447
1448 /**
1449 * Default settings.
1450 *
1451 * @return array<string,mixed>
1452 */
1453 protected function get_settings_defaults(): array
1454 {
1455 return [
1456 'enabled' => false,
1457 'position' => 'bottom',
1458 'alignment' => 'center',
1459 'devices' => ['mobile', 'desktop'],
1460 'trigger' => 'always',
1461 'trigger_scroll' => 100,
1462 'trigger_delay' => 0,
1463 'z_index' => 9999,
1464 'background' => 'rgba(20,20,20,0.95)',
1465 'border_radius' => 14,
1466 'shadow' => '0px 10px 30px 0px rgba(0,0,0,0.18)',
1467 'shadow_x' => 0,
1468 'shadow_y' => 10,
1469 'shadow_blur' => 30,
1470 'shadow_spread' => 0,
1471 'shadow_color' => 'rgba(0,0,0,0.18)',
1472 'item_size' => 48,
1473 'item_spacing' => 10,
1474 'item_shape' => 'circle',
1475 'hide_on_scroll_down' => false,
1476 'show_on_scroll_up' => false,
1477 'analytics_enabled' => false,
1478 'items' => [],
1479 ];
1480 }
1481
1482 /**
1483 * Sanitize settings.
1484 *
1485 * @param array<string,mixed> $input Raw input.
1486 *
1487 * @return array<string,mixed>
1488 */
1489 public function sanitize_settings($input): array
1490 {
1491 $defaults = $this->get_settings_defaults();
1492
1493 // Shadow fields - construct combined shadow string from components
1494 $shadow_x = isset($input['shadow_x']) ? (int) $input['shadow_x'] : $defaults['shadow_x'];
1495 $shadow_y = isset($input['shadow_y']) ? (int) $input['shadow_y'] : $defaults['shadow_y'];
1496 $shadow_blur = isset($input['shadow_blur']) ? absint($input['shadow_blur']) : $defaults['shadow_blur'];
1497 $shadow_spread = isset($input['shadow_spread']) ? (int) $input['shadow_spread'] : $defaults['shadow_spread'];
1498 $shadow_color = sanitize_text_field($input['shadow_color'] ?? $defaults['shadow_color']);
1499 $shadow_combined = $shadow_x . 'px ' . $shadow_y . 'px ' . $shadow_blur . 'px ' . $shadow_spread . 'px ' . $shadow_color;
1500
1501 $output = [
1502 'enabled' => !empty($input['enabled']),
1503 'position' => in_array($input['position'] ?? 'bottom', ['bottom', 'left', 'right'], true) ? $input['position'] : 'bottom',
1504 'alignment' => in_array($input['alignment'] ?? 'center', ['center', 'left', 'right'], true) ? $input['alignment'] : 'center',
1505 'devices' => [],
1506 'trigger' => in_array($input['trigger'] ?? 'always', ['always', 'scroll', 'delay'], true) ? $input['trigger'] : 'always',
1507 'trigger_scroll' => isset($input['trigger_scroll']) ? absint($input['trigger_scroll']) : $defaults['trigger_scroll'],
1508 'trigger_delay' => isset($input['trigger_delay']) ? absint($input['trigger_delay']) : $defaults['trigger_delay'],
1509 'z_index' => isset($input['z_index']) ? (int) $input['z_index'] : $defaults['z_index'],
1510 'background' => sanitize_text_field($input['background'] ?? $defaults['background']),
1511 'border_radius' => isset($input['border_radius']) ? absint($input['border_radius']) : $defaults['border_radius'],
1512 'shadow' => $shadow_combined,
1513 'shadow_x' => $shadow_x,
1514 'shadow_y' => $shadow_y,
1515 'shadow_blur' => $shadow_blur,
1516 'shadow_spread' => $shadow_spread,
1517 'shadow_color' => $shadow_color,
1518 'item_size' => isset($input['item_size']) ? absint($input['item_size']) : $defaults['item_size'],
1519 'item_spacing' => isset($input['item_spacing']) ? absint($input['item_spacing']) : $defaults['item_spacing'],
1520 'item_shape' => in_array($input['item_shape'] ?? 'circle', ['circle', 'rounded', 'square'], true) ? $input['item_shape'] : 'circle',
1521 'hide_on_scroll_down' => !empty($input['hide_on_scroll_down']),
1522 'show_on_scroll_up' => !empty($input['show_on_scroll_up']),
1523 'analytics_enabled' => !empty($input['analytics_enabled']),
1524 'items' => [],
1525 ];
1526
1527 if (!empty($input['devices']) && is_array($input['devices'])) {
1528 foreach ($input['devices'] as $device) {
1529 if (in_array($device, ['desktop', 'mobile'], true)) {
1530 $output['devices'][] = $device;
1531 }
1532 }
1533 }
1534 if (empty($output['devices'])) {
1535 $output['devices'] = ['mobile', 'desktop'];
1536 }
1537
1538 if (!empty($input['items']) && is_array($input['items'])) {
1539 foreach ($input['items'] as $item) {
1540 $output['items'][] = [
1541 'type' => sanitize_text_field($item['type'] ?? 'phone'),
1542 'label' => sanitize_text_field($item['label'] ?? ''),
1543 'icon' => sanitize_text_field($item['icon'] ?? ''),
1544 'color' => sanitize_text_field($item['color'] ?? ''),
1545 'color_hover' => sanitize_text_field($item['color_hover'] ?? ''),
1546 'icon_color' => sanitize_text_field($item['icon_color'] ?? ''),
1547 'icon_color_hover' => sanitize_text_field($item['icon_color_hover'] ?? ''),
1548 'url' => esc_url_raw($item['url'] ?? ''),
1549 'phone' => sanitize_text_field($item['phone'] ?? ''),
1550 'username' => sanitize_text_field($item['username'] ?? ''),
1551 'email' => sanitize_email($item['email'] ?? ''),
1552 'popup_id' => sanitize_text_field($item['popup_id'] ?? ''),
1553 'offcanvas_id' => sanitize_text_field($item['offcanvas_id'] ?? ''),
1554 'scroll_target' => sanitize_text_field($item['scroll_target'] ?? ''),
1555 'target' => in_array($item['target'] ?? '_self', ['_self', '_blank'], true) ? $item['target'] : '_self',
1556 'rel' => sanitize_text_field($item['rel'] ?? 'noopener noreferrer'),
1557 'show_label' => !empty($item['show_label']),
1558 'badge_text' => sanitize_text_field($item['badge_text'] ?? ''),
1559 'badge_color' => sanitize_text_field($item['badge_color'] ?? ''),
1560 'pulse' => !empty($item['pulse']),
1561 'desktop_only' => !empty($item['desktop_only']),
1562 'mobile_only' => !empty($item['mobile_only']),
1563 'order' => isset($item['order']) ? (int) $item['order'] : 0,
1564 ];
1565 }
1566 }
1567
1568 return $output;
1569 }
1570
1571 /**
1572 * Apply license restrictions to settings (remove Pro-only bits).
1573 *
1574 * @param array<string,mixed> $settings Settings.
1575 *
1576 * @return array<string,mixed>
1577 */
1578 protected function apply_license_restrictions(array $settings): array
1579 {
1580 if ($this->can_use_pro()) {
1581 return $settings;
1582 }
1583
1584 // Free limitations.
1585 $settings['position'] = 'bottom';
1586 if ('delay' === ($settings['trigger'] ?? 'always')) {
1587 $settings['trigger'] = 'always';
1588 $settings['trigger_delay'] = 0;
1589 }
1590 $settings['hide_on_scroll_down'] = false;
1591 $settings['show_on_scroll_up'] = false;
1592
1593 $settings['items'] = array_values(
1594 array_filter(
1595 $settings['items'],
1596 static function ($item) {
1597 return in_array($item['type'] ?? 'phone', ['phone', 'email', 'link', 'whatsapp', 'telegram'], true);
1598 }
1599 )
1600 );
1601
1602 foreach ($settings['items'] as &$item) {
1603 $item['pulse'] = false;
1604 $item['badge_text'] = '';
1605 $item['badge_color'] = '';
1606 $item['show_label'] = false;
1607 }
1608 unset($item);
1609
1610 return $settings;
1611 }
1612
1613 /**
1614 * Check if Pro is available.
1615 *
1616 * @return bool
1617 */
1618 protected function can_use_pro(): bool
1619 {
1620 if (!function_exists('king_addons_freemius')) {
1621 return false;
1622 }
1623
1624 $fs = king_addons_freemius();
1625 if (!is_object($fs) || !method_exists($fs, 'can_use_premium_code')) {
1626 return false;
1627 }
1628
1629 return (bool) $fs->can_use_premium_code();
1630 }
1631 }
1632
1633
1634
1635
1636
1637
1638
1639