_instance_check(); $this->_register_cpt_and_taxonomies(); self::$_ = ET_Core_Data_Utils::instance(); $this->_register_hooks(); $this->_register_ajax_callbacks(); $root_directory = defined( 'ET_BUILDER_PLUGIN_ACTIVE' ) ? ET_BUILDER_PLUGIN_DIR : get_template_directory(); self::$_i18n = require $root_directory . '/cloud/i18n/library.php'; self::$_standard_post_types = self::_standard_post_types(); } /** * Compare by slug * * @param object $a First category in comparison. * @param object $b Second category in comparison. * * @return bool */ public static function compare_by_slug( $a, $b ) { return strcmp( $a->slug, $b->slug ); } /** * Gets a translated string from {@see self::$_i18n}. * * @param string $string The untranslated string. * @param string $path Optional path for nested strings. * * @return string The translated string if found, the original string otherwise. */ public static function __( $string, $path = '' ) { $path .= $path ? ".{$string}" : $string; return self::$_->array_get( self::$_i18n, $path, $string ); } /** * Dies if an instance already exists. * * @since 3.0.99 */ protected function _instance_check() { if ( self::$_instance ) { et_error( 'Multiple instances are not allowed!' ); wp_die(); } } /** * Get the name of a thumbnail image size used in the library UI. * * @since 3.0.99 * * @param string $type The thumbnail type. Accepts 'thumbnail', 'screenshot'. * * @return string */ protected static function _get_image_size_name( $type ) { $names = array( 'thumbnail' => 'full', 'thumbnail_small' => 'et-pb-portfolio-image', 'screenshot' => 'et-pb-portfolio-image-single', ); $name = $names[ $type ]; /** * Filters the names of the registered image sizes to use for layout thumbnails. The * dynamic portion of the filter name, '$type', refers to the layout image * type ('thumbnail' or 'screenshot'). * * @since 3.0.99 * * @param string $name The name of the registered image size that should be used. */ return apply_filters( "et_builder_layout_{$type}_image_size_name", $name ); } /** * Returns a filtered short name for a layout. * * @since 3.0.99 * * @param string $long_name Layout title. * @param WP_Post $layout Layout post. * * @return string */ protected function _get_layout_short_name( $long_name, $layout ) { /** * Filters the short name for layouts that do not have one. * * @since 3.0.99 * * @param string $long_name * @param WP_Post $layout */ return apply_filters( 'et_builder_library_layout_short_name', $long_name, $layout ); } /** * Processes layout categories for inclusion in the library UI layouts data. * * @since 3.0.99 * * @param WP_POST $post Unprocessed layout. * @param object $layout Currently processing layout. * @param int $index The layout's index position. * @param array[] $layout_categories Processed layouts. */ protected function _process_layout_categories( $post, $layout, $index, &$layout_categories ) { $terms = wp_get_post_terms( $post->ID, $this->layout_categories->name ); if ( ! $terms ) { $layout->category_slug = 'uncategorized'; return; } foreach ( $terms as $category ) { $category_name = self::__( html_entity_decode( $category->name ), '@categories' ); $category_name = et_core_intentionally_unescaped( $category_name, 'react_jsx' ); if ( ! isset( $layout_categories[ $category->term_id ] ) ) { $layout_categories[ $category->term_id ] = array( 'id' => $category->term_id, 'name' => $category_name, 'slug' => $category->slug, 'items' => array(), ); } $layout_categories[ $category->term_id ]['items'][] = $index; $layout->categories[] = $category_name; $layout->category_ids[] = $category->term_id; if ( ! isset( $layout->category_slug ) ) { $layout->category_slug = $category->slug; } $id = get_post_meta( $post->ID, self::$_primary_category_key, true ); if ( $id ) { // $id is a string, $category->term_id is an int. if ( $id === $category->term_id ) { // This is the primary category (used in the layout URL). $layout->category_slug = $category->slug; } } } } /** * Processes layout tags for inclusion in the library UI layouts data. * * @since 3.0.99 * * @param WP_POST $post Unprocessed layout. * @param object $layout Currently processing layout. * @param int $index The layout's index position. * @param array[] $layout_tags Processed layouts. */ protected function _process_layout_tags( $post, $layout, $index, &$layout_tags ) { $terms = wp_get_post_terms( $post->ID, $this->layout_tags->name ); if ( ! $terms ) { return; } foreach ( $terms as $tag ) { $tag_name = self::__( html_entity_decode( $tag->name ), '@tags' ); $tag_name = et_core_intentionally_unescaped( $tag_name, 'react_jsx' ); if ( ! isset( $layout_tags[ $tag->term_id ] ) ) { $layout_tags[ $tag->term_id ] = array( 'id' => $tag->term_id, 'name' => $tag_name, 'slug' => $tag->slug, 'items' => array(), ); } $layout_tags[ $tag->term_id ]['items'][] = $index; $layout->tags[] = $tag_name; $layout->tag_ids[] = $tag->term_id; if ( ! isset( $layout->tag_slug ) ) { $layout->tag_slug = $tag->slug; } $id = get_post_meta( $post->ID, self::$_primary_category_key, true ); if ( $id ) { // $id is a string, $category->term_id is an int. if ( $id === $tag->term_id ) { // This is the primary category (used in the layout URL). $layout->tag_slug = $tag->slug; } } } } /** * Processes layout packs for inclusion in the library UI layouts data. * * @since 3.0.99 * * @param WP_POST $post Unprocessed layout. * @param object $layout Currently processing layout. * @param int $index The layout's index position. * @param array[] $layout_packs Processed layouts. */ protected function _process_layout_packs( $post, $layout, $index, &$layout_packs ) { $terms = wp_get_post_terms( $post->ID, $this->layout_packs->name ); if ( ! $terms ) { return; } $pack = array_shift( $terms ); $pack_name = self::__( html_entity_decode( $pack->name ), '@packs' ); $pack_name = et_core_intentionally_unescaped( $pack_name, 'react_jsx' ); if ( ! isset( $layout_packs[ $pack->term_id ] ) ) { $layout_packs[ $pack->term_id ] = array( 'id' => $pack->term_id, 'name' => $pack_name, 'slug' => $pack->slug, 'date' => $layout->date, 'items' => array(), ); } if ( $layout->is_landing ) { $layout_packs[ $pack->term_id ]['thumbnail'] = $layout->thumbnail; $layout_packs[ $pack->term_id ]['screenshot'] = $layout->screenshot; $layout_packs[ $pack->term_id ]['description'] = et_core_intentionally_unescaped( html_entity_decode( $post->post_excerpt ), 'react_jsx' ); $layout_packs[ $pack->term_id ]['category_slug'] = $layout->category_slug; $layout_packs[ $pack->term_id ]['landing_index'] = $index; } $layout_packs[ $pack->term_id ]['items'][] = $index; $layout_packs[ $pack->term_id ]['categories'] = $layout->categories; $layout_packs[ $pack->term_id ]['category_ids'] = $layout->category_ids; $layout_packs[ $pack->term_id ]['tags'] = $layout->tags; $layout_packs[ $pack->term_id ]['tag_ids'] = $layout->tag_ids; $layout->pack = $pack_name; $layout->pack_id = $pack->term_id; } /** * Registers the Library's AJAX callbacks. * * @since 3.0.99 */ protected function _register_ajax_callbacks() { add_action( 'wp_ajax_et_builder_library_get_layouts_data', array( $this, 'wp_ajax_et_builder_library_get_layouts_data' ) ); add_action( 'wp_ajax_et_builder_library_get_layout', array( $this, 'wp_ajax_et_builder_library_get_layout' ) ); add_action( 'wp_ajax_et_builder_library_update_terms', array( $this, 'wp_ajax_et_builder_library_update_terms' ) ); add_action( 'wp_ajax_et_builder_library_save_temp_layout', array( $this, 'wp_ajax_et_builder_library_save_temp_layout' ) ); add_action( 'wp_ajax_et_builder_library_update_item', array( $this, 'wp_ajax_et_builder_library_update_item' ) ); add_action( 'wp_ajax_et_builder_library_convert_item', array( $this, 'wp_ajax_et_builder_library_convert_item' ) ); add_action( 'wp_ajax_et_builder_library_upload_thumbnail', array( $this, 'wp_ajax_et_builder_library_upload_thumbnail' ) ); add_action( 'wp_ajax_et_builder_library_update_account', array( $this, 'wp_ajax_et_builder_library_update_account' ) ); add_action( 'wp_ajax_et_builder_library_remove_temp_layout', array( $this, 'wp_ajax_et_builder_library_remove_temp_layout' ) ); add_action( 'wp_ajax_et_builder_toggle_cloud_status', array( $this, 'wp_ajax_et_builder_toggle_cloud_status' ) ); add_action( 'wp_ajax_et_builder_library_get_cloud_token', array( $this, 'wp_ajax_et_builder_library_get_cloud_token' ) ); add_action( 'wp_ajax_et_builder_library_clear_temp_presets', array( $this, 'wp_ajax_et_builder_library_clear_temp_presets' ) ); } /** * Registers the Library's custom post types and taxonomies. * * @since 3.0.99 */ protected function _register_cpt_and_taxonomies() { $files = glob( ET_BUILDER_DIR . 'post/*/Layout*.php' ); if ( ! $files ) { return; } foreach ( $files as $file ) { require_once $file; } $this->layouts = ET_Builder_Post_Type_Layout::instance(); $this->layout_categories = ET_Builder_Post_Taxonomy_LayoutCategory::instance(); $this->layout_tags = ET_Builder_Post_Taxonomy_LayoutTag::instance(); $this->layout_packs = ET_Builder_Post_Taxonomy_LayoutPack::instance(); $this->layout_types = ET_Builder_Post_Taxonomy_LayoutType::instance(); $this->layout_width = ET_Builder_Post_Taxonomy_LayoutWidth::instance(); ET_Builder_Post_Taxonomy_LayoutScope::instance(); // We manually call register_all() now to ensure the CPT and taxonomies are registered // at exactly the same point during the request that they were in prior releases. ET_Builder_Post_Type_Layout::register_all( 'builder' ); self::$_primary_category_key = "_yoast_wpseo_primary_{$this->layout_categories->name}"; } /** * Registers the Library's non-AJAX callbacks. * * @since 3.0.99 */ public function _register_hooks() { add_action( 'admin_init', 'ET_Builder_Library::update_old_layouts' ); add_action( 'admin_enqueue_scripts', array( $this, 'wp_hook_admin_enqueue_scripts' ), 4 ); add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); add_action( 'wp_footer', array( $this, 'render_session_expired_modal' ) ); add_filter( 'et_theme_builder_template_settings_options_term_pages', array( $this, 'tb_remove_unsupported_taxonomies' ), 10, 2 ); add_filter( 'parent_file', array( $this, 'wp_hook_parent_file' ), 10, 1 ); add_filter( 'submenu_file', array( $this, 'wp_hook_submenu_file' ), 10, 1 ); add_filter( 'tag_row_actions', array( $this, 'wp_filter_tag_row_actions' ), 10, 2 ); } /** * Returns sorted layout category and pack IDs for use in library UI layouts data. * * @since 3.0.99 * * @param array[] $categories Layout categories. * @param array[] $packs Layout packs. * * @return array[] { * * @type int[] $categories Layout category ids sorted alphabetically by category name. * @type int[] $packs Layout pack ids sorted alphabetically by pack name. * } */ protected static function _sort_builder_library_data( $categories, $packs, $tags ) { $categories = array_values( $categories ); $packs = array_values( $packs ); $tags = array_values( $tags ); $sorted = array(); foreach ( array( 'categories', 'packs', 'tags' ) as $taxonomy ) { $sorted[ $taxonomy ] = array(); $$taxonomy = self::$_->array_sort_by( $$taxonomy, 'slug' ); foreach ( $$taxonomy as $term ) { $sorted[ $taxonomy ][] = $term['id']; } } return $sorted; } /** * Get Divi Library's Standard Post Types. * * @return mixed|void */ public static function _standard_post_types() { /** * Filters the Divi Library's Standard Post Types. * * @since 3.0.99 * * @param string[] $standard_post_types */ return apply_filters( 'et_pb_standard_post_types', self::$_standard_post_types ); } /** * Generates layouts data for the builder's library UI. * * @since 3.0.99 * * @return array $data */ public function builder_library_layouts_data( $library_type = 'layout' ) { $layout_categories = array(); $layout_packs = array(); $layout_tags = array(); $layouts = array(); $index = 0; $thumbnail = self::_get_image_size_name( 'thumbnail' ); $thumbnail_small = self::_get_image_size_name( 'thumbnail_small' ); $screenshot = self::_get_image_size_name( 'screenshot' ); $extra_layout_post_type = 'layout'; $posts = $this->layouts ->query() ->not()->with_meta( '_et_pb_built_for_post_type', $extra_layout_post_type ) ->run( array( 'post_status' => array( 'publish', 'trash' ), 'fields' => 'ids', ) ); $posts = is_array( $posts ) ? $posts : array( $posts ); foreach ( $posts as $post_id ) { $post = get_post( $post_id ); $layout = new stdClass(); setup_postdata( $post ); // check if current user can edit library item. $can_edit_post = current_user_can( 'edit_post', $post->ID ); $layout->id = $post->ID; $layout->index = $index; $layout->date = $post->post_date; $types = wp_get_post_terms( $layout->id, $this->layout_types->name ); if ( ! $types ) { continue; } $layout->type = $types[0]->name; if ( $library_type !== $layout->type ) { continue; } $width_values = wp_get_post_terms( $layout->id, $this->layout_width->name, array( 'fields' => 'names' ) ); $layout->width = ! empty( $width_values ) ? $width_values[0] : 'regular'; $layout->row_layout = get_post_meta( $post->ID, '_et_pb_row_layout', true ); $layout->subtype = get_post_meta( $post->ID, '_et_pb_module_type', true ); if ( '' !== $layout->subtype ) { $module = ET_Builder_Element::get_module( $layout->subtype ); $layout->subtitle = ! empty( $module->name ) ? $module->name : $layout->type; } $title = html_entity_decode( $post->post_title ); $short_name = get_post_meta( $post->ID, '_et_builder_library_short_name', true ); if ( ! $short_name ) { $short_name = $this->_get_layout_short_name( $title, $post ); if ( $short_name !== $title ) { update_post_meta( $post->ID, '_et_builder_library_short_name', $short_name ); } } $layout->short_name = ''; $layout->name = $layout->short_name; if ( $title ) { // Remove periods since we use dot notation to retrieve translation. str_replace( '.', '', $title ); $layout->name = et_core_intentionally_unescaped( self::__( $title, '@layoutsLong' ), 'react_jsx' ); } if ( $short_name ) { // Remove periods since we use dot notation to retrieve translation. str_replace( '.', '', $title ); $layout->short_name = et_core_intentionally_unescaped( self::__( $short_name, '@layoutsShort' ), 'react_jsx' ); } $layout->slug = $post->post_name; $layout->url = esc_url( wp_make_link_relative( get_permalink( $post ) ) ); $layout->thumbnail = esc_url( get_the_post_thumbnail_url( $post->ID, $thumbnail ) ); $layout->thumbnail_small = esc_url( get_the_post_thumbnail_url( $post->ID, $thumbnail_small ) ); $layout->screenshot = esc_url( get_the_post_thumbnail_url( $post->ID, $screenshot ) ); $layout->is_global = $this->layouts->is_global( $layout->id ); $layout->is_favorite = $this->layouts->is_favorite( $layout->id ); $layout->is_landing = ! empty( $post->post_excerpt ); $layout->description = ''; $layout->isTrash = 'trash' === $post->post_status; // phpcs:ignore ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase -- This is valid format for the property in the Cloud App. $layout->isReadOnly = ! $can_edit_post; // phpcs:ignore ET.Sniffs.ValidVariableName.UsedPropertyNotSnakeCase -- This is valid format for the property in the Cloud App. $layout->categories = array(); $layout->category_ids = array(); $layout->tags = array(); $layout->tag_ids = array(); $this->_process_layout_categories( $post, $layout, $index, $layout_categories ); $this->_process_layout_tags( $post, $layout, $index, $layout_tags ); $this->_process_layout_packs( $post, $layout, $index, $layout_packs ); wp_reset_postdata(); $layouts[] = $layout; $index++; } /** * Filters data for the 'My Saved Layouts' tab. * * @since 3.1 * * @param array[] $saved_layouts_data { * Saved Layouts Data * * @type array[] $categories { * Layout Categories * * @type $id mixed[] { * Category * * @type int $id Id. * @type int[] $layouts Id's of layouts in category. * @type string $name Name. * @type string $slug Slug. * } * ... * } * @type array[] $packs { * Layout Packs * * @type $id mixed[] { * Pack * * @type string $category_ids Category ids. * @type string $category_slug Primary category slug. * @type string $date Published date. * @type string $description Description. * @type int $id Id. * @type int[] $layouts Id's of layouts in pack. * @type string $name Name. * @type string $screenshot Screenshot URL. * @type string $slug Slug. * @type string $thumbnail Thumbnail URL. * } * ... * } * @type object[] $layouts { * Layouts * * @type object { * Layout * * @type int $id ID * @type string[] $categories * @type int[] $category_ids * @type string $category_slug * @type int $date * @type string $description * @type int $index * @type bool $is_global * @type bool $is_landing * @type string $name * @type string $screenshot * @type string $short_name * @type string $slug * @type string $thumbnail * @type string $thumbnail_small * @type string $type * @type string $url * } * ... * } * @type array[] $sorted { * Sorted Ids * * @type int[] $categories * @type int[] $packs * } * } */ $saved_layouts_data = array( 'categories' => $this->_get_processed_terms( 'layout_category' ), 'packs' => $layout_packs, 'tags' => $this->_get_processed_terms( 'layout_tag' ), 'items' => $layouts, ); $saved_layouts_data = apply_filters( 'et_builder_library_saved_layouts', $saved_layouts_data ); /** * Filters custom tabs layout data for the library modal. Custom tabs must be registered * via the {@see 'et_builder_library_modal_custom_tabs'} filter. * * @since 3.1 * * @param array[] $custom_layouts_data { * Custom Layouts Data Organized By Modal Tab * * @type array[] $tab_slug See {@see 'et_builder_library_saved_layouts'} for array structure. * ... * } * @param array[] $saved_layouts_data {@see 'et_builder_library_saved_layouts'} for array structure. */ $custom_layouts_data = apply_filters( 'et_builder_library_custom_layouts', array( 'existing_pages' => $this->_builder_library_modal_custom_tabs_existing_pages(), ), $saved_layouts_data ); return array( 'layouts_data' => $saved_layouts_data, 'custom_layouts_data' => $custom_layouts_data, ); } /** * Gets the terms list and processes it into desired format. * * @since 4.17.0 * * @param string $term_name Term Name. * * @return array $terms_by_id */ protected function _get_processed_terms( $term_name ) { $terms = get_terms( $term_name, array( 'hide_empty' => false ) ); $terms_by_id = array(); if ( is_wp_error( $terms ) || empty( $terms ) ) { return array(); } foreach ( $terms as $term ) { $term_id = $term->term_id; $terms_by_id[ $term_id ]['id'] = $term_id; $terms_by_id[ $term_id ]['name'] = $term->name; $terms_by_id[ $term_id ]['slug'] = $term->slug; $terms_by_id[ $term_id ]['count'] = $term->count; } return $terms_by_id; } /** * Filters data for the 'Your Existing Pages' tab. * * @since 3.4 * * @return array[] $saved_layouts_data { * Existing Pages/Posts Data * * @type array[] $categories { * Post Types Filters * * @type $id mixed[] { * Post Type * * @type int $id Id. * @type int[] $layouts Id's of layouts in filter. * @type string $name Name. * @type string $slug Slug. * } * ... * } * @type array[] $packs { * Layout Packs * * @type $id mixed[] { * Pack * * @type string $category_ids Category ids. * @type string $category_slug Primary category slug. * @type string $date Published date. * @type string $description Description. * @type int $id Id. * @type int[] $layouts Id's of layouts in pack. * @type string $name Name. * @type string $screenshot Screenshot URL. * @type string $slug Slug. * @type string $thumbnail Thumbnail URL. * } * ... * } * @type object[] $layouts { * Pages/Posts Data * * @type object { * Page/Post Object * * @type int $id ID * @type string[] $categories * @type int[] $category_ids * @type string $category_slug * @type int $date * @type string $description * @type int $index * @type bool $is_global * @type bool $is_landing * @type string $name * @type string $screenshot * @type string $short_name * @type string $slug * @type string $thumbnail * @type string $thumbnail_small * @type string $type * @type string $url * } * ... * } * @type array[] $sorted { * Sorted Ids * * @type int[] $categories * @type int[] $packs * } * } */ protected function _builder_library_modal_custom_tabs_existing_pages() { et_core_nonce_verified_previously(); $categories = array(); $packs = array(); $layouts = array(); $index = 0; $thumbnail = self::_get_image_size_name( 'screenshot' ); $thumbnail_small = self::_get_image_size_name( 'thumbnail_small' ); /** * Array of post types that should be listed as categories under "Existing Pages". * * @since 4.0 * * @param string[] $post_types */ $post_types = apply_filters( 'et_library_builder_post_types', et_builder_get_builder_post_types() ); // Remove Extra's category layouts from "Your Existing Pages" layout list. if ( in_array( 'layout', $post_types, true ) ) { unset( $post_types[ array_search( 'layout', $post_types, true ) ] ); } if ( wp_doing_ajax() ) { // VB case. $exclude = isset( $_POST['postId'] ) ? (int) $_POST['postId'] : false; } else { // BB case. $exclude = get_the_ID(); } if ( $post_types ) { $category_id = 1; $layout_index = 0; // Keep track of slugs in case there are duplicates. $seen = array(); // List of post types which should be excluded from the Pages tab. $unsupported_post_types = array( ET_BUILDER_LAYOUT_POST_TYPE, ET_THEME_BUILDER_TEMPLATE_POST_TYPE, ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE, ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE, ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE, ET_THEME_BUILDER_THEME_BUILDER_POST_TYPE, ); foreach ( $post_types as $post_type ) { if ( in_array( $post_type, $unsupported_post_types, true ) ) { continue; } $post_type_obj = get_post_type_object( $post_type ); if ( ! $post_type_obj ) { continue; } $category = new StdClass(); $category->id = $category_id; $category->layouts = array(); $category->slug = $post_type; $category->name = $post_type_obj->label; $query = new ET_Core_Post_Query( $post_type ); $posts = $query // Do not include unused Theme Builder layouts. For more information // see et_theme_builder_trash_draft_and_unused_posts(). ->not()->with_meta( '_et_theme_builder_marked_as_unused' ) ->run(); $posts = self::$_->array_sort_by( is_array( $posts ) ? $posts : array( $posts ), 'post_name' ); if ( ! empty( $posts ) ) { foreach ( $posts as $post ) { // Check if page builder is activated. if ( ! et_pb_is_pagebuilder_used( $post->ID ) ) { continue; } // Do not add the current page to the list. if ( $post->ID === $exclude ) { continue; } // Check if content has shortcode. if ( ! has_shortcode( $post->post_content, 'et_pb_section' ) ) { continue; } // Only include posts that the user is allowed to edit. if ( ! current_user_can( 'edit_post', $post->ID ) ) { continue; } $title = html_entity_decode( $post->post_title ); $slug = $post->post_name; if ( ! $slug ) { // Generate a slug, if none is available - this is necessary as draft posts // that have never been published will not have a slug by default. $slug = wp_unique_post_slug( $post->post_title . '-' . $post->ID, $post->ID, $post->post_status, $post->post_type, $post->post_parent ); } if ( empty( $title ) || empty( $slug ) ) { continue; } // Make sure we don't have duplicate slugs since we're using them as key in React. // slugs should always be unique but enabling/disabling WPML can break this rule. if ( isset( $seen[ $slug ] ) ) { continue; } $type_label = et_theme_builder_is_layout_post_type( $post_type ) ? $post_type_obj->labels->singular_name : $post_type; $seen[ $slug ] = true; $layout = new stdClass(); $layout->index = $index; $layout->id = $post->ID; $layout->date = $post->post_date; $layout->status = $post->post_status; $layout->icon = 'layout'; $layout->type = $type_label; $layout->name = et_core_intentionally_unescaped( $title, 'react_jsx' ); $layout->short_name = et_core_intentionally_unescaped( $title, 'react_jsx' ); $layout->slug = $slug; $layout->url = esc_url( wp_make_link_relative( get_permalink( $post ) ) ); $layout->thumbnail = esc_url( get_the_post_thumbnail_url( $post->ID, $thumbnail ) ); $layout->thumbnail_small = esc_url( get_the_post_thumbnail_url( $post->ID, $thumbnail_small ) ); $layout->categories = array(); $layout->category_ids = array( $category_id ); $layout->is_global = false; $layout->is_landing = false; $layout->is_favorite = $this->layouts->is_favorite( $layout->id ); $layout->description = ''; $layout->category_slug = $post_type; // $layout_index is the array index, not the $post->ID $category->layouts[] = $layout_index; $post_status_object = get_post_status_object( $post->post_status ); $layout->status = isset( $post_status_object->label ) ? $post_status_object->label : $post->post_status; $layouts[] = $layout; $index++; } } $categories[ $category_id++ ] = $category; } } if ( count( $categories ) > 1 ) { // Sort categories (post_type in this case) by slug. uasort( $categories, array( 'self', 'compare_by_slug' ) ); } return array( 'categories' => $categories, 'packs' => $packs, 'items' => $layouts, 'options' => array( 'content' => array( 'title' => array( et_core_intentionally_unescaped( self::__( '%d Pages' ), 'react_jsx' ), et_core_intentionally_unescaped( self::__( '%d Page' ), 'react_jsx' ), ), ), 'sidebar' => array( 'title' => et_core_intentionally_unescaped( self::__( 'Find A Page' ), 'react_jsx' ), 'filterTitle' => et_core_intentionally_unescaped( self::__( 'Post Types' ), 'react_jsx' ), ), 'list' => array( 'columns' => array( 'status' => et_core_intentionally_unescaped( self::__( 'Status' ), 'react_jsx' ), ), ), ), 'sorted' => array( 'categories' => array_keys( $categories ), 'packs' => $packs, ), ); } /** * Get custom tabs for the library modal. * * @param string $post_type Post type. * * @return array[] { * Custom Tabs * * @type string $tab_slug Tab display name. * ... * } */ public static function builder_library_modal_custom_tabs( $post_type ) { /** * Filters custom tabs for the library modal. * * @since 3.1 * * @param array[] $custom_tabs See {@self::builder_library_modal_custom_tabs()} return value. */ $custom_tabs = array(); if ( 'layout' !== $post_type ) { $custom_tabs['existing_pages'] = esc_html__( 'Your Existing Pages', 'et_builder' ); } return apply_filters( 'et_builder_library_modal_custom_tabs', $custom_tabs, $post_type ); } /** * Gets the post types that have existing layouts built for them. * * @since 3.1 Supersedes {@see et_pb_get_standard_post_types()} * Supersedes {@see et_pb_get_used_built_for_post_types()} * @since 2.0 * * @param string $type Accepts 'standard' or 'all'. Default 'standard'. * * @return string[] $post_types */ public static function built_for_post_types( $type = 'standard' ) { static $all_built_for_post_types; if ( 'standard' === $type ) { return self::$_standard_post_types; } if ( $all_built_for_post_types ) { return $all_built_for_post_types; } global $wpdb; $all_built_for_post_types = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT( meta_value ) FROM {$wpdb->postmeta} WHERE meta_key = %s AND meta_value > ''", '_et_pb_built_for_post_type' ) ); return $all_built_for_post_types; } /** * Get the class instance. * * @since 3.0.99 * * @return ET_Builder_Library */ public static function instance() { if ( ! self::$_instance ) { self::$_instance = new self(); } return self::$_instance; } /** * Performs one-time maintenance tasks on library layouts in the database. * {@see 'admin_init'} * * @since 3.1 Relocated from `builder/layouts.php`. New task: create 'Legacy Layouts' category. * @since 2.0 */ public static function update_old_layouts() { $layouts = ET_Builder_Post_Type_Layout::instance(); if ( 'yes' !== get_theme_mod( 'et_updated_layouts_built_for_post_types', 'no' ) ) { $posts = $layouts ->query() ->not()->with_meta( '_et_pb_built_for_post_type' ) ->run(); foreach ( (array) $posts as $single_post ) { update_post_meta( $single_post->ID, '_et_pb_built_for_post_type', 'page' ); } set_theme_mod( 'et_updated_layouts_built_for_post_types', 'yes' ); } if ( ! et_get_option( 'et_pb_layouts_updated', false ) ) { $types = array( 'section', 'row', 'module', 'fullwidth_section', 'specialty_section', 'fullwidth_module', ); $posts = $layouts ->query() ->not()->is_type( $types ) ->run(); foreach ( (array) $posts as $single_post ) { if ( ! get_the_terms( $single_post->ID, 'layout_type' ) ) { wp_set_object_terms( $single_post->ID, 'layout', 'layout_type', true ); } } et_update_option( 'et_pb_layouts_updated', true ); } if ( ! et_get_option( 'library_removed_legacy_layouts', false ) ) { $posts = $layouts ->query() ->with_meta( '_et_pb_predefined_layout' ) ->run(); foreach ( $posts as $post ) { if ( 'layout' === get_post_meta( $post->ID, '_et_pb_built_for_post_type', true ) ) { // Don't touch Extra's Category Builder layouts. continue; } // Sanity check just to be safe. if ( get_post_meta( $post->ID, '_et_pb_predefined_layout', true ) ) { wp_delete_post( $post->ID, true ); } } et_update_option( 'library_removed_legacy_layouts', true ); } } /** * AJAX Callback: Gets a layout by ID. * * @since 3.0.99 * * @global $_POST['id'] The id of the desired layout. * @global $_POST ['nonce'] Nonce: 'et_builder_library_get_layout'. * * @return string|void $layout JSON encoded. See return value of {@see et_pb_retrieve_templates()} * for array structure. */ public function wp_ajax_et_builder_library_get_layout() { et_core_security_check( 'edit_posts', 'et_builder_library_get_layout', 'nonce' ); $id = isset( $_POST['id'] ) ? (int) sanitize_text_field( $_POST['id'] ) : 0; $content_type = isset( $_POST['contentType'] ) ? (string) sanitize_text_field( $_POST['contentType'] ) : 'processed'; $library_type = isset( $_POST['libraryType'] ) ? (string) sanitize_text_field( $_POST['libraryType'] ) : 'layout'; $built_for = isset( $_POST['postType'] ) ? (string) sanitize_text_field( $_POST['postType'] ) : 'page'; if ( empty( $id ) ) { wp_send_json_error(); } $result = array(); $post = get_post( $id ); $post_type = isset( $post->post_type ) ? $post->post_type : ET_BUILDER_LAYOUT_POST_TYPE; switch ( $post_type ) { case ET_BUILDER_LAYOUT_POST_TYPE: $layouts = et_pb_retrieve_templates( $library_type, '', 'all', '0', $built_for, 'all', array() ); foreach ( $layouts as $layout ) { if ( $id === $layout['ID'] ) { $result = $layout; break; } } $result['savedShortcode'] = $result['shortcode']; if ( 'processed' === $content_type ) { if ( ! isset( $_POST['is_BB'] ) ) { $result['savedShortcode'] = et_fb_process_shortcode( $result['savedShortcode'] ); } else { $post_content_processed = do_shortcode( $result['shortcode'] ); $result['migrations'] = ET_Builder_Module_Settings_Migration::$migrated; } unset( $result['shortcode'] ); } break; default: $post_content = $post->post_content; if ( 'processed' === $content_type ) { if ( ! isset( $_POST['is_BB'] ) ) { $post_content = et_fb_process_shortcode( stripslashes( $post_content ) ); } } $result['savedShortcode'] = $post_content; $result['shortcode'] = $post_content; break; } if ( 'exported' === $content_type ) { $result['exported'] = get_exported_content( $result['shortcode'] ); } $response = wp_json_encode( array( 'success' => true, 'data' => $result, ) ); $tmp_dir = function_exists( 'sys_get_temp_dir' ) ? sys_get_temp_dir() : '/tmp'; $tmp_file = tempnam( $tmp_dir, 'et' ); et_()->WPFS()->put_contents( $tmp_file, $response ); // Remove any previous buffered content since we're setting `Content-Length` header // based on $response value only. while ( ob_get_level() ) { ob_end_clean(); } header( 'Content-Length: ' . @filesize( $tmp_file ) ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- `filesize` may fail due to the permissions denied error. @unlink( $tmp_file ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- `unlink` may fail due to the permissions denied error. // Charset has to be explicitly mentioned when it is other than UTF-8. header( 'Content-Type: application/json; charset=' . esc_attr( get_option( 'blog_charset' ) ) ); die( et_core_intentionally_unescaped( $response, 'html' ) ); } /** * AJAX Callback: Add/Remove Library terms for layout_tag and layout_category taxonomies. * * @since 4.17.0 * * @global $_POST['payload'] Array with the terms list and update type (add/remove) for each. * * @return string JSON encoded. */ public function wp_ajax_et_builder_library_update_terms() { if ( ! current_user_can( 'manage_categories' ) ) { wp_send_json_error(); } et_core_security_check( 'edit_posts', 'et_builder_library_update_terms', 'nonce' ); $payload = isset( $_POST['payload'] ) ? (array) $_POST['payload'] : array(); // phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- $_POST['payload'] is an array, it's value sanitization is done at the time of accessing value. if ( empty( $payload ) ) { wp_send_json_error(); } $new_terms = array(); foreach ( $payload as $single_item ) { $filter_type = $single_item['filterType']; $taxonomy = 'tags' === $single_item['filterType'] ? 'layout_tag' : 'layout_category'; switch ( $single_item['updateType'] ) { case 'remove': $term_id = (int) $single_item['id']; wp_delete_term( $term_id, $taxonomy ); break; case 'rename': $term_id = (int) $single_item['id']; $new_name = (string) $single_item['newName']; if ( '' !== $new_name ) { $updated_term_data = wp_update_term( $term_id, $taxonomy, array( 'name' => $new_name ) ); if ( ! is_wp_error( $updated_term_data ) ) { $new_terms[] = array( 'name' => $new_name, 'id' => $updated_term_data['term_id'], 'location' => 'local', ); } } break; case 'add': $term_name = (string) $single_item['id']; $new_term_data = wp_insert_term( $term_name, $taxonomy ); if ( ! is_wp_error( $new_term_data ) ) { $new_terms[] = array( 'name' => $term_name, 'id' => $new_term_data['term_id'], 'location' => 'local', ); } break; } } wp_send_json_success( array( 'newFilters' => $new_terms, 'filterType' => $filter_type, 'localLibraryTerms' => [ 'layout_category' => et_fb_prepare_library_terms(), 'layout_tag' => et_fb_prepare_library_terms( 'layout_tag' ), ], ) ); } /** * AJAX Callback: Remove the Library layout after it was moved to the Cloud. * * @since 4.17.0 * * @global $_POST['payload'] Array with the layout data to remove. * * @return void|string JSON encoded in case of empty payload */ public function wp_ajax_et_builder_toggle_cloud_status() { et_core_security_check( 'edit_posts', 'et_builder_library_toggle_item_location', 'nonce' ); $payload = isset( $_POST['payload'] ) ? (array) $_POST['payload'] : array(); // phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- $_POST['payload'] is an array, it's value sanitization is done at the time of accessing value. if ( empty( $payload ) ) { wp_send_json_error(); } $post_id = absint( $payload['id'] ); if ( ! current_user_can( 'edit_post', $post_id ) ) { wp_send_json_error(); } $unsupported_post_types = array( ET_BUILDER_LAYOUT_POST_TYPE, ET_THEME_BUILDER_TEMPLATE_POST_TYPE, ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE, ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE, ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE, ET_THEME_BUILDER_THEME_BUILDER_POST_TYPE, ); if ( ! in_array( get_post_type( $post_id ), $unsupported_post_types, true ) ) { wp_send_json_error(); } wp_delete_post( $post_id, true ); wp_send_json_success( array( 'localLibraryTerms' => [ 'layout_category' => et_fb_prepare_library_terms(), 'layout_tag' => et_fb_prepare_library_terms( 'layout_tag' ), ], ) ); } /** * AJAX Callback: Save the temp layout into database with the 'draft' status * Uses {@see et_pb_create_layout} to submit the library post * * @since 4.17.0 * * @global $_POST['payload'] Array with the layout data to create. * * @return void */ public function wp_ajax_et_builder_library_save_temp_layout() { et_core_security_check( 'edit_posts', 'et_fb_save_library_modules_nonce', 'nonce' ); $payload = isset( $_POST['payload'] ) ? (array) $_POST['payload'] : array(); // phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- $_POST['payload'] is an array, it's value sanitization is done at the time of accessing value. $is_draft = 'draft' === $payload['postStatus']; $name = sanitize_text_field( self::$_->array_get( $payload, 'itemName', '' ) ); $content = self::$_->array_get( $payload, 'itemContent', '' ); $prefix = esc_html__( 'Edit Cloud Item', 'et_builder' ); $name = $is_draft ? $prefix . ': ' . $name : $name; $layout_type = self::$_->array_get( $payload, 'itemType', 'layout' ); $tax_input = array( 'layout_type' => sanitize_text_field( $layout_type ), 'module_width' => sanitize_text_field( self::$_->array_get( $payload, 'moduleWidth', 'regular' ) ), ); if ( 'row' === $layout_type ) { $meta_input['_et_pb_row_layout'] = $payload['rowLayout']; } if ( 'module' === $layout_type && isset( $payload['moduleType'] ) ) { $meta_input['_et_pb_module_type'] = $payload['moduleType']; } $meta_input['_et_pb_built_for_post_type'] = 'page'; $new_layout_id = et_pb_create_layout( $name, $content, $meta_input, $tax_input, '', '', $post_status = $payload['postStatus'] ); wp_send_json_success( array( 'layoutId' => $new_layout_id, ) ); } /** * AJAX Callback: Removes temp layout from the website * * @since 4.17.0 * * @global $_POST['payload'] Array with the layout id to remove. * * @return void */ public function wp_ajax_et_builder_library_remove_temp_layout() { et_core_security_check( 'edit_posts', 'et_fb_remove_library_modules_nonce', 'nonce' ); $post_id = isset( $_POST['post_id'] ) ? (int) $_POST['post_id'] : ''; $module_presets_manager = ET_Builder_Global_Presets_Settings::instance(); $module_presets_manager->clear_temp_presets(); if ( 0 === $post_id ) { $library_layouts = ET_Builder_Post_Type_Layout::instance(); $draft_posts = $library_layouts->query()->run( array( 'post_status' => array( 'draft' ) ) ); if ( ! empty( $draft_posts ) ) { if ( is_array( $draft_posts ) ) { // Several posts were returned. foreach ( $draft_posts as $post ) { if ( ! current_user_can( 'edit_post', $post->ID ) ) { continue; } wp_delete_post( $post->ID, true ); } } else { if ( ! current_user_can( 'edit_post', $draft_posts->ID ) ) { wp_send_json_error(); } // Single post was returned. wp_delete_post( $draft_posts->ID, true ); } } } else { if ( ! current_user_can( 'edit_post', $post_id ) ) { wp_send_json_error(); } wp_delete_post( $post_id, true ); } } /** * AJAX Callback: Removes temp presets from the website * * @since 4.17.0 * * @return void */ public function wp_ajax_et_builder_library_clear_temp_presets() { et_core_security_check( 'edit_posts', 'et_fb_clear_temp_presets_nonce', 'nonce' ); $module_presets_manager = ET_Builder_Global_Presets_Settings::instance(); $module_presets_manager->clear_temp_presets(); } /** * Returns 'publish' string to set the post correct status for restored library items. * * @since 4.17.0 * * @return string new post status. */ public static function et_builder_set_untrash_status() { return 'publish'; } /** * AJAX Callback: Upload thumbnail and assign it to specified post. * * @since 4.17.0 * * @global $_FILES['imageFile'] File to upload. * @global $_POST['postId'] Post id to set thumbnail for. * * @return void */ public function wp_ajax_et_builder_library_upload_thumbnail() { et_core_security_check( 'edit_posts', 'et_builder_library_update_layout', 'nonce' ); $post_id = isset( $_POST['postId'] ) ? (int) $_POST['postId'] : ''; $image_url_raw = isset( $_POST['imageURL'] ) ? esc_url_raw( $_POST['imageURL'] ) : ''; // phpcs:ignore ET.Sniffs.ValidVariableName.VariableNotSnakeCase -- This is valid format for the property in the Cloud App. // Upload and set featured image. if ( $image_url_raw && '' !== $image_url_raw ) { $upload = media_sideload_image( $image_url_raw, $post_id, $post_id, 'id' ); $attachment_id = is_wp_error( $upload ) ? 0 : $upload; $image_url = get_attached_file( $attachment_id ); $image_metadata = wp_generate_attachment_metadata( $attachment_id, $image_url ); wp_update_attachment_metadata( $attachment_id, $image_metadata ); $result = set_post_thumbnail( $post_id, $attachment_id ); wp_send_json_success(); } } /** * AJAX Callback: Update the library item (layout/section/row/module). Following updates supported: * - Duplicate * - Edit Categories/Tags. New categories/tags can be created as well. * - Rename * - Toggle Favorite status * - Delete * * @since 4.17.0 * * @global $_POST['payload'] Array with the update details. * * @return string JSON encoded with the updated item details and new terms (which could be created in Duplicate action) */ public function wp_ajax_et_builder_library_update_item() { et_core_security_check( 'edit_posts', 'et_builder_library_update_layout', 'nonce' ); $payload = isset( $_POST['payload'] ) ? (array) $_POST['payload'] : array(); // phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- $_POST['payload'] is an array, it's value sanitization is done at the time of accessing value. if ( empty( $payload ) ) { wp_send_json_error(); } $update_details = $payload['update_details']; $update_type = $update_details['updateType']; $item_id = intval( $payload['item_id'] ); $new_id = ''; $categories = empty( $update_details['itemCategories'] ) ? [] : array_unique( array_map( 'intval', $update_details['itemCategories'] ) ); $tags = empty( $update_details['itemTags'] ) ? [] : array_unique( array_map( 'intval', $update_details['itemTags'] ) ); $new_categories = array(); $new_tags = array(); $item_update = array( 'ID' => $item_id, ); $is_library_post_type = 'et_pb_layout' === get_post_type( $item_id ); if ( ! empty( $update_details['newCategoryName'] ) && current_user_can( 'manage_categories' ) ) { $new_names_array = explode( ',', $update_details['newCategoryName'] ); foreach ( $new_names_array as $new_name ) { if ( '' !== $new_name ) { $new_term = wp_insert_term( $new_name, 'layout_category' ); if ( ! is_wp_error( $new_term ) ) { $categories[] = $new_term['term_id']; $new_categories[] = array( 'name' => $new_name, 'id' => $new_term['term_id'], 'count' => 1, ); } elseif ( ! empty( $new_term->error_data ) && ! empty( $new_term->error_data['term_exists'] ) ) { $categories[] = $new_term->error_data['term_exists']; } } } } if ( ! empty( $update_details['newTagName'] ) && current_user_can( 'manage_categories' ) ) { $new_names_array = explode( ',', $update_details['newTagName'] ); foreach ( $new_names_array as $new_name ) { if ( '' !== $new_name ) { $new_term = wp_insert_term( $new_name, 'layout_tag' ); if ( ! is_wp_error( $new_term ) ) { $tags[] = $new_term['term_id']; $new_tags[] = array( 'name' => $new_name, 'id' => $new_term['term_id'], 'count' => 1, ); } elseif ( ! empty( $new_term->error_data ) && ! empty( $new_term->error_data['term_exists'] ) ) { $tags[] = $new_term->error_data['term_exists']; } } } } switch ( $update_type ) { case 'duplicate': case 'duplicate_and_delete': case 'duplicate_premade_item': case 'save_existing_page': case 'split_layout': case 'split_section': case 'split_row': $is_item_from_cloud = isset( $update_details['shortcode'] ); $title = sanitize_text_field( $update_details['itemName'] ); $meta_input = array(); $item_thumbnail = false; if ( $is_item_from_cloud ) { $content = $update_details['shortcode']; $built_for = 'page'; $scope = ! empty( $update_details['global'] ) && 'on' === $update_details['global'] ? 'global' : 'non_global'; $layout_type = isset( $update_details['layoutType'] ) ? sanitize_text_field( $update_details['layoutType'] ) : 'layout'; $module_width = isset( $update_details['moduleWidth'] ) ? sanitize_text_field( $update_details['moduleWidth'] ) : 'regular'; $favorite_status = isset( $update_details['favoriteStatus'] ) && 'on' === sanitize_text_field( $update_details['favoriteStatus'] ) ? 'favorite' : ''; if ( 'row' === $layout_type ) { $meta_input['_et_pb_row_layout'] = $update_details['rowLayout']; } if ( 'module' === $layout_type ) { $meta_input['_et_pb_module_type'] = $update_details['moduleType']; } if ( '' !== $favorite_status ) { $meta_input['favorite_status'] = $favorite_status; } } else { $content = get_the_content( null, false, $item_id ); $built_for = get_post_meta( $item_id, '_et_pb_built_for_post_type', true ); $module_width = wp_get_post_terms( $item_id, 'module_width', array( 'fields' => 'names' ) ); $module_width = is_wp_error( $module_width ) ? 'regular' : sanitize_text_field( $module_width[0] ); $layout_type = wp_get_post_terms( $item_id, 'layout_type', array( 'fields' => 'names' ) ); $layout_type = is_wp_error( $layout_type ) || '' === $layout_type ? 'layout' : sanitize_text_field( $layout_type[0] ); $item_thumbnail = get_post_thumbnail_id( $item_id ); if ( ! empty( $update_details['global'] ) ) { $scope = 'on' === $update_details['global'] ? 'global' : 'non_global'; } else { $scope = wp_get_post_terms( $item_id, 'scope', array( 'fields' => 'names' ) ); $scope = is_wp_error( $scope ) ? 'non_global' : sanitize_text_field( $scope[0] ); } if ( 'row' === $layout_type ) { $row_layout = get_post_meta( $item_id, '_et_pb_row_layout', true ); $meta_input['_et_pb_row_layout'] = $row_layout; } if ( 'module' === $layout_type ) { $module_type = get_post_meta( $item_id, '_et_pb_module_type', true ); $meta_input['_et_pb_module_type'] = $module_type; } } $meta_input['_et_pb_built_for_post_type'] = $built_for; $new_item = array( 'post_title' => $title, 'post_content' => $content, 'post_status' => 'publish', 'post_type' => 'et_pb_layout', 'tax_input' => array( 'layout_category' => $categories, 'layout_tag' => $tags, 'layout_type' => $layout_type, 'scope' => $scope, 'module_width' => $module_width, ), 'meta_input' => $meta_input, ); $new_id = wp_insert_post( $new_item ); if ( $item_thumbnail ) { set_post_thumbnail( $new_id, $item_thumbnail ); } break; case 'edit_cats': if ( ! current_user_can( 'manage_categories' ) ) { return; } wp_set_object_terms( $item_id, $categories, 'layout_category' ); wp_set_object_terms( $item_id, $tags, 'layout_tag' ); break; case 'rename': if ( ! current_user_can( 'edit_post', $item_id ) ) { return; } $item_update['post_title'] = sanitize_text_field( $update_details['itemName'] ); wp_update_post( $item_update ); break; case 'toggle_fav': if ( ! current_user_can( 'edit_post', $item_id ) ) { return; } $favorite_status = 'on' === sanitize_text_field( $update_details['favoriteStatus'] ) ? 'favorite' : ''; update_post_meta( $item_id, 'favorite_status', $favorite_status ); break; case 'delete': if ( current_user_can( 'delete_post', $item_id ) && $is_library_post_type ) { wp_trash_post( $item_id ); } break; case 'delete_permanently': if ( current_user_can( 'delete_post', $item_id ) && $is_library_post_type ) { wp_delete_post( $item_id, true ); } break; case 'restore': if ( ! current_user_can( 'edit_post', $item_id ) || ! $is_library_post_type ) { return; } // wp_untrash_post() restores the post to `draft` by default, we have to set `publish` status via filter. add_filter( 'wp_untrash_post_status', array( 'ET_Builder_Library', 'et_builder_set_untrash_status' ) ); wp_untrash_post( $item_id ); remove_filter( 'wp_untrash_post_status', array( 'ET_Builder_Library', 'et_builder_set_untrash_status' ) ); break; } $processed_new_tags = array(); $processed_new_cats = array(); $updated_tags = get_terms( array( 'taxonomy' => 'layout_tag', 'hide_empty' => false, ) ); $updated_categories = get_terms( array( 'taxonomy' => 'layout_category', 'hide_empty' => false, ) ); if ( ! empty( $updated_tags ) ) { foreach ( $updated_tags as $single_tag ) { $processed_new_tags[] = array( 'name' => $single_tag->name, 'id' => $single_tag->term_id, 'count' => $single_tag->count, 'location' => 'local', ); } } if ( ! empty( $updated_categories ) ) { foreach ( $updated_categories as $single_category ) { $processed_new_cats[] = array( 'name' => $single_category->name, 'id' => $single_category->term_id, 'count' => $single_category->count, 'location' => 'local', ); } } wp_send_json_success( array( 'updatedItem' => $item_id, 'newItem' => $new_id, 'updateType' => $update_type, 'categories' => $categories, 'tags' => $tags, 'updatedTerms' => array( 'categories' => $processed_new_cats, 'tags' => $processed_new_tags, ), ) ); } /** * AJAX Callback: Convert the library item (row/module). Following updates supported: * - Convert Module To Row * - Convert Module To Section * - Convert Row To Section * * @since 4.23.1 * * @global $_POST['payload'] Array with the item details. */ public function wp_ajax_et_builder_library_convert_item() { et_core_security_check( 'edit_posts', 'et_builder_library_convert_layout', 'nonce' ); $payload = isset( $_POST['payload'] ) ? (array) $_POST['payload'] : []; // phpcs:ignore ET.Sniffs.ValidatedSanitizedInput -- $_POST['payload'] is an array, it's value sanitization is done at the time of accessing value. if ( empty( $payload ) ) { wp_send_json_error(); } $builder_version = '_builder_version="' . ET_BUILDER_VERSION . '"'; $section_start = '[et_pb_section fb_built="1" fullwidth="off" ' . $builder_version . ' _module_preset="default"]'; $row_start = '[et_pb_row ' . $builder_version . ' _module_preset="default" theme_builder_area="post_content"]'; $column_start = '[et_pb_column ' . $builder_version . ' _module_preset="default" type="4_4" theme_builder_area="post_content"]'; $column_end = '[/et_pb_column]'; $row_end = '[/et_pb_row]'; $section_end = '[/et_pb_section]'; $fullwidth_wrapper = str_replace( 'fullwidth="off"', 'fullwidth="on" template_type="section"', $section_start ); switch ( $payload['action'] ) { case 'convert_row_to_section': $wrapper_start = $section_start; $wrapper_end = $section_end; $from_type = 'row'; $to_type = 'section'; break; case 'convert_module_to_row': $wrapper_start = $row_start . $column_start; $wrapper_end = $column_end . $row_end; $from_type = 'module'; $to_type = 'row'; break; case 'convert_module_to_section': $wrapper_start = $section_start . $row_start . $column_start; $wrapper_end = $column_end . $row_end . $section_end; $from_type = 'module'; $to_type = 'section'; break; } /** * For cloud item. */ if ( isset( $payload['content'] ) ) { if ( 'convert_module_to_section' === $payload['action'] && 'fullwidth' === $payload['item']['width'] ) { // For fullwidth module, there is no row and column. $wrapper_start = $fullwidth_wrapper; $wrapper_end = $section_end; } $post_content = $wrapper_start . wp_unslash( reset( $payload['content']['data'] ) ) . $wrapper_end; wp_send_json_success( $post_content ); } /** * For local item. */ $post_id = isset( $payload['item']['id'] ) ? absint( $payload['item']['id'] ) : 0; $item = get_post( $post_id ); if ( ! $item ) { wp_send_json_error(); } if ( 'convert_module_to_section' === $payload['action'] ) { $module_type = get_post_meta( $post_id, '_et_pb_module_type', true ); if ( false !== strpos( $module_type, 'et_pb_fullwidth' ) ) { // For fullwidth module, there is no row and column. $wrapper_start = $fullwidth_wrapper; $wrapper_end = $section_end; } } $scope_terms = get_the_terms( $post_id, 'scope' ); $scope_terms = wp_list_pluck( $scope_terms, 'slug' ); $is_global = in_array( 'global', $scope_terms, true ); $post_content = $wrapper_start . $item->post_content . $wrapper_end; $new_id = wp_insert_post( [ 'ID' => $is_global ? 0 : $post_id, // If global item, create a new post. 'post_content' => $post_content, 'post_date' => $item->post_date, 'post_title' => $item->post_title, 'post_type' => $item->post_type, 'post_status' => 'publish', ] ); if ( is_wp_error( $new_id ) ) { wp_send_json_error( $new_id->get_error_message() ); } if ( ! $is_global ) { wp_remove_object_terms( $post_id, $from_type, 'layout_type' ); } add_post_meta( $new_id, '_et_pb_built_for_post_type', 'page' ); delete_post_meta( $new_id, '_et_pb_module_type' ); wp_set_object_terms( $new_id, $to_type, 'layout_type' ); wp_send_json_success( $post_content ); } /** * AJAX Callback: Gets Cloud access token from DB and send it to client. * * @since 4.17.0 * * @return void */ public function wp_ajax_et_builder_library_get_cloud_token() { et_core_security_check( 'edit_posts', 'et_builder_library_get_cloud_token', 'nonce' ); $access_token = get_transient( 'et_cloud_access_token' ); wp_send_json_success( array( 'accessToken' => $access_token, ) ); } /** * AJAX Callback: Gets layouts data for the builder's library UI. * * @since 3.0.99 * * @global $_POST['nonce'] Nonce: 'et_builder_library_get_layouts_data'. * * @return string|void $layouts_data JSON Encoded. */ public function wp_ajax_et_builder_library_get_layouts_data() { et_core_security_check( 'edit_posts', 'et_builder_library_get_layouts_data', 'nonce' ); $library_type = isset( $_POST['et_library_type'] ) ? (string) sanitize_text_field( $_POST['et_library_type'] ) : 'layout'; wp_send_json_success( $this->builder_library_layouts_data( $library_type ) ); } /** * AJAX Callback: Updates ET Account in database. * * @since 3.0.99 * * @global $_POST['nonce'] Nonce: 'et_builder_library_update_account'. * @global $_POST['username'] Username * @global $_POST['api_key'] API Key * @global $_POST['status'] Account Status */ public function wp_ajax_et_builder_library_update_account() { et_core_security_check( 'manage_options', 'et_builder_library_update_account', 'nonce' ); $args = $_POST; if ( ! self::$_->all( $args ) ) { wp_send_json_error(); } $args = array_map( 'sanitize_text_field', $args ); $updates_options = get_site_option( 'et_automatic_updates_options', array() ); $account = array( 'username' => $args['et_username'], 'api_key' => $args['et_api_key'], ); update_site_option( 'et_automatic_updates_options', array_merge( $updates_options, $account ) ); update_site_option( 'et_account_status', $args['status'] ); wp_send_json_success(); } /** * Filters out library tags and categories from Theme Builder settings. * These taxonomies are not available on Frontend and user shouldn't be able to select it. * * @param bool $initial_value original value. * @param object $taxonomy taxonomy to check. * * @since 4.17.0 * * @return bool */ public function tb_remove_unsupported_taxonomies( $initial_value, $taxonomy ) { if ( in_array( $taxonomy->name, array( 'layout_category', 'layout_tag' ), true ) ) { return false; } return $initial_value; } /** * Enqueue styles. * * @since 4.17.0 */ public function enqueue_scripts() { // Enqueue resource for edit session expire page. if ( $this->vb_is_editing_session_expired() ) { et_core_register_admin_assets(); wp_enqueue_style( 'et-core-admin' ); wp_enqueue_script( 'et-core-admin' ); } } /** * Render modal to display a message when editing session expire. * * @since 4.17.0 */ public function render_session_expired_modal() { if ( ! $this->vb_is_editing_session_expired() ) { return; } ?>
is_404() && isset( $_GET['cloudItem'] ); } /** * Enqueues library-related styles and scripts in the admin. * {@see 'admin_enqueue_scripts'} * * @param string $page The current admin page. * * @since 3.0.99 */ public function wp_hook_admin_enqueue_scripts( $page ) { global $typenow; et_core_load_main_fonts(); wp_enqueue_style( 'et-builder-notification-popup-styles', ET_BUILDER_URI . '/styles/notification_popup_styles.css', array(), ET_BUILDER_PRODUCT_VERSION ); if ( 'et_pb_layout' === $typenow ) { $new_layout_modal = et_pb_generate_new_layout_modal(); wp_enqueue_style( 'library-styles', ET_BUILDER_URI . '/styles/library_pages.css', array( 'et-core-admin' ), ET_BUILDER_PRODUCT_VERSION ); $deps = array( 'jquery', ); wp_enqueue_script( 'library-scripts', ET_BUILDER_URI . '/scripts/library_scripts.js', $deps, ET_BUILDER_PRODUCT_VERSION, false ); $new_template_options_data = array( 'ajaxurl' => admin_url( 'admin-ajax.php' ), 'et_admin_load_nonce' => wp_create_nonce( 'et_admin_load_nonce' ), 'modal_output' => $new_layout_modal, ); wp_localize_script( 'library-scripts', 'et_pb_new_template_options', $new_template_options_data ); } else { wp_enqueue_script( 'et-builder-failure-notice', ET_BUILDER_URI . '/scripts/failure_notice.js', array( 'jquery' ), ET_BUILDER_PRODUCT_VERSION, false ); } } /** * Highlight Divi menu in admin when viewing layout tag. * * @param string $parent_file The parent file. * @return string */ public function wp_hook_parent_file( $parent_file ) { global $submenu_file; if ( 'edit.php' === $parent_file && in_array( $submenu_file, self::$submenu_files, true ) ) { return 'et_divi_options'; } return $parent_file; } /** * Highlight Divi Library submenu in admin when viewing layout tag. * * @param string $submenu_file The submenu file. * @return string */ public function wp_hook_submenu_file( $submenu_file ) { global $parent_file; if ( 'et_divi_options' === $parent_file && in_array( $submenu_file, self::$submenu_files, true ) ) { return 'edit.php?post_type=et_pb_layout'; } return $submenu_file; } /** * Remove the edit action links displayed for each term in the layout_category and layout_tag list table. * * @param string[] $actions An array of action links to be displayed. * @param WP_Term $tag Term object. */ public function wp_filter_tag_row_actions( $actions, $tag ) { if ( in_array( $tag->taxonomy, array( 'layout_category', 'layout_tag' ), true ) ) { unset( $actions['edit'] ); } return $actions; } } ET_Builder_Library::instance();