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

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