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

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

1,887 lines 45.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\Base\Elements_Iteration_Actions\Assets as Assets_Iteration_Action;
5 use Elementor\Core\Base\Elements_Iteration_Actions\Base as Elements_Iteration_Action;
6 use Elementor\Core\Behaviors\Interfaces\Lock_Behavior;
7 use Elementor\Core\Files\CSS\Post as Post_CSS;
8 use Elementor\Core\Settings\Page\Model as Page_Model;
9 use Elementor\Core\Utils\Exceptions;
10 use Elementor\Includes\Elements\Container;
11 use Elementor\Plugin;
12 use Elementor\Controls_Manager;
13 use Elementor\Controls_Stack;
14 use Elementor\TemplateLibrary\Source_Local;
15 use Elementor\User;
16 use Elementor\Core\Settings\Manager as SettingsManager;
17 use Elementor\Utils;
18 use Elementor\Widget_Base;
19 use Elementor\Core\Settings\Page\Manager as PageManager;
20 use ElementorPro\Modules\Library\Widgets\Template;
21
22 if ( ! defined( 'ABSPATH' ) ) {
23 exit; // Exit if accessed directly
24 }
25
26 /**
27 * Elementor document.
28 *
29 * An abstract class that provides the needed properties and methods to
30 * manage and handle documents in inheriting classes.
31 *
32 * @since 2.0.0
33 * @abstract
34 */
35 abstract class Document extends Controls_Stack {
36
37 /**
38 * Document type meta key.
39 */
40 const TYPE_META_KEY = '_elementor_template_type';
41 const PAGE_META_KEY = '_elementor_page_settings';
42
43 const BUILT_WITH_ELEMENTOR_META_KEY = '_elementor_edit_mode';
44
45 /**
46 * Document publish status.
47 */
48 const STATUS_PUBLISH = 'publish';
49
50 /**
51 * Document draft status.
52 */
53 const STATUS_DRAFT = 'draft';
54
55 /**
56 * Document private status.
57 */
58 const STATUS_PRIVATE = 'private';
59
60 /**
61 * Document autosave status.
62 */
63 const STATUS_AUTOSAVE = 'autosave';
64
65 /**
66 * Document pending status.
67 */
68 const STATUS_PENDING = 'pending';
69
70
71 private $main_id;
72
73 /**
74 * @var bool
75 */
76 private $is_saving = false;
77
78 private static $properties = [];
79
80 /**
81 * @var Elements_Iteration_Action[]
82 */
83 private $elements_iteration_actions = [];
84
85 /**
86 * Document post data.
87 *
88 * Holds the document post data.
89 *
90 * @since 2.0.0
91 * @access protected
92 *
93 * @var \WP_Post WordPress post data.
94 */
95 protected $post;
96
97 /**
98 * @param array $internal_elements
99 *
100 * @return array[]
101 */
102 private function get_container_elements_data( array $internal_elements ): array {
103 return [
104 [
105 'id' => Utils::generate_random_string(),
106 'elType' => 'container',
107 'elements' => $internal_elements,
108 ],
109 ];
110 }
111
112 /**
113 * @param array $internal_elements
114 *
115 * @return array[]
116 */
117 private function get_sections_elements_data( array $internal_elements ): array {
118 return [
119 [
120 'id' => Utils::generate_random_string(),
121 'elType' => 'section',
122 'elements' => [
123 [
124 'id' => Utils::generate_random_string(),
125 'elType' => 'column',
126 'elements' => $internal_elements,
127 ],
128 ],
129 ],
130 ];
131 }
132
133 /**
134 * @since 2.1.0
135 * @access protected
136 * @static
137 */
138 protected static function get_editor_panel_categories() {
139 return Plugin::$instance->elements_manager->get_categories();
140 }
141
142 /**
143 * Get properties.
144 *
145 * Retrieve the document properties.
146 *
147 * @since 2.0.0
148 * @access public
149 * @static
150 *
151 * @return array Document properties.
152 */
153 public static function get_properties() {
154 return [
155 'has_elements' => true,
156 'is_editable' => true,
157 'edit_capability' => '',
158 'show_in_finder' => true,
159 'show_on_admin_bar' => true,
160 'support_kit' => false,
161 ];
162 }
163
164 /**
165 * @since 2.1.0
166 * @access public
167 * @static
168 */
169 public static function get_editor_panel_config() {
170 $default_route = 'panel/elements/categories';
171
172 if ( ! Plugin::instance()->role_manager->user_can( 'design' ) ) {
173 $default_route = 'panel/page-settings/settings';
174 }
175
176 return [
177 'title' => static::get_title(), // JS Container title.
178 'widgets_settings' => [],
179 'elements_categories' => static::get_editor_panel_categories(),
180 'default_route' => $default_route,
181 'has_elements' => static::get_property( 'has_elements' ),
182 'support_kit' => static::get_property( 'support_kit' ),
183 'messages' => [
184 /* translators: %s: Document title. */
185 'publish_notification' => sprintf( esc_html__( 'Hurray! Your %s is live.', 'elementor' ), static::get_title() ),
186 ],
187 ];
188 }
189
190 /**
191 * Get element title.
192 *
193 * Retrieve the element title.
194 *
195 * @since 2.0.0
196 * @access public
197 * @static
198 *
199 * @return string Element title.
200 */
201 public static function get_title() {
202 return esc_html__( 'Document', 'elementor' );
203 }
204
205 public static function get_plural_title() {
206 return static::get_title();
207 }
208
209 public static function get_add_new_title() {
210 return sprintf( esc_html__( 'Add New %s', 'elementor' ), static::get_title() );
211 }
212
213 /**
214 * Get property.
215 *
216 * Retrieve the document property.
217 *
218 * @since 2.0.0
219 * @access public
220 * @static
221 *
222 * @param string $key The property key.
223 *
224 * @return mixed The property value.
225 */
226 public static function get_property( $key ) {
227 $id = static::get_class_full_name();
228
229 if ( ! isset( self::$properties[ $id ] ) ) {
230 self::$properties[ $id ] = static::get_properties();
231 }
232
233 return self::get_items( self::$properties[ $id ], $key );
234 }
235
236 /**
237 * @since 2.0.0
238 * @access public
239 * @static
240 */
241 public static function get_class_full_name() {
242 return get_called_class();
243 }
244
245 public static function get_create_url() {
246 $properties = static::get_properties();
247
248 // BC Support - Each document should define it own CPT this code is for BC support.
249 $cpt = Source_Local::CPT;
250
251 if ( isset( $properties['cpt'][0] ) ) {
252 $cpt = $properties['cpt'][0];
253 }
254
255 return Plugin::$instance->documents->get_create_new_post_url( $cpt, static::get_type() );
256 }
257
258 public function get_name() {
259 return static::get_type();
260 }
261
262 /**
263 * @since 2.0.0
264 * @access public
265 */
266 public function get_unique_name() {
267 return static::get_type() . '-' . $this->post->ID;
268 }
269
270 /**
271 * @since 2.3.0
272 * @access public
273 */
274 public function get_post_type_title() {
275 $post_type_object = get_post_type_object( $this->post->post_type );
276
277 return $post_type_object->labels->singular_name;
278 }
279
280 /**
281 * @since 2.0.0
282 * @access public
283 */
284 public function get_main_id() {
285 if ( ! $this->main_id ) {
286 $post_id = $this->post->ID;
287
288 $parent_post_id = wp_is_post_revision( $post_id );
289
290 if ( $parent_post_id ) {
291 $post_id = $parent_post_id;
292 }
293
294 $this->main_id = $post_id;
295 }
296
297 return $this->main_id;
298 }
299
300 /**
301 * @return null|Lock_Behavior
302 */
303 public static function get_lock_behavior_v2() {
304 return null;
305 }
306
307 /**
308 * @since 2.0.0
309 * @access public
310 *
311 * @param $data
312 *
313 * @throws \Exception If the widget was not found.
314 *
315 * @return string
316 */
317 public function render_element( $data ) {
318 // Start buffering
319 ob_start();
320
321 /** @var Widget_Base $widget */
322 $widget = Plugin::$instance->elements_manager->create_element_instance( $data );
323
324 if ( ! $widget ) {
325 throw new \Exception( 'Widget not found.' );
326 }
327
328 $widget->render_content();
329
330 $render_html = ob_get_clean();
331
332 return $render_html;
333 }
334
335 /**
336 * @since 2.0.0
337 * @access public
338 */
339 public function get_main_post() {
340 return get_post( $this->get_main_id() );
341 }
342
343 public function get_container_attributes() {
344 $id = $this->get_main_id();
345
346 $attributes = [
347 'data-elementor-type' => $this->get_name(),
348 'data-elementor-id' => $id,
349 'class' => 'elementor elementor-' . $id,
350 ];
351
352 $version_meta = $this->get_main_meta( '_elementor_version' );
353
354 if ( version_compare( $version_meta, '2.5.0', '<' ) ) {
355 $attributes['class'] .= ' elementor-bc-flex-widget';
356 }
357
358 if ( Plugin::$instance->preview->is_preview() ) {
359 $attributes['data-elementor-title'] = static::get_title();
360 } else {
361 $elementor_settings = $this->get_frontend_settings();
362 if ( ! empty( $elementor_settings ) ) {
363 $attributes['data-elementor-settings'] = wp_json_encode( $elementor_settings );
364 }
365 }
366
367 // apply this filter to allow the attributes to be modified by different sources
368 return apply_filters( 'elementor/document/wrapper_attributes', $attributes, $this );
369 }
370
371 /**
372 * @since 2.0.0
373 * @access public
374 */
375 public function get_wp_preview_url() {
376 $main_post_id = $this->get_main_id();
377 $document = $this;
378
379 // Ajax request from editor.
380 $initial_document_id = Utils::get_super_global_value( $_POST, 'initial_document_id' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
381
382 if ( ! empty( $initial_document_id ) ) {
383 $document = Plugin::$instance->documents->get( $initial_document_id ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
384 }
385
386 $url = get_preview_post_link(
387 $document->get_main_id(),
388 [
389 'preview_id' => $main_post_id,
390 'preview_nonce' => wp_create_nonce( 'post_preview_' . $main_post_id ),
391 ]
392 );
393
394 /**
395 * Document "WordPress preview" URL.
396 *
397 * Filters the WordPress preview URL.
398 *
399 * @since 2.0.0
400 *
401 * @param string $url WordPress preview URL.
402 * @param Document $this The document instance.
403 */
404 $url = apply_filters( 'elementor/document/urls/wp_preview', $url, $this );
405
406 return $url;
407 }
408
409 /**
410 * @since 2.0.0
411 * @access public
412 */
413 public function get_exit_to_dashboard_url() {
414 $url = get_edit_post_link( $this->get_main_id(), 'raw' );
415
416 /**
417 * Document "exit to dashboard" URL.
418 *
419 * Filters the "Exit To Dashboard" URL.
420 *
421 * @since 2.0.0
422 *
423 * @param string $url The exit URL
424 * @param Document $this The document instance.
425 */
426 $url = apply_filters( 'elementor/document/urls/exit_to_dashboard', $url, $this );
427
428 return $url;
429 }
430
431 /**
432 * Get All Post Type URL
433 *
434 * Get url of the page which display all the posts of the current active document's post type.
435 *
436 * @since 3.7.0
437 *
438 * @return string $url
439 */
440 public function get_all_post_type_url() {
441 $post_type = get_post_type( $this->get_main_id() );
442
443 $url = get_admin_url() . 'edit.php';
444
445 if ( 'post' !== $post_type ) {
446 $url .= '?post_type=' . $post_type;
447 }
448
449 /**
450 * Document "display all post type" URL.
451 *
452 * @since 3.7.0
453 *
454 * @param string $url The URL.
455 * @param Document $this The document instance.
456 */
457 $url = apply_filters( 'elementor/document/urls/all_post_type', $url, $this );
458
459 return $url;
460 }
461
462 /**
463 * Get Main WP dashboard URL.
464 *
465 * @since 3.7.0
466 *
467 * @return string $url
468 */
469 protected function get_main_dashboard_url() {
470 $url = get_dashboard_url();
471
472 /**
473 * Document "Main Dashboard" URL.
474 *
475 * @since 3.7.0
476 *
477 * @param string $url The URL.
478 * @param Document $this The document instance.
479 */
480 $url = apply_filters( 'elementor/document/urls/main_dashboard', $url, $this );
481
482 return $url;
483 }
484
485 /**
486 * Get auto-saved post revision.
487 *
488 * Retrieve the auto-saved post revision that is newer than current post.
489 *
490 * @since 2.0.0
491 * @access public
492 *
493 *
494 * @return bool|Document
495 */
496
497 public function get_newer_autosave() {
498 $autosave = $this->get_autosave();
499
500 // Detect if there exists an autosave newer than the post.
501 if ( $autosave && mysql2date( 'U', $autosave->get_post()->post_modified_gmt, false ) > mysql2date( 'U', $this->post->post_modified_gmt, false ) ) {
502 return $autosave;
503 }
504
505 return false;
506 }
507
508 /**
509 * @since 2.0.0
510 * @access public
511 */
512 public function is_autosave() {
513 return wp_is_post_autosave( $this->post->ID );
514 }
515
516 /**
517 * Check if the current document is a 'revision'
518 *
519 * @return bool
520 */
521 public function is_revision() {
522 return 'revision' === $this->post->post_type;
523 }
524
525 /**
526 * Checks if the current document status is 'trash'.
527 *
528 * @return bool
529 */
530 public function is_trash() {
531 return 'trash' === $this->post->post_status;
532 }
533
534 /**
535 * @since 2.0.0
536 * @access public
537 *
538 * @param int $user_id
539 * @param bool $create
540 *
541 * @return bool|Document
542 */
543 public function get_autosave( $user_id = 0, $create = false ) {
544 if ( ! $user_id ) {
545 $user_id = get_current_user_id();
546 }
547
548 $autosave_id = $this->get_autosave_id( $user_id );
549
550 if ( $autosave_id ) {
551 $document = Plugin::$instance->documents->get( $autosave_id );
552 } elseif ( $create ) {
553 $autosave_id = wp_create_post_autosave( [
554 'post_ID' => $this->post->ID,
555 'post_type' => $this->post->post_type,
556 'post_title' => $this->post->post_title,
557 'post_excerpt' => $this->post->post_excerpt,
558 // Hack to cause $autosave_is_different=true in `wp_create_post_autosave`.
559 'post_content' => '<!-- Created With Elementor -->',
560 'post_modified' => current_time( 'mysql' ),
561 ] );
562
563 Plugin::$instance->db->copy_elementor_meta( $this->post->ID, $autosave_id );
564
565 $document = Plugin::$instance->documents->get( $autosave_id );
566 $document->save_template_type();
567 } else {
568 $document = false;
569 }
570
571 return $document;
572 }
573
574 /**
575 * Add/Remove edit link in dashboard.
576 *
577 * Add or remove an edit link to the post/page action links on the post/pages list table.
578 *
579 * Fired by `post_row_actions` and `page_row_actions` filters.
580 *
581 * @access public
582 *
583 * @param array $actions An array of row action links.
584 *
585 * @return array An updated array of row action links.
586 */
587 public function filter_admin_row_actions( $actions ) {
588 if ( $this->is_built_with_elementor() && $this->is_editable_by_current_user() ) {
589 $actions['edit_with_elementor'] = sprintf(
590 '<a href="%1$s">%2$s</a>',
591 $this->get_edit_url(),
592 __( 'Edit with Elementor', 'elementor' )
593 );
594 }
595
596 return $actions;
597 }
598
599 /**
600 * @since 2.0.0
601 * @access public
602 */
603 public function is_editable_by_current_user() {
604 $edit_capability = static::get_property( 'edit_capability' );
605 if ( $edit_capability && ! current_user_can( $edit_capability ) ) {
606 return false;
607 }
608
609 return self::get_property( 'is_editable' ) && User::is_current_user_can_edit( $this->get_main_id() );
610 }
611
612 /**
613 * @since 2.9.0
614 * @access protected
615 */
616 protected function get_initial_config() {
617 // 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.
618
619 $locked_user = Plugin::$instance->editor->get_locked_user( $this->get_main_id() );
620
621 if ( $locked_user ) {
622 $locked_user = $locked_user->display_name;
623 }
624
625 $post = $this->get_main_post();
626
627 $post_type_object = get_post_type_object( $post->post_type );
628
629 $settings = SettingsManager::get_settings_managers_config();
630
631 $config = [
632 'id' => $this->get_main_id(),
633 'type' => $this->get_name(),
634 'version' => $this->get_main_meta( '_elementor_version' ),
635 'settings' => $settings['page'],
636 'remoteLibrary' => $this->get_remote_library_config(),
637 'last_edited' => $this->get_last_edited(),
638 'panel' => static::get_editor_panel_config(),
639 'container' => 'body',
640 'post_type_title' => $this->get_post_type_title(),
641 'user' => [
642 'can_publish' => current_user_can( $post_type_object->cap->publish_posts ),
643
644 // Deprecated config since 2.9.0.
645 'locked' => $locked_user,
646 ],
647 'urls' => [
648 'exit_to_dashboard' => $this->get_exit_to_dashboard_url(), // WP post type edit page
649 'all_post_type' => $this->get_all_post_type_url(),
650 'preview' => $this->get_preview_url(),
651 'wp_preview' => $this->get_wp_preview_url(),
652 'permalink' => $this->get_permalink(),
653 'have_a_look' => $this->get_have_a_look_url(),
654 'main_dashboard' => $this->get_main_dashboard_url(),
655 ],
656 ];
657
658 $post_status_object = get_post_status_object( $post->post_status );
659
660 if ( $post_status_object ) {
661 $config['status'] = [
662 'value' => $post_status_object->name,
663 'label' => $post_status_object->label,
664 ];
665 }
666
667 do_action( 'elementor/document/before_get_config', $this );
668
669 if ( static::get_property( 'has_elements' ) ) {
670 $container_config = [];
671 $experiments_manager = Plugin::$instance->experiments;
672
673 if ( $experiments_manager->is_feature_active( 'container' ) ) {
674 $container_config = [
675 'container' => Plugin::$instance->elements_manager->get_element_types( 'container' )->get_config(),
676 ];
677 }
678
679 $config['elements'] = $this->get_elements_raw_data( null, true );
680 $config['widgets'] = $container_config + Plugin::$instance->widgets_manager->get_widget_types_config();
681 }
682
683 $additional_config = [];
684
685 /**
686 * Additional document configuration.
687 *
688 * Filters the document configuration by adding additional configuration.
689 * External developers can use this hook to add custom configuration in
690 * addition to Elementor's initial configuration.
691 *
692 * Use the $post_id to add custom configuration for different pages.
693 *
694 * @param array $additional_config The additional document configuration.
695 * @param int $post_id The post ID of the document.
696 */
697 $additional_config = apply_filters( 'elementor/document/config', $additional_config, $this->get_main_id() );
698
699 if ( ! empty( $additional_config ) ) {
700 $config = array_replace_recursive( $config, $additional_config );
701 }
702
703 return $config;
704 }
705
706 /**
707 * @since 3.1.0
708 * @access protected
709 */
710 protected function register_controls() {
711 $this->register_document_controls();
712
713 /**
714 * Register document controls.
715 *
716 * Fires after Elementor registers the document controls.
717 *
718 * External developers can use this hook to add new controls to the document.
719 *
720 * @since 2.0.0
721 *
722 * @param Document $this The document instance.
723 */
724 do_action( 'elementor/documents/register_controls', $this );
725 }
726
727 /**
728 * @since 2.0.0
729 * @access public
730 *
731 * @param $data
732 *
733 * @return bool
734 */
735 public function save( $data ) {
736 /**
737 * Set locale to "C" to avoid issues with comma as decimal separator.
738 *
739 * @see https://github.com/elementor/elementor/issues/10992
740 */
741 $original_lc = setlocale( LC_NUMERIC, 0 );
742 setlocale( LC_NUMERIC, 'C' );
743
744 /**
745 * Document save data.
746 *
747 * Filter the document data before saving process starts.
748 *
749 * External developers can use this hook to change the data before
750 * saving it to the database.
751 *
752 * @since 3.3.0
753 *
754 * @param array $data The document data.
755 * @param \Elementor\Core\Base\Document $this The document instance.
756 */
757 $data = apply_filters( 'elementor/document/save/data', $data, $this );
758
759 $this->add_handle_revisions_changed_filter();
760
761 if ( ! $this->is_editable_by_current_user() ) {
762 return false;
763 }
764
765 $this->set_is_saving( true );
766
767 /**
768 * Before document save.
769 *
770 * Fires when document save starts on Elementor.
771 *
772 * @since 2.5.12
773 *
774 * @param \Elementor\Core\Base\Document $this The current document.
775 * @param $data.
776 */
777 do_action( 'elementor/document/before_save', $this, $data );
778
779 if ( ! current_user_can( 'unfiltered_html' ) ) {
780 $data = wp_kses_post_deep( $data );
781 }
782
783 if ( ! empty( $data['settings'] ) ) {
784 if ( isset( $data['settings']['post_status'] ) && self::STATUS_AUTOSAVE === $data['settings']['post_status'] ) {
785 if ( ! defined( 'DOING_AUTOSAVE' ) ) {
786 define( 'DOING_AUTOSAVE', true );
787 }
788 }
789
790 $this->save_settings( $data['settings'] );
791
792 $this->refresh_post();
793 }
794
795 // Don't check is_empty, because an empty array should be saved.
796 if ( isset( $data['elements'] ) && is_array( $data['elements'] ) ) {
797 $this->save_elements( $data['elements'] );
798 }
799
800 $this->save_template_type();
801
802 $this->save_version();
803
804 // Remove Post CSS
805 $post_css = Post_CSS::create( $this->post->ID );
806
807 $post_css->delete();
808
809 /**
810 * After document save.
811 *
812 * Fires when document save is complete.
813 *
814 * @since 2.5.12
815 *
816 * @param \Elementor\Core\Base\Document $this The current document.
817 * @param $data.
818 */
819 do_action( 'elementor/document/after_save', $this, $data );
820
821 $this->set_is_saving( false );
822
823 $this->remove_handle_revisions_changed_filter();
824
825 setlocale( LC_NUMERIC, $original_lc );
826
827 return true;
828 }
829
830 public function refresh_post() {
831 $this->post = get_post( $this->post->ID );
832 }
833
834 /**
835 * @param array $new_settings
836 *
837 * @return static
838 */
839 public function update_settings( array $new_settings ) {
840 $document_settings = $this->get_meta( PageManager::META_KEY );
841
842 if ( ! $document_settings ) {
843 $document_settings = [];
844 }
845
846 $this->save_settings(
847 array_replace_recursive( $document_settings, $new_settings )
848 );
849
850 return $this;
851 }
852
853 /**
854 * Is built with Elementor.
855 *
856 * Check whether the post was built with Elementor.
857 *
858 * @since 2.0.0
859 * @access public
860 *
861 * @return bool Whether the post was built with Elementor.
862 */
863 public function is_built_with_elementor() {
864 return ! ! $this->get_meta( self::BUILT_WITH_ELEMENTOR_META_KEY );
865 }
866
867 /**
868 * Mark the post as "built with elementor" or not.
869 *
870 * @param bool $is_built_with_elementor
871 *
872 * @return $this
873 */
874 public function set_is_built_with_elementor( $is_built_with_elementor ) {
875 if ( $is_built_with_elementor ) {
876 // Use the string `builder` and not a boolean for rollback compatibility
877 $this->update_meta( self::BUILT_WITH_ELEMENTOR_META_KEY, 'builder' );
878 } else {
879 $this->delete_meta( self::BUILT_WITH_ELEMENTOR_META_KEY );
880 }
881
882 return $this;
883 }
884
885 /**
886 * @since 2.0.0
887 * @access public
888 * @static
889 *
890 * @return mixed
891 */
892 public function get_edit_url() {
893 $url = add_query_arg(
894 [
895 'post' => $this->get_main_id(),
896 'action' => 'elementor',
897 ],
898 admin_url( 'post.php' )
899 );
900
901 /**
902 * Document edit url.
903 *
904 * Filters the document edit url.
905 *
906 * @since 2.0.0
907 *
908 * @param string $url The edit url.
909 * @param Document $this The document instance.
910 */
911 $url = apply_filters( 'elementor/document/urls/edit', $url, $this );
912
913 return $url;
914 }
915
916 /**
917 * @since 2.0.0
918 * @access public
919 */
920 public function get_preview_url() {
921 /**
922 * Use a static var - to avoid change the `ver` parameter on every call.
923 */
924 static $url;
925
926 if ( empty( $url ) ) {
927
928 add_filter( 'pre_option_permalink_structure', '__return_empty_string' );
929
930 $url = set_url_scheme( add_query_arg( [
931 'elementor-preview' => $this->get_main_id(),
932 'ver' => time(),
933 ], $this->get_permalink() ) );
934
935 remove_filter( 'pre_option_permalink_structure', '__return_empty_string' );
936
937 /**
938 * Document preview URL.
939 *
940 * Filters the document preview URL.
941 *
942 * @since 2.0.0
943 *
944 * @param string $url The preview URL.
945 * @param Document $this The document instance.
946 */
947 $url = apply_filters( 'elementor/document/urls/preview', $url, $this );
948 }
949
950 return $url;
951 }
952
953 /**
954 * @since 2.0.0
955 * @access public
956 *
957 * @param string $key
958 *
959 * @return array
960 */
961 public function get_json_meta( $key ) {
962 $meta = get_post_meta( $this->post->ID, $key, true );
963
964 if ( is_string( $meta ) && ! empty( $meta ) ) {
965 $meta = json_decode( $meta, true );
966 }
967
968 if ( empty( $meta ) ) {
969 $meta = [];
970 }
971
972 return $meta;
973 }
974
975 public function update_json_meta( $key, $value ) {
976 $this->update_meta(
977 $key,
978 // `wp_slash` in order to avoid the unslashing during the `update_post_meta`
979 wp_slash( wp_json_encode( $value ) )
980 );
981 }
982
983 /**
984 * @since 2.0.0
985 * @access public
986 *
987 * @param null $data
988 * @param bool $with_html_content
989 *
990 * @return array
991 */
992 public function get_elements_raw_data( $data = null, $with_html_content = false ) {
993 if ( ! static::get_property( 'has_elements' ) ) {
994 return [];
995 }
996
997 if ( is_null( $data ) ) {
998 $data = $this->get_elements_data();
999 }
1000
1001 // Change the current documents, so widgets can use `documents->get_current` and other post data
1002 Plugin::$instance->documents->switch_to_document( $this );
1003
1004 $editor_data = [];
1005
1006 foreach ( $data as $element_data ) {
1007 if ( ! is_array( $element_data ) ) {
1008 throw new \Exception( 'Invalid data: ' . wp_json_encode( [
1009 'data' => $data,
1010 'element' => $element_data,
1011 ] ) );
1012 }
1013
1014 $element = Plugin::$instance->elements_manager->create_element_instance( $element_data );
1015
1016 if ( ! $element ) {
1017 continue;
1018 }
1019
1020 if ( $this->is_saving ) {
1021 $element_data = $element->get_data_for_save();
1022 } else {
1023 $element_data = $element->get_raw_data( $with_html_content );
1024 }
1025
1026 $editor_data[] = $element_data;
1027 } // End foreach().
1028
1029 Plugin::$instance->documents->restore_document();
1030
1031 return $editor_data;
1032 }
1033
1034 /**
1035 * @since 2.0.0
1036 * @access public
1037 *
1038 * @param string $status
1039 *
1040 * @return array
1041 */
1042 public function get_elements_data( $status = self::STATUS_PUBLISH ) {
1043 $elements = $this->get_json_meta( '_elementor_data' );
1044
1045 if ( self::STATUS_DRAFT === $status ) {
1046 $autosave = $this->get_newer_autosave();
1047
1048 if ( is_object( $autosave ) ) {
1049 $autosave_elements = Plugin::$instance->documents
1050 ->get( $autosave->get_post()->ID )
1051 ->get_json_meta( '_elementor_data' );
1052 }
1053 }
1054
1055 if ( Plugin::$instance->editor->is_edit_mode() ) {
1056 if ( empty( $elements ) && empty( $autosave_elements ) ) {
1057 // Convert to Elementor.
1058 $elements = $this->convert_to_elementor();
1059 if ( $this->is_autosave() ) {
1060 Plugin::$instance->db->copy_elementor_meta( $this->post->post_parent, $this->post->ID );
1061 }
1062 }
1063 }
1064
1065 if ( ! empty( $autosave_elements ) ) {
1066 $elements = $autosave_elements;
1067 }
1068
1069 return $elements;
1070 }
1071
1072 /**
1073 * Get document setting from DB.
1074 *
1075 * @return array
1076 */
1077 public function get_db_document_settings() {
1078 return $this->get_meta( static::PAGE_META_KEY );
1079 }
1080
1081 /**
1082 * @since 2.3.0
1083 * @access public
1084 */
1085 public function convert_to_elementor() {
1086 $this->save( [] );
1087
1088 if ( empty( $this->post->post_content ) ) {
1089 return [];
1090 }
1091
1092 // Check if it's only a shortcode.
1093 preg_match_all( '/' . get_shortcode_regex() . '/', $this->post->post_content, $matches, PREG_SET_ORDER );
1094 if ( ! empty( $matches ) ) {
1095 foreach ( $matches as $shortcode ) {
1096 if ( trim( $this->post->post_content ) === $shortcode[0] ) {
1097 $widget_type = Plugin::$instance->widgets_manager->get_widget_types( 'shortcode' );
1098 $settings = [
1099 'shortcode' => $this->post->post_content,
1100 ];
1101 break;
1102 }
1103 }
1104 }
1105
1106 if ( empty( $widget_type ) ) {
1107 $widget_type = Plugin::$instance->widgets_manager->get_widget_types( 'text-editor' );
1108 $settings = [
1109 'editor' => $this->post->post_content,
1110 ];
1111 }
1112
1113 // TODO: Better coding to start template for editor
1114 $converted_blocks = [
1115 [
1116 'id' => Utils::generate_random_string(),
1117 'elType' => $widget_type::get_type(),
1118 'widgetType' => $widget_type->get_name(),
1119 'settings' => $settings,
1120 ],
1121 ];
1122
1123 return Plugin::$instance->experiments->is_feature_active( 'container' )
1124 ? $this->get_container_elements_data( $converted_blocks )
1125 : $this->get_sections_elements_data( $converted_blocks );
1126 }
1127
1128 /**
1129 * @since 2.1.3
1130 * @access public
1131 */
1132 public function print_elements_with_wrapper( $elements_data = null ) {
1133 if ( ! $elements_data ) {
1134 $elements_data = $this->get_elements_data();
1135 }
1136
1137 $is_dom_optimization_active = Plugin::$instance->experiments->is_feature_active( 'e_dom_optimization' );
1138 ?>
1139 <div <?php Utils::print_html_attributes( $this->get_container_attributes() ); ?>>
1140 <?php if ( ! $is_dom_optimization_active ) : ?>
1141 <div class="elementor-inner">
1142 <div class="elementor-section-wrap">
1143 <?php endif; ?>
1144 <?php $this->print_elements( $elements_data ); ?>
1145 <?php if ( ! $is_dom_optimization_active ) : ?>
1146 </div>
1147 </div>
1148 <?php endif; ?>
1149 </div>
1150 <?php
1151 }
1152
1153 /**
1154 * @since 2.0.0
1155 * @access public
1156 */
1157 public function get_css_wrapper_selector() {
1158 return '';
1159 }
1160
1161 /**
1162 * @since 2.0.0
1163 * @access public
1164 */
1165 public function get_panel_page_settings() {
1166 return [
1167 /* translators: %s: Document title. */
1168 'title' => sprintf( esc_html__( '%s Settings', 'elementor' ), static::get_title() ),
1169 ];
1170 }
1171
1172 /**
1173 * @since 2.0.0
1174 * @access public
1175 */
1176 public function get_post() {
1177 return $this->post;
1178 }
1179
1180 /**
1181 * @since 2.0.0
1182 * @access public
1183 */
1184 public function get_permalink() {
1185 return get_permalink( $this->get_main_id() );
1186 }
1187
1188 /**
1189 * @since 2.0.8
1190 * @access public
1191 */
1192 public function get_content( $with_css = false ) {
1193 return Plugin::$instance->frontend->get_builder_content( $this->post->ID, $with_css );
1194 }
1195
1196 /**
1197 * @since 2.0.0
1198 * @access public
1199 */
1200 public function delete() {
1201 if ( 'revision' === $this->post->post_type ) {
1202 $deleted = wp_delete_post_revision( $this->post );
1203 } else {
1204 $deleted = wp_delete_post( $this->post->ID );
1205 }
1206
1207 return $deleted && ! is_wp_error( $deleted );
1208 }
1209
1210 public function force_delete() {
1211 $deleted = wp_delete_post( $this->post->ID, true );
1212
1213 return $deleted && ! is_wp_error( $deleted );
1214 }
1215
1216 /**
1217 * On import update dynamic content (e.g. post and term IDs).
1218 *
1219 * @since 3.8.0
1220 *
1221 * @param array $config The config of the passed element.
1222 * @param array $data The data that requires updating/replacement when imported.
1223 * @param array|null $controls The available controls.
1224 *
1225 * @return array Element data.
1226 */
1227 public static function on_import_update_dynamic_content( array $config, array $data, $controls = null ) : array {
1228 foreach ( $config as &$element_config ) {
1229 $element_instance = Plugin::$instance->elements_manager->create_element_instance( $element_config );
1230
1231 if ( is_null( $element_instance ) ) {
1232 continue;
1233 }
1234
1235 if ( $element_instance->has_own_method( 'on_import_replace_dynamic_content' ) ) {
1236 // TODO: Remove this check in the future.
1237 $element_config = $element_instance::on_import_replace_dynamic_content( $element_config, $data['post_ids'] );
1238 } else {
1239 $element_config = $element_instance::on_import_update_dynamic_content( $element_config, $data, $element_instance->get_controls() );
1240 }
1241
1242 $element_config['elements'] = static::on_import_update_dynamic_content( $element_config['elements'], $data );
1243 }
1244
1245 return $config;
1246 }
1247
1248 /**
1249 * Update dynamic settings in the document for import.
1250 *
1251 * @param array $settings The settings of the document.
1252 * @param array $config Import config to update the settings.
1253 *
1254 * @return array
1255 */
1256 public function on_import_update_settings( array $settings, array $config ): array {
1257 $controls = $this->get_controls();
1258 $controls_manager = Plugin::$instance->controls_manager;
1259
1260 foreach ( $settings as $key => $value ) {
1261
1262 if ( ! isset( $controls[ $key ] ) ) {
1263 continue;
1264 }
1265
1266 $control = $controls[ $key ];
1267 $control_instance = $controls_manager->get_control( $control['type'] );
1268
1269 if ( ! $control_instance ) {
1270 continue;
1271 }
1272
1273 $settings[ $key ] = $control_instance->on_import_update_settings( $value, $control, $config );
1274 }
1275
1276 return $settings;
1277 }
1278
1279 /**
1280 * Save editor elements.
1281 *
1282 * Save data from the editor to the database.
1283 *
1284 * @since 2.0.0
1285 * @access protected
1286 *
1287 * @param array $elements
1288 */
1289 protected function save_elements( $elements ) {
1290 $editor_data = $this->get_elements_raw_data( $elements );
1291
1292 // We need the `wp_slash` in order to avoid the unslashing during the `update_post_meta`
1293 $json_value = wp_slash( wp_json_encode( $editor_data ) );
1294
1295 // Don't use `update_post_meta` that can't handle `revision` post type
1296 $is_meta_updated = update_metadata( 'post', $this->post->ID, '_elementor_data', $json_value );
1297
1298 /**
1299 * Before saving data.
1300 *
1301 * Fires before Elementor saves data to the database.
1302 *
1303 * @since 1.0.0
1304 *
1305 * @param string $status Post status.
1306 * @param int|bool $is_meta_updated Meta ID if the key didn't exist, true on successful update, false on failure.
1307 */
1308 do_action( 'elementor/db/before_save', $this->post->post_status, $is_meta_updated );
1309
1310 Plugin::$instance->db->save_plain_text( $this->post->ID );
1311
1312 $elements_iteration_actions = $this->get_elements_iteration_actions();
1313
1314 if ( $elements_iteration_actions ) {
1315 $this->iterate_elements( $elements, $elements_iteration_actions, 'save' );
1316 }
1317
1318 /**
1319 * After saving data.
1320 *
1321 * Fires after Elementor saves data to the database.
1322 *
1323 * @since 1.0.0
1324 *
1325 * @param int $post_id The ID of the post.
1326 * @param array $editor_data Sanitize posted data.
1327 */
1328 do_action( 'elementor/editor/after_save', $this->post->ID, $editor_data );
1329 }
1330
1331 /**
1332 * @since 2.0.0
1333 * @access public
1334 *
1335 * @param int $user_id Optional. User ID. Default value is `0`.
1336 *
1337 * @return bool|int
1338 */
1339 public function get_autosave_id( $user_id = 0 ) {
1340 if ( ! $user_id ) {
1341 $user_id = get_current_user_id();
1342 }
1343
1344 $autosave = Utils::get_post_autosave( $this->post->ID, $user_id );
1345 if ( $autosave ) {
1346 return $autosave->ID;
1347 }
1348
1349 return false;
1350 }
1351
1352 public function save_version() {
1353 if ( ! defined( 'IS_ELEMENTOR_UPGRADE' ) ) {
1354 // Save per revision.
1355 $this->update_meta( '_elementor_version', ELEMENTOR_VERSION );
1356
1357 /**
1358 * Document version save.
1359 *
1360 * Fires when document version is saved on Elementor.
1361 * Will not fire during Elementor Upgrade.
1362 *
1363 * @since 2.5.12
1364 *
1365 * @param \Elementor\Core\Base\Document $this The current document.
1366 *
1367 */
1368 do_action( 'elementor/document/save_version', $this );
1369 }
1370 }
1371
1372 /**
1373 * @since 2.3.0
1374 * @access public
1375 */
1376 public function save_template_type() {
1377 return $this->update_main_meta( self::TYPE_META_KEY, $this->get_name() );
1378 }
1379
1380 /**
1381 * @since 2.3.0
1382 * @access public
1383 */
1384 public function get_template_type() {
1385 return $this->get_main_meta( self::TYPE_META_KEY );
1386 }
1387
1388 /**
1389 * @since 2.0.0
1390 * @access public
1391 *
1392 * @param string $key Meta data key.
1393 *
1394 * @return mixed
1395 */
1396 public function get_main_meta( $key ) {
1397 return get_post_meta( $this->get_main_id(), $key, true );
1398 }
1399
1400 /**
1401 * @since 2.0.4
1402 * @access public
1403 *
1404 * @param string $key Meta data key.
1405 * @param string $value Meta data value.
1406 *
1407 * @return bool|int
1408 */
1409 public function update_main_meta( $key, $value ) {
1410 return update_post_meta( $this->get_main_id(), $key, $value );
1411 }
1412
1413 /**
1414 * @since 2.0.4
1415 * @access public
1416 *
1417 * @param string $key Meta data key.
1418 * @param string $value Optional. Meta data value. Default is an empty string.
1419 *
1420 * @return bool
1421 */
1422 public function delete_main_meta( $key, $value = '' ) {
1423 return delete_post_meta( $this->get_main_id(), $key, $value );
1424 }
1425
1426 /**
1427 * @since 2.0.0
1428 * @access public
1429 *
1430 * @param string $key Meta data key.
1431 *
1432 * @return mixed
1433 */
1434 public function get_meta( $key ) {
1435 return get_post_meta( $this->post->ID, $key, true );
1436 }
1437
1438 /**
1439 * @since 2.0.0
1440 * @access public
1441 *
1442 * @param string $key Meta data key.
1443 * @param mixed $value Meta data value.
1444 *
1445 * @return bool|int
1446 */
1447 public function update_meta( $key, $value ) {
1448 // Use `update_metadata` in order to work also with revisions.
1449 return update_metadata( 'post', $this->post->ID, $key, $value );
1450 }
1451
1452 /**
1453 * @since 2.0.3
1454 * @access public
1455 *
1456 * @param string $key Meta data key.
1457 * @param string $value Meta data value.
1458 *
1459 * @return bool
1460 */
1461 public function delete_meta( $key, $value = '' ) {
1462 // Use `delete_metadata` in order to work also with revisions.
1463 return delete_metadata( 'post', $this->post->ID, $key, $value );
1464 }
1465
1466 /**
1467 * @since 2.0.0
1468 * @access public
1469 */
1470 public function get_last_edited() {
1471 $post = $this->post;
1472 $autosave_post = $this->get_autosave();
1473
1474 if ( $autosave_post ) {
1475 $post = $autosave_post->get_post();
1476 }
1477
1478 $date = date_i18n( _x( 'M j, H:i', 'revision date format', 'elementor' ), strtotime( $post->post_modified ) );
1479 $display_name = get_the_author_meta( 'display_name', $post->post_author );
1480
1481 if ( $autosave_post || 'revision' === $post->post_type ) {
1482 /* translators: 1: Saving date, 2: Author display name. */
1483 $last_edited = sprintf( esc_html__( 'Draft saved on %1$s by %2$s', 'elementor' ), '<time>' . $date . '</time>', $display_name );
1484 } else {
1485 /* translators: 1: Editing date, 2: Author display name. */
1486 $last_edited = sprintf( esc_html__( 'Last edited on %1$s by %2$s', 'elementor' ), '<time>' . $date . '</time>', $display_name );
1487 }
1488
1489 return $last_edited;
1490 }
1491
1492
1493 /**
1494 * @return bool
1495 */
1496 public function is_saving() {
1497 return $this->is_saving;
1498 }
1499
1500 /**
1501 * @param $is_saving
1502 *
1503 * @return $this
1504 */
1505 public function set_is_saving( $is_saving ) {
1506 $this->is_saving = $is_saving;
1507
1508 return $this;
1509 }
1510
1511 /**
1512 * @since 2.0.0
1513 * @access public
1514 *
1515 * @param array $data
1516 *
1517 * @throws \Exception If the post does not exist.
1518 */
1519 public function __construct( array $data = [] ) {
1520 if ( $data ) {
1521 if ( empty( $data['post_id'] ) ) {
1522 $this->post = new \WP_Post( (object) [] );
1523 } else {
1524 $this->post = get_post( $data['post_id'] );
1525
1526 if ( ! $this->post ) {
1527 throw new \Exception( sprintf( 'Post ID #%s does not exist.', $data['post_id'] ), Exceptions::NOT_FOUND );
1528 }
1529 }
1530
1531 // Each Control_Stack is based on a unique ID.
1532 $data['id'] = $data['post_id'];
1533
1534 if ( ! isset( $data['settings'] ) ) {
1535 $data['settings'] = [];
1536 }
1537
1538 $saved_settings = get_post_meta( $this->post->ID, '_elementor_page_settings', true );
1539 if ( ! empty( $saved_settings ) && is_array( $saved_settings ) ) {
1540 $data['settings'] += $saved_settings;
1541 }
1542 }
1543
1544 parent::__construct( $data );
1545 }
1546
1547 /*
1548 * Get Export Data
1549 *
1550 * Filters a document's data on export
1551 *
1552 * @since 3.2.0
1553 * @access public
1554 *
1555 * @return array The data to export
1556 */
1557 public function get_export_data() {
1558 $content = Plugin::$instance->db->iterate_data( $this->get_elements_data(), function( $element_data ) {
1559 $element_data['id'] = Utils::generate_random_string();
1560
1561 $element = Plugin::$instance->elements_manager->create_element_instance( $element_data );
1562
1563 // If the widget/element does not exist, like a plugin that creates a widget but deactivated.
1564 if ( ! $element ) {
1565 return null;
1566 }
1567
1568 return $this->process_element_import_export( $element, 'on_export' );
1569 } );
1570
1571 return [
1572 'content' => $content,
1573 'settings' => $this->get_data( 'settings' ),
1574 'metadata' => $this->get_export_metadata(),
1575 ];
1576 }
1577
1578 public function get_export_summary() {
1579 return [
1580 'title' => $this->post->post_title,
1581 'doc_type' => $this->get_name(),
1582 'thumbnail' => get_the_post_thumbnail_url( $this->post ),
1583 ];
1584 }
1585
1586 /*
1587 * Get Import Data
1588 *
1589 * Filters a document's data on import
1590 *
1591 * @since 3.2.0
1592 * @access public
1593 *
1594 * @return array The data to import
1595 */
1596 public function get_import_data( array $data ) {
1597 $data['content'] = Plugin::$instance->db->iterate_data( $data['content'], function( $element_data ) {
1598 $element = Plugin::$instance->elements_manager->create_element_instance( $element_data );
1599
1600 // If the widget/element isn't exist, like a plugin that creates a widget but deactivated
1601 if ( ! $element ) {
1602 return null;
1603 }
1604
1605 return $this->process_element_import_export( $element, 'on_import' );
1606 } );
1607
1608 if ( ! empty( $data['settings'] ) ) {
1609 $template_model = new Page_Model( [
1610 'id' => 0,
1611 'settings' => $data['settings'],
1612 ] );
1613
1614 $page_data = $this->process_element_import_export( $template_model, 'on_import' );
1615
1616 $data['settings'] = $page_data['settings'];
1617 }
1618
1619 return $data;
1620 }
1621
1622 /**
1623 * Import
1624 *
1625 * Allows to import an external data to a document
1626 *
1627 * @since 3.2.0
1628 * @access public
1629 *
1630 * @param array $data
1631 */
1632 public function import( array $data ) {
1633 $data = $this->get_import_data( $data );
1634
1635 $this->save( [
1636 'elements' => $data['content'],
1637 'settings' => $data['settings'],
1638 ] );
1639
1640 if ( $data['import_settings']['thumbnail'] ) {
1641 $attachment = Plugin::$instance->templates_manager->get_import_images_instance()->import( [ 'url' => $data['import_settings']['thumbnail'] ] );
1642
1643 set_post_thumbnail( $this->get_main_post(), $attachment['id'] );
1644 }
1645
1646 if ( ! empty( $data['metadata'] ) ) {
1647 foreach ( $data['metadata'] as $key => $value ) {
1648 $this->update_meta( $key, $value );
1649 }
1650 }
1651 }
1652
1653 public function process_element_import_export( Controls_Stack $element, $method, $element_data = null ) {
1654 if ( null === $element_data ) {
1655 $element_data = $element->get_data();
1656 }
1657
1658 if ( method_exists( $element, $method ) ) {
1659 // TODO: Use the internal element data without parameters.
1660 $element_data = $element->{$method}( $element_data );
1661 }
1662
1663 foreach ( $element->get_controls() as $control ) {
1664 $control_class = Plugin::$instance->controls_manager->get_control( $control['type'] );
1665
1666 // If the control isn't exist, like a plugin that creates the control but deactivated.
1667 if ( ! $control_class ) {
1668 return $element_data;
1669 }
1670
1671 // Do not add default value to the final settings, if there is no value at the
1672 // data before the methods `on_import` or `on_export` called.
1673 $has_value = isset( $element_data['settings'][ $control['name'] ] );
1674
1675 if ( $has_value && method_exists( $control_class, $method ) ) {
1676 $element_data['settings'][ $control['name'] ] = $control_class->{$method}(
1677 $element_data['settings'][ $control['name'] ],
1678 $control
1679 );
1680 }
1681
1682 // On Export, check if the control has an argument 'export' => false.
1683 if ( 'on_export' === $method && isset( $control['export'] ) && false === $control['export'] ) {
1684 unset( $element_data['settings'][ $control['name'] ] );
1685 }
1686 }
1687
1688 return $element_data;
1689 }
1690
1691 protected function get_export_metadata() {
1692 $metadata = get_post_meta( $this->get_main_id() );
1693
1694 foreach ( $metadata as $meta_key => $meta_value ) {
1695 if ( is_protected_meta( $meta_key, 'post' ) ) {
1696 unset( $metadata[ $meta_key ] );
1697
1698 continue;
1699 }
1700
1701 $metadata[ $meta_key ] = $meta_value[0];
1702 }
1703
1704 return $metadata;
1705 }
1706
1707 protected function get_remote_library_config() {
1708 $config = [
1709 'type' => 'block',
1710 'default_route' => 'templates/blocks',
1711 'category' => $this->get_name(),
1712 'autoImportSettings' => false,
1713 ];
1714
1715 return $config;
1716 }
1717
1718 /**
1719 * @since 2.0.4
1720 * @access protected
1721 *
1722 * @param $settings
1723 */
1724 protected function save_settings( $settings ) {
1725 $page_settings_manager = SettingsManager::get_settings_managers( 'page' );
1726 $page_settings_manager->ajax_before_save_settings( $settings, $this->post->ID );
1727 $page_settings_manager->save_settings( $settings, $this->post->ID );
1728 }
1729
1730 /**
1731 * @since 2.1.3
1732 * @access protected
1733 */
1734 protected function print_elements( $elements_data ) {
1735 // Collect all data updaters that should be updated on runtime.
1736 $runtime_elements_iteration_actions = $this->get_runtime_elements_iteration_actions();
1737
1738 if ( $runtime_elements_iteration_actions ) {
1739 $this->iterate_elements( $elements_data, $runtime_elements_iteration_actions, 'render' );
1740 }
1741
1742 foreach ( $elements_data as $element_data ) {
1743 $element = Plugin::$instance->elements_manager->create_element_instance( $element_data );
1744
1745 if ( ! $element ) {
1746 continue;
1747 }
1748
1749 $element->print_element();
1750 }
1751 }
1752
1753 protected function register_document_controls() {
1754 $this->start_controls_section(
1755 'document_settings',
1756 [
1757 'label' => esc_html__( 'General Settings', 'elementor' ),
1758 'tab' => Controls_Manager::TAB_SETTINGS,
1759 ]
1760 );
1761
1762 $this->add_control(
1763 'post_title',
1764 [
1765 'label' => esc_html__( 'Title', 'elementor' ),
1766 'type' => Controls_Manager::TEXT,
1767 'default' => $this->post->post_title,
1768 'label_block' => true,
1769 ]
1770 );
1771
1772 $post_type_object = get_post_type_object( $this->post->post_type );
1773
1774 $can_publish = $post_type_object && current_user_can( $post_type_object->cap->publish_posts );
1775 $is_published = self::STATUS_PUBLISH === $this->post->post_status || self::STATUS_PRIVATE === $this->post->post_status;
1776
1777 if ( $is_published || $can_publish || ! Plugin::$instance->editor->is_edit_mode() ) {
1778
1779 $statuses = $this->get_post_statuses();
1780 if ( 'future' === $this->get_main_post()->post_status ) {
1781 $statuses['future'] = esc_html__( 'Future', 'elementor' );
1782 }
1783
1784 $this->add_control(
1785 'post_status',
1786 [
1787 'label' => esc_html__( 'Status', 'elementor' ),
1788 'type' => Controls_Manager::SELECT,
1789 'default' => $this->get_main_post()->post_status,
1790 'options' => $statuses,
1791 ]
1792 );
1793 }
1794
1795 $this->end_controls_section();
1796 }
1797
1798 protected function get_post_statuses() {
1799 return get_post_statuses();
1800 }
1801
1802 protected function get_have_a_look_url() {
1803 return $this->get_permalink();
1804 }
1805
1806 public function handle_revisions_changed( $post_has_changed, $last_revision, $post ) {
1807 // In case default, didn't determine the changes.
1808 if ( ! $post_has_changed ) {
1809 $last_revision_id = $last_revision->ID;
1810 $last_revision_document = Plugin::instance()->documents->get( $last_revision_id );
1811 $post_document = Plugin::instance()->documents->get( $post->ID );
1812
1813 $last_revision_settings = $last_revision_document->get_settings();
1814 $post_settings = $post_document->get_settings();
1815
1816 // TODO: Its better to add crc32 signature for each revision and then only compare one part of the checksum.
1817 $post_has_changed = $last_revision_settings !== $post_settings;
1818 }
1819
1820 return $post_has_changed;
1821 }
1822
1823 private function add_handle_revisions_changed_filter() {
1824 add_filter( 'wp_save_post_revision_post_has_changed', [ $this, 'handle_revisions_changed' ], 10, 3 );
1825 }
1826
1827 private function remove_handle_revisions_changed_filter() {
1828 remove_filter( 'wp_save_post_revision_post_has_changed', [ $this, 'handle_revisions_changed' ] );
1829 }
1830
1831 private function get_runtime_elements_iteration_actions() {
1832 $runtime_elements_iteration_actions = [];
1833
1834 $elements_iteration_actions = $this->get_elements_iteration_actions();
1835
1836 foreach ( $elements_iteration_actions as $elements_iteration_action ) {
1837 if ( $elements_iteration_action->is_action_needed() ) {
1838 $runtime_elements_iteration_actions[] = $elements_iteration_action;
1839 }
1840 }
1841
1842 return $runtime_elements_iteration_actions;
1843 }
1844
1845 private function iterate_elements( $elements, $elements_iteration_actions, $mode ) {
1846 $unique_page_elements = [];
1847
1848 foreach ( $elements_iteration_actions as $elements_iteration_action ) {
1849 $elements_iteration_action->set_mode( $mode );
1850 }
1851
1852 Plugin::$instance->db->iterate_data( $elements, function( array $element_data ) use ( &$unique_page_elements, $elements_iteration_actions ) {
1853 $element_type = 'widget' === $element_data['elType'] ? $element_data['widgetType'] : $element_data['elType'];
1854
1855 $element = Plugin::$instance->elements_manager->create_element_instance( $element_data );
1856
1857 if ( $element ) {
1858 if ( ! in_array( $element_type, $unique_page_elements, true ) ) {
1859 $unique_page_elements[] = $element_type;
1860
1861 foreach ( $elements_iteration_actions as $elements_iteration_action ) {
1862 $elements_iteration_action->unique_element_action( $element );
1863 }
1864 }
1865
1866 foreach ( $elements_iteration_actions as $elements_iteration_action ) {
1867 $elements_iteration_action->element_action( $element );
1868 }
1869 }
1870
1871 return $element_data;
1872 } );
1873
1874 foreach ( $elements_iteration_actions as $elements_iteration_action ) {
1875 $elements_iteration_action->after_elements_iteration();
1876 }
1877 }
1878
1879 private function get_elements_iteration_actions() {
1880 if ( ! $this->elements_iteration_actions ) {
1881 $this->elements_iteration_actions[] = new Assets_Iteration_Action( $this );
1882 }
1883
1884 return $this->elements_iteration_actions;
1885 }
1886 }
1887