: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @type array[] $sorted {
* @type int[] $categories
$saved_layouts_data = apply_filters( 'et_builder_library_saved_layouts', array(
'categories' => $layout_categories,
'packs' => $layout_packs,
'sorted' => self::_sort_builder_library_data( $layout_categories, $layout_packs ),
* Filters custom tabs layout data for the library modal. Custom tabs must be registered
* via the {@see 'et_builder_library_modal_custom_tabs'} filter.
* @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 );
'layouts_data' => $saved_layouts_data,
'custom_layouts_data' => $custom_layouts_data,
* Filters data for the 'Your Existing Pages' tab.
* @return array[] $saved_layouts_data {
* Existing Pages/Posts Data
* @type array[] $categories {
* @type int[] $layouts Id's of layouts in filter.
* @type string $name Name.
* @type string $slug Slug.
* @type string $category_ids Category ids.
* @type string $category_slug Primary category slug.
* @type string $date Published date.
* @type string $description Description.
* @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 {
* @type string[] $categories
* @type int[] $category_ids
* @type string $category_slug
* @type string $description
* @type string $screenshot
* @type string $short_name
* @type string $thumbnail
* @type string $thumbnail_small
* @type array[] $sorted {
* @type int[] $categories
protected function builder_library_modal_custom_tabs_existing_pages() {
et_core_nonce_verified_previously();
$thumbnail = self::_get_image_size_name( 'thumbnail' );
$thumbnail_small = self::_get_image_size_name( 'thumbnail_small' );
$screenshot = self::_get_image_size_name( 'screenshot' );
* Array of post types that should be listed as categories under "Existing Pages".
* @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 ) ) {
unset( $post_types[ array_search( 'layout', $post_types ) ] );
$exclude = isset( $_POST['postId'] ) ? (int) $_POST['postId'] : false;
// Keep track of slugs in case there are duplicates.
foreach ( $post_types as $post_type ) {
if ( ET_BUILDER_LAYOUT_POST_TYPE === $post_type ) {
$post_type_obj = get_post_type_object( $post_type );
if ( ! $post_type_obj ) {
$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 );
// 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' )
$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 ) ) {
// Do not add the current page to the list
if ( $post->ID === $exclude ) {
// Check if content has shortcode.
if ( ! has_shortcode( $post->post_content, 'et_pb_section' ) ) {
// Only include posts that the user is allowed to edit
if ( ! current_user_can( 'edit_post', $post->ID ) ) {
$title = html_entity_decode( $post->post_title );
$slug = $post->post_name;
// 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 ) ) {
// 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 ] ) ) {
$type_label = et_theme_builder_is_layout_post_type( $post_type )
? $post_type_obj->labels->singular_name
$layout = new stdClass();
$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->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->categories = array();
$layout->category_ids = array( $category_id );
$layout->is_global = false;
$layout->is_landing = false;
$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++ ] = $layout;
$categories[ $category_id++ ] = $category;
if ( count( $categories ) > 1) {
// Sort categories (post_type in this case) by slug
uasort( $categories, array( 'self', 'compare_by_slug' ) );
'categories' => $categories,
et_core_intentionally_unescaped( self::__( '%d Pages' ), 'react_jsx' ),
et_core_intentionally_unescaped( self::__( '%d Page' ), 'react_jsx' ),
'title' => et_core_intentionally_unescaped( self::__( 'Find A Page' ), 'react_jsx' ),
'status' => et_core_intentionally_unescaped( self::__( 'Status' ), 'react_jsx' ),
'categories' => array_keys( $categories ),
* Get custom tabs for the library modal.
* @param string $post_type
* @type string $tab_slug Tab display name.
public static function builder_library_modal_custom_tabs( $post_type ) {
* Filters custom tabs for the library modal.
* @param array[] $custom_tabs See {@self::builder_library_modal_custom_tabs()} return value.
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()}
* @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;
return $all_built_for_post_types = $wpdb->get_col(
"SELECT DISTINCT( meta_value ) FROM {$wpdb->postmeta} WHERE meta_key = %s AND meta_value > ''",
'_et_pb_built_for_post_type'
* Get the class instance.
* @return ET_Builder_Library
public static function instance() {
if ( ! self::$_instance ) {
self::$_instance = new self;
* Performs one-time maintenance tasks on library layouts in the database.
* @since 3.1 Relocated from `builder/layouts.php`. New task: create 'Legacy Layouts' category.
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' ) ) {
->not()->with_meta( '_et_pb_built_for_post_type' )
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 ) ) {
->not()->is_type( $types )
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 ) ) {
->with_meta( '_et_pb_predefined_layout' )
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.
// 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.
* @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()}
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) $_POST['id'] : 0;
$post_type = isset( $post->post_type ) ? $post->post_type : ET_BUILDER_LAYOUT_POST_TYPE;
case ET_BUILDER_LAYOUT_POST_TYPE:
$layouts = et_pb_retrieve_templates( 'layout', '', 'all', '0', 'all', 'all', array(), $post_type );
foreach ( $layouts as $layout ) {
if ( $id === $layout['ID'] ) {
$result['savedShortcode'] = $result['shortcode'];
if ( ! isset( $_POST['is_BB'] ) ) {
$result['savedShortcode'] = et_fb_process_shortcode( $result['savedShortcode'] );
$post_content_processed = do_shortcode( $result['shortcode'] );
$result['migrations'] = ET_Builder_Module_Settings_Migration::$migrated;
unset( $result['shortcode'] );
$post_content = $post->post_content;