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