PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.65
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.65
51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / features / Conditional_Display / Conditional_Display.php

Conditional_Display.php in King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder 51.1.65, at includes/features/Conditional_Display/Conditional_Display.php

1,539 lines 47.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Conditional Display feature.
4 *
5 * Dynamically show or hide Elementor elements based on various conditions
6 * including user roles, dates, devices, URL parameters, WooCommerce conditions, and more.
7 *
8 * @package King_Addons
9 */
10
11 namespace King_Addons;
12
13 use Elementor\Controls_Manager;
14 use Elementor\Element_Base;
15 use Elementor\Plugin;
16
17 if (!defined('ABSPATH')) {
18 exit;
19 }
20
21 /**
22 * Conditional Display class.
23 *
24 * Provides controls and runtime logic to conditionally show/hide Elementor elements.
25 */
26 class Conditional_Display
27 {
28 /**
29 * Cached Elementor templates list.
30 *
31 * @var array<int|string,string>|null
32 */
33 private static ?array $templates_cache = null;
34
35 /**
36 * Cached WP roles list.
37 *
38 * @var array<string,string>|null
39 */
40 private static ?array $roles_cache = null;
41
42 /**
43 * Constructor.
44 */
45 public function __construct()
46 {
47 // Editor assets.
48 add_action('elementor/preview/enqueue_scripts', [$this, 'enqueue_preview_script'], 1);
49 add_action('elementor/preview/enqueue_styles', [$this, 'enqueue_styles'], 1);
50 add_action('elementor/frontend/after_enqueue_styles', [$this, 'enqueue_styles'], 1);
51
52 // Controls registration.
53 add_action('elementor/element/common/_section_style/after_section_end', [$this, 'register_controls'], 10, 2);
54 add_action('elementor/element/section/section_advanced/after_section_end', [$this, 'register_controls'], 10, 2);
55 add_action('elementor/element/container/section_layout/after_section_end', [$this, 'register_controls'], 10, 2);
56 add_action('elementor/element/column/section_advanced/after_section_end', [$this, 'register_controls'], 10, 2);
57
58 // Runtime filters for proper element hiding.
59 add_filter('elementor/frontend/widget/should_render', [$this, 'should_render_element'], 10, 2);
60 add_filter('elementor/frontend/section/should_render', [$this, 'should_render_element'], 10, 2);
61 add_filter('elementor/frontend/container/should_render', [$this, 'should_render_element'], 10, 2);
62 add_filter('elementor/frontend/column/should_render', [$this, 'should_render_element'], 10, 2);
63
64 // Before render for editor marking and fallback content.
65 add_action('elementor/frontend/widget/before_render', [$this, 'before_render_element']);
66 add_action('elementor/frontend/section/before_render', [$this, 'before_render_element']);
67 add_action('elementor/frontend/container/before_render', [$this, 'before_render_element']);
68 add_action('elementor/frontend/column/before_render', [$this, 'before_render_element']);
69 }
70
71 /**
72 * Enqueue preview script for Elementor editor.
73 *
74 * @return void
75 */
76 public function enqueue_preview_script(): void
77 {
78 wp_enqueue_script(
79 KING_ADDONS_ASSETS_UNIQUE_KEY . '-conditional-display-preview-handler',
80 KING_ADDONS_URL . 'includes/features/Conditional_Display/preview-handler.js',
81 ['jquery'],
82 KING_ADDONS_VERSION,
83 true
84 );
85 }
86
87 /**
88 * Enqueue styles for editor preview and frontend.
89 *
90 * @return void
91 */
92 public function enqueue_styles(): void
93 {
94 wp_enqueue_style(
95 KING_ADDONS_ASSETS_UNIQUE_KEY . '-conditional-display-style',
96 KING_ADDONS_URL . 'includes/features/Conditional_Display/style.css',
97 [],
98 KING_ADDONS_VERSION
99 );
100 }
101
102 /**
103 * Register Conditional Display controls.
104 *
105 * @param Element_Base $element Elementor element.
106 * @param array<mixed> $args Hook arguments.
107 *
108 * @return void
109 */
110 public function register_controls(Element_Base $element, $args): void
111 {
112 $element->start_controls_section(
113 'king_addons_cd_section',
114 [
115 'label' => KING_ADDONS_ELEMENTOR_ICON . esc_html__('Conditional Display', 'king-addons'),
116 'tab' => Controls_Manager::TAB_ADVANCED,
117 ]
118 );
119
120 // Main enable switch.
121 $element->add_control(
122 'cd_enable',
123 [
124 'label' => esc_html__('Enable Conditional Display', 'king-addons'),
125 'type' => Controls_Manager::SWITCHER,
126 'label_on' => esc_html__('Yes', 'king-addons'),
127 'label_off' => esc_html__('No', 'king-addons'),
128 'return_value' => 'yes',
129 'default' => '',
130 ]
131 );
132
133 // Conditions relation (AND/OR).
134 $element->add_control(
135 'cd_relation',
136 [
137 'label' => esc_html__('Conditions Relation', 'king-addons'),
138 'type' => Controls_Manager::SELECT,
139 'options' => [
140 'all' => esc_html__('Match all conditions (AND)', 'king-addons'),
141 'any' => esc_html__('Match any condition (OR)', 'king-addons'),
142 ],
143 'default' => 'all',
144 'condition' => [
145 'cd_enable' => 'yes',
146 ],
147 ]
148 );
149
150 // Invert result switch.
151 $element->add_control(
152 'cd_invert',
153 [
154 'label' => esc_html__('Invert Result', 'king-addons'),
155 'type' => Controls_Manager::SWITCHER,
156 'description' => esc_html__('If enabled, element will show when conditions are NOT met.', 'king-addons'),
157 'condition' => [
158 'cd_enable' => 'yes',
159 ],
160 ]
161 );
162
163 // Fallback type selection.
164 $element->add_control(
165 'cd_fallback_type',
166 [
167 'label' => esc_html__('When conditions are not met', 'king-addons'),
168 'type' => Controls_Manager::SELECT,
169 'options' => [
170 'hide' => esc_html__('Hide element', 'king-addons'),
171 'message' => esc_html__('Show message', 'king-addons'),
172 'template' => sprintf(__('Render template %s', 'king-addons'), '<i class="eicon-pro-icon"></i>'),
173 ],
174 'default' => 'hide',
175 'condition' => [
176 'cd_enable' => 'yes',
177 ],
178 ]
179 );
180
181 // Fallback message textarea.
182 $element->add_control(
183 'cd_fallback_msg',
184 [
185 'label' => esc_html__('Fallback Message', 'king-addons'),
186 'type' => Controls_Manager::TEXTAREA,
187 'rows' => 3,
188 'default' => '',
189 'condition' => [
190 'cd_enable' => 'yes',
191 'cd_fallback_type' => 'message',
192 ],
193 ]
194 );
195
196 // Fallback template selector (Pro).
197 $element->add_control(
198 'cd_fallback_template',
199 [
200 'label' => esc_html__('Template', 'king-addons'),
201 'type' => Controls_Manager::SELECT2,
202 'options' => $this->get_elementor_templates_options(),
203 'label_block' => true,
204 'condition' => [
205 'cd_enable' => 'yes',
206 'cd_fallback_type' => 'template',
207 ],
208 ]
209 );
210
211 // ========== USER CONDITIONS ==========
212 $element->add_control(
213 'cd_heading_user',
214 [
215 'label' => esc_html__('User Conditions', 'king-addons'),
216 'type' => Controls_Manager::HEADING,
217 'separator' => 'before',
218 'condition' => [
219 'cd_enable' => 'yes',
220 ],
221 ]
222 );
223
224 // Login status.
225 $element->add_control(
226 'cd_cond_login_mode',
227 [
228 'label' => esc_html__('Login Status', 'king-addons'),
229 'type' => Controls_Manager::SELECT,
230 'options' => [
231 'any' => esc_html__('Any (ignore)', 'king-addons'),
232 'logged_in' => esc_html__('Logged in only', 'king-addons'),
233 'logged_out' => esc_html__('Logged out only', 'king-addons'),
234 ],
235 'default' => 'any',
236 'condition' => [
237 'cd_enable' => 'yes',
238 ],
239 ]
240 );
241
242 // User roles multi-select.
243 $element->add_control(
244 'cd_cond_roles',
245 [
246 'label' => esc_html__('Allowed Roles', 'king-addons'),
247 'type' => Controls_Manager::SELECT2,
248 'multiple' => true,
249 'options' => $this->get_wp_roles_for_control(),
250 'label_block' => true,
251 'condition' => [
252 'cd_enable' => 'yes',
253 'cd_cond_login_mode' => 'logged_in',
254 ],
255 ]
256 );
257
258 // ========== DEVICE CONDITIONS ==========
259 $element->add_control(
260 'cd_heading_device',
261 [
262 'label' => esc_html__('Device Conditions', 'king-addons'),
263 'type' => Controls_Manager::HEADING,
264 'separator' => 'before',
265 'condition' => [
266 'cd_enable' => 'yes',
267 ],
268 ]
269 );
270
271 $element->add_control(
272 'cd_cond_device_enable',
273 [
274 'label' => esc_html__('Device Condition', 'king-addons'),
275 'type' => Controls_Manager::SWITCHER,
276 'return_value' => 'yes',
277 'condition' => [
278 'cd_enable' => 'yes',
279 ],
280 ]
281 );
282
283 $element->add_control(
284 'cd_cond_device_types',
285 [
286 'label' => esc_html__('Show on Devices', 'king-addons'),
287 'type' => Controls_Manager::SELECT2,
288 'multiple' => true,
289 'options' => [
290 'desktop' => esc_html__('Desktop', 'king-addons'),
291 'tablet' => esc_html__('Tablet', 'king-addons'),
292 'mobile' => esc_html__('Mobile', 'king-addons'),
293 ],
294 'label_block' => true,
295 'condition' => [
296 'cd_enable' => 'yes',
297 'cd_cond_device_enable' => 'yes',
298 ],
299 ]
300 );
301
302 // Browser condition (Pro).
303 $element->add_control(
304 'cd_cond_browser_enable',
305 [
306 'label' => sprintf(__('Browser Condition %s', 'king-addons'), '<i class="eicon-pro-icon"></i>'),
307 'type' => Controls_Manager::SWITCHER,
308 'return_value' => 'yes',
309 'condition' => [
310 'cd_enable' => 'yes',
311 ],
312 ]
313 );
314
315 $element->add_control(
316 'cd_cond_browser_types',
317 [
318 'label' => esc_html__('Show on Browsers', 'king-addons'),
319 'type' => Controls_Manager::SELECT2,
320 'multiple' => true,
321 'options' => [
322 'chrome' => esc_html__('Chrome', 'king-addons'),
323 'firefox' => esc_html__('Firefox', 'king-addons'),
324 'safari' => esc_html__('Safari', 'king-addons'),
325 'edge' => esc_html__('Edge', 'king-addons'),
326 'opera' => esc_html__('Opera', 'king-addons'),
327 'ie' => esc_html__('Internet Explorer', 'king-addons'),
328 ],
329 'label_block' => true,
330 'condition' => [
331 'cd_enable' => 'yes',
332 'cd_cond_browser_enable' => 'yes',
333 ],
334 ]
335 );
336
337 // ========== DATE / TIME CONDITIONS ==========
338 $element->add_control(
339 'cd_heading_datetime',
340 [
341 'label' => esc_html__('Date / Time Conditions', 'king-addons'),
342 'type' => Controls_Manager::HEADING,
343 'separator' => 'before',
344 'condition' => [
345 'cd_enable' => 'yes',
346 ],
347 ]
348 );
349
350 $element->add_control(
351 'cd_cond_date_enable',
352 [
353 'label' => esc_html__('Date Range Condition', 'king-addons'),
354 'type' => Controls_Manager::SWITCHER,
355 'return_value' => 'yes',
356 'condition' => [
357 'cd_enable' => 'yes',
358 ],
359 ]
360 );
361
362 $element->add_control(
363 'cd_cond_date_from',
364 [
365 'label' => esc_html__('Date From', 'king-addons'),
366 'type' => Controls_Manager::DATE_TIME,
367 'label_block' => true,
368 'condition' => [
369 'cd_enable' => 'yes',
370 'cd_cond_date_enable' => 'yes',
371 ],
372 ]
373 );
374
375 $element->add_control(
376 'cd_cond_date_to',
377 [
378 'label' => esc_html__('Date To', 'king-addons'),
379 'type' => Controls_Manager::DATE_TIME,
380 'label_block' => true,
381 'condition' => [
382 'cd_enable' => 'yes',
383 'cd_cond_date_enable' => 'yes',
384 ],
385 ]
386 );
387
388 $element->add_control(
389 'cd_cond_days_of_week',
390 [
391 'label' => esc_html__('Days of Week', 'king-addons'),
392 'type' => Controls_Manager::SELECT2,
393 'multiple' => true,
394 'options' => [
395 'mon' => esc_html__('Monday', 'king-addons'),
396 'tue' => esc_html__('Tuesday', 'king-addons'),
397 'wed' => esc_html__('Wednesday', 'king-addons'),
398 'thu' => esc_html__('Thursday', 'king-addons'),
399 'fri' => esc_html__('Friday', 'king-addons'),
400 'sat' => esc_html__('Saturday', 'king-addons'),
401 'sun' => esc_html__('Sunday', 'king-addons'),
402 ],
403 'label_block' => true,
404 'condition' => [
405 'cd_enable' => 'yes',
406 'cd_cond_date_enable' => 'yes',
407 ],
408 ]
409 );
410
411 // Time of day condition.
412 $element->add_control(
413 'cd_cond_time_enable',
414 [
415 'label' => esc_html__('Time of Day Condition', 'king-addons'),
416 'type' => Controls_Manager::SWITCHER,
417 'return_value' => 'yes',
418 'condition' => [
419 'cd_enable' => 'yes',
420 ],
421 ]
422 );
423
424 $element->add_control(
425 'cd_cond_time_from',
426 [
427 'label' => esc_html__('Time From (HH:MM)', 'king-addons'),
428 'type' => Controls_Manager::TEXT,
429 'placeholder' => '09:00',
430 'condition' => [
431 'cd_enable' => 'yes',
432 'cd_cond_time_enable' => 'yes',
433 ],
434 ]
435 );
436
437 $element->add_control(
438 'cd_cond_time_to',
439 [
440 'label' => esc_html__('Time To (HH:MM)', 'king-addons'),
441 'type' => Controls_Manager::TEXT,
442 'placeholder' => '18:00',
443 'condition' => [
444 'cd_enable' => 'yes',
445 'cd_cond_time_enable' => 'yes',
446 ],
447 ]
448 );
449
450 // ========== URL / QUERY CONDITIONS ==========
451 $element->add_control(
452 'cd_heading_url',
453 [
454 'label' => esc_html__('URL / Query Conditions', 'king-addons'),
455 'type' => Controls_Manager::HEADING,
456 'separator' => 'before',
457 'condition' => [
458 'cd_enable' => 'yes',
459 ],
460 ]
461 );
462
463 $element->add_control(
464 'cd_cond_url_enable',
465 [
466 'label' => esc_html__('URL Condition', 'king-addons'),
467 'type' => Controls_Manager::SWITCHER,
468 'return_value' => 'yes',
469 'condition' => [
470 'cd_enable' => 'yes',
471 ],
472 ]
473 );
474
475 $element->add_control(
476 'cd_cond_url_contains',
477 [
478 'label' => esc_html__('URL Contains', 'king-addons'),
479 'type' => Controls_Manager::TEXT,
480 'label_block' => true,
481 'condition' => [
482 'cd_enable' => 'yes',
483 'cd_cond_url_enable' => 'yes',
484 ],
485 ]
486 );
487
488 $element->add_control(
489 'cd_cond_get_key',
490 [
491 'label' => esc_html__('GET Parameter Name', 'king-addons'),
492 'type' => Controls_Manager::TEXT,
493 'condition' => [
494 'cd_enable' => 'yes',
495 'cd_cond_url_enable' => 'yes',
496 ],
497 ]
498 );
499
500 $element->add_control(
501 'cd_cond_get_value',
502 [
503 'label' => esc_html__('GET Parameter Value (optional)', 'king-addons'),
504 'type' => Controls_Manager::TEXT,
505 'description' => esc_html__('Leave empty to check only parameter existence.', 'king-addons'),
506 'condition' => [
507 'cd_enable' => 'yes',
508 'cd_cond_url_enable' => 'yes',
509 ],
510 ]
511 );
512
513 // ========== PRO CONDITIONS ==========
514 $element->add_control(
515 'cd_heading_pro',
516 [
517 'label' => esc_html__('Pro Conditions', 'king-addons'),
518 'type' => Controls_Manager::HEADING,
519 'separator' => 'before',
520 'condition' => [
521 'cd_enable' => 'yes',
522 ],
523 ]
524 );
525
526 // Referrer condition (Pro).
527 $element->add_control(
528 'cd_cond_ref_enable',
529 [
530 'label' => sprintf(__('Referrer Condition %s', 'king-addons'), '<i class="eicon-pro-icon"></i>'),
531 'type' => Controls_Manager::SWITCHER,
532 'return_value' => 'yes',
533 'condition' => [
534 'cd_enable' => 'yes',
535 ],
536 ]
537 );
538
539 $element->add_control(
540 'cd_cond_ref_contains',
541 [
542 'label' => esc_html__('Referrer Contains', 'king-addons'),
543 'type' => Controls_Manager::TEXT,
544 'label_block' => true,
545 'condition' => [
546 'cd_enable' => 'yes',
547 'cd_cond_ref_enable' => 'yes',
548 ],
549 ]
550 );
551
552 // WooCommerce conditions (Pro).
553 $element->add_control(
554 'cd_cond_woo_enable',
555 [
556 'label' => sprintf(__('WooCommerce Conditions %s', 'king-addons'), '<i class="eicon-pro-icon"></i>'),
557 'type' => Controls_Manager::SWITCHER,
558 'return_value' => 'yes',
559 'condition' => [
560 'cd_enable' => 'yes',
561 ],
562 ]
563 );
564
565 $element->add_control(
566 'cd_cond_woo_min_cart_total',
567 [
568 'label' => esc_html__('Minimum Cart Total', 'king-addons'),
569 'type' => Controls_Manager::NUMBER,
570 'min' => 0,
571 'step' => 0.01,
572 'condition' => [
573 'cd_enable' => 'yes',
574 'cd_cond_woo_enable' => 'yes',
575 ],
576 ]
577 );
578
579 $element->add_control(
580 'cd_cond_woo_product_in_cart',
581 [
582 'label' => esc_html__('Products in Cart (IDs)', 'king-addons'),
583 'type' => Controls_Manager::TEXT,
584 'description' => esc_html__('Comma-separated product IDs. Element shows if ANY is in cart.', 'king-addons'),
585 'label_block' => true,
586 'condition' => [
587 'cd_enable' => 'yes',
588 'cd_cond_woo_enable' => 'yes',
589 ],
590 ]
591 );
592
593 $element->add_control(
594 'cd_cond_woo_bought_products',
595 [
596 'label' => esc_html__('Previously Bought Products (IDs)', 'king-addons'),
597 'type' => Controls_Manager::TEXT,
598 'description' => esc_html__('Comma-separated product IDs. Element shows if user bought ANY.', 'king-addons'),
599 'label_block' => true,
600 'condition' => [
601 'cd_enable' => 'yes',
602 'cd_cond_woo_enable' => 'yes',
603 ],
604 ]
605 );
606
607 // Cookie condition (Pro).
608 $element->add_control(
609 'cd_cond_cookie_enable',
610 [
611 'label' => sprintf(__('Cookie Condition %s', 'king-addons'), '<i class="eicon-pro-icon"></i>'),
612 'type' => Controls_Manager::SWITCHER,
613 'return_value' => 'yes',
614 'condition' => [
615 'cd_enable' => 'yes',
616 ],
617 ]
618 );
619
620 $element->add_control(
621 'cd_cond_cookie_name',
622 [
623 'label' => esc_html__('Cookie Name', 'king-addons'),
624 'type' => Controls_Manager::TEXT,
625 'condition' => [
626 'cd_enable' => 'yes',
627 'cd_cond_cookie_enable' => 'yes',
628 ],
629 ]
630 );
631
632 $element->add_control(
633 'cd_cond_cookie_value',
634 [
635 'label' => esc_html__('Cookie Value (optional)', 'king-addons'),
636 'type' => Controls_Manager::TEXT,
637 'description' => esc_html__('Leave empty to check only cookie existence.', 'king-addons'),
638 'condition' => [
639 'cd_enable' => 'yes',
640 'cd_cond_cookie_enable' => 'yes',
641 ],
642 ]
643 );
644
645 // Language / Locale condition (Pro).
646 $element->add_control(
647 'cd_cond_lang_enable',
648 [
649 'label' => sprintf(__('Language Condition %s', 'king-addons'), '<i class="eicon-pro-icon"></i>'),
650 'type' => Controls_Manager::SWITCHER,
651 'return_value' => 'yes',
652 'condition' => [
653 'cd_enable' => 'yes',
654 ],
655 ]
656 );
657
658 $element->add_control(
659 'cd_cond_lang_codes',
660 [
661 'label' => esc_html__('Languages', 'king-addons'),
662 'type' => Controls_Manager::SELECT2,
663 'multiple' => true,
664 'options' => $this->get_language_options(),
665 'label_block' => true,
666 'description' => esc_html__('Works with WPML, Polylang, and WordPress locale.', 'king-addons'),
667 'condition' => [
668 'cd_enable' => 'yes',
669 'cd_cond_lang_enable' => 'yes',
670 ],
671 ]
672 );
673
674 $element->end_controls_section();
675 }
676
677 /**
678 * Get cached Elementor templates list.
679 *
680 * @return array<int|string,string>
681 */
682 private function get_elementor_templates_options(): array
683 {
684 if (null !== self::$templates_cache) {
685 return self::$templates_cache;
686 }
687
688 $templates = get_posts([
689 'post_type' => 'elementor_library',
690 'posts_per_page' => -1,
691 'post_status' => 'publish',
692 'orderby' => 'title',
693 'order' => 'ASC',
694 'fields' => 'ids',
695 ]);
696
697 self::$templates_cache = [];
698
699 if (!empty($templates)) {
700 foreach ($templates as $template_id) {
701 self::$templates_cache[$template_id] = get_the_title($template_id);
702 }
703 }
704
705 return self::$templates_cache;
706 }
707
708 /**
709 * Get cached WP roles list.
710 *
711 * @return array<string,string>
712 */
713 private function get_wp_roles_for_control(): array
714 {
715 if (null !== self::$roles_cache) {
716 return self::$roles_cache;
717 }
718
719 global $wp_roles;
720 self::$roles_cache = [];
721
722 if (!isset($wp_roles)) {
723 $wp_roles = wp_roles();
724 }
725
726 foreach ($wp_roles->roles as $key => $role) {
727 self::$roles_cache[$key] = $role['name'];
728 }
729
730 return self::$roles_cache;
731 }
732
733 /**
734 * Get language options for SELECT2.
735 *
736 * @return array<string,string>
737 */
738 private function get_language_options(): array
739 {
740 return [
741 'en' => 'English',
742 'es' => 'Spanish (Español)',
743 'fr' => 'French (Français)',
744 'de' => 'German (Deutsch)',
745 'it' => 'Italian (Italiano)',
746 'pt' => 'Portuguese (Português)',
747 'ru' => 'Russian (Русский)',
748 'zh' => 'Chinese (中文)',
749 'ja' => 'Japanese (日本語)',
750 'ko' => 'Korean (한국어)',
751 'ar' => 'Arabic (العربية)',
752 'hi' => 'Hindi (हिन्दी)',
753 'nl' => 'Dutch (Nederlands)',
754 'pl' => 'Polish (Polski)',
755 'tr' => 'Turkish (Türkçe)',
756 'sv' => 'Swedish (Svenska)',
757 'da' => 'Danish (Dansk)',
758 'fi' => 'Finnish (Suomi)',
759 'no' => 'Norwegian (Norsk)',
760 'cs' => 'Czech (Čeština)',
761 'el' => 'Greek (Ελληνικά)',
762 'he' => 'Hebrew (עברית)',
763 'th' => 'Thai (ไทย)',
764 'vi' => 'Vietnamese (Tiếng Việt)',
765 'id' => 'Indonesian (Bahasa Indonesia)',
766 'uk' => 'Ukrainian (Українська)',
767 'ro' => 'Romanian (Română)',
768 'hu' => 'Hungarian (Magyar)',
769 'bg' => 'Bulgarian (Български)',
770 'sk' => 'Slovak (Slovenčina)',
771 ];
772 }
773
774 /**
775 * Filter: Should render element.
776 *
777 * This method is used to completely prevent element rendering.
778 *
779 * @param bool $should_render Default render decision.
780 * @param Element_Base $element Elementor element.
781 *
782 * @return bool
783 */
784 public function should_render_element(bool $should_render, Element_Base $element): bool
785 {
786 if (!$should_render) {
787 return false;
788 }
789
790 $settings = $element->get_settings_for_display();
791
792 if (empty($settings['cd_enable']) || 'yes' !== $settings['cd_enable']) {
793 return true;
794 }
795
796 // Always render in editor/preview mode.
797 if ($this->is_editor_or_preview()) {
798 return true;
799 }
800
801 $result = $this->evaluate_conditions($settings);
802
803 // Handle inversion.
804 if (!empty($settings['cd_invert']) && 'yes' === $settings['cd_invert']) {
805 $result = !$result;
806 }
807
808 // If conditions pass, render normally.
809 if ($result) {
810 return true;
811 }
812
813 // Check if we need to show fallback content.
814 $fallback = $settings['cd_fallback_type'] ?? 'hide';
815 if ('hide' === $fallback) {
816 return false; // Don't render at all.
817 }
818
819 // For message/template fallback, we still need to render.
820 return true;
821 }
822
823 /**
824 * Before render: mark element in editor or output fallback.
825 *
826 * @param Element_Base $element Elementor element.
827 *
828 * @return void
829 */
830 public function before_render_element(Element_Base $element): void
831 {
832 $settings = $element->get_settings_for_display();
833
834 if (empty($settings['cd_enable']) || 'yes' !== $settings['cd_enable']) {
835 return;
836 }
837
838 // Mark element in editor/preview for visual indication.
839 if ($this->is_editor_or_preview()) {
840 $element->add_render_attribute('_wrapper', 'data-king-cd-enabled', 'yes');
841 $element->add_render_attribute('_wrapper', 'class', 'king-addons-cd-preview');
842
843 // Add data attributes for condition info.
844 $active_conditions = $this->get_active_conditions_list($settings);
845 if (!empty($active_conditions)) {
846 $element->add_render_attribute('_wrapper', 'data-king-cd-conditions', esc_attr(implode(', ', $active_conditions)));
847 }
848 return;
849 }
850
851 $result = $this->evaluate_conditions($settings);
852
853 if (!empty($settings['cd_invert']) && 'yes' === $settings['cd_invert']) {
854 $result = !$result;
855 }
856
857 // If conditions pass, do nothing.
858 if ($result) {
859 return;
860 }
861
862 // Apply fallback.
863 $this->apply_fallback($element, $settings);
864 }
865
866 /**
867 * Get list of active conditions for editor display.
868 *
869 * @param array<string,mixed> $settings Element settings.
870 *
871 * @return array<string>
872 */
873 private function get_active_conditions_list(array $settings): array
874 {
875 $conditions = [];
876
877 $mode = $settings['cd_cond_login_mode'] ?? 'any';
878 if ('logged_in' === $mode) {
879 $conditions[] = 'User: Logged In';
880 } elseif ('logged_out' === $mode) {
881 $conditions[] = 'User: Logged Out';
882 }
883
884 if (!empty($settings['cd_cond_device_enable']) && 'yes' === $settings['cd_cond_device_enable']) {
885 $conditions[] = 'Device';
886 }
887
888 if (!empty($settings['cd_cond_browser_enable']) && 'yes' === $settings['cd_cond_browser_enable']) {
889 $conditions[] = 'Browser';
890 }
891
892 if (!empty($settings['cd_cond_date_enable']) && 'yes' === $settings['cd_cond_date_enable']) {
893 $conditions[] = 'Date';
894 }
895
896 if (!empty($settings['cd_cond_time_enable']) && 'yes' === $settings['cd_cond_time_enable']) {
897 $conditions[] = 'Time';
898 }
899
900 if (!empty($settings['cd_cond_url_enable']) && 'yes' === $settings['cd_cond_url_enable']) {
901 $conditions[] = 'URL';
902 }
903
904 if (!empty($settings['cd_cond_ref_enable']) && 'yes' === $settings['cd_cond_ref_enable']) {
905 $conditions[] = 'Referrer (Pro)';
906 }
907
908 if (!empty($settings['cd_cond_woo_enable']) && 'yes' === $settings['cd_cond_woo_enable']) {
909 $conditions[] = 'WooCommerce (Pro)';
910 }
911
912 if (!empty($settings['cd_cond_cookie_enable']) && 'yes' === $settings['cd_cond_cookie_enable']) {
913 $conditions[] = 'Cookie (Pro)';
914 }
915
916 if (!empty($settings['cd_cond_lang_enable']) && 'yes' === $settings['cd_cond_lang_enable']) {
917 $conditions[] = 'Language (Pro)';
918 }
919
920 return $conditions;
921 }
922
923 /**
924 * Evaluate all conditions.
925 *
926 * @param array<string,mixed> $settings Element settings.
927 *
928 * @return bool True if conditions pass (element should be shown).
929 */
930 private function evaluate_conditions(array $settings): bool
931 {
932 $relation = $settings['cd_relation'] ?? 'all';
933
934 $results = [];
935
936 $results[] = $this->check_login_role($settings);
937 $results[] = $this->check_device($settings);
938 $results[] = $this->check_browser($settings);
939 $results[] = $this->check_date_time($settings);
940 $results[] = $this->check_time_of_day($settings);
941 $results[] = $this->check_url($settings);
942 $results[] = $this->check_referrer($settings);
943 $results[] = $this->check_woo($settings);
944 $results[] = $this->check_cookie($settings);
945 $results[] = $this->check_language($settings);
946
947 // Filter out null values (disabled conditions).
948 $results = array_filter($results, static function ($value) {
949 return null !== $value;
950 });
951
952 // If no active conditions, show element by default.
953 if (empty($results)) {
954 return true;
955 }
956
957 if ('any' === $relation) {
958 // OR: at least one must be true.
959 return in_array(true, $results, true);
960 }
961
962 // AND: all must be true.
963 return !in_array(false, $results, true);
964 }
965
966 /**
967 * Apply fallback when conditions are not met.
968 *
969 * @param Element_Base $element Elementor element.
970 * @param array<string,mixed> $settings Element settings.
971 *
972 * @return void
973 */
974 private function apply_fallback(Element_Base $element, array $settings): void
975 {
976 $fallback = $settings['cd_fallback_type'] ?? 'hide';
977
978 switch ($fallback) {
979 case 'message':
980 $message = $settings['cd_fallback_msg'] ?? '';
981 echo '<div class="king-addons-cd-fallback-message">' . wp_kses_post($message) . '</div>';
982 $this->suppress_element_render($element);
983 break;
984
985 case 'template':
986 if ($this->can_use_pro() && !empty($settings['cd_fallback_template'])) {
987 $this->render_template((int) $settings['cd_fallback_template']);
988 }
989 $this->suppress_element_render($element);
990 break;
991
992 case 'hide':
993 default:
994 $this->suppress_element_render($element);
995 break;
996 }
997 }
998
999 /**
1000 * Check login/role condition.
1001 *
1002 * @param array<string,mixed> $settings Settings.
1003 *
1004 * @return bool|null Null if condition is not active.
1005 */
1006 private function check_login_role(array $settings): ?bool
1007 {
1008 $mode = $settings['cd_cond_login_mode'] ?? 'any';
1009
1010 if ('any' === $mode) {
1011 return null;
1012 }
1013
1014 $is_logged_in = is_user_logged_in();
1015
1016 if ('logged_in' === $mode && !$is_logged_in) {
1017 return false;
1018 }
1019
1020 if ('logged_out' === $mode && $is_logged_in) {
1021 return false;
1022 }
1023
1024 // Check specific roles if logged in.
1025 if ('logged_in' === $mode && $is_logged_in && !empty($settings['cd_cond_roles']) && is_array($settings['cd_cond_roles'])) {
1026 $user = wp_get_current_user();
1027 foreach ($user->roles as $role) {
1028 if (in_array($role, $settings['cd_cond_roles'], true)) {
1029 return true;
1030 }
1031 }
1032 return false;
1033 }
1034
1035 return true;
1036 }
1037
1038 /**
1039 * Check device condition.
1040 *
1041 * @param array<string,mixed> $settings Settings.
1042 *
1043 * @return bool|null
1044 */
1045 private function check_device(array $settings): ?bool
1046 {
1047 if (empty($settings['cd_cond_device_enable']) || 'yes' !== $settings['cd_cond_device_enable']) {
1048 return null;
1049 }
1050
1051 if (empty($settings['cd_cond_device_types']) || !is_array($settings['cd_cond_device_types'])) {
1052 return null;
1053 }
1054
1055 // Simple detection: mobile includes tablet.
1056 $is_mobile = wp_is_mobile();
1057 $current = $is_mobile ? 'mobile' : 'desktop';
1058
1059 // Also check tablet separately using UA.
1060 if ($is_mobile && $this->is_tablet()) {
1061 $current = 'tablet';
1062 }
1063
1064 return in_array($current, $settings['cd_cond_device_types'], true);
1065 }
1066
1067 /**
1068 * Check if current device is tablet.
1069 *
1070 * @return bool
1071 */
1072 private function is_tablet(): bool
1073 {
1074 if (empty($_SERVER['HTTP_USER_AGENT'])) {
1075 return false;
1076 }
1077
1078 $ua = strtolower($_SERVER['HTTP_USER_AGENT']);
1079 return (strpos($ua, 'tablet') !== false || strpos($ua, 'ipad') !== false) &&
1080 strpos($ua, 'mobile') === false;
1081 }
1082
1083 /**
1084 * Check browser condition (Pro).
1085 *
1086 * @param array<string,mixed> $settings Settings.
1087 *
1088 * @return bool|null
1089 */
1090 private function check_browser(array $settings): ?bool
1091 {
1092 if (empty($settings['cd_cond_browser_enable']) || 'yes' !== $settings['cd_cond_browser_enable']) {
1093 return null;
1094 }
1095
1096 if (!$this->can_use_pro()) {
1097 return true; // Pro feature not available, skip condition.
1098 }
1099
1100 if (empty($settings['cd_cond_browser_types']) || !is_array($settings['cd_cond_browser_types'])) {
1101 return null;
1102 }
1103
1104 $browser = $this->detect_browser();
1105 return in_array($browser, $settings['cd_cond_browser_types'], true);
1106 }
1107
1108 /**
1109 * Detect current browser.
1110 *
1111 * @return string Browser identifier.
1112 */
1113 private function detect_browser(): string
1114 {
1115 if (empty($_SERVER['HTTP_USER_AGENT'])) {
1116 return 'unknown';
1117 }
1118
1119 $ua = $_SERVER['HTTP_USER_AGENT'];
1120
1121 // Order matters - check more specific first.
1122 if (preg_match('/Edg(e|A|iOS)?/i', $ua)) {
1123 return 'edge';
1124 }
1125 if (preg_match('/OPR|Opera/i', $ua)) {
1126 return 'opera';
1127 }
1128 if (preg_match('/Chrome/i', $ua) && !preg_match('/Chromium/i', $ua)) {
1129 return 'chrome';
1130 }
1131 if (preg_match('/Safari/i', $ua) && !preg_match('/Chrome/i', $ua)) {
1132 return 'safari';
1133 }
1134 if (preg_match('/Firefox/i', $ua)) {
1135 return 'firefox';
1136 }
1137 if (preg_match('/MSIE|Trident/i', $ua)) {
1138 return 'ie';
1139 }
1140
1141 return 'unknown';
1142 }
1143
1144 /**
1145 * Check date/time condition.
1146 *
1147 * @param array<string,mixed> $settings Settings.
1148 *
1149 * @return bool|null
1150 */
1151 private function check_date_time(array $settings): ?bool
1152 {
1153 if (empty($settings['cd_cond_date_enable']) || 'yes' !== $settings['cd_cond_date_enable']) {
1154 return null;
1155 }
1156
1157 $now = current_time('timestamp');
1158
1159 // Check date from.
1160 if (!empty($settings['cd_cond_date_from'])) {
1161 $from = strtotime((string) $settings['cd_cond_date_from']);
1162 if ($from && $now < $from) {
1163 return false;
1164 }
1165 }
1166
1167 // Check date to.
1168 if (!empty($settings['cd_cond_date_to'])) {
1169 $to = strtotime((string) $settings['cd_cond_date_to']);
1170 if ($to && $now > $to) {
1171 return false;
1172 }
1173 }
1174
1175 // Check days of week.
1176 if (!empty($settings['cd_cond_days_of_week']) && is_array($settings['cd_cond_days_of_week'])) {
1177 $current_day = strtolower(wp_date('D', $now));
1178 if (!in_array($current_day, $settings['cd_cond_days_of_week'], true)) {
1179 return false;
1180 }
1181 }
1182
1183 return true;
1184 }
1185
1186 /**
1187 * Check time of day condition.
1188 *
1189 * @param array<string,mixed> $settings Settings.
1190 *
1191 * @return bool|null
1192 */
1193 private function check_time_of_day(array $settings): ?bool
1194 {
1195 if (empty($settings['cd_cond_time_enable']) || 'yes' !== $settings['cd_cond_time_enable']) {
1196 return null;
1197 }
1198
1199 $current_time = wp_date('H:i');
1200
1201 $time_from = $settings['cd_cond_time_from'] ?? '';
1202 $time_to = $settings['cd_cond_time_to'] ?? '';
1203
1204 if (empty($time_from) && empty($time_to)) {
1205 return null;
1206 }
1207
1208 if (!empty($time_from) && $current_time < $time_from) {
1209 return false;
1210 }
1211
1212 if (!empty($time_to) && $current_time > $time_to) {
1213 return false;
1214 }
1215
1216 return true;
1217 }
1218
1219 /**
1220 * Check URL/query condition.
1221 *
1222 * @param array<string,mixed> $settings Settings.
1223 *
1224 * @return bool|null
1225 */
1226 private function check_url(array $settings): ?bool
1227 {
1228 if (empty($settings['cd_cond_url_enable']) || 'yes' !== $settings['cd_cond_url_enable']) {
1229 return null;
1230 }
1231
1232 $url = (is_ssl() ? 'https://' : 'http://') .
1233 ($_SERVER['HTTP_HOST'] ?? '') .
1234 ($_SERVER['REQUEST_URI'] ?? '');
1235
1236 // Check URL contains.
1237 if (!empty($settings['cd_cond_url_contains'])) {
1238 if (false === strpos($url, $settings['cd_cond_url_contains'])) {
1239 return false;
1240 }
1241 }
1242
1243 // Check GET parameter.
1244 if (!empty($settings['cd_cond_get_key'])) {
1245 $key = sanitize_text_field($settings['cd_cond_get_key']);
1246 $expected_value = $settings['cd_cond_get_value'] ?? '';
1247
1248 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1249 if (!isset($_GET[$key])) {
1250 return false;
1251 }
1252
1253 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1254 if ('' !== $expected_value && sanitize_text_field($_GET[$key]) !== $expected_value) {
1255 return false;
1256 }
1257 }
1258
1259 return true;
1260 }
1261
1262 /**
1263 * Check referrer condition (Pro).
1264 *
1265 * @param array<string,mixed> $settings Settings.
1266 *
1267 * @return bool|null
1268 */
1269 private function check_referrer(array $settings): ?bool
1270 {
1271 if (empty($settings['cd_cond_ref_enable']) || 'yes' !== $settings['cd_cond_ref_enable']) {
1272 return null;
1273 }
1274
1275 if (!$this->can_use_pro()) {
1276 return true;
1277 }
1278
1279 if (empty($_SERVER['HTTP_REFERER'])) {
1280 return false;
1281 }
1282
1283 $ref = (string) $_SERVER['HTTP_REFERER'];
1284
1285 if (!empty($settings['cd_cond_ref_contains'])) {
1286 return false !== strpos($ref, $settings['cd_cond_ref_contains']);
1287 }
1288
1289 return true;
1290 }
1291
1292 /**
1293 * Check WooCommerce conditions (Pro).
1294 *
1295 * @param array<string,mixed> $settings Settings.
1296 *
1297 * @return bool|null
1298 */
1299 private function check_woo(array $settings): ?bool
1300 {
1301 if (empty($settings['cd_cond_woo_enable']) || 'yes' !== $settings['cd_cond_woo_enable']) {
1302 return null;
1303 }
1304
1305 if (!$this->can_use_pro()) {
1306 return true;
1307 }
1308
1309 if (!class_exists('WooCommerce') || !function_exists('WC')) {
1310 return false;
1311 }
1312
1313 $cart = WC()->cart;
1314 if (!$cart) {
1315 return false;
1316 }
1317
1318 // Check minimum cart total.
1319 if (isset($settings['cd_cond_woo_min_cart_total']) && '' !== $settings['cd_cond_woo_min_cart_total']) {
1320 $min_total = (float) $settings['cd_cond_woo_min_cart_total'];
1321 $total = (float) $cart->get_total('edit');
1322 if ($total < $min_total) {
1323 return false;
1324 }
1325 }
1326
1327 // Check products in cart.
1328 if (!empty($settings['cd_cond_woo_product_in_cart'])) {
1329 $ids = $this->sanitize_ids($settings['cd_cond_woo_product_in_cart']);
1330 if (!empty($ids)) {
1331 $in_cart = false;
1332 foreach ($cart->get_cart() as $item) {
1333 $product_id = (int) ($item['product_id'] ?? 0);
1334 $variation_id = (int) ($item['variation_id'] ?? 0);
1335 if (in_array($product_id, $ids, true) || in_array($variation_id, $ids, true)) {
1336 $in_cart = true;
1337 break;
1338 }
1339 }
1340 if (!$in_cart) {
1341 return false;
1342 }
1343 }
1344 }
1345
1346 // Check previously bought products.
1347 if (!empty($settings['cd_cond_woo_bought_products']) && is_user_logged_in()) {
1348 $ids = $this->sanitize_ids($settings['cd_cond_woo_bought_products']);
1349 if (!empty($ids)) {
1350 $user = wp_get_current_user();
1351 $bought = false;
1352 foreach ($ids as $pid) {
1353 if (function_exists('wc_customer_bought_product') &&
1354 wc_customer_bought_product($user->user_email, $user->ID, $pid)) {
1355 $bought = true;
1356 break;
1357 }
1358 }
1359 if (!$bought) {
1360 return false;
1361 }
1362 }
1363 }
1364
1365 return true;
1366 }
1367
1368 /**
1369 * Check cookie condition (Pro).
1370 *
1371 * @param array<string,mixed> $settings Settings.
1372 *
1373 * @return bool|null
1374 */
1375 private function check_cookie(array $settings): ?bool
1376 {
1377 if (empty($settings['cd_cond_cookie_enable']) || 'yes' !== $settings['cd_cond_cookie_enable']) {
1378 return null;
1379 }
1380
1381 if (!$this->can_use_pro()) {
1382 return true;
1383 }
1384
1385 if (empty($settings['cd_cond_cookie_name'])) {
1386 return null;
1387 }
1388
1389 $name = sanitize_text_field($settings['cd_cond_cookie_name']);
1390 $expected_value = $settings['cd_cond_cookie_value'] ?? '';
1391
1392 if (!isset($_COOKIE[$name])) {
1393 return false;
1394 }
1395
1396 if ('' !== $expected_value && (string) $_COOKIE[$name] !== (string) $expected_value) {
1397 return false;
1398 }
1399
1400 return true;
1401 }
1402
1403 /**
1404 * Check language condition (Pro).
1405 *
1406 * @param array<string,mixed> $settings Settings.
1407 *
1408 * @return bool|null
1409 */
1410 private function check_language(array $settings): ?bool
1411 {
1412 if (empty($settings['cd_cond_lang_enable']) || 'yes' !== $settings['cd_cond_lang_enable']) {
1413 return null;
1414 }
1415
1416 if (!$this->can_use_pro()) {
1417 return true;
1418 }
1419
1420 if (empty($settings['cd_cond_lang_codes']) || !is_array($settings['cd_cond_lang_codes'])) {
1421 return null;
1422 }
1423
1424 $current_lang = $this->get_current_language();
1425
1426 foreach ($settings['cd_cond_lang_codes'] as $lang) {
1427 if (strpos($current_lang, $lang) === 0) {
1428 return true;
1429 }
1430 }
1431
1432 return false;
1433 }
1434
1435 /**
1436 * Get current site language.
1437 *
1438 * Supports WPML, Polylang, and falls back to WordPress locale.
1439 *
1440 * @return string Language code.
1441 */
1442 private function get_current_language(): string
1443 {
1444 // WPML support.
1445 if (defined('ICL_LANGUAGE_CODE') && ICL_LANGUAGE_CODE) {
1446 return ICL_LANGUAGE_CODE;
1447 }
1448
1449 // Polylang support.
1450 if (function_exists('pll_current_language')) {
1451 return pll_current_language('slug') ?: get_locale();
1452 }
1453
1454 // WordPress locale fallback.
1455 $locale = get_locale();
1456 return substr($locale, 0, 2); // Return first 2 chars (e.g., 'en' from 'en_US').
1457 }
1458
1459 /**
1460 * Render Elementor template.
1461 *
1462 * @param int $template_id Template ID.
1463 *
1464 * @return void
1465 */
1466 private function render_template(int $template_id): void
1467 {
1468 if (!did_action('elementor/loaded')) {
1469 return;
1470 }
1471
1472 echo '<div class="king-addons-cd-fallback-template">';
1473 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1474 echo Plugin::$instance->frontend->get_builder_content_for_display($template_id);
1475 echo '</div>';
1476 }
1477
1478 /**
1479 * Suppress element rendering.
1480 *
1481 * @param Element_Base $element Elementor element.
1482 *
1483 * @return void
1484 */
1485 private function suppress_element_render(Element_Base $element): void
1486 {
1487 // Add hidden class for CSS hiding as fallback.
1488 $element->add_render_attribute('_wrapper', 'class', 'king-addons-cd-hidden');
1489 $element->add_render_attribute('_wrapper', 'style', 'display:none !important;');
1490 }
1491
1492 /**
1493 * Check if in editor or preview mode.
1494 *
1495 * @return bool
1496 */
1497 private function is_editor_or_preview(): bool
1498 {
1499 if (!did_action('elementor/loaded')) {
1500 return false;
1501 }
1502
1503 return Plugin::$instance->editor->is_edit_mode() || Plugin::$instance->preview->is_preview_mode();
1504 }
1505
1506 /**
1507 * Sanitize comma-separated IDs.
1508 *
1509 * @param string $raw Raw string.
1510 *
1511 * @return array<int>
1512 */
1513 private function sanitize_ids(string $raw): array
1514 {
1515 return array_values(array_filter(array_map('absint', array_map('trim', explode(',', $raw)))));
1516 }
1517
1518 /**
1519 * Check Pro license availability.
1520 *
1521 * @return bool
1522 */
1523 private function can_use_pro(): bool
1524 {
1525 if (!function_exists('king_addons_freemius')) {
1526 return false;
1527 }
1528
1529 return king_addons_freemius()->can_use_premium_code__premium_only();
1530 }
1531 }
1532
1533
1534
1535
1536
1537
1538
1539