balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

document.php (40655B)


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