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

1,630 lines 81.6 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 = 'dark';
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'; ?>"><?php esc_html_e('Light', 'king-addons'); ?></button>
434 <button type="button" class="ka-v3-segmented-btn" data-theme="dark" aria-pressed="<?php echo $theme_mode === 'dark' ? 'true' : 'false'; ?>"><?php esc_html_e('Dark', 'king-addons'); ?></button>
435 <button type="button" class="ka-v3-segmented-btn" data-theme="auto" aria-pressed="<?php echo $theme_mode === 'auto' ? 'true' : 'false'; ?>"><?php esc_html_e('Auto', 'king-addons'); ?></button>
436 </div>
437 </div>
438 </div>
439
440 <!-- Tabs -->
441 <div class="ka-tabs">
442 <button type="button" class="ka-tab active" data-tab="general"><?php esc_html_e('General', 'king-addons'); ?></button>
443 <button type="button" class="ka-tab" data-tab="buttons"><?php esc_html_e('Buttons', 'king-addons'); ?></button>
444 <button type="button" class="ka-tab" data-tab="advanced">
445 <?php esc_html_e('Advanced', 'king-addons'); ?>
446 <?php if (!$is_pro): ?><span class="ka-pro-badge">Pro</span><?php endif; ?>
447 </button>
448 </div>
449
450 <form action="<?php echo esc_url(admin_url('options.php')); ?>" method="post">
451 <?php settings_fields('king_addons_sticky_contact_bar'); ?>
452
453 <!-- General Tab -->
454 <div class="ka-tab-content active" data-tab="general">
455 <!-- General Settings -->
456 <div class="ka-card">
457 <div class="ka-card-header">
458 <span class="dashicons dashicons-admin-settings"></span>
459 <h2><?php esc_html_e('General Settings', 'king-addons'); ?></h2>
460 </div>
461 <div class="ka-card-body">
462 <div class="ka-row">
463 <div class="ka-row-label"><?php esc_html_e('Enable', 'king-addons'); ?></div>
464 <div class="ka-row-field">
465 <label class="ka-toggle">
466 <input type="checkbox" name="<?php echo esc_attr(self::OPTION_KEY); ?>[enabled]" value="1" <?php checked(!empty($settings['enabled'])); ?> />
467 <span class="ka-toggle-slider"></span>
468 <span class="ka-toggle-label"><?php esc_html_e('Show sticky contact bar', 'king-addons'); ?></span>
469 </label>
470 </div>
471 </div>
472 <div class="ka-grid-2">
473 <div class="ka-row">
474 <div class="ka-row-label"><?php esc_html_e('Position', 'king-addons'); ?></div>
475 <div class="ka-row-field">
476 <select name="<?php echo esc_attr(self::OPTION_KEY); ?>[position]">
477 <option value="bottom" <?php selected($settings['position'], 'bottom'); ?>><?php esc_html_e('Bottom', 'king-addons'); ?></option>
478 <option value="left" <?php selected($settings['position'], 'left'); ?> <?php disabled(!$is_pro); ?>><?php esc_html_e('Left (Pro)', 'king-addons'); ?></option>
479 <option value="right" <?php selected($settings['position'], 'right'); ?> <?php disabled(!$is_pro); ?>><?php esc_html_e('Right (Pro)', 'king-addons'); ?></option>
480 </select>
481 </div>
482 </div>
483 <div class="ka-row">
484 <div class="ka-row-label"><?php esc_html_e('Alignment', 'king-addons'); ?></div>
485 <div class="ka-row-field">
486 <select name="<?php echo esc_attr(self::OPTION_KEY); ?>[alignment]">
487 <option value="center" <?php selected($settings['alignment'], 'center'); ?>><?php esc_html_e('Center', 'king-addons'); ?></option>
488 <option value="left" <?php selected($settings['alignment'], 'left'); ?>><?php esc_html_e('Left', 'king-addons'); ?></option>
489 <option value="right" <?php selected($settings['alignment'], 'right'); ?>><?php esc_html_e('Right', 'king-addons'); ?></option>
490 </select>
491 </div>
492 </div>
493 </div>
494 <div class="ka-row">
495 <div class="ka-row-label"><?php esc_html_e('Devices', 'king-addons'); ?></div>
496 <div class="ka-row-field">
497 <div class="ka-checkbox-group">
498 <label class="ka-checkbox-item">
499 <input type="checkbox" name="<?php echo esc_attr(self::OPTION_KEY); ?>[devices][]" value="desktop" <?php checked(in_array('desktop', (array) $settings['devices'], true)); ?> />
500 <?php esc_html_e('Desktop', 'king-addons'); ?>
501 </label>
502 <label class="ka-checkbox-item">
503 <input type="checkbox" name="<?php echo esc_attr(self::OPTION_KEY); ?>[devices][]" value="mobile" <?php checked(in_array('mobile', (array) $settings['devices'], true)); ?> />
504 <?php esc_html_e('Mobile', 'king-addons'); ?>
505 </label>
506 </div>
507 </div>
508 </div>
509 <div class="ka-grid-2">
510 <div class="ka-row">
511 <div class="ka-row-label"><?php esc_html_e('Trigger', 'king-addons'); ?></div>
512 <div class="ka-row-field">
513 <select name="<?php echo esc_attr(self::OPTION_KEY); ?>[trigger]">
514 <option value="always" <?php selected($settings['trigger'], 'always'); ?>><?php esc_html_e('Always visible', 'king-addons'); ?></option>
515 <option value="scroll" <?php selected($settings['trigger'], 'scroll'); ?>><?php esc_html_e('After scroll', 'king-addons'); ?></option>
516 <option value="delay" <?php selected($settings['trigger'], 'delay'); ?> <?php disabled(!$is_pro); ?>><?php esc_html_e('After delay (Pro)', 'king-addons'); ?></option>
517 </select>
518 </div>
519 </div>
520 <div class="ka-row">
521 <div class="ka-row-label"><?php esc_html_e('Scroll Offset', 'king-addons'); ?></div>
522 <div class="ka-row-field">
523 <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" />
524 <span style="color:#64748b;margin-left:6px">px</span>
525 </div>
526 </div>
527 </div>
528 </div>
529 </div>
530
531 <!-- Appearance -->
532 <?php
533 // Parse shadow settings
534 $shadow_x = isset($settings['shadow_x']) ? (int) $settings['shadow_x'] : 0;
535 $shadow_y = isset($settings['shadow_y']) ? (int) $settings['shadow_y'] : 8;
536 $shadow_blur = isset($settings['shadow_blur']) ? (int) $settings['shadow_blur'] : 24;
537 $shadow_spread = isset($settings['shadow_spread']) ? (int) $settings['shadow_spread'] : 0;
538 $shadow_color = isset($settings['shadow_color']) ? $settings['shadow_color'] : 'rgba(0,0,0,0.15)';
539 ?>
540 <div class="ka-card">
541 <div class="ka-card-header">
542 <span class="dashicons dashicons-art"></span>
543 <h2><?php esc_html_e('Appearance', 'king-addons'); ?></h2>
544 </div>
545 <div class="ka-card-body">
546 <div class="ka-grid-2">
547 <div class="ka-row">
548 <div class="ka-row-label"><?php esc_html_e('Background', 'king-addons'); ?></div>
549 <div class="ka-row-field">
550 <div class="ka-color-wrap">
551 <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" />
552 </div>
553 </div>
554 </div>
555 <div class="ka-row">
556 <div class="ka-row-label"><?php esc_html_e('Border Radius', 'king-addons'); ?></div>
557 <div class="ka-row-field">
558 <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" />
559 <span style="color:#64748b;margin-left:6px">px</span>
560 </div>
561 </div>
562 <div class="ka-row">
563 <div class="ka-row-label"><?php esc_html_e('Z-index', 'king-addons'); ?></div>
564 <div class="ka-row-field">
565 <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" />
566 </div>
567 </div>
568 <div class="ka-row">
569 <div class="ka-row-label"><?php esc_html_e('Item Shape', 'king-addons'); ?></div>
570 <div class="ka-row-field">
571 <select name="<?php echo esc_attr(self::OPTION_KEY); ?>[item_shape]">
572 <option value="circle" <?php selected($settings['item_shape'], 'circle'); ?>><?php esc_html_e('Circle', 'king-addons'); ?></option>
573 <option value="rounded" <?php selected($settings['item_shape'], 'rounded'); ?>><?php esc_html_e('Rounded', 'king-addons'); ?></option>
574 <option value="square" <?php selected($settings['item_shape'], 'square'); ?>><?php esc_html_e('Square', 'king-addons'); ?></option>
575 </select>
576 </div>
577 </div>
578 <div class="ka-row">
579 <div class="ka-row-label"><?php esc_html_e('Item Size', 'king-addons'); ?></div>
580 <div class="ka-row-field">
581 <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" />
582 <span style="color:#64748b;margin-left:6px">px</span>
583 </div>
584 </div>
585 <div class="ka-row">
586 <div class="ka-row-label"><?php esc_html_e('Item Spacing', 'king-addons'); ?></div>
587 <div class="ka-row-field">
588 <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" />
589 <span style="color:#64748b;margin-left:6px">px</span>
590 </div>
591 </div>
592 </div>
593 <!-- Shadow Builder -->
594 <div class="ka-row" style="flex-direction:column;gap:10px">
595 <div class="ka-row-label" style="padding-top:0"><?php esc_html_e('Box Shadow', 'king-addons'); ?></div>
596 <div class="ka-shadow-builder">
597 <div class="ka-shadow-field">
598 <label><?php esc_html_e('X', 'king-addons'); ?></label>
599 <input type="number" name="<?php echo esc_attr(self::OPTION_KEY); ?>[shadow_x]" value="<?php echo esc_attr($shadow_x); ?>" id="ka-shadow-x" />
600 </div>
601 <div class="ka-shadow-field">
602 <label><?php esc_html_e('Y', 'king-addons'); ?></label>
603 <input type="number" name="<?php echo esc_attr(self::OPTION_KEY); ?>[shadow_y]" value="<?php echo esc_attr($shadow_y); ?>" id="ka-shadow-y" />
604 </div>
605 <div class="ka-shadow-field">
606 <label><?php esc_html_e('Blur', 'king-addons'); ?></label>
607 <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" />
608 </div>
609 <div class="ka-shadow-field">
610 <label><?php esc_html_e('Spread', 'king-addons'); ?></label>
611 <input type="number" name="<?php echo esc_attr(self::OPTION_KEY); ?>[shadow_spread]" value="<?php echo esc_attr($shadow_spread); ?>" id="ka-shadow-spread" />
612 </div>
613 <div class="ka-shadow-field">
614 <label><?php esc_html_e('Color', 'king-addons'); ?></label>
615 <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" />
616 </div>
617 <div class="ka-shadow-preview" id="ka-shadow-preview"></div>
618 </div>
619 </div>
620 <!-- Keep legacy shadow field as hidden for backward compatibility -->
621 <input type="hidden" name="<?php echo esc_attr(self::OPTION_KEY); ?>[shadow]" id="ka-shadow-combined" value="<?php echo esc_attr($settings['shadow']); ?>" />
622 </div>
623 </div>
624 </div><!-- /General Tab -->
625
626 <!-- Buttons Tab -->
627 <div class="ka-tab-content" data-tab="buttons">
628 <div class="ka-card">
629 <div class="ka-card-header">
630 <span class="dashicons dashicons-admin-links"></span>
631 <h2><?php esc_html_e('Contact Buttons', 'king-addons'); ?></h2>
632 </div>
633 <div class="ka-card-body">
634 <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>
635 <div id="ka-scbar-items">
636 <?php
637 $items = is_array($settings['items']) ? $settings['items'] : [];
638 if (empty($items)) {
639 $items[] = [];
640 }
641 foreach ($items as $index => $item) {
642 $this->render_item_row($index, $item, $is_pro);
643 }
644 ?>
645 </div>
646 <button type="button" class="ka-scb-add-btn" id="ka-scbar-add-item">
647 <span class="dashicons dashicons-plus-alt2" style="margin-right:6px"></span>
648 <?php esc_html_e('Add Button', 'king-addons'); ?>
649 </button>
650 </div>
651 </div>
652 </div><!-- /Buttons Tab -->
653
654 <!-- Advanced Tab -->
655 <div class="ka-tab-content" data-tab="advanced">
656 <!-- Advanced Settings (Pro) -->
657 <div class="ka-card">
658 <div class="ka-card-header">
659 <span class="dashicons dashicons-admin-generic"></span>
660 <h2><?php esc_html_e('Advanced Settings', 'king-addons'); ?></h2>
661 <?php if (!$is_pro): ?><span class="ka-pro-badge">Pro</span><?php endif; ?>
662 </div>
663 <div class="ka-card-body">
664 <div class="ka-grid-2">
665 <div class="ka-row">
666 <div class="ka-row-label"><?php esc_html_e('Delay (ms)', 'king-addons'); ?></div>
667 <div class="ka-row-field">
668 <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); ?> />
669 <p class="ka-row-desc"><?php esc_html_e('Show bar after this delay when trigger is set to "After delay".', 'king-addons'); ?></p>
670 </div>
671 </div>
672 <div class="ka-row">
673 <div class="ka-row-label"><?php esc_html_e('Hide on Scroll Down', 'king-addons'); ?></div>
674 <div class="ka-row-field">
675 <label class="ka-toggle">
676 <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); ?> />
677 <span class="ka-toggle-slider"></span>
678 </label>
679 </div>
680 </div>
681 <div class="ka-row">
682 <div class="ka-row-label"><?php esc_html_e('Show on Scroll Up', '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); ?>[show_on_scroll_up]" value="1" <?php checked(!empty($settings['show_on_scroll_up'])); ?> <?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('Enable Analytics', '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); ?>[analytics_enabled]" value="1" <?php checked(!empty($settings['analytics_enabled'])); ?> <?php disabled(!$is_pro); ?> />
695 <span class="ka-toggle-slider"></span>
696 <span class="ka-toggle-label"><?php esc_html_e('Track clicks via dataLayer/gtag', 'king-addons'); ?></span>
697 </label>
698 </div>
699 </div>
700 </div>
701 </div>
702 </div>
703
704 <!-- Display Conditions (Pro) -->
705 <div class="ka-card">
706 <div class="ka-card-header">
707 <span class="dashicons dashicons-visibility"></span>
708 <h2><?php esc_html_e('Display Conditions', 'king-addons'); ?></h2>
709 <?php if (!$is_pro): ?><span class="ka-pro-badge">Pro</span><?php endif; ?>
710 </div>
711 <div class="ka-card-body">
712 <?php if (!$is_pro): ?>
713 <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>
714 <?php else: ?>
715 <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>
716 <div id="ka-scbar-conditions">
717 <?php
718 $conditions = is_array($settings['conditions'] ?? null) ? $settings['conditions'] : [];
719 if (empty($conditions)) {
720 echo '<p class="ka-row-desc">' . esc_html__('No conditions set. Bar will appear on all pages.', 'king-addons') . '</p>';
721 }
722 foreach ($conditions as $cond_index => $condition) {
723 $this->render_condition_row($cond_index, $condition);
724 }
725 ?>
726 </div>
727 <button type="button" class="ka-scb-add-btn" id="ka-scbar-add-condition" style="margin-top:12px">
728 <span class="dashicons dashicons-plus-alt2" style="margin-right:6px"></span>
729 <?php esc_html_e('Add Condition', 'king-addons'); ?>
730 </button>
731 <?php endif; ?>
732 </div>
733 </div>
734 </div><!-- /Advanced Tab -->
735
736 <!-- Save Button -->
737 <div class="ka-card ka-card-footer">
738 <button type="submit" class="ka-save-btn">
739 <span class="dashicons dashicons-saved"></span>
740 <?php esc_html_e('Save Settings', 'king-addons'); ?>
741 </button>
742 </div>
743 </form>
744 </div>
745
746 <script>
747 (function() {
748 const ajaxUrl = '<?php echo esc_url(admin_url('admin-ajax.php')); ?>';
749 const nonce = '<?php echo esc_js(wp_create_nonce('king_addons_dashboard_ui')); ?>';
750 const segment = document.getElementById('ka-v3-theme-segment');
751 const mql = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
752 let mode = (segment && segment.getAttribute('data-active') ? segment.getAttribute('data-active') : 'dark').toString();
753 let mqlHandler = null;
754
755 function saveUISetting(key, value) {
756 const body = new URLSearchParams();
757 body.set('action', 'king_addons_save_dashboard_ui');
758 body.set('nonce', nonce);
759 body.set('key', key);
760 body.set('value', value);
761 fetch(ajaxUrl, {
762 method: 'POST',
763 headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
764 body: body.toString()
765 });
766 }
767
768 function updateSegment(activeMode) {
769 if (!segment) {
770 return;
771 }
772 segment.setAttribute('data-active', activeMode);
773 segment.querySelectorAll('.ka-v3-segmented-btn').forEach((btn) => {
774 const theme = btn.getAttribute('data-theme');
775 btn.setAttribute('aria-pressed', theme === activeMode ? 'true' : 'false');
776 });
777 }
778
779 function applyTheme(isDark) {
780 document.body.classList.toggle('ka-v3-dark', isDark);
781 document.documentElement.classList.toggle('ka-v3-dark', isDark);
782 }
783
784 function setThemeMode(nextMode, save) {
785 mode = nextMode;
786 updateSegment(nextMode);
787
788 if (mqlHandler && mql) {
789 if (mql.removeEventListener) {
790 mql.removeEventListener('change', mqlHandler);
791 } else if (mql.removeListener) {
792 mql.removeListener(mqlHandler);
793 }
794 mqlHandler = null;
795 }
796
797 if (nextMode === 'auto') {
798 applyTheme(!!(mql && mql.matches));
799 mqlHandler = (e) => {
800 if (mode !== 'auto') {
801 return;
802 }
803 applyTheme(!!e.matches);
804 };
805 if (mql) {
806 if (mql.addEventListener) {
807 mql.addEventListener('change', mqlHandler);
808 } else if (mql.addListener) {
809 mql.addListener(mqlHandler);
810 }
811 }
812 } else {
813 applyTheme(nextMode === 'dark');
814 }
815
816 if (save) {
817 saveUISetting('theme_mode', nextMode);
818 }
819 }
820
821 window.kaV3ToggleDark = function() {
822 const isDark = document.body.classList.contains('ka-v3-dark');
823 setThemeMode(isDark ? 'light' : 'dark', true);
824 };
825
826 if (segment) {
827 segment.addEventListener('click', function(e) {
828 const btn = e.target.closest('.ka-v3-segmented-btn');
829 if (!btn) return;
830 e.preventDefault();
831 setThemeMode((btn.getAttribute('data-theme') || 'dark').toString(), true);
832 });
833 }
834
835 if (segment) {
836 setThemeMode(mode, false);
837 }
838 })();
839
840 // Tab navigation
841 (function() {
842 const tabs = document.querySelectorAll('.ka-tab');
843 const contents = document.querySelectorAll('.ka-tab-content');
844
845 tabs.forEach(tab => {
846 tab.addEventListener('click', function(e) {
847 e.preventDefault();
848 const target = this.dataset.tab;
849 tabs.forEach(t => t.classList.remove('active'));
850 contents.forEach(c => c.classList.remove('active'));
851 this.classList.add('active');
852 document.querySelector('.ka-tab-content[data-tab="' + target + '"]').classList.add('active');
853 });
854 });
855 })();
856 </script>
857
858 <template id="ka-scbar-item-template">
859 <?php $this->render_item_row('__i__', [], $is_pro); ?>
860 </template>
861 <?php if ($is_pro): ?>
862 <template id="ka-scbar-condition-template">
863 <?php $this->render_condition_row('__c__', []); ?>
864 </template>
865 <?php endif; ?>
866 <?php
867 // Enqueue color picker
868 wp_enqueue_style('wp-color-picker');
869 wp_enqueue_script('wp-color-picker');
870 ?>
871 <script>
872 (function($) {
873 'use strict';
874
875 /**
876 * Initialize color pickers in a given container.
877 *
878 * @param {HTMLElement} container The container to search for color pickers.
879 */
880 function initColorPickers(container) {
881 if (!$.fn.wpColorPicker) return;
882 $(container).find('.ka-color-picker').each(function() {
883 if ($(this).closest('.wp-picker-container').length) return;
884 $(this).wpColorPicker({
885 defaultColor: $(this).data('default-color') || '',
886 change: function() {
887 updateShadowPreview();
888 }
889 });
890 });
891 }
892
893 /**
894 * Update shadow preview box.
895 */
896 function updateShadowPreview() {
897 const x = $('#ka-shadow-x').val() || 0;
898 const y = $('#ka-shadow-y').val() || 0;
899 const blur = $('#ka-shadow-blur').val() || 0;
900 const spread = $('#ka-shadow-spread').val() || 0;
901 const colorInput = $('#ka-shadow-color');
902 let color = colorInput.val() || 'rgba(0,0,0,0.15)';
903
904 // Get color from wp-color-picker if initialized
905 if (colorInput.closest('.wp-picker-container').length) {
906 color = colorInput.wpColorPicker('color') || color;
907 }
908
909 const shadow = x + 'px ' + y + 'px ' + blur + 'px ' + spread + 'px ' + color;
910 $('#ka-shadow-preview').css('box-shadow', shadow);
911 $('#ka-shadow-combined').val(shadow);
912 }
913
914 $(document).ready(function() {
915 // Initialize color pickers on page load
916 initColorPickers(document);
917
918 // Shadow builder - update preview on any change
919 $('#ka-shadow-x, #ka-shadow-y, #ka-shadow-blur, #ka-shadow-spread').on('input change', updateShadowPreview);
920
921 // Initial shadow preview
922 setTimeout(updateShadowPreview, 500);
923
924 // Button items repeater
925 const container = document.getElementById('ka-scbar-items');
926 const addBtn = document.getElementById('ka-scbar-add-item');
927 const tpl = document.getElementById('ka-scbar-item-template');
928 if (container && addBtn && tpl) {
929 addBtn.addEventListener('click', function() {
930 const idx = container.querySelectorAll('.ka-scb-item').length;
931 const html = tpl.innerHTML.replace(/__i__/g, idx);
932 const wrap = document.createElement('div');
933 wrap.innerHTML = html;
934 const row = wrap.firstElementChild;
935 container.appendChild(row);
936 // Initialize color pickers in new row
937 initColorPickers(row);
938 });
939
940 container.addEventListener('click', function(e) {
941 const btn = e.target.closest('.ka-scb-remove-btn');
942 if (!btn) return;
943 e.preventDefault();
944 const row = btn.closest('.ka-scb-item');
945 if (row) row.remove();
946 });
947 }
948
949 // Conditions (Pro)
950 const condContainer = document.getElementById('ka-scbar-conditions');
951 const condAddBtn = document.getElementById('ka-scbar-add-condition');
952 const condTpl = document.getElementById('ka-scbar-condition-template');
953 if (condContainer && condAddBtn && condTpl) {
954 condAddBtn.addEventListener('click', function() {
955 const idx = condContainer.querySelectorAll('.ka-scb-condition').length;
956 const html = condTpl.innerHTML.replace(/__c__/g, idx);
957 const wrap = document.createElement('div');
958 wrap.innerHTML = html;
959 const row = wrap.firstElementChild;
960 condContainer.appendChild(row);
961 });
962
963 condContainer.addEventListener('click', function(e) {
964 const btn = e.target.closest('.ka-scb-remove-condition');
965 if (!btn) return;
966 e.preventDefault();
967 const row = btn.closest('.ka-scb-condition');
968 if (row) row.remove();
969 });
970 }
971 });
972 })(jQuery);
973 </script>
974 <?php
975 }
976
977 /**
978 * Render single item row for admin repeater.
979 *
980 * @param int|string $index Item index.
981 * @param array $item Item data.
982 * @param bool $is_pro Whether pro is active.
983 *
984 * @return void
985 */
986 protected function render_item_row($index, array $item, bool $is_pro): void
987 {
988 $prefix = self::OPTION_KEY . '[items][' . $index . ']';
989 $type = $item['type'] ?? 'phone';
990 $type_labels = [
991 'phone' => esc_html__('Phone', 'king-addons'),
992 'email' => esc_html__('Email', 'king-addons'),
993 'link' => esc_html__('URL', 'king-addons'),
994 'whatsapp' => esc_html__('WhatsApp', 'king-addons'),
995 'telegram' => esc_html__('Telegram', 'king-addons'),
996 ];
997 $label_text = $item['label'] ?? ($type_labels[$type] ?? ucfirst($type));
998 ?>
999 <div class="ka-scb-item">
1000 <div class="ka-scb-item-header">
1001 <span class="ka-scb-item-title"><?php echo esc_html($label_text); ?></span>
1002 <button type="button" class="ka-scb-remove-btn"><?php esc_html_e('Remove', 'king-addons'); ?></button>
1003 </div>
1004 <div class="ka-scb-item-grid">
1005 <div class="ka-scb-item-row">
1006 <span class="ka-scb-item-label"><?php esc_html_e('Type', 'king-addons'); ?></span>
1007 <select name="<?php echo esc_attr($prefix); ?>[type]">
1008 <option value="phone" <?php selected($type, 'phone'); ?>><?php esc_html_e('Phone', 'king-addons'); ?></option>
1009 <option value="email" <?php selected($type, 'email'); ?>><?php esc_html_e('Email', 'king-addons'); ?></option>
1010 <option value="link" <?php selected($type, 'link'); ?>><?php esc_html_e('Custom URL', 'king-addons'); ?></option>
1011 <option value="whatsapp" <?php selected($type, 'whatsapp'); ?>><?php esc_html_e('WhatsApp', 'king-addons'); ?></option>
1012 <option value="telegram" <?php selected($type, 'telegram'); ?>><?php esc_html_e('Telegram', 'king-addons'); ?></option>
1013 <option value="popup" <?php selected($type, 'popup'); ?> <?php disabled(!$is_pro); ?>><?php esc_html_e('Popup (Pro)', 'king-addons'); ?></option>
1014 <option value="messenger" <?php selected($type, 'messenger'); ?> <?php disabled(!$is_pro); ?>><?php esc_html_e('Messenger (Pro)', 'king-addons'); ?></option>
1015 <option value="viber" <?php selected($type, 'viber'); ?> <?php disabled(!$is_pro); ?>><?php esc_html_e('Viber (Pro)', 'king-addons'); ?></option>
1016 </select>
1017 </div>
1018 <div class="ka-scb-item-row">
1019 <span class="ka-scb-item-label"><?php esc_html_e('Label', 'king-addons'); ?></span>
1020 <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'); ?>" />
1021 </div>
1022 <div class="ka-scb-item-row">
1023 <span class="ka-scb-item-label"><?php esc_html_e('Icon Class', 'king-addons'); ?></span>
1024 <input type="text" name="<?php echo esc_attr($prefix); ?>[icon]" value="<?php echo esc_attr($item['icon'] ?? ''); ?>" placeholder="fa-brands fa-whatsapp" />
1025 </div>
1026 <div class="ka-scb-item-row">
1027 <span class="ka-scb-item-label"><?php esc_html_e('Phone', 'king-addons'); ?></span>
1028 <input type="text" name="<?php echo esc_attr($prefix); ?>[phone]" value="<?php echo esc_attr($item['phone'] ?? ''); ?>" placeholder="+123456789" />
1029 </div>
1030 <div class="ka-scb-item-row">
1031 <span class="ka-scb-item-label"><?php esc_html_e('Email', 'king-addons'); ?></span>
1032 <input type="email" name="<?php echo esc_attr($prefix); ?>[email]" value="<?php echo esc_attr($item['email'] ?? ''); ?>" />
1033 </div>
1034 <div class="ka-scb-item-row">
1035 <span class="ka-scb-item-label"><?php esc_html_e('URL', 'king-addons'); ?></span>
1036 <input type="text" name="<?php echo esc_attr($prefix); ?>[url]" value="<?php echo esc_attr($item['url'] ?? ''); ?>" placeholder="https://" />
1037 </div>
1038 <div class="ka-scb-item-row">
1039 <span class="ka-scb-item-label"><?php esc_html_e('Username', 'king-addons'); ?></span>
1040 <input type="text" name="<?php echo esc_attr($prefix); ?>[username]" value="<?php echo esc_attr($item['username'] ?? ''); ?>" placeholder="@username" />
1041 </div>
1042 <div class="ka-scb-item-row">
1043 <span class="ka-scb-item-label"><?php esc_html_e('Background', 'king-addons'); ?></span>
1044 <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" />
1045 </div>
1046 <div class="ka-scb-item-row">
1047 <span class="ka-scb-item-label"><?php esc_html_e('Hover BG', 'king-addons'); ?></span>
1048 <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" />
1049 </div>
1050 <div class="ka-scb-item-row">
1051 <span class="ka-scb-item-label"><?php esc_html_e('Icon Color', 'king-addons'); ?></span>
1052 <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" />
1053 </div>
1054 <div class="ka-scb-item-row">
1055 <span class="ka-scb-item-label"><?php esc_html_e('Target', 'king-addons'); ?></span>
1056 <select name="<?php echo esc_attr($prefix); ?>[target]">
1057 <option value="_self" <?php selected($item['target'] ?? '', '_self'); ?>><?php esc_html_e('Same Tab', 'king-addons'); ?></option>
1058 <option value="_blank" <?php selected($item['target'] ?? '', '_blank'); ?>><?php esc_html_e('New Tab', 'king-addons'); ?></option>
1059 </select>
1060 </div>
1061 <div class="ka-scb-item-row">
1062 <span class="ka-scb-item-label"><?php esc_html_e('Order', 'king-addons'); ?></span>
1063 <input type="number" name="<?php echo esc_attr($prefix); ?>[order]" value="<?php echo esc_attr($item['order'] ?? 0); ?>" style="max-width:80px" />
1064 </div>
1065 </div>
1066 <!-- Hidden fields for full data preservation -->
1067 <input type="hidden" name="<?php echo esc_attr($prefix); ?>[rel]" value="<?php echo esc_attr($item['rel'] ?? 'noopener noreferrer'); ?>" />
1068 <input type="hidden" name="<?php echo esc_attr($prefix); ?>[icon_color_hover]" value="<?php echo esc_attr($item['icon_color_hover'] ?? ''); ?>" />
1069 <input type="hidden" name="<?php echo esc_attr($prefix); ?>[popup_id]" value="<?php echo esc_attr($item['popup_id'] ?? ''); ?>" />
1070 <input type="hidden" name="<?php echo esc_attr($prefix); ?>[offcanvas_id]" value="<?php echo esc_attr($item['offcanvas_id'] ?? ''); ?>" />
1071 <input type="hidden" name="<?php echo esc_attr($prefix); ?>[scroll_target]" value="<?php echo esc_attr($item['scroll_target'] ?? ''); ?>" />
1072 <input type="hidden" name="<?php echo esc_attr($prefix); ?>[show_label]" value="<?php echo !empty($item['show_label']) ? '1' : ''; ?>" />
1073 <input type="hidden" name="<?php echo esc_attr($prefix); ?>[badge_text]" value="<?php echo esc_attr($item['badge_text'] ?? ''); ?>" />
1074 <input type="hidden" name="<?php echo esc_attr($prefix); ?>[badge_color]" value="<?php echo esc_attr($item['badge_color'] ?? ''); ?>" />
1075 <input type="hidden" name="<?php echo esc_attr($prefix); ?>[pulse]" value="<?php echo !empty($item['pulse']) ? '1' : ''; ?>" />
1076 <input type="hidden" name="<?php echo esc_attr($prefix); ?>[desktop_only]" value="<?php echo !empty($item['desktop_only']) ? '1' : ''; ?>" />
1077 <input type="hidden" name="<?php echo esc_attr($prefix); ?>[mobile_only]" value="<?php echo !empty($item['mobile_only']) ? '1' : ''; ?>" />
1078 </div>
1079 <?php
1080 }
1081
1082 /**
1083 * Render a single condition row for admin.
1084 *
1085 * @param int|string $index Row index.
1086 * @param array<string,string> $condition Condition data.
1087 *
1088 * @return void
1089 */
1090 protected function render_condition_row($index, array $condition): void
1091 {
1092 $prefix = self::OPTION_KEY . '[conditions][' . $index . ']';
1093 $type = $condition['type'] ?? 'page_type';
1094 $operator = $condition['operator'] ?? 'is';
1095 $value = $condition['value'] ?? '';
1096 ?>
1097 <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;">
1098 <select name="<?php echo esc_attr($prefix); ?>[type]" style="flex:1;padding:6px 10px;border:1px solid #e2e8f0;border-radius:6px;">
1099 <option value="page_type" <?php selected($type, 'page_type'); ?>><?php esc_html_e('Page Type', 'king-addons'); ?></option>
1100 <option value="user_role" <?php selected($type, 'user_role'); ?>><?php esc_html_e('User Role', 'king-addons'); ?></option>
1101 <option value="url_contains" <?php selected($type, 'url_contains'); ?>><?php esc_html_e('URL Contains', 'king-addons'); ?></option>
1102 <option value="post_id" <?php selected($type, 'post_id'); ?>><?php esc_html_e('Post ID', 'king-addons'); ?></option>
1103 </select>
1104 <select name="<?php echo esc_attr($prefix); ?>[operator]" style="width:100px;padding:6px 10px;border:1px solid #e2e8f0;border-radius:6px;">
1105 <option value="is" <?php selected($operator, 'is'); ?>><?php esc_html_e('Is', 'king-addons'); ?></option>
1106 <option value="is_not" <?php selected($operator, 'is_not'); ?>><?php esc_html_e('Is Not', 'king-addons'); ?></option>
1107 </select>
1108 <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;" />
1109 <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>
1110 </div>
1111 <?php
1112 }
1113
1114 /**
1115 * Render the bar on frontend.
1116 *
1117 * @return void
1118 */
1119 public function render_bar(): void
1120 {
1121 $settings = $this->get_settings();
1122
1123 if (empty($settings['enabled'])) {
1124 return;
1125 }
1126
1127 $settings = $this->apply_license_restrictions($settings);
1128
1129 if (!$this->check_display_conditions($settings)) {
1130 return;
1131 }
1132
1133 $items = is_array($settings['items']) ? $settings['items'] : [];
1134 if (empty($items)) {
1135 return;
1136 }
1137
1138 wp_enqueue_style(KING_ADDONS_ASSETS_UNIQUE_KEY . '-sticky-contact-bar');
1139 wp_enqueue_script(KING_ADDONS_ASSETS_UNIQUE_KEY . '-sticky-contact-bar');
1140
1141 echo $this->get_bar_html($settings, $items); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1142 }
1143
1144 /**
1145 * Check simple display conditions (devices).
1146 *
1147 * @param array<string,mixed> $settings Settings.
1148 *
1149 * @return bool
1150 */
1151 protected function check_display_conditions(array $settings): bool
1152 {
1153 $devices = isset($settings['devices']) && is_array($settings['devices']) ? $settings['devices'] : ['desktop', 'mobile'];
1154 $is_mobile = wp_is_mobile();
1155
1156 if (in_array('mobile', $devices, true) && $is_mobile) {
1157 return true;
1158 }
1159
1160 if (in_array('desktop', $devices, true) && !$is_mobile) {
1161 return true;
1162 }
1163
1164 return false;
1165 }
1166
1167 /**
1168 * Build bar HTML.
1169 *
1170 * @param array<string,mixed> $settings Settings.
1171 * @param array<int,array> $items Items.
1172 *
1173 * @return string
1174 */
1175 protected function get_bar_html(array $settings, array $items): string
1176 {
1177 $position = $settings['position'] ?? 'bottom';
1178 $alignment = $settings['alignment'] ?? 'center';
1179
1180 $classes = [
1181 'ka-sticky-contact-bar',
1182 'ka-position-' . esc_attr($position),
1183 'ka-align-' . esc_attr($alignment),
1184 'ka-shape-' . esc_attr($settings['item_shape'] ?? 'circle'),
1185 'is-hidden',
1186 ];
1187
1188 $style_parts = [];
1189 if (!empty($settings['background'])) {
1190 $style_parts[] = '--ka-scbar-bg:' . $settings['background'];
1191 }
1192 if (!empty($settings['border_radius'])) {
1193 $style_parts[] = '--ka-scbar-radius:' . absint($settings['border_radius']) . 'px';
1194 }
1195 if (!empty($settings['shadow'])) {
1196 $style_parts[] = '--ka-scbar-shadow:' . $settings['shadow'];
1197 }
1198 if (!empty($settings['item_size'])) {
1199 $style_parts[] = '--ka-scbar-size:' . absint($settings['item_size']) . 'px';
1200 }
1201 if (isset($settings['item_spacing'])) {
1202 $style_parts[] = '--ka-scbar-gap:' . absint($settings['item_spacing']) . 'px';
1203 }
1204 if (isset($settings['z_index'])) {
1205 $style_parts[] = '--ka-scbar-z:' . (int) $settings['z_index'];
1206 }
1207
1208 $data_attrs = [
1209 'data-trigger="' . esc_attr($settings['trigger'] ?? 'always') . '"',
1210 'data-trigger-scroll="' . esc_attr((int) ($settings['trigger_scroll'] ?? 0)) . '"',
1211 'data-trigger-delay="' . esc_attr((int) ($settings['trigger_delay'] ?? 0)) . '"',
1212 'data-hide-on-scroll-down="' . (!empty($settings['hide_on_scroll_down']) ? 'yes' : 'no') . '"',
1213 'data-show-on-scroll-up="' . (!empty($settings['show_on_scroll_up']) ? 'yes' : 'no') . '"',
1214 'data-analytics-enabled="' . (!empty($settings['analytics_enabled']) ? 'yes' : 'no') . '"',
1215 ];
1216
1217 usort(
1218 $items,
1219 static function ($a, $b) {
1220 return (int) ($a['order'] ?? 0) <=> (int) ($b['order'] ?? 0);
1221 }
1222 );
1223
1224 $items_html = '';
1225 foreach ($items as $item) {
1226 $item_html = $this->build_item_html($item);
1227 if ($item_html) {
1228 $items_html .= $item_html;
1229 }
1230 }
1231
1232 if ('' === $items_html) {
1233 return '';
1234 }
1235
1236 $style_attr = !empty($style_parts) ? ' style="' . esc_attr(implode(';', $style_parts)) . '"' : '';
1237
1238 return '<div class="' . esc_attr(implode(' ', $classes)) . '" ' . implode(' ', $data_attrs) . $style_attr . '><div class="ka-sticky-contact-bar__inner">' . $items_html . '</div></div>';
1239 }
1240
1241 /**
1242 * Build single item HTML.
1243 *
1244 * @param array<string,mixed> $item Item data.
1245 *
1246 * @return string
1247 */
1248 protected function build_item_html(array $item): string
1249 {
1250 $type = $item['type'] ?? 'phone';
1251 $free_types = ['phone', 'email', 'link', 'whatsapp', 'telegram'];
1252 $is_pro = $this->can_use_pro();
1253
1254 if (!$is_pro && !in_array($type, $free_types, true)) {
1255 return '';
1256 }
1257
1258 $href = $this->get_item_href($type, $item);
1259 if ('' === $href) {
1260 return '';
1261 }
1262
1263 $classes = ['ka-sticky-contact-item', 'ka-type-' . esc_attr($type)];
1264 if (!empty($item['pulse']) && $is_pro) {
1265 $classes[] = 'ka-sticky-contact-item--pulse';
1266 }
1267 if (!empty($item['show_label']) && $is_pro) {
1268 $classes[] = 'ka-sticky-contact-item--label-visible';
1269 }
1270 if (!empty($item['desktop_only'])) {
1271 $classes[] = 'ka-desktop-only';
1272 }
1273 if (!empty($item['mobile_only'])) {
1274 $classes[] = 'ka-mobile-only';
1275 }
1276
1277 $style_parts = [];
1278 if (!empty($item['color'])) {
1279 $style_parts[] = '--ka-scbar-item-bg:' . $item['color'];
1280 }
1281 if (!empty($item['color_hover'])) {
1282 $style_parts[] = '--ka-scbar-item-bg-hover:' . $item['color_hover'];
1283 }
1284 if (!empty($item['icon_color'])) {
1285 $style_parts[] = '--ka-scbar-icon-color:' . $item['icon_color'];
1286 }
1287 if (!empty($item['icon_color_hover'])) {
1288 $style_parts[] = '--ka-scbar-icon-color-hover:' . $item['icon_color_hover'];
1289 }
1290
1291 $aria = !empty($item['label']) ? $item['label'] : ucfirst($type);
1292
1293 $data = [];
1294 if (in_array($type, ['popup', 'offcanvas', 'scroll'], true)) {
1295 $data[] = 'data-action="' . esc_attr($type) . '"';
1296 }
1297 if ('popup' === $type && !empty($item['popup_id'])) {
1298 $data[] = 'data-popup-id="' . esc_attr($item['popup_id']) . '"';
1299 }
1300 if ('offcanvas' === $type && !empty($item['offcanvas_id'])) {
1301 $data[] = 'data-offcanvas-id="' . esc_attr($item['offcanvas_id']) . '"';
1302 }
1303 if ('scroll' === $type && !empty($item['scroll_target'])) {
1304 $data[] = 'data-scroll-target="' . esc_attr($item['scroll_target']) . '"';
1305 }
1306
1307 $label_html = '';
1308 if (!empty($item['show_label']) && $is_pro && !empty($item['label'])) {
1309 $label_html = '<span class="ka-sticky-contact-item__label">' . esc_html($item['label']) . '</span>';
1310 }
1311
1312 $badge_html = '';
1313 if (!empty($item['badge_text']) && $is_pro) {
1314 $badge_style = '';
1315 if (!empty($item['badge_color'])) {
1316 $badge_style = ' style="background:' . esc_attr($item['badge_color']) . ';"';
1317 }
1318 $badge_html = '<span class="ka-sticky-contact-item__badge"' . $badge_style . '>' . esc_html($item['badge_text']) . '</span>';
1319 }
1320
1321 $icon_html = '';
1322 if (!empty($item['icon'])) {
1323 $icon_html = '<span class="ka-sticky-contact-icon"><i class="' . esc_attr($item['icon']) . '" aria-hidden="true"></i></span>';
1324 } else {
1325 // Use default SVG icon based on type
1326 $default_svg = $this->get_default_icon_svg($type);
1327 if ($default_svg) {
1328 $icon_html = '<span class="ka-sticky-contact-icon">' . $default_svg . '</span>';
1329 }
1330 }
1331
1332 $style_attr = !empty($style_parts) ? ' style="' . esc_attr(implode(';', $style_parts)) . '"' : '';
1333
1334 $target = !empty($item['target']) ? $item['target'] : '_self';
1335 $rel = !empty($item['rel']) ? $item['rel'] : 'noopener noreferrer';
1336
1337 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>';
1338 }
1339
1340 /**
1341 * Get href per item type.
1342 *
1343 * @param string $type Item type.
1344 * @param array<string,mixed> $item Item data.
1345 *
1346 * @return string
1347 */
1348 protected function get_item_href(string $type, array $item): string
1349 {
1350 switch ($type) {
1351 case 'phone':
1352 return !empty($item['phone']) ? 'tel:' . preg_replace('/\s+/', '', $item['phone']) : '';
1353 case 'email':
1354 return !empty($item['email']) ? 'mailto:' . sanitize_email($item['email']) : '';
1355 case 'whatsapp':
1356 if (!empty($item['url'])) {
1357 return esc_url_raw($item['url']);
1358 }
1359 if (!empty($item['phone'])) {
1360 $num = preg_replace('/\D+/', '', $item['phone']);
1361 return $num ? 'https://wa.me/' . $num : '';
1362 }
1363 return '';
1364 case 'telegram':
1365 if (!empty($item['url'])) {
1366 return esc_url_raw($item['url']);
1367 }
1368 if (!empty($item['username'])) {
1369 return 'https://t.me/' . ltrim($item['username'], '@');
1370 }
1371 return '';
1372 case 'link':
1373 return !empty($item['url']) ? esc_url_raw($item['url']) : '';
1374 case 'messenger':
1375 if (!empty($item['url'])) {
1376 return esc_url_raw($item['url']);
1377 }
1378 if (!empty($item['username'])) {
1379 return 'https://m.me/' . ltrim($item['username'], '@');
1380 }
1381 return '';
1382 case 'viber':
1383 if (!empty($item['url'])) {
1384 return esc_url_raw($item['url']);
1385 }
1386 if (!empty($item['phone'])) {
1387 $num = preg_replace('/\D+/', '', $item['phone']);
1388 return $num ? 'viber://chat?number=' . $num : '';
1389 }
1390 return '';
1391 case 'popup':
1392 case 'offcanvas':
1393 case 'scroll':
1394 return 'javascript:void(0)';
1395 default:
1396 return '';
1397 }
1398 }
1399
1400 /**
1401 * Get default SVG icon for a button type.
1402 *
1403 * @param string $type Button type.
1404 *
1405 * @return string SVG markup or empty string.
1406 */
1407 protected function get_default_icon_svg(string $type): string
1408 {
1409 $icons = [
1410 '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>',
1411 '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>',
1412 '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>',
1413 '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>',
1414 '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>',
1415 '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>',
1416 '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>',
1417 '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>',
1418 '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>',
1419 '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>',
1420 ];
1421
1422 return $icons[$type] ?? '';
1423 }
1424
1425 /**
1426 * Get settings merged with defaults.
1427 *
1428 * @return array<string,mixed>
1429 */
1430 public function get_settings(): array
1431 {
1432 $saved = get_option(self::OPTION_KEY, []);
1433 if (!is_array($saved)) {
1434 $saved = [];
1435 }
1436 return wp_parse_args($saved, $this->get_settings_defaults());
1437 }
1438
1439 /**
1440 * Default settings.
1441 *
1442 * @return array<string,mixed>
1443 */
1444 protected function get_settings_defaults(): array
1445 {
1446 return [
1447 'enabled' => false,
1448 'position' => 'bottom',
1449 'alignment' => 'center',
1450 'devices' => ['mobile', 'desktop'],
1451 'trigger' => 'always',
1452 'trigger_scroll' => 100,
1453 'trigger_delay' => 0,
1454 'z_index' => 9999,
1455 'background' => 'rgba(20,20,20,0.95)',
1456 'border_radius' => 14,
1457 'shadow' => '0px 10px 30px 0px rgba(0,0,0,0.18)',
1458 'shadow_x' => 0,
1459 'shadow_y' => 10,
1460 'shadow_blur' => 30,
1461 'shadow_spread' => 0,
1462 'shadow_color' => 'rgba(0,0,0,0.18)',
1463 'item_size' => 48,
1464 'item_spacing' => 10,
1465 'item_shape' => 'circle',
1466 'hide_on_scroll_down' => false,
1467 'show_on_scroll_up' => false,
1468 'analytics_enabled' => false,
1469 'items' => [],
1470 ];
1471 }
1472
1473 /**
1474 * Sanitize settings.
1475 *
1476 * @param array<string,mixed> $input Raw input.
1477 *
1478 * @return array<string,mixed>
1479 */
1480 public function sanitize_settings($input): array
1481 {
1482 $defaults = $this->get_settings_defaults();
1483
1484 // Shadow fields - construct combined shadow string from components
1485 $shadow_x = isset($input['shadow_x']) ? (int) $input['shadow_x'] : $defaults['shadow_x'];
1486 $shadow_y = isset($input['shadow_y']) ? (int) $input['shadow_y'] : $defaults['shadow_y'];
1487 $shadow_blur = isset($input['shadow_blur']) ? absint($input['shadow_blur']) : $defaults['shadow_blur'];
1488 $shadow_spread = isset($input['shadow_spread']) ? (int) $input['shadow_spread'] : $defaults['shadow_spread'];
1489 $shadow_color = sanitize_text_field($input['shadow_color'] ?? $defaults['shadow_color']);
1490 $shadow_combined = $shadow_x . 'px ' . $shadow_y . 'px ' . $shadow_blur . 'px ' . $shadow_spread . 'px ' . $shadow_color;
1491
1492 $output = [
1493 'enabled' => !empty($input['enabled']),
1494 'position' => in_array($input['position'] ?? 'bottom', ['bottom', 'left', 'right'], true) ? $input['position'] : 'bottom',
1495 'alignment' => in_array($input['alignment'] ?? 'center', ['center', 'left', 'right'], true) ? $input['alignment'] : 'center',
1496 'devices' => [],
1497 'trigger' => in_array($input['trigger'] ?? 'always', ['always', 'scroll', 'delay'], true) ? $input['trigger'] : 'always',
1498 'trigger_scroll' => isset($input['trigger_scroll']) ? absint($input['trigger_scroll']) : $defaults['trigger_scroll'],
1499 'trigger_delay' => isset($input['trigger_delay']) ? absint($input['trigger_delay']) : $defaults['trigger_delay'],
1500 'z_index' => isset($input['z_index']) ? (int) $input['z_index'] : $defaults['z_index'],
1501 'background' => sanitize_text_field($input['background'] ?? $defaults['background']),
1502 'border_radius' => isset($input['border_radius']) ? absint($input['border_radius']) : $defaults['border_radius'],
1503 'shadow' => $shadow_combined,
1504 'shadow_x' => $shadow_x,
1505 'shadow_y' => $shadow_y,
1506 'shadow_blur' => $shadow_blur,
1507 'shadow_spread' => $shadow_spread,
1508 'shadow_color' => $shadow_color,
1509 'item_size' => isset($input['item_size']) ? absint($input['item_size']) : $defaults['item_size'],
1510 'item_spacing' => isset($input['item_spacing']) ? absint($input['item_spacing']) : $defaults['item_spacing'],
1511 'item_shape' => in_array($input['item_shape'] ?? 'circle', ['circle', 'rounded', 'square'], true) ? $input['item_shape'] : 'circle',
1512 'hide_on_scroll_down' => !empty($input['hide_on_scroll_down']),
1513 'show_on_scroll_up' => !empty($input['show_on_scroll_up']),
1514 'analytics_enabled' => !empty($input['analytics_enabled']),
1515 'items' => [],
1516 ];
1517
1518 if (!empty($input['devices']) && is_array($input['devices'])) {
1519 foreach ($input['devices'] as $device) {
1520 if (in_array($device, ['desktop', 'mobile'], true)) {
1521 $output['devices'][] = $device;
1522 }
1523 }
1524 }
1525 if (empty($output['devices'])) {
1526 $output['devices'] = ['mobile', 'desktop'];
1527 }
1528
1529 if (!empty($input['items']) && is_array($input['items'])) {
1530 foreach ($input['items'] as $item) {
1531 $output['items'][] = [
1532 'type' => sanitize_text_field($item['type'] ?? 'phone'),
1533 'label' => sanitize_text_field($item['label'] ?? ''),
1534 'icon' => sanitize_text_field($item['icon'] ?? ''),
1535 'color' => sanitize_text_field($item['color'] ?? ''),
1536 'color_hover' => sanitize_text_field($item['color_hover'] ?? ''),
1537 'icon_color' => sanitize_text_field($item['icon_color'] ?? ''),
1538 'icon_color_hover' => sanitize_text_field($item['icon_color_hover'] ?? ''),
1539 'url' => esc_url_raw($item['url'] ?? ''),
1540 'phone' => sanitize_text_field($item['phone'] ?? ''),
1541 'username' => sanitize_text_field($item['username'] ?? ''),
1542 'email' => sanitize_email($item['email'] ?? ''),
1543 'popup_id' => sanitize_text_field($item['popup_id'] ?? ''),
1544 'offcanvas_id' => sanitize_text_field($item['offcanvas_id'] ?? ''),
1545 'scroll_target' => sanitize_text_field($item['scroll_target'] ?? ''),
1546 'target' => in_array($item['target'] ?? '_self', ['_self', '_blank'], true) ? $item['target'] : '_self',
1547 'rel' => sanitize_text_field($item['rel'] ?? 'noopener noreferrer'),
1548 'show_label' => !empty($item['show_label']),
1549 'badge_text' => sanitize_text_field($item['badge_text'] ?? ''),
1550 'badge_color' => sanitize_text_field($item['badge_color'] ?? ''),
1551 'pulse' => !empty($item['pulse']),
1552 'desktop_only' => !empty($item['desktop_only']),
1553 'mobile_only' => !empty($item['mobile_only']),
1554 'order' => isset($item['order']) ? (int) $item['order'] : 0,
1555 ];
1556 }
1557 }
1558
1559 return $output;
1560 }
1561
1562 /**
1563 * Apply license restrictions to settings (remove Pro-only bits).
1564 *
1565 * @param array<string,mixed> $settings Settings.
1566 *
1567 * @return array<string,mixed>
1568 */
1569 protected function apply_license_restrictions(array $settings): array
1570 {
1571 if ($this->can_use_pro()) {
1572 return $settings;
1573 }
1574
1575 // Free limitations.
1576 $settings['position'] = 'bottom';
1577 if ('delay' === ($settings['trigger'] ?? 'always')) {
1578 $settings['trigger'] = 'always';
1579 $settings['trigger_delay'] = 0;
1580 }
1581 $settings['hide_on_scroll_down'] = false;
1582 $settings['show_on_scroll_up'] = false;
1583
1584 $settings['items'] = array_values(
1585 array_filter(
1586 $settings['items'],
1587 static function ($item) {
1588 return in_array($item['type'] ?? 'phone', ['phone', 'email', 'link', 'whatsapp', 'telegram'], true);
1589 }
1590 )
1591 );
1592
1593 foreach ($settings['items'] as &$item) {
1594 $item['pulse'] = false;
1595 $item['badge_text'] = '';
1596 $item['badge_color'] = '';
1597 $item['show_label'] = false;
1598 }
1599 unset($item);
1600
1601 return $settings;
1602 }
1603
1604 /**
1605 * Check if Pro is available.
1606 *
1607 * @return bool
1608 */
1609 protected function can_use_pro(): bool
1610 {
1611 if (!function_exists('king_addons_freemius')) {
1612 return false;
1613 }
1614
1615 $fs = king_addons_freemius();
1616 if (!is_object($fs) || !method_exists($fs, 'can_use_premium_code')) {
1617 return false;
1618 }
1619
1620 return (bool) $fs->can_use_premium_code();
1621 }
1622 }
1623
1624
1625
1626
1627
1628
1629
1630