PluginProbe ʕ •ᴥ•ʔ
Kirki – Freeform Page Builder, Website Builder & Customizer / 6.2.1
Kirki – Freeform Page Builder, Website Builder & Customizer v6.2.1
6.2.1 6.2.0 6.1.1 6.1.0 6.0.14 6.0.13 6.0.12 6.0.11 6.0.10 6.0.9 6.0.8 6.0.7 6.0.6 6.0.5 6.0.4 6.0.3 6.0.2 6.0.1 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 4.0.19 4.0.20 4.0.21 4.0.22 4.0.23 4.0.24 4.1 4.2.0 5.0.0 5.1.0 5.1.1 5.2.0 5.2.1 5.2.2 5.2.3 6.0.0 trunk 3.0.40 3.0.41 3.0.42 3.0.43 3.0.44 3.0.45 3.1.0 3.1.1 3.1.2
kirki / includes / Frontend / Preview / Preview.php
kirki / includes / Frontend / Preview Last commit date
templates 3 months ago DataHelper.php 3 days ago ExceptionalElements.php 3 days ago Preview.php 3 days ago Utils.php 3 days ago
Preview.php
2469 lines
1 <?php
2 /**
3 * Preview script for html markup generator
4 *
5 * @package kirki
6 */
7
8 namespace Kirki\Frontend\Preview;
9
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit; // Exit if accessed directly.
12 }
13
14 use Kirki\Ajax\Symbol;
15 use Kirki\Frontend\Preview\ExceptionalElements;
16 use Kirki\Ajax\UserData;
17 use Kirki\Ajax\WpAdmin;
18 use Kirki\API\ContentManager\ContentManagerHelper;
19 use Kirki\HelperFunctions;
20
21 use function Kirki\Framework\dd;
22
23 /**
24 * Preview class
25 */
26 class Preview extends ExceptionalElements {
27
28 /**
29 * $root for entry id or parent element id.
30 */
31 protected $root = null;
32 /**
33 * $data is for all element data array.
34 */
35 protected $data = array();
36 /**
37 * $symbol_id if preview is generate for symbol then it has symbol_id for maintain class-prefix.
38 */
39 protected $symbol_id = null;
40 protected $prefix = false;
41 /**
42 * $style_blocks for all style blocks merged array. like=> global, migrated, symbols. etc.
43 */
44 protected $style_blocks = array();
45 /**
46 * $only_used_style_blocks only used style blocks.
47 */
48 private $only_used_style_blocks = array();
49 /**
50 * $only_used_popup_id_array only used popup post ids.
51 */
52 public static $only_used_popup_id_array = array();
53 /**
54 * $printed_font_family_tracker for tracking already printed font family markup.
55 */
56 private static $printed_font_family_tracker = array();
57 private static $printed_variable_tracker = array();
58 private static $section_id_tracker_for_href = array(); // ['section_id' => 'Section-2', 'section_id_1' => 'section-2-1' ]
59 /**
60 * $view_ports user all viewports data.
61 */
62 private $view_ports = array();
63
64 /**
65 * Initialize some variables for element related data. Like: custom codes, lightbox, map, slider etc.
66 * START
67 */
68 /**
69 * $sliders for collect slider elements data.
70 */
71 private $sliders = array();
72 /**
73 * $navigation for collect navigation elements data.
74 */
75 private $navigations = array();
76
77 /**
78 * $navigation item for collect navigation item elements data.
79 */
80 private $navigation_item = array();
81
82
83 /**
84 * $inputs for collect inputs elements data.
85 */
86 private $inputs = array();
87 /**
88 * $maps for collect map elements data.
89 */
90 private $maps = array();
91 /**
92 * $lotties for collect lottie elements data.
93 */
94 private $lotties = array();
95 /**
96 * $popups for collect popup elements data.
97 */
98 private $popups = array();
99 /**
100 * $tabs for collect tab elements data.
101 */
102 private $tabs = array();
103 /**
104 * $lightboxes for collect lightbox elements data.
105 */
106 private $lightboxes = array();
107 /**
108 * $re_captchas for collect reCaptcha elements data.
109 */
110 private $re_captchas = array();
111 /**
112 * $videos for collect video elements data.
113 */
114 private $videos = array();
115 /**
116 * $interactions for collect interaction elements data.
117 */
118 private $interactions = array();
119
120 /**
121 * $interaction library for collect interaction library elements data.
122 */
123 private $interaction_library = array();
124 /**
125 * $collections for collect collection elements data.
126 */
127 private $collections = array();
128 /**
129 * $forms for collect form elements data.
130 */
131 private $forms = array();
132
133 /**
134 * $liquid_glass for liquid glass effect
135 */
136 private $liquid_glass = array();
137
138 /**
139 * $custom_codes for collect customCode elements data.
140 */
141 public $custom_codes = '';
142
143 /**
144 * $dropdown for collect dropdown elements data.
145 */
146 private $dropdown = array();
147 /**
148 * Initialize some variables for element related data. Like: custom codes, lightbox, map, slider etc.
149 * END
150 */
151
152 /**
153 * elements wise variable modes array.
154 */
155 private $ele_variable_modes = array();
156
157
158 /**
159 * interaction ['scroll-into-ele'] & ['scroll-out-ele'] tracker
160 */
161 private $interaction_preset_and_text_animation_tracker = array();
162
163 private $interaction_library_text_animation_tracker = array();
164
165 private $scroll_into_custom_interaction_tracker = '';
166
167 private $track_animation_for_elements_with_this_class = '';
168
169 private $track_animation_for_children_with_this_class = '';
170
171 private $track_animation_for_sibling_with_this_class = '';
172
173 private $track_animation_for_trigger_sibling_with_this_class = '';
174
175 private $track_animation_for_trigger_with_this_class = '';
176
177 /**
178 * END
179 */
180
181
182 /**
183 * List of exceptional elements.
184 */
185 private $exceptional_elements = array(
186 'custom-code',
187 'map',
188 'svg',
189 'svg-icon',
190 'textarea',
191 'select',
192 'video',
193 'radio-group',
194 'checkbox-element',
195 'radio-button',
196 'image',
197 'collection',
198 'loading', // collection loading element
199 'items',
200 'collection-wrapper',
201 'users',
202 'pagination',
203 'pagination-item',
204 'pagination-number',
205 'terms',
206 'menus',
207 'symbol',
208 'link-block',
209 'form',
210 'button',
211 'file-upload-inner',
212 'file-upload-threshold-text',
213 'file-upload',
214 'popup-body',
215 'navigation',
216 'navigation-item',
217 'navigation-items',
218 'section',
219 'common',
220 'slider',
221 'slider_mask',
222 'slider_nav',
223 'tabs',
224 'tab_menu',
225 'tab',
226 'tab_content',
227 'tab_pane'
228 );
229
230 /**
231 * List of dynamic content element.
232 */
233 private $exceptional_elements_contains_dyn_content = array( 'collection' );
234 /**
235 * List of inline elements.
236 */
237 private $inline_elements = array( 'button', 'link-block', 'link-text' );
238
239 /**
240 * List of anchor elements.
241 */
242 private $prevent_anchor_elements = array( 'heading', 'image', 'paragraph' );
243 /**
244 * List anchor attrs.
245 */
246 private $anchor_attrs = array( 'href', 'target', 'rel' );
247
248
249 /**
250 * Preview script initilizer
251 *
252 * @param array $data elments data array.
253 * @param array $style_blocks elments style_blocks array.
254 * @param string $root root element id.
255 * @param string $symbol_id if preview script is initiate for symbol element generation.
256 *
257 * @return void
258 */
259 public function __construct( $data, $style_blocks, $root = null, $symbol_id = null, $prefix = false ) {
260 if ( $root ) {
261 $this->root = $root;
262 } else {
263 $this->root = 'root';
264 }
265 $this->data = $data;
266 $this->style_blocks = $style_blocks;
267 $this->view_ports = UserData::get_view_port_list();
268 if ( $symbol_id ) {
269 $this->symbol_id = $symbol_id;
270 }
271 $this->prefix = $prefix;
272 }
273
274 /**
275 * Get the html string
276 *
277 * @return string
278 */
279 public function getHTML( $options = array() ) {
280 // TODO: need to fix this code
281 // if(!isset($options['user']) && get_current_user_id() > 0){
282 // $options['user'] = Users::get_user_by_id(get_current_user_id());
283 // }
284 return $this->recGenHTML( $this->root, $options );
285 }
286
287 /**
288 * Get the style blocks that are used in the html
289 * this method is called after getHtml() method call and from the same instance
290 *
291 * @return string
292 */
293 public function get_only_used_style_blocks() {
294 return $this->only_used_style_blocks;
295 }
296
297 public function add_to_only_used_style_blocks( $style_block ) {
298 $this->only_used_style_blocks[ $style_block['id'] ] = $style_block;
299 if ( isset( $style_block['liquid-glass'] ) ) {
300 $this->liquid_glass[ $style_block['id'] ] = array(
301 'id' => $style_block['id'],
302 'name' => $style_block['name'],
303 'liquid-glass' => $style_block['liquid-glass'],
304 'type' => $style_block['type'],
305 'prefix' => $this->prefix,
306 );
307 }
308 }
309
310
311 private function updateTranslateCSSString( $end ) {
312 $transforms = array();
313
314 if ( ! empty( $end['x']['unit'] ) && $end['x']['unit'] !== 'auto' ) {
315 $transforms[] = 'translateX(' . $this->addUnit( $end['x'] ) . ')';
316 }
317 if ( ! empty( $end['y']['unit'] ) && $end['y']['unit'] !== 'auto' ) {
318 $transforms[] = 'translateY(' . $this->addUnit( $end['y'] ) . ')';
319 }
320 if ( ! empty( $end['z']['unit'] ) && $end['z']['unit'] !== 'auto' ) {
321 $transforms[] = 'translateZ(' . $this->addUnit( $end['z'] ) . ')';
322 }
323
324 return implode( ' ', $transforms ); // Return only the transform part
325 }
326
327
328 private function updateRotateCSSString( $end ) {
329 $rotateProperties = array();
330
331 if ( $end['x']['unit'] !== 'auto' ) {
332 $rotateProperties[] = 'rotateX(' . $this->addUnit( $end['x'] ) . ')';
333 }
334 if ( $end['y']['unit'] !== 'auto' ) {
335 $rotateProperties[] = 'rotateY(' . $this->addUnit( $end['y'] ) . ')';
336 }
337 if ( $end['z']['unit'] !== 'auto' ) {
338 $rotateProperties[] = 'rotateZ(' . $this->addUnit( $end['z'] ) . ')';
339 }
340
341 return implode( ' ', $rotateProperties );
342 }
343
344
345 private function updateSkewCSSString( $end ) {
346 $skewProperties = array();
347
348 if ( $end['x']['unit'] !== 'auto' ) {
349 $skewProperties[] = 'skewX(' . $this->addUnit( $end['x'] ) . ')';
350 }
351 if ( $end['y']['unit'] !== 'auto' ) {
352 $skewProperties[] = 'skewY(' . $this->addUnit( $end['y'] ) . ')';
353 }
354
355 return implode( ' ', $skewProperties );
356 }
357
358
359 private function updateScaleCSSString( $end ) {
360 $scaleProperties = array();
361
362 if ( $end['x']['unit'] !== 'none' ) {
363 $scaleProperties[] = 'scaleX(' . $end['x']['value'] . ')';
364 }
365 if ( $end['y']['unit'] !== 'none' ) {
366 $scaleProperties[] = 'scaleY(' . $end['y']['value'] . ')';
367 }
368
369 return implode( ' ', $scaleProperties );
370 }
371
372
373
374 private function addUnit( $valueObj ) {
375 $specialValues = array( 'auto', 'fit-content', 'min-content', 'max-content', 'none', 'scrollHeight', 'scrollWidth' );
376
377 // Check if the unit is in specialValues, if so, don't append it
378 $unit = in_array( $valueObj['unit'], $specialValues ) ? '' : $valueObj['unit'];
379
380 return $valueObj['value'] . $unit;
381 }
382
383 private function updateCssStringForAnimation( $property, $end ) {
384 $css = '';
385 $transformProperties = array(); // Store all transform values
386
387 switch ( $property ) {
388 case 'move':
389 $transformProperties[] = $this->updateTranslateCSSString( $end );
390 break;
391 case 'rotate':
392 $transformProperties[] = $this->updateRotateCSSString( $end );
393 break;
394 case 'scale':
395 $transformProperties[] = $this->updateScaleCSSString( $end );
396 break;
397 case 'size':
398 $css .= 'width: ' . ( $end['width']['value'] === 'scrollWidth' ? 'auto' : $this->addUnit( $end['width'] ) ) . ';';
399 $css .= 'height: ' . ( $end['height']['value'] === 'scrollHeight' ? 'auto' : $this->addUnit( $end['height'] ) ) . ';';
400 break;
401 case 'skew':
402 $transformProperties[] = $this->updateSkewCSSString( $end );
403 break;
404 case 'fade':
405 $css .= 'opacity: ' . $end['value'] . ';';
406 break;
407 case 'color':
408 $css .= 'color: ' . $end['value'] . '; fill: ' . $end['value'] . ';';
409 break;
410 case 'border-color':
411 $css .= 'border-color: ' . $end['value'] . ';';
412 break;
413 case 'border-radius':
414 $css .= 'border-top-left-radius: ' . $this->addUnit( $end['border-top-left-radius'] ) . ';';
415 $css .= 'border-top-right-radius: ' . $this->addUnit( $end['border-top-right-radius'] ) . ';';
416 $css .= 'border-bottom-right-radius: ' . $this->addUnit( $end['border-bottom-right-radius'] ) . ';';
417 $css .= 'border-bottom-left-radius: ' . $this->addUnit( $end['border-bottom-left-radius'] ) . ';';
418 break;
419 case 'background-color':
420 $css .= 'background: ' . $end['value'] . ';';
421 break;
422 case 'background-size-position':
423 $css .= 'background-size: ' . $this->addUnit( $end['sizeWidth'] ) . ' ' . $this->addUnit( $end['sizeHeight'] ) . ';';
424 $css .= 'background-position-x: ' . $this->addUnit( $end['positionX'] ) . ';';
425 $css .= 'background-position-y: ' . $this->addUnit( $end['positionY'] ) . ';';
426 break;
427 case 'background-size':
428 $css .= 'background-size: ' . $this->addUnit( $end['sizeWidth'] ) . ' ' . $this->addUnit( $end['sizeHeight'] ) . ';';
429 break;
430 case 'background-position':
431 $css .= 'background-position-x: ' . $this->addUnit( $end['positionX'] ) . ';';
432 $css .= 'background-position-y: ' . $this->addUnit( $end['positionY'] ) . ';';
433 break;
434 case 'filter':
435 $filters = array();
436 foreach ( $end as $key => $value ) {
437 $filters[] = "$key(" . $this->addUnit( $value ) . ')';
438 }
439 $css .= 'filter: ' . implode( ' ', $filters ) . ';';
440 break;
441
442 default:
443 break;
444 }
445
446 if ( ! empty( $transformProperties ) ) {
447 $css .= implode( ' ', array_unique( $transformProperties ) );
448 $transformProperties = array(); // Reset for the next element
449 }
450
451 return $css;
452 }
453
454
455 /**
456 * Get the style tag string
457 *
458 * @param array|bool $blocks all styleblocks.
459 * @return string
460 */
461 public function getStyleTag( $blocks = false ) {
462 if ( ! $blocks ) {
463 $blocks = $this->style_blocks;
464 }
465
466 $s = '';
467
468 if ( count( $this->ele_variable_modes ) > 0 ) {
469 $s .= $this->getElementWiseVariableCssCodes();
470 }
471
472 if ( $blocks ) {
473 foreach ( $this->view_ports as $key => $vp ) {
474 $css = '';
475 foreach ( $blocks as $style_block ) {
476 $css .= $this->getRawCssDeviceWise( $style_block, $vp );
477 }
478 if ( $css ) {
479 $s .= "<style data='" .'kirki-element-styles-' . $key . "'>";
480 $s .= $css;
481 $s .= '</style>';
482 }
483 }
484 }
485 return $s;
486 }
487
488 public function get_interaction_set_as_initial_css() {
489 $s = '';
490 if ( count( $this->interaction_preset_and_text_animation_tracker ) > 0 || count( $this->interaction_library_text_animation_tracker ) > 0 ) {
491 $animation_css = '';
492 foreach ( $this->interaction_preset_and_text_animation_tracker as $ele_id => $bool ) {
493 $animation_css .= "[data-kirki='" . $ele_id . "'] { visibility: hidden; }";
494 }
495
496 foreach ( $this->interaction_library_text_animation_tracker as $ele_id => $devices ) {
497 $css_value = "[data-kirki='" . $ele_id . "'] { visibility: hidden; }";
498 $media_queries = ! empty( $devices ) ? $this->setMediaQuery( $css_value, $devices ) : '';
499 $animation_css .= ! empty( $media_queries ) ? $media_queries : $css_value;
500 }
501
502 if ( $animation_css ) {
503 $s .= "<style data='kirki-element-animation-visibility'>";
504 $s .= $animation_css;
505 $s .= '</style>';
506 }
507 }
508
509 if ( $this->scroll_into_custom_interaction_tracker ) {
510 $s .= "<style data='kirki-scroll-into-custom-animation'>";
511 $s .= $this->scroll_into_custom_interaction_tracker;
512 $s .= '</style>';
513 }
514
515 if ( $this->track_animation_for_elements_with_this_class ) {
516 $s .= "<style data='kirki-custom-animation-for-all-elements-with-same-class'>";
517 $s .= $this->track_animation_for_elements_with_this_class;
518 $s .= '</style>';
519 }
520
521 if ( $this->track_animation_for_children_with_this_class ) {
522 $s .= "<style data='kirki-custom-animation-for-all-children-with-same-class'>";
523 $s .= $this->track_animation_for_children_with_this_class;
524 $s .= '</style>';
525 }
526
527 if ( $this->track_animation_for_sibling_with_this_class ) {
528 $s .= "<style data='kirki-custom-animation-for-all-sibling-with-same-class'>";
529 $s .= $this->track_animation_for_sibling_with_this_class;
530 $s .= '</style>';
531 }
532
533 if ( $this->track_animation_for_trigger_sibling_with_this_class ) {
534 $s .= "<style data='kirki-custom-animation-for-trigger-sibling-with-same-class'>";
535 $s .= $this->track_animation_for_trigger_sibling_with_this_class;
536 $s .= '</style>';
537 }
538
539 if ( $this->track_animation_for_trigger_with_this_class ) {
540 $s .= "<style data='kirki-custom-animation-for-trigger-with-same-class'>";
541 $s .= $this->track_animation_for_trigger_with_this_class;
542 $s .= '</style>';
543 }
544
545 return $s;
546 }
547
548 private function setInitialAnimation( $ele_id, $animations, $single_res_value ) {
549 $elementStyleValues = array();
550 $hasClassName = false;
551 $css = '';
552 $media_queries = '';
553
554 foreach ( $animations as $ani_key => $animation ) {
555 if ( ! empty( $animation['setAsInitial'] ) && $animation['setAsInitial'] ) {
556
557 foreach ( $this->processElementStyles( $ele_id, $animation ) as $key => $value ) {
558 if ( ! isset( $elementStyleValues[ $key ] ) ) {
559 $elementStyleValues[ $key ] = array(
560 'transform' => $value['transform'] ?? array(),
561 'other' => $value['other'] ?? array(),
562 );
563 } else {
564 $elementStyleValues[ $key ]['transform'] = array_merge(
565 $elementStyleValues[ $key ]['transform'] ?? array(),
566 $value['transform'] ?? array()
567 );
568 $elementStyleValues[ $key ]['other'] = array_merge(
569 $elementStyleValues[ $key ]['other'] ?? array(),
570 $value['other'] ?? array()
571 );
572 }
573 }
574 }
575 }
576
577 if ( ! empty( $elementStyleValues ) ) {
578 $css .= $this->setAnimationStyles( $elementStyleValues, $hasClassName );
579 $media_queries = $this->getMediaQuery( $css, $single_res_value );
580 $this->scroll_into_custom_interaction_tracker .= ! empty( $media_queries ) ? $media_queries : $css;
581 }
582 }
583
584
585
586
587
588 private function setAnimationStyles( $elementStyleValues, $hasClassName ) {
589 $s = '';
590 foreach ( $elementStyleValues as $ele_id => $styles ) {
591 if ( empty( $styles['transform'] ) && empty( $styles['other'] ) ) {
592 continue;
593 }
594
595 $css = $hasClassName ? '' : "[data-kirki='$ele_id'] { ";
596 $css .= ! empty( $styles['transform'] ) ? 'transform: ' . implode( ' ', $styles['transform'] ) . '; ' : '';
597 $css .= ! empty( $styles['other'] ) ? implode( ' ', $styles['other'] ) : '';
598 $css .= $hasClassName ? '' : ' }';
599
600 $s .= $css;
601 }
602 return $s;
603 }
604
605 private function processElementStyles( $ele_id, $animation ) {
606 $elementStyles = array(); // Ensure the array is initialized properly
607
608 if ( ! isset( $elementStyles[ $ele_id ] ) ) {
609 $elementStyles[ $ele_id ] = array(
610 'transform' => array(),
611 'other' => array(),
612 );
613 }
614
615 if ( ! isset( $animation['property'] ) || ! isset( $animation['end'] ) ) {
616 return array(); // Skip invalid animations
617 }
618
619 $cssValue = $this->updateCssStringForAnimation( $animation['property'], $animation['end'] );
620 if ( ! empty( $cssValue ) ) {
621
622 switch ( $animation['property'] ) {
623 case 'move':
624 case 'rotate':
625 case 'scale':
626 case 'skew':
627 $elementStyles[ $ele_id ]['transform'][] = $cssValue;
628 break;
629 default:
630 $elementStyles[ $ele_id ]['other'][] = $cssValue;
631 break;
632 }
633 }
634 return $elementStyles;
635 }
636
637
638 /**
639 * Get the html string
640 *
641 * @param array $block single style block.
642 * @param array $vp viewport.
643 *
644 * @return string
645 */
646 private function getRawCssDeviceWise( $block, $vp ) {
647 $css_string = '';
648 $selector = $this->getSelectorFromBlock( $block );
649 $variants = $block['variant'];
650
651 $global_css = "";
652 $local_css = "";
653
654 foreach ( $variants as $key => $value ) {
655 $variant = explode( '_', $key );
656 if ( $variant[0] === $vp['id'] ) {
657 if( $block && isset($block['isGlobalStyle']) && $block['isGlobalStyle'] === true){
658 $global_css .= $this->createMediaQueryString( $selector, $key, $value, $vp );
659 }else{
660 $local_css .= $this->createMediaQueryString( $selector, $key, $value, $vp );
661 }
662 }
663 }
664 return $global_css . $local_css;
665 }
666
667 /**
668 * Create Media Query String
669 *
670 * @param string $selector calss/tag selector like => .class-name.
671 * @param string $device_name media query key like => md.
672 * @param string $css_text css text like => color:red.
673 * @param object $vp current viewport object.
674 *
675 * @return string
676 */
677 private function createMediaQueryString( $selector, $device_name, $css_text, $vp ) {
678 if ( $css_text === '' ) {
679 return '';
680 }
681 $css_string = '';
682 $variant = explode( '_', $device_name );
683 $type = $vp['type'];
684 if ( count( $variant ) === 1 ) {
685 // default class or sub class for devices.
686 if ( $variant[0] === 'md' ) {
687 $css_string .= $selector . '{' . $css_text . '}';
688 } elseif ( isset( $this->view_ports[ $variant[0] ] ) ) {
689 $css_string .= '@media only screen and (' . $type . '-width: ' . $this->view_ports[ $variant[0] ][ $type . 'Width' ] . 'px) {' . $selector . '{' . $css_text . '}}';
690 }
691 } elseif ( count( $variant ) === 2 ) {
692 // active, hover, focus or pseudo class.
693 $psuedo_class = $this->checkIfHasPsuedoClassValue( $variant[1] );
694 if ( $variant[0] === 'md' ) {
695 $css_string .= $selector . $psuedo_class . '{' . $css_text . '}';
696 } elseif ( isset( $this->view_ports[ $variant[0] ] ) ) {
697 $css_string .= '@media only screen and (' . $type . '-width: ' . $this->view_ports[ $variant[0] ][ $type . 'Width' ] . 'px) {';
698 $css_string .= $selector . $psuedo_class . '{' . $css_text . '}';
699 $css_string .= '}';
700 }
701 } elseif ( count( $variant ) === 3 ) {
702 // active, hover, focus + pseudo class.
703 $psuedo_class1 = $this->checkIfHasPsuedoClassValue( $variant[1] );
704 $psuedo_class2 = $this->checkIfHasPsuedoClassValue( $variant[2] );
705
706 if ( $variant[0] === 'md' ) {
707 $css_string .= $selector . $psuedo_class1 . $psuedo_class2 . '{' . $css_text . '}';
708 } elseif ( isset( $this->view_ports[ $variant[0] ] ) ) {
709 $css_string .= '@media only screen and (' . $type . '-width: ' . $this->view_ports[ $variant[0] ][ $type . 'Width' ] . 'px) {';
710 $css_string .= $selector . $psuedo_class1 . $psuedo_class2 . '{' . $css_text . '}';
711 $css_string .= '}';
712 }
713 }
714 return $css_string;
715 }
716
717 /**
718 * Get the custom fonts links
719 *
720 * @return string
721 */
722 public function getCustomFontsLinks() {
723 $post_id = $this->symbol_id ? $this->symbol_id : HelperFunctions::get_post_id_if_possible_from_url();
724 $post = get_post( $post_id );
725
726 if ( ! $post ) {
727 return '';
728 }
729
730 $s = '';
731 if ( 'kirki_symbol' === $post->post_type ) {
732 $symbol = Symbol::get_single_symbol( $post_id, true );
733 if ( isset( $symbol['symbolData'], $symbol['symbolData']['customFonts'] ) ) {
734 foreach ( $symbol['symbolData']['customFonts'] as $key => $f ) {
735 $s .= HelperFunctions::getFontsHTMLMarkup( $f );
736 }
737 }
738 }
739 return $s;
740 }
741
742 /**
743 * Get the script tag string
744 *
745 * @return string
746 */
747 public function getScriptTag( $should_take_app_script = true ) {
748 $s = '';
749 $empty_vars = $this->getVariableString( 'Sliders', $this->sliders );
750 $empty_vars .= $this->getVariableString( 'Maps', $this->maps );
751 $empty_vars .= $this->getVariableString( 'Lotties', $this->lotties );
752 $empty_vars .= $this->getVariableString( 'Popups', $this->popups );
753 $empty_vars .= $this->getVariableString( 'Lightboxes', $this->lightboxes );
754 $empty_vars .= $this->getVariableString( 'ReCaptchas', $this->re_captchas );
755 $empty_vars .= $this->getVariableString( 'Videos', $this->videos );
756 $empty_vars .= $this->getVariableString( 'Tabs', $this->tabs );
757 $empty_vars .= $this->getVariableString( 'Interactions', $this->interactions );
758 $empty_vars .= $this->getVariableString( 'InteractionLibrary', $this->interaction_library );
759 $empty_vars .= $this->getVariableString( 'Collections', $this->collections );
760 $empty_vars .= $this->getVariableString( 'Forms', $this->forms );
761 $empty_vars .= $this->getVariableString( 'Dropdown', $this->dropdown );
762 $empty_vars .= $this->getVariableString( 'Navigations', $this->navigations );
763 $empty_vars .= $this->getVariableString( 'NavigationItem', $this->navigation_item );
764 $empty_vars .= $this->getVariableString( 'Inputs', $this->inputs );
765 $empty_vars .= $this->getVariableString( 'LiquidGlass', $this->liquid_glass );
766
767 if ( $empty_vars ) {
768 $s .= "<script data='kirki-elements-property-vars'>";
769 $s .= $empty_vars;
770 $s .= '</script>';
771 }
772
773 if ( $this->custom_codes ) {
774 $s .= "<script data='kirki-elements-property-dev-mode'>";
775 $s .= $this->custom_codes;
776 $s .= '</script>';
777 }
778
779 $updatedScriptTags = false;
780 if ( $should_take_app_script ) {
781 $updatedScriptTags = apply_filters('kirki_add_script_tags', $s );
782 }
783
784 if ( $updatedScriptTags ) {
785 $s = $updatedScriptTags;
786 }
787
788 return $s;
789 }
790
791 private function getVariableString( $name, $value ) {
792 if ( count( $value ) === 0 ) {
793 return '';
794 }
795 $prefix = 'kirki';
796 $value = wp_json_encode( $value );
797 $s = "var $prefix$name = window.$prefix$name === undefined? $value : {...$prefix$name, ...$value};";
798 return $s;
799 }
800
801 /**
802 * Returns the JS code with `<script>` tag user put into `Page settings`
803 *
804 * @return string
805 */
806 public static function getBodyCustomCode() {
807 $post_id = HelperFunctions::get_post_id_if_possible_from_url();
808 $custom_code = get_post_meta( $post_id, KIRKI_PAGE_CUSTOM_CODE, true );
809 $code = "<div data-kirki-code='custom-code'>";
810
811 if ( isset( $custom_code['body'], $custom_code['body']['value'] ) ) {
812 $code .= $custom_code['body']['value'];
813 }
814 $code .= '</div>';
815 return $code;
816 }
817
818 /**
819 * Returns the css code with `<style>` tag user put into `Page settings`
820 *
821 * @return string
822 */
823 public static function getHeadCustomCode() {
824 $post_id = HelperFunctions::get_post_id_if_possible_from_url();
825 $custom_code = get_post_meta( $post_id, KIRKI_PAGE_CUSTOM_CODE, true );
826 $code = '';
827 if ( isset( $custom_code['head'], $custom_code['head']['value'] ) ) {
828 $code .= "<meta data-kirki-code='start' />";
829 $code .= $custom_code['head']['value'];
830 $code .= "<meta data-kirki-code='end' />";
831 }
832 return $code;
833 }
834
835 public function getElementWiseVariableCssCodes() {
836 $s = '';
837 foreach ( $this->ele_variable_modes as $ele_id => $modes ) {
838 // if ( $mode === 'inherit' ) {
839 // continue;
840 // }
841 $s .= self::getVariableCssCode( $ele_id, '[data-kirki="' . $ele_id . '"]', $modes );
842 }
843 return $s;
844 }
845
846 public static function getVariableCssCode( $key = 'global', $selector = ':root', $modes = [], $style_tag = true ) {
847 $modes = HelperFunctions::normalize_variable_mode( $modes );
848 $s = '';
849 foreach ( $modes as $mode_type => $mode ) {
850 $s .= self::generateSingleVariableModeCssCode($mode_type, $key, $selector, $mode, $style_tag );
851 }
852 return $s;
853 }
854
855 private static function generateSingleVariableModeCssCode($mode_type,$key = 'global', $selector = ':root', $mode=false, $style_tag = true){
856 $k = "$key-$selector-$mode";
857 $variables = UserData::get_kirki_variable_data();
858
859 if ( ! $mode || $mode === 'inherit' ) {
860 $mode = 'default';
861 }
862
863 $s = $style_tag ? "<style id='kirki-variables-" . $key . "'>".$selector."{" : $selector."{";
864 $view_ports = UserData::get_view_port_list();
865
866 $text_style_css = "";
867
868 foreach ($variables['data'] as $key2 => $group) {
869 if($group['key'] !== $mode_type) {
870 continue;
871 }
872 foreach ( $group['variables'] as $key3 => $variable) {
873 if ( ! isset( $variable['value'][ $mode ] ) ) {
874 continue;
875 }
876
877 $name = '--' . $variable['id'];
878
879 switch ( $variable['type'] ) {
880 case 'size':
881 $s .= "$name:" . $variable['value'][$mode]['value'] . $variable['value'][$mode]['unit'] . ";";
882 break;
883 case 'font-family':
884 $font_value = $variable['value'][$mode];
885 if (strpos($font_value, '"') === 0 || strpos($font_value, "'") === 0) {
886 $s .= "$name:" . $font_value . ";";
887 } else {
888 $s .= "$name:\"" . $font_value . "\";";
889 }
890 // $s .= "$name:\"" . $variable['value'][$mode] . "\";";
891 break;
892 case 'color':
893 $s .= "$name:" . $variable['value'][$mode] . ";";
894 break;
895 case 'text-style':
896 $text_style_css .= self::buildTextStyleCss( $variable, $mode, $view_ports );
897 break;
898 }
899 }
900 }
901
902 if($selector && $selector !== ':root'){
903 $text_style_css = "$selector{$text_style_css}";
904 }
905
906 $s .= $style_tag ? "}$text_style_css</style>" : "}$text_style_css";
907
908 self::$printed_variable_tracker[ $k ] = true;
909 return $s;
910 }
911
912 /**
913 * Build CSS rules for a single text-style variable.
914 *
915 * @param array $variable The text-style variable data.
916 * @param string $mode The active mode key.
917 * @param array $view_ports The viewport configuration list.
918 *
919 * @return string
920 */
921 private static function buildTextStyleCss( $variable, $mode, $view_ports ) {
922 $css = '';
923 $mode_data = $variable['value'][ $mode ];
924 $var_id = $variable['id'];
925 $font_family = isset( $mode_data['font-family'] ) ? $mode_data['font-family'] : '';
926 $styles = isset( $mode_data['styles'] ) ? $mode_data['styles'] : array();
927 $ts_selector = '[data-text_style="' . $var_id . '"]';
928
929 foreach ( $styles as $device => $props ) {
930 $css_declarations = '';
931
932 if ( $device === 'md' && $font_family ) {
933 $css_declarations .= 'font-family:' . $font_family . ';';
934 }
935
936 foreach ( $props as $prop_name => $prop_value ) {
937
938 // font-feature-settings
939 if (
940 $prop_name === 'font-feature-settings' &&
941 is_array( $prop_value ) &&
942 isset( $prop_value['value'] ) &&
943 is_array( $prop_value['value'] )
944 ) {
945 $css_string = implode(
946 ', ',
947 array_map(
948 function( $key ) {
949 return '"' . $key . '"';
950 },
951 array_keys( $prop_value['value'] )
952 )
953 );
954
955 $css_declarations .= $prop_name . ':' . $css_string . ';';
956 continue;
957 }
958
959 // value + unit object
960 if (
961 is_array( $prop_value ) &&
962 isset( $prop_value['value'], $prop_value['unit'] )
963 ) {
964
965 // font-size clamp
966 if (
967 $prop_name === 'font-size' &&
968 isset( $prop_value['type'] ) &&
969 str_contains( $prop_value['type'], 'clamp-' )
970 ) {
971
972 $min_value = isset($prop_value['min'], $prop_value['min']['value'] ) ? $prop_value['min']['value'] : 0;
973 $min_unit = isset($prop_value['min'], $prop_value['min']['unit'] ) ? $prop_value['min']['unit'] : 'px';
974
975 $base_value = isset($prop_value['base'], $prop_value['base']['value'] ) ? $prop_value['base']['value'] : 0;
976 $base_unit = isset($prop_value['base'], $prop_value['base']['unit'] ) ? $prop_value['base']['unit'] : 'px';
977
978 $max_value = isset($prop_value['max'], $prop_value['max']['value'] ) ? $prop_value['max']['value'] : 0;
979 $max_unit = isset($prop_value['max'], $prop_value['max']['unit'] ) ? $prop_value['max']['unit'] : 'px';
980
981 $css_declarations .= "{$prop_name}:clamp({$min_value}{$min_unit}, {$base_value}{$base_unit}, {$max_value}{$max_unit});";
982
983 // special max-width values
984 } elseif (
985 $prop_name === 'max-width' &&
986 in_array( $prop_value['value'], array( 'none', 'fit-content', 'max-content', 'min-content' ), true )
987 ) {
988
989 $css_declarations .= $prop_name . ':' . $prop_value['value'] . ';';
990
991 } else {
992
993 $css_declarations .= $prop_name . ':' . $prop_value['value'] . $prop_value['unit'] . ';';
994 }
995
996 // string or number
997 } elseif (
998 ( is_string( $prop_value ) && $prop_value !== '' ) ||
999 is_numeric( $prop_value )
1000 ) {
1001
1002 $css_declarations .= $prop_name . ':' . $prop_value . ';';
1003 }
1004 }
1005
1006 if ( empty( $css_declarations ) ) {
1007 continue;
1008 }
1009
1010 $rule = $ts_selector . '{' . $css_declarations . '}';
1011
1012 if ( $device === 'md' ) {
1013 $css .= $rule;
1014 } elseif ( isset( $view_ports[ $device ] ) ) {
1015 $max_width = $view_ports[ $device ]['maxWidth'];
1016 $css .= '@media only screen and (max-width:' . $max_width . 'px){' . $rule . '}';
1017 }
1018 }
1019
1020 return $css;
1021 }
1022
1023 /**
1024 * Returns `<meta>` tag with provided content
1025 *
1026 * @param string $name The meta name.
1027 * @param string $content The meta description.
1028 * @return string The `<meta />` tag.
1029 */
1030 private static function getMeta( $name, $content ) {
1031 // Replace &nbsp; and \u00a0 with a space
1032 $content = preg_replace( '/(?:&nbsp;|\x{00a0})/u', ' ', $content );
1033
1034 // Escape double quotes in the content to avoid breaking the HTML attribute
1035 $content = htmlspecialchars( $content, ENT_QUOTES, 'UTF-8' );
1036
1037 return "<meta name=\"{$name}\" content=\"{$content}\" />";
1038 }
1039
1040
1041 /**
1042 * All meta tags
1043 *
1044 * @return string
1045 */
1046 public static function getSeoMetaTags( $post_id = null ) {
1047 $post_id = $post_id ? $post_id : HelperFunctions::get_post_id_if_possible_from_url();
1048 $seo_settings = false;
1049
1050 // get set settings from template if current page is kirki template
1051 $template_data = HelperFunctions::get_template_data_if_current_page_is_kirki_template();
1052 if ( $template_data ) {
1053 $seo_settings = get_post_meta( $template_data['template_id'], KIRKI_PAGE_SEO_SETTINGS_META_KEY, true );
1054 } else {
1055 $seo_settings = get_post_meta( $post_id, KIRKI_PAGE_SEO_SETTINGS_META_KEY, true );
1056 }
1057
1058 // $title = esc_html(get_the_title());
1059 $meta_tags = '';
1060
1061 // if ( $seo_settings && $seo_settings['seoSettings'] && $seo_settings['seoSettings']['seoTitleTag'] && $seo_settings['seoSettings']['seoTitleTag']['value'] ) {
1062 // $seo_title = self::getSeoValue($post_id, $seo_settings['seoSettings']['seoTitleTag']['value']);
1063 // $title = $seo_title;
1064 // $meta_tags .= self::getMeta('title', $seo_title);
1065 // }
1066
1067 // $meta_tags .= '<title>' . $title . '</title>';
1068
1069 if ( $seo_settings && $seo_settings['seoSettings'] && $seo_settings['seoSettings']['seoMetaDesc'] && $seo_settings['seoSettings']['seoMetaDesc']['value'] ) {
1070 $seo_meta_desc = self::getSeoValue( $post_id, $seo_settings['seoSettings']['seoMetaDesc']['value'] );
1071 $meta_tags .= self::getMeta( 'description', $seo_meta_desc );
1072 }
1073
1074 if ( $seo_settings && $seo_settings['openGraph'] && $seo_settings['openGraph']['openGraphImage'] && $seo_settings['openGraph']['openGraphImage']['value'] ) {
1075 $seo_open_graph_image = self::getSeoValue( $post_id, $seo_settings['openGraph']['openGraphImage']['value'] );
1076
1077 $meta_tags .= self::getMeta( 'og:image', $seo_open_graph_image );
1078 $meta_tags .= self::getMeta( 'twitter:card', 'summary_large_image' );
1079 $meta_tags .= self::getMeta( 'twitter:image', $seo_open_graph_image );
1080 }
1081
1082 if ( $seo_settings && $seo_settings['openGraph'] && $seo_settings['openGraph']['openGraphTitle'] ) {
1083 // If og:title is sameAsSeoTitle.
1084 if (
1085 $seo_settings['openGraph']['openGraphTitle']['sameAsSeoTitle'] &&
1086 $seo_settings && $seo_settings['seoSettings'] && $seo_settings['seoSettings']['seoTitleTag'] && $seo_settings['seoSettings']['seoTitleTag']['value']
1087 ) {
1088 $seo_title = self::getSeoValue( $post_id, $seo_settings['seoSettings']['seoTitleTag']['value'] );
1089
1090 $meta_tags .= self::getMeta( 'og:title', $seo_title );
1091 $meta_tags .= self::getMeta( 'twitter:title', $seo_title );
1092 } elseif ( $seo_settings['openGraph']['openGraphTitle']['value'] ) {
1093
1094 $og_title = self::getSeoValue( $post_id, $seo_settings['openGraph']['openGraphTitle']['value'] );
1095
1096 $meta_tags .= self::getMeta( 'og:title', $og_title );
1097 $meta_tags .= self::getMeta( 'twitter:title', $og_title );
1098 }
1099 }
1100
1101 if ( $seo_settings && $seo_settings['openGraph'] && $seo_settings['openGraph']['openGraphDesc'] ) {
1102 // If og:description is sameAsSeoMeta.
1103 if (
1104 $seo_settings['openGraph']['openGraphDesc']['sameAsSeoMeta'] &&
1105 $seo_settings && $seo_settings['seoSettings'] && $seo_settings['seoSettings']['seoMetaDesc'] && $seo_settings['seoSettings']['seoMetaDesc']['value']
1106 ) {
1107 $seo_meta_desc = self::getSeoValue( $post_id, $seo_settings['seoSettings']['seoMetaDesc']['value'] );
1108
1109 $meta_tags .= self::getMeta( 'og:description', $seo_meta_desc );
1110 $meta_tags .= self::getMeta( 'twitter:description', $seo_meta_desc );
1111 } elseif ( $seo_settings['openGraph']['openGraphDesc']['value'] ) {
1112
1113 $og_meta_des = self::getSeoValue( $post_id, $seo_settings['openGraph']['openGraphDesc']['value'] );
1114
1115 $meta_tags .= self::getMeta( 'og:description', $og_meta_des );
1116 $meta_tags .= self::getMeta( 'twitter:description', $og_meta_des );
1117 }
1118 }
1119
1120 $meta_tags .= self::getMeta( 'og:url', get_permalink( $post_id ) );
1121 return $meta_tags;
1122 }
1123
1124 /**
1125 * Get the seo title
1126 *
1127 * @param int $post_id post id.
1128 * @param string|array $value seo title tag value.
1129 *
1130 * @return string
1131 */
1132
1133 public static function getSeoValue( $post_id, $value ) {
1134 $seo_value = '';
1135
1136 if ( is_array( $value ) ) {
1137 $post = get_post( $post_id );
1138 $post_parent_id = null;
1139 // if template preview open
1140 if ( $post->post_type && str_contains( $post->post_type,'kirki_template' ) ) {
1141 // get post condition
1142 $post_conditions = get_post_meta( $post->ID,'kirki_template_conditions', true ); // kirki_cm_parentId
1143 $condition = $post_conditions[0];
1144 if (
1145 isset( $condition['post_type'] ) &&
1146 ! empty( $condition['post_type'] )
1147 ) {
1148 if ( strpos( $condition['post_type'], KIRKI_CONTENT_MANAGER_PREFIX ) !== false ) {
1149 // content manager related post
1150 $post_parent_id = str_replace( KIRKI_CONTENT_MANAGER_PREFIX . '_', '', $condition['post_type'] );
1151
1152 $args = array(
1153 'post_parent' => $post_parent_id,
1154 'page' => 1,
1155 'posts_per_page' => 1,
1156 );
1157
1158 $res = ContentManagerHelper::get_all_child_items( $args );
1159
1160 if ( $res && $res[0] ) {
1161 $post = (object) $res[0];
1162 }
1163 }
1164 }
1165 }
1166
1167 foreach ( $value as $key => $val ) {
1168 $option_type = $val['type'];
1169 $option_value = $val['value'];
1170
1171 if ( $option_type === 'text' ) {
1172 $seo_value .= $option_value;
1173 } elseif ( $option_type === 'post' ) {
1174 // post author
1175 if ( isset( $post->$option_value ) && $option_value === 'post_author' ) {
1176 $seo_value .= get_the_author_meta( 'display_name', $post->post_author );
1177 } elseif ( $option_value === 'post_id' ) {
1178 $seo_value .= isset( $post->ID ) ? $post->ID : '';
1179 } else {
1180 $seo_value .= isset( $post->$option_value ) ? $post->$option_value : '';
1181 }
1182 } elseif ( $option_type === KIRKI_CONTENT_MANAGER_PREFIX . '_field' ) {
1183 $meta_key = ContentManagerHelper::get_child_post_meta_key_using_field_id( $post->post_parent, $option_value );
1184
1185 $post_meta_value = get_post_meta( $post->ID, $meta_key, true );
1186
1187 // for image field
1188 if ( $post_meta_value && isset( $post_meta_value['url'] ) ) {
1189 $post_meta_value = $post_meta_value['url'];
1190 }
1191
1192 $seo_value .= wp_strip_all_tags( $post_meta_value );
1193 } elseif ( $option_type === 'featured_image' ) {
1194 $seo_value .= get_the_post_thumbnail_url( $post->ID );
1195 } elseif ( $option_type === 'user' ) {
1196 $user_id = HelperFunctions::get_user_id_if_possible_from_url();
1197
1198 if ( $option_value === 'user_id' ) {
1199 $seo_value .= $user_id;
1200 } elseif ( $option_value === 'user_name' ) {
1201 $seo_value .= get_the_author_meta( 'display_name', $user_id );
1202 } else {
1203 $seo_value .= get_the_author_meta( $option_value, $user_id );
1204 }
1205 } elseif ( $option_type === 'term' ) {
1206 $term_id = HelperFunctions::get_term_id_if_possible_from_url();
1207 if ( $term_id ) {
1208 $term = get_term( $term_id );
1209
1210 if ( $option_value === 'term_id' ) {
1211 $seo_value .= $term->term_id;
1212 } elseif ( $option_value === 'term_name' ) {
1213 $seo_value .= $term->name;
1214 } elseif ( $option_value === 'term_slug' ) {
1215 $seo_value .= $term->slug;
1216 }
1217 }
1218 }
1219 }
1220 } else {
1221 $seo_value = $value;
1222 }
1223
1224 $seo_value = do_shortcode( $seo_value );
1225
1226 return $seo_value;
1227 }
1228
1229 /**
1230 * Check if has pseudo class value
1231 *
1232 * @param string $s pseudo class name like => active, hover, focus, child(5th).
1233 *
1234 * @return string
1235 */
1236 private function checkIfHasPsuedoClassValue( $s ) {
1237 if ( strpos( $s, '-----' ) !== false ) {
1238 $s_arr = explode( '-----', $s );
1239 $s = $s_arr[0] . '(' . $s_arr[1] . ')';
1240 }
1241 if(in_array($s, KIRKI_PRESERVED_CLASS_LIST)){
1242 return ".$s";
1243 }
1244 $pseudoClass = str_contains( $s, 'before' ) || str_contains( $s, 'after' ) || str_contains( $s, 'placeholder' ) ? '::' . $s : ':' . $s;
1245 return $pseudoClass;
1246 }
1247
1248
1249 /**
1250 * Get the selector from block
1251 *
1252 * @param array $block single style block.
1253 *
1254 * @return string
1255 */
1256 private function getSelectorFromBlock( $block ) {
1257 $selector = '';
1258 if ( isset( $block['type'] ) && $block['type'] === 'class' ) {
1259 $block['name'] = HelperFunctions::add_prefix_to_class_name( $this->prefix, $block['name'] );
1260 if ( is_array( $block['name'] ) ) {
1261 $selector = '.' . str_replace( ' ', '.', $this->makeClassStringFromArray( $block['name'] ) );
1262 } else {
1263 $selector = '.' . $this->makeClassStringFromArray( array( $block['name'] ) );
1264 }
1265 } elseif ( isset( $block['type'] ) && $block['type'] === 'tag' ) {
1266 $selector = isset( $block['tag'] ) ? $block['tag'] : ( isset( $block['name'] ) ? $block['name'] : '' );
1267 }
1268 return $selector;
1269 }
1270
1271 /**
1272 * Insert element related data if needed
1273 *
1274 * @param array $element single element data.
1275 * @return void
1276 */
1277 private function insertElementRelatedConfig( $element, $options = array() ) {
1278 if ( ! $element || !isset( $element['id'] ) ) {
1279 return;
1280 }
1281 $id = $element['id'];
1282 if ( isset( $element['properties'] ) ) {
1283 $properties = $element['properties'];
1284
1285 if ( isset( $properties['interactions'] ) ) {
1286 $this->interactions[ $id ] = $this->updateClassListForInteractionFromStyleBlockId( $properties['interactions'], $element );
1287 }
1288 if(isset($properties['interactionLibrary'])) {
1289 $this->interaction_library[ $id ] = $this->updateStylesForInteractionLibrary( $properties['interactionLibrary'], $element );
1290 }
1291 if ( isset( $properties['code'], $properties['code']['javascript'] ) ) {
1292 $this->custom_codes .= str_replace( 'KIRKI_TARGET_ELEMENT_ID', $id, $properties['code']['javascript'] );
1293 }
1294
1295 if ( isset( $properties['variableMode'] ) ) {
1296 $this->ele_variable_modes[ $id ] = $properties['variableMode'];
1297 }
1298
1299 // Store slider properties.
1300 if ( $element['name'] === 'slider' ) {
1301 $this->sliders[ $id ] = $properties['slider'];
1302 }
1303
1304 if ( $element['name'] === 'popup' ) {
1305 $this->popups[ $id ] = $properties['popup'];
1306 }
1307
1308 if ( $element['name'] === 'map' || $element['name'] === 'google-map' ) {
1309 $this->maps[ $id ] = $properties['map'];
1310 }
1311
1312 if ( $element['name'] === 'video' ) {
1313 $this->videos[ $id ] = $properties['attributes'];
1314 }
1315 // Store Lottie properties.
1316 if ( $element['name'] === 'lottie' ) {
1317 $this->lotties[ $id ] = $properties['lottie'];
1318 }
1319 // Store Lottie properties.
1320 if ( $element['name'] === 'dropdown' ) {
1321 $this->dropdown[ $id ] = $properties['dropdown'];
1322 }
1323
1324 if ( $element['name'] === 'tabs' ) {
1325 $this->tabs[ $id ]['active_tab'] = $properties['active_tab'];
1326 $this->tabs[ $id ]['animationName'] = $properties['animationName'];
1327 $this->tabs[ $id ]['easing'] = isset( $properties['easing'] ) ? $properties['easing'] : 'ease';
1328 $this->tabs[ $id ]['duration'] = isset( $properties['duration'] ) ? $properties['duration'] : 100;
1329 }
1330
1331 if ( $element['name'] === 'lightbox' ) {
1332 $this->lightboxes[ $id ] = $properties['lightbox'];
1333 }
1334
1335 if ( $element['name'] === 'collection' ) {
1336 $this->collections[$id] = array_merge(
1337 $properties['dynamicContent'],
1338 array(
1339 'isSearchableCollection' => isset($options['search_related_collection_ids'][$id]) ? true : false,
1340 )
1341 );
1342 }
1343
1344 // navigation properties.
1345 if ( $element['name'] === 'navigation' ) {
1346 $this->navigations[ $id ] = $properties['navigation'];
1347 }
1348
1349 // navigation item properties.
1350 if ( $element['name'] === 'navigation-item' ) {
1351 $this->navigation_item[ $id ] = isset( $properties['navigationItem'] ) ? $properties['navigationItem'] : array(); // TODO:
1352 }
1353
1354 // input properties.
1355 if ( $element['name'] === 'input' ) {
1356 $this->inputs[ $id ] = $properties;
1357 }
1358
1359 if (
1360 $element['name'] === 'input' ||
1361 $element['name'] === 'textarea' ||
1362 $element['name'] === 'select' ||
1363 $element['name'] === 'checkbox-element' ||
1364 $element['name'] === 'radio-group' ||
1365 $element['name'] === 'file-upload'
1366 ) {
1367 $parent_form_id = $options['form']['id'] ?? '';
1368 $session_data = HelperFunctions::get_session_data( $parent_form_id );
1369
1370 $type = $element['properties']['attributes']['type'] ?? '';
1371 $others_attributes = array();
1372
1373 if ( 'file-upload' === $element['name'] ) {
1374 $type = 'file';
1375 $others_attributes['max-file-size'] = $element['properties']['maxFileSize'] ?? 2;
1376 }
1377
1378 if ( $session_data && isset( $element['properties']['attributes']['name'] ) ) {
1379 if ( ! isset( $session_data['fields'] ) ) {
1380 $session_data['fields'] = array(
1381 $element['properties']['attributes']['name'] => array_merge(
1382 array(
1383 'type' => $type,
1384 'required' => $element['properties']['attributes']['required'] ?? false,
1385 ),
1386 $element['properties']['attributes'],
1387 $others_attributes,
1388 ),
1389 );
1390 } else {
1391 $session_data['fields'][ $element['properties']['attributes']['name'] ] = array_merge(
1392 array(
1393 'type' => $type,
1394 'required' => $element['properties']['attributes']['required'] ?? false,
1395 ),
1396 $element['properties']['attributes'],
1397 $others_attributes,
1398 );
1399 }
1400
1401 HelperFunctions::set_session_data( $parent_form_id, $session_data );
1402 }
1403 }
1404 if ( $element['name'] === 'form' ) {
1405 $this->forms[ $id ] = array_merge( $properties['form'], $properties['attributes'] );
1406 $this->check_popup_inside_form( $properties['form'] );
1407 HelperFunctions::set_session_data( $id, array_merge( array( 'id' => $id ), $this->forms[ $id ] ) );
1408 }
1409
1410 if ( $element['name'] === 'button' ) {
1411 $this->check_popup_inside_button( $properties );
1412 }
1413
1414 if ( $element['name'] === 'button' || $element['name'] === 'link-text' || $element['name'] === 'link-block' ) {
1415 $this->check_popup_inside_button( $properties );
1416 }
1417
1418 if ( $element['name'] === 'recaptcha' ) {
1419 $common_data = WpAdmin::get_common_data( true );
1420 if ( ! isset( $common_data['recaptcha'], $common_data['recaptcha']['GRC_version'] ) ) {
1421 return;
1422 }
1423 $version = $common_data['recaptcha']['GRC_version'];
1424 $recaptcha = $common_data['recaptcha'][ $version ];
1425
1426 $this->re_captchas[ $id ]['data-version'] = $version;
1427 $this->re_captchas[ $id ]['data-sitekey'] = $recaptcha['GRC_site_key'];
1428 }
1429 }
1430 }
1431
1432 private function getClassNameAndAnimationStyle( $animations, $styleBlockId ) {
1433 if ( ! empty( $this->style_blocks[ $styleBlockId ] ) ) {
1434 $className = $this->style_blocks[ $styleBlockId ]['name'];
1435
1436 if ( $className ) {
1437 // If className is an array, join all values with a dot separator
1438 $classSelector = HelperFunctions::get_selector_from_sb_name( $className );
1439 $innerCSS = '';
1440 foreach ( $animations as $animation ) {
1441 if ( ! empty( $animation['setAsInitial'] ) && $animation['setAsInitial'] ) {
1442 $temp = $this->processElementStyles( $classSelector, $animation );
1443 $innerCSS .= $this->setAnimationStyles( $temp, $hasClassName = true );
1444 }
1445 }
1446 if ( $innerCSS ) {
1447 return "$classSelector { $innerCSS }";
1448 }
1449 }
1450 }
1451
1452 return ''; // Return an empty string if no className is found
1453 }
1454
1455
1456 private function setMediaQuery( $cssValue, $deviceList ) {
1457 $media_queries = '';
1458 $viewPorts = $this->view_ports;
1459
1460 // Sort viewports by value ascending
1461 uasort(
1462 $viewPorts,
1463 function( $a, $b ) {
1464 return $a['value'] <=> $b['value'];
1465 }
1466 );
1467
1468 // Get sorted keys
1469 $orderedKeys = array_keys( $viewPorts );
1470 $ranges = array();
1471 $prev_viewport = null;
1472 $count = 0;
1473
1474 foreach ( $viewPorts as $key => $vp ) {
1475 if ( in_array( $key, $deviceList ) ) {
1476 if ( $count === 0 && $vp['value'] < 1200 ) {
1477 $ranges[] = array(
1478 'min' => null,
1479 'max' => $vp['value'],
1480 );
1481 } elseif ( $vp['value'] < 1200 ) {
1482 $ranges[] = array(
1483 'min' => $prev_viewport['value'] + 1,
1484 'max' => $vp['value'],
1485 );
1486 } elseif ( $count === count( $viewPorts ) - 1 && $vp['value'] > 1200 ) {
1487 $ranges[] = array(
1488 'min' => $vp['value'],
1489 'max' => null,
1490 );
1491 } elseif ( $vp['value'] > 1200 ) {
1492 $ranges[] = array(
1493 'min' => $prev_viewport['value'] + 1,
1494 'max' => $vp['value'],
1495 );
1496 } else {
1497 // this is md device
1498 if ( $count === count( $viewPorts ) - 1 ) {
1499 // thats means: no md upper device like: 1600
1500 $ranges[] = array(
1501 'min' => $prev_viewport['value'] + 1,
1502 'max' => null,
1503 );
1504 } else {
1505 // next viewport
1506 $next_viewport = $viewPorts[ $orderedKeys[ $count + 1 ] ];
1507 $ranges[] = array(
1508 'min' => $prev_viewport['value'] + 1,
1509 'max' => $next_viewport['value'] - 1,
1510 );
1511 }
1512 }
1513 }
1514 $count++;
1515 $prev_viewport = $vp;
1516 }
1517 foreach ( $ranges as $key => $value ) {
1518 $min = $value['min'];
1519 $max = $value['max'];
1520 if ( $min !== null && $max !== null ) {
1521 $media_queries .= "@media (min-width: {$min}px) and (max-width: {$max}px) { $cssValue } ";
1522 } elseif ( $min !== null ) {
1523 $media_queries .= "@media (min-width: {$min}px) { $cssValue } ";
1524 } elseif ( $max !== null ) {
1525 $media_queries .= "@media (max-width: {$max}px) { $cssValue } ";
1526 }
1527 }
1528
1529 return $media_queries;
1530 }
1531
1532
1533
1534 private function getMediaQuery( $cssValue, $single_res_value ) {
1535 return isset( $single_res_value['deviceAndClassList']['devices'] ) && count( $single_res_value['deviceAndClassList']['devices'] ) > 0
1536 ? $this->setMediaQuery( $cssValue, $single_res_value['deviceAndClassList']['devices'] )
1537 : '';
1538 }
1539
1540 private function setInteractionCssValueToTracker( $classApplyTo, $element, $ele_id, $animations, $styleBlockId, $single_res_value ) {
1541 $parent_selector = false;
1542 if ( isset( $single_res_value['deviceAndClassList'], $single_res_value['deviceAndClassList']['applyToClass'], $single_res_value['deviceAndClassList']['styleBlockId'] ) && $single_res_value['deviceAndClassList']['applyToClass'] ) {
1543 if ( isset( $this->style_blocks[ $single_res_value['deviceAndClassList']['styleBlockId'] ] ) ) {
1544 $sb = $this->style_blocks[ $single_res_value['deviceAndClassList']['styleBlockId'] ];
1545 $parent_selector = HelperFunctions::get_selector_from_sb_name( $sb['name'] );
1546 }
1547 }
1548
1549 $cssValue = $this->getClassNameAndAnimationStyle( $animations, $styleBlockId );
1550
1551 if ( ! $cssValue ) {
1552 return;
1553 }
1554
1555 switch ( $classApplyTo ) {
1556
1557 case 'childrens':
1558 $parentID = $element['id'];
1559
1560 if ( ! $parent_selector ) {
1561 $parent_selector = "[data-kirki='" . $parentID . "']";
1562 }
1563 $animation_css = $parent_selector . ' ' . $cssValue;
1564 $media_queries = $this->getMediaQuery( $animation_css, $single_res_value );
1565
1566 $finalCss = ! empty( $media_queries ) ? $media_queries : $animation_css;
1567 $this->track_animation_for_children_with_this_class .= $finalCss;
1568
1569 break;
1570
1571 case 'siblings':
1572 if ( $cssValue ) {
1573 $child_sb = $this->style_blocks[ $styleBlockId ];
1574 $child_selector = HelperFunctions::get_selector_from_sb_name( $child_sb['name'] );
1575 if ( $parent_selector ) {
1576 $animation_css = "$parent_selector $child_selector ~ $cssValue";
1577 } else {
1578 // $animation_css = "$child_selector ~ $cssValue";
1579 $animation_css = "[data-kirki='" . $ele_id . "'] ~" . ' ' . $cssValue;
1580 }
1581
1582 $media_queries = $this->getMediaQuery( $animation_css, $single_res_value );
1583 $finalCss = ! empty( $media_queries ) ? $media_queries : $animation_css;
1584 $this->track_animation_for_sibling_with_this_class .= $finalCss;
1585 }
1586 break;
1587
1588 case 'trigger-siblings':
1589 $parentID = $element['parentId'];
1590
1591 if ( ! $parent_selector ) {
1592 $parent_selector = "[data-kirki='" . $parentID . "']";
1593 }
1594 $animation_css = "$parent_selector > " . $cssValue;
1595 $media_queries = $this->getMediaQuery( $animation_css, $single_res_value );
1596 $finalCss = ! empty( $media_queries ) ? $media_queries : $animation_css;
1597 $this->track_animation_for_trigger_sibling_with_this_class .= $finalCss;
1598
1599 break;
1600
1601 case 'trigger':
1602 $media_queries = $this->getMediaQuery( $cssValue, $single_res_value );
1603 $finalCss = ! empty( $media_queries ) ? $media_queries : $cssValue;
1604 $this->track_animation_for_trigger_with_this_class .= $finalCss;
1605
1606 break;
1607
1608 default:
1609 $media_queries = $this->getMediaQuery( $cssValue, $single_res_value );
1610 $finalCss = ! empty( $media_queries ) ? $media_queries : $cssValue;
1611 $this->track_animation_for_elements_with_this_class .= $finalCss;
1612 break;
1613
1614 }
1615 }
1616
1617
1618 private function updateClassListForInteractionFromStyleBlockId( $interactionData, $element ) {
1619 $id = $element['id'];
1620 foreach ( $interactionData as $el_as_target_key => $el_as_target_value ) {
1621 if ( $el_as_target_key === 'deviceAndClassList' ) {
1622 if ( isset( $interactionData['deviceAndClassList']['styleBlockId'] ) ) {
1623 $this_block = isset( $this->style_blocks[ $interactionData['deviceAndClassList']['styleBlockId'] ] ) ? $this->style_blocks[ $interactionData['deviceAndClassList']['styleBlockId'] ] : false;
1624 if ( $this_block ) {
1625 $class_names = array();
1626 if ( is_array( $this_block['name'] ) ) {
1627 $class_names = HelperFunctions::add_prefix_to_class_name( $this->prefix, $this_block['name'] );
1628 } else {
1629 $class_names[] = HelperFunctions::add_prefix_to_class_name( $this->prefix, $this_block['name'] );
1630 }
1631 $interactionData['deviceAndClassList']['classList'] = $class_names;
1632 $this->add_to_only_used_style_blocks( $this_block );
1633 }
1634 }
1635 } elseif ( $el_as_target_key === 'elementAsTrigger' ) {
1636 foreach ( $el_as_target_value as $event_key => $event_value ) {
1637 foreach ( $event_value as $custom_or_preset_key => $custom_or_preset_value ) {
1638 foreach ( $custom_or_preset_value as $single_res_key => $single_res_value ) {
1639 $new_data = array();
1640 foreach ( $single_res_value['data'] as $ele_id => $animations ) {
1641 if ( strpos( $ele_id, '____info' ) !== false ) {
1642 continue;
1643 }
1644
1645 if ( ( $custom_or_preset_key === 'preset' || $custom_or_preset_key === 'textAnimation' ) && ! empty( $animations ) && count( $animations ) > 0 ) {
1646 if ( $event_key === 'scroll-into-ele' || $event_key === 'scroll-out-ele' ) {
1647 $this->interaction_preset_and_text_animation_tracker[ $ele_id ] = true;
1648 }
1649 }
1650 // set initial value for custom interaction
1651 $this->setInitialAnimation( $ele_id, $animations, $single_res_value );
1652
1653 foreach ( $animations as $ani_key => $animation ) {
1654
1655 if ( isset( $animation['property'] ) && $animation['property'] === 'class-change' ) {
1656 if ( isset( $animation['end'], $animation['end']['className'], $animation['end']['className']['id'] ) ) {
1657 $this_block = isset( $this->style_blocks[ $animation['end']['className']['id'] ] ) ? $this->style_blocks[ $animation['end']['className']['id'] ] : false;
1658 if ( $this_block ) {
1659
1660 $class_names = array();
1661 if ( is_array( $this_block['name'] ) ) {
1662 $class_names = HelperFunctions::add_prefix_to_class_name( $this->prefix, $this_block['name'] );
1663 } else {
1664 $class_names[] = HelperFunctions::add_prefix_to_class_name( $this->prefix, $this_block['name'] );
1665 }
1666
1667 $animation['end']['className']['name'] = $class_names;
1668 $this->add_to_only_used_style_blocks( $this_block );
1669 }
1670 }
1671 }
1672 $animations[ $ani_key ] = $animation;
1673 }
1674 $new_data[ $ele_id ] = $animations;
1675 if ( isset( $single_res_value['data'][ $ele_id . '____info' ] ) ) {
1676
1677 if ( isset( $single_res_value['data'][ $ele_id . '____info' ]['styleBlockId'] ) ) {
1678 $classApplyTo = isset( $single_res_value['data'][ $ele_id . '____info' ]['classApplyOnly'] ) ? $single_res_value['data'][ $ele_id . '____info' ]['classApplyOnly'] : '*';
1679 $applyToClass = isset( $single_res_value['data'][ $ele_id . '____info' ]['applyToClass'] ) ? $single_res_value['data'][ $ele_id . '____info' ]['applyToClass'] : false;
1680 $styleBlockId = $single_res_value['data'][ $ele_id . '____info' ]['styleBlockId'];
1681
1682 if ( $applyToClass ) {
1683 $this->setInteractionCssValueToTracker( $classApplyTo, $element, $ele_id, $animations, $styleBlockId, $single_res_value );
1684 }
1685
1686 $this_block = isset( $this->style_blocks[ $single_res_value['data'][ $ele_id . '____info' ]['styleBlockId'] ] ) ? $this->style_blocks[ $single_res_value['data'][ $ele_id . '____info' ]['styleBlockId'] ] : false;
1687 if ( $this_block ) {
1688
1689 $class_names = array();
1690 if ( is_array( $this_block['name'] ) ) {
1691 $class_names = HelperFunctions::add_prefix_to_class_name( $this->prefix, $this_block['name'] );
1692 } else {
1693 $class_names[] = HelperFunctions::add_prefix_to_class_name( $this->prefix, $this_block['name'] );
1694 }
1695
1696 $single_res_value['data'][ $ele_id . '____info' ]['classList'] = $class_names;
1697
1698 $new_data[ $ele_id . '____info' ] = $single_res_value['data'][ $ele_id . '____info' ];
1699
1700 $this->add_to_only_used_style_blocks( $this_block );
1701 }
1702 } else {
1703 // legecy code support
1704 $new_data[ $ele_id . '____info' ] = $single_res_value['data'][ $ele_id . '____info' ];
1705 }
1706 }
1707 }
1708 $interactionData[ $el_as_target_key ][ $event_key ][ $custom_or_preset_key ][ $single_res_key ]['data'] = $new_data;
1709 }
1710 }
1711 }
1712 }
1713 }
1714 return $interactionData;
1715 }
1716
1717 private function updateStylesForInteractionLibrary( $interactionLibraryData, $element ) {
1718 $id = $element['id'];
1719 foreach ( $interactionLibraryData as $interactionLibrary ) {
1720 if ( isset( $interactionLibrary['type'] ) && $interactionLibrary['type'] === 'textAnimation' ) {
1721 $devices = isset( $interactionLibrary['deviceAndClassList']['devices'] ) ? $interactionLibrary['deviceAndClassList']['devices'] : array();
1722 $this->interaction_library_text_animation_tracker[ $id ] = $devices;
1723 break;
1724 }
1725 }
1726 return $interactionLibraryData;
1727 }
1728
1729 /**
1730 * Get element related config
1731 *
1732 * @return array
1733 */
1734 public function get_element_related_config() {
1735 return array(
1736 'interactions' => $this->interactions,
1737 'sliders' => $this->sliders,
1738 'popups' => $this->popups,
1739 'maps' => $this->maps,
1740 'lotties' => $this->lotties,
1741 'tabs' => $this->tabs,
1742 'lightboxes' => $this->lightboxes,
1743 'collections' => $this->collections,
1744 'forms' => $this->forms,
1745 'reCaptchas' => $this->re_captchas,
1746 'dropdown' => $this->dropdown,
1747 );
1748 }
1749
1750 /**
1751 * Merge element related config.
1752 *
1753 * Set/merge all element releted cofig.
1754 *
1755 * @param array $configs element congiguration.
1756 *
1757 * @return void
1758 */
1759 public function merge_element_related_config( $configs ) {
1760 $this->interactions = array_merge( $this->interactions, $configs['interactions'] );
1761 $this->sliders = array_merge( $this->sliders, $configs['sliders'] );
1762 $this->popups = array_merge( $this->popups, $configs['popups'] );
1763 $this->maps = array_merge( $this->maps, $configs['maps'] );
1764 $this->lotties = array_merge( $this->lotties, $configs['lotties'] );
1765 $this->tabs = array_merge( $this->tabs, $configs['tabs'] );
1766 $this->lightboxes = array_merge( $this->lightboxes, $configs['lightboxes'] );
1767 $this->collections = array_merge( $this->collections, $configs['collections'] );
1768 $this->forms = array_merge( $this->forms, $configs['forms'] );
1769 $this->re_captchas = array_merge( $this->re_captchas, $configs['reCaptchas'] );
1770 $this->dropdown = array_merge( $this->dropdown, $configs['dropdown'] );
1771 }
1772
1773 /**
1774 * Check popup inside button element. Insert the popup inside data.
1775 *
1776 * @param array $button_properties button properties.
1777 *
1778 * @return void
1779 */
1780 private function check_popup_inside_button( $button_properties ) {
1781 // Search for popups.
1782 $type = isset( $button_properties['type'] ) ? $button_properties['type'] : '';
1783
1784 if ( $type === 'popup' ) {
1785 $popup_id = (int) $button_properties['attributes']['popup'];
1786 if ( is_numeric( $popup_id ) ) {
1787 $this->insert_popup_into_data( $popup_id );
1788 }
1789 }
1790 }
1791
1792 /**
1793 * Check popup inside form element. then insert the popup data.
1794 *
1795 * @param array $form_properties form properties.
1796 * @return void
1797 */
1798 private function check_popup_inside_form( $form_properties ) {
1799 // Search for popups.
1800 $action_on_submit = isset( $form_properties['onSubmit']['type'] ) ? $form_properties['onSubmit']['type'] : '';
1801
1802 $action_on_submit_success = isset( $form_properties['onSubmit']['value']['success'] ) ? $form_properties['onSubmit']['value']['success'] : '';
1803
1804 $action_on_submit_fail = isset( $form_properties['onSubmit']['value']['fail'] ) ? $form_properties['onSubmit']['value']['fail'] : '';
1805
1806 if ( $action_on_submit === 'popup' ) {
1807 if ( is_numeric( $action_on_submit_success ) ) {
1808 $this->insert_popup_into_data( $action_on_submit_success );
1809 }
1810
1811 if ( is_numeric( $action_on_submit_fail ) ) {
1812 $this->insert_popup_into_data( $action_on_submit_fail );
1813 }
1814 }
1815 }
1816
1817 /**
1818 * Insert popup into data
1819 *
1820 * @param int $popup_id popup post id.
1821 *
1822 * @return void
1823 */
1824 private function insert_popup_into_data( $popup_id ) {
1825 if ( in_array( $popup_id, self::$only_used_popup_id_array, true ) ) {
1826 return;
1827 }
1828 self::$only_used_popup_id_array[] = $popup_id;
1829 }
1830
1831 /**
1832 * Recursive function to generate html string
1833 *
1834 * @param int $id root id.
1835 * @param array $options elements option.
1836 *
1837 * @return string
1838 */
1839 public function recGenHTML( $id, $options = array() ) {
1840 if ( ! isset( $this->data[ $id ] ) ) {
1841 return '';
1842 }
1843 $this_data = $this->data[ $id ];
1844 if ( isset( $this_data['hide'] ) && $this_data['hide'] === true ) {
1845 return '';
1846 }
1847
1848 if ( isset( $this_data['properties'], $this_data['properties']['access'] ) ) {
1849 if ( ! ( isset( $options['check_access'] ) && $options['check_access'] === false ) && ! HelperFunctions::is_element_accessible( $this_data['properties']['access'] ) ) {
1850 return '';
1851 }
1852 }
1853
1854 if ( isset( $this_data['properties'], $this_data['properties']['visibilityConditions'] ) ) {
1855 if ( ! HelperFunctions::checkVisibilityConditions( $this_data, $options ) ) {
1856 return '';
1857 }
1858 }
1859
1860 $options = array_merge(
1861 $options,
1862 ['element_id' => $id]
1863 );
1864
1865 $this->insertElementRelatedConfig( $this_data, $options );
1866
1867 if ( isset( $this_data['source'] ) && $this_data['source'] !== 'kirki' ) {
1868 // if souce is not equal kirki then the element came from external plugin
1869 return $this->external_element_apply_filter_hook( $this_data['source'], $this_data, $options );
1870 } else {
1871 if(isset($this_data['name'])){
1872 $html = $this->external_element_apply_filter_hook( $this_data['name'], $this_data, $options );
1873 if ( $html ) {
1874 return $html;
1875 }
1876 }
1877 }
1878 return $this->generateSingleElement( $this_data, $options );
1879 }
1880
1881 public function generateSingleElement( $this_data, $options ) {
1882 if (isset( $this_data['name'] ) && in_array( $this_data['name'], $this->exceptional_elements, true ) ) {
1883 if ( ! in_array( $this_data['name'], $this->exceptional_elements_contains_dyn_content, true ) || isset( $this_data['properties']['dynamicContent'] ) ) {
1884 return $this->get_this_exceptional_element( $this_data, $this->getAllAttributes( $this_data ), $options );
1885 }
1886 }
1887 $html = '';
1888 if ( isset( $this_data['properties'], $this_data['properties']['noEndTag'] ) ) {
1889 $html = $this->noEndTagElements( $this_data, $options );
1890 } else {
1891 $html = $this->hasEndTagElements( $this_data, $options );
1892 }
1893 return $html;
1894 }
1895
1896 public function external_element_apply_filter_hook( $source, $this_data, $options ) {
1897 return apply_filters(
1898 'kirki_element_generator_' . $source,
1899 false,
1900 array(
1901 'element' => $this_data,
1902 'elements' => $this->data,
1903 'style_blocks' => $this->style_blocks,
1904 'attributes' => $this->getAllAttributes( $this_data ),
1905 'options' => $options,
1906 'generate_child_element' => array( $this, 'recGenHTML' ),
1907 'generate_element' => array( $this, 'generateSingleElement' ),
1908 'generate_child_element_with_new_id' => array( new HelperFunctions(), 'rec_update_data_id_then_return_new_html' ),
1909 'get_data_and_styles_from_root' => array( new DataHelper(), 'get_data_and_styles_from_root' ),
1910 'get_collection_info' => array( $this, 'get_collection_info' ),
1911 )
1912 );
1913 }
1914
1915
1916 /**
1917 * Elements for has end tag
1918 *
1919 * @param array $this_data single element data.
1920 * @param array $options element options data.
1921 *
1922 * @return string html markup.
1923 */
1924 private function hasEndTagElements( $this_data, $options ) {
1925 $tag = isset( $this_data['properties'], $this_data['properties']['tag'] ) ? $this_data['properties']['tag'] : 'div';
1926 $attributes = $this->getAllAttributes( $this_data );
1927 $preload_link = '';
1928
1929 if ( isset($this_data['name']) && $this_data['name'] == 'link-text' ) {
1930 $href = $this->get_href_value( $this_data, $options );
1931
1932 if ( $href ) {
1933 // replace href.
1934 $attributes = preg_replace( '/href="([^"]+")/i', "href='$href'", $attributes );
1935
1936 // check if the link is active.
1937 $attributes = $this->add_link_active_class( $href, $attributes );
1938
1939 $preload_link = $this->get_preload_link( $href, $this_data );
1940 }
1941
1942 if ( isset( $this_data['properties']['dynamicContent'] ) ) {
1943 unset( $this_data['properties']['dynamicContent'] );
1944 }
1945 }
1946
1947 $child_content_or_html = $this->get_child_content_or_childrens( $this_data, $options );
1948
1949 $nested_not_allowed_tags = array( 'a', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' );
1950 if ( in_array( $tag, $nested_not_allowed_tags ) && HelperFunctions::check_string_has_this_tags( $child_content_or_html, $tag ) ) {
1951 $tag = 'div';
1952 }
1953 // Start Tag <div>.
1954 $html = '<' . $tag . ' ' . $attributes . '>';
1955 $html .= $child_content_or_html;
1956 $html .= '</' . $tag . '>';
1957 $html .= $preload_link;
1958 // End Tag </div>.
1959 return $html;
1960 }
1961
1962 public function get_child_content_or_childrens( $this_data, $options ) {
1963 $html = '';
1964 if ( ! isset( $this_data['children'] ) ) {
1965 $html .= $this->print_content( $this_data, $options );
1966 } else {
1967 // Check if the current element has children
1968 if ( isset( $this_data['id'], $this->data[ $this_data['id'] ], $this->data[ $this_data['id'] ]['children'] ) ) {
1969 $child_count = count( $this->data[ $this_data['id'] ]['children'] );
1970 for ( $i = 0; $i < $child_count; $i++ ) {
1971 $merged_options = array_merge(
1972 $options,
1973 array(
1974 'item_index' => $i,
1975 )
1976 );
1977 $html .= $this->recGenHTML( $this->data[ $this_data['id'] ]['children'][ $i ], $merged_options );
1978 }
1979 }
1980 }
1981 return $html;
1982 }
1983
1984 /**
1985 * Print richtext content.
1986 *
1987 * @param array $this_data single element data.
1988 * @param array $options single element options/dynamic data.
1989 * @return string html markup.
1990 */
1991 private function print_content( $this_data, $options ) {
1992 $html = '';
1993 $properties = $this_data['properties'];
1994 $content = isset( $properties['contents'] ) ? $properties['contents'] : '';
1995 $dynamic_content = isset( $properties['dynamicContent'] ) ? $properties['dynamicContent'] : false;
1996 $tag = isset( $this_data['properties'], $this_data['properties']['tag'] ) ? $this_data['properties']['tag'] : 'div';
1997
1998 if ( $dynamic_content ) {
1999 $html = Utils::getDynamicRichTextValue( $dynamic_content, $options );
2000 } else {
2001 if ( is_array( $content ) ) {
2002 $content_count = count( $content );
2003 for ( $i = 0; $i < $content_count; $i++ ) {
2004 if ( is_array( $content[ $i ] ) ) {
2005 if( isset($content[ $i ]['id']) ){
2006 $html .= $this->recGenHTML( $content[ $i ]['id'], $options );
2007 }
2008 } else {
2009 $html .= htmlspecialchars( $content[ $i ] );
2010 }
2011 }
2012 } else {
2013 $html .= htmlspecialchars( $content );
2014 }
2015 }
2016
2017 $href = $this->get_href_value( $this_data, $options );
2018
2019 if ( $tag !== 'a' && $href ) {
2020 $target = isset( $properties['attributes'], $properties['attributes']['target'] ) ? "target={$properties['attributes']['target']}" : '';
2021 $rel = isset( $properties['attributes'], $properties['attributes']['rel'] ) ? "rel={$properties['attributes']['rel']}" : '';
2022
2023 if ( isset( $properties['type'] ) ) {
2024 $html = "<a href={$href} {$target} {$rel}>{$html}</a>";
2025 }
2026 }
2027
2028 return $html;
2029 }
2030
2031 public function get_href_value( $this_data, $options = array() ) {
2032 $name = $this_data['name'];
2033 $properties = $this_data['properties'];
2034 $dynamic_content = isset( $properties['dynamicContent'] ) ? $properties['dynamicContent'] : false;
2035 $href = isset( $properties['attributes']['href'] ) ? $properties['attributes']['href'] : false;
2036
2037 switch ( $name ) {
2038 case 'link-block':
2039 case 'link-text':
2040 case 'navigation-item':
2041 case 'paragraph':
2042 case 'heading':
2043 case 'button': {
2044 if ( $dynamic_content ) {
2045
2046 $contentInfo = array(
2047 'dynamicContent' => $dynamic_content,
2048 'options' => $options,
2049 );
2050 if ( isset( $options['itemType'], $options[ $options['itemType'] ], $options[ $options['itemType'] ]->ID ) ) {
2051 $contentInfo['collectionItem'] = array(
2052 'ID' => $options[ $options['itemType'] ]->ID,
2053 );
2054 }
2055 $content = apply_filters( 'kirki_dynamic_content', false, $contentInfo );
2056 if ( $content ) {
2057 // Plain-text dynamic values (comment fields) are visitor supplied,
2058 // so they are never trusted as a URL here.
2059 if ( is_string( $content ) && in_array( $dynamic_content['type'] ?? '', Utils::get_plain_text_dynamic_content_types(), true ) ) {
2060 return esc_url( html_entity_decode( $content, ENT_QUOTES | ENT_HTML5, 'UTF-8' ) );
2061 }
2062 return $content;
2063 }
2064
2065 if ( $dynamic_content['type'] === 'post' ) {
2066 if ( isset( $options['post'], $dynamic_content['value'] ) && isset( $options['post']->{$dynamic_content['value']} ) ) {
2067 $href = $options['post']->{$dynamic_content['value']};
2068 } else {
2069 $href = HelperFunctions::get_post_dynamic_content( isset($dynamic_content['value']) ? $dynamic_content['value'] : false, isset( $options['post'] ) ? $options['post'] : null );
2070 }
2071 } elseif ( $dynamic_content['type'] === 'term' && isset( $options['term'], $options['term']['term_id'] ) ) {
2072
2073 // $href = get_tag_link($options['term']['term_id']);
2074
2075 $term = $options['term'];
2076 $taxonomy = isset( $term['taxonomy'] ) ? $term['taxonomy'] : 'post_tag';
2077
2078 $href = get_term_link( $term['term_id'], $taxonomy );
2079 if ( is_wp_error( $href ) ) {
2080 error_log( 'get_term_link error: ' . $href->get_error_message() );
2081 $href = '';
2082 }
2083 // $href = $options['post']->{$dynamic_content['value']};
2084 } elseif ( $dynamic_content['type'] === 'menu' && isset( $options['menu'], $options['menu']->{$dynamic_content['value']} ) ) {
2085 $href = $options['menu']->{$dynamic_content['value']};
2086 } elseif ( $dynamic_content['type'] === 'user' && isset( $options['user'], $options['user'][ $dynamic_content['value'] ] ) ) {
2087 $href = $options['user'][ $dynamic_content['value'] ];
2088 // $href = $options['post']->{$dynamic_content['value']};
2089 } else {
2090 $itemType = $dynamic_content['type'];
2091 if ( isset( $options[ $itemType ] ) ) {
2092 $data = $options[ $itemType ] ?? array();
2093 $value = isset( $data[ $dynamic_content['value'] ] ) ? $data[ $dynamic_content['value'] ] : '';
2094 $href = $value;
2095 }
2096 }
2097 } elseif ( $dynamic_content && isset( $options['itemType'], $dynamic_content['value'] ) && $options['itemType'] === 'term' ) {
2098 $term = $options['term'];
2099 $property = $dynamic_content['value'];
2100 // Check if 'term' and the dynamic property are set
2101 if ( isset( $term ) && isset( $term->$property ) ) {
2102 // Use the dynamic property's value as the href
2103 $href = $term->$property;
2104 } else {
2105 // Use the fallback link generated by get_tag_link
2106 $href = get_tag_link( $term['term_id'] );
2107 }
2108 $href = HelperFunctions::content_manager_link_filter( $dynamic_content, $href );
2109 } else {
2110 if ( isset( $properties['type'] ) ) {
2111 if ( $properties['type'] === 'popup' ) {
2112 // $href = 'javascript:void(0);';
2113 } elseif ( $properties['type'] === 'page' ) {
2114 $href = isset( $properties['attributes']['href'] ) ? $properties['attributes']['href'] : false;
2115 if ( is_numeric( $href ) && intval( $href ) == $href ) {
2116 // thats means its a page id
2117 $href = get_permalink( $href );
2118 }
2119 } elseif ( $properties['type'] === 'section' ) {
2120 $cleaned_id = ltrim( $href, '#' );
2121 $href = '#' . self::get_unique_section_id_from_title( $cleaned_id );
2122 }
2123 }
2124 }
2125 return $href;
2126 }
2127 default: {
2128 return $href;
2129 }
2130 }
2131 }
2132
2133 public function get_unique_section_id_from_title( $current_id ) {
2134 if ( isset( self::$section_id_tracker_for_href[ $current_id ] ) ) {
2135 return self::$section_id_tracker_for_href[ $current_id ];
2136 }
2137
2138 $section_title = isset( $this->data[ $current_id ]['title'] ) ? $this->data[ $current_id ]['title'] : $current_id;
2139 $base_slug = strtolower( preg_replace( '/\s+/', '-', $section_title ) );
2140 $href = $base_slug;
2141 $suffix = 1;
2142
2143 while ( in_array( $href, self::$section_id_tracker_for_href ) ) {
2144 $href = $base_slug . '-' . $suffix++;
2145 }
2146
2147 self::$section_id_tracker_for_href[ $current_id ] = $href;
2148 return $href;
2149 }
2150
2151 /**
2152 * Get preload link
2153 *
2154 * @param string $href value
2155 * @param array $this_data single element data.
2156 *
2157 * @return string preload link
2158 */
2159 public function get_preload_link( $href, $this_data ) {
2160 $properties = $this_data['properties'];
2161
2162 if ( isset( $properties['preload'] ) && $properties['preload'] !== 'default' ) {
2163 return '<link rel="' . $properties['preload'] . '" href="' . $href . '">';
2164 }
2165
2166 return '';
2167 }
2168
2169 /**
2170 * Get class names
2171 *
2172 * @param string $href value
2173 * @param array $this_data single element data.
2174 * @param string $attributes
2175 *
2176 * @return string updated attributes
2177 */
2178 public function add_link_active_class( $href, $attributes, $this_data = null, $options = null ) {
2179
2180 if ( ! $href ) {
2181 return $attributes;
2182 }
2183
2184 // Resolve $href to a post ID
2185 $targetId = null;
2186 $is_home = false;
2187
2188 // 1. If it's a plain permalink like ?page_id=437
2189 if ( strpos( $href, 'page_id=' ) !== false ) {
2190 $url_parts = wp_parse_url( $href );
2191 if ( ! empty( $url_parts['query'] ) ) {
2192 parse_str( $url_parts['query'], $params );
2193 if ( ! empty( $params['page_id'] ) ) {
2194 $targetId = (int) $params['page_id'];
2195 }
2196 }
2197 } else {
2198 // 2. If it's a relative or full pretty permalink
2199 if ( strpos( $href, 'http' ) !== 0 ) {
2200 $href = home_url( $href );
2201 }
2202
2203 $href = rtrim( $href, '/' );
2204 $home_url = rtrim( home_url(), '/' );
2205
2206 if ( $href === $home_url ) {
2207 $is_home = true;
2208 } else {
2209 $post = get_page_by_path( ltrim( (string) wp_parse_url( $href, PHP_URL_PATH ), '/' ) );
2210
2211 if ( $post ) {
2212 $targetId = $post->ID;
2213 }
2214 }
2215 }
2216
2217 $currentId = HelperFunctions::get_post_id_if_possible_from_url();
2218 $home_page_id = get_option( 'page_on_front' );
2219 $current_term_id = HelperFunctions::get_term_id_if_possible_from_url();
2220 $current_url = strtok( home_url( add_query_arg( null, null ) ), '?' );
2221 $current_url = rtrim( $current_url, '/' );
2222 $term_id = null;
2223
2224 if ( $this_data && $options && isset( $this_data['properties']['dynamicContent'] ) && $this_data['properties']['dynamicContent']['type'] && $this_data['properties']['dynamicContent']['type'] === 'term' && $this_data['properties']['dynamicContent']['value'] === 'link' ) {
2225 $term_id = $options['term']['term_id'];
2226 }
2227
2228 $is_match = (
2229 ( $targetId && $currentId && $targetId === $currentId ) ||
2230 ( $is_home && $currentId && $currentId === intval( $home_page_id ) ) ||
2231 ( $term_id && $current_term_id && $term_id === $current_term_id ) ||
2232 ( $current_url === $href )
2233 );
2234
2235 if ( $is_match ) {
2236 if ( preg_match( '/class="/i', $attributes ) ) {
2237 $attributes = preg_replace(
2238 '/class="([^"]*)"/i',
2239 'class="$1 ' . 'kirki-active-link"',
2240 $attributes
2241 );
2242 } else {
2243 $attributes .= ' class="' . 'kirki-active-link"';
2244 }
2245 }
2246
2247 return $attributes;
2248 }
2249
2250 /**
2251 * Elements for no end tag
2252 *
2253 * @param array $this_data element single data.
2254 * @param array $options element single option data.
2255 *
2256 * @return string html markup.
2257 */
2258 private function noEndTagElements( $this_data, $options ) {
2259 $html = '<' . $this_data['properties']['tag'] . ' ' . $this->getAllAttributes( $this_data ) . '/>';
2260 return $html;
2261 }
2262
2263 /**
2264 * Get elements all attributes
2265 *
2266 * @param array $this_element single element data.
2267 * @param array $filter_condition Attribute filter condition. Empty array returns all attributes.
2268 *
2269 * @return string attributes string.
2270 */
2271 public function getAllAttributes( $this_element, $filter_condition = array() ) {
2272 if ( ! isset( $this_element['properties'] ) ) {
2273 return '';
2274 }
2275
2276 $attr_str = '';
2277 $class_names = trim( $this->getClassNames( $this_element ) );
2278 if ( $class_names ) {
2279 $attr_str .= 'class="' . $class_names . '"';
2280 }
2281
2282 $others_attributes = '';
2283 if ( isset( $this_element['properties']['attributes'] ) ) {
2284 $attributes = $this_element['properties']['attributes'];
2285 $element_name = $this_element['name'];
2286
2287 if ( $this_element['name'] === 'image' && isset( $attributes['width'] ) ) {
2288 unset( $attributes['width'] );
2289 }
2290
2291 $others_attributes = array_map(
2292 function ( $value, $key ) use ( $element_name ) {
2293 if ( in_array( $element_name, $this->prevent_anchor_elements, true ) && in_array( $key, $this->anchor_attrs, true ) ) {
2294 return '';
2295 }
2296
2297 if ( ! $this->attribute_validation( $key, $value ) ) {
2298 return '';
2299 }
2300
2301 if ( is_array( $value ) ) {
2302 $value = implode( ' ', $value );
2303 }
2304
2305 return $key . '="' . $value . '"';
2306 },
2307 array_values( $attributes ),
2308 array_keys( $attributes )
2309 );
2310
2311 $others_attributes = ' ' . implode( ' ', $others_attributes );
2312 }
2313
2314 $custom_attributes = '';
2315 if ( isset( $this_element['properties']['customAttributes'] ) ) {
2316 // phpcs:ignore WordPress.PHP.DisallowShortTernary.Found
2317 $attributes = $this_element['properties']['customAttributes'] ?: array();
2318 $custom_attributes = array_map(
2319 function ( $value, $key ) {
2320 return $key . '="' . $value . '"';
2321 },
2322 array_values( $attributes ),
2323 array_keys( $attributes )
2324 );
2325 $custom_attributes = ' ' . implode( ' ', $custom_attributes );
2326 }
2327
2328 if ( count( $filter_condition ) > 0 ) {
2329 $merged_attributes = array_merge( $this_element['properties']['attributes'], $attributes );
2330 // Merge attributes from other sources.
2331 $merged_attributes['class'] = $class_names;
2332 $merged_attributes['data-kirki'] = $this_element['id'];
2333
2334 if ( isset( $filter_condition['rest'] ) && false === $filter_condition['rest'] ) {
2335 // `rest => false` will pick only the `true` filter attributes.
2336 $filtered_attributes = array_filter(
2337 $merged_attributes,
2338 function ( $at ) use ( $filter_condition ) {
2339 return array_key_exists( $at, $filter_condition ) && true === $filter_condition[ $at ];
2340 },
2341 ARRAY_FILTER_USE_KEY
2342 );
2343 } else {
2344 // By default `rest` will be avaluted to `true`, so it will only remove the `false` attributes.
2345 $filtered_attributes = array_filter(
2346 $merged_attributes,
2347 function ( $at ) use ( $filter_condition ) {
2348 return ! ( array_key_exists( $at, $filter_condition ) && false === $filter_condition[ $at ] );
2349 },
2350 ARRAY_FILTER_USE_KEY
2351 );
2352 }
2353
2354 $attr_str = array_reduce(
2355 array_keys( $filtered_attributes ),
2356 function ( $carry, $key ) use ( $filtered_attributes ) {
2357 return $carry . ' ' . $key . '=' . wp_json_encode( $filtered_attributes[ $key ] );
2358 },
2359 '',
2360 );
2361
2362 } else {
2363 $attr_str .= ' data-kirki="' . $this_element['id'] . '"' . $others_attributes . $custom_attributes;
2364 }
2365
2366 if(!empty($this_element['properties']['textStyleId'])) {
2367 $attr_str .= ' data-text_style="' . $this_element['properties']['textStyleId'] . '"';
2368 }
2369
2370 // $attr_str .= ' data-kirki_name="' . $this_element['name'] . '"';
2371
2372 return $attr_str;
2373 }
2374
2375 /**
2376 * Attribute validation form different element attributes.
2377 *
2378 * @param string $attr name of the attribute.
2379 * @param string $value value of the attribute.
2380 *
2381 * @return bool
2382 */
2383 private function attribute_validation( $attr, $value ) {
2384 if ( $attr === 'multiple' && ! $value ) {
2385 return false;
2386 } elseif ( $attr === 'required' && ! $value ) {
2387 return false;
2388 } elseif ( $attr === 'src' && ! $value ) {
2389 return false;
2390 } elseif ( $attr === 'href' && ! $value ) {
2391 return false;
2392 } elseif ( $attr === 'checked' && ! $value ) {
2393 return false;
2394 }
2395 return true;
2396 }
2397
2398 /**
2399 * Get element class names
2400 *
2401 * @param array $this_element element data.
2402 *
2403 * @return string
2404 */
2405 private function getClassNames( $this_element ) {
2406 $class_array = array();
2407
2408 $style_ids = isset( $this_element['styleIds'] ) ? $this_element['styleIds'] : array();
2409
2410 $manageble_style_ids = isset( $this_element['properties'], $this_element['properties']['classesIds'] ) ? $this_element['properties']['classesIds'] : array();
2411 // add in first in styleids
2412 $style_ids = array_merge($manageble_style_ids, $style_ids);
2413
2414 if ( ! empty( $style_ids ) ) {
2415 $style_ids_count = count( $style_ids );
2416 for ( $i = 0; $i < $style_ids_count; $i++ ) {
2417 $style_id = $style_ids[ $i ];
2418 $s_block = isset( $this->style_blocks[ $style_id ] ) ? $this->style_blocks[ $style_id ] : null;
2419 if ( ! isset( $s_block ) ) {
2420 continue;
2421 }
2422 $this->add_to_only_used_style_blocks( $s_block );
2423 $c_name = $s_block['name'];
2424 if ( is_array( $c_name ) ) {
2425 $c_name = HelperFunctions::add_prefix_to_class_name( $this->prefix, $c_name );
2426 $class_array = array_merge( $class_array, $c_name );
2427 } else {
2428 $class_array[] = HelperFunctions::add_prefix_to_class_name( $this->prefix, $c_name );
2429 }
2430 }
2431 }
2432
2433 $ele_class_names = isset( $this_element['className'] ) ? explode( ' ', $this_element['className'] ) : array();
2434 $class_array = array_merge( $ele_class_names, $class_array );
2435
2436 if ( in_array( $this_element['name'], $this->inline_elements, true ) ) {
2437 array_push( $class_array, 'kirki-inline-element' );
2438 }
2439
2440 // Check if 'kirki-active-link' class is already added then remove it
2441 $class_array = array_filter(
2442 $class_array,
2443 function ( $class ) {
2444 return $class !== 'kirki-active-link';
2445 }
2446 );
2447
2448 return $this->makeClassStringFromArray( $class_array );
2449 }
2450
2451 /**
2452 * Make class string from array
2453 *
2454 * @param array $arr class array.
2455 *
2456 * @return string
2457 */
2458 private function makeClassStringFromArray( $arr ) {
2459 $arr = array_unique( $arr );
2460 $arr = array_map(
2461 function ( $c ) {
2462 return HelperFunctions::get_class_name_from_string( $c );
2463 },
2464 $arr
2465 );
2466 return join( ' ', $arr );
2467 }
2468 }
2469