PluginProbe
Elementor Website Builder – more than just a page builder / 3.4.4
Elementor Website Builder – more than just a page builder v3.4.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.4.4, at includes/frontend.php

1,452 lines 35.9 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 esc_html( $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 if ( Plugin::$instance->preview->is_preview_mode() ) {
302 $editor_preferences = SettingsManager::get_settings_managers( 'editorPreferences' );
303
304 $show_hidden_elements = $editor_preferences->get_model()->get_settings( 'show_hidden_elements' );
305
306 if ( 'yes' === $show_hidden_elements ) {
307 $classes[] = 'e-preview--show-hidden-elements';
308 }
309 }
310
311 return $classes;
312 }
313
314 /**
315 * Add content filter.
316 *
317 * Remove plain content and render the content generated by Elementor.
318 *
319 * @since 1.8.0
320 * @access public
321 */
322 public function add_content_filter() {
323 add_filter( 'the_content', [ $this, 'apply_builder_in_content' ], self::THE_CONTENT_FILTER_PRIORITY );
324 }
325
326 /**
327 * Remove content filter.
328 *
329 * When the Elementor generated content rendered, we remove the filter to prevent multiple
330 * accuracies. This way we make sure Elementor renders the content only once.
331 *
332 * @since 1.8.0
333 * @access public
334 */
335 public function remove_content_filter() {
336 remove_filter( 'the_content', [ $this, 'apply_builder_in_content' ], self::THE_CONTENT_FILTER_PRIORITY );
337 }
338
339 /**
340 * Registers scripts.
341 *
342 * Registers all the frontend scripts.
343 *
344 * Fired by `wp_enqueue_scripts` action.
345 *
346 * @since 1.2.1
347 * @access public
348 */
349 public function register_scripts() {
350 /**
351 * Before frontend register scripts.
352 *
353 * Fires before Elementor frontend scripts are registered.
354 *
355 * @since 1.2.1
356 */
357 do_action( 'elementor/frontend/before_register_scripts' );
358
359 wp_register_script(
360 'elementor-webpack-runtime',
361 $this->get_js_assets_url( 'webpack.runtime', 'assets/js/' ),
362 [],
363 ELEMENTOR_VERSION,
364 true
365 );
366
367 wp_register_script(
368 'elementor-frontend-modules',
369 $this->get_js_assets_url( 'frontend-modules' ),
370 [
371 'elementor-webpack-runtime',
372 'jquery',
373 ],
374 ELEMENTOR_VERSION,
375 true
376 );
377
378 wp_register_script(
379 'elementor-waypoints',
380 $this->get_js_assets_url( 'waypoints', 'assets/lib/waypoints/' ),
381 [
382 'jquery',
383 ],
384 '4.0.2',
385 true
386 );
387
388 wp_register_script(
389 'flatpickr',
390 $this->get_js_assets_url( 'flatpickr', 'assets/lib/flatpickr/' ),
391 [
392 'jquery',
393 ],
394 '4.1.4',
395 true
396 );
397
398 wp_register_script(
399 'imagesloaded',
400 $this->get_js_assets_url( 'imagesloaded', 'assets/lib/imagesloaded/' ),
401 [
402 'jquery',
403 ],
404 '4.1.0',
405 true
406 );
407
408 wp_register_script(
409 'jquery-numerator',
410 $this->get_js_assets_url( 'jquery-numerator', 'assets/lib/jquery-numerator/' ),
411 [
412 'jquery',
413 ],
414 '0.2.1',
415 true
416 );
417
418 /**
419 * @deprecated since 2.7.0 Use Swiper instead
420 */
421 wp_register_script(
422 'jquery-slick',
423 $this->get_js_assets_url( 'slick', 'assets/lib/slick/' ),
424 [
425 'jquery',
426 ],
427 '1.8.1',
428 true
429 );
430
431 wp_register_script(
432 'elementor-dialog',
433 $this->get_js_assets_url( 'dialog', 'assets/lib/dialog/' ),
434 [
435 'jquery-ui-position',
436 ],
437 '4.8.1',
438 true
439 );
440
441 wp_register_script(
442 'elementor-gallery',
443 $this->get_js_assets_url( 'e-gallery', 'assets/lib/e-gallery/js/' ),
444 [
445 'jquery',
446 ],
447 '1.2.0',
448 true
449 );
450
451 wp_register_script(
452 'share-link',
453 $this->get_js_assets_url( 'share-link', 'assets/lib/share-link/' ),
454 [
455 'jquery',
456 ],
457 ELEMENTOR_VERSION,
458 true
459 );
460
461 wp_register_script(
462 'elementor-frontend',
463 $this->get_js_assets_url( 'frontend' ),
464 $this->get_elementor_frontend_dependencies(),
465 ELEMENTOR_VERSION,
466 true
467 );
468
469 /**
470 * After frontend register scripts.
471 *
472 * Fires after Elementor frontend scripts are registered.
473 *
474 * @since 1.2.1
475 */
476 do_action( 'elementor/frontend/after_register_scripts' );
477 }
478
479 /**
480 * Registers styles.
481 *
482 * Registers all the frontend styles.
483 *
484 * Fired by `wp_enqueue_scripts` action.
485 *
486 * @since 1.2.0
487 * @access public
488 */
489 public function register_styles() {
490 /**
491 * Before frontend register styles.
492 *
493 * Fires before Elementor frontend styles are registered.
494 *
495 * @since 1.2.0
496 */
497 do_action( 'elementor/frontend/before_register_styles' );
498
499 wp_register_style(
500 'font-awesome',
501 $this->get_css_assets_url( 'font-awesome', 'assets/lib/font-awesome/css/' ),
502 [],
503 '4.7.0'
504 );
505
506 wp_register_style(
507 'elementor-icons',
508 $this->get_css_assets_url( 'elementor-icons', 'assets/lib/eicons/css/' ),
509 [],
510 '5.12.0'
511 );
512
513 wp_register_style(
514 'flatpickr',
515 $this->get_css_assets_url( 'flatpickr', 'assets/lib/flatpickr/' ),
516 [],
517 '4.1.4'
518 );
519
520 wp_register_style(
521 'elementor-gallery',
522 $this->get_css_assets_url( 'e-gallery', 'assets/lib/e-gallery/css/' ),
523 [],
524 '1.2.0'
525 );
526
527 $min_suffix = Utils::is_script_debug() ? '' : '.min';
528
529 $direction_suffix = is_rtl() ? '-rtl' : '';
530
531 $frontend_base_file_name = $this->is_optimized_css_mode() ? 'frontend-lite' : 'frontend';
532
533 $frontend_file_name = $frontend_base_file_name . $direction_suffix . $min_suffix . '.css';
534
535 $has_custom_file = Plugin::$instance->breakpoints->has_custom_breakpoints();
536
537 if ( $has_custom_file ) {
538 $frontend_file = new FrontendFile( 'custom-' . $frontend_file_name, Breakpoints_Manager::get_stylesheet_templates_path() . $frontend_file_name );
539
540 $time = $frontend_file->get_meta( 'time' );
541
542 if ( ! $time ) {
543 $frontend_file->update();
544 }
545
546 $frontend_file_url = $frontend_file->get_url();
547 } else {
548 $frontend_file_url = ELEMENTOR_ASSETS_URL . 'css/' . $frontend_file_name;
549 }
550
551 $frontend_dependencies = [];
552
553 if ( ! Plugin::$instance->experiments->is_feature_active( 'e_dom_optimization' ) ) {
554 // If The Dom Optimization feature is disabled, register the legacy CSS
555 wp_register_style(
556 'elementor-frontend-legacy',
557 ELEMENTOR_ASSETS_URL . 'css/frontend-legacy' . $direction_suffix . $min_suffix . '.css',
558 [],
559 ELEMENTOR_VERSION
560 );
561
562 $frontend_dependencies[] = 'elementor-frontend-legacy';
563 }
564
565 wp_register_style(
566 'elementor-frontend',
567 $frontend_file_url,
568 $frontend_dependencies,
569 $has_custom_file ? null : ELEMENTOR_VERSION
570 );
571
572 /**
573 * After frontend register styles.
574 *
575 * Fires after Elementor frontend styles are registered.
576 *
577 * @since 1.2.0
578 */
579 do_action( 'elementor/frontend/after_register_styles' );
580 }
581
582 /**
583 * Enqueue scripts.
584 *
585 * Enqueue all the frontend scripts.
586 *
587 * @since 1.0.0
588 * @access public
589 */
590 public function enqueue_scripts() {
591 /**
592 * Before frontend enqueue scripts.
593 *
594 * Fires before Elementor frontend scripts are enqueued.
595 *
596 * @since 1.0.0
597 */
598 do_action( 'elementor/frontend/before_enqueue_scripts' );
599
600 wp_enqueue_script( 'elementor-frontend' );
601
602 if ( ! $this->is_improved_assets_loading() ) {
603 wp_enqueue_script(
604 'preloaded-modules',
605 $this->get_js_assets_url( 'preloaded-modules', 'assets/js/' ),
606 [
607 'elementor-frontend',
608 ],
609 ELEMENTOR_VERSION,
610 true
611 );
612 }
613
614 $this->print_config();
615
616 $this->enqueue_conditional_assets();
617
618 /**
619 * After frontend enqueue scripts.
620 *
621 * Fires after Elementor frontend scripts are enqueued.
622 *
623 * @since 1.0.0
624 */
625 do_action( 'elementor/frontend/after_enqueue_scripts' );
626 }
627
628 /**
629 * Enqueue styles.
630 *
631 * Enqueue all the frontend styles.
632 *
633 * Fired by `wp_enqueue_scripts` action.
634 *
635 * @since 1.0.0
636 * @access public
637 */
638 public function enqueue_styles() {
639 static $is_enqueue_styles_already_triggered;
640
641 if ( ! $is_enqueue_styles_already_triggered ) {
642 $is_enqueue_styles_already_triggered = true;
643
644 /**
645 * Before frontend styles enqueued.
646 *
647 * Fires before Elementor frontend styles are enqueued.
648 *
649 * @since 1.0.0
650 */
651 do_action( 'elementor/frontend/before_enqueue_styles' );
652
653 $this->add_elementor_icons_inline_css();
654
655 // The e-icons are needed in preview mode for the editor icons (plus-icon for new section, folder-icon for the templates library etc.).
656 if ( ! $this->is_improved_assets_loading() || Plugin::$instance->preview->is_preview_mode() ) {
657 wp_enqueue_style( 'elementor-icons' );
658 }
659
660 wp_enqueue_style( 'elementor-frontend' );
661
662 /**
663 * After frontend styles enqueued.
664 *
665 * Fires after Elementor frontend styles are enqueued.
666 *
667 * @since 1.0.0
668 */
669 do_action( 'elementor/frontend/after_enqueue_styles' );
670
671 if ( ! Plugin::$instance->preview->is_preview_mode() ) {
672 $this->parse_global_css_code();
673
674 $post_id = get_the_ID();
675 // Check $post_id for virtual pages. check is singular because the $post_id is set to the first post on archive pages.
676 if ( $post_id && is_singular() ) {
677 $css_file = Post_CSS::create( get_the_ID() );
678 $css_file->enqueue();
679 }
680 }
681 }
682 }
683
684 /**
685 * Enqueue assets conditionally.
686 *
687 * Enqueue all assets that were pre-enabled.
688 *
689 * @since 3.3.0
690 * @access private
691 */
692 private function enqueue_conditional_assets() {
693 Plugin::$instance->assets_loader->enqueue_assets();
694 }
695
696 /**
697 * Elementor footer scripts and styles.
698 *
699 * Handle styles and scripts that are not printed in the header.
700 *
701 * Fired by `wp_footer` action.
702 *
703 * @since 1.0.11
704 * @access public
705 */
706 public function wp_footer() {
707 if ( ! $this->_has_elementor_in_page ) {
708 return;
709 }
710
711 $this->enqueue_styles();
712 $this->enqueue_scripts();
713
714 $this->print_fonts_links();
715 }
716
717 /**
718 * Print fonts links.
719 *
720 * Enqueue all the frontend fonts by url.
721 *
722 * Fired by `wp_head` action.
723 *
724 * @since 1.9.4
725 * @access public
726 */
727 public function print_fonts_links() {
728 $google_fonts = [
729 'google' => [],
730 'early' => [],
731 ];
732
733 foreach ( $this->fonts_to_enqueue as $key => $font ) {
734 $font_type = Fonts::get_font_type( $font );
735
736 switch ( $font_type ) {
737 case Fonts::GOOGLE:
738 $google_fonts['google'][] = $font;
739 break;
740
741 case Fonts::EARLYACCESS:
742 $google_fonts['early'][] = $font;
743 break;
744
745 case false:
746 $this->maybe_enqueue_icon_font( $font );
747 break;
748 default:
749 /**
750 * Print font links.
751 *
752 * Fires when Elementor frontend fonts are printed on the HEAD tag.
753 *
754 * The dynamic portion of the hook name, `$font_type`, refers to the font type.
755 *
756 * @since 2.0.0
757 *
758 * @param string $font Font name.
759 */
760 do_action( "elementor/fonts/print_font_links/{$font_type}", $font );
761 }
762 }
763 $this->fonts_to_enqueue = [];
764
765 $this->enqueue_google_fonts( $google_fonts );
766 $this->enqueue_icon_fonts();
767 }
768
769 private function maybe_enqueue_icon_font( $icon_font_type ) {
770 if ( ! Icons_Manager::is_migration_allowed() ) {
771 return;
772 }
773
774 $icons_types = Icons_Manager::get_icon_manager_tabs();
775 if ( ! isset( $icons_types[ $icon_font_type ] ) ) {
776 return;
777 }
778
779 $icon_type = $icons_types[ $icon_font_type ];
780 if ( isset( $icon_type['url'] ) ) {
781 $this->icon_fonts_to_enqueue[ $icon_font_type ] = [ $icon_type['url'] ];
782 }
783 }
784
785 private function enqueue_icon_fonts() {
786 if ( empty( $this->icon_fonts_to_enqueue ) || ! Icons_Manager::is_migration_allowed() ) {
787 return;
788 }
789
790 foreach ( $this->icon_fonts_to_enqueue as $icon_type => $css_url ) {
791 wp_enqueue_style( 'elementor-icons-' . $icon_type );
792 $this->enqueued_icon_fonts[] = $css_url;
793 }
794
795 //clear enqueued icons
796 $this->icon_fonts_to_enqueue = [];
797 }
798
799 /**
800 * Print Google fonts.
801 *
802 * Enqueue all the frontend Google fonts.
803 *
804 * Fired by `wp_head` action.
805 *
806 * @since 1.0.0
807 * @access private
808 *
809 * @param array $google_fonts Optional. Google fonts to print in the frontend.
810 * Default is an empty array.
811 */
812 private function enqueue_google_fonts( $google_fonts = [] ) {
813 static $google_fonts_index = 0;
814
815 $print_google_fonts = true;
816
817 /**
818 * Print frontend google fonts.
819 *
820 * Filters whether to enqueue Google fonts in the frontend.
821 *
822 * @since 1.0.0
823 *
824 * @param bool $print_google_fonts Whether to enqueue Google fonts. Default is true.
825 */
826 $print_google_fonts = apply_filters( 'elementor/frontend/print_google_fonts', $print_google_fonts );
827
828 if ( ! $print_google_fonts ) {
829 return;
830 }
831
832 // Print used fonts
833 if ( ! empty( $google_fonts['google'] ) ) {
834 $google_fonts_index++;
835
836 foreach ( $google_fonts['google'] as &$font ) {
837 $font = str_replace( ' ', '+', $font ) . ':100,100italic,200,200italic,300,300italic,400,400italic,500,500italic,600,600italic,700,700italic,800,800italic,900,900italic';
838 }
839
840 // Defining a font-display type to google fonts.
841 $font_display_url_str = '&display=' . Fonts::get_font_display_setting();
842
843 $fonts_url = sprintf( 'https://fonts.googleapis.com/css?family=%1$s%2$s', implode( rawurlencode( '|' ), $google_fonts['google'] ), $font_display_url_str );
844
845 $subsets = [
846 'ru_RU' => 'cyrillic',
847 'bg_BG' => 'cyrillic',
848 'he_IL' => 'hebrew',
849 'el' => 'greek',
850 'vi' => 'vietnamese',
851 'uk' => 'cyrillic',
852 'cs_CZ' => 'latin-ext',
853 'ro_RO' => 'latin-ext',
854 'pl_PL' => 'latin-ext',
855 'hr_HR' => 'latin-ext',
856 'hu_HU' => 'latin-ext',
857 'sk_SK' => 'latin-ext',
858 'tr_TR' => 'latin-ext',
859 'lt_LT' => 'latin-ext',
860 ];
861
862 /**
863 * Google font subsets.
864 *
865 * Filters the list of Google font subsets from which locale will be enqueued in frontend.
866 *
867 * @since 1.0.0
868 *
869 * @param array $subsets A list of font subsets.
870 */
871 $subsets = apply_filters( 'elementor/frontend/google_font_subsets', $subsets );
872
873 $locale = get_locale();
874
875 if ( isset( $subsets[ $locale ] ) ) {
876 $fonts_url .= '&subset=' . $subsets[ $locale ];
877 }
878
879 wp_enqueue_style( 'google-fonts-' . $google_fonts_index, $fonts_url ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
880 }
881
882 if ( ! empty( $google_fonts['early'] ) ) {
883 foreach ( $google_fonts['early'] as $current_font ) {
884 $google_fonts_index++;
885
886 //printf( '<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/earlyaccess/%s.css">', strtolower( str_replace( ' ', '', $current_font ) ) );
887
888 $font_url = sprintf( 'https://fonts.googleapis.com/earlyaccess/%s.css', strtolower( str_replace( ' ', '', $current_font ) ) );
889
890 wp_enqueue_style( 'google-earlyaccess-' . $google_fonts_index, $font_url ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
891 }
892 }
893
894 }
895
896 /**
897 * Enqueue fonts.
898 *
899 * Enqueue all the frontend fonts.
900 *
901 * @since 1.2.0
902 * @access public
903 *
904 * @param array $font Fonts to enqueue in the frontend.
905 */
906 public function enqueue_font( $font ) {
907 if ( in_array( $font, $this->registered_fonts ) ) {
908 return;
909 }
910
911 $this->fonts_to_enqueue[] = $font;
912 $this->registered_fonts[] = $font;
913 }
914
915 /**
916 * Parse global CSS.
917 *
918 * Enqueue the global CSS file.
919 *
920 * @since 1.2.0
921 * @access protected
922 */
923 protected function parse_global_css_code() {
924 $scheme_css_file = Global_CSS::create( 'global.css' );
925
926 $scheme_css_file->enqueue();
927 }
928
929 /**
930 * Apply builder in content.
931 *
932 * Used to apply the Elementor page editor on the post content.
933 *
934 * @since 1.0.0
935 * @access public
936 *
937 * @param string $content The post content.
938 *
939 * @return string The post content.
940 */
941 public function apply_builder_in_content( $content ) {
942 $this->restore_content_filters();
943
944 if ( Plugin::$instance->preview->is_preview_mode() || $this->_is_excerpt ) {
945 return $content;
946 }
947
948 // Remove the filter itself in order to allow other `the_content` in the elements
949 $this->remove_content_filter();
950
951 $post_id = get_the_ID();
952 $builder_content = $this->get_builder_content( $post_id );
953
954 if ( ! empty( $builder_content ) ) {
955 $content = $builder_content;
956 $this->remove_content_filters();
957 }
958
959 // Add the filter again for other `the_content` calls
960 $this->add_content_filter();
961
962 return $content;
963 }
964
965 /**
966 * Retrieve builder content.
967 *
968 * Used to render and return the post content with all the Elementor elements.
969 *
970 * Note that this method is an internal method, please use `get_builder_content_for_display()`.
971 *
972 * @since 1.0.0
973 * @access public
974 *
975 * @param int $post_id The post ID.
976 * @param bool $with_css Optional. Whether to retrieve the content with CSS
977 * or not. Default is false.
978 *
979 * @return string The post content.
980 */
981 public function get_builder_content( $post_id, $with_css = false ) {
982 if ( post_password_required( $post_id ) ) {
983 return '';
984 }
985
986 $document = Plugin::$instance->documents->get_doc_for_frontend( $post_id );
987
988 if ( ! $document || ! $document->is_built_with_elementor() ) {
989 return '';
990 }
991
992 // Change the current post, so widgets can use `documents->get_current`.
993 Plugin::$instance->documents->switch_to_document( $document );
994
995 $data = $document->get_elements_data();
996
997 /**
998 * Frontend builder content data.
999 *
1000 * Filters the builder content in the frontend.
1001 *
1002 * @since 1.0.0
1003 *
1004 * @param array $data The builder content.
1005 * @param int $post_id The post ID.
1006 */
1007 $data = apply_filters( 'elementor/frontend/builder_content_data', $data, $post_id );
1008
1009 do_action( 'elementor/frontend/before_get_builder_content', $document, $this->_is_excerpt );
1010
1011 if ( empty( $data ) ) {
1012 Plugin::$instance->documents->restore_document();
1013
1014 return '';
1015 }
1016
1017 if ( ! $this->_is_excerpt ) {
1018 if ( $document->is_autosave() ) {
1019 $css_file = Post_Preview::create( $document->get_post()->ID );
1020 } else {
1021 $css_file = Post_CSS::create( $post_id );
1022 }
1023
1024 $css_file->enqueue();
1025 }
1026
1027 ob_start();
1028
1029 // Handle JS and Customizer requests, with CSS inline.
1030 if ( is_customize_preview() || wp_doing_ajax() ) {
1031 $with_css = true;
1032 }
1033
1034 if ( ! empty( $css_file ) && $with_css ) {
1035 $css_file->print_css();
1036 }
1037
1038 $document->print_elements_with_wrapper( $data );
1039
1040 $content = ob_get_clean();
1041
1042 $content = $this->process_more_tag( $content );
1043
1044 /**
1045 * Frontend content.
1046 *
1047 * Filters the content in the frontend.
1048 *
1049 * @since 1.0.0
1050 *
1051 * @param string $content The content.
1052 */
1053 $content = apply_filters( 'elementor/frontend/the_content', $content );
1054
1055 if ( ! empty( $content ) ) {
1056 $this->_has_elementor_in_page = true;
1057 }
1058
1059 Plugin::$instance->documents->restore_document();
1060
1061 // BC
1062 // TODO: use Deprecation::do_deprecated_action() in 3.1.0
1063 do_action( 'elementor/frontend/get_builder_content', $document, $this->_is_excerpt, $with_css );
1064
1065 return $content;
1066 }
1067
1068 /**
1069 * Retrieve builder content for display.
1070 *
1071 * Used to render and return the post content with all the Elementor elements.
1072 *
1073 * @since 1.0.0
1074 * @access public
1075 *
1076 * @param int $post_id The post ID.
1077 *
1078 * @param bool $with_css Optional. Whether to retrieve the content with CSS
1079 * or not. Default is false.
1080 *
1081 * @return string The post content.
1082 */
1083 public function get_builder_content_for_display( $post_id, $with_css = false ) {
1084 if ( ! get_post( $post_id ) ) {
1085 return '';
1086 }
1087
1088 $editor = Plugin::$instance->editor;
1089
1090 // Avoid recursion
1091 if ( get_the_ID() === (int) $post_id ) {
1092 $content = '';
1093 if ( $editor->is_edit_mode() ) {
1094 $content = '<div class="elementor-alert elementor-alert-danger">' . esc_html__( 'Invalid Data: The Template ID cannot be the same as the currently edited template. Please choose a different one.', 'elementor' ) . '</div>';
1095 }
1096
1097 return $content;
1098 }
1099
1100 // 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
1101 $is_edit_mode = $editor->is_edit_mode();
1102 $editor->set_edit_mode( false );
1103
1104 $with_css = $with_css ? true : $is_edit_mode;
1105
1106 $content = $this->get_builder_content( $post_id, $with_css );
1107
1108 // Restore edit mode state
1109 Plugin::$instance->editor->set_edit_mode( $is_edit_mode );
1110
1111 return $content;
1112 }
1113
1114 /**
1115 * Start excerpt flag.
1116 *
1117 * Flags when `the_excerpt` is called. Used to avoid enqueueing CSS in the excerpt.
1118 *
1119 * @since 1.4.3
1120 * @access public
1121 *
1122 * @param string $excerpt The post excerpt.
1123 *
1124 * @return string The post excerpt.
1125 */
1126 public function start_excerpt_flag( $excerpt ) {
1127 $this->_is_excerpt = true;
1128 return $excerpt;
1129 }
1130
1131 /**
1132 * End excerpt flag.
1133 *
1134 * Flags when `the_excerpt` call ended.
1135 *
1136 * @since 1.4.3
1137 * @access public
1138 *
1139 * @param string $excerpt The post excerpt.
1140 *
1141 * @return string The post excerpt.
1142 */
1143 public function end_excerpt_flag( $excerpt ) {
1144 $this->_is_excerpt = false;
1145 return $excerpt;
1146 }
1147
1148 /**
1149 * Remove content filters.
1150 *
1151 * Remove WordPress default filters that conflicted with Elementor.
1152 *
1153 * @since 1.5.0
1154 * @access public
1155 */
1156 public function remove_content_filters() {
1157 $filters = [
1158 'wpautop',
1159 'shortcode_unautop',
1160 'wptexturize',
1161 ];
1162
1163 foreach ( $filters as $filter ) {
1164 // Check if another plugin/theme do not already removed the filter.
1165 if ( has_filter( 'the_content', $filter ) ) {
1166 remove_filter( 'the_content', $filter );
1167 $this->content_removed_filters[] = $filter;
1168 }
1169 }
1170 }
1171
1172 /**
1173 * Has Elementor In Page
1174 *
1175 * Determine whether the current page is using Elementor.
1176 *
1177 * @since 2.0.9
1178 *
1179 * @access public
1180 * @return bool
1181 */
1182 public function has_elementor_in_page() {
1183 return $this->_has_elementor_in_page;
1184 }
1185
1186 public function create_action_hash( $action, array $settings = [] ) {
1187 return '#' . rawurlencode( sprintf( 'elementor-action:action=%1$s&settings=%2$s', $action, base64_encode( wp_json_encode( $settings ) ) ) );
1188 }
1189
1190 /**
1191 * Is the current render mode is static.
1192 *
1193 * @return bool
1194 */
1195 public function is_static_render_mode() {
1196 // The render mode manager is exists only in frontend,
1197 // so by default if it is not exist the method will return false.
1198 if ( ! $this->render_mode_manager ) {
1199 return false;
1200 }
1201
1202 return $this->render_mode_manager->get_current()->is_static();
1203 }
1204
1205 /**
1206 * Get Init Settings
1207 *
1208 * Used to define the default/initial settings of the object. Inheriting classes may implement this method to define
1209 * their own default/initial settings.
1210 *
1211 * @since 2.3.0
1212 *
1213 * @access protected
1214 * @return array
1215 */
1216 protected function get_init_settings() {
1217 $is_preview_mode = Plugin::$instance->preview->is_preview_mode( Plugin::$instance->preview->get_post_id() );
1218
1219 $active_experimental_features = Plugin::$instance->experiments->get_active_features();
1220
1221 $active_experimental_features = array_fill_keys( array_keys( $active_experimental_features ), true );
1222
1223 $assets_url = ELEMENTOR_ASSETS_URL;
1224
1225 /**
1226 * Frontend assets URL
1227 *
1228 * Filters Elementor frontend assets URL.
1229 *
1230 * @since 2.3.0
1231 *
1232 * @param string $assets_url The frontend assets URL. Default is ELEMENTOR_ASSETS_URL.
1233 */
1234 $assets_url = apply_filters( 'elementor/frontend/assets_url', $assets_url );
1235
1236 $settings = [
1237 'environmentMode' => [
1238 'edit' => $is_preview_mode,
1239 'wpPreview' => is_preview(),
1240 'isScriptDebug' => Utils::is_script_debug(),
1241 ],
1242 'i18n' => [
1243 'shareOnFacebook' => esc_html__( 'Share on Facebook', 'elementor' ),
1244 'shareOnTwitter' => esc_html__( 'Share on Twitter', 'elementor' ),
1245 'pinIt' => esc_html__( 'Pin it', 'elementor' ),
1246 'download' => esc_html__( 'Download', 'elementor' ),
1247 'downloadImage' => esc_html__( 'Download image', 'elementor' ),
1248 'fullscreen' => esc_html__( 'Fullscreen', 'elementor' ),
1249 'zoom' => esc_html__( 'Zoom', 'elementor' ),
1250 'share' => esc_html__( 'Share', 'elementor' ),
1251 'playVideo' => esc_html__( 'Play Video', 'elementor' ),
1252 'previous' => esc_html__( 'Previous', 'elementor' ),
1253 'next' => esc_html__( 'Next', 'elementor' ),
1254 'close' => esc_html__( 'Close', 'elementor' ),
1255 ],
1256 'is_rtl' => is_rtl(),
1257 // 'breakpoints' object is kept for BC.
1258 'breakpoints' => Responsive::get_breakpoints(),
1259 // 'responsive' contains the custom breakpoints config introduced in Elementor v3.2.0
1260 'responsive' => [
1261 'breakpoints' => Plugin::$instance->breakpoints->get_breakpoints_config(),
1262 ],
1263 'version' => ELEMENTOR_VERSION,
1264 'is_static' => $this->is_static_render_mode(),
1265 'experimentalFeatures' => $active_experimental_features,
1266 'urls' => [
1267 'assets' => $assets_url,
1268 ],
1269 ];
1270
1271 $settings['settings'] = SettingsManager::get_settings_frontend_config();
1272
1273 $kit = Plugin::$instance->kits_manager->get_active_kit_for_frontend();
1274 $settings['kit'] = $kit->get_frontend_settings();
1275
1276 if ( is_singular() ) {
1277 $post = get_post();
1278
1279 $title = Utils::urlencode_html_entities( wp_get_document_title() );
1280
1281 // Try to use the 'large' WP image size because the Pinterest share API
1282 // has problems accepting shares with large images sometimes, and the WP 'large' thumbnail is
1283 // the largest default WP image size that will probably not be changed in most sites
1284 $featured_image_url = get_the_post_thumbnail_url( null, 'large' );
1285
1286 // If the large size was nullified, use the full size which cannot be nullified/deleted
1287 if ( ! $featured_image_url ) {
1288 $featured_image_url = get_the_post_thumbnail_url( null, 'full' );
1289 }
1290
1291 $settings['post'] = [
1292 'id' => $post->ID,
1293 'title' => $title,
1294 'excerpt' => $post->post_excerpt,
1295 'featuredImage' => $featured_image_url,
1296 ];
1297 } else {
1298 $settings['post'] = [
1299 'id' => 0,
1300 'title' => wp_get_document_title(),
1301 'excerpt' => get_the_archive_description(),
1302 ];
1303 }
1304
1305 $empty_object = (object) [];
1306
1307 if ( $is_preview_mode ) {
1308 $settings['elements'] = [
1309 'data' => $empty_object,
1310 'editSettings' => $empty_object,
1311 'keys' => $empty_object,
1312 ];
1313 }
1314
1315 if ( is_user_logged_in() ) {
1316 $user = wp_get_current_user();
1317
1318 if ( ! empty( $user->roles ) ) {
1319 $settings['user'] = [
1320 'roles' => $user->roles,
1321 ];
1322 }
1323 }
1324
1325 return $settings;
1326 }
1327
1328 /**
1329 * Restore content filters.
1330 *
1331 * Restore removed WordPress filters that conflicted with Elementor.
1332 *
1333 * @since 1.5.0
1334 * @access public
1335 */
1336 public function restore_content_filters() {
1337 foreach ( $this->content_removed_filters as $filter ) {
1338 add_filter( 'the_content', $filter );
1339 }
1340
1341 $this->content_removed_filters = [];
1342 }
1343
1344 /**
1345 * Process More Tag
1346 *
1347 * Respect the native WP (<!--more-->) tag
1348 *
1349 * @access private
1350 * @since 2.0.4
1351 *
1352 * @param $content
1353 *
1354 * @return string
1355 */
1356 private function process_more_tag( $content ) {
1357 $post = get_post();
1358 $content = str_replace( '&lt;!--more--&gt;', '<!--more-->', $content );
1359 $parts = get_extended( $content );
1360 if ( empty( $parts['extended'] ) ) {
1361 return $content;
1362 }
1363
1364 if ( is_singular() ) {
1365 return $parts['main'] . '<div id="more-' . $post->ID . '"></div>' . $parts['extended'];
1366 }
1367
1368 if ( empty( $parts['more_text'] ) ) {
1369 $parts['more_text'] = esc_html__( '(more&hellip;)', 'elementor' );
1370 }
1371
1372 $more_link_text = sprintf(
1373 '<span aria-label="%1$s">%2$s</span>',
1374 sprintf(
1375 /* translators: %s: Name of current post */
1376 __( 'Continue reading %s', 'elementor' ),
1377 the_title_attribute( [
1378 'echo' => false,
1379 ] )
1380 ),
1381 $parts['more_text']
1382 );
1383
1384 $more_link = sprintf( ' <a href="%s#more-%s" class="more-link elementor-more-link">%s</a>', get_permalink(), $post->ID, $more_link_text );
1385
1386 /**
1387 * The content "more" link.
1388 *
1389 * Filters the "more" link displayed after the content.
1390 *
1391 * This hook can be used either to change the link syntax or to change the
1392 * text inside the link.
1393 *
1394 * @since 2.0.4
1395 *
1396 * @param string $more_link The more link.
1397 * @param string $more_link_text The text inside the more link.
1398 */
1399 $more_link = apply_filters( 'the_content_more_link', $more_link, $more_link_text );
1400
1401 return force_balance_tags( $parts['main'] ) . $more_link;
1402 }
1403
1404 private function is_improved_assets_loading() {
1405 return Plugin::$instance->experiments->is_feature_active( 'e_optimized_assets_loading' );
1406 }
1407
1408 private function get_elementor_frontend_dependencies() {
1409 $dependencies = [
1410 'elementor-frontend-modules',
1411 'elementor-waypoints',
1412 'jquery-ui-position',
1413 ];
1414
1415 if ( ! $this->is_improved_assets_loading() ) {
1416 wp_register_script(
1417 'swiper',
1418 $this->get_js_assets_url( 'swiper', 'assets/lib/swiper/' ),
1419 [],
1420 '5.3.6',
1421 true
1422 );
1423
1424 $dependencies[] = 'swiper';
1425 $dependencies[] = 'share-link';
1426 $dependencies[] = 'elementor-dialog';
1427 }
1428
1429 return $dependencies;
1430 }
1431
1432 private function is_optimized_css_mode() {
1433 $is_optimized_css_loading = Plugin::$instance->experiments->is_feature_active( 'e_optimized_css_loading' );
1434
1435 return ! Utils::is_script_debug() && $is_optimized_css_loading && ! Plugin::$instance->preview->is_preview_mode();
1436 }
1437
1438 private function add_elementor_icons_inline_css() {
1439 $elementor_icons_library_version = '5.10.0';
1440
1441 /**
1442 * The e-icons font-face must be printed inline due to custom breakpoints.
1443 * When using custom breakpoints, the frontend CSS is loaded from the custom-frontend CSS file.
1444 * The custom frontend file is located in a different path ('uploads' folder).
1445 * Therefore, it cannot be called from a CSS file that its relative path can vary.
1446 */
1447 $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 );
1448
1449 wp_add_inline_style( 'elementor-frontend', $elementor_icons_inline_css );
1450 }
1451 }
1452