PluginProbe
Elementor Website Builder – more than just a page builder / 3.1.0-beta1
Elementor Website Builder – more than just a page builder v3.1.0-beta1
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 / base / document.php

document.php in Elementor Website Builder – more than just a page builder 3.1.0-beta1, at core/base/document.php

1,393 lines 31.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Elementor\Core\Base;
3
4 use Elementor\Core\Files\CSS\Post as Post_CSS;
5 use Elementor\Core\Utils\Exceptions;
6 use Elementor\Plugin;
7 use Elementor\Controls_Manager;
8 use Elementor\Controls_Stack;
9 use Elementor\User;
10 use Elementor\Core\Settings\Manager as SettingsManager;
11 use Elementor\Utils;
12 use Elementor\Widget_Base;
13 use Elementor\Core\Settings\Page\Manager as PageManager;
14
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit; // Exit if accessed directly
17 }
18
19 /**
20 * Elementor document.
21 *
22 * An abstract class that provides the needed properties and methods to
23 * manage and handle documents in inheriting classes.
24 *
25 * @since 2.0.0
26 * @abstract
27 */
28 abstract class Document extends Controls_Stack {
29
30 /**
31 * Document type meta key.
32 */
33 const TYPE_META_KEY = '_elementor_template_type';
34 const PAGE_META_KEY = '_elementor_page_settings';
35
36 const BUILT_WITH_ELEMENTOR_META_KEY = '_elementor_edit_mode';
37
38 /**
39 * Document publish status.
40 */
41 const STATUS_PUBLISH = 'publish';
42
43 /**
44 * Document draft status.
45 */
46 const STATUS_DRAFT = 'draft';
47
48 /**
49 * Document private status.
50 */
51 const STATUS_PRIVATE = 'private';
52
53 /**
54 * Document autosave status.
55 */
56 const STATUS_AUTOSAVE = 'autosave';
57
58 /**
59 * Document pending status.
60 */
61 const STATUS_PENDING = 'pending';
62
63
64 private $main_id;
65
66 /**
67 * @var bool
68 */
69 private $is_saving = false;
70
71 private static $properties = [];
72
73 /**
74 * Document post data.
75 *
76 * Holds the document post data.
77 *
78 * @since 2.0.0
79 * @access protected
80 *
81 * @var \WP_Post WordPress post data.
82 */
83 protected $post;
84
85 /**
86 * Settings to be Saved
87 *
88 * A variable that temporarily stores the new settings while the document is being saved.
89 *
90 * @since 3.1.0
91 * @access protected
92 */
93 protected $data_to_be_saved;
94
95 /**
96 * @since 2.1.0
97 * @access protected
98 * @static
99 */
100 protected static function get_editor_panel_categories() {
101 return Plugin::$instance->elements_manager->get_categories();
102 }
103
104 /**
105 * Get properties.
106 *
107 * Retrieve the document properties.
108 *
109 * @since 2.0.0
110 * @access public
111 * @static
112 *
113 * @return array Document properties.
114 */
115 public static function get_properties() {
116 return [
117 'has_elements' => true,
118 'is_editable' => true,
119 'edit_capability' => '',
120 'show_in_finder' => true,
121 'show_on_admin_bar' => true,
122 'support_kit' => false,
123 ];
124 }
125
126 /**
127 * @since 2.1.0
128 * @access public
129 * @static
130 */
131 public static function get_editor_panel_config() {
132 $default_route = 'panel/elements/categories';
133
134 if ( ! Plugin::instance()->role_manager->user_can( 'design' ) ) {
135 $default_route = 'panel/page-settings/settings';
136 }
137
138 return [
139 'title' => static::get_title(), // JS Container title.
140 'widgets_settings' => [],
141 'elements_categories' => static::get_editor_panel_categories(),
142 'default_route' => $default_route,
143 'has_elements' => static::get_property( 'has_elements' ),
144 'support_kit' => static::get_property( 'support_kit' ),
145 'messages' => [
146 /* translators: %s: the document title. */
147 'publish_notification' => sprintf( __( 'Hurray! Your %s is live.', 'elementor' ), static::get_title() ),
148 ],
149 ];
150 }
151
152 /**
153 * Get element title.
154 *
155 * Retrieve the element title.
156 *
157 * @since 2.0.0
158 * @access public
159 * @static
160 *
161 * @return string Element title.
162 */
163 public static function get_title() {
164 return __( 'Document', 'elementor' );
165 }
166
167 /**
168 * Get property.
169 *
170 * Retrieve the document property.
171 *
172 * @since 2.0.0
173 * @access public
174 * @static
175 *
176 * @param string $key The property key.
177 *
178 * @return mixed The property value.
179 */
180 public static function get_property( $key ) {
181 $id = static::get_class_full_name();
182
183 if ( ! isset( self::$properties[ $id ] ) ) {
184 self::$properties[ $id ] = static::get_properties();
185 }
186
187 return self::get_items( self::$properties[ $id ], $key );
188 }
189
190 /**
191 * @since 2.0.0
192 * @access public
193 * @static
194 */
195 public static function get_class_full_name() {
196 return get_called_class();
197 }
198
199 /**
200 * @since 2.0.0
201 * @access public
202 */
203 public function get_unique_name() {
204 return $this->get_name() . '-' . $this->post->ID;
205 }
206
207 /**
208 * @since 2.3.0
209 * @access public
210 */
211 public function get_post_type_title() {
212 $post_type_object = get_post_type_object( $this->post->post_type );
213
214 return $post_type_object->labels->singular_name;
215 }
216
217 /**
218 * @since 2.0.12
219 * @deprecated 2.4.0 Use `Document::get_remote_library_config()` instead
220 * @access public
221 */
222 public function get_remote_library_type() {
223 _deprecated_function( __METHOD__, '2.4.0', __CLASS__ . '::get_remote_library_config()' );
224 }
225
226 /**
227 * @since 2.0.0
228 * @access public
229 */
230 public function get_main_id() {
231 if ( ! $this->main_id ) {
232 $post_id = $this->post->ID;
233
234 $parent_post_id = wp_is_post_revision( $post_id );
235
236 if ( $parent_post_id ) {
237 $post_id = $parent_post_id;
238 }
239
240 $this->main_id = $post_id;
241 }
242
243 return $this->main_id;
244 }
245
246 /**
247 * @since 2.0.0
248 * @access public
249 *
250 * @param $data
251 *
252 * @throws \Exception If the widget was not found.
253 *
254 * @return string
255 */
256 public function render_element( $data ) {
257 // Start buffering
258 ob_start();
259
260 /** @var Widget_Base $widget */
261 $widget = Plugin::$instance->elements_manager->create_element_instance( $data );
262
263 if ( ! $widget ) {
264 throw new \Exception( 'Widget not found.' );
265 }
266
267 $widget->render_content();
268
269 $render_html = ob_get_clean();
270
271 return $render_html;
272 }
273
274 /**
275 * @since 2.0.0
276 * @access public
277 */
278 public function get_main_post() {
279 return get_post( $this->get_main_id() );
280 }
281
282 /**
283 * @since 2.0.6
284 * @deprecated 2.4.0 Use `Document::get_container_attributes()` instead
285 * @access public
286 */
287 public function get_container_classes() {
288 _deprecated_function( __METHOD__, '2.4.0', __CLASS__ . '::get_container_attributes()' );
289
290 return '';
291 }
292
293 public function get_container_attributes() {
294 $id = $this->get_main_id();
295
296 $attributes = [
297 'data-elementor-type' => $this->get_name(),
298 'data-elementor-id' => $id,
299 'class' => 'elementor elementor-' . $id,
300 ];
301
302 $version_meta = $this->get_main_meta( '_elementor_version' );
303
304 if ( version_compare( $version_meta, '2.5.0', '<' ) ) {
305 $attributes['class'] .= ' elementor-bc-flex-widget';
306 }
307
308 if ( Plugin::$instance->preview->is_preview() ) {
309 $attributes['data-elementor-title'] = static::get_title();
310 } else {
311 $attributes['data-elementor-settings'] = wp_json_encode( $this->get_frontend_settings() );
312 }
313
314 return $attributes;
315 }
316
317 /**
318 * @since 2.0.0
319 * @access public
320 */
321 public function get_wp_preview_url() {
322 $main_post_id = $this->get_main_id();
323 $document = $this;
324
325 // Ajax request from editor.
326 if ( ! empty( $_POST['initial_document_id'] ) ) {
327 $document = Plugin::$instance->documents->get( $_POST['initial_document_id'] );
328 }
329
330 $url = get_preview_post_link(
331 $document->get_main_id(),
332 [
333 'preview_id' => $main_post_id,
334 'preview_nonce' => wp_create_nonce( 'post_preview_' . $main_post_id ),
335 ]
336 );
337
338 /**
339 * Document "WordPress preview" URL.
340 *
341 * Filters the WordPress preview URL.
342 *
343 * @since 2.0.0
344 *
345 * @param string $url WordPress preview URL.
346 * @param Document $this The document instance.
347 */
348 $url = apply_filters( 'elementor/document/urls/wp_preview', $url, $this );
349
350 return $url;
351 }
352
353 /**
354 * @since 2.0.0
355 * @access public
356 */
357 public function get_exit_to_dashboard_url() {
358 $url = get_edit_post_link( $this->get_main_id(), 'raw' );
359
360 /**
361 * Document "exit to dashboard" URL.
362 *
363 * Filters the "Exit To Dashboard" URL.
364 *
365 * @since 2.0.0
366 *
367 * @param string $url The exit URL
368 * @param Document $this The document instance.
369 */
370 $url = apply_filters( 'elementor/document/urls/exit_to_dashboard', $url, $this );
371
372 return $url;
373 }
374
375 /**
376 * Get auto-saved post revision.
377 *
378 * Retrieve the auto-saved post revision that is newer than current post.
379 *
380 * @since 2.0.0
381 * @access public
382 *
383 *
384 * @return bool|Document
385 */
386
387 public function get_newer_autosave() {
388 $autosave = $this->get_autosave();
389
390 // Detect if there exists an autosave newer than the post.
391 if ( $autosave && mysql2date( 'U', $autosave->get_post()->post_modified_gmt, false ) > mysql2date( 'U', $this->post->post_modified_gmt, false ) ) {
392 return $autosave;
393 }
394
395 return false;
396 }
397
398 /**
399 * @since 2.0.0
400 * @access public
401 */
402 public function is_autosave() {
403 return wp_is_post_autosave( $this->post->ID );
404 }
405
406 /**
407 * Check if the current document is a 'revision'
408 *
409 * @return bool
410 */
411 public function is_revision() {
412 return 'revision' === $this->post->post_type;
413 }
414
415 /**
416 * Checks if the current document status is 'trash'.
417 *
418 * @return bool
419 */
420 public function is_trash() {
421 return 'trash' === $this->post->post_status;
422 }
423
424 /**
425 * @since 2.0.0
426 * @access public
427 *
428 * @param int $user_id
429 * @param bool $create
430 *
431 * @return bool|Document
432 */
433 public function get_autosave( $user_id = 0, $create = false ) {
434 if ( ! $user_id ) {
435 $user_id = get_current_user_id();
436 }
437
438 $autosave_id = $this->get_autosave_id( $user_id );
439
440 if ( $autosave_id ) {
441 $document = Plugin::$instance->documents->get( $autosave_id );
442 } elseif ( $create ) {
443 $autosave_id = wp_create_post_autosave( [
444 'post_ID' => $this->post->ID,
445 'post_type' => $this->post->post_type,
446 'post_title' => $this->post->post_title,
447 'post_excerpt' => $this->post->post_excerpt,
448 // Hack to cause $autosave_is_different=true in `wp_create_post_autosave`.
449 'post_content' => '<!-- Created With Elementor -->',
450 'post_modified' => current_time( 'mysql' ),
451 ] );
452
453 Plugin::$instance->db->copy_elementor_meta( $this->post->ID, $autosave_id );
454
455 $document = Plugin::$instance->documents->get( $autosave_id );
456 $document->save_template_type();
457 } else {
458 $document = false;
459 }
460
461 return $document;
462 }
463
464 /**
465 * Add/Remove edit link in dashboard.
466 *
467 * Add or remove an edit link to the post/page action links on the post/pages list table.
468 *
469 * Fired by `post_row_actions` and `page_row_actions` filters.
470 *
471 * @access public
472 *
473 * @param array $actions An array of row action links.
474 *
475 * @return array An updated array of row action links.
476 */
477 public function filter_admin_row_actions( $actions ) {
478 if ( $this->is_built_with_elementor() && $this->is_editable_by_current_user() ) {
479 $actions['edit_with_elementor'] = sprintf(
480 '<a href="%1$s">%2$s</a>',
481 $this->get_edit_url(),
482 __( 'Edit with Elementor', 'elementor' )
483 );
484 }
485
486 return $actions;
487 }
488
489 /**
490 * @since 2.0.0
491 * @access public
492 */
493 public function is_editable_by_current_user() {
494 $edit_capability = static::get_property( 'edit_capability' );
495 if ( $edit_capability && ! current_user_can( $edit_capability ) ) {
496 return false;
497 }
498
499 return self::get_property( 'is_editable' ) && User::is_current_user_can_edit( $this->get_main_id() );
500 }
501
502 /**
503 * @since 2.9.0
504 * @access protected
505 */
506 protected function get_initial_config() {
507 // Get document data *after* the scripts hook - so plugins can run compatibility before get data, but *before* enqueue the editor script - so elements can enqueue their own scripts that depended in editor script.
508
509 $locked_user = Plugin::$instance->editor->get_locked_user( $this->get_main_id() );
510
511 if ( $locked_user ) {
512 $locked_user = $locked_user->display_name;
513 }
514
515 $post_type_object = get_post_type_object( $this->get_main_post()->post_type );
516
517 $settings = SettingsManager::get_settings_managers_config();
518
519 $config = [
520 'id' => $this->get_main_id(),
521 'type' => $this->get_name(),
522 'version' => $this->get_main_meta( '_elementor_version' ),
523 'settings' => $settings['page'],
524 'remoteLibrary' => $this->get_remote_library_config(),
525 'last_edited' => $this->get_last_edited(),
526 'panel' => static::get_editor_panel_config(),
527 'container' => 'body',
528 'post_type_title' => $this->get_post_type_title(),
529 'user' => [
530 'can_publish' => current_user_can( $post_type_object->cap->publish_posts ),
531
532 // Deprecated config since 2.9.0.
533 'locked' => $locked_user,
534 ],
535 'urls' => [
536 'exit_to_dashboard' => $this->get_exit_to_dashboard_url(),
537 'preview' => $this->get_preview_url(),
538 'wp_preview' => $this->get_wp_preview_url(),
539 'permalink' => $this->get_permalink(),
540 'have_a_look' => $this->get_have_a_look_url(),
541 ],
542 ];
543
544 if ( static::get_property( 'has_elements' ) ) {
545 $config['elements'] = $this->get_elements_raw_data( null, true );
546 $config['widgets'] = Plugin::$instance->widgets_manager->get_widget_types_config();
547 }
548
549 $additional_config = apply_filters( 'elementor/document/config', [], $this->get_main_id() );
550
551 if ( ! empty( $additional_config ) ) {
552 $config = array_replace_recursive( $config, $additional_config );
553 }
554
555 return $config;
556 }
557
558 /**
559 * @since 3.1.0
560 * @access protected
561 */
562 protected function register_controls() {
563 $this->register_document_controls();
564 /**
565 * Register document controls.
566 *
567 * Fires after Elementor registers the document controls.
568 *
569 * @since 2.0.0
570 *
571 * @param Document $this The document instance.
572 */
573 do_action( 'elementor/documents/register_controls', $this );
574 }
575
576 /**
577 * @since 2.0.0
578 * @access public
579 *
580 * @param $data
581 *
582 * @return bool
583 */
584 public function save( $data ) {
585 $this->add_handle_revisions_changed_filter();
586
587 if ( ! $this->is_editable_by_current_user() ) {
588 return false;
589 }
590
591 $this->data_to_be_saved = $data;
592
593 $this->set_is_saving( true );
594
595 /**
596 * Before document save.
597 *
598 * Fires when document save starts on Elementor.
599 *
600 * @since 2.5.12
601 *
602 * @param \Elementor\Core\Base\Document $this The current document.
603 * @param $data.
604 */
605 do_action( 'elementor/document/before_save', $this, $data );
606
607 if ( ! current_user_can( 'unfiltered_html' ) ) {
608 $data = wp_kses_post_deep( $data );
609 }
610
611 if ( ! empty( $data['settings'] ) ) {
612 if ( isset( $data['settings']['post_status'] ) && self::STATUS_AUTOSAVE === $data['settings']['post_status'] ) {
613 if ( ! defined( 'DOING_AUTOSAVE' ) ) {
614 define( 'DOING_AUTOSAVE', true );
615 }
616 }
617
618 $this->save_settings( $data['settings'] );
619
620 // Refresh post after save settings.
621 $this->post = get_post( $this->post->ID );
622 }
623
624 // Don't check is_empty, because an empty array should be saved.
625 if ( isset( $data['elements'] ) && is_array( $data['elements'] ) ) {
626 $this->save_elements( $data['elements'] );
627 }
628
629 $this->save_template_type();
630
631 $this->save_version();
632
633 // Remove Post CSS
634 $post_css = Post_CSS::create( $this->post->ID );
635
636 $post_css->delete();
637
638 /**
639 * After document save.
640 *
641 * Fires when document save is complete.
642 *
643 * @since 2.5.12
644 *
645 * @param \Elementor\Core\Base\Document $this The current document.
646 * @param $data.
647 */
648 do_action( 'elementor/document/after_save', $this, $data );
649
650 $this->set_is_saving( false );
651
652 unset( $this->data_to_be_saved );
653
654 $this->remove_handle_revisions_changed_filter();
655
656 return true;
657 }
658
659 /**
660 * @param array $new_settings
661 *
662 * @return static
663 */
664 public function update_settings( array $new_settings ) {
665 $document_settings = $this->get_meta( PageManager::META_KEY );
666
667 if ( ! $document_settings ) {
668 $document_settings = [];
669 }
670
671 $this->save_settings(
672 array_replace_recursive( $document_settings, $new_settings )
673 );
674
675 return $this;
676 }
677
678 /**
679 * Is built with Elementor.
680 *
681 * Check whether the post was built with Elementor.
682 *
683 * @since 2.0.0
684 * @access public
685 *
686 * @return bool Whether the post was built with Elementor.
687 */
688 public function is_built_with_elementor() {
689 return ! ! $this->get_meta( self::BUILT_WITH_ELEMENTOR_META_KEY );
690 }
691
692 /**
693 * Mark the post as "built with elementor" or not.
694 *
695 * @param bool $is_built_with_elementor
696 *
697 * @return $this
698 */
699 public function set_is_built_with_elementor( $is_built_with_elementor ) {
700 if ( $is_built_with_elementor ) {
701 // Use the string `builder` and not a boolean for rollback compatibility
702 $this->update_meta( self::BUILT_WITH_ELEMENTOR_META_KEY, 'builder' );
703 } else {
704 $this->delete_meta( self::BUILT_WITH_ELEMENTOR_META_KEY );
705 }
706
707 return $this;
708 }
709
710 /**
711 * @since 2.0.0
712 * @access public
713 * @static
714 *
715 * @return mixed
716 */
717 public function get_edit_url() {
718 $url = add_query_arg(
719 [
720 'post' => $this->get_main_id(),
721 'action' => 'elementor',
722 ],
723 admin_url( 'post.php' )
724 );
725
726 /**
727 * Document edit url.
728 *
729 * Filters the document edit url.
730 *
731 * @since 2.0.0
732 *
733 * @param string $url The edit url.
734 * @param Document $this The document instance.
735 */
736 $url = apply_filters( 'elementor/document/urls/edit', $url, $this );
737
738 return $url;
739 }
740
741 /**
742 * @since 2.0.0
743 * @access public
744 */
745 public function get_preview_url() {
746 /**
747 * Use a static var - to avoid change the `ver` parameter on every call.
748 */
749 static $url;
750
751 if ( empty( $url ) ) {
752
753 add_filter( 'pre_option_permalink_structure', '__return_empty_string' );
754
755 $url = set_url_scheme( add_query_arg( [
756 'elementor-preview' => $this->get_main_id(),
757 'ver' => time(),
758 ], $this->get_permalink() ) );
759
760 remove_filter( 'pre_option_permalink_structure', '__return_empty_string' );
761
762 /**
763 * Document preview URL.
764 *
765 * Filters the document preview URL.
766 *
767 * @since 2.0.0
768 *
769 * @param string $url The preview URL.
770 * @param Document $this The document instance.
771 */
772 $url = apply_filters( 'elementor/document/urls/preview', $url, $this );
773 }
774
775 return $url;
776 }
777
778 /**
779 * @since 2.0.0
780 * @access public
781 *
782 * @param string $key
783 *
784 * @return array
785 */
786 public function get_json_meta( $key ) {
787 $meta = get_post_meta( $this->post->ID, $key, true );
788
789 if ( is_string( $meta ) && ! empty( $meta ) ) {
790 $meta = json_decode( $meta, true );
791 }
792
793 if ( empty( $meta ) ) {
794 $meta = [];
795 }
796
797 return $meta;
798 }
799
800 /**
801 * @since 2.0.0
802 * @access public
803 *
804 * @param null $data
805 * @param bool $with_html_content
806 *
807 * @return array
808 */
809 public function get_elements_raw_data( $data = null, $with_html_content = false ) {
810 if ( ! static::get_property( 'has_elements' ) ) {
811 return [];
812 }
813
814 if ( is_null( $data ) ) {
815 $data = $this->get_elements_data();
816 }
817
818 // Change the current documents, so widgets can use `documents->get_current` and other post data
819 Plugin::$instance->documents->switch_to_document( $this );
820
821 $editor_data = [];
822
823 foreach ( $data as $element_data ) {
824 $element = Plugin::$instance->elements_manager->create_element_instance( $element_data );
825
826 if ( ! $element ) {
827 continue;
828 }
829
830 $editor_data[] = $element->get_raw_data( $with_html_content );
831 } // End foreach().
832
833 Plugin::$instance->documents->restore_document();
834
835 return $editor_data;
836 }
837
838 /**
839 * @since 2.0.0
840 * @access public
841 *
842 * @param string $status
843 *
844 * @return array
845 */
846 public function get_elements_data( $status = self::STATUS_PUBLISH ) {
847 $elements = $this->get_json_meta( '_elementor_data' );
848
849 if ( self::STATUS_DRAFT === $status ) {
850 $autosave = $this->get_newer_autosave();
851
852 if ( is_object( $autosave ) ) {
853 $autosave_elements = Plugin::$instance->documents
854 ->get( $autosave->get_post()->ID )
855 ->get_json_meta( '_elementor_data' );
856 }
857 }
858
859 if ( Plugin::$instance->editor->is_edit_mode() ) {
860 if ( empty( $elements ) && empty( $autosave_elements ) ) {
861 // Convert to Elementor.
862 $elements = $this->convert_to_elementor();
863 if ( $this->is_autosave() ) {
864 Plugin::$instance->db->copy_elementor_meta( $this->post->post_parent, $this->post->ID );
865 }
866 }
867 }
868
869 if ( ! empty( $autosave_elements ) ) {
870 $elements = $autosave_elements;
871 }
872
873 return $elements;
874 }
875
876 /**
877 * @since 2.3.0
878 * @access public
879 */
880 public function convert_to_elementor() {
881 $this->save( [] );
882
883 if ( empty( $this->post->post_content ) ) {
884 return [];
885 }
886
887 // Check if it's only a shortcode.
888 preg_match_all( '/' . get_shortcode_regex() . '/', $this->post->post_content, $matches, PREG_SET_ORDER );
889 if ( ! empty( $matches ) ) {
890 foreach ( $matches as $shortcode ) {
891 if ( trim( $this->post->post_content ) === $shortcode[0] ) {
892 $widget_type = Plugin::$instance->widgets_manager->get_widget_types( 'shortcode' );
893 $settings = [
894 'shortcode' => $this->post->post_content,
895 ];
896 break;
897 }
898 }
899 }
900
901 if ( empty( $widget_type ) ) {
902 $widget_type = Plugin::$instance->widgets_manager->get_widget_types( 'text-editor' );
903 $settings = [
904 'editor' => $this->post->post_content,
905 ];
906 }
907
908 // TODO: Better coding to start template for editor
909 return [
910 [
911 'id' => Utils::generate_random_string(),
912 'elType' => 'section',
913 'elements' => [
914 [
915 'id' => Utils::generate_random_string(),
916 'elType' => 'column',
917 'elements' => [
918 [
919 'id' => Utils::generate_random_string(),
920 'elType' => $widget_type::get_type(),
921 'widgetType' => $widget_type->get_name(),
922 'settings' => $settings,
923 ],
924 ],
925 ],
926 ],
927 ],
928 ];
929 }
930
931 /**
932 * @since 2.1.3
933 * @access public
934 */
935 public function print_elements_with_wrapper( $elements_data = null ) {
936 if ( ! $elements_data ) {
937 $elements_data = $this->get_elements_data();
938 }
939
940 $is_dom_optimization_active = Plugin::$instance->experiments->is_feature_active( 'e_dom_optimization' );
941 ?>
942 <div <?php echo Utils::render_html_attributes( $this->get_container_attributes() ); ?>>
943 <?php if ( ! $is_dom_optimization_active ) { ?>
944 <div class="elementor-inner">
945 <?php } ?>
946 <div class="elementor-section-wrap">
947 <?php $this->print_elements( $elements_data ); ?>
948 </div>
949 <?php if ( ! $is_dom_optimization_active ) { ?>
950 </div>
951 <?php } ?>
952 </div>
953 <?php
954 }
955
956 /**
957 * @since 2.0.0
958 * @access public
959 */
960 public function get_css_wrapper_selector() {
961 return '';
962 }
963
964 /**
965 * @since 2.0.0
966 * @access public
967 */
968 public function get_panel_page_settings() {
969 return [
970 /* translators: %s: Document title */
971 'title' => sprintf( __( '%s Settings', 'elementor' ), static::get_title() ),
972 ];
973 }
974
975 /**
976 * @since 2.0.0
977 * @access public
978 */
979 public function get_post() {
980 return $this->post;
981 }
982
983 /**
984 * @since 2.0.0
985 * @access public
986 */
987 public function get_permalink() {
988 return get_permalink( $this->get_main_id() );
989 }
990
991 /**
992 * @since 2.0.8
993 * @access public
994 */
995 public function get_content( $with_css = false ) {
996 return Plugin::$instance->frontend->get_builder_content( $this->post->ID, $with_css );
997 }
998
999 /**
1000 * @since 2.0.0
1001 * @access public
1002 */
1003 public function delete() {
1004 if ( 'revision' === $this->post->post_type ) {
1005 $deleted = wp_delete_post_revision( $this->post );
1006 } else {
1007 $deleted = wp_delete_post( $this->post->ID );
1008 }
1009
1010 return $deleted && ! is_wp_error( $deleted );
1011 }
1012
1013 /**
1014 * Save editor elements.
1015 *
1016 * Save data from the editor to the database.
1017 *
1018 * @since 2.0.0
1019 * @access protected
1020 *
1021 * @param array $elements
1022 */
1023 protected function save_elements( $elements ) {
1024 $editor_data = $this->get_elements_raw_data( $elements );
1025
1026 // We need the `wp_slash` in order to avoid the unslashing during the `update_post_meta`
1027 $json_value = wp_slash( wp_json_encode( $editor_data ) );
1028
1029 // Don't use `update_post_meta` that can't handle `revision` post type
1030 $is_meta_updated = update_metadata( 'post', $this->post->ID, '_elementor_data', $json_value );
1031
1032 /**
1033 * Before saving data.
1034 *
1035 * Fires before Elementor saves data to the database.
1036 *
1037 * @since 1.0.0
1038 *
1039 * @param string $status Post status.
1040 * @param int|bool $is_meta_updated Meta ID if the key didn't exist, true on successful update, false on failure.
1041 */
1042 do_action( 'elementor/db/before_save', $this->post->post_status, $is_meta_updated );
1043
1044 Plugin::$instance->db->save_plain_text( $this->post->ID );
1045
1046 /**
1047 * After saving data.
1048 *
1049 * Fires after Elementor saves data to the database.
1050 *
1051 * @since 1.0.0
1052 *
1053 * @param int $post_id The ID of the post.
1054 * @param array $editor_data Sanitize posted data.
1055 */
1056 do_action( 'elementor/editor/after_save', $this->post->ID, $editor_data );
1057 }
1058
1059 /**
1060 * @since 2.0.0
1061 * @access public
1062 *
1063 * @param int $user_id Optional. User ID. Default value is `0`.
1064 *
1065 * @return bool|int
1066 */
1067 public function get_autosave_id( $user_id = 0 ) {
1068 if ( ! $user_id ) {
1069 $user_id = get_current_user_id();
1070 }
1071
1072 $autosave = Utils::get_post_autosave( $this->post->ID, $user_id );
1073 if ( $autosave ) {
1074 return $autosave->ID;
1075 }
1076
1077 return false;
1078 }
1079
1080 public function save_version() {
1081 if ( ! defined( 'IS_ELEMENTOR_UPGRADE' ) ) {
1082 // Save per revision.
1083 $this->update_meta( '_elementor_version', ELEMENTOR_VERSION );
1084
1085 /**
1086 * Document version save.
1087 *
1088 * Fires when document version is saved on Elementor.
1089 * Will not fire during Elementor Upgrade.
1090 *
1091 * @since 2.5.12
1092 *
1093 * @param \Elementor\Core\Base\Document $this The current document.
1094 *
1095 */
1096 do_action( 'elementor/document/save_version', $this );
1097 }
1098 }
1099
1100 /**
1101 * @since 2.3.0
1102 * @access public
1103 */
1104 public function save_template_type() {
1105 return $this->update_main_meta( self::TYPE_META_KEY, $this->get_name() );
1106 }
1107
1108 /**
1109 * @since 2.3.0
1110 * @access public
1111 */
1112 public function get_template_type() {
1113 return $this->get_main_meta( self::TYPE_META_KEY );
1114 }
1115
1116 /**
1117 * @since 2.0.0
1118 * @access public
1119 *
1120 * @param string $key Meta data key.
1121 *
1122 * @return mixed
1123 */
1124 public function get_main_meta( $key ) {
1125 return get_post_meta( $this->get_main_id(), $key, true );
1126 }
1127
1128 /**
1129 * @since 2.0.4
1130 * @access public
1131 *
1132 * @param string $key Meta data key.
1133 * @param string $value Meta data value.
1134 *
1135 * @return bool|int
1136 */
1137 public function update_main_meta( $key, $value ) {
1138 return update_post_meta( $this->get_main_id(), $key, $value );
1139 }
1140
1141 /**
1142 * @since 2.0.4
1143 * @access public
1144 *
1145 * @param string $key Meta data key.
1146 * @param string $value Optional. Meta data value. Default is an empty string.
1147 *
1148 * @return bool
1149 */
1150 public function delete_main_meta( $key, $value = '' ) {
1151 return delete_post_meta( $this->get_main_id(), $key, $value );
1152 }
1153
1154 /**
1155 * @since 2.0.0
1156 * @access public
1157 *
1158 * @param string $key Meta data key.
1159 *
1160 * @return mixed
1161 */
1162 public function get_meta( $key ) {
1163 return get_post_meta( $this->post->ID, $key, true );
1164 }
1165
1166 /**
1167 * @since 2.0.0
1168 * @access public
1169 *
1170 * @param string $key Meta data key.
1171 * @param mixed $value Meta data value.
1172 *
1173 * @return bool|int
1174 */
1175 public function update_meta( $key, $value ) {
1176 // Use `update_metadata` in order to work also with revisions.
1177 return update_metadata( 'post', $this->post->ID, $key, $value );
1178 }
1179
1180 /**
1181 * @since 2.0.3
1182 * @access public
1183 *
1184 * @param string $key Meta data key.
1185 * @param string $value Meta data value.
1186 *
1187 * @return bool
1188 */
1189 public function delete_meta( $key, $value = '' ) {
1190 // Use `delete_metadata` in order to work also with revisions.
1191 return delete_metadata( 'post', $this->post->ID, $key, $value );
1192 }
1193
1194 /**
1195 * @since 2.0.0
1196 * @access public
1197 */
1198 public function get_last_edited() {
1199 $post = $this->post;
1200 $autosave_post = $this->get_autosave();
1201
1202 if ( $autosave_post ) {
1203 $post = $autosave_post->get_post();
1204 }
1205
1206 $date = date_i18n( _x( 'M j, H:i', 'revision date format', 'elementor' ), strtotime( $post->post_modified ) );
1207 $display_name = get_the_author_meta( 'display_name', $post->post_author );
1208
1209 if ( $autosave_post || 'revision' === $post->post_type ) {
1210 /* translators: 1: Saving date, 2: Author display name */
1211 $last_edited = sprintf( __( 'Draft saved on %1$s by %2$s', 'elementor' ), '<time>' . $date . '</time>', $display_name );
1212 } else {
1213 /* translators: 1: Editing date, 2: Author display name */
1214 $last_edited = sprintf( __( 'Last edited on %1$s by %2$s', 'elementor' ), '<time>' . $date . '</time>', $display_name );
1215 }
1216
1217 return $last_edited;
1218 }
1219
1220
1221 /**
1222 * @return bool
1223 */
1224 public function is_saving() {
1225 return $this->is_saving;
1226 }
1227
1228 /**
1229 * @param $is_saving
1230 *
1231 * @return $this
1232 */
1233 public function set_is_saving( $is_saving ) {
1234 $this->is_saving = $is_saving;
1235
1236 return $this;
1237 }
1238
1239 /**
1240 * @since 2.0.0
1241 * @access public
1242 *
1243 * @param array $data
1244 *
1245 * @throws \Exception If the post does not exist.
1246 */
1247 public function __construct( array $data = [] ) {
1248 if ( $data ) {
1249 if ( empty( $data['post_id'] ) ) {
1250 $this->post = new \WP_Post( (object) [] );
1251 } else {
1252 $this->post = get_post( $data['post_id'] );
1253
1254 if ( ! $this->post ) {
1255 throw new \Exception( sprintf( 'Post ID #%s does not exist.', $data['post_id'] ), Exceptions::NOT_FOUND );
1256 }
1257 }
1258
1259 // Each Control_Stack is based on a unique ID.
1260 $data['id'] = $data['post_id'];
1261
1262 if ( ! isset( $data['settings'] ) ) {
1263 $data['settings'] = [];
1264 }
1265
1266 $saved_settings = get_post_meta( $this->post->ID, '_elementor_page_settings', true );
1267 if ( ! empty( $saved_settings ) && is_array( $saved_settings ) ) {
1268 $data['settings'] += $saved_settings;
1269 }
1270 }
1271
1272 parent::__construct( $data );
1273 }
1274
1275 protected function get_remote_library_config() {
1276 $config = [
1277 'type' => 'block',
1278 'default_route' => 'templates/blocks',
1279 'category' => $this->get_name(),
1280 'autoImportSettings' => false,
1281 ];
1282
1283 return $config;
1284 }
1285
1286 /**
1287 * @since 2.0.4
1288 * @access protected
1289 *
1290 * @param $settings
1291 */
1292 protected function save_settings( $settings ) {
1293 $page_settings_manager = SettingsManager::get_settings_managers( 'page' );
1294 $page_settings_manager->ajax_before_save_settings( $settings, $this->post->ID );
1295 $page_settings_manager->save_settings( $settings, $this->post->ID );
1296 }
1297
1298 /**
1299 * @since 2.1.3
1300 * @access protected
1301 */
1302 protected function print_elements( $elements_data ) {
1303 foreach ( $elements_data as $element_data ) {
1304 $element = Plugin::$instance->elements_manager->create_element_instance( $element_data );
1305
1306 if ( ! $element ) {
1307 continue;
1308 }
1309
1310 $element->print_element();
1311 }
1312 }
1313
1314 protected function register_document_controls() {
1315 $this->start_controls_section(
1316 'document_settings',
1317 [
1318 'label' => __( 'General Settings', 'elementor' ),
1319 'tab' => Controls_Manager::TAB_SETTINGS,
1320 ]
1321 );
1322
1323 $this->add_control(
1324 'post_title',
1325 [
1326 'label' => __( 'Title', 'elementor' ),
1327 'type' => Controls_Manager::TEXT,
1328 'default' => $this->post->post_title,
1329 'label_block' => true,
1330 'separator' => 'none',
1331 ]
1332 );
1333
1334 $post_type_object = get_post_type_object( $this->post->post_type );
1335
1336 $can_publish = $post_type_object && current_user_can( $post_type_object->cap->publish_posts );
1337 $is_published = self::STATUS_PUBLISH === $this->post->post_status || self::STATUS_PRIVATE === $this->post->post_status;
1338
1339 if ( $is_published || $can_publish || ! Plugin::$instance->editor->is_edit_mode() ) {
1340
1341 $statuses = $this->get_post_statuses();
1342 if ( 'future' === $this->get_main_post()->post_status ) {
1343 $statuses['future'] = __( 'Future', 'elementor' );
1344 }
1345
1346 $this->add_control(
1347 'post_status',
1348 [
1349 'label' => __( 'Status', 'elementor' ),
1350 'type' => Controls_Manager::SELECT,
1351 'default' => $this->get_main_post()->post_status,
1352 'options' => $statuses,
1353 ]
1354 );
1355 }
1356
1357 $this->end_controls_section();
1358 }
1359
1360 protected function get_post_statuses() {
1361 return get_post_statuses();
1362 }
1363
1364 protected function get_have_a_look_url() {
1365 return $this->get_permalink();
1366 }
1367
1368 public function handle_revisions_changed( $post_has_changed, $last_revision, $post ) {
1369 // In case default, didn't determine the changes.
1370 if ( ! $post_has_changed ) {
1371 $last_revision_id = $last_revision->ID;
1372 $last_revision_document = Plugin::instance()->documents->get( $last_revision_id );
1373 $post_document = Plugin::instance()->documents->get( $post->ID );
1374
1375 $last_revision_settings = $last_revision_document->get_settings();
1376 $post_settings = $post_document->get_settings();
1377
1378 // TODO: Its better to add crc32 signature for each revision and then only compare one part of the checksum.
1379 $post_has_changed = $last_revision_settings !== $post_settings;
1380 }
1381
1382 return $post_has_changed;
1383 }
1384
1385 private function add_handle_revisions_changed_filter() {
1386 add_filter( 'wp_save_post_revision_post_has_changed', [ $this, 'handle_revisions_changed' ], 10, 3 );
1387 }
1388
1389 private function remove_handle_revisions_changed_filter() {
1390 remove_filter( 'wp_save_post_revision_post_has_changed', [ $this, 'handle_revisions_changed' ] );
1391 }
1392 }
1393