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

1,541 lines 38.2 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\Frontend\Render_Mode_Manager;
6 use Elementor\Core\Responsive\Files\Frontend as FrontendFile;
7 use Elementor\Core\Files\CSS\Global_CSS;
8 use Elementor\Core\Files\CSS\Post as Post_CSS;
9 use Elementor\Core\Files\CSS\Post_Preview;
10 use Elementor\Core\Responsive\Responsive;
11 use Elementor\Core\Settings\Manager as SettingsManager;
12 use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager;
13 use Elementor\Modules\FloatingButtons\Module;
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 private $google_fonts_index = 0;
145
146 /**
147 * @var string
148 */
149 private $e_swiper_asset_path;
150
151 /**
152 * @var string
153 */
154 private $e_swiper_version;
155
156 /**
157 * Front End constructor.
158 *
159 * Initializing Elementor front end. Make sure we are not in admin, not and
160 * redirect from old URL structure of Elementor editor.
161 *
162 * @since 1.0.0
163 * @access public
164 */
165 public function __construct() {
166 // We don't need this class in admin side, but in AJAX requests.
167 if ( is_admin() && ! wp_doing_ajax() ) {
168 return;
169 }
170
171 add_action( 'template_redirect', [ $this, 'init_render_mode' ], -1 /* Before admin bar. */ );
172 add_action( 'template_redirect', [ $this, 'init' ] );
173 add_action( 'wp_enqueue_scripts', [ $this, 'register_scripts' ], 5 );
174 add_action( 'wp_enqueue_scripts', [ $this, 'register_styles' ], 5 );
175
176 $this->add_content_filter();
177 $this->init_swiper_settings();
178
179 // Hack to avoid enqueue post CSS while it's a `the_excerpt` call.
180 add_filter( 'get_the_excerpt', [ $this, 'start_excerpt_flag' ], 1 );
181 add_filter( 'get_the_excerpt', [ $this, 'end_excerpt_flag' ], 20 );
182 }
183
184 /**
185 * Get module name.
186 *
187 * Retrieve the module name.
188 *
189 * @since 2.3.0
190 * @access public
191 *
192 * @return string Module name.
193 */
194 public function get_name() {
195 return 'frontend';
196 }
197
198 /**
199 * Init render mode manager.
200 */
201 public function init_render_mode() {
202 if ( Plugin::$instance->editor->is_edit_mode() ) {
203 return;
204 }
205
206 $this->render_mode_manager = new Render_Mode_Manager();
207 }
208
209 /**
210 * Init.
211 *
212 * Initialize Elementor front end. Hooks the needed actions to run Elementor
213 * in the front end, including script and style registration.
214 *
215 * Fired by `template_redirect` action.
216 *
217 * @since 1.0.0
218 * @access public
219 */
220 public function init() {
221 if ( Plugin::$instance->editor->is_edit_mode() ) {
222 return;
223 }
224
225 add_filter( 'body_class', [ $this, 'body_class' ] );
226
227 if ( Plugin::$instance->preview->is_preview_mode() ) {
228 return;
229 }
230
231 if ( current_user_can( 'manage_options' ) ) {
232 Plugin::$instance->init_common();
233 }
234
235 $this->post_id = get_the_ID();
236
237 $document = Plugin::$instance->documents->get( $this->post_id );
238
239 if ( is_singular() && $document && $document->is_built_with_elementor() ) {
240 add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_styles' ] );
241 }
242
243 // Priority 7 to allow google fonts in header template to load in <head> tag
244 add_action( 'wp_head', [ $this, 'print_fonts_links' ], 7 );
245 add_action( 'wp_head', [ $this, 'print_google_fonts_preconnect_tag' ], 8 );
246 add_action( 'wp_head', [ $this, 'add_theme_color_meta_tag' ] );
247 add_action( 'wp_footer', [ $this, 'wp_footer' ] );
248 }
249
250 public function print_google_fonts_preconnect_tag() {
251 if ( 0 >= $this->google_fonts_index ) {
252 return;
253 }
254
255 echo '<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin>';
256 }
257
258 /**
259 * @since 2.0.12
260 * @access public
261 * @param string|array $class
262 */
263 public function add_body_class( $class ) {
264 if ( is_array( $class ) ) {
265 $this->body_classes = array_merge( $this->body_classes, $class );
266 } else {
267 $this->body_classes[] = $class;
268 }
269 }
270
271 /**
272 * Add Theme Color Meta Tag
273 *
274 * @since 3.0.0
275 * @access public
276 */
277 public function add_theme_color_meta_tag() {
278 $kit = Plugin::$instance->kits_manager->get_active_kit_for_frontend();
279 $mobile_theme_color = $kit->get_settings( 'mobile_browser_background' );
280
281 if ( ! empty( $mobile_theme_color ) ) {
282 ?>
283 <meta name="theme-color" content="<?php echo esc_attr( $mobile_theme_color ); ?>">
284 <?php
285 }
286 }
287
288 /**
289 * Body tag classes.
290 *
291 * Add new elementor classes to the body tag.
292 *
293 * Fired by `body_class` filter.
294 *
295 * @since 1.0.0
296 * @access public
297 *
298 * @param array $classes Optional. One or more classes to add to the body tag class list.
299 * Default is an empty array.
300 *
301 * @return array Body tag classes.
302 */
303 public function body_class( $classes = [] ) {
304 $classes = array_merge( $classes, $this->body_classes );
305
306 $id = get_the_ID();
307
308 $document = Plugin::$instance->documents->get( $id );
309
310 if ( is_singular() && $document && $document->is_built_with_elementor() ) {
311 $classes[] = 'elementor-page elementor-page-' . $id;
312 }
313
314 if ( Plugin::$instance->preview->is_preview_mode() ) {
315 $editor_preferences = SettingsManager::get_settings_managers( 'editorPreferences' );
316
317 $show_hidden_elements = $editor_preferences->get_model()->get_settings( 'show_hidden_elements' );
318
319 if ( 'yes' === $show_hidden_elements ) {
320 $classes[] = 'e-preview--show-hidden-elements';
321 }
322 }
323
324 return $classes;
325 }
326
327 /**
328 * Add content filter.
329 *
330 * Remove plain content and render the content generated by Elementor.
331 *
332 * @since 1.8.0
333 * @access public
334 */
335 public function add_content_filter() {
336 add_filter( 'the_content', [ $this, 'apply_builder_in_content' ], self::THE_CONTENT_FILTER_PRIORITY );
337 }
338
339 public function init_swiper_settings() {
340 $e_swiper_latest = Plugin::$instance->experiments->is_feature_active( 'e_swiper_latest' );
341 $this->e_swiper_asset_path = $e_swiper_latest ? 'assets/lib/swiper/v8/' : 'assets/lib/swiper/';
342 $this->e_swiper_version = $e_swiper_latest ? '8.4.5' : '5.3.6';
343 }
344
345 /**
346 * Remove content filter.
347 *
348 * When the Elementor generated content rendered, we remove the filter to prevent multiple
349 * accuracies. This way we make sure Elementor renders the content only once.
350 *
351 * @since 1.8.0
352 * @access public
353 */
354 public function remove_content_filter() {
355 remove_filter( 'the_content', [ $this, 'apply_builder_in_content' ], self::THE_CONTENT_FILTER_PRIORITY );
356 }
357
358 /**
359 * Registers scripts.
360 *
361 * Registers all the frontend scripts.
362 *
363 * Fired by `wp_enqueue_scripts` action.
364 *
365 * @since 1.2.1
366 * @access public
367 */
368 public function register_scripts() {
369 /**
370 * Before frontend register scripts.
371 *
372 * Fires before Elementor frontend scripts are registered.
373 *
374 * @since 1.2.1
375 */
376 do_action( 'elementor/frontend/before_register_scripts' );
377
378 wp_register_script(
379 'elementor-webpack-runtime',
380 $this->get_js_assets_url( 'webpack.runtime', 'assets/js/' ),
381 [],
382 ELEMENTOR_VERSION,
383 true
384 );
385
386 wp_register_script(
387 'elementor-frontend-modules',
388 $this->get_js_assets_url( 'frontend-modules' ),
389 [
390 'elementor-webpack-runtime',
391 'jquery',
392 ],
393 ELEMENTOR_VERSION,
394 true
395 );
396
397 wp_register_script(
398 'elementor-waypoints',
399 $this->get_js_assets_url( 'waypoints', 'assets/lib/waypoints/' ),
400 [
401 'jquery',
402 ],
403 '4.0.2',
404 true
405 );
406
407 wp_register_script(
408 'flatpickr',
409 $this->get_js_assets_url( 'flatpickr', 'assets/lib/flatpickr/' ),
410 [
411 'jquery',
412 ],
413 '4.1.4',
414 true
415 );
416
417 wp_register_script(
418 'imagesloaded',
419 $this->get_js_assets_url( 'imagesloaded', 'assets/lib/imagesloaded/' ),
420 [
421 'jquery',
422 ],
423 '4.1.0',
424 true
425 );
426
427 wp_register_script(
428 'jquery-numerator',
429 $this->get_js_assets_url( 'jquery-numerator', 'assets/lib/jquery-numerator/' ),
430 [
431 'jquery',
432 ],
433 '0.2.1',
434 true
435 );
436
437 wp_register_script(
438 'elementor-dialog',
439 $this->get_js_assets_url( 'dialog', 'assets/lib/dialog/' ),
440 [
441 'jquery-ui-position',
442 ],
443 '4.9.0',
444 true
445 );
446
447 wp_register_script(
448 'elementor-gallery',
449 $this->get_js_assets_url( 'e-gallery', 'assets/lib/e-gallery/js/' ),
450 [
451 'jquery',
452 ],
453 '1.2.0',
454 true
455 );
456
457 wp_register_script(
458 'share-link',
459 $this->get_js_assets_url( 'share-link', 'assets/lib/share-link/' ),
460 [
461 'jquery',
462 ],
463 ELEMENTOR_VERSION,
464 true
465 );
466
467 wp_register_script(
468 'elementor-frontend',
469 $this->get_js_assets_url( 'frontend' ),
470 [
471 'elementor-frontend-modules',
472 'elementor-waypoints',
473 'jquery-ui-position',
474 ],
475 ELEMENTOR_VERSION,
476 true
477 );
478
479 /**
480 * After frontend register scripts.
481 *
482 * Fires after Elementor frontend scripts are registered.
483 *
484 * @since 1.2.1
485 */
486 do_action( 'elementor/frontend/after_register_scripts' );
487 }
488
489 /**
490 * Registers styles.
491 *
492 * Registers all the frontend styles.
493 *
494 * Fired by `wp_enqueue_scripts` action.
495 *
496 * @since 1.2.0
497 * @access public
498 */
499 public function register_styles() {
500 /**
501 * Before frontend register styles.
502 *
503 * Fires before Elementor frontend styles are registered.
504 *
505 * @since 1.2.0
506 */
507 do_action( 'elementor/frontend/before_register_styles' );
508
509 wp_register_style(
510 'font-awesome',
511 $this->get_css_assets_url( 'font-awesome', 'assets/lib/font-awesome/css/' ),
512 [],
513 '4.7.0'
514 );
515
516 wp_register_style(
517 'elementor-icons',
518 $this->get_css_assets_url( 'elementor-icons', 'assets/lib/eicons/css/' ),
519 [],
520 Icons_Manager::ELEMENTOR_ICONS_VERSION
521 );
522
523 wp_register_style(
524 'flatpickr',
525 $this->get_css_assets_url( 'flatpickr', 'assets/lib/flatpickr/' ),
526 [],
527 '4.1.4'
528 );
529
530 wp_register_style(
531 'elementor-gallery',
532 $this->get_css_assets_url( 'e-gallery', 'assets/lib/e-gallery/css/' ),
533 [],
534 '1.2.0'
535 );
536
537 $min_suffix = Utils::is_script_debug() ? '' : '.min';
538
539 $direction_suffix = is_rtl() ? '-rtl' : '';
540
541 $frontend_base_file_name = $this->is_optimized_css_mode() ? 'frontend-lite' : 'frontend';
542
543 $frontend_file_name = $frontend_base_file_name . $direction_suffix . $min_suffix . '.css';
544
545 $has_custom_breakpoints = Plugin::$instance->breakpoints->has_custom_breakpoints();
546
547 wp_register_style(
548 'elementor-frontend',
549 $this->get_frontend_file_url( $frontend_file_name, $has_custom_breakpoints ),
550 [],
551 $has_custom_breakpoints ? null : ELEMENTOR_VERSION
552 );
553
554 wp_register_style(
555 'swiper',
556 $this->get_css_assets_url( 'swiper', $this->e_swiper_asset_path . 'css/' ),
557 [],
558 $this->e_swiper_version
559 );
560
561 /**
562 * After frontend register styles.
563 *
564 * Fires after Elementor frontend styles are registered.
565 *
566 * @since 1.2.0
567 */
568 do_action( 'elementor/frontend/after_register_styles' );
569 }
570
571 /**
572 * Enqueue scripts.
573 *
574 * Enqueue all the frontend scripts.
575 *
576 * @since 1.0.0
577 * @access public
578 */
579 public function enqueue_scripts() {
580 /**
581 * Before frontend enqueue scripts.
582 *
583 * Fires before Elementor frontend scripts are enqueued.
584 *
585 * @since 1.0.0
586 */
587 do_action( 'elementor/frontend/before_enqueue_scripts' );
588
589 wp_enqueue_script( 'elementor-frontend' );
590
591 $this->print_config();
592
593 $this->enqueue_conditional_assets();
594
595 /**
596 * After frontend enqueue scripts.
597 *
598 * Fires after Elementor frontend scripts are enqueued.
599 *
600 * @since 1.0.0
601 */
602 do_action( 'elementor/frontend/after_enqueue_scripts' );
603 }
604
605 /**
606 * Enqueue styles.
607 *
608 * Enqueue all the frontend styles.
609 *
610 * Fired by `wp_enqueue_scripts` action.
611 *
612 * @since 1.0.0
613 * @access public
614 */
615 public function enqueue_styles() {
616 static $is_enqueue_styles_already_triggered;
617
618 if ( ! $is_enqueue_styles_already_triggered ) {
619 $is_enqueue_styles_already_triggered = true;
620
621 /**
622 * Before frontend styles enqueued.
623 *
624 * Fires before Elementor frontend styles are enqueued.
625 *
626 * @since 1.0.0
627 */
628 do_action( 'elementor/frontend/before_enqueue_styles' );
629
630 // The e-icons are needed in preview mode for the editor icons (plus-icon for new section, folder-icon for the templates library etc.).
631 if ( ! Plugin::$instance->experiments->is_feature_active( 'e_font_icon_svg' ) || Plugin::$instance->preview->is_preview_mode() ) {
632 wp_enqueue_style( 'elementor-icons' );
633 }
634
635 wp_enqueue_style( 'elementor-frontend' );
636
637 wp_enqueue_style( 'swiper' );
638
639 /**
640 * After frontend styles enqueued.
641 *
642 * Fires after Elementor frontend styles are enqueued.
643 *
644 * @since 1.0.0
645 */
646 do_action( 'elementor/frontend/after_enqueue_styles' );
647
648 if ( ! Plugin::$instance->preview->is_preview_mode() ) {
649 $this->parse_global_css_code();
650
651 $post_id = get_the_ID();
652 // Check $post_id for virtual pages. check is singular because the $post_id is set to the first post on archive pages.
653 if ( $post_id && is_singular() ) {
654 $css_file = Post_CSS::create( get_the_ID() );
655 $css_file->enqueue();
656 }
657 }
658 }
659 }
660
661 /**
662 * Get Frontend File URL
663 *
664 * Returns the URL for the CSS file to be loaded in the front end. If requested via the second parameter, a custom
665 * file is generated based on a passed template file name. Otherwise, the URL for the default CSS file is returned.
666 *
667 * @since 3.4.5
668 *
669 * @access public
670 *
671 * @param string $frontend_file_name
672 * @param boolean $custom_file
673 *
674 * @return string frontend file URL
675 */
676 public function get_frontend_file_url( $frontend_file_name, $custom_file ) {
677 if ( $custom_file ) {
678 $frontend_file = $this->get_frontend_file( $frontend_file_name );
679
680 $frontend_file_url = $frontend_file->get_url();
681 } else {
682 $frontend_file_url = ELEMENTOR_ASSETS_URL . 'css/' . $frontend_file_name;
683 }
684
685 return $frontend_file_url;
686 }
687
688 /**
689 * Get Frontend File Path
690 *
691 * Returns the path for the CSS file to be loaded in the front end. If requested via the second parameter, a custom
692 * file is generated based on a passed template file name. Otherwise, the path for the default CSS file is returned.
693 *
694 * @since 3.5.0
695 * @access public
696 *
697 * @param string $frontend_file_name
698 * @param boolean $custom_file
699 *
700 * @return string frontend file path
701 */
702 public function get_frontend_file_path( $frontend_file_name, $custom_file ) {
703 if ( $custom_file ) {
704 $frontend_file = $this->get_frontend_file( $frontend_file_name );
705
706 $frontend_file_path = $frontend_file->get_path();
707 } else {
708 $frontend_file_path = ELEMENTOR_ASSETS_PATH . 'css/' . $frontend_file_name;
709 }
710
711 return $frontend_file_path;
712 }
713
714 /**
715 * Get Frontend File
716 *
717 * Returns a frontend file instance.
718 *
719 * @since 3.5.0
720 * @access public
721 *
722 * @param string $frontend_file_name
723 * @param string $file_prefix
724 * @param string $template_file_path
725 *
726 * @return FrontendFile
727 */
728 public function get_frontend_file( $frontend_file_name, $file_prefix = 'custom-', $template_file_path = '' ) {
729 static $cached_frontend_files = [];
730
731 $file_name = $file_prefix . $frontend_file_name;
732
733 if ( isset( $cached_frontend_files[ $file_name ] ) ) {
734 return $cached_frontend_files[ $file_name ];
735 }
736
737 if ( ! $template_file_path ) {
738 $template_file_path = Breakpoints_Manager::get_stylesheet_templates_path() . $frontend_file_name;
739 }
740
741 $frontend_file = new FrontendFile( $file_name, $template_file_path );
742
743 $time = $frontend_file->get_meta( 'time' );
744
745 if ( ! $time ) {
746 $frontend_file->update();
747 }
748
749 $cached_frontend_files[ $file_name ] = $frontend_file;
750
751 return $frontend_file;
752 }
753
754 /**
755 * Enqueue assets conditionally.
756 *
757 * Enqueue all assets that were pre-enabled.
758 *
759 * @since 3.3.0
760 * @access private
761 */
762 private function enqueue_conditional_assets() {
763 Plugin::$instance->assets_loader->enqueue_assets();
764 }
765
766 /**
767 * Elementor footer scripts and styles.
768 *
769 * Handle styles and scripts that are not printed in the header.
770 *
771 * Fired by `wp_footer` action.
772 *
773 * @since 1.0.11
774 * @access public
775 */
776 public function wp_footer() {
777 if ( ! $this->_has_elementor_in_page ) {
778 return;
779 }
780
781 $this->enqueue_styles();
782 $this->enqueue_scripts();
783
784 $this->print_fonts_links();
785 }
786
787 /**
788 * @return array|array[]
789 */
790 public function get_list_of_google_fonts_by_type(): array {
791 $google_fonts = [
792 'google' => [],
793 'early' => [],
794 ];
795
796 foreach ( $this->fonts_to_enqueue as $key => $font ) {
797 $font_type = Fonts::get_font_type( $font );
798
799 switch ( $font_type ) {
800 case Fonts::GOOGLE:
801 $google_fonts['google'][] = $font;
802 break;
803
804 case Fonts::EARLYACCESS:
805 $google_fonts['early'][] = $font;
806 break;
807
808 case false:
809 $this->maybe_enqueue_icon_font( $font );
810 break;
811 default:
812 /**
813 * Print font links.
814 *
815 * Fires when Elementor frontend fonts are printed on the HEAD tag.
816 *
817 * The dynamic portion of the hook name, `$font_type`, refers to the font type.
818 *
819 * @since 2.0.0
820 *
821 * @param string $font Font name.
822 */
823 do_action( "elementor/fonts/print_font_links/{$font_type}", $font );
824 }
825 }
826 $this->fonts_to_enqueue = [];
827
828 return $google_fonts;
829 }
830
831 /**
832 * Print fonts links.
833 *
834 * Enqueue all the frontend fonts by url.
835 *
836 * Fired by `wp_head` action.
837 *
838 * @since 1.9.4
839 * @access public
840 */
841 public function print_fonts_links() {
842 $google_fonts = $this->get_list_of_google_fonts_by_type();
843
844 $this->enqueue_google_fonts( $google_fonts );
845 $this->enqueue_icon_fonts();
846 }
847
848 private function maybe_enqueue_icon_font( $icon_font_type ) {
849 if ( ! Icons_Manager::is_migration_allowed() ) {
850 return;
851 }
852
853 $icons_types = Icons_Manager::get_icon_manager_tabs();
854 if ( ! isset( $icons_types[ $icon_font_type ] ) ) {
855 return;
856 }
857
858 $icon_type = $icons_types[ $icon_font_type ];
859 if ( isset( $icon_type['url'] ) ) {
860 $this->icon_fonts_to_enqueue[ $icon_font_type ] = [ $icon_type['url'] ];
861 }
862 }
863
864 private function enqueue_icon_fonts() {
865 if ( empty( $this->icon_fonts_to_enqueue ) || ! Icons_Manager::is_migration_allowed() ) {
866 return;
867 }
868
869 foreach ( $this->icon_fonts_to_enqueue as $icon_type => $css_url ) {
870 wp_enqueue_style( 'elementor-icons-' . $icon_type );
871 $this->enqueued_icon_fonts[] = $css_url;
872 }
873
874 //clear enqueued icons
875 $this->icon_fonts_to_enqueue = [];
876 }
877
878 /**
879 * @param array $fonts Stable google fonts ($google_fonts['google']).
880 * @return string
881 */
882 public function get_stable_google_fonts_url( array $fonts ): string {
883 foreach ( $fonts as &$font ) {
884 $font = str_replace( ' ', '+', $font ) . ':100,100italic,200,200italic,300,300italic,400,400italic,500,500italic,600,600italic,700,700italic,800,800italic,900,900italic';
885 }
886
887 // Defining a font-display type to google fonts.
888 $font_display_url_str = '&display=' . Fonts::get_font_display_setting();
889
890 $fonts_url = sprintf( 'https://fonts.googleapis.com/css?family=%1$s%2$s', implode( rawurlencode( '|' ), $fonts ), $font_display_url_str );
891
892 $subsets = [
893 'ru_RU' => 'cyrillic',
894 'bg_BG' => 'cyrillic',
895 'he_IL' => 'hebrew',
896 'el' => 'greek',
897 'vi' => 'vietnamese',
898 'uk' => 'cyrillic',
899 'cs_CZ' => 'latin-ext',
900 'ro_RO' => 'latin-ext',
901 'pl_PL' => 'latin-ext',
902 'hr_HR' => 'latin-ext',
903 'hu_HU' => 'latin-ext',
904 'sk_SK' => 'latin-ext',
905 'tr_TR' => 'latin-ext',
906 'lt_LT' => 'latin-ext',
907 ];
908
909 /**
910 * Google font subsets.
911 *
912 * Filters the list of Google font subsets from which locale will be enqueued in frontend.
913 *
914 * @since 1.0.0
915 *
916 * @param array $subsets A list of font subsets.
917 */
918 $subsets = apply_filters( 'elementor/frontend/google_font_subsets', $subsets );
919
920 $locale = get_locale();
921
922 if ( isset( $subsets[ $locale ] ) ) {
923 $fonts_url .= '&subset=' . $subsets[ $locale ];
924 }
925
926 return $fonts_url;
927 }
928
929 /**
930 * @param array $fonts Early Access google fonts ($google_fonts['early']).
931 * @return array
932 */
933 public function get_early_access_google_font_urls( array $fonts ): array {
934 $font_urls = [];
935
936 foreach ( $fonts as $font ) {
937 $font_urls[] = sprintf( 'https://fonts.googleapis.com/earlyaccess/%s.css', strtolower( str_replace( ' ', '', $font ) ) );
938 }
939
940 return $font_urls;
941 }
942
943 /**
944 * Print Google fonts.
945 *
946 * Enqueue all the frontend Google fonts.
947 *
948 * Fired by `wp_head` action.
949 *
950 * @since 1.0.0
951 * @access private
952 *
953 * @param array $google_fonts Optional. Google fonts to print in the frontend.
954 * Default is an empty array.
955 */
956 private function enqueue_google_fonts( $google_fonts = [] ) {
957 $print_google_fonts = Fonts::is_google_fonts_enabled();
958
959 /**
960 * Print frontend google fonts.
961 *
962 * Filters whether to enqueue Google fonts in the frontend.
963 *
964 * @since 1.0.0
965 *
966 * @param bool $print_google_fonts Whether to enqueue Google fonts. Default is true.
967 */
968 $print_google_fonts = apply_filters( 'elementor/frontend/print_google_fonts', $print_google_fonts );
969
970 if ( ! $print_google_fonts ) {
971 return;
972 }
973
974 // Print used fonts
975 if ( ! empty( $google_fonts['google'] ) ) {
976 $this->google_fonts_index++;
977
978 $fonts_url = $this->get_stable_google_fonts_url( $google_fonts['google'] );
979
980 wp_enqueue_style( 'google-fonts-' . $this->google_fonts_index, $fonts_url ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
981 }
982
983 if ( ! empty( $google_fonts['early'] ) ) {
984 $early_access_font_urls = $this->get_early_access_google_font_urls( $google_fonts['early'] );
985
986 foreach ( $early_access_font_urls as $ea_font_url ) {
987 $this->google_fonts_index++;
988
989 //printf( '<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/earlyaccess/%s.css">', strtolower( str_replace( ' ', '', $current_font ) ) );
990
991 wp_enqueue_style( 'google-earlyaccess-' . $this->google_fonts_index, $ea_font_url ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
992 }
993 }
994
995 }
996
997 /**
998 * Enqueue fonts.
999 *
1000 * Enqueue all the frontend fonts.
1001 *
1002 * @since 1.2.0
1003 * @access public
1004 *
1005 * @param array $font Fonts to enqueue in the frontend.
1006 */
1007 public function enqueue_font( $font ) {
1008 if ( in_array( $font, $this->registered_fonts ) ) {
1009 return;
1010 }
1011
1012 $this->fonts_to_enqueue[] = $font;
1013 $this->registered_fonts[] = $font;
1014 }
1015
1016 /**
1017 * Parse global CSS.
1018 *
1019 * Enqueue the global CSS file.
1020 *
1021 * @since 1.2.0
1022 * @access protected
1023 */
1024 protected function parse_global_css_code() {
1025 $scheme_css_file = Global_CSS::create( 'global.css' );
1026
1027 $scheme_css_file->enqueue();
1028 }
1029
1030 /**
1031 * Apply builder in content.
1032 *
1033 * Used to apply the Elementor page editor on the post content.
1034 *
1035 * @since 1.0.0
1036 * @access public
1037 *
1038 * @param string $content The post content.
1039 *
1040 * @return string The post content.
1041 */
1042 public function apply_builder_in_content( $content ) {
1043 $this->restore_content_filters();
1044
1045 if ( Plugin::$instance->preview->is_preview_mode() || $this->_is_excerpt ) {
1046 return $content;
1047 }
1048
1049 // Remove the filter itself in order to allow other `the_content` in the elements
1050 $this->remove_content_filter();
1051
1052 $post_id = get_the_ID();
1053 $builder_content = $this->get_builder_content( $post_id );
1054
1055 if ( ! empty( $builder_content ) ) {
1056 $content = $builder_content;
1057 $this->remove_content_filters();
1058 }
1059
1060 // Add the filter again for other `the_content` calls
1061 $this->add_content_filter();
1062
1063 return $content;
1064 }
1065
1066 /**
1067 * Retrieve builder content.
1068 *
1069 * Used to render and return the post content with all the Elementor elements.
1070 *
1071 * Note that this method is an internal method, please use `get_builder_content_for_display()`.
1072 *
1073 * @since 1.0.0
1074 * @access public
1075 *
1076 * @param int $post_id The post ID.
1077 * @param bool $with_css Optional. Whether to retrieve the content with CSS
1078 * or not. Default is false.
1079 *
1080 * @return string The post content.
1081 */
1082 public function get_builder_content( $post_id, $with_css = false ) {
1083 if ( post_password_required( $post_id ) ) {
1084 return '';
1085 }
1086
1087 $document = Plugin::$instance->documents->get_doc_for_frontend( $post_id );
1088
1089 if ( ! $document || ! $document->is_built_with_elementor() ) {
1090 return '';
1091 }
1092
1093 // Change the current post, so widgets can use `documents->get_current`.
1094 Plugin::$instance->documents->switch_to_document( $document );
1095
1096 $data = $document->get_elements_data();
1097
1098 /**
1099 * Frontend builder content data.
1100 *
1101 * Filters the builder content in the frontend.
1102 *
1103 * @since 1.0.0
1104 *
1105 * @param array $data The builder content.
1106 * @param int $post_id The post ID.
1107 */
1108 $data = apply_filters( 'elementor/frontend/builder_content_data', $data, $post_id );
1109
1110 do_action( 'elementor/frontend/before_get_builder_content', $document, $this->_is_excerpt );
1111
1112 if ( empty( $data ) ) {
1113 Plugin::$instance->documents->restore_document();
1114
1115 return '';
1116 }
1117
1118 if ( ! $this->_is_excerpt ) {
1119 if ( $document->is_autosave() ) {
1120 $css_file = Post_Preview::create( $document->get_post()->ID );
1121 } else {
1122 $css_file = Post_CSS::create( $post_id );
1123 }
1124
1125 /**
1126 * Builder Content - Before Enqueue CSS File
1127 *
1128 * Allows intervening with a document's CSS file before it is enqueued.
1129 *
1130 * @param $css_file Post_CSS|Post_Preview
1131 */
1132 $css_file = apply_filters( 'elementor/frontend/builder_content/before_enqueue_css_file', $css_file );
1133
1134 $css_file->enqueue();
1135 }
1136
1137 ob_start();
1138
1139 // Handle JS and Customizer requests, with CSS inline.
1140 if ( is_customize_preview() || wp_doing_ajax() ) {
1141 $with_css = true;
1142 }
1143
1144 /**
1145 * Builder Content - With CSS
1146 *
1147 * Allows overriding the `$with_css` parameter which is a factor in determining whether to print the document's
1148 * CSS and font links inline in a `style` tag above the document's markup.
1149 *
1150 * @param $with_css boolean
1151 */
1152 $with_css = apply_filters( 'elementor/frontend/builder_content/before_print_css', $with_css );
1153
1154 if ( ! empty( $css_file ) && $with_css ) {
1155 $css_file->print_css();
1156 }
1157
1158 $document->print_elements_with_wrapper( $data );
1159
1160 $content = ob_get_clean();
1161
1162 $content = $this->process_more_tag( $content );
1163
1164 /**
1165 * Frontend content.
1166 *
1167 * Filters the content in the frontend.
1168 *
1169 * @since 1.0.0
1170 *
1171 * @param string $content The content.
1172 */
1173 $content = apply_filters( 'elementor/frontend/the_content', $content );
1174
1175 if ( ! empty( $content ) ) {
1176 $this->_has_elementor_in_page = true;
1177 }
1178
1179 Plugin::$instance->documents->restore_document();
1180
1181 // BC
1182 // TODO: use Deprecation::do_deprecated_action() in 3.1.0
1183 do_action( 'elementor/frontend/get_builder_content', $document, $this->_is_excerpt, $with_css );
1184
1185 return $content;
1186 }
1187
1188 /**
1189 * Retrieve builder content for display.
1190 *
1191 * Used to render and return the post content with all the Elementor elements.
1192 *
1193 * @since 1.0.0
1194 * @access public
1195 *
1196 * @param int $post_id The post ID.
1197 *
1198 * @param bool $with_css Optional. Whether to retrieve the content with CSS
1199 * or not. Default is false.
1200 *
1201 * @return string The post content.
1202 */
1203 public function get_builder_content_for_display( $post_id, $with_css = false ) {
1204 if ( ! get_post( $post_id ) ) {
1205 return '';
1206 }
1207
1208 $editor = Plugin::$instance->editor;
1209
1210 // Avoid recursion
1211 if ( get_the_ID() === (int) $post_id ) {
1212 $content = '';
1213 if ( $editor->is_edit_mode() ) {
1214 $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>';
1215 }
1216
1217 return $content;
1218 }
1219
1220 // 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
1221 $is_edit_mode = $editor->is_edit_mode();
1222 $editor->set_edit_mode( false );
1223
1224 $with_css = $with_css ? true : $is_edit_mode;
1225
1226 $content = $this->get_builder_content( $post_id, $with_css );
1227
1228 // Restore edit mode state
1229 Plugin::$instance->editor->set_edit_mode( $is_edit_mode );
1230
1231 return $content;
1232 }
1233
1234 /**
1235 * Start excerpt flag.
1236 *
1237 * Flags when `the_excerpt` is called. Used to avoid enqueueing CSS in the excerpt.
1238 *
1239 * @since 1.4.3
1240 * @access public
1241 *
1242 * @param string $excerpt The post excerpt.
1243 *
1244 * @return string The post excerpt.
1245 */
1246 public function start_excerpt_flag( $excerpt ) {
1247 $this->_is_excerpt = true;
1248 return $excerpt;
1249 }
1250
1251 /**
1252 * End excerpt flag.
1253 *
1254 * Flags when `the_excerpt` call ended.
1255 *
1256 * @since 1.4.3
1257 * @access public
1258 *
1259 * @param string $excerpt The post excerpt.
1260 *
1261 * @return string The post excerpt.
1262 */
1263 public function end_excerpt_flag( $excerpt ) {
1264 $this->_is_excerpt = false;
1265 return $excerpt;
1266 }
1267
1268 /**
1269 * Remove content filters.
1270 *
1271 * Remove WordPress default filters that conflicted with Elementor.
1272 *
1273 * @since 1.5.0
1274 * @access public
1275 */
1276 public function remove_content_filters() {
1277 $filters = [
1278 'wpautop',
1279 'shortcode_unautop',
1280 'wptexturize',
1281 ];
1282
1283 foreach ( $filters as $filter ) {
1284 // Check if another plugin/theme do not already removed the filter.
1285 if ( has_filter( 'the_content', $filter ) ) {
1286 remove_filter( 'the_content', $filter );
1287 $this->content_removed_filters[] = $filter;
1288 }
1289 }
1290 }
1291
1292 /**
1293 * Has Elementor In Page
1294 *
1295 * Determine whether the current page is using Elementor.
1296 *
1297 * @since 2.0.9
1298 *
1299 * @access public
1300 * @return bool
1301 */
1302 public function has_elementor_in_page() {
1303 return $this->_has_elementor_in_page;
1304 }
1305
1306 public function create_action_hash( $action, array $settings = [] ) {
1307 return '#' . rawurlencode( sprintf( 'elementor-action:action=%1$s&settings=%2$s', $action, base64_encode( wp_json_encode( $settings ) ) ) );
1308 }
1309
1310 /**
1311 * Is the current render mode is static.
1312 *
1313 * @return bool
1314 */
1315 public function is_static_render_mode() {
1316 // The render mode manager is exists only in frontend,
1317 // so by default if it is not exist the method will return false.
1318 if ( ! $this->render_mode_manager ) {
1319 return false;
1320 }
1321
1322 return $this->render_mode_manager->get_current()->is_static();
1323 }
1324
1325 /**
1326 * Get Init Settings
1327 *
1328 * Used to define the default/initial settings of the object. Inheriting classes may implement this method to define
1329 * their own default/initial settings.
1330 *
1331 * @since 2.3.0
1332 *
1333 * @access protected
1334 * @return array
1335 */
1336 protected function get_init_settings() {
1337 $is_preview_mode = Plugin::$instance->preview->is_preview_mode( Plugin::$instance->preview->get_post_id() );
1338
1339 $active_experimental_features = Plugin::$instance->experiments->get_active_features();
1340
1341 $active_experimental_features = array_fill_keys( array_keys( $active_experimental_features ), true );
1342
1343 $assets_url = ELEMENTOR_ASSETS_URL;
1344
1345 /**
1346 * Frontend assets URL
1347 *
1348 * Filters Elementor frontend assets URL.
1349 *
1350 * @since 2.3.0
1351 *
1352 * @param string $assets_url The frontend assets URL. Default is ELEMENTOR_ASSETS_URL.
1353 */
1354 $assets_url = apply_filters( 'elementor/frontend/assets_url', $assets_url );
1355
1356 $settings = [
1357 'environmentMode' => [
1358 'edit' => $is_preview_mode,
1359 'wpPreview' => is_preview(),
1360 'isScriptDebug' => Utils::is_script_debug(),
1361 ],
1362 'i18n' => [
1363 'shareOnFacebook' => esc_html__( 'Share on Facebook', 'elementor' ),
1364 'shareOnTwitter' => esc_html__( 'Share on Twitter', 'elementor' ),
1365 'pinIt' => esc_html__( 'Pin it', 'elementor' ),
1366 'download' => esc_html__( 'Download', 'elementor' ),
1367 'downloadImage' => esc_html__( 'Download image', 'elementor' ),
1368 'fullscreen' => esc_html__( 'Fullscreen', 'elementor' ),
1369 'zoom' => esc_html__( 'Zoom', 'elementor' ),
1370 'share' => esc_html__( 'Share', 'elementor' ),
1371 'playVideo' => esc_html__( 'Play Video', 'elementor' ),
1372 'previous' => esc_html__( 'Previous', 'elementor' ),
1373 'next' => esc_html__( 'Next', 'elementor' ),
1374 'close' => esc_html__( 'Close', 'elementor' ),
1375 'a11yCarouselWrapperAriaLabel' => __( 'Carousel | Horizontal scrolling: Arrow Left & Right', 'elementor' ),
1376 'a11yCarouselPrevSlideMessage' => __( 'Previous slide', 'elementor' ),
1377 'a11yCarouselNextSlideMessage' => __( 'Next slide', 'elementor' ),
1378 'a11yCarouselFirstSlideMessage' => __( 'This is the first slide', 'elementor' ),
1379 'a11yCarouselLastSlideMessage' => __( 'This is the last slide', 'elementor' ),
1380 'a11yCarouselPaginationBulletMessage' => __( 'Go to slide', 'elementor' ),
1381 ],
1382 'is_rtl' => is_rtl(),
1383 // 'breakpoints' object is kept for BC.
1384 'breakpoints' => Responsive::get_breakpoints(),
1385 // 'responsive' contains the custom breakpoints config introduced in Elementor v3.2.0
1386 'responsive' => [
1387 'breakpoints' => Plugin::$instance->breakpoints->get_breakpoints_config(),
1388 ],
1389 'version' => ELEMENTOR_VERSION,
1390 'is_static' => $this->is_static_render_mode(),
1391 'experimentalFeatures' => $active_experimental_features,
1392 'urls' => [
1393 'assets' => $assets_url,
1394 'ajaxurl' => admin_url( 'admin-ajax.php' ),
1395 ],
1396 'nonces' => [
1397 'floatingButtonsClickTracking' => wp_create_nonce( Module::CLICK_TRACKING_NONCE ),
1398 ],
1399 'swiperClass' => Plugin::$instance->experiments->is_feature_active( 'e_swiper_latest' ) ? 'swiper' : 'swiper-container',
1400 ];
1401
1402 $settings['settings'] = SettingsManager::get_settings_frontend_config();
1403
1404 $kit = Plugin::$instance->kits_manager->get_active_kit_for_frontend();
1405 $settings['kit'] = $kit->get_frontend_settings();
1406
1407 if ( is_singular() ) {
1408 $post = get_post();
1409
1410 $title = Utils::urlencode_html_entities( wp_get_document_title() );
1411
1412 // Try to use the 'large' WP image size because the Pinterest share API
1413 // has problems accepting shares with large images sometimes, and the WP 'large' thumbnail is
1414 // the largest default WP image size that will probably not be changed in most sites
1415 $featured_image_url = get_the_post_thumbnail_url( null, 'large' );
1416
1417 // If the large size was nullified, use the full size which cannot be nullified/deleted
1418 if ( ! $featured_image_url ) {
1419 $featured_image_url = get_the_post_thumbnail_url( null, 'full' );
1420 }
1421
1422 $settings['post'] = [
1423 'id' => $post->ID,
1424 'title' => $title,
1425 'excerpt' => $post->post_excerpt,
1426 'featuredImage' => $featured_image_url,
1427 ];
1428 } else {
1429 $settings['post'] = [
1430 'id' => 0,
1431 'title' => wp_get_document_title(),
1432 'excerpt' => get_the_archive_description(),
1433 ];
1434 }
1435
1436 $empty_object = (object) [];
1437
1438 if ( $is_preview_mode ) {
1439 $settings['elements'] = [
1440 'data' => $empty_object,
1441 'editSettings' => $empty_object,
1442 'keys' => $empty_object,
1443 ];
1444 }
1445
1446 if ( is_user_logged_in() ) {
1447 $user = wp_get_current_user();
1448
1449 if ( ! empty( $user->roles ) ) {
1450 $settings['user'] = [
1451 'roles' => $user->roles,
1452 ];
1453 }
1454 }
1455
1456 return $settings;
1457 }
1458
1459 /**
1460 * Restore content filters.
1461 *
1462 * Restore removed WordPress filters that conflicted with Elementor.
1463 *
1464 * @since 1.5.0
1465 * @access public
1466 */
1467 public function restore_content_filters() {
1468 foreach ( $this->content_removed_filters as $filter ) {
1469 add_filter( 'the_content', $filter );
1470 }
1471
1472 $this->content_removed_filters = [];
1473 }
1474
1475 /**
1476 * Process More Tag
1477 *
1478 * Respect the native WP (<!--more-->) tag
1479 *
1480 * @access private
1481 * @since 2.0.4
1482 *
1483 * @param $content
1484 *
1485 * @return string
1486 */
1487 private function process_more_tag( $content ) {
1488 $post = get_post();
1489 $content = str_replace( '&lt;!--more--&gt;', '<!--more-->', $content );
1490 $parts = get_extended( $content );
1491 if ( empty( $parts['extended'] ) ) {
1492 return $content;
1493 }
1494
1495 if ( is_singular() ) {
1496 return $parts['main'] . '<div id="more-' . $post->ID . '"></div>' . $parts['extended'];
1497 }
1498
1499 if ( empty( $parts['more_text'] ) ) {
1500 $parts['more_text'] = esc_html__( '(more&hellip;)', 'elementor' );
1501 }
1502
1503 $more_link_text = sprintf(
1504 '<span aria-label="%1$s">%2$s</span>',
1505 sprintf(
1506 /* translators: %s: Current post name. */
1507 __( 'Continue reading %s', 'elementor' ),
1508 the_title_attribute( [
1509 'echo' => false,
1510 ] )
1511 ),
1512 $parts['more_text']
1513 );
1514
1515 $more_link = sprintf( ' <a href="%s#more-%s" class="more-link elementor-more-link">%s</a>', get_permalink(), $post->ID, $more_link_text );
1516
1517 /**
1518 * The content "more" link.
1519 *
1520 * Filters the "more" link displayed after the content.
1521 *
1522 * This hook can be used either to change the link syntax or to change the
1523 * text inside the link.
1524 *
1525 * @since 2.0.4
1526 *
1527 * @param string $more_link The more link.
1528 * @param string $more_link_text The text inside the more link.
1529 */
1530 $more_link = apply_filters( 'the_content_more_link', $more_link, $more_link_text );
1531
1532 return force_balance_tags( $parts['main'] ) . $more_link;
1533 }
1534
1535 private function is_optimized_css_mode() {
1536 $is_optimized_css_loading = Plugin::$instance->experiments->is_feature_active( 'e_optimized_css_loading' );
1537
1538 return ! Utils::is_script_debug() && $is_optimized_css_loading && ! Plugin::$instance->preview->is_preview_mode();
1539 }
1540 }
1541