PluginProbe
Elementor Website Builder – more than just a page builder / 4.0.1
Elementor Website Builder – more than just a page builder v4.0.1
4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 4.1.0-beta1 4.1.0-dev1 4.0.7 All 451 releases
elementor / core / editor / editor.php

editor.php in Elementor Website Builder – more than just a page builder 4.0.1, at core/editor/editor.php

664 lines 16.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Elementor\Core\Editor;
3
4 use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager;
5 use Elementor\Core\Common\Modules\Ajax\Module;
6 use Elementor\Core\Debug\Loading_Inspection_Manager;
7 use Elementor\Core\Editor\Loader\Editor_Loader_Factory;
8 use Elementor\Core\Editor\Loader\Editor_Loader_Interface;
9 use Elementor\Core\Settings\Manager as SettingsManager;
10 use Elementor\Plugin;
11 use Elementor\TemplateLibrary\Source_Local;
12 use Elementor\Utils;
13 use Elementor\Core\Editor\Data;
14
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit; // Exit if accessed directly.
17 }
18
19 /**
20 * Elementor editor.
21 *
22 * Elementor editor handler class is responsible for initializing Elementor
23 * editor and register all the actions needed to display the editor.
24 *
25 * @since 1.0.0
26 */
27 class Editor {
28
29 /**
30 * User capability required to access Elementor editor.
31 */
32 const EDITING_CAPABILITY = 'edit_posts';
33
34 /**
35 * Post ID.
36 *
37 * Holds the ID of the current post being edited.
38 *
39 * @since 1.0.0
40 * @access private
41 *
42 * @var int Post ID.
43 */
44 private $post_id;
45
46 /**
47 * Whether the edit mode is active.
48 *
49 * Used to determine whether we are in edit mode.
50 *
51 * @since 1.0.0
52 * @access private
53 *
54 * @var bool Whether the edit mode is active.
55 */
56 private $is_edit_mode;
57
58 /**
59 * @var Notice_Bar
60 */
61 public $notice_bar;
62
63 /**
64 * @var Promotion
65 */
66 public $promotion;
67
68 /**
69 * @var Editor_Loader_Interface
70 */
71 private $loader;
72
73 /**
74 * Init.
75 *
76 * Initialize Elementor editor. Registers all needed actions to run Elementor,
77 * removes conflicting actions etc.
78 *
79 * Fired by `admin_action_elementor` action.
80 *
81 * @since 1.0.0
82 * @access public
83 *
84 * @param bool $to_die Optional. Whether to die at the end. Default is `true`.
85 */
86 public function init( $to_die = true ) {
87 if ( empty( $_REQUEST['post'] ) ) {
88 return;
89 }
90
91 $this->set_post_id( absint( $_REQUEST['post'] ) );
92
93 if ( ! $this->is_edit_mode( $this->post_id ) ) {
94 return;
95 }
96
97 // BC: From 2.9.0, the editor shouldn't handle the global post / current document.
98 // Use requested id and not the global in order to avoid conflicts with plugins that changes the global post.
99 query_posts( [
100 'p' => $this->post_id,
101 'post_type' => get_post_type( $this->post_id ),
102 ] );
103
104 Plugin::$instance->db->switch_to_post( $this->post_id );
105
106 $document = Plugin::$instance->documents->get( $this->post_id );
107
108 Plugin::$instance->documents->switch_to_document( $document );
109
110 // Change mode to Builder
111 $document->set_is_built_with_elementor( true );
112
113 // End BC.
114
115 Loading_Inspection_Manager::instance()->register_inspections();
116
117 // Send MIME Type header like WP admin-header.
118 @header( 'Content-Type: ' . get_option( 'html_type' ) . '; charset=' . get_option( 'blog_charset' ) );
119
120 add_filter( 'show_admin_bar', '__return_false' );
121
122 // Remove all WordPress actions
123 remove_all_actions( 'wp_head' );
124 remove_all_actions( 'wp_print_styles' );
125 remove_all_actions( 'wp_print_head_scripts' );
126 remove_all_actions( 'wp_footer' );
127
128 // Handle `wp_head`
129 add_action( 'wp_head', 'wp_enqueue_scripts', 1 );
130 add_action( 'wp_head', 'wp_print_styles', 8 );
131 add_action( 'wp_head', 'wp_print_head_scripts', 9 );
132 add_action( 'wp_head', 'wp_site_icon' );
133 add_action( 'wp_head', [ $this, 'editor_head_trigger' ], 30 );
134
135 // Handle `wp_footer`
136 add_action( 'wp_footer', 'wp_print_footer_scripts', 20 );
137 add_action( 'wp_footer', 'wp_auth_check_html', 30 );
138 add_action( 'wp_footer', [ $this, 'wp_footer' ] );
139
140 // Handle `wp_enqueue_scripts`
141 remove_all_actions( 'wp_enqueue_scripts' );
142
143 // Also remove all scripts hooked into after_wp_tiny_mce.
144 remove_all_actions( 'after_wp_tiny_mce' );
145
146 add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_scripts' ], 999999 );
147 add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_styles' ], 999999 );
148
149 // Setup default heartbeat options
150 add_filter( 'heartbeat_settings', function( $settings ) {
151 $settings['interval'] = 15;
152 return $settings;
153 } );
154
155 // Tell to WP Cache plugins do not cache this request.
156 Utils::do_not_cache();
157
158 do_action( 'elementor/editor/init' );
159
160 $this->get_loader()->print_root_template();
161
162 // From the action it's an empty string, from tests its `false`
163 if ( false !== $to_die ) {
164 die;
165 }
166 }
167
168 /**
169 * Retrieve post ID.
170 *
171 * Get the ID of the current post.
172 *
173 * @since 1.8.0
174 * @access public
175 *
176 * @return int Post ID.
177 */
178 public function get_post_id() {
179 return $this->post_id;
180 }
181
182 /**
183 * Redirect to new URL.
184 *
185 * Used as a fallback function for the old URL structure of Elementor page
186 * edit URL.
187 *
188 * Fired by `template_redirect` action.
189 *
190 * @since 1.6.0
191 * @access public
192 */
193 public function redirect_to_new_url() {
194 if ( ! isset( $_GET['elementor'] ) ) {
195 return;
196 }
197
198 $document = Plugin::$instance->documents->get( get_the_ID() );
199
200 if ( ! $document ) {
201 wp_die( esc_html__( 'Document not found.', 'elementor' ) );
202 }
203
204 if ( ! $document->is_editable_by_current_user() || ! $document->is_built_with_elementor() ) {
205 return;
206 }
207
208 wp_safe_redirect( $document->get_edit_url() );
209 die;
210 }
211
212 /**
213 * Whether the edit mode is active.
214 *
215 * Used to determine whether we are in the edit mode.
216 *
217 * @since 1.0.0
218 * @access public
219 *
220 * @param int $post_id Optional. Post ID. Default is `null`, the current
221 * post ID.
222 *
223 * @return bool Whether the edit mode is active.
224 */
225 public function is_edit_mode( $post_id = null ) {
226 if ( null !== $this->is_edit_mode ) {
227 return $this->is_edit_mode;
228 }
229
230 if ( empty( $post_id ) ) {
231 $post_id = $this->post_id;
232 }
233
234 $document = Plugin::$instance->documents->get( $post_id );
235
236 if ( ! $document || ! $document->is_editable_by_current_user() ) {
237 return false;
238 }
239
240 /** @var Module ajax */
241 $ajax_data = Plugin::$instance->common->get_component( 'ajax' )->get_current_action_data();
242
243 if ( ! empty( $ajax_data ) && 'get_document_config' === $ajax_data['action'] ) {
244 return true;
245 }
246
247 // Ajax request as Editor mode
248 $actions = [
249 'elementor',
250
251 // Templates
252 'elementor_get_templates',
253 'elementor_save_template',
254 'elementor_get_template',
255 'elementor_delete_template',
256 'elementor_import_template',
257 'elementor_library_direct_actions',
258 ];
259
260 if ( isset( $_REQUEST['action'] ) && in_array( $_REQUEST['action'], $actions ) ) {
261 return true;
262 }
263
264 return false;
265 }
266
267 /**
268 * Lock post.
269 *
270 * Mark the post as currently being edited by the current user.
271 *
272 * @since 1.0.0
273 * @access public
274 *
275 * @param int $post_id The ID of the post being edited.
276 */
277 public function lock_post( $post_id ) {
278 if ( ! function_exists( 'wp_set_post_lock' ) ) {
279 require_once ABSPATH . 'wp-admin/includes/post.php';
280 }
281
282 wp_set_post_lock( $post_id );
283 }
284
285 /**
286 * Get locked user.
287 *
288 * Check what user is currently editing the post.
289 *
290 * @since 1.0.0
291 * @access public
292 *
293 * @param int $post_id The ID of the post being edited.
294 *
295 * @return \WP_User|false User information or false if the post is not locked.
296 */
297 public function get_locked_user( $post_id ) {
298 if ( ! function_exists( 'wp_check_post_lock' ) ) {
299 require_once ABSPATH . 'wp-admin/includes/post.php';
300 }
301
302 $locked_user = wp_check_post_lock( $post_id );
303 if ( ! $locked_user ) {
304 return false;
305 }
306
307 return get_user_by( 'id', $locked_user );
308 }
309
310 /**
311 * NOTICE: This method not in use, it's here for backward compatibility.
312 *
313 * Print Editor Template.
314 *
315 * Include the wrapper template of the editor.
316 *
317 * @since 2.2.0
318 * @access public
319 */
320 public function print_editor_template() {
321 include ELEMENTOR_PATH . 'includes/editor-templates/editor-wrapper.php';
322 }
323
324 /**
325 * Enqueue scripts.
326 *
327 * Registers all the editor scripts and enqueues them.
328 *
329 * @since 1.0.0
330 * @access public
331 */
332 public function enqueue_scripts() {
333 remove_action( 'wp_enqueue_scripts', [ $this, __FUNCTION__ ], 999999 );
334
335 global $wp_styles, $wp_scripts;
336
337 // Reset global variable
338 $wp_styles = new \WP_Styles(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
339 $wp_scripts = new \WP_Scripts(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
340
341 $this->get_loader()->register_scripts();
342
343 /**
344 * Before editor enqueue scripts.
345 *
346 * Fires before Elementor editor scripts are enqueued.
347 *
348 * @since 1.0.0
349 */
350 do_action( 'elementor/editor/before_enqueue_scripts' );
351
352 // Tweak for WP Admin menu icons
353 wp_print_styles( 'editor-buttons' );
354
355 $this->get_loader()->enqueue_scripts();
356
357 Plugin::$instance->controls_manager->enqueue_control_scripts();
358
359 /**
360 * After editor enqueue scripts.
361 *
362 * Fires after Elementor editor scripts are enqueued.
363 *
364 * @since 1.0.0
365 */
366 do_action( 'elementor/editor/after_enqueue_scripts' );
367 }
368
369 /**
370 * Enqueue styles.
371 *
372 * Registers all the editor styles and enqueues them.
373 *
374 * @since 1.0.0
375 * @access public
376 */
377 public function enqueue_styles() {
378 /**
379 * Before editor enqueue styles.
380 *
381 * Fires before Elementor editor styles are enqueued.
382 *
383 * @since 1.0.0
384 */
385 do_action( 'elementor/editor/before_enqueue_styles' );
386
387 $this->get_loader()->register_styles();
388 $this->get_loader()->enqueue_styles();
389
390 $this->enqueue_theme_ui_styles();
391
392 $breakpoints = Plugin::$instance->breakpoints->get_breakpoints();
393
394 // The two breakpoints under 'tablet' need to be checked for values.
395 if ( $breakpoints[ Breakpoints_Manager::BREAKPOINT_KEY_MOBILE ]->is_custom() || $breakpoints[ Breakpoints_Manager::BREAKPOINT_KEY_MOBILE_EXTRA ]->is_enabled() ) {
396 wp_add_inline_style(
397 'elementor-editor',
398 '.elementor-device-tablet #elementor-preview-responsive-wrapper { width: ' . Plugin::$instance->breakpoints->get_device_min_breakpoint( Breakpoints_Manager::BREAKPOINT_KEY_TABLET ) . 'px; }'
399 );
400 }
401
402 /**
403 * After editor enqueue styles.
404 *
405 * Fires after Elementor editor styles are enqueued.
406 *
407 * @since 1.0.0
408 */
409 do_action( 'elementor/editor/after_enqueue_styles' );
410 }
411
412 private function enqueue_theme_ui_styles() {
413 $ui_theme_selected = SettingsManager::get_settings_managers( 'editorPreferences' )->get_model()->get_settings( 'ui_theme' );
414
415 $ui_themes = [
416 'light',
417 'dark',
418 ];
419
420 if ( 'auto' === $ui_theme_selected || ! in_array( $ui_theme_selected, $ui_themes, true ) ) {
421 $ui_light_theme_media_queries = '(prefers-color-scheme: light)';
422 $ui_dark_theme_media_queries = '(prefers-color-scheme: dark)';
423 } else {
424 $ui_light_theme_media_queries = 'none';
425 $ui_dark_theme_media_queries = 'none';
426
427 if ( 'light' === $ui_theme_selected ) {
428 $ui_light_theme_media_queries = 'all';
429 } elseif ( 'dark' === $ui_theme_selected ) {
430 $ui_dark_theme_media_queries = 'all';
431 }
432 }
433
434 $this->enqueue_theme_ui( 'light', $ui_light_theme_media_queries );
435 $this->enqueue_theme_ui( 'dark', $ui_dark_theme_media_queries );
436 }
437
438 private function enqueue_theme_ui( $ui_theme, $ui_theme_media_queries = 'all' ) {
439 $suffix = Utils::is_script_debug() ? '' : '.min';
440
441 wp_enqueue_style(
442 'e-theme-ui-' . $ui_theme,
443 ELEMENTOR_ASSETS_URL . 'css/theme-' . $ui_theme . $suffix . '.css',
444 [],
445 ELEMENTOR_VERSION,
446 $ui_theme_media_queries
447 );
448 }
449
450 /**
451 * Editor head trigger.
452 *
453 * Fires the 'elementor/editor/wp_head' action in the head tag in Elementor
454 * editor.
455 *
456 * @since 1.0.0
457 * @access public
458 */
459 public function editor_head_trigger() {
460 /**
461 * Elementor editor head.
462 *
463 * Fires on Elementor editor head tag.
464 *
465 * Used to prints scripts or any other data in the head tag.
466 *
467 * @since 1.0.0
468 */
469 do_action( 'elementor/editor/wp_head' );
470 }
471
472 /**
473 * WP footer.
474 *
475 * Prints Elementor editor with all the editor templates, and render controls,
476 * widgets and content elements.
477 *
478 * Fired by `wp_footer` action.
479 *
480 * @since 1.0.0
481 * @access public
482 */
483 public function wp_footer() {
484 $plugin = Plugin::$instance;
485
486 $plugin->controls_manager->render_controls();
487 $plugin->widgets_manager->render_widgets_content();
488 $plugin->elements_manager->render_elements_content();
489
490 $plugin->dynamic_tags->print_templates();
491
492 $this->get_loader()->register_additional_templates();
493
494 /**
495 * Elementor editor footer.
496 *
497 * Fires on Elementor editor before closing the body tag.
498 *
499 * Used to prints scripts or any other HTML before closing the body tag.
500 *
501 * @since 1.0.0
502 */
503 do_action( 'elementor/editor/footer' );
504 }
505
506 /**
507 * Set edit mode.
508 *
509 * Used to update the edit mode.
510 *
511 * @since 1.0.0
512 * @access public
513 *
514 * @param bool $edit_mode Whether the edit mode is active.
515 */
516 public function set_edit_mode( $edit_mode ) {
517 $this->is_edit_mode = $edit_mode;
518 }
519
520 /**
521 * Editor constructor.
522 *
523 * Initializing Elementor editor and redirect from old URL structure of
524 * Elementor editor.
525 *
526 * @since 1.0.0
527 * @access public
528 */
529 public function __construct() {
530 Plugin::$instance->data_manager_v2->register_controller( new Data\Globals\Controller() );
531
532 $this->notice_bar = new Notice_Bar();
533 $this->promotion = new Promotion();
534
535 add_action( 'admin_action_elementor', [ $this, 'init' ] );
536 add_action( 'template_redirect', [ $this, 'redirect_to_new_url' ] );
537
538 // Handle autocomplete feature for URL control.
539 add_filter( 'wp_link_query_args', [ $this, 'filter_wp_link_query_args' ] );
540 add_filter( 'wp_link_query', [ $this, 'filter_wp_link_query' ] );
541
542 add_filter( 'replace_editor', [ $this, 'filter_replace_editor' ], 10, 2 );
543 }
544
545 /**
546 * Signals to WordPress that Elementor is replacing the block editor on its own editor page,
547 * so that block-editor-specific behaviour (e.g. WP 7.0 COOP/COEP isolation headers) is not
548 * applied when the Elementor editor is active.
549 *
550 * @param bool $replace Whether the editor is being replaced.
551 * @param \WP_Post $post The post being edited.
552 *
553 * @return bool
554 */
555 public function filter_replace_editor( $replace, $post ) {
556 if ( isset( $_REQUEST['action'] ) && 'elementor' === $_REQUEST['action'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
557 return true;
558 }
559
560 return $replace;
561 }
562
563 /**
564 * @since 2.2.0
565 * @access public
566 */
567 public function filter_wp_link_query_args( $query ) {
568 $library_cpt_key = array_search( Source_Local::CPT, $query['post_type'], true );
569 if ( false !== $library_cpt_key ) {
570 unset( $query['post_type'][ $library_cpt_key ] );
571 }
572
573 return $query;
574 }
575
576 /**
577 * @since 2.2.0
578 * @access public
579 */
580 public function filter_wp_link_query( $results ) {
581
582 // PHPCS - The user data is not used.
583 if ( isset( $_POST['editor'] ) && 'elementor' === $_POST['editor'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
584 $post_type_object = get_post_type_object( 'post' );
585 $post_label = $post_type_object->labels->singular_name;
586
587 foreach ( $results as & $result ) {
588 if ( 'post' === get_post_type( $result['ID'] ) ) {
589 $result['info'] = $post_label;
590 }
591 }
592 }
593
594 return $results;
595 }
596
597 public function set_post_id( $post_id ) {
598 $this->post_id = $post_id;
599 }
600
601 /**
602 * Get loader.
603 *
604 * @return Editor_Loader_Interface
605 */
606 private function get_loader() {
607 if ( ! $this->loader ) {
608 $this->loader = Editor_Loader_Factory::create();
609
610 $this->loader->init();
611 }
612
613 return $this->loader;
614 }
615
616 /**
617 * Get elements presets.
618 *
619 * @return array
620 */
621 public function get_elements_presets() {
622 $element_types = Plugin::$instance->elements_manager->get_element_types();
623 $presets = [];
624
625 foreach ( $element_types as $el_type => $element ) {
626 $this->check_element_for_presets( $element, $el_type, $presets );
627 }
628
629 return $presets;
630 }
631
632 /**
633 * @return void
634 */
635 private function check_element_for_presets( $element, $el_type, &$presets ) {
636 $element_presets = $element->get_panel_presets();
637
638 if ( empty( $element_presets ) ) {
639 return;
640 }
641
642 foreach ( $element_presets as $key => $preset ) {
643 $this->maybe_add_preset( $el_type, $preset, $key, $presets );
644 }
645 }
646
647 /**
648 * @return void
649 */
650 private function maybe_add_preset( $el_type, $preset, $key, &$presets ) {
651 if ( $this->is_valid_preset( $el_type, $preset ) ) {
652 $presets[ $key ] = $preset;
653 }
654 }
655
656 /**
657 * @return boolean
658 */
659 private function is_valid_preset( $el_type, $preset ) {
660 return isset( $preset['replacements']['custom']['originalWidget'] )
661 && $el_type === $preset['replacements']['custom']['originalWidget'];
662 }
663 }
664