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

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