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

1,415 lines 35.1 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 $subsets = apply_filters( 'elementor/frontend/google_font_subsets', $subsets );
863
864 $locale = get_locale();
865
866 if ( isset( $subsets[ $locale ] ) ) {
867 $fonts_url .= '&subset=' . $subsets[ $locale ];
868 }
869
870 wp_enqueue_style( 'google-fonts-' . $google_fonts_index, $fonts_url ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
871 }
872
873 if ( ! empty( $google_fonts['early'] ) ) {
874 foreach ( $google_fonts['early'] as $current_font ) {
875 $google_fonts_index++;
876
877 //printf( '<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/earlyaccess/%s.css">', strtolower( str_replace( ' ', '', $current_font ) ) );
878
879 $font_url = sprintf( 'https://fonts.googleapis.com/earlyaccess/%s.css', strtolower( str_replace( ' ', '', $current_font ) ) );
880
881 wp_enqueue_style( 'google-earlyaccess-' . $google_fonts_index, $font_url ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
882 }
883 }
884
885 }
886
887 /**
888 * Enqueue fonts.
889 *
890 * Enqueue all the frontend fonts.
891 *
892 * @since 1.2.0
893 * @access public
894 *
895 * @param array $font Fonts to enqueue in the frontend.
896 */
897 public function enqueue_font( $font ) {
898 if ( in_array( $font, $this->registered_fonts ) ) {
899 return;
900 }
901
902 $this->fonts_to_enqueue[] = $font;
903 $this->registered_fonts[] = $font;
904 }
905
906 /**
907 * Parse global CSS.
908 *
909 * Enqueue the global CSS file.
910 *
911 * @since 1.2.0
912 * @access protected
913 */
914 protected function parse_global_css_code() {
915 $scheme_css_file = Global_CSS::create( 'global.css' );
916
917 $scheme_css_file->enqueue();
918 }
919
920 /**
921 * Apply builder in content.
922 *
923 * Used to apply the Elementor page editor on the post content.
924 *
925 * @since 1.0.0
926 * @access public
927 *
928 * @param string $content The post content.
929 *
930 * @return string The post content.
931 */
932 public function apply_builder_in_content( $content ) {
933 $this->restore_content_filters();
934
935 if ( Plugin::$instance->preview->is_preview_mode() || $this->_is_excerpt ) {
936 return $content;
937 }
938
939 // Remove the filter itself in order to allow other `the_content` in the elements
940 $this->remove_content_filter();
941
942 $post_id = get_the_ID();
943 $builder_content = $this->get_builder_content( $post_id );
944
945 if ( ! empty( $builder_content ) ) {
946 $content = $builder_content;
947 $this->remove_content_filters();
948 }
949
950 // Add the filter again for other `the_content` calls
951 $this->add_content_filter();
952
953 return $content;
954 }
955
956 /**
957 * Retrieve builder content.
958 *
959 * Used to render and return the post content with all the Elementor elements.
960 *
961 * Note that this method is an internal method, please use `get_builder_content_for_display()`.
962 *
963 * @since 1.0.0
964 * @access public
965 *
966 * @param int $post_id The post ID.
967 * @param bool $with_css Optional. Whether to retrieve the content with CSS
968 * or not. Default is false.
969 *
970 * @return string The post content.
971 */
972 public function get_builder_content( $post_id, $with_css = false ) {
973 if ( post_password_required( $post_id ) ) {
974 return '';
975 }
976
977 $document = Plugin::$instance->documents->get_doc_for_frontend( $post_id );
978
979 if ( ! $document || ! $document->is_built_with_elementor() ) {
980 return '';
981 }
982
983 // Change the current post, so widgets can use `documents->get_current`.
984 Plugin::$instance->documents->switch_to_document( $document );
985
986 $data = $document->get_elements_data();
987
988 /**
989 * Frontend builder content data.
990 *
991 * Filters the builder content in the frontend.
992 *
993 * @since 1.0.0
994 *
995 * @param array $data The builder content.
996 * @param int $post_id The post ID.
997 */
998 $data = apply_filters( 'elementor/frontend/builder_content_data', $data, $post_id );
999
1000 do_action( 'elementor/frontend/before_get_builder_content', $document, $this->_is_excerpt );
1001
1002 if ( empty( $data ) ) {
1003 Plugin::$instance->documents->restore_document();
1004
1005 return '';
1006 }
1007
1008 if ( ! $this->_is_excerpt ) {
1009 if ( $document->is_autosave() ) {
1010 $css_file = Post_Preview::create( $document->get_post()->ID );
1011 } else {
1012 $css_file = Post_CSS::create( $post_id );
1013 }
1014
1015 $css_file->enqueue();
1016 }
1017
1018 ob_start();
1019
1020 // Handle JS and Customizer requests, with CSS inline.
1021 if ( is_customize_preview() || wp_doing_ajax() ) {
1022 $with_css = true;
1023 }
1024
1025 if ( ! empty( $css_file ) && $with_css ) {
1026 $css_file->print_css();
1027 }
1028
1029 $document->print_elements_with_wrapper( $data );
1030
1031 $content = ob_get_clean();
1032
1033 $content = $this->process_more_tag( $content );
1034
1035 /**
1036 * Frontend content.
1037 *
1038 * Filters the content in the frontend.
1039 *
1040 * @since 1.0.0
1041 *
1042 * @param string $content The content.
1043 */
1044 $content = apply_filters( 'elementor/frontend/the_content', $content );
1045
1046 if ( ! empty( $content ) ) {
1047 $this->_has_elementor_in_page = true;
1048 }
1049
1050 Plugin::$instance->documents->restore_document();
1051
1052 // BC
1053 // TODO: use Deprecation::do_deprecated_action() in 3.1.0
1054 do_action( 'elementor/frontend/get_builder_content', $document, $this->_is_excerpt, $with_css );
1055
1056 return $content;
1057 }
1058
1059 /**
1060 * Retrieve builder content for display.
1061 *
1062 * Used to render and return the post content with all the Elementor elements.
1063 *
1064 * @since 1.0.0
1065 * @access public
1066 *
1067 * @param int $post_id The post ID.
1068 *
1069 * @param bool $with_css Optional. Whether to retrieve the content with CSS
1070 * or not. Default is false.
1071 *
1072 * @return string The post content.
1073 */
1074 public function get_builder_content_for_display( $post_id, $with_css = false ) {
1075 if ( ! get_post( $post_id ) ) {
1076 return '';
1077 }
1078
1079 $editor = Plugin::$instance->editor;
1080
1081 // Avoid recursion
1082 if ( get_the_ID() === (int) $post_id ) {
1083 $content = '';
1084 if ( $editor->is_edit_mode() ) {
1085 $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>';
1086 }
1087
1088 return $content;
1089 }
1090
1091 // 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
1092 $is_edit_mode = $editor->is_edit_mode();
1093 $editor->set_edit_mode( false );
1094
1095 $with_css = $with_css ? true : $is_edit_mode;
1096
1097 $content = $this->get_builder_content( $post_id, $with_css );
1098
1099 // Restore edit mode state
1100 Plugin::$instance->editor->set_edit_mode( $is_edit_mode );
1101
1102 return $content;
1103 }
1104
1105 /**
1106 * Start excerpt flag.
1107 *
1108 * Flags when `the_excerpt` is called. Used to avoid enqueueing CSS in the excerpt.
1109 *
1110 * @since 1.4.3
1111 * @access public
1112 *
1113 * @param string $excerpt The post excerpt.
1114 *
1115 * @return string The post excerpt.
1116 */
1117 public function start_excerpt_flag( $excerpt ) {
1118 $this->_is_excerpt = true;
1119 return $excerpt;
1120 }
1121
1122 /**
1123 * End excerpt flag.
1124 *
1125 * Flags when `the_excerpt` call ended.
1126 *
1127 * @since 1.4.3
1128 * @access public
1129 *
1130 * @param string $excerpt The post excerpt.
1131 *
1132 * @return string The post excerpt.
1133 */
1134 public function end_excerpt_flag( $excerpt ) {
1135 $this->_is_excerpt = false;
1136 return $excerpt;
1137 }
1138
1139 /**
1140 * Remove content filters.
1141 *
1142 * Remove WordPress default filters that conflicted with Elementor.
1143 *
1144 * @since 1.5.0
1145 * @access public
1146 */
1147 public function remove_content_filters() {
1148 $filters = [
1149 'wpautop',
1150 'shortcode_unautop',
1151 'wptexturize',
1152 ];
1153
1154 foreach ( $filters as $filter ) {
1155 // Check if another plugin/theme do not already removed the filter.
1156 if ( has_filter( 'the_content', $filter ) ) {
1157 remove_filter( 'the_content', $filter );
1158 $this->content_removed_filters[] = $filter;
1159 }
1160 }
1161 }
1162
1163 /**
1164 * Has Elementor In Page
1165 *
1166 * Determine whether the current page is using Elementor.
1167 *
1168 * @since 2.0.9
1169 *
1170 * @access public
1171 * @return bool
1172 */
1173 public function has_elementor_in_page() {
1174 return $this->_has_elementor_in_page;
1175 }
1176
1177 public function create_action_hash( $action, array $settings = [] ) {
1178 return '#' . rawurlencode( sprintf( 'elementor-action:action=%1$s&settings=%2$s', $action, base64_encode( wp_json_encode( $settings ) ) ) );
1179 }
1180
1181 /**
1182 * Is the current render mode is static.
1183 *
1184 * @return bool
1185 */
1186 public function is_static_render_mode() {
1187 // The render mode manager is exists only in frontend,
1188 // so by default if it is not exist the method will return false.
1189 if ( ! $this->render_mode_manager ) {
1190 return false;
1191 }
1192
1193 return $this->render_mode_manager->get_current()->is_static();
1194 }
1195
1196 /**
1197 * Get Init Settings
1198 *
1199 * Used to define the default/initial settings of the object. Inheriting classes may implement this method to define
1200 * their own default/initial settings.
1201 *
1202 * @since 2.3.0
1203 *
1204 * @access protected
1205 * @return array
1206 */
1207 protected function get_init_settings() {
1208 $is_preview_mode = Plugin::$instance->preview->is_preview_mode( Plugin::$instance->preview->get_post_id() );
1209
1210 $active_experimental_features = Plugin::$instance->experiments->get_active_features();
1211
1212 $active_experimental_features = array_fill_keys( array_keys( $active_experimental_features ), true );
1213
1214 $settings = [
1215 'environmentMode' => [
1216 'edit' => $is_preview_mode,
1217 'wpPreview' => is_preview(),
1218 'isScriptDebug' => Utils::is_script_debug(),
1219 ],
1220 'i18n' => [
1221 'shareOnFacebook' => esc_html__( 'Share on Facebook', 'elementor' ),
1222 'shareOnTwitter' => esc_html__( 'Share on Twitter', 'elementor' ),
1223 'pinIt' => esc_html__( 'Pin it', 'elementor' ),
1224 'download' => esc_html__( 'Download', 'elementor' ),
1225 'downloadImage' => esc_html__( 'Download image', 'elementor' ),
1226 'fullscreen' => esc_html__( 'Fullscreen', 'elementor' ),
1227 'zoom' => esc_html__( 'Zoom', 'elementor' ),
1228 'share' => esc_html__( 'Share', 'elementor' ),
1229 'playVideo' => esc_html__( 'Play Video', 'elementor' ),
1230 'previous' => esc_html__( 'Previous', 'elementor' ),
1231 'next' => esc_html__( 'Next', 'elementor' ),
1232 'close' => esc_html__( 'Close', 'elementor' ),
1233 ],
1234 'is_rtl' => is_rtl(),
1235 // 'breakpoints' object is kept for BC.
1236 'breakpoints' => Responsive::get_breakpoints(),
1237 // 'responsive' contains the custom breakpoints config introduced in Elementor v3.2.0
1238 'responsive' => [
1239 'breakpoints' => Plugin::$instance->breakpoints->get_breakpoints_config(),
1240 ],
1241 'version' => ELEMENTOR_VERSION,
1242 'is_static' => $this->is_static_render_mode(),
1243 'experimentalFeatures' => $active_experimental_features,
1244 'urls' => [
1245 'assets' => apply_filters( 'elementor/frontend/assets_url', ELEMENTOR_ASSETS_URL ),
1246 ],
1247 ];
1248
1249 $settings['settings'] = SettingsManager::get_settings_frontend_config();
1250
1251 $kit = Plugin::$instance->kits_manager->get_active_kit_for_frontend();
1252 $settings['kit'] = $kit->get_frontend_settings();
1253
1254 if ( is_singular() ) {
1255 $post = get_post();
1256
1257 $title = Utils::urlencode_html_entities( wp_get_document_title() );
1258
1259 // Try to use the 'large' WP image size because the Pinterest share API
1260 // has problems accepting shares with large images sometimes, and the WP 'large' thumbnail is
1261 // the largest default WP image size that will probably not be changed in most sites
1262 $featured_image_url = get_the_post_thumbnail_url( null, 'large' );
1263
1264 // If the large size was nullified, use the full size which cannot be nullified/deleted
1265 if ( ! $featured_image_url ) {
1266 $featured_image_url = get_the_post_thumbnail_url( null, 'full' );
1267 }
1268
1269 $settings['post'] = [
1270 'id' => $post->ID,
1271 'title' => $title,
1272 'excerpt' => $post->post_excerpt,
1273 'featuredImage' => $featured_image_url,
1274 ];
1275 } else {
1276 $settings['post'] = [
1277 'id' => 0,
1278 'title' => wp_get_document_title(),
1279 'excerpt' => get_the_archive_description(),
1280 ];
1281 }
1282
1283 $empty_object = (object) [];
1284
1285 if ( $is_preview_mode ) {
1286 $settings['elements'] = [
1287 'data' => $empty_object,
1288 'editSettings' => $empty_object,
1289 'keys' => $empty_object,
1290 ];
1291 }
1292
1293 if ( is_user_logged_in() ) {
1294 $user = wp_get_current_user();
1295
1296 if ( ! empty( $user->roles ) ) {
1297 $settings['user'] = [
1298 'roles' => $user->roles,
1299 ];
1300 }
1301 }
1302
1303 return $settings;
1304 }
1305
1306 /**
1307 * Restore content filters.
1308 *
1309 * Restore removed WordPress filters that conflicted with Elementor.
1310 *
1311 * @since 1.5.0
1312 * @access public
1313 */
1314 public function restore_content_filters() {
1315 foreach ( $this->content_removed_filters as $filter ) {
1316 add_filter( 'the_content', $filter );
1317 }
1318
1319 $this->content_removed_filters = [];
1320 }
1321
1322 /**
1323 * Process More Tag
1324 *
1325 * Respect the native WP (<!--more-->) tag
1326 *
1327 * @access private
1328 * @since 2.0.4
1329 *
1330 * @param $content
1331 *
1332 * @return string
1333 */
1334 private function process_more_tag( $content ) {
1335 $post = get_post();
1336 $content = str_replace( '&lt;!--more--&gt;', '<!--more-->', $content );
1337 $parts = get_extended( $content );
1338 if ( empty( $parts['extended'] ) ) {
1339 return $content;
1340 }
1341
1342 if ( is_singular() ) {
1343 return $parts['main'] . '<div id="more-' . $post->ID . '"></div>' . $parts['extended'];
1344 }
1345
1346 if ( empty( $parts['more_text'] ) ) {
1347 $parts['more_text'] = esc_html__( '(more&hellip;)', 'elementor' );
1348 }
1349
1350 $more_link_text = sprintf(
1351 '<span aria-label="%1$s">%2$s</span>',
1352 sprintf(
1353 /* translators: %s: Name of current post */
1354 __( 'Continue reading %s', 'elementor' ),
1355 the_title_attribute( [
1356 'echo' => false,
1357 ] )
1358 ),
1359 $parts['more_text']
1360 );
1361
1362 $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 );
1363
1364 return force_balance_tags( $parts['main'] ) . $more_link;
1365 }
1366
1367 private function is_improved_assets_loading() {
1368 return Plugin::$instance->experiments->is_feature_active( 'e_optimized_assets_loading' );
1369 }
1370
1371 private function get_elementor_frontend_dependencies() {
1372 $dependencies = [
1373 'elementor-frontend-modules',
1374 'elementor-waypoints',
1375 'jquery-ui-position',
1376 ];
1377
1378 if ( ! $this->is_improved_assets_loading() ) {
1379 wp_register_script(
1380 'swiper',
1381 $this->get_js_assets_url( 'swiper', 'assets/lib/swiper/' ),
1382 [],
1383 '5.3.6',
1384 true
1385 );
1386
1387 $dependencies[] = 'swiper';
1388 $dependencies[] = 'share-link';
1389 $dependencies[] = 'elementor-dialog';
1390 }
1391
1392 return $dependencies;
1393 }
1394
1395 private function is_optimized_css_mode() {
1396 $is_optimized_css_loading = Plugin::$instance->experiments->is_feature_active( 'e_optimized_css_loading' );
1397
1398 return ! Utils::is_script_debug() && $is_optimized_css_loading && ! Plugin::$instance->preview->is_preview_mode();
1399 }
1400
1401 private function add_elementor_icons_inline_css() {
1402 $elementor_icons_library_version = '5.10.0';
1403
1404 /**
1405 * The e-icons font-face must be printed inline due to custom breakpoints.
1406 * When using custom breakpoints, the frontend CSS is loaded from the custom-frontend CSS file.
1407 * The custom frontend file is located in a different path ('uploads' folder).
1408 * Therefore, it cannot be called from a CSS file that its relative path can vary.
1409 */
1410 $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 );
1411
1412 wp_add_inline_style( 'elementor-frontend', $elementor_icons_inline_css );
1413 }
1414 }
1415