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