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

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