PluginProbe
Elementor Website Builder – more than just a page builder / 3.20.4
Elementor Website Builder – more than just a page builder v3.20.4
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.20.4, at core/base/document.php

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