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