PluginProbe
Elementor Website Builder – more than just a page builder / 3.2.4
Elementor Website Builder – more than just a page builder v3.2.4
4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 4.1.0-beta1 4.1.0-dev1 4.0.7 All 451 releases
elementor / includes / frontend.php

frontend.php in Elementor Website Builder – more than just a page builder 3.2.4, at includes/frontend.php

1,407 lines 34.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Elementor;
3
4 use Elementor\Core\Base\App;
5 use Elementor\Core\Base\Document;
6 use Elementor\Core\Frontend\Render_Mode_Manager;
7 use Elementor\Core\Responsive\Files\Frontend as FrontendFile;
8 use Elementor\Core\Files\CSS\Global_CSS;
9 use Elementor\Core\Files\CSS\Post as Post_CSS;
10 use Elementor\Core\Files\CSS\Post_Preview;
11 use Elementor\Core\Responsive\Responsive;
12 use Elementor\Core\Settings\Manager as SettingsManager;
13 use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager;
14
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit; // Exit if accessed directly.
17 }
18
19 /**
20 * Elementor frontend.
21 *
22 * Elementor frontend handler class is responsible for initializing Elementor in
23 * the frontend.
24 *
25 * @since 1.0.0
26 */
27 class Frontend extends App {
28
29 /**
30 * The priority of the content filter.
31 */
32 const THE_CONTENT_FILTER_PRIORITY = 9;
33
34 /**
35 * Post ID.
36 *
37 * Holds the ID of the current post.
38 *
39 * @access private
40 *
41 * @var int Post ID.
42 */
43 private $post_id;
44
45 /**
46 * Fonts to enqueue
47 *
48 * Holds the list of fonts that are being used in the current page.
49 *
50 * @since 1.9.4
51 * @access public
52 *
53 * @var array Used fonts. Default is an empty array.
54 */
55 public $fonts_to_enqueue = [];
56
57 /**
58 * Holds the class that respond to manage the render mode.
59 *
60 * @var Render_Mode_Manager
61 */
62 public $render_mode_manager;
63
64 /**
65 * Registered fonts.
66 *
67 * Holds the list of enqueued fonts in the current page.
68 *
69 * @since 1.0.0
70 * @access private
71 *
72 * @var array Registered fonts. Default is an empty array.
73 */
74 private $registered_fonts = [];
75
76 /**
77 * Icon Fonts to enqueue
78 *
79 * Holds the list of Icon fonts that are being used in the current page.
80 *
81 * @since 2.4.0
82 * @access private
83 *
84 * @var array Used icon fonts. Default is an empty array.
85 */
86 private $icon_fonts_to_enqueue = [];
87
88 /**
89 * Enqueue Icon Fonts
90 *
91 * Holds the list of Icon fonts already enqueued in the current page.
92 *
93 * @since 2.4.0
94 * @access private
95 *
96 * @var array enqueued icon fonts. Default is an empty array.
97 */
98 private $enqueued_icon_fonts = [];
99
100 /**
101 * Whether the page is using Elementor.
102 *
103 * Used to determine whether the current page is using Elementor.
104 *
105 * @since 1.0.0
106 * @access private
107 *
108 * @var bool Whether Elementor is being used. Default is false.
109 */
110 private $_has_elementor_in_page = false;
111
112 /**
113 * Whether the excerpt is being called.
114 *
115 * Used to determine whether the call to `the_content()` came from `get_the_excerpt()`.
116 *
117 * @since 1.0.0
118 * @access private
119 *
120 * @var bool Whether the excerpt is being used. Default is false.
121 */
122 private $_is_excerpt = false;
123
124 /**
125 * Filters removed from the content.
126 *
127 * Hold the list of filters removed from `the_content()`. Used to hold the filters that
128 * conflicted with Elementor while Elementor process the content.
129 *
130 * @since 1.0.0
131 * @access private
132 *
133 * @var array Filters removed from the content. Default is an empty array.
134 */
135 private $content_removed_filters = [];
136
137 /**
138 * @var string[]
139 */
140 private $body_classes = [
141 'elementor-default',
142 ];
143
144 /**
145 * Front End constructor.
146 *
147 * Initializing Elementor front end. Make sure we are not in admin, not and
148 * redirect from old URL structure of Elementor editor.
149 *
150 * @since 1.0.0
151 * @access public
152 */
153 public function __construct() {
154 // We don't need this class in admin side, but in AJAX requests.
155 if ( is_admin() && ! wp_doing_ajax() ) {
156 return;
157 }
158
159 add_action( 'template_redirect', [ $this, 'init_render_mode' ], -1 /* Before admin bar. */ );
160 add_action( 'template_redirect', [ $this, 'init' ] );
161 add_action( 'wp_enqueue_scripts', [ $this, 'register_scripts' ], 5 );
162 add_action( 'wp_enqueue_scripts', [ $this, 'register_styles' ], 5 );
163
164 // TODO: a temporary solution to a scenario that the elementor-icons.css file was de-registered and the e-icons font fonts should not be loaded.
165 add_action( 'wp_enqueue_scripts', function() {
166 if ( ! wp_style_is( 'elementor-icons', 'registered' ) ) {
167 $elementor_icons_css_reset = '[class^="eicon"], [class*=" eicon-"] { font-family: "initial"; } [class^="eicon"]:before, [class*=" eicon-"]:before { content: ""; }';
168
169 wp_add_inline_style( 'elementor-frontend', $elementor_icons_css_reset );
170 }
171 }, 30 );
172
173 $this->add_content_filter();
174
175 // Hack to avoid enqueue post CSS while it's a `the_excerpt` call.
176 add_filter( 'get_the_excerpt', [ $this, 'start_excerpt_flag' ], 1 );
177 add_filter( 'get_the_excerpt', [ $this, 'end_excerpt_flag' ], 20 );
178 }
179
180 /**
181 * Get module name.
182 *
183 * Retrieve the module name.
184 *
185 * @since 2.3.0
186 * @access public
187 *
188 * @return string Module name.
189 */
190 public function get_name() {
191 return 'frontend';
192 }
193
194 /**
195 * Init render mode manager.
196 */
197 public function init_render_mode() {
198 if ( Plugin::$instance->editor->is_edit_mode() ) {
199 return;
200 }
201
202 $this->render_mode_manager = new Render_Mode_Manager();
203 }
204
205 /**
206 * Init.
207 *
208 * Initialize Elementor front end. Hooks the needed actions to run Elementor
209 * in the front end, including script and style registration.
210 *
211 * Fired by `template_redirect` action.
212 *
213 * @since 1.0.0
214 * @access public
215 */
216 public function init() {
217 if ( Plugin::$instance->editor->is_edit_mode() ) {
218 return;
219 }
220
221 add_filter( 'body_class', [ $this, 'body_class' ] );
222
223 if ( Plugin::$instance->preview->is_preview_mode() ) {
224 return;
225 }
226
227 if ( current_user_can( 'manage_options' ) ) {
228 Plugin::$instance->init_common();
229 }
230
231 $this->post_id = get_the_ID();
232
233 $document = Plugin::$instance->documents->get( $this->post_id );
234
235 if ( is_singular() && $document && $document->is_built_with_elementor() ) {
236 add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_styles' ] );
237 }
238
239 // Priority 7 to allow google fonts in header template to load in <head> tag
240 add_action( 'wp_head', [ $this, 'print_fonts_links' ], 7 );
241 add_action( 'wp_head', [ $this, 'add_theme_color_meta_tag' ] );
242 add_action( 'wp_footer', [ $this, 'wp_footer' ] );
243 }
244
245 /**
246 * @since 2.0.12
247 * @access public
248 * @param string|array $class
249 */
250 public function add_body_class( $class ) {
251 if ( is_array( $class ) ) {
252 $this->body_classes = array_merge( $this->body_classes, $class );
253 } else {
254 $this->body_classes[] = $class;
255 }
256 }
257
258 /**
259 * Add Theme Color Meta Tag
260 *
261 * @since 3.0.0
262 * @access public
263 */
264 public function add_theme_color_meta_tag() {
265 $kit = Plugin::$instance->kits_manager->get_active_kit_for_frontend();
266 $mobile_theme_color = $kit->get_settings( 'mobile_theme_color' );
267
268 if ( ! empty( $mobile_theme_color ) ) {
269 ?>
270 <meta name="theme-color" content="<?php echo $mobile_theme_color; ?>">
271 <?php
272 }
273 }
274
275 /**
276 * Body tag classes.
277 *
278 * Add new elementor classes to the body tag.
279 *
280 * Fired by `body_class` filter.
281 *
282 * @since 1.0.0
283 * @access public
284 *
285 * @param array $classes Optional. One or more classes to add to the body tag class list.
286 * Default is an empty array.
287 *
288 * @return array Body tag classes.
289 */
290 public function body_class( $classes = [] ) {
291 $classes = array_merge( $classes, $this->body_classes );
292
293 $id = get_the_ID();
294
295 $document = Plugin::$instance->documents->get( $id );
296
297 if ( is_singular() && $document && $document->is_built_with_elementor() ) {
298 $classes[] = 'elementor-page elementor-page-' . $id;
299 }
300
301 return $classes;
302 }
303
304 /**
305 * Add content filter.
306 *
307 * Remove plain content and render the content generated by Elementor.
308 *
309 * @since 1.8.0
310 * @access public
311 */
312 public function add_content_filter() {
313 add_filter( 'the_content', [ $this, 'apply_builder_in_content' ], self::THE_CONTENT_FILTER_PRIORITY );
314 }
315
316 /**
317 * Remove content filter.
318 *
319 * When the Elementor generated content rendered, we remove the filter to prevent multiple
320 * accuracies. This way we make sure Elementor renders the content only once.
321 *
322 * @since 1.8.0
323 * @access public
324 */
325 public function remove_content_filter() {
326 remove_filter( 'the_content', [ $this, 'apply_builder_in_content' ], self::THE_CONTENT_FILTER_PRIORITY );
327 }
328
329 /**
330 * Registers scripts.
331 *
332 * Registers all the frontend scripts.
333 *
334 * Fired by `wp_enqueue_scripts` action.
335 *
336 * @since 1.2.1
337 * @access public
338 */
339 public function register_scripts() {
340 /**
341 * Before frontend register scripts.
342 *
343 * Fires before Elementor frontend scripts are registered.
344 *
345 * @since 1.2.1
346 */
347 do_action( 'elementor/frontend/before_register_scripts' );
348
349 wp_register_script(
350 'elementor-webpack-runtime',
351 $this->get_js_assets_url( 'webpack.runtime', 'assets/js/' ),
352 [],
353 ELEMENTOR_VERSION,
354 true
355 );
356
357 wp_register_script(
358 'elementor-frontend-modules',
359 $this->get_js_assets_url( 'frontend-modules' ),
360 [
361 'elementor-webpack-runtime',
362 'jquery',
363 ],
364 ELEMENTOR_VERSION,
365 true
366 );
367
368 wp_register_script(
369 'elementor-waypoints',
370 $this->get_js_assets_url( 'waypoints', 'assets/lib/waypoints/' ),
371 [
372 'jquery',
373 ],
374 '4.0.2',
375 true
376 );
377
378 wp_register_script(
379 'flatpickr',
380 $this->get_js_assets_url( 'flatpickr', 'assets/lib/flatpickr/' ),
381 [
382 'jquery',
383 ],
384 '4.1.4',
385 true
386 );
387
388 wp_register_script(
389 'imagesloaded',
390 $this->get_js_assets_url( 'imagesloaded', 'assets/lib/imagesloaded/' ),
391 [
392 'jquery',
393 ],
394 '4.1.0',
395 true
396 );
397
398 wp_register_script(
399 'jquery-numerator',
400 $this->get_js_assets_url( 'jquery-numerator', 'assets/lib/jquery-numerator/' ),
401 [
402 'jquery',
403 ],
404 '0.2.1',
405 true
406 );
407
408 /**
409 * @deprecated since 2.7.0 Use Swiper instead
410 */
411 wp_register_script(
412 'jquery-slick',
413 $this->get_js_assets_url( 'slick', 'assets/lib/slick/' ),
414 [
415 'jquery',
416 ],
417 '1.8.1',
418 true
419 );
420
421 wp_register_script(
422 'elementor-dialog',
423 $this->get_js_assets_url( 'dialog', 'assets/lib/dialog/' ),
424 [
425 'jquery-ui-position',
426 ],
427 '4.8.1',
428 true
429 );
430
431 wp_register_script(
432 'elementor-gallery',
433 $this->get_js_assets_url( 'e-gallery', 'assets/lib/e-gallery/js/' ),
434 [
435 'jquery',
436 ],
437 '1.2.0',
438 true
439 );
440
441 wp_register_script(
442 'share-link',
443 $this->get_js_assets_url( 'share-link', 'assets/lib/share-link/' ),
444 [
445 'jquery',
446 ],
447 ELEMENTOR_VERSION,
448 true
449 );
450
451 wp_register_script(
452 'elementor-frontend',
453 $this->get_js_assets_url( 'frontend' ),
454 $this->get_elementor_frontend_dependencies(),
455 ELEMENTOR_VERSION,
456 true
457 );
458
459 /**
460 * After frontend register scripts.
461 *
462 * Fires after Elementor frontend scripts are registered.
463 *
464 * @since 1.2.1
465 */
466 do_action( 'elementor/frontend/after_register_scripts' );
467 }
468
469 /**
470 * Registers styles.
471 *
472 * Registers all the frontend styles.
473 *
474 * Fired by `wp_enqueue_scripts` action.
475 *
476 * @since 1.2.0
477 * @access public
478 */
479 public function register_styles() {
480 /**
481 * Before frontend register styles.
482 *
483 * Fires before Elementor frontend styles are registered.
484 *
485 * @since 1.2.0
486 */
487 do_action( 'elementor/frontend/before_register_styles' );
488
489 wp_register_style(
490 'font-awesome',
491 $this->get_css_assets_url( 'font-awesome', 'assets/lib/font-awesome/css/' ),
492 [],
493 '4.7.0'
494 );
495
496 wp_register_style(
497 'elementor-icons',
498 $this->get_css_assets_url( 'elementor-icons', 'assets/lib/eicons/css/' ),
499 [],
500 '5.11.0'
501 );
502
503 wp_register_style(
504 'elementor-animations',
505 $this->get_css_assets_url( 'animations', 'assets/lib/animations/', true ),
506 [],
507 ELEMENTOR_VERSION
508 );
509
510 wp_register_style(
511 'flatpickr',
512 $this->get_css_assets_url( 'flatpickr', 'assets/lib/flatpickr/' ),
513 [],
514 '4.1.4'
515 );
516
517 wp_register_style(
518 'elementor-gallery',
519 $this->get_css_assets_url( 'e-gallery', 'assets/lib/e-gallery/css/' ),
520 [],
521 '1.2.0'
522 );
523
524 $min_suffix = Utils::is_script_debug() ? '' : '.min';
525
526 $direction_suffix = is_rtl() ? '-rtl' : '';
527
528 $frontend_file_name = 'frontend' . $direction_suffix . $min_suffix . '.css';
529
530 $has_custom_file = Plugin::$instance->breakpoints->has_custom_breakpoints();
531
532 if ( $has_custom_file ) {
533 $frontend_file = new FrontendFile( 'custom-' . $frontend_file_name, Breakpoints_Manager::get_stylesheet_templates_path() . $frontend_file_name );
534
535 $time = $frontend_file->get_meta( 'time' );
536
537 if ( ! $time ) {
538 $frontend_file->update();
539 }
540
541 $frontend_file_url = $frontend_file->get_url();
542 } else {
543 $frontend_file_url = ELEMENTOR_ASSETS_URL . 'css/' . $frontend_file_name;
544 }
545
546 $frontend_dependencies = [];
547
548 if ( ! Plugin::$instance->experiments->is_feature_active( 'e_dom_optimization' ) ) {
549 // If The Dom Optimization feature is disabled, register the legacy CSS
550 wp_register_style(
551 'elementor-frontend-legacy',
552 ELEMENTOR_ASSETS_URL . 'css/frontend-legacy' . $direction_suffix . $min_suffix . '.css',
553 [],
554 ELEMENTOR_VERSION
555 );
556
557 $frontend_dependencies[] = 'elementor-frontend-legacy';
558 }
559
560 wp_register_style(
561 'elementor-frontend',
562 $frontend_file_url,
563 $frontend_dependencies,
564 $has_custom_file ? null : ELEMENTOR_VERSION
565 );
566
567 /**
568 * After frontend register styles.
569 *
570 * Fires after Elementor frontend styles are registered.
571 *
572 * @since 1.2.0
573 */
574 do_action( 'elementor/frontend/after_register_styles' );
575 }
576
577 /**
578 * Enqueue scripts.
579 *
580 * Enqueue all the frontend scripts.
581 *
582 * @since 1.0.0
583 * @access public
584 */
585 public function enqueue_scripts() {
586 /**
587 * Before frontend enqueue scripts.
588 *
589 * Fires before Elementor frontend scripts are enqueued.
590 *
591 * @since 1.0.0
592 */
593 do_action( 'elementor/frontend/before_enqueue_scripts' );
594
595 wp_enqueue_script( 'elementor-frontend' );
596
597 if ( ! $this->is_improved_assets_loading() ) {
598 wp_enqueue_script(
599 'preloaded-modules',
600 $this->get_js_assets_url( 'preloaded-modules', 'assets/js/' ),
601 [
602 'elementor-frontend',
603 ],
604 ELEMENTOR_VERSION,
605 true
606 );
607 }
608
609 $this->print_config();
610
611 /**
612 * After frontend enqueue scripts.
613 *
614 * Fires after Elementor frontend scripts are enqueued.
615 *
616 * @since 1.0.0
617 */
618 do_action( 'elementor/frontend/after_enqueue_scripts' );
619 }
620
621 /**
622 * Enqueue styles.
623 *
624 * Enqueue all the frontend styles.
625 *
626 * Fired by `wp_enqueue_scripts` action.
627 *
628 * @since 1.0.0
629 * @access public
630 */
631 public function enqueue_styles() {
632 static $is_enqueue_styles_already_triggered;
633
634 if ( ! $is_enqueue_styles_already_triggered ) {
635 $is_enqueue_styles_already_triggered = true;
636
637 /**
638 * Before frontend styles enqueued.
639 *
640 * Fires before Elementor frontend styles are enqueued.
641 *
642 * @since 1.0.0
643 */
644 do_action( 'elementor/frontend/before_enqueue_styles' );
645
646 $this->add_elementor_icons_inline_css();
647
648 // The e-icons are needed in preview mode for the editor icons (plus-icon for new section, folder-icon for the templates library etc.).
649 if ( ! $this->is_improved_assets_loading() || Plugin::$instance->preview->is_preview_mode() ) {
650 wp_enqueue_style( 'elementor-icons' );
651 }
652 wp_enqueue_style( 'elementor-animations' );
653 wp_enqueue_style( 'elementor-frontend' );
654
655 /**
656 * After frontend styles enqueued.
657 *
658 * Fires after Elementor frontend styles are enqueued.
659 *
660 * @since 1.0.0
661 */
662 do_action( 'elementor/frontend/after_enqueue_styles' );
663
664 if ( ! Plugin::$instance->preview->is_preview_mode() ) {
665 $this->parse_global_css_code();
666
667 $post_id = get_the_ID();
668 // Check $post_id for virtual pages. check is singular because the $post_id is set to the first post on archive pages.
669 if ( $post_id && is_singular() ) {
670 $css_file = Post_CSS::create( get_the_ID() );
671 $css_file->enqueue();
672 }
673 }
674 }
675 }
676
677 /**
678 * Elementor footer scripts and styles.
679 *
680 * Handle styles and scripts that are not printed in the header.
681 *
682 * Fired by `wp_footer` action.
683 *
684 * @since 1.0.11
685 * @access public
686 */
687 public function wp_footer() {
688 if ( ! $this->_has_elementor_in_page ) {
689 return;
690 }
691
692 $this->enqueue_styles();
693 $this->enqueue_scripts();
694
695 $this->print_fonts_links();
696 }
697
698 /**
699 * Print fonts links.
700 *
701 * Enqueue all the frontend fonts by url.
702 *
703 * Fired by `wp_head` action.
704 *
705 * @since 1.9.4
706 * @access public
707 */
708 public function print_fonts_links() {
709 $google_fonts = [
710 'google' => [],
711 'early' => [],
712 ];
713
714 foreach ( $this->fonts_to_enqueue as $key => $font ) {
715 $font_type = Fonts::get_font_type( $font );
716
717 switch ( $font_type ) {
718 case Fonts::GOOGLE:
719 $google_fonts['google'][] = $font;
720 break;
721
722 case Fonts::EARLYACCESS:
723 $google_fonts['early'][] = $font;
724 break;
725
726 case false:
727 $this->maybe_enqueue_icon_font( $font );
728 break;
729 default:
730 /**
731 * Print font links.
732 *
733 * Fires when Elementor frontend fonts are printed on the HEAD tag.
734 *
735 * The dynamic portion of the hook name, `$font_type`, refers to the font type.
736 *
737 * @since 2.0.0
738 *
739 * @param string $font Font name.
740 */
741 do_action( "elementor/fonts/print_font_links/{$font_type}", $font );
742 }
743 }
744 $this->fonts_to_enqueue = [];
745
746 $this->enqueue_google_fonts( $google_fonts );
747 $this->enqueue_icon_fonts();
748 }
749
750 private function maybe_enqueue_icon_font( $icon_font_type ) {
751 if ( ! Icons_Manager::is_migration_allowed() ) {
752 return;
753 }
754
755 $icons_types = Icons_Manager::get_icon_manager_tabs();
756 if ( ! isset( $icons_types[ $icon_font_type ] ) ) {
757 return;
758 }
759
760 $icon_type = $icons_types[ $icon_font_type ];
761 if ( isset( $icon_type['url'] ) ) {
762 $this->icon_fonts_to_enqueue[ $icon_font_type ] = [ $icon_type['url'] ];
763 }
764 }
765
766 private function enqueue_icon_fonts() {
767 if ( empty( $this->icon_fonts_to_enqueue ) || ! Icons_Manager::is_migration_allowed() ) {
768 return;
769 }
770
771 foreach ( $this->icon_fonts_to_enqueue as $icon_type => $css_url ) {
772 wp_enqueue_style( 'elementor-icons-' . $icon_type );
773 $this->enqueued_icon_fonts[] = $css_url;
774 }
775
776 //clear enqueued icons
777 $this->icon_fonts_to_enqueue = [];
778 }
779
780 /**
781 * Print Google fonts.
782 *
783 * Enqueue all the frontend Google fonts.
784 *
785 * Fired by `wp_head` action.
786 *
787 * @since 1.0.0
788 * @access private
789 *
790 * @param array $google_fonts Optional. Google fonts to print in the frontend.
791 * Default is an empty array.
792 */
793 private function enqueue_google_fonts( $google_fonts = [] ) {
794 static $google_fonts_index = 0;
795
796 $print_google_fonts = true;
797
798 /**
799 * Print frontend google fonts.
800 *
801 * Filters whether to enqueue Google fonts in the frontend.
802 *
803 * @since 1.0.0
804 *
805 * @param bool $print_google_fonts Whether to enqueue Google fonts. Default is true.
806 */
807 $print_google_fonts = apply_filters( 'elementor/frontend/print_google_fonts', $print_google_fonts );
808
809 if ( ! $print_google_fonts ) {
810 return;
811 }
812
813 // Print used fonts
814 if ( ! empty( $google_fonts['google'] ) ) {
815 $google_fonts_index++;
816
817 foreach ( $google_fonts['google'] as &$font ) {
818 $font = str_replace( ' ', '+', $font ) . ':100,100italic,200,200italic,300,300italic,400,400italic,500,500italic,600,600italic,700,700italic,800,800italic,900,900italic';
819 }
820
821 // Defining a font-display type to google fonts.
822 $font_display_url_str = '&display=' . Fonts::get_font_display_setting();
823
824 $fonts_url = sprintf( 'https://fonts.googleapis.com/css?family=%1$s%2$s', implode( rawurlencode( '|' ), $google_fonts['google'] ), $font_display_url_str );
825
826 $subsets = [
827 'ru_RU' => 'cyrillic',
828 'bg_BG' => 'cyrillic',
829 'he_IL' => 'hebrew',
830 'el' => 'greek',
831 'vi' => 'vietnamese',
832 'uk' => 'cyrillic',
833 'cs_CZ' => 'latin-ext',
834 'ro_RO' => 'latin-ext',
835 'pl_PL' => 'latin-ext',
836 'hr_HR' => 'latin-ext',
837 'hu_HU' => 'latin-ext',
838 'sk_SK' => 'latin-ext',
839 'tr_TR' => 'latin-ext',
840 'lt_LT' => 'latin-ext',
841 ];
842
843 $subsets = apply_filters( 'elementor/frontend/google_font_subsets', $subsets );
844
845 $locale = get_locale();
846
847 if ( isset( $subsets[ $locale ] ) ) {
848 $fonts_url .= '&subset=' . $subsets[ $locale ];
849 }
850
851 wp_enqueue_style( 'google-fonts-' . $google_fonts_index, $fonts_url ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
852 }
853
854 if ( ! empty( $google_fonts['early'] ) ) {
855 foreach ( $google_fonts['early'] as $current_font ) {
856 $google_fonts_index++;
857
858 //printf( '<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/earlyaccess/%s.css">', strtolower( str_replace( ' ', '', $current_font ) ) );
859
860 $font_url = sprintf( 'https://fonts.googleapis.com/earlyaccess/%s.css', strtolower( str_replace( ' ', '', $current_font ) ) );
861
862 wp_enqueue_style( 'google-earlyaccess-' . $google_fonts_index, $font_url ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
863 }
864 }
865
866 }
867
868 /**
869 * Enqueue fonts.
870 *
871 * Enqueue all the frontend fonts.
872 *
873 * @since 1.2.0
874 * @access public
875 *
876 * @param array $font Fonts to enqueue in the frontend.
877 */
878 public function enqueue_font( $font ) {
879 if ( in_array( $font, $this->registered_fonts ) ) {
880 return;
881 }
882
883 $this->fonts_to_enqueue[] = $font;
884 $this->registered_fonts[] = $font;
885 }
886
887 /**
888 * Parse global CSS.
889 *
890 * Enqueue the global CSS file.
891 *
892 * @since 1.2.0
893 * @access protected
894 */
895 protected function parse_global_css_code() {
896 $scheme_css_file = Global_CSS::create( 'global.css' );
897
898 $scheme_css_file->enqueue();
899 }
900
901 /**
902 * Apply builder in content.
903 *
904 * Used to apply the Elementor page editor on the post content.
905 *
906 * @since 1.0.0
907 * @access public
908 *
909 * @param string $content The post content.
910 *
911 * @return string The post content.
912 */
913 public function apply_builder_in_content( $content ) {
914 $this->restore_content_filters();
915
916 if ( Plugin::$instance->preview->is_preview_mode() || $this->_is_excerpt ) {
917 return $content;
918 }
919
920 // Remove the filter itself in order to allow other `the_content` in the elements
921 $this->remove_content_filter();
922
923 $post_id = get_the_ID();
924 $builder_content = $this->get_builder_content( $post_id );
925
926 if ( ! empty( $builder_content ) ) {
927 $content = $builder_content;
928 $this->remove_content_filters();
929 }
930
931 // Add the filter again for other `the_content` calls
932 $this->add_content_filter();
933
934 return $content;
935 }
936
937 /**
938 * Retrieve builder content.
939 *
940 * Used to render and return the post content with all the Elementor elements.
941 *
942 * Note that this method is an internal method, please use `get_builder_content_for_display()`.
943 *
944 * @since 1.0.0
945 * @access public
946 *
947 * @param int $post_id The post ID.
948 * @param bool $with_css Optional. Whether to retrieve the content with CSS
949 * or not. Default is false.
950 *
951 * @return string The post content.
952 */
953 public function get_builder_content( $post_id, $with_css = false ) {
954 if ( post_password_required( $post_id ) ) {
955 return '';
956 }
957
958 $document = Plugin::$instance->documents->get_doc_for_frontend( $post_id );
959
960 if ( ! $document || ! $document->is_built_with_elementor() ) {
961 return '';
962 }
963
964 // Change the current post, so widgets can use `documents->get_current`.
965 Plugin::$instance->documents->switch_to_document( $document );
966
967 $data = $document->get_elements_data();
968
969 /**
970 * Frontend builder content data.
971 *
972 * Filters the builder content in the frontend.
973 *
974 * @since 1.0.0
975 *
976 * @param array $data The builder content.
977 * @param int $post_id The post ID.
978 */
979 $data = apply_filters( 'elementor/frontend/builder_content_data', $data, $post_id );
980
981 do_action( 'elementor/frontend/before_get_builder_content', $document, $this->_is_excerpt );
982
983 if ( empty( $data ) ) {
984 Plugin::$instance->documents->restore_document();
985
986 return '';
987 }
988
989 if ( ! $this->_is_excerpt ) {
990 if ( $document->is_autosave() ) {
991 $css_file = Post_Preview::create( $document->get_post()->ID );
992 } else {
993 $css_file = Post_CSS::create( $post_id );
994 }
995
996 $css_file->enqueue();
997 }
998
999 ob_start();
1000
1001 // Handle JS and Customizer requests, with CSS inline.
1002 if ( is_customize_preview() || wp_doing_ajax() ) {
1003 $with_css = true;
1004 }
1005
1006 if ( ! empty( $css_file ) && $with_css ) {
1007 $css_file->print_css();
1008 }
1009
1010 $document->print_elements_with_wrapper( $data );
1011
1012 $content = ob_get_clean();
1013
1014 $content = $this->process_more_tag( $content );
1015
1016 /**
1017 * Frontend content.
1018 *
1019 * Filters the content in the frontend.
1020 *
1021 * @since 1.0.0
1022 *
1023 * @param string $content The content.
1024 */
1025 $content = apply_filters( 'elementor/frontend/the_content', $content );
1026
1027 if ( ! empty( $content ) ) {
1028 $this->_has_elementor_in_page = true;
1029 }
1030
1031 Plugin::$instance->documents->restore_document();
1032
1033 // BC
1034 // TODO: use Deprecation::do_deprecated_action() in 3.1.0
1035 do_action( 'elementor/frontend/get_builder_content', $document, $this->_is_excerpt, $with_css );
1036
1037 return $content;
1038 }
1039
1040 /**
1041 * Retrieve builder content for display.
1042 *
1043 * Used to render and return the post content with all the Elementor elements.
1044 *
1045 * @since 1.0.0
1046 * @access public
1047 *
1048 * @param int $post_id The post ID.
1049 *
1050 * @param bool $with_css Optional. Whether to retrieve the content with CSS
1051 * or not. Default is false.
1052 *
1053 * @return string The post content.
1054 */
1055 public function get_builder_content_for_display( $post_id, $with_css = false ) {
1056 if ( ! get_post( $post_id ) ) {
1057 return '';
1058 }
1059
1060 $editor = Plugin::$instance->editor;
1061
1062 // Avoid recursion
1063 if ( get_the_ID() === (int) $post_id ) {
1064 $content = '';
1065 if ( $editor->is_edit_mode() ) {
1066 $content = '<div class="elementor-alert elementor-alert-danger">' . __( 'Invalid Data: The Template ID cannot be the same as the currently edited template. Please choose a different one.', 'elementor' ) . '</div>';
1067 }
1068
1069 return $content;
1070 }
1071
1072 // Set edit mode as false, so don't render settings and etc. use the $is_edit_mode to indicate if we need the CSS inline
1073 $is_edit_mode = $editor->is_edit_mode();
1074 $editor->set_edit_mode( false );
1075
1076 $with_css = $with_css ? true : $is_edit_mode;
1077
1078 $content = $this->get_builder_content( $post_id, $with_css );
1079
1080 // Restore edit mode state
1081 Plugin::$instance->editor->set_edit_mode( $is_edit_mode );
1082
1083 return $content;
1084 }
1085
1086 /**
1087 * Start excerpt flag.
1088 *
1089 * Flags when `the_excerpt` is called. Used to avoid enqueueing CSS in the excerpt.
1090 *
1091 * @since 1.4.3
1092 * @access public
1093 *
1094 * @param string $excerpt The post excerpt.
1095 *
1096 * @return string The post excerpt.
1097 */
1098 public function start_excerpt_flag( $excerpt ) {
1099 $this->_is_excerpt = true;
1100 return $excerpt;
1101 }
1102
1103 /**
1104 * End excerpt flag.
1105 *
1106 * Flags when `the_excerpt` call ended.
1107 *
1108 * @since 1.4.3
1109 * @access public
1110 *
1111 * @param string $excerpt The post excerpt.
1112 *
1113 * @return string The post excerpt.
1114 */
1115 public function end_excerpt_flag( $excerpt ) {
1116 $this->_is_excerpt = false;
1117 return $excerpt;
1118 }
1119
1120 /**
1121 * Remove content filters.
1122 *
1123 * Remove WordPress default filters that conflicted with Elementor.
1124 *
1125 * @since 1.5.0
1126 * @access public
1127 */
1128 public function remove_content_filters() {
1129 $filters = [
1130 'wpautop',
1131 'shortcode_unautop',
1132 'wptexturize',
1133 ];
1134
1135 foreach ( $filters as $filter ) {
1136 // Check if another plugin/theme do not already removed the filter.
1137 if ( has_filter( 'the_content', $filter ) ) {
1138 remove_filter( 'the_content', $filter );
1139 $this->content_removed_filters[] = $filter;
1140 }
1141 }
1142 }
1143
1144 /**
1145 * Has Elementor In Page
1146 *
1147 * Determine whether the current page is using Elementor.
1148 *
1149 * @since 2.0.9
1150 *
1151 * @access public
1152 * @return bool
1153 */
1154 public function has_elementor_in_page() {
1155 return $this->_has_elementor_in_page;
1156 }
1157
1158 public function create_action_hash( $action, array $settings = [] ) {
1159 return '#' . rawurlencode( sprintf( 'elementor-action:action=%1$s&settings=%2$s', $action, base64_encode( wp_json_encode( $settings ) ) ) );
1160 }
1161
1162 /**
1163 * Is the current render mode is static.
1164 *
1165 * @return bool
1166 */
1167 public function is_static_render_mode() {
1168 // The render mode manager is exists only in frontend,
1169 // so by default if it is not exist the method will return false.
1170 if ( ! $this->render_mode_manager ) {
1171 return false;
1172 }
1173
1174 return $this->render_mode_manager->get_current()->is_static();
1175 }
1176
1177 /**
1178 * Get Init Settings
1179 *
1180 * Used to define the default/initial settings of the object. Inheriting classes may implement this method to define
1181 * their own default/initial settings.
1182 *
1183 * @since 2.3.0
1184 *
1185 * @access protected
1186 * @return array
1187 */
1188 protected function get_init_settings() {
1189 $is_preview_mode = Plugin::$instance->preview->is_preview_mode( Plugin::$instance->preview->get_post_id() );
1190
1191 $active_experimental_features = Plugin::$instance->experiments->get_active_features();
1192
1193 $active_experimental_features = array_fill_keys( array_keys( $active_experimental_features ), true );
1194
1195 $settings = [
1196 'environmentMode' => [
1197 'edit' => $is_preview_mode,
1198 'wpPreview' => is_preview(),
1199 'isScriptDebug' => Utils::is_script_debug(),
1200 ],
1201 'i18n' => [
1202 'shareOnFacebook' => __( 'Share on Facebook', 'elementor' ),
1203 'shareOnTwitter' => __( 'Share on Twitter', 'elementor' ),
1204 'pinIt' => __( 'Pin it', 'elementor' ),
1205 'download' => __( 'Download', 'elementor' ),
1206 'downloadImage' => __( 'Download image', 'elementor' ),
1207 'fullscreen' => __( 'Fullscreen', 'elementor' ),
1208 'zoom' => __( 'Zoom', 'elementor' ),
1209 'share' => __( 'Share', 'elementor' ),
1210 'playVideo' => __( 'Play Video', 'elementor' ),
1211 'previous' => __( 'Previous', 'elementor' ),
1212 'next' => __( 'Next', 'elementor' ),
1213 'close' => __( 'Close', 'elementor' ),
1214 ],
1215 'is_rtl' => is_rtl(),
1216 // 'breakpoints' object is kept for BC.
1217 'breakpoints' => Responsive::get_breakpoints(),
1218 // 'responsive' contains the custom breakpoints config introduced in Elementor v3.2.0
1219 'responsive' => [
1220 'breakpoints' => $this->get_breakpoints_config(),
1221 ],
1222 'version' => ELEMENTOR_VERSION,
1223 'is_static' => $this->is_static_render_mode(),
1224 'experimentalFeatures' => $active_experimental_features,
1225 'urls' => [
1226 'assets' => apply_filters( 'elementor/frontend/assets_url', ELEMENTOR_ASSETS_URL ),
1227 ],
1228 ];
1229
1230 $settings['settings'] = SettingsManager::get_settings_frontend_config();
1231
1232 $kit = Plugin::$instance->kits_manager->get_active_kit_for_frontend();
1233 $settings['kit'] = $kit->get_frontend_settings();
1234
1235 if ( is_singular() ) {
1236 $post = get_post();
1237
1238 $title = Utils::urlencode_html_entities( wp_get_document_title() );
1239
1240 // Try to use the 'large' WP image size because the Pinterest share API
1241 // has problems accepting shares with large images sometimes, and the WP 'large' thumbnail is
1242 // the largest default WP image size that will probably not be changed in most sites
1243 $featured_image_url = get_the_post_thumbnail_url( null, 'large' );
1244
1245 // If the large size was nullified, use the full size which cannot be nullified/deleted
1246 if ( ! $featured_image_url ) {
1247 $featured_image_url = get_the_post_thumbnail_url( null, 'full' );
1248 }
1249
1250 $settings['post'] = [
1251 'id' => $post->ID,
1252 'title' => $title,
1253 'excerpt' => $post->post_excerpt,
1254 'featuredImage' => $featured_image_url,
1255 ];
1256 } else {
1257 $settings['post'] = [
1258 'id' => 0,
1259 'title' => wp_get_document_title(),
1260 'excerpt' => get_the_archive_description(),
1261 ];
1262 }
1263
1264 $empty_object = (object) [];
1265
1266 if ( $is_preview_mode ) {
1267 $settings['elements'] = [
1268 'data' => $empty_object,
1269 'editSettings' => $empty_object,
1270 'keys' => $empty_object,
1271 ];
1272 }
1273
1274 if ( is_user_logged_in() ) {
1275 $user = wp_get_current_user();
1276
1277 if ( ! empty( $user->roles ) ) {
1278 $settings['user'] = [
1279 'roles' => $user->roles,
1280 ];
1281 }
1282 }
1283
1284 return $settings;
1285 }
1286
1287 private function get_breakpoints_config() {
1288 $breakpoints = Plugin::$instance->breakpoints->get_breakpoints();
1289
1290 $config = [];
1291
1292 foreach ( $breakpoints as $breakpoint_name => $breakpoint ) {
1293 $config[ $breakpoint_name ] = [
1294 'label' => $breakpoint->get_label(),
1295 'value' => $breakpoint->get_value(),
1296 'direction' => $breakpoint->get_direction(),
1297 'is_enabled' => $breakpoint->is_enabled(),
1298 ];
1299 }
1300
1301 return $config;
1302 }
1303
1304 /**
1305 * Restore content filters.
1306 *
1307 * Restore removed WordPress filters that conflicted with Elementor.
1308 *
1309 * @since 1.5.0
1310 * @access private
1311 */
1312 private function restore_content_filters() {
1313 foreach ( $this->content_removed_filters as $filter ) {
1314 add_filter( 'the_content', $filter );
1315 }
1316
1317 $this->content_removed_filters = [];
1318 }
1319
1320 /**
1321 * Process More Tag
1322 *
1323 * Respect the native WP (<!--more-->) tag
1324 *
1325 * @access private
1326 * @since 2.0.4
1327 *
1328 * @param $content
1329 *
1330 * @return string
1331 */
1332 private function process_more_tag( $content ) {
1333 $post = get_post();
1334 $content = str_replace( '&lt;!--more--&gt;', '<!--more-->', $content );
1335 $parts = get_extended( $content );
1336 if ( empty( $parts['extended'] ) ) {
1337 return $content;
1338 }
1339
1340 if ( is_singular() ) {
1341 return $parts['main'] . '<div id="more-' . $post->ID . '"></div>' . $parts['extended'];
1342 }
1343
1344 if ( empty( $parts['more_text'] ) ) {
1345 $parts['more_text'] = __( '(more&hellip;)', 'elementor' );
1346 }
1347
1348 $more_link_text = sprintf(
1349 '<span aria-label="%1$s">%2$s</span>',
1350 sprintf(
1351 /* translators: %s: Name of current post */
1352 __( 'Continue reading %s', 'elementor' ),
1353 the_title_attribute( [
1354 'echo' => false,
1355 ] )
1356 ),
1357 $parts['more_text']
1358 );
1359
1360 $more_link = apply_filters( 'the_content_more_link', sprintf( ' <a href="%s#more-%s" class="more-link elementor-more-link">%s</a>', get_permalink(), $post->ID, $more_link_text ), $more_link_text );
1361
1362 return force_balance_tags( $parts['main'] ) . $more_link;
1363 }
1364
1365 private function is_improved_assets_loading() {
1366 return Plugin::$instance->experiments->is_feature_active( 'e_optimized_assets_loading' );
1367 }
1368
1369 private function get_elementor_frontend_dependencies() {
1370 $dependencies = [
1371 'elementor-frontend-modules',
1372 'elementor-waypoints',
1373 'jquery-ui-position',
1374 ];
1375
1376 if ( ! $this->is_improved_assets_loading() ) {
1377 wp_register_script(
1378 'swiper',
1379 $this->get_js_assets_url( 'swiper', 'assets/lib/swiper/' ),
1380 [],
1381 '5.3.6',
1382 true
1383 );
1384
1385 $dependencies[] = 'swiper';
1386 $dependencies[] = 'share-link';
1387 $dependencies[] = 'elementor-dialog';
1388 }
1389
1390 return $dependencies;
1391 }
1392
1393 private function add_elementor_icons_inline_css() {
1394 $elementor_icons_library_version = '5.10.0';
1395
1396 /**
1397 * The e-icons font-face must be printed inline due to custom breakpoints.
1398 * When using custom breakpoints, the frontend CSS is loaded from the custom-frontend CSS file.
1399 * The custom frontend file is located in a different path ('uploads' folder).
1400 * Therefore, it cannot be called from a CSS file that its relative path can vary.
1401 */
1402 $elementor_icons_inline_css = sprintf( '@font-face{font-family:eicons;src:url(%1$slib/eicons/fonts/eicons.eot?%2$s);src:url(%1$slib/eicons/fonts/eicons.eot?%2$s#iefix) format("embedded-opentype"),url(%1$slib/eicons/fonts/eicons.woff2?%2$s) format("woff2"),url(%1$slib/eicons/fonts/eicons.woff?%2$s) format("woff"),url(%1$slib/eicons/fonts/eicons.ttf?%2$s) format("truetype"),url(%1$slib/eicons/fonts/eicons.svg?%2$s#eicon) format("svg");font-weight:400;font-style:normal}', ELEMENTOR_ASSETS_URL, $elementor_icons_library_version );
1403
1404 wp_add_inline_style( 'elementor-frontend', $elementor_icons_inline_css );
1405 }
1406 }
1407