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

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