Utils::generate_random_string(), 'elType' => 'container', 'elements' => $internal_elements, ], ]; } /** * @param array $internal_elements * * @return array[] */ private function get_sections_elements_data( array $internal_elements ): array { return [ [ 'id' => Utils::generate_random_string(), 'elType' => 'section', 'elements' => [ [ 'id' => Utils::generate_random_string(), 'elType' => 'column', 'elements' => $internal_elements, ], ], ], ]; } /** * @since 2.1.0 * @access protected * @static */ protected static function get_editor_panel_categories() { return Plugin::$instance->elements_manager->get_categories(); } /** * Get properties. * * Retrieve the document properties. * * @since 2.0.0 * @access public * @static * * @return array Document properties. */ public static function get_properties() { return [ 'has_elements' => true, 'is_editable' => true, 'edit_capability' => '', 'show_in_finder' => true, 'show_on_admin_bar' => true, 'support_kit' => false, 'show_navigator' => true, 'allow_adding_widgets' => true, 'support_page_layout' => true, 'show_copy_and_share' => false, 'library_close_title' => esc_html__( 'Close', 'elementor' ), 'publish_button_title' => esc_html__( 'Publish', 'elementor' ), 'allow_closing_remote_library' => true, ]; } /** * @since 2.1.0 * @access public * @static */ public static function get_editor_panel_config() { $default_route = 'panel/elements/categories'; if ( ! Plugin::instance()->role_manager->user_can( 'design' ) ) { $default_route = 'panel/page-settings/settings'; } return [ 'title' => static::get_title(), // JS Container title. 'widgets_settings' => [], 'elements_categories' => self::get_filtered_editor_panel_categories(), 'default_route' => $default_route, 'has_elements' => static::get_property( 'has_elements' ), 'support_kit' => static::get_property( 'support_kit' ), 'messages' => [ 'publish_notification' => sprintf( /* translators: %s: Document title. */ esc_html__( 'Hurray! Your %s is live.', 'elementor' ), static::get_title() ), ], 'show_navigator' => static::get_property( 'show_navigator' ), 'allow_adding_widgets' => static::get_property( 'allow_adding_widgets' ), 'show_copy_and_share' => static::get_property( 'show_copy_and_share' ), 'library_close_title' => static::get_property( 'library_close_title' ), 'publish_button_title' => static::get_property( 'publish_button_title' ), 'allow_closing_remote_library' => static::get_property( 'allow_closing_remote_library' ), ]; } public static function get_filtered_editor_panel_categories(): array { $categories = static::get_editor_panel_categories(); $has_pro = Utils::has_pro(); foreach ( $categories as $index => $category ) { if ( isset( $category['promotion'] ) ) { $categories = self::get_panel_category_item( $category['promotion'], $index, $categories, $has_pro ); } } return $categories; } /** * @param $promotion * @param $index * @param array $categories * * @return array */ private static function get_panel_category_item( $promotion, $index, array $categories, bool $has_pro ): array { if ( ! $has_pro ) { $categories[ $index ]['promotion'] = Filtered_Promotions_Manager::get_filtered_promotion_data( $promotion, 'elementor/panel/' . $index . '/custom_promotion', 'url' ); } else { unset( $categories[ $index ]['promotion'] ); } return $categories; } /** * Get element title. * * Retrieve the element title. * * @since 2.0.0 * @access public * @static * * @return string Element title. */ public static function get_title() { return esc_html__( 'Document', 'elementor' ); } public static function get_plural_title() { return static::get_title(); } public static function get_add_new_title() { return sprintf( /* translators: %s: Document title. */ esc_html__( 'Add New %s', 'elementor' ), static::get_title() ); } /** * Get property. * * Retrieve the document property. * * @since 2.0.0 * @access public * @static * * @param string $key The property key. * * @return mixed The property value. */ public static function get_property( $key ) { $id = static::get_class_full_name(); if ( ! isset( self::$properties[ $id ] ) ) { self::$properties[ $id ] = static::get_properties(); } return self::get_items( self::$properties[ $id ], $key ); } /** * @since 2.0.0 * @access public * @static */ public static function get_class_full_name() { return get_called_class(); } public static function get_create_url() { $properties = static::get_properties(); // BC Support - Each document should define it own CPT this code is for BC support. $cpt = Source_Local::CPT; if ( isset( $properties['cpt'][0] ) ) { $cpt = $properties['cpt'][0]; } return Plugin::$instance->documents->get_create_new_post_url( $cpt, static::get_type() ); } public function get_name() { return static::get_type(); } /** * @since 2.0.0 * @access public */ public function get_unique_name() { return static::get_type() . '-' . $this->post->ID; } /** * @since 2.3.0 * @access public */ public function get_post_type_title() { $post_type_object = get_post_type_object( $this->post->post_type ); return $post_type_object->labels->singular_name; } /** * @since 2.0.0 * @access public */ public function get_main_id() { if ( ! $this->main_id ) { $post_id = $this->post->ID; $parent_post_id = wp_is_post_revision( $post_id ); if ( $parent_post_id ) { $post_id = $parent_post_id; } $this->main_id = $post_id; } return $this->main_id; } /** * @return null|Lock_Behavior */ public static function get_lock_behavior_v2() { return null; } /** * @since 2.0.0 * @access public * * @param $data * * @throws \Exception If the widget was not found. * * @return string */ public function render_element( $data ) { // Start buffering ob_start(); /** @var Widget_Base $widget */ $widget = Plugin::$instance->elements_manager->create_element_instance( $data ); if ( ! $widget ) { throw new \Exception( 'Widget not found.' ); } $widget->render_content(); $render_html = ob_get_clean(); return $render_html; } /** * @since 2.0.0 * @access public */ public function get_main_post() { return get_post( $this->get_main_id() ); } public function get_container_attributes() { $id = $this->get_main_id(); $attributes = [ 'data-elementor-type' => $this->get_name(), 'data-elementor-id' => $id, 'class' => 'elementor elementor-' . $id, ]; $version_meta = $this->get_main_meta( '_elementor_version' ); if ( version_compare( $version_meta, '2.5.0', '<' ) ) { $attributes['class'] .= ' elementor-bc-flex-widget'; } if ( Plugin::$instance->preview->is_preview() ) { $attributes['data-elementor-title'] = static::get_title(); } else { $elementor_settings = $this->get_frontend_settings(); if ( ! empty( $elementor_settings ) ) { $attributes['data-elementor-settings'] = wp_json_encode( $elementor_settings ); } } // apply this filter to allow the attributes to be modified by different sources return apply_filters( 'elementor/document/wrapper_attributes', $attributes, $this ); } /** * @since 2.0.0 * @access public */ public function get_wp_preview_url() { $main_post_id = $this->get_main_id(); $document = $this; // Ajax request from editor. $initial_document_id = Utils::get_super_global_value( $_POST, 'initial_document_id' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( ! empty( $initial_document_id ) ) { $document = Plugin::$instance->documents->get( $initial_document_id ); // phpcs:ignore WordPress.Security.NonceVerification.Missing } $url = get_preview_post_link( $document->get_main_id(), [ 'preview_id' => $main_post_id, 'preview_nonce' => wp_create_nonce( 'post_preview_' . $main_post_id ), ] ); /** * Document "WordPress preview" URL. * * Filters the WordPress preview URL. * * @since 2.0.0 * * @param string $url WordPress preview URL. * @param Document $this The document instance. */ $url = apply_filters( 'elementor/document/urls/wp_preview', $url, $this ); return $url; } /** * @since 2.0.0 * @access public */ public function get_exit_to_dashboard_url() { $url = get_edit_post_link( $this->get_main_id(), 'raw' ); /** * Document "exit to dashboard" URL. * * Filters the "Exit To Dashboard" URL. * * @since 2.0.0 * * @param string $url The exit URL * @param Document $this The document instance. */ $url = apply_filters( 'elementor/document/urls/exit_to_dashboard', $url, $this ); return $url; } /** * Get All Post Type URL * * Get url of the page which display all the posts of the current active document's post type. * * @since 3.7.0 * * @return string $url */ public function get_all_post_type_url() { $post_type = get_post_type( $this->get_main_id() ); $url = get_admin_url() . 'edit.php'; if ( 'post' !== $post_type ) { $url .= '?post_type=' . $post_type; } /** * Document "display all post type" URL. * * @since 3.7.0 * * @param string $url The URL. * @param Document $this The document instance. */ $url = apply_filters( 'elementor/document/urls/all_post_type', $url, $this ); return $url; } /** * Get Main WP dashboard URL. * * @since 3.7.0 * * @return string $url */ protected function get_main_dashboard_url() { $url = get_dashboard_url(); /** * Document "Main Dashboard" URL. * * @since 3.7.0 * * @param string $url The URL. * @param Document $this The document instance. */ $url = apply_filters( 'elementor/document/urls/main_dashboard', $url, $this ); return $url; } /** * Get auto-saved post revision. * * Retrieve the auto-saved post revision that is newer than current post. * * @since 2.0.0 * @access public * * * @return bool|Document */ public function get_newer_autosave() { $autosave = $this->get_autosave(); // Detect if there exists an autosave newer than the post. if ( $autosave && mysql2date( 'U', $autosave->get_post()->post_modified_gmt, false ) > mysql2date( 'U', $this->post->post_modified_gmt, false ) ) { return $autosave; } return false; } /** * @since 2.0.0 * @access public */ public function is_autosave() { return wp_is_post_autosave( $this->post->ID ); } /** * Check if the current document is a 'revision' * * @return bool */ public function is_revision() { return 'revision' === $this->post->post_type; } /** * Checks if the current document status is 'trash'. * * @return bool */ public function is_trash() { return 'trash' === $this->post->post_status; } /** * @since 2.0.0 * @access public * * @param int $user_id * @param bool $create * * @return bool|Document */ public function get_autosave( $user_id = 0, $create = false ) { if ( ! $user_id ) { $user_id = get_current_user_id(); } $autosave_id = $this->get_autosave_id( $user_id ); if ( $autosave_id ) { $document = Plugin::$instance->documents->get( $autosave_id ); } elseif ( $create ) { $autosave_id = wp_create_post_autosave( [ 'post_ID' => $this->post->ID, 'post_type' => $this->post->post_type, 'post_title' => $this->post->post_title, 'post_excerpt' => $this->post->post_excerpt, // Hack to cause $autosave_is_different=true in `wp_create_post_autosave`. 'post_content' => '', 'post_modified' => current_time( 'mysql' ), ] ); Plugin::$instance->db->copy_elementor_meta( $this->post->ID, $autosave_id ); $document = Plugin::$instance->documents->get( $autosave_id ); $document->save_template_type(); } else { $document = false; } return $document; } /** * Add/Remove edit link in dashboard. * * Add or remove an edit link to the post/page action links on the post/pages list table. * * Fired by `post_row_actions` and `page_row_actions` filters. * * @access public * * @param array $actions An array of row action links. * * @return array An updated array of row action links. */ public function filter_admin_row_actions( $actions ) { if ( $this->is_built_with_elementor() && $this->is_editable_by_current_user() ) { $actions['edit_with_elementor'] = sprintf( '%2$s', $this->get_edit_url(), __( 'Edit with Elementor', 'elementor' ) ); } return $actions; } /** * @since 2.0.0 * @access public */ public function is_editable_by_current_user() { $edit_capability = static::get_property( 'edit_capability' ); if ( $edit_capability && ! current_user_can( $edit_capability ) ) { return false; } return self::get_property( 'is_editable' ) && User::is_current_user_can_edit( $this->get_main_id() ); } /** * @since 2.9.0 * @access protected */ protected function get_initial_config() { // 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. $locked_user = Plugin::$instance->editor->get_locked_user( $this->get_main_id() ); if ( $locked_user ) { $locked_user = $locked_user->display_name; } $post = $this->get_main_post(); $post_type_object = get_post_type_object( $post->post_type ); $settings = SettingsManager::get_settings_managers_config(); $config = [ 'id' => $this->get_main_id(), 'type' => $this->get_name(), 'version' => $this->get_main_meta( '_elementor_version' ), 'settings' => $settings['page'], 'remoteLibrary' => $this->get_remote_library_config(), 'last_edited' => $this->get_last_edited(), 'panel' => static::get_editor_panel_config(), 'container' => 'body', 'post_type_title' => $this->get_post_type_title(), 'user' => [ 'can_publish' => current_user_can( $post_type_object->cap->publish_posts ), // Deprecated config since 2.9.0. 'locked' => $locked_user, ], 'urls' => [ 'exit_to_dashboard' => $this->get_exit_to_dashboard_url(), // WP post type edit page 'all_post_type' => $this->get_all_post_type_url(), 'preview' => $this->get_preview_url(), 'wp_preview' => $this->get_wp_preview_url(), 'permalink' => $this->get_permalink(), 'have_a_look' => $this->get_have_a_look_url(), 'main_dashboard' => $this->get_main_dashboard_url(), ], ]; $post_status_object = get_post_status_object( $post->post_status ); if ( $post_status_object ) { $config['status'] = [ 'value' => $post_status_object->name, 'label' => $post_status_object->label, ]; } do_action( 'elementor/document/before_get_config', $this ); if ( static::get_property( 'has_elements' ) ) { $container_config = []; if ( Plugin::$instance->experiments->is_feature_active( 'container' ) ) { $container_config = [ 'container' => Plugin::$instance->elements_manager->get_element_types( 'container' )->get_config(), ]; } $config['elements'] = $this->get_elements_raw_data( null, true ); $config['widgets'] = $container_config + Plugin::$instance->widgets_manager->get_widget_types_config(); } $additional_config = []; /** * Additional document configuration. * * Filters the document configuration by adding additional configuration. * External developers can use this hook to add custom configuration in * addition to Elementor's initial configuration. * * Use the $post_id to add custom configuration for different pages. * * @param array $additional_config The additional document configuration. * @param int $post_id The post ID of the document. */ $additional_config = apply_filters( 'elementor/document/config', $additional_config, $this->get_main_id() ); if ( ! empty( $additional_config ) ) { $config = array_replace_recursive( $config, $additional_config ); } return $config; } /** * @since 3.1.0 * @access protected */ protected function register_controls() { $this->register_document_controls(); /** * Register document controls. * * Fires after Elementor registers the document controls. * * External developers can use this hook to add new controls to the document. * * @since 2.0.0 * * @param Document $this The document instance. */ do_action( 'elementor/documents/register_controls', $this ); } /** * @since 2.0.0 * @access public * * @param $data * * @return bool */ public function save( $data ) { /** * Set locale to "C" to avoid issues with comma as decimal separator. * * @see https://github.com/elementor/elementor/issues/10992 */ $original_lc = setlocale( LC_NUMERIC, 0 ); setlocale( LC_NUMERIC, 'C' ); /** * Document save data. * * Filter the document data before saving process starts. * * External developers can use this hook to change the data before * saving it to the database. * * @since 3.3.0 * * @param array $data The document data. * @param \Elementor\Core\Base\Document $this The document instance. */ $data = apply_filters( 'elementor/document/save/data', $data, $this ); $this->add_handle_revisions_changed_filter(); if ( ! $this->is_editable_by_current_user() ) { return false; } $this->set_is_saving( true ); /** * Before document save. * * Fires when document save starts on Elementor. * * @since 2.5.12 * * @param \Elementor\Core\Base\Document $this The current document. * @param $data. */ do_action( 'elementor/document/before_save', $this, $data ); if ( ! current_user_can( 'unfiltered_html' ) ) { $data = wp_kses_post_deep( $data ); } if ( ! empty( $data['settings'] ) ) { if ( isset( $data['settings']['post_status'] ) && self::STATUS_AUTOSAVE === $data['settings']['post_status'] ) { if ( ! defined( 'DOING_AUTOSAVE' ) ) { define( 'DOING_AUTOSAVE', true ); } } $this->save_settings( $data['settings'] ); $this->refresh_post(); } // Don't check is_empty, because an empty array should be saved. if ( isset( $data['elements'] ) && is_array( $data['elements'] ) ) { $this->save_elements( $data['elements'] ); } $this->save_template_type(); $this->save_version(); // Remove Post CSS $post_css = Post_CSS::create( $this->post->ID ); $post_css->delete(); // Remove Document Cache $this->delete_cache(); /** * After document save. * * Fires when document save is complete. * * @since 2.5.12 * * @param \Elementor\Core\Base\Document $this The current document. * @param $data. */ do_action( 'elementor/document/after_save', $this, $data ); $this->set_is_saving( false ); $this->remove_handle_revisions_changed_filter(); setlocale( LC_NUMERIC, $original_lc ); return true; } public function refresh_post() { $this->post = get_post( $this->post->ID ); } /** * @param array $new_settings * * @return static */ public function update_settings( array $new_settings ) { $document_settings = $this->get_meta( PageManager::META_KEY ); if ( ! $document_settings ) { $document_settings = []; } $this->save_settings( array_replace_recursive( $document_settings, $new_settings ) ); return $this; } /** * Is built with Elementor. * * Check whether the post was built with Elementor. * * @since 2.0.0 * @access public * * @return bool Whether the post was built with Elementor. */ public function is_built_with_elementor() { return ! ! $this->get_meta( self::BUILT_WITH_ELEMENTOR_META_KEY ); } /** * Mark the post as "built with elementor" or not. * * @param bool $is_built_with_elementor * * @return $this */ public function set_is_built_with_elementor( $is_built_with_elementor ) { if ( $is_built_with_elementor ) { // Use the string `builder` and not a boolean for rollback compatibility $this->update_meta( self::BUILT_WITH_ELEMENTOR_META_KEY, 'builder' ); } else { $this->delete_meta( self::BUILT_WITH_ELEMENTOR_META_KEY ); } return $this; } /** * @since 2.0.0 * @access public * @static * * @return mixed */ public function get_edit_url() { $url = add_query_arg( [ 'post' => $this->get_main_id(), 'action' => 'elementor', ], admin_url( 'post.php' ) ); /** * Document edit url. * * Filters the document edit url. * * @since 2.0.0 * * @param string $url The edit url. * @param Document $this The document instance. */ $url = apply_filters( 'elementor/document/urls/edit', $url, $this ); return $url; } /** * @since 2.0.0 * @access public */ public function get_preview_url() { /** * Use a static var - to avoid change the `ver` parameter on every call. */ static $url; if ( empty( $url ) ) { add_filter( 'pre_option_permalink_structure', '__return_empty_string' ); $url = set_url_scheme( add_query_arg( [ 'elementor-preview' => $this->get_main_id(), 'ver' => time(), ], $this->get_permalink() ) ); remove_filter( 'pre_option_permalink_structure', '__return_empty_string' ); /** * Document preview URL. * * Filters the document preview URL. * * @since 2.0.0 * * @param string $url The preview URL. * @param Document $this The document instance. */ $url = apply_filters( 'elementor/document/urls/preview', $url, $this ); } return $url; } /** * @since 2.0.0 * @access public * * @param string $key * * @return array */ public function get_json_meta( $key ) { $meta = get_post_meta( $this->post->ID, $key, true ); if ( is_string( $meta ) && ! empty( $meta ) ) { $meta = json_decode( $meta, true ); } if ( empty( $meta ) ) { $meta = []; } return $meta; } public function update_json_meta( $key, $value ) { $this->update_meta( $key, // `wp_slash` in order to avoid the unslashing during the `update_post_meta` wp_slash( wp_json_encode( $value ) ) ); } /** * @since 2.0.0 * @access public * * @param null $data * @param bool $with_html_content * * @return array */ public function get_elements_raw_data( $data = null, $with_html_content = false ) { if ( ! static::get_property( 'has_elements' ) ) { return []; } if ( is_null( $data ) ) { $data = $this->get_elements_data(); } // Change the current documents, so widgets can use `documents->get_current` and other post data Plugin::$instance->documents->switch_to_document( $this ); $editor_data = []; foreach ( $data as $element_data ) { if ( ! is_array( $element_data ) ) { throw new \Exception( 'Invalid data: ' . wp_json_encode( [ 'data' => $data, 'element' => $element_data, ] ) ); } $element = Plugin::$instance->elements_manager->create_element_instance( $element_data ); if ( ! $element ) { continue; } if ( $this->is_saving ) { $element_data = $element->get_data_for_save(); } else { $element_data = $element->get_raw_data( $with_html_content ); } $editor_data[] = $element_data; } // End foreach(). Plugin::$instance->documents->restore_document(); return $editor_data; } /** * @since 2.0.0 * @access public * * @param string $status * * @return array */ public function get_elements_data( $status = self::STATUS_PUBLISH ) { $elements = $this->get_json_meta( '_elementor_data' ); if ( self::STATUS_DRAFT === $status ) { $autosave = $this->get_newer_autosave(); if ( is_object( $autosave ) ) { $autosave_elements = Plugin::$instance->documents ->get( $autosave->get_post()->ID ) ->get_json_meta( '_elementor_data' ); } } if ( Plugin::$instance->editor->is_edit_mode() ) { if ( empty( $elements ) && empty( $autosave_elements ) ) { // Convert to Elementor. $elements = $this->convert_to_elementor(); if ( $this->is_autosave() ) { Plugin::$instance->db->copy_elementor_meta( $this->post->post_parent, $this->post->ID ); } } } if ( ! empty( $autosave_elements ) ) { $elements = $autosave_elements; } return $elements; } /** * Get document setting from DB. * * @return array */ public function get_db_document_settings() { return $this->get_meta( static::PAGE_META_KEY ); } /** * @since 2.3.0 * @access public */ public function convert_to_elementor() { $this->save( [] ); if ( empty( $this->post->post_content ) ) { return []; } // Check if it's only a shortcode. preg_match_all( '/' . get_shortcode_regex() . '/', $this->post->post_content, $matches, PREG_SET_ORDER ); if ( ! empty( $matches ) ) { foreach ( $matches as $shortcode ) { if ( trim( $this->post->post_content ) === $shortcode[0] ) { $widget_type = Plugin::$instance->widgets_manager->get_widget_types( 'shortcode' ); $settings = [ 'shortcode' => $this->post->post_content, ]; break; } } } if ( empty( $widget_type ) ) { $widget_type = Plugin::$instance->widgets_manager->get_widget_types( 'text-editor' ); $settings = [ 'editor' => $this->post->post_content, ]; } // TODO: Better coding to start template for editor $converted_blocks = [ [ 'id' => Utils::generate_random_string(), 'elType' => $widget_type::get_type(), 'widgetType' => $widget_type->get_name(), 'settings' => $settings, ], ]; return Plugin::$instance->experiments->is_feature_active( 'container' ) ? $this->get_container_elements_data( $converted_blocks ) : $this->get_sections_elements_data( $converted_blocks ); } /** * @since 2.1.3 * @access public */ public function print_elements_with_wrapper( $elements_data = null ) { if ( ! $elements_data ) { $elements_data = $this->get_elements_data(); } ?>