PluginProbe
Elementor Website Builder – more than just a page builder / 3.20.3
Elementor Website Builder – more than just a page builder v3.20.3
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 3.20.3, at core/editor/editor.php

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