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