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