frontend.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038
  1. <?php
  2. namespace Elementor;
  3. use Elementor\Core\Base\Document;
  4. use Elementor\Core\Responsive\Files\Frontend as FrontendFile;
  5. use Elementor\Core\Files\CSS\Global_CSS;
  6. use Elementor\Core\Files\CSS\Post as Post_CSS;
  7. use Elementor\Core\Files\CSS\Post_Preview;
  8. use Elementor\Core\Responsive\Responsive;
  9. use Elementor\Core\Settings\Manager as SettingsManager;
  10. if ( ! defined( 'ABSPATH' ) ) {
  11. exit; // Exit if accessed directly.
  12. }
  13. /**
  14. * Elementor frontend.
  15. *
  16. * Elementor frontend handler class is responsible for initializing Elementor in
  17. * the frontend.
  18. *
  19. * @since 1.0.0
  20. */
  21. class Frontend {
  22. /**
  23. * The priority of the content filter.
  24. */
  25. const THE_CONTENT_FILTER_PRIORITY = 9;
  26. /**
  27. * Post ID.
  28. *
  29. * Holds the ID of the current post.
  30. *
  31. * @access private
  32. *
  33. * @var int Post ID.
  34. */
  35. private $post_id;
  36. /**
  37. * Fonts to enqueue
  38. *
  39. * Holds the list of fonts that are being used in the current page.
  40. *
  41. * @since 1.9.4
  42. * @access private
  43. *
  44. * @var array Used fonts. Default is an empty array.
  45. */
  46. private $fonts_to_enqueue = [];
  47. /**
  48. * Registered fonts.
  49. *
  50. * Holds the list of enqueued fonts in the current page.
  51. *
  52. * @since 1.0.0
  53. * @access private
  54. *
  55. * @var array Registered fonts. Default is an empty array.
  56. */
  57. private $registered_fonts = [];
  58. /**
  59. * Whether the page is using Elementor.
  60. *
  61. * Used to determine whether the current page is using Elementor.
  62. *
  63. * @since 1.0.0
  64. * @access private
  65. *
  66. * @var bool Whether Elementor is being used. Default is false.
  67. */
  68. private $_has_elementor_in_page = false;
  69. /**
  70. * Whether the excerpt is being called.
  71. *
  72. * Used to determine whether the call to `the_content()` came from `get_the_excerpt()`.
  73. *
  74. * @since 1.0.0
  75. * @access private
  76. *
  77. * @var bool Whether the excerpt is being used. Default is false.
  78. */
  79. private $_is_excerpt = false;
  80. /**
  81. * Filters removed from the content.
  82. *
  83. * Hold the list of filters removed from `the_content()`. Used to hold the filters that
  84. * conflicted with Elementor while Elementor process the content.
  85. *
  86. * @since 1.0.0
  87. * @access private
  88. *
  89. * @var array Filters removed from the content. Default is an empty array.
  90. */
  91. private $content_removed_filters = [];
  92. /**
  93. * @var Document[]
  94. */
  95. private $admin_bar_edit_documents = [];
  96. /**
  97. * @var string[]
  98. */
  99. private $body_classes = [
  100. 'elementor-default',
  101. ];
  102. /**
  103. * Init.
  104. *
  105. * Initialize Elementor front end. Hooks the needed actions to run Elementor
  106. * in the front end, including script and style registration.
  107. *
  108. * Fired by `template_redirect` action.
  109. *
  110. * @since 1.0.0
  111. * @access public
  112. */
  113. public function init() {
  114. if ( Plugin::$instance->editor->is_edit_mode() ) {
  115. return;
  116. }
  117. add_filter( 'body_class', [ $this, 'body_class' ] );
  118. if ( Plugin::$instance->preview->is_preview_mode() ) {
  119. return;
  120. }
  121. $this->post_id = get_the_ID();
  122. if ( is_singular() && Plugin::$instance->db->is_built_with_elementor( $this->post_id ) ) {
  123. add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_styles' ] );
  124. }
  125. // Priority 7 to allow google fonts in header template to load in <head> tag
  126. add_action( 'wp_head', [ $this, 'print_fonts_links' ], 7 );
  127. add_action( 'wp_footer', [ $this, 'wp_footer' ] );
  128. // Add Edit with the Elementor in Admin Bar.
  129. add_action( 'admin_bar_menu', [ $this, 'add_menu_in_admin_bar' ], 200 );
  130. }
  131. /**
  132. * @param string|array $class
  133. */
  134. public function add_body_class( $class ) {
  135. if ( is_array( $class ) ) {
  136. $this->body_classes = array_merge( $this->body_classes, $class );
  137. } else {
  138. $this->body_classes[] = $class;
  139. }
  140. }
  141. /**
  142. * Body tag classes.
  143. *
  144. * Add new elementor classes to the body tag.
  145. *
  146. * Fired by `body_class` filter.
  147. *
  148. * @since 1.0.0
  149. * @access public
  150. *
  151. * @param array $classes Optional. One or more classes to add to the body tag class list.
  152. * Default is an empty array.
  153. *
  154. * @return array Body tag classes.
  155. */
  156. public function body_class( $classes = [] ) {
  157. $classes = array_merge( $classes, $this->body_classes );
  158. $id = get_the_ID();
  159. if ( is_singular() && Plugin::$instance->db->is_built_with_elementor( $id ) ) {
  160. $classes[] = 'elementor-page elementor-page-' . $id;
  161. }
  162. return $classes;
  163. }
  164. /**
  165. * Add content filter.
  166. *
  167. * Remove plain content and render the content generated by Elementor.
  168. *
  169. * @since 1.8.0
  170. * @access public
  171. */
  172. public function add_content_filter() {
  173. add_filter( 'the_content', [ $this, 'apply_builder_in_content' ], self::THE_CONTENT_FILTER_PRIORITY );
  174. }
  175. /**
  176. * Remove content filter.
  177. *
  178. * When the Elementor generated content rendered, we remove the filter to prevent multiple
  179. * accuracies. This way we make sure Elementor renders the content only once.
  180. *
  181. * @since 1.8.0
  182. * @access public
  183. */
  184. public function remove_content_filter() {
  185. remove_filter( 'the_content', [ $this, 'apply_builder_in_content' ], self::THE_CONTENT_FILTER_PRIORITY );
  186. }
  187. /**
  188. * Registers scripts.
  189. *
  190. * Registers all the frontend scripts.
  191. *
  192. * Fired by `wp_enqueue_scripts` action.
  193. *
  194. * @since 1.2.1
  195. * @access public
  196. */
  197. public function register_scripts() {
  198. /**
  199. * Before frontend register scripts.
  200. *
  201. * Fires before Elementor frontend scripts are registered.
  202. *
  203. * @since 1.2.1
  204. */
  205. do_action( 'elementor/frontend/before_register_scripts' );
  206. $suffix = Utils::is_script_debug() ? '' : '.min';
  207. wp_register_script(
  208. 'elementor-waypoints',
  209. ELEMENTOR_ASSETS_URL . 'lib/waypoints/waypoints' . $suffix . '.js',
  210. [
  211. 'jquery',
  212. ],
  213. '4.0.2',
  214. true
  215. );
  216. wp_register_script(
  217. 'flatpickr',
  218. ELEMENTOR_ASSETS_URL . 'lib/flatpickr/flatpickr' . $suffix . '.js',
  219. [
  220. 'jquery',
  221. ],
  222. '4.1.4',
  223. true
  224. );
  225. wp_register_script(
  226. 'imagesloaded',
  227. ELEMENTOR_ASSETS_URL . 'lib/imagesloaded/imagesloaded' . $suffix . '.js',
  228. [
  229. 'jquery',
  230. ],
  231. '4.1.0',
  232. true
  233. );
  234. wp_register_script(
  235. 'jquery-numerator',
  236. ELEMENTOR_ASSETS_URL . 'lib/jquery-numerator/jquery-numerator' . $suffix . '.js',
  237. [
  238. 'jquery',
  239. ],
  240. '0.2.1',
  241. true
  242. );
  243. wp_register_script(
  244. 'jquery-swiper',
  245. ELEMENTOR_ASSETS_URL . 'lib/swiper/swiper.jquery' . $suffix . '.js',
  246. [
  247. 'jquery',
  248. ],
  249. '4.4.3',
  250. true
  251. );
  252. wp_register_script(
  253. 'jquery-slick',
  254. ELEMENTOR_ASSETS_URL . 'lib/slick/slick' . $suffix . '.js',
  255. [
  256. 'jquery',
  257. ],
  258. '1.8.1',
  259. true
  260. );
  261. wp_register_script(
  262. 'elementor-dialog',
  263. ELEMENTOR_ASSETS_URL . 'lib/dialog/dialog' . $suffix . '.js',
  264. [
  265. 'jquery-ui-position',
  266. ],
  267. '4.5.0',
  268. true
  269. );
  270. wp_register_script(
  271. 'elementor-frontend',
  272. ELEMENTOR_ASSETS_URL . 'js/frontend' . $suffix . '.js',
  273. [
  274. 'elementor-dialog',
  275. 'elementor-waypoints',
  276. 'jquery-swiper',
  277. ],
  278. ELEMENTOR_VERSION,
  279. true
  280. );
  281. /**
  282. * After frontend register scripts.
  283. *
  284. * Fires after Elementor frontend scripts are registered.
  285. *
  286. * @since 1.2.1
  287. */
  288. do_action( 'elementor/frontend/after_register_scripts' );
  289. }
  290. /**
  291. * Registers styles.
  292. *
  293. * Registers all the frontend styles.
  294. *
  295. * Fired by `wp_enqueue_scripts` action.
  296. *
  297. * @since 1.2.0
  298. * @access public
  299. */
  300. public function register_styles() {
  301. /**
  302. * Before frontend register styles.
  303. *
  304. * Fires before Elementor frontend styles are registered.
  305. *
  306. * @since 1.2.0
  307. */
  308. do_action( 'elementor/frontend/before_register_styles' );
  309. $suffix = Utils::is_script_debug() ? '' : '.min';
  310. $direction_suffix = is_rtl() ? '-rtl' : '';
  311. wp_register_style(
  312. 'elementor-icons',
  313. ELEMENTOR_ASSETS_URL . 'lib/eicons/css/elementor-icons' . $suffix . '.css',
  314. [],
  315. '3.8.0'
  316. );
  317. wp_register_style(
  318. 'font-awesome',
  319. ELEMENTOR_ASSETS_URL . 'lib/font-awesome/css/font-awesome' . $suffix . '.css',
  320. [],
  321. '4.7.0'
  322. );
  323. wp_register_style(
  324. 'elementor-animations',
  325. ELEMENTOR_ASSETS_URL . 'lib/animations/animations.min.css',
  326. [],
  327. ELEMENTOR_VERSION
  328. );
  329. wp_register_style(
  330. 'flatpickr',
  331. ELEMENTOR_ASSETS_URL . 'lib/flatpickr/flatpickr' . $suffix . '.css',
  332. [],
  333. '4.1.4'
  334. );
  335. $frontend_file_name = 'frontend' . $direction_suffix . $suffix . '.css';
  336. $has_custom_file = Responsive::has_custom_breakpoints();
  337. if ( $has_custom_file ) {
  338. $frontend_file = new FrontendFile( 'custom-' . $frontend_file_name, Responsive::get_stylesheet_templates_path() . $frontend_file_name );
  339. $time = $frontend_file->get_meta( 'time' );
  340. if ( ! $time ) {
  341. $frontend_file->update();
  342. }
  343. $frontend_file_url = $frontend_file->get_url();
  344. } else {
  345. $frontend_file_url = ELEMENTOR_ASSETS_URL . 'css/' . $frontend_file_name;
  346. }
  347. wp_register_style(
  348. 'elementor-frontend',
  349. $frontend_file_url,
  350. [],
  351. $has_custom_file ? null : ELEMENTOR_VERSION
  352. );
  353. /**
  354. * After frontend register styles.
  355. *
  356. * Fires after Elementor frontend styles are registered.
  357. *
  358. * @since 1.2.0
  359. */
  360. do_action( 'elementor/frontend/after_register_styles' );
  361. }
  362. /**
  363. * Enqueue scripts.
  364. *
  365. * Enqueue all the frontend scripts.
  366. *
  367. * @since 1.0.0
  368. * @access public
  369. */
  370. public function enqueue_scripts() {
  371. /**
  372. * Before frontend enqueue scripts.
  373. *
  374. * Fires before Elementor frontend scripts are enqueued.
  375. *
  376. * @since 1.0.0
  377. */
  378. do_action( 'elementor/frontend/before_enqueue_scripts' );
  379. wp_enqueue_script( 'elementor-frontend' );
  380. $is_preview_mode = Plugin::$instance->preview->is_preview_mode( Plugin::$instance->preview->get_post_id() );
  381. $elementor_frontend_config = [
  382. 'isEditMode' => $is_preview_mode,
  383. 'is_rtl' => is_rtl(),
  384. 'breakpoints' => Responsive::get_breakpoints(),
  385. 'version' => ELEMENTOR_VERSION,
  386. 'urls' => [
  387. 'assets' => ELEMENTOR_ASSETS_URL,
  388. ],
  389. ];
  390. $elementor_frontend_config['settings'] = SettingsManager::get_settings_frontend_config();
  391. if ( is_singular() ) {
  392. $post = get_post();
  393. $elementor_frontend_config['post'] = [
  394. 'id' => $post->ID,
  395. 'title' => $post->post_title,
  396. 'excerpt' => $post->post_excerpt,
  397. ];
  398. } else {
  399. $elementor_frontend_config['post'] = [
  400. 'id' => 0,
  401. 'title' => wp_get_document_title(),
  402. 'excerpt' => '',
  403. ];
  404. }
  405. if ( $is_preview_mode ) {
  406. $elements_manager = Plugin::$instance->elements_manager;
  407. $elements_frontend_keys = [
  408. 'section' => $elements_manager->get_element_types( 'section' )->get_frontend_settings_keys(),
  409. 'column' => $elements_manager->get_element_types( 'column' )->get_frontend_settings_keys(),
  410. ];
  411. $elements_frontend_keys += Plugin::$instance->widgets_manager->get_widgets_frontend_settings_keys();
  412. $elementor_frontend_config['elements'] = [
  413. 'data' => (object) [],
  414. 'editSettings' => (object) [],
  415. 'keys' => $elements_frontend_keys,
  416. ];
  417. }
  418. wp_localize_script( 'elementor-frontend', 'elementorFrontendConfig', $elementor_frontend_config );
  419. /**
  420. * After frontend enqueue scripts.
  421. *
  422. * Fires after Elementor frontend scripts are enqueued.
  423. *
  424. * @since 1.0.0
  425. */
  426. do_action( 'elementor/frontend/after_enqueue_scripts' );
  427. }
  428. /**
  429. * Enqueue styles.
  430. *
  431. * Enqueue all the frontend styles.
  432. *
  433. * Fired by `wp_enqueue_scripts` action.
  434. *
  435. * @since 1.0.0
  436. * @access public
  437. */
  438. public function enqueue_styles() {
  439. /**
  440. * Before frontend styles enqueued.
  441. *
  442. * Fires before Elementor frontend styles are enqueued.
  443. *
  444. * @since 1.0.0
  445. */
  446. do_action( 'elementor/frontend/before_enqueue_styles' );
  447. wp_enqueue_style( 'elementor-icons' );
  448. wp_enqueue_style( 'font-awesome' );
  449. wp_enqueue_style( 'elementor-animations' );
  450. wp_enqueue_style( 'elementor-frontend' );
  451. /**
  452. * After frontend styles enqueued.
  453. *
  454. * Fires after Elementor frontend styles are enqueued.
  455. *
  456. * @since 1.0.0
  457. */
  458. do_action( 'elementor/frontend/after_enqueue_styles' );
  459. if ( ! Plugin::$instance->preview->is_preview_mode() ) {
  460. $this->parse_global_css_code();
  461. $css_file = new Post_CSS( get_the_ID() );
  462. $css_file->enqueue();
  463. }
  464. }
  465. /**
  466. * Elementor footer scripts and styles.
  467. *
  468. * Handle styles and scripts that are not printed in the header.
  469. *
  470. * Fired by `wp_footer` action.
  471. *
  472. * @since 1.0.11
  473. * @access public
  474. */
  475. public function wp_footer() {
  476. if ( ! $this->_has_elementor_in_page ) {
  477. return;
  478. }
  479. $this->enqueue_styles();
  480. $this->enqueue_scripts();
  481. $this->print_fonts_links();
  482. }
  483. /**
  484. * Print fonts links.
  485. *
  486. * Enqueue all the frontend fonts by url.
  487. *
  488. * Fired by `wp_head` action.
  489. *
  490. * @since 1.9.4
  491. * @access public
  492. */
  493. public function print_fonts_links() {
  494. $google_fonts = [
  495. 'google' => [],
  496. 'early' => [],
  497. ];
  498. foreach ( $this->fonts_to_enqueue as $key => $font ) {
  499. $font_type = Fonts::get_font_type( $font );
  500. switch ( $font_type ) {
  501. case Fonts::GOOGLE:
  502. $google_fonts['google'][] = $font;
  503. break;
  504. case Fonts::EARLYACCESS:
  505. $google_fonts['early'][] = $font;
  506. break;
  507. default:
  508. /**
  509. * Print font links.
  510. *
  511. * Fires when Elementor frontend fonts are printed on the HEAD tag.
  512. *
  513. * The dynamic portion of the hook name, `$font_type`, refers to the font type.
  514. *
  515. * @since 2.0.0
  516. *
  517. * @param string $font Font name.
  518. */
  519. do_action( "elementor/fonts/print_font_links/{$font_type}", $font );
  520. }
  521. }
  522. $this->fonts_to_enqueue = [];
  523. $this->enqueue_google_fonts( $google_fonts );
  524. }
  525. /**
  526. * Print Google fonts.
  527. *
  528. * Enqueue all the frontend Google fonts.
  529. *
  530. * Fired by `wp_head` action.
  531. *
  532. * @since 1.0.0
  533. * @access private
  534. *
  535. * @param array $google_fonts Optional. Google fonts to print in the frontend.
  536. * Default is an empty array.
  537. */
  538. private function enqueue_google_fonts( $google_fonts = [] ) {
  539. static $google_fonts_index = 0;
  540. $print_google_fonts = true;
  541. /**
  542. * Print frontend google fonts.
  543. *
  544. * Filters whether to enqueue Google fonts in the frontend.
  545. *
  546. * @since 1.0.0
  547. *
  548. * @param bool $print_google_fonts Whether to enqueue Google fonts. Default is true.
  549. */
  550. $print_google_fonts = apply_filters( 'elementor/frontend/print_google_fonts', $print_google_fonts );
  551. if ( ! $print_google_fonts ) {
  552. return;
  553. }
  554. // Print used fonts
  555. if ( ! empty( $google_fonts['google'] ) ) {
  556. $google_fonts_index++;
  557. foreach ( $google_fonts['google'] as &$font ) {
  558. $font = str_replace( ' ', '+', $font ) . ':100,100italic,200,200italic,300,300italic,400,400italic,500,500italic,600,600italic,700,700italic,800,800italic,900,900italic';
  559. }
  560. $fonts_url = sprintf( 'https://fonts.googleapis.com/css?family=%s', implode( rawurlencode( '|' ), $google_fonts['google'] ) );
  561. $subsets = [
  562. 'ru_RU' => 'cyrillic',
  563. 'bg_BG' => 'cyrillic',
  564. 'he_IL' => 'hebrew',
  565. 'el' => 'greek',
  566. 'vi' => 'vietnamese',
  567. 'uk' => 'cyrillic',
  568. 'cs_CZ' => 'latin-ext',
  569. 'ro_RO' => 'latin-ext',
  570. 'pl_PL' => 'latin-ext',
  571. ];
  572. $locale = get_locale();
  573. if ( isset( $subsets[ $locale ] ) ) {
  574. $fonts_url .= '&subset=' . $subsets[ $locale ];
  575. }
  576. wp_enqueue_style( 'google-fonts-' . $google_fonts_index, $fonts_url ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
  577. }
  578. if ( ! empty( $google_fonts['early'] ) ) {
  579. foreach ( $google_fonts['early'] as $current_font ) {
  580. $google_fonts_index++;
  581. //printf( '<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/earlyaccess/%s.css">', strtolower( str_replace( ' ', '', $current_font ) ) );
  582. $font_url = sprintf( 'https://fonts.googleapis.com/earlyaccess/%s.css', strtolower( str_replace( ' ', '', $current_font ) ) );
  583. wp_enqueue_style( 'google-earlyaccess-' . $google_fonts_index, $font_url ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
  584. }
  585. }
  586. }
  587. /**
  588. * Enqueue fonts.
  589. *
  590. * Enqueue all the frontend fonts.
  591. *
  592. * @since 1.2.0
  593. * @access public
  594. *
  595. * @param array $font Fonts to enqueue in the frontend.
  596. */
  597. public function enqueue_font( $font ) {
  598. if ( in_array( $font, $this->registered_fonts ) ) {
  599. return;
  600. }
  601. $this->fonts_to_enqueue[] = $font;
  602. $this->registered_fonts[] = $font;
  603. }
  604. /**
  605. * Parse global CSS.
  606. *
  607. * Enqueue the global CSS file.
  608. *
  609. * @since 1.2.0
  610. * @access protected
  611. */
  612. protected function parse_global_css_code() {
  613. $scheme_css_file = new Global_CSS( 'global.css' );
  614. $scheme_css_file->enqueue();
  615. }
  616. /**
  617. * Apply builder in content.
  618. *
  619. * Used to apply the Elementor page editor on the post content.
  620. *
  621. * @since 1.0.0
  622. * @access public
  623. *
  624. * @param string $content The post content.
  625. *
  626. * @return string The post content.
  627. */
  628. public function apply_builder_in_content( $content ) {
  629. $this->restore_content_filters();
  630. if ( Plugin::$instance->preview->is_preview_mode() || $this->_is_excerpt ) {
  631. return $content;
  632. }
  633. // Remove the filter itself in order to allow other `the_content` in the elements
  634. $this->remove_content_filter();
  635. $post_id = get_the_ID();
  636. $builder_content = $this->get_builder_content( $post_id );
  637. if ( ! empty( $builder_content ) ) {
  638. $content = $builder_content;
  639. $this->remove_content_filters();
  640. }
  641. // Add the filter again for other `the_content` calls
  642. $this->add_content_filter();
  643. return $content;
  644. }
  645. /**
  646. * Retrieve builder content.
  647. *
  648. * Used to render and return the post content with all the Elementor elements.
  649. *
  650. * Note that this method is an internal method, please use `get_builder_content_for_display()`.
  651. *
  652. * @since 1.0.0
  653. * @access public
  654. *
  655. * @param int $post_id The post ID.
  656. * @param bool $with_css Optional. Whether to retrieve the content with CSS
  657. * or not. Default is false.
  658. *
  659. * @return string The post content.
  660. */
  661. public function get_builder_content( $post_id, $with_css = false ) {
  662. if ( post_password_required( $post_id ) ) {
  663. return '';
  664. }
  665. if ( ! Plugin::$instance->db->is_built_with_elementor( $post_id ) ) {
  666. return '';
  667. }
  668. $document = Plugin::$instance->documents->get_doc_for_frontend( $post_id );
  669. // Change the current post, so widgets can use `documents->get_current`.
  670. Plugin::$instance->documents->switch_to_document( $document );
  671. if ( $document->is_editable_by_current_user() ) {
  672. $this->admin_bar_edit_documents[ $document->get_main_id() ] = $document;
  673. }
  674. $data = $document->get_elements_data();
  675. /**
  676. * Frontend builder content data.
  677. *
  678. * Filters the builder content in the frontend.
  679. *
  680. * @since 1.0.0
  681. *
  682. * @param array $data The builder content.
  683. * @param int $post_id The post ID.
  684. */
  685. $data = apply_filters( 'elementor/frontend/builder_content_data', $data, $post_id );
  686. if ( empty( $data ) ) {
  687. return '';
  688. }
  689. if ( ! $this->_is_excerpt ) {
  690. if ( $document->is_autosave() ) {
  691. $css_file = new Post_Preview( $document->get_post()->ID );
  692. } else {
  693. $css_file = new Post_CSS( $post_id );
  694. }
  695. $css_file->enqueue();
  696. }
  697. ob_start();
  698. // Handle JS and Customizer requests, with CSS inline.
  699. if ( is_customize_preview() || Utils::is_ajax() ) {
  700. $with_css = true;
  701. }
  702. if ( ! empty( $css_file ) && $with_css ) {
  703. $css_file->print_css();
  704. }
  705. $document->print_elements_with_wrapper( $data );
  706. $content = ob_get_clean();
  707. /**
  708. * Frontend content.
  709. *
  710. * Filters the content in the frontend.
  711. *
  712. * @since 1.0.0
  713. *
  714. * @param string $content The content.
  715. */
  716. $content = apply_filters( 'elementor/frontend/the_content', $content );
  717. if ( ! empty( $content ) ) {
  718. $this->_has_elementor_in_page = true;
  719. }
  720. Plugin::$instance->documents->restore_document();
  721. return $content;
  722. }
  723. /**
  724. * Add Elementor menu to admin bar.
  725. *
  726. * Add new admin bar item only on singular pages, to display a link that
  727. * allows the user to edit with Elementor.
  728. *
  729. * Fired by `admin_bar_menu` action.
  730. *
  731. * @since 1.3.4
  732. * @access public
  733. *
  734. * @param \WP_Admin_Bar $wp_admin_bar WP_Admin_Bar instance, passed by reference.
  735. */
  736. public function add_menu_in_admin_bar( \WP_Admin_Bar $wp_admin_bar ) {
  737. if ( empty( $this->admin_bar_edit_documents ) ) {
  738. return;
  739. }
  740. $queried_object_id = get_queried_object_id();
  741. $menu_args = [
  742. 'id' => 'elementor_edit_page',
  743. 'title' => __( 'Edit with Elementor', 'elementor' ),
  744. ];
  745. if ( is_singular() && isset( $this->admin_bar_edit_documents[ $queried_object_id ] ) ) {
  746. $menu_args['href'] = $this->admin_bar_edit_documents[ $queried_object_id ]->get_edit_url();
  747. unset( $this->admin_bar_edit_documents[ $queried_object_id ] );
  748. }
  749. $wp_admin_bar->add_node( $menu_args );
  750. foreach ( $this->admin_bar_edit_documents as $document ) {
  751. $wp_admin_bar->add_menu( [
  752. 'id' => 'elementor_edit_doc_' . $document->get_main_id(),
  753. 'parent' => 'elementor_edit_page',
  754. 'title' => sprintf( '<span class="elementor-edit-link-title">%s</span><span class="elementor-edit-link-type">%s</span>', $document->get_post()->post_title, $document::get_title() ),
  755. 'href' => $document->get_edit_url(),
  756. ] );
  757. }
  758. }
  759. /**
  760. * Retrieve builder content for display.
  761. *
  762. * Used to render and return the post content with all the Elementor elements.
  763. *
  764. * @since 1.0.0
  765. * @access public
  766. *
  767. * @param int $post_id The post ID.
  768. *
  769. * @param bool $with_css Optional. Whether to retrieve the content with CSS
  770. * or not. Default is false.
  771. *
  772. * @return string The post content.
  773. */
  774. public function get_builder_content_for_display( $post_id, $with_css = false ) {
  775. if ( ! get_post( $post_id ) ) {
  776. return '';
  777. }
  778. $editor = Plugin::$instance->editor;
  779. // Avoid recursion
  780. if ( get_the_ID() === (int) $post_id ) {
  781. $content = '';
  782. if ( $editor->is_edit_mode() ) {
  783. $content = '<div class="elementor-alert elementor-alert-danger">' . __( 'Invalid Data: The Template ID cannot be the same as the currently edited template. Please choose a different one.', 'elementor' ) . '</div>';
  784. }
  785. return $content;
  786. }
  787. // 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
  788. $is_edit_mode = $editor->is_edit_mode();
  789. $editor->set_edit_mode( false );
  790. $with_css = $with_css ? true : $is_edit_mode;
  791. $content = $this->get_builder_content( $post_id, $with_css );
  792. // Restore edit mode state
  793. Plugin::$instance->editor->set_edit_mode( $is_edit_mode );
  794. return $content;
  795. }
  796. /**
  797. * Start excerpt flag.
  798. *
  799. * Flags when `the_excerpt` is called. Used to avoid enqueueing CSS in the excerpt.
  800. *
  801. * @since 1.4.3
  802. * @access public
  803. *
  804. * @param string $excerpt The post excerpt.
  805. *
  806. * @return string The post excerpt.
  807. */
  808. public function start_excerpt_flag( $excerpt ) {
  809. $this->_is_excerpt = true;
  810. return $excerpt;
  811. }
  812. /**
  813. * End excerpt flag.
  814. *
  815. * Flags when `the_excerpt` call ended.
  816. *
  817. * @since 1.4.3
  818. * @access public
  819. *
  820. * @param string $excerpt The post excerpt.
  821. *
  822. * @return string The post excerpt.
  823. */
  824. public function end_excerpt_flag( $excerpt ) {
  825. $this->_is_excerpt = false;
  826. return $excerpt;
  827. }
  828. /**
  829. * Remove content filters.
  830. *
  831. * Remove WordPress default filters that conflicted with Elementor.
  832. *
  833. * @since 1.5.0
  834. * @access public
  835. */
  836. public function remove_content_filters() {
  837. $filters = [
  838. 'wpautop',
  839. 'shortcode_unautop',
  840. 'wptexturize',
  841. ];
  842. foreach ( $filters as $filter ) {
  843. // Check if another plugin/theme do not already removed the filter.
  844. if ( has_filter( 'the_content', $filter ) ) {
  845. remove_filter( 'the_content', $filter );
  846. $this->content_removed_filters[] = $filter;
  847. }
  848. }
  849. }
  850. /**
  851. * Restore content filters.
  852. *
  853. * Restore removed WordPress filters that conflicted with Elementor.
  854. *
  855. * @since 1.5.0
  856. * @access private
  857. */
  858. private function restore_content_filters() {
  859. foreach ( $this->content_removed_filters as $filter ) {
  860. add_filter( 'the_content', $filter );
  861. }
  862. $this->content_removed_filters = [];
  863. }
  864. public function has_elementor_in_page() {
  865. return $this->_has_elementor_in_page;
  866. }
  867. /**
  868. * Front End constructor.
  869. *
  870. * Initializing Elementor front end. Make sure we are not in admin, not and
  871. * redirect from old URL structure of Elementor editor.
  872. *
  873. * @since 1.0.0
  874. * @access public
  875. */
  876. public function __construct() {
  877. // We don't need this class in admin side, but in AJAX requests.
  878. if ( is_admin() && ! Utils::is_ajax() ) {
  879. return;
  880. }
  881. add_action( 'template_redirect', [ $this, 'init' ] );
  882. add_action( 'wp_enqueue_scripts', [ $this, 'register_scripts' ], 5 );
  883. add_action( 'wp_enqueue_scripts', [ $this, 'register_styles' ], 5 );
  884. $this->add_content_filter();
  885. // Hack to avoid enqueue post CSS while it's a `the_excerpt` call.
  886. add_filter( 'get_the_excerpt', [ $this, 'start_excerpt_flag' ], 1 );
  887. add_filter( 'get_the_excerpt', [ $this, 'end_excerpt_flag' ], 20 );
  888. }
  889. }