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