<?php
if ( ! defined( 'ET_THEME_BUILDER_DIR' ) ) {
	define( 'ET_THEME_BUILDER_DIR', ET_BUILDER_DIR . 'frontend-builder/theme-builder/' );
}

if ( ! defined( 'ET_THEME_BUILDER_THEME_BUILDER_POST_TYPE' ) ) {
	define( 'ET_THEME_BUILDER_THEME_BUILDER_POST_TYPE', 'et_theme_builder' );
}

if ( ! defined( 'ET_THEME_BUILDER_TEMPLATE_POST_TYPE' ) ) {
	define( 'ET_THEME_BUILDER_TEMPLATE_POST_TYPE', 'et_template' );
}

if ( ! defined( 'ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE' ) ) {
	define( 'ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE', 'et_header_layout' );
}

if ( ! defined( 'ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE' ) ) {
	define( 'ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE', 'et_body_layout' );
}

if ( ! defined( 'ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE' ) ) {
	define( 'ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE', 'et_footer_layout' );
}

if ( ! defined( 'ET_THEME_BUILDER_SETTING_SEPARATOR' ) ) {
	// Must be a single character.
	define( 'ET_THEME_BUILDER_SETTING_SEPARATOR', ':' );
}

if ( ! defined( 'ET_THEME_BUILDER_DYNAMIC_CONTENT_REGEX' ) ) {
	define( 'ET_THEME_BUILDER_DYNAMIC_CONTENT_REGEX', '/@ET-DC@(.*?)@/' );
}

require_once ET_THEME_BUILDER_DIR . 'ThemeBuilderApiErrors.php';
require_once ET_THEME_BUILDER_DIR . 'ThemeBuilderRequest.php';
require_once ET_THEME_BUILDER_DIR . 'template-setting-validations.php';
require_once ET_THEME_BUILDER_DIR . 'api.php';
require_once ET_THEME_BUILDER_DIR . 'admin.php';
require_once ET_THEME_BUILDER_DIR . 'frontend.php';
require_once ET_THEME_BUILDER_DIR . 'dynamic-content.php';

// Conditional Includes.
if ( et_is_woocommerce_plugin_active() ) {
	require_once ET_THEME_BUILDER_DIR . 'woocommerce.php';
	require_once ET_THEME_BUILDER_DIR . 'WoocommerceProductVariationPlaceholder.php';
	require_once ET_THEME_BUILDER_DIR . 'WoocommerceProductVariablePlaceholder.php';
	require_once ET_THEME_BUILDER_DIR . 'WoocommerceProductVariablePlaceholderDataStoreCPT.php';
}

if ( et_core_is_wpml_plugin_active() ) {
	require_once ET_THEME_BUILDER_DIR . 'wpml.php';
}

/**
 * Register all relevant Theme Builder entities such as post types.
 *
 * @since 4.0
 *
 * @return void
 */
function et_theme_builder_register_entities() {
	$publicly_queryable = isset( $_GET['et_fb'] ) && '1' === $_GET['et_fb'] && et_pb_is_allowed( 'use_visual_builder' );

	register_post_type(
		ET_THEME_BUILDER_THEME_BUILDER_POST_TYPE,
		array(
			'labels'             => array(
				'name'               => esc_html__( 'Theme Builders', 'et_builder' ),
				'singular_name'      => esc_html__( 'Theme Builder', 'et_builder' ),
				'add_new'            => esc_html__( 'Add New', 'et_builder' ),
				'add_new_item'       => esc_html__( 'Add New Theme Builder', 'et_builder' ),
				'edit_item'          => esc_html__( 'Edit Theme Builder', 'et_builder' ),
				'new_item'           => esc_html__( 'New Theme Builder', 'et_builder' ),
				'all_items'          => esc_html__( 'All Theme Builders', 'et_builder' ),
				'view_item'          => esc_html__( 'View Theme Builder', 'et_builder' ),
				'search_items'       => esc_html__( 'Search Theme Builders', 'et_builder' ),
				'not_found'          => esc_html__( 'Nothing found', 'et_builder' ),
				'not_found_in_trash' => esc_html__( 'Nothing found in Trash', 'et_builder' ),
				'parent_item_colon'  => '',
			),
			'can_export'         => false,
			'capability_type'    => 'post',
			'has_archive'        => false,
			'hierarchical'       => false,
			'map_meta_cap'       => true,
			'public'             => false,
			'publicly_queryable' => false,
			'query_var'          => false,
			'show_ui'            => false,
			'show_in_rest'       => false,
			'rewrite'            => true,
			'supports'           => array( 'title', 'author' ),
		)
	);

	register_post_type(
		ET_THEME_BUILDER_TEMPLATE_POST_TYPE,
		array(
			'labels'             => array(
				'name'               => esc_html__( 'Templates', 'et_builder' ),
				'singular_name'      => esc_html__( 'Template', 'et_builder' ),
				'add_new'            => esc_html__( 'Add New', 'et_builder' ),
				'add_new_item'       => esc_html__( 'Add New Template', 'et_builder' ),
				'edit_item'          => esc_html__( 'Edit Template', 'et_builder' ),
				'new_item'           => esc_html__( 'New Template', 'et_builder' ),
				'all_items'          => esc_html__( 'All Templates', 'et_builder' ),
				'view_item'          => esc_html__( 'View Template', 'et_builder' ),
				'search_items'       => esc_html__( 'Search Templates', 'et_builder' ),
				'not_found'          => esc_html__( 'Nothing found', 'et_builder' ),
				'not_found_in_trash' => esc_html__( 'Nothing found in Trash', 'et_builder' ),
				'parent_item_colon'  => '',
			),
			'can_export'         => true,
			'capability_type'    => 'post',
			'has_archive'        => false,
			'hierarchical'       => false,
			'map_meta_cap'       => true,
			'public'             => false,
			'publicly_queryable' => $publicly_queryable,
			'query_var'          => false,
			'show_ui'            => false,
			'show_in_rest'       => false,
			'rewrite'            => true,
			'supports'           => array( 'title', 'author' ),
		)
	);

	register_post_type(
		ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE,
		array(
			'labels'             => array(
				'name'               => esc_html__( 'Header Templates', 'et_builder' ),
				'singular_name'      => esc_html__( 'Header Template', 'et_builder' ),
				'add_new'            => esc_html__( 'Add New', 'et_builder' ),
				'add_new_item'       => esc_html__( 'Add New Header Template', 'et_builder' ),
				'edit_item'          => esc_html__( 'Edit Header Template', 'et_builder' ),
				'new_item'           => esc_html__( 'New Header Template', 'et_builder' ),
				'all_items'          => esc_html__( 'All Header Templates', 'et_builder' ),
				'view_item'          => esc_html__( 'View Header Template', 'et_builder' ),
				'search_items'       => esc_html__( 'Search Header Templates', 'et_builder' ),
				'not_found'          => esc_html__( 'Nothing found', 'et_builder' ),
				'not_found_in_trash' => esc_html__( 'Nothing found in Trash', 'et_builder' ),
				'parent_item_colon'  => '',
			),
			'can_export'         => true,
			'capability_type'    => 'post',
			'has_archive'        => false,
			'hierarchical'       => false,
			'map_meta_cap'       => true,
			'public'             => false,
			'publicly_queryable' => $publicly_queryable,
			'query_var'          => false,
			'show_ui'            => false,
			'show_in_rest'       => false,
			'rewrite'            => true,
			'supports'           => array( 'title', 'editor', 'author', 'revisions', 'comments' ),
		)
	);

	register_post_type(
		ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE,
		array(
			'labels'             => array(
				'name'               => esc_html__( 'Body Templates', 'et_builder' ),
				'singular_name'      => esc_html__( 'Body Template', 'et_builder' ),
				'add_new'            => esc_html__( 'Add New', 'et_builder' ),
				'add_new_item'       => esc_html__( 'Add New Body Template', 'et_builder' ),
				'edit_item'          => esc_html__( 'Edit Body Template', 'et_builder' ),
				'new_item'           => esc_html__( 'New Body Template', 'et_builder' ),
				'all_items'          => esc_html__( 'All Body Templates', 'et_builder' ),
				'view_item'          => esc_html__( 'View Body Template', 'et_builder' ),
				'search_items'       => esc_html__( 'Search Body Templates', 'et_builder' ),
				'not_found'          => esc_html__( 'Nothing found', 'et_builder' ),
				'not_found_in_trash' => esc_html__( 'Nothing found in Trash', 'et_builder' ),
				'parent_item_colon'  => '',
			),
			'can_export'         => true,
			'capability_type'    => 'post',
			'has_archive'        => false,
			'hierarchical'       => false,
			'map_meta_cap'       => true,
			'public'             => false,
			'publicly_queryable' => $publicly_queryable,
			'query_var'          => false,
			'show_ui'            => false,
			'show_in_rest'       => false,
			'rewrite'            => true,
			'supports'           => array( 'title', 'editor', 'author', 'revisions', 'comments' ),
		)
	);

	register_post_type(
		ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE,
		array(
			'labels'             => array(
				'name'               => esc_html__( 'Footer Templates', 'et_builder' ),
				'singular_name'      => esc_html__( 'Footer Template', 'et_builder' ),
				'add_new'            => esc_html__( 'Add New', 'et_builder' ),
				'add_new_item'       => esc_html__( 'Add New Footer Template', 'et_builder' ),
				'edit_item'          => esc_html__( 'Edit Footer Template', 'et_builder' ),
				'new_item'           => esc_html__( 'New Footer Template', 'et_builder' ),
				'all_items'          => esc_html__( 'All Footer Templates', 'et_builder' ),
				'view_item'          => esc_html__( 'View Footer Template', 'et_builder' ),
				'search_items'       => esc_html__( 'Search Footer Templates', 'et_builder' ),
				'not_found'          => esc_html__( 'Nothing found', 'et_builder' ),
				'not_found_in_trash' => esc_html__( 'Nothing found in Trash', 'et_builder' ),
				'parent_item_colon'  => '',
			),
			'can_export'         => true,
			'capability_type'    => 'post',
			'has_archive'        => false,
			'hierarchical'       => false,
			'map_meta_cap'       => true,
			'public'             => false,
			'publicly_queryable' => $publicly_queryable,
			'query_var'          => false,
			'show_ui'            => false,
			'show_in_rest'       => false,
			'rewrite'            => true,
			'supports'           => array( 'title', 'editor', 'author', 'revisions', 'comments' ),
		)
	);
}
add_action( 'init', 'et_theme_builder_register_entities', 11 );

/**
 * Get array of post types that can be layouts within templates.
 *
 * @since 4.0
 *
 * @return string[]
 */
function et_theme_builder_get_layout_post_types() {
	return array(
		ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE,
		ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE,
		ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE,
	);
}

/**
 * Convert 'header', 'body', 'footer' to the appropriate layout post type name.
 *
 * @since 4.0
 *
 * @param string $layout_type
 *
 * @return string
 */
function et_theme_builder_get_valid_layout_post_type( $layout_type ) {
	$map = array(
		'header' => ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE,
		'body'   => ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE,
		'footer' => ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE,
	);

	if ( ! isset( $map[ $layout_type ] ) ) {
		return '';
	}

	return $map[ $layout_type ];
}

/**
 * Get whether post type is a Theme Builder layout type.
 *
 * @since 4.0
 *
 * @param string $post_type
 *
 * @return boolean
 */
function et_theme_builder_is_layout_post_type( $post_type ) {
	return in_array( $post_type, et_theme_builder_get_layout_post_types(), true );
}

/**
 * Get list of post content module slugs.
 *
 * @since 4.0
 *
 * @return string[]
 */
function et_theme_builder_get_post_content_modules() {
	return array( 'et_pb_post_content', 'et_pb_fullwidth_post_content' );
}

/**
 * Filter post types with builder support by default.
 *
 * @since 4.0
 *
 * @param $post_types
 *
 * @return array
 */
function et_theme_builder_filter_builder_default_post_types( $post_types ) {
	return array_merge( $post_types, et_theme_builder_get_layout_post_types() );
}
add_filter( 'et_builder_default_post_types', 'et_theme_builder_filter_builder_default_post_types' );
add_filter( 'et_library_builder_post_types', 'et_theme_builder_filter_builder_default_post_types' );

/**
 * Filter post types which should be blocklisted from appearing as options when enabling/disabling the builder.
 *
 * @param $post_types
 *
 * @return array
 */
function et_theme_builder_filter_builder_post_type_options_blocklist( $post_types ) {
	return array_merge(
		$post_types,
		et_theme_builder_get_layout_post_types(),
		array( ET_THEME_BUILDER_TEMPLATE_POST_TYPE )
	);
}
add_filter( 'et_builder_post_type_options_blocklist', 'et_theme_builder_filter_builder_default_post_types' );

/**
 * Filter builder status for template area posts.
 *
 * @since 4.0
 *
 * @param $enabled
 * @param $post_id
 *
 * @return bool
 */
function et_theme_builder_filter_enable_builder_for_post_types( $enabled, $post_id ) {
	$post_type = get_post_type( $post_id );

	if ( et_theme_builder_is_layout_post_type( $post_type ) ) {
		$enabled = true;
	}

	return $enabled;
}
add_filter( 'et_builder_fb_enabled_for_post', 'et_theme_builder_filter_enable_builder_for_post_types', 10, 2 );

/**
 * Get the theme builder post.
 *
 * @since 4.0
 *
 * @param boolean $live Get the live version or the draft one.
 * @param boolean $create Create the post if it does not exist.
 *
 * @return integer
 */
function et_theme_builder_get_theme_builder_post_id( $live, $create = true ) {
	$status = $live ? 'publish' : 'auto-draft';
	$query  = new WP_Query(
		array(
			'post_type'              => ET_THEME_BUILDER_THEME_BUILDER_POST_TYPE,
			'post_status'            => $status,
			'posts_per_page'         => 1,
			'orderby'                => 'date',
			'order'                  => 'desc',
			'fields'                 => 'ids',
			'no_found_rows'          => true,
			'update_post_meta_cache' => false,
			'update_post_term_cache' => false,
		)
	);

	if ( ! empty( $query->posts ) ) {
		return $query->posts[0];
	}

	if ( ! $create ) {
		return 0;
	}

	$post_id = wp_insert_post(
		array(
			'post_type'   => ET_THEME_BUILDER_THEME_BUILDER_POST_TYPE,
			'post_status' => $status,
			'post_title'  => 'Theme Builder',
		)
	);

	return $post_id;
}

/**
 * Get the theme builder post's template IDs.
 *
 * @since 4.0
 *
 * @param boolean $live Get the live version or the draft one.
 *
 * @return integer[]
 */
function et_theme_builder_get_theme_builder_template_ids( $live ) {
	$post_id = et_theme_builder_get_theme_builder_post_id( $live, false );

	// Try to get the template Ids from the backup.
	// that maybe stored during saving templates process.
	// @see et_theme_builder_api_save function.
	$template_ids = get_option( 'et_tb_templates_backup_' . $post_id, false );

	// If there is no backup available, then query the post meta.
	if ( false === $template_ids ) {
		$template_ids = get_post_meta( $post_id, '_et_template', false );
	}

	$template_ids = is_array( $template_ids ) ? $template_ids : array();
	$template_ids = array_map( 'intval', $template_ids );

	return $template_ids;
}

/**
 * Get the theme builder post's templates.
 *
 * @since 4.0
 *
 * @param boolean $live Get the live version or the draft one.
 *
 * @return array
 */
function et_theme_builder_get_theme_builder_templates( $live ) {
	return array_filter(
		array_map(
			'et_theme_builder_get_template',
			et_theme_builder_get_theme_builder_template_ids( $live )
		)
	);
}

/**
 * Get a template.
 * Returns an empty array if the template is not found.
 *
 * @since 4.0
 *
 * @param integer $template_id
 *
 * @return array
 */
function et_theme_builder_get_template( $template_id ) {
	$post = get_post( $template_id );

	if ( null === $post || ET_THEME_BUILDER_TEMPLATE_POST_TYPE !== $post->post_type ) {
		return array();
	}

	$autogenerated_title = '1' === get_post_meta( $template_id, '_et_autogenerated_title', true );
	$header_id           = (int) get_post_meta( $post->ID, '_et_header_layout_id', true );
	$header_enabled      = get_post_meta( $post->ID, '_et_header_layout_enabled', true ) === '1';
	$body_id             = (int) get_post_meta( $post->ID, '_et_body_layout_id', true );
	$body_enabled        = get_post_meta( $post->ID, '_et_body_layout_enabled', true ) === '1';
	$footer_id           = (int) get_post_meta( $post->ID, '_et_footer_layout_id', true );
	$footer_enabled      = get_post_meta( $post->ID, '_et_footer_layout_enabled', true ) === '1';
	$use_on              = get_post_meta( $post->ID, '_et_use_on', false );
	$exclude_from        = get_post_meta( $post->ID, '_et_exclude_from', false );

	return array(
		'id'           => $post->ID,
		'default'      => get_post_meta( $post->ID, '_et_default', true ) === '1',
		'enabled'      => get_post_meta( $post->ID, '_et_enabled', true ) === '1',
		'title'        => $autogenerated_title ? '' : $post->post_title,
		'layouts'      => array(
			'header' => array(
				'id'       => $header_id,
				'enabled'  => $header_enabled,
				'override' => 0 !== $header_id || false === $header_enabled,
			),
			'body'   => array(
				'id'       => $body_id,
				'enabled'  => $body_enabled,
				'override' => 0 !== $body_id || false === $body_enabled,
			),
			'footer' => array(
				'id'       => $footer_id,
				'enabled'  => $footer_enabled,
				'override' => 0 !== $footer_id || false === $footer_enabled,
			),
		),
		'use_on'       => is_array( $use_on ) ? $use_on : array(),
		'exclude_from' => is_array( $exclude_from ) ? $exclude_from : array(),
	);
}

/**
 * Trash the theme builder draft and any unused theme builder templates and layouts.
 *
 * @since 4.0
 *
 * @return void
 */
function et_theme_builder_trash_draft_and_unused_posts() {
	$mark_meta_key = '_et_theme_builder_marked_as_unused';
	$live_id       = et_theme_builder_get_theme_builder_post_id( true, false );
	$draft_id      = et_theme_builder_get_theme_builder_post_id( false, false );

	if ( $draft_id > 0 ) {
		wp_trash_post( $draft_id );
	}

	$used_templates = get_post_meta( $live_id, '_et_template', false );
	$used_templates = is_array( $used_templates ) ? array_map( 'intval', $used_templates ) : array();
	$used_posts     = array();

	foreach ( $used_templates as $template_id ) {
		$used_posts[] = $template_id;
		$used_posts[] = (int) get_post_meta( $template_id, '_et_header_layout_id', true );
		$used_posts[] = (int) get_post_meta( $template_id, '_et_body_layout_id', true );
		$used_posts[] = (int) get_post_meta( $template_id, '_et_footer_layout_id', true );
	}

	$used_posts = array_filter( $used_posts );

	// Unmark all used posts.
	foreach ( $used_posts as $post_id ) {
		delete_post_meta( $post_id, $mark_meta_key );
	}

	// Mark unreferenced layouts for trashing.
	$posts_to_mark = new WP_Query(
		array(
			'post_type'              => array(
				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,
			),
			'post__not_in'           => $used_posts,
			'posts_per_page'         => -1,
			'fields'                 => 'ids',
			'meta_query'             => array(
				array(
					'key'     => $mark_meta_key,
					'compare' => 'NOT EXISTS',
					'value'   => 'https://core.trac.wordpress.org/ticket/23268',
				),
			),
			'no_found_rows'          => true,
			'update_post_meta_cache' => false,
			'update_post_term_cache' => false,
		)
	);

	foreach ( $posts_to_mark->posts as $post_id ) {
		update_post_meta( $post_id, $mark_meta_key, date( 'Y-m-d H:i:s' ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- No need to use gmdate.
	}

	// Trash any posts marked more than 7 days ago.
	// We only trash up to 50 posts at a time in order to avoid performance issues.
	// Any leftover posts will be cleaned up eventually whenever this is called again.
	$posts_to_trash = new WP_Query(
		array(
			'post_type'              => array(
				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,
			),
			'posts_per_page'         => 50,
			'fields'                 => 'ids',
			'meta_query'             => array(
				array(
					'key'     => $mark_meta_key,
					'compare' => '<',
					'value'   => date( 'Y-m-d H:i:s', time() - 60 * 60 * 24 * 7 ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date -- No need to use gmdate.
					'type'    => 'DATE',
				),
			),
			'no_found_rows'          => true,
			'update_post_meta_cache' => false,
			'update_post_term_cache' => false,
		)
	);

	foreach ( $posts_to_trash->posts as $post_id ) {
		wp_trash_post( $post_id );
	}
}

/**
 * Get the template settings options for a given post type.
 *
 * @since 4.0
 *
 * @param string $post_type_name
 *
 * @return array
 */
function et_theme_builder_get_template_settings_options_for_post_type( $post_type_name ) {
	$post_type = get_post_type_object( $post_type_name );

	if ( null === $post_type ) {
		return array();
	}

	$post_type_plural = ucwords( $post_type->labels->name );
	$taxonomies       = get_object_taxonomies( $post_type_name, 'objects' );

	$group = array(
		'label'    => et_core_intentionally_unescaped( $post_type_plural, 'react_jsx' ),
		'settings' => array(
			array(
				'id'       => implode(
					ET_THEME_BUILDER_SETTING_SEPARATOR,
					array( 'singular', 'post_type', $post_type_name, 'all' )
				),
				// Translators: %1$s: Post type plural name.
				'label'    => et_core_intentionally_unescaped( sprintf( __( 'All %1$s', 'et_builder' ), $post_type_plural ), 'react_jsx' ),
				'priority' => 70,
				'validate' => 'et_theme_builder_template_setting_validate_singular_post_type_all',
			),
		),
	);

	if ( 'page' === $post_type_name ) {
		$group['settings'][] = array(
			'id'       => 'homepage',
			'label'    => et_core_intentionally_unescaped( __( 'Homepage', 'et_builder' ), 'react_jsx' ),
			'priority' => 110,
			'validate' => 'et_theme_builder_template_setting_validate_homepage',
		);
	} elseif ( 'post' === $post_type_name || $post_type->has_archive ) {
		$group['settings'][] = array(
			'id'       => implode(
				ET_THEME_BUILDER_SETTING_SEPARATOR,
				array( 'archive', 'post_type', $post_type_name )
			),
			'label'    => 'post' === $post_type_name
				? et_core_intentionally_unescaped( __( 'Blog', 'et_builder' ), 'react_jsx' )
				// Translators: %1$s: Post type plural name.
				: et_core_intentionally_unescaped( sprintf( __( '%1$s Archive Page', 'et_builder' ), $post_type_plural ), 'react_jsx' ),
			'title'    => trim( str_replace( home_url(), '', get_post_type_archive_link( $post_type_name ) ), '/' ),
			'priority' => 60,
			'validate' => 'et_theme_builder_template_setting_validate_archive_post_type',
		);
	}

	foreach ( $taxonomies as $taxonomy ) {
		/**
		 * Filters whether the given taxonomy should be used to generate the following template settings:
		 * - Posts with Specific %
		 *
		 * @since 4.3.3
		 *
		 * @param boolean $show
		 */
		$show = apply_filters( 'et_theme_builder_template_settings_options_posts_with_specific_term', $taxonomy->show_ui );

		if ( ! $show ) {
			continue;
		}

		$taxonomy_plural  = ucwords( $taxonomy->labels->name );
		$use_short_plural = in_array(
			$taxonomy->name,
			array(
				'project_category',
				'project_tag',
				'product_cat',
				'product_tag',
			),
			true
		);

		// Translators: %1$s: Post type plural name; %2$s: Taxonomy plural name.
		$label = et_core_intentionally_unescaped(
			sprintf(
				__( '%1$s with Specific %2$s', 'et_builder' ),
				$post_type_plural,
				$use_short_plural ? esc_html__( 'Tags', 'et_builder' ) : $taxonomy_plural
			),
			'react_jsx'
		);

		if ( in_array( $taxonomy->name, array( 'category', 'project_category', 'product_cat' ), true ) ) {
			// Translators: %1$s: Post type plural name; %2$s: Taxonomy plural name.
			$label = et_core_intentionally_unescaped(
				sprintf(
					__( '%1$s in Specific %2$s', 'et_builder' ),
					$post_type_plural,
					$use_short_plural ? esc_html__( 'Categories', 'et_builder' ) : $taxonomy_plural
				),
				'react_jsx'
			);
		}

		$group['settings'][] = array(
			'id'       => implode(
				ET_THEME_BUILDER_SETTING_SEPARATOR,
				array( 'singular', 'taxonomy', $taxonomy->name, 'term', 'id', '' )
			),
			// Translators: %1$s: Post type plural name; %2$s: Taxonomy plural name.
			'label'    => $label,
			'priority' => 80,
			'validate' => 'et_theme_builder_template_setting_validate_singular_taxonomy_term_id',
			'options'  => array(
				'label' => $taxonomy_plural,
				'type'  => 'taxonomy',
				'value' => $taxonomy->name,
			),
		);
	}

	$group['settings'][] = array(
		'id'       => implode(
			ET_THEME_BUILDER_SETTING_SEPARATOR,
			array( 'singular', 'post_type', $post_type_name, 'id', '' )
		),
		// Translators: %1$s: Post type plural name.
		'label'    => et_core_intentionally_unescaped( sprintf( __( 'Specific %1$s', 'et_builder' ), $post_type_plural ), 'react_jsx' ),
		'priority' => 100,
		'validate' => 'et_theme_builder_template_setting_validate_singular_post_type_id',
		'options'  => array(
			'label' => $post_type_plural,
			'type'  => 'post_type',
			'value' => $post_type_name,
		),
	);

	if ( is_post_type_hierarchical( $post_type_name ) ) {
		$group['settings'][] = array(
			'id'       => implode(
				ET_THEME_BUILDER_SETTING_SEPARATOR,
				array( 'singular', 'post_type', $post_type_name, 'children', 'id', '' )
			),
			// Translators: %1$s: Post type plural name.
			'label'    => et_core_intentionally_unescaped( sprintf( __( 'Children of Specific %1$s', 'et_builder' ), $post_type_plural ), 'react_jsx' ),
			'priority' => 90,
			'validate' => 'et_theme_builder_template_setting_validate_singular_post_type_children_id',
			'options'  => array(
				'label' => $post_type_plural,
				'type'  => 'post_type',
				'value' => $post_type_name,
			),
		);
	}

	return $group;
}


/**
 * Get the template settings options for all archive pages.
 *
 * @since 4.0
 *
 * @return array
 */
function et_theme_builder_get_template_settings_options_for_archive_pages() {
	$taxonomies = get_taxonomies(
		array(
			'public'   => true,
			'show_ui'  => true,
			'_builtin' => false,
		),
		'objects'
	);

	ksort( $taxonomies );

	$taxonomies = array_merge(
		array(
			'category' => get_taxonomy( 'category' ),
			'post_tag' => get_taxonomy( 'post_tag' ),
		),
		$taxonomies
	);

	$group = array(
		'label'    => et_core_intentionally_unescaped( __( 'Archive Pages', 'et_builder' ), 'react_jsx' ),
		'settings' => array(
			array(
				'id'       => implode(
					ET_THEME_BUILDER_SETTING_SEPARATOR,
					array( 'archive', 'all' )
				),
				'label'    => et_core_intentionally_unescaped( __( 'All Archive Pages', 'et_builder' ), 'react_jsx' ),
				'priority' => 30,
				'validate' => 'et_theme_builder_template_setting_validate_archive_all',
			),
		),
	);

	foreach ( $taxonomies as $taxonomy ) {
		/**
		 * Filters whether the given taxonomy should be used to generate the following template settings:
		 * - All % Pages
		 * - Specific % Pages
		 *
		 * @since 4.3.3
		 *
		 * @param boolean $show
		 */
		$show = apply_filters( 'et_theme_builder_template_settings_options_term_pages', $taxonomy->public && $taxonomy->show_ui );

		if ( ! $show ) {
			continue;
		}

		$taxonomy_plural = ucwords( $taxonomy->labels->name );
		$taxonomy_name   = $taxonomy_plural;

		if ( 'product_cat' === $taxonomy->name ) {
			// WooCommerce registers Product Categories with a singular name of Category instead of Product Category...
			$taxonomy_name = __( 'Product Category', 'et_builder' );
		} elseif ( false !== strpos( $taxonomy->name, 'cat' ) ) {
			// Use singular for Category.
			$taxonomy_name = ucwords( $taxonomy->labels->singular_name );
		}

		$group['settings'][] = array(
			'id'       => implode(
				ET_THEME_BUILDER_SETTING_SEPARATOR,
				array( 'archive', 'taxonomy', $taxonomy->name, 'all' )
			),
			// Translators: %1$s: Taxonomy name.
			'label'    => et_core_intentionally_unescaped( sprintf( __( 'All %1$s Pages', 'et_builder' ), $taxonomy_name ), 'react_jsx' ),
			'priority' => 70,
			'validate' => 'et_theme_builder_template_setting_validate_archive_taxonomy_all',
		);

		$group['settings'][] = array(
			'id'       => implode(
				ET_THEME_BUILDER_SETTING_SEPARATOR,
				array( 'archive', 'taxonomy', $taxonomy->name, 'term', 'id', '' )
			),
			// Translators: %1$s: Taxonomy name.
			'label'    => et_core_intentionally_unescaped( sprintf( __( 'Specific %1$s Pages', 'et_builder' ), $taxonomy_name ), 'react_jsx' ),
			'priority' => 75,
			'validate' => 'et_theme_builder_template_setting_validate_archive_taxonomy_term_id',
			'options'  => array(
				'label' => $taxonomy_plural,
				'type'  => 'taxonomy',
				'value' => $taxonomy->name,
			),
		);
	}

	$group['settings'][] = array(
		'id'       => implode(
			ET_THEME_BUILDER_SETTING_SEPARATOR,
			array( 'archive', 'user', 'all' )
		),
		'label'    => et_core_intentionally_unescaped( __( 'All Author Pages', 'et_builder' ), 'react_jsx' ),
		'priority' => 50,
		'validate' => 'et_theme_builder_template_setting_validate_archive_user_all',
	);

	$group['settings'][] = array(
		'id'       => implode(
			ET_THEME_BUILDER_SETTING_SEPARATOR,
			array( 'archive', 'user', 'id', '' )
		),
		'label'    => et_core_intentionally_unescaped( __( 'Specific Author Page', 'et_builder' ), 'react_jsx' ),
		'priority' => 55,
		'validate' => 'et_theme_builder_template_setting_validate_archive_user_id',
		'options'  => array(
			'label' => et_core_intentionally_unescaped( __( 'Users', 'et_builder' ), 'react_jsx' ),
			'type'  => 'user',
			'value' => '',
		),
	);

	$group['settings'][] = array(
		'id'       => implode(
			ET_THEME_BUILDER_SETTING_SEPARATOR,
			array( 'archive', 'user', 'role', '' )
		),
		'label'    => et_core_intentionally_unescaped( __( 'Specific Author Page By Role', 'et_builder' ), 'react_jsx' ),
		'priority' => 53,
		'validate' => 'et_theme_builder_template_setting_validate_archive_user_role',
		'options'  => array(
			'label' => et_core_intentionally_unescaped( __( 'Roles', 'et_builder' ), 'react_jsx' ),
			'type'  => 'user_role',
			'value' => '',
		),
	);

	$group['settings'][] = array(
		'id'       => implode(
			ET_THEME_BUILDER_SETTING_SEPARATOR,
			array( 'archive', 'date', 'all' )
		),
		'label'    => et_core_intentionally_unescaped( __( 'All Date Pages', 'et_builder' ), 'react_jsx' ),
		'priority' => 40,
		'validate' => 'et_theme_builder_template_setting_validate_archive_date_all',
	);

	$_utils = ET_Core_Data_Utils::instance();

	// Order settings alphabetically by label.
	$group['settings'] = $_utils->array_sort_by( $group['settings'], 'label' );

	return $group;
}

/**
 * Get array of template setting options.
 * Settings that have children should have a trailing ET_THEME_BUILDER_SETTING_SEPARATOR in their id.
 * Settings that have children should have their id be unique even without the trailing ET_THEME_BUILDER_SETTING_SEPARATOR.
 *
 * @since 4.0
 *
 * @return array
 */
function et_theme_builder_get_template_settings_options() {
	$post_types = get_post_types(
		array(
			'public'   => true,
			'_builtin' => false,
		)
	);
	sort( $post_types );

	$options = array(
		'page'    => et_theme_builder_get_template_settings_options_for_post_type( 'page' ),
		'post'    => et_theme_builder_get_template_settings_options_for_post_type( 'post' ),
		'archive' => et_theme_builder_get_template_settings_options_for_archive_pages(),
	);

	foreach ( $post_types as $post_type_name ) {
		$options[ $post_type_name ] = et_theme_builder_get_template_settings_options_for_post_type( $post_type_name );
	}

	$options['other'] = array(
		'label'    => et_core_intentionally_unescaped( __( 'Other', 'et_builder' ), 'react_jsx' ),
		'settings' => array(
			array(
				'id'       => 'search',
				'label'    => et_core_intentionally_unescaped( __( 'Search Results', 'et_builder' ), 'react_jsx' ),
				'priority' => 1,
				'validate' => 'et_theme_builder_template_setting_validate_search',
			),
			array(
				'id'       => '404',
				'label'    => et_core_intentionally_unescaped( __( '404 Page', 'et_builder' ), 'react_jsx' ),
				'priority' => 1,
				'validate' => 'et_theme_builder_template_setting_validate_404',
			),
		),
	);

	/**
	 * Filters available template settings options.
	 *
	 * @since 4.0
	 *
	 * @param array
	 */
	$options = apply_filters( 'et_theme_builder_template_settings_options', $options );

	return $options;
}

/**
 * Get flat array of template setting options from the current live and draft theme builder posts.
 *
 * @since 4.0
 *
 * @return array[]
 */
function et_theme_builder_get_template_settings_options_for_preloading() {
	$templates   = array_unique(
		array_merge(
			et_theme_builder_get_theme_builder_template_ids( true ),
			et_theme_builder_get_theme_builder_template_ids( false )
		)
	);
	$setting_ids = array();

	foreach ( $templates as $template_id ) {
		$use_on       = get_post_meta( $template_id, '_et_use_on', false );
		$exclude_from = get_post_meta( $template_id, '_et_exclude_from', false );

		if ( ! is_array( $use_on ) ) {
			$use_on = array();
		}

		if ( ! is_array( $exclude_from ) ) {
			$exclude_from = array();
		}

		$setting_ids = array_merge( $setting_ids, $use_on, $exclude_from );
	}

	return et_theme_builder_load_template_setting_options( array_unique( $setting_ids ) );
}

/**
 * Sanitize an array of use_on/exclude_from conditions stripping out invalid ones.
 *
 * @since 4.0
 *
 * @param string[] $setting_ids
 *
 * @return string[]
 */
function et_theme_builder_load_template_setting_options( $setting_ids ) {
	$flat_parent_settings = et_theme_builder_get_flat_template_settings_options();
	$groups               = array();

	foreach ( $setting_ids as $setting_id ) {
		$parent_id = explode( ET_THEME_BUILDER_SETTING_SEPARATOR, $setting_id );
		$entity_id = implode( '', array_slice( $parent_id, -1 ) );
		$parent_id = array_slice( $parent_id, 0, -1 );
		$parent_id = implode( ET_THEME_BUILDER_SETTING_SEPARATOR, $parent_id ) . ET_THEME_BUILDER_SETTING_SEPARATOR;

		if ( ! isset( $flat_parent_settings[ $parent_id ] ) ) {
			// Top-level, invalid or unknown setting.
			continue;
		}

		if ( ! isset( $groups[ $parent_id ] ) ) {
			$groups[ $parent_id ] = array(
				'parent'   => $flat_parent_settings[ $parent_id ],
				'settings' => array(),
			);
		}

		$groups[ $parent_id ]['settings'][ $setting_id ] = $entity_id;
	}

	$settings = array();

	foreach ( $groups as $parent_id => $group ) {
		$settings = array_merge(
			$settings,
			et_theme_builder_get_template_setting_child_options( $group['parent'], $group['settings'] )
		);
	}

	return $settings;
}

/**
 * Get a flat array of template setting options.
 *
 * @since 4.0
 *
 * @return array
 */
function et_theme_builder_get_flat_template_settings_options() {
	$settings = et_theme_builder_get_template_settings_options();
	$flat     = array();

	foreach ( $settings as $group ) {
		foreach ( $group['settings'] as $setting ) {
			$flat[ $setting['id'] ] = $setting;
		}
	}

	return $flat;
}

function et_theme_builder_get_template_setting_child_options( $parent, $include = array(), $search = '', $page = 1, $per_page = 30 ) {
	$include = array_map( 'intval', $include );

	if ( ! empty( $include ) ) {
		$search   = '';
		$page     = 1;
		$per_page = -1;
	}

	$page   = $page >= 1 ? $page : 1;
	$values = array();

	/**
	 * Fires before loading child options from the database.
	 *
	 * @since 4.2
	 *
	 * @param string $parent_id
	 * @param string $child_type
	 * @param string $child_value
	 */
	do_action( 'et_theme_builder_before_get_template_setting_child_options', $parent['id'], $parent['options']['type'], $parent['options']['value'] );

	switch ( $parent['options']['type'] ) {
		case 'post_type':
			$posts = get_posts(
				array(
					'post_type'      => $parent['options']['value'],
					'post_status'    => 'any',
					'post__in'       => $include,
					's'              => $search,
					'posts_per_page' => $per_page,
					'paged'          => $page,
				)
			);

			foreach ( $posts as $post ) {
				$id            = $parent['id'] . $post->ID;
				$values[ $id ] = array(
					'id'       => $id,
					'parent'   => $parent['id'],
					'label'    => et_core_intentionally_unescaped( $post->post_title, 'react_jsx' ),
					'title'    => et_core_intentionally_unescaped( $post->post_name, 'react_jsx' ),
					'priority' => $parent['priority'],
					'validate' => $parent['validate'],
				);
			}
			break;

		case 'taxonomy':
			$terms = get_terms(
				array(
					'taxonomy'   => $parent['options']['value'],
					'hide_empty' => false,
					'include'    => $include,
					'search'     => $search,
					'number'     => -1 === $per_page ? false : $per_page,
					'offset'     => -1 !== $per_page ? ( $page - 1 ) * $per_page : 0,
				)
			);

			foreach ( $terms as $term ) {
				$id            = $parent['id'] . $term->term_id;
				$values[ $id ] = array(
					'id'       => $id,
					'parent'   => $parent['id'],
					'label'    => et_core_intentionally_unescaped( $term->name, 'react_jsx' ),
					'title'    => et_core_intentionally_unescaped( $term->slug, 'react_jsx' ),
					'priority' => $parent['priority'],
					'validate' => $parent['validate'],
				);
			}
			break;

		case 'user':
			$users = get_users(
				array(
					'include' => $include,
					'search'  => $search,
					'number'  => $per_page,
					'paged'   => $page,
				)
			);

			foreach ( $users as $user ) {
				$id            = $parent['id'] . $user->ID;
				$values[ $id ] = array(
					'id'       => $id,
					'parent'   => $parent['id'],
					'label'    => et_core_intentionally_unescaped( $user->display_name, 'react_jsx' ),
					'title'    => et_core_intentionally_unescaped( $user->user_login, 'react_jsx' ),
					'priority' => $parent['priority'],
					'validate' => $parent['validate'],
				);
			}
			break;

		case 'user_role':
			$roles = wp_roles()->get_names();

			foreach ( $roles as $role => $label ) {
				$id            = $parent['id'] . $role;
				$values[ $id ] = array(
					'id'       => $id,
					'parent'   => $parent['id'],
					'label'    => et_core_intentionally_unescaped( $label, 'react_jsx' ),
					'title'    => et_core_intentionally_unescaped( $role, 'react_jsx' ),
					'priority' => $parent['priority'],
					'validate' => $parent['validate'],
				);
			}
			break;
	}

	/**
	 * Fires after loading child options from the database.
	 *
	 * @since 4.2
	 *
	 * @param string $parent_id
	 * @param string $child_type
	 * @param string $child_value
	 */
	do_action( 'et_theme_builder_after_get_template_setting_child_options', $parent['id'], $parent['options']['type'], $parent['options']['value'] );

	return $values;
}

/**
 * Get the template and its layouts, if any, for the given request.
 *
 * @since 4.0
 *
 * @param ET_Theme_Builder_Request $request Request to check against. Defaults to the current one.
 * @param bool                     $cache Cache the result or not, regardless of whether any layouts should be loaded.
 * @param bool                     $load_from_cache Load the cached result for the given post ID, if available.
 *
 * @return array Array of layouts or an empty array if no layouts should be loaded.
 */
function et_theme_builder_get_template_layouts( $request = null, $cache = true, $load_from_cache = true ) {
	static $store = array();

	if ( null === $request ) {
		if ( is_embed() ) {
			// Ignore TB templates when displaying posts intended for embedding.
			return array();
		}

		if ( is_et_pb_preview() ) {
			// Ignore TB templates when previewing.
			return array();
		}

		$request = ET_Theme_Builder_Request::from_current();
	}

	if ( null === $request || ET_GB_Block_Layout::is_layout_block_preview() ) {
		return array();
	}

	$cache_key = "{$request->get_type()}:{$request->get_subtype()}:{$request->get_id()}";

	if ( $load_from_cache && isset( $store[ $cache_key ] ) ) {
		return $store[ $cache_key ];
	}

	$post_type = ET_Theme_Builder_Request::TYPE_SINGULAR === $request->get_type() ? $request->get_subtype() : '';
	$layouts   = array();

	if ( et_theme_builder_is_layout_post_type( $post_type ) ) {
		// We are currently editing a layout in the VB.
		$layouts = array_replace(
			array(
				ET_THEME_BUILDER_TEMPLATE_POST_TYPE      => 0,
				ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE => array(
					'id'       => 0,
					'enabled'  => false,
					'override' => true,
				),
				ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE   => array(
					'id'       => 0,
					'enabled'  => false,
					'override' => true,
				),
				ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE => array(
					'id'       => 0,
					'enabled'  => false,
					'override' => true,
				),
			),
			array(
				$post_type => array(
					'id'       => $request->get_id(),
					'enabled'  => true,
					'override' => true,
				),
			)
		);
	} else {
		// We are currently displaying a template in the FE.
		$templates   = et_theme_builder_get_theme_builder_templates( true );
		$settings    = et_theme_builder_get_flat_template_settings_options();
		$template    = $request->get_template( $templates, $settings );
		$is_singular = is_singular();

		if ( ! empty( $template ) ) {
			$is_default      = $template['default'];
			$override_header = $template['layouts']['header']['override'];
			$override_body   = $template['layouts']['body']['override'];
			$override_footer = $template['layouts']['footer']['override'];

			// The Default Website Template has a special case - it should not take over if
			// it does not override any areas otherwise it will take over ALL site pages.
			if ( ! $is_default || $override_header || $override_body || $override_footer ) {
				$layouts = array(
					ET_THEME_BUILDER_TEMPLATE_POST_TYPE    => $is_singular ? $template['id'] : false,
					ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE => $template['layouts']['header'],
					ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE => $template['layouts']['body'],
					ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE => $template['layouts']['footer'],
				);
			}
		}
	}

	/**
	 * Filter template layouts.
	 *
	 * @since 4.0
	 *
	 * @param array $layouts
	 */
	$layouts = apply_filters( 'et_theme_builder_template_layouts', $layouts );

	// Add AB Subjects array.
	foreach ( $layouts as $key => $layout ) {
		if ( is_array( $layout ) && $layout['override'] ) {
			$layouts[ $key ]['et_pb_ab_subjects'] = et_pb_ab_get_subjects( $layout['id'] );
		}
	}

	if ( $cache ) {
		$store[ $cache_key ] = $layouts;
	}

	return $layouts;
}

/**
 * Get whether TB overrides the specified layout for the current request.
 *
 * @since 4.0.6
 *
 * @param string $layout Layout post type.
 *
 * @return boolean
 */
function et_theme_builder_overrides_layout( $layout ) {
	$layouts = et_theme_builder_get_template_layouts();

	return ! empty( $layouts ) && $layouts[ $layout ]['override'];
}

/**
 * Get whether the specified layout will properly render the real post content.
 *
 * @since 4.0
 *
 * @param array $layout
 *
 * @return boolean
 */
function et_theme_builder_layout_has_post_content( $layout ) {
	if ( ! $layout['override'] ) {
		// The layout does not override the content so post content will render.
		return true;
	}

	if ( $layout['enabled'] ) {
		$content = get_post_field( 'post_content', $layout['id'] );
		$modules = et_theme_builder_get_post_content_modules();

		foreach ( $modules as $module ) {
			if ( has_shortcode( $content, $module ) ) {
				return true;
			}
		}
	}

	return false;
}

/**
 * Create or update a Theme Builder template.
 *
 * @since 4.0
 *
 * @param integer $theme_builder_id Theme builder ID.
 * @param array   $template         Template.
 * @param boolean $allow_default    Allow default.
 *
 * @return (integer|false) Return false on failure.
 */
function et_theme_builder_store_template( $theme_builder_id, $template, $allow_default ) {
	$_                   = et_();
	$raw_post_id         = $_->array_get( $template, 'id', 0 );
	$post_id             = is_numeric( $raw_post_id ) ? (int) $raw_post_id : 0;
	$title               = sanitize_text_field( $_->array_get( $template, 'title', '' ) );
	$default             = $allow_default && '1' === $_->array_get( $template, 'default', '1' );
	$enabled             = '1' === $_->array_get( $template, 'enabled', '1' );
	$header_id           = (int) $_->array_get( $template, 'layouts.header.id', 0 );
	$header_enabled      = (bool) $_->array_get( $template, 'layouts.header.enabled', true );
	$body_id             = (int) $_->array_get( $template, 'layouts.body.id', 0 );
	$body_enabled        = (bool) $_->array_get( $template, 'layouts.body.enabled', true );
	$footer_id           = (int) $_->array_get( $template, 'layouts.footer.id', 0 );
	$footer_enabled      = (bool) $_->array_get( $template, 'layouts.footer.enabled', true );
	$use_on              = array_map( 'sanitize_text_field', $_->array_get( $template, 'use_on', array() ) );
	$exclude_from        = array_map( 'sanitize_text_field', $_->array_get( $template, 'exclude_from', array() ) );
	$exists              = $post_id > 0 && ET_THEME_BUILDER_TEMPLATE_POST_TYPE === get_post_type( $post_id ) && 'publish' === get_post_status( $post_id );
	$autogenerated_title = '1' === $_->array_get( $template, 'autogenerated_title', '1' );

	if ( ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE !== get_post_type( $header_id ) || 'publish' !== get_post_status( $header_id ) ) {
		$header_id = 0;
	}

	if ( ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE !== get_post_type( $body_id ) || 'publish' !== get_post_status( $body_id ) ) {
		$body_id = 0;
	}

	if ( ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE !== get_post_type( $footer_id ) || 'publish' !== get_post_status( $footer_id ) ) {
		$footer_id = 0;
	}

	if ( $exists ) {
		// Preform update only when needed.
		if ( get_post_field( 'post_title', $post_id, 'raw' ) !== $title ) {
			wp_update_post(
				array(
					'ID'         => $post_id,
					'post_title' => $title,
				)
			);
		}

		// Update layout title for each template.
		et_theme_builder_update_layout_title( $template );
	} else {
		$post_id = wp_insert_post(
			array(
				'post_type'   => ET_THEME_BUILDER_TEMPLATE_POST_TYPE,
				'post_status' => 'publish',
				'post_title'  => $title,
			)
		);
	}

	if ( 0 === $post_id || is_wp_error( $post_id ) ) {
		return false;
	}

	$metas = array(
		'_et_autogenerated_title'   => $autogenerated_title ? '1' : '0',
		'_et_default'               => $default ? '1' : '0',
		'_et_enabled'               => $enabled ? '1' : '0',
		'_et_header_layout_id'      => $header_id,
		'_et_header_layout_enabled' => $header_enabled ? '1' : '0',
		'_et_body_layout_id'        => $body_id,
		'_et_body_layout_enabled'   => $body_enabled ? '1' : '0',
		'_et_footer_layout_id'      => $footer_id,
		'_et_footer_layout_enabled' => $footer_enabled ? '1' : '0',
	);

	foreach ( $metas as $key => $value ) {
		if ( strval( $value ) === strval( get_post_meta( $post_id, $key, true ) ) ) {
			continue;
		}

		update_post_meta( $post_id, $key, $value );
	}

	// Handle _et_use_on meta.
	delete_post_meta( $post_id, '_et_use_on' );
	if ( $use_on ) {
		$use_on_unique = array_unique( $use_on );

		foreach ( $use_on_unique as $condition ) {
			add_post_meta( $post_id, '_et_use_on', $condition );
		}
	}

	// Handle _et_exclude_from meta.
	delete_post_meta( $post_id, '_et_exclude_from' );
	if ( $exclude_from ) {
		$exclude_from_unique = array_unique( $exclude_from );

		foreach ( $exclude_from_unique as $condition ) {
			add_post_meta( $post_id, '_et_exclude_from', $condition );
		}
	}

	return $post_id;
}

/**
 * Sanitize a Theme Builder template.
 *
 * @since 4.0
 *
 * @param array $template
 *
 * @return array
 */
function et_theme_builder_sanitize_template( $template ) {
	$_                   = et_();
	$autogenerated_title = $_->array_get( $template, 'autogenerated_title', '0' );
	$default             = $_->array_get( $template, 'default', '0' );
	$enabled             = $_->array_get( $template, 'enabled', '0' );
	$use_on              = $_->array_get( $template, 'use_on', array() );
	$exclude_from        = $_->array_get( $template, 'exclude_from', array() );
	$header_enabled      = $_->array_get( $template, 'layouts.header.enabled', '1' );
	$body_enabled        = $_->array_get( $template, 'layouts.body.enabled', '1' );
	$footer_enabled      = $_->array_get( $template, 'layouts.footer.enabled', '1' );

	$sanitized = array(
		'title'               => sanitize_text_field( $_->array_get( $template, 'title', '' ) ),
		'autogenerated_title' => true === $autogenerated_title || '1' === $autogenerated_title,
		'default'             => true === $default || '1' === $default,
		'enabled'             => true === $enabled || '1' === $enabled,
		'use_on'              => array_map( 'sanitize_text_field', $use_on ),
		'exclude_from'        => array_map( 'sanitize_text_field', $exclude_from ),
		'layouts'             => array(
			'header' => array(
				'id'      => (int) $_->array_get( $template, 'layouts.header.id', '0' ),
				'enabled' => true === $header_enabled || '1' === $header_enabled,
			),
			'body'   => array(
				'id'      => (int) $_->array_get( $template, 'layouts.body.id', '0' ),
				'enabled' => true === $body_enabled || '1' === $body_enabled,
			),
			'footer' => array(
				'id'      => (int) $_->array_get( $template, 'layouts.footer.id', '0' ),
				'enabled' => true === $footer_enabled || '1' === $footer_enabled,
			),
		),
	);

	return $sanitized;
}

/**
 * Insert a Theme Builder layout post.
 *
 * @since 4.0
 *
 * @param array $options
 *
 * @return integer|WP_Error
 */
function et_theme_builder_insert_layout( $options ) {
	$post_id = wp_insert_post(
		array_merge(
			array(
				'post_status' => 'publish',
				'post_title'  => 'Theme Builder Layout',
			),
			$options
		),
		true
	);

	if ( is_wp_error( $post_id ) ) {
		return $post_id;
	}

	wp_set_object_terms( $post_id, 'layout', 'layout_type', true );
	et_builder_enable_for_post( $post_id );

	return $post_id;
}

/**
 * Overrides cache post_type so that TB custom post types and 'page' share the same files.
 *
 * @since 4.0
 *
 * @param string $post_type
 *
 * @return string.
 */
function et_theme_builder_cache_post_type( $post_type ) {
	if ( et_theme_builder_is_layout_post_type( $post_type ) ) {
		// Use a generic name for all Theme Builder post type modules
		// as they are identical for most practical reasons.
		$post_type = 'page';
	}

	return $post_type;
}
add_filter( 'et_builder_cache_post_type', 'et_theme_builder_cache_post_type' );

/**
 * Decorate a page resource slug based on the current request and TB.
 *
 * @since 4.0.7
 *
 * @param integer|string $post_id
 * @param string         $resource_slug Resource slug.
 *
 * @return string
 */
function et_theme_builder_decorate_page_resource_slug( $post_id, $resource_slug ) {
	if ( ! is_numeric( $post_id ) || ! is_singular() ) {
		return $resource_slug;
	}

	$post_type = get_post_type( (int) $post_id );

	if ( et_theme_builder_is_layout_post_type( $post_type ) ) {
		$resource_slug .= '-tb-for-' . ET_Post_Stack::get_main_post_id();
	} else {
		$layout_types = et_theme_builder_get_layout_post_types();
		$layouts      = et_theme_builder_get_template_layouts();

		foreach ( $layout_types as $type ) {
			if ( ! isset( $layouts[ $type ] ) || ! $layouts[ $type ]['override'] ) {
				continue;
			}

			$resource_slug .= '-tb-' . $layouts[ $type ]['id'];
		}
	}

	return $resource_slug;
}
add_filter( 'et_builder_cache_post_type', 'et_theme_builder_cache_post_type' );

/**
 * Clear cache of 3P caching plugins partially on the posts or all of them.
 *
 * @since 4.5.0
 *
 * @param string|array $post_ids 'all' or array of post IDs.
 *
 * @return void
 */
function et_theme_builder_clear_wp_cache( $post_ids = 'all' ) {
	if ( ! et_pb_detect_cache_plugins() ) {
		return;
	}

	if ( empty( $post_ids ) ) {
		return;
	}

	if ( 'all' === $post_ids ) {
		et_core_clear_wp_cache();
	} elseif ( is_array( $post_ids ) ) {
		foreach ( $post_ids as $post_id ) {
			et_core_clear_wp_cache( $post_id );
		}
	}
}

/**
 * Clear cache of 3P caching plugins fully or partially after TB layouts saved.
 *
 * Clear all the cache when the template updated is:
 * - Default template
 * - Used on archive, 404, or all posts
 * - Non static homepage
 *
 * @since 4.5.0
 *
 * @param int $layout_id
 *
 * @return void
 */
function et_theme_builder_clear_wp_post_cache( $layout_id = '' ) {
	$layout_type = get_post_type( $layout_id );

	if ( ! et_theme_builder_is_layout_post_type( $layout_type ) ) {
		return;
	}

	if ( ! et_pb_detect_cache_plugins() ) {
		return;
	}

	// Get template of current TB layout.
	$template = new WP_Query(
		array(
			'post_type'              => ET_THEME_BUILDER_TEMPLATE_POST_TYPE,
			'post_status'            => 'publish',
			'posts_per_page'         => 1,
			'fields'                 => 'ids',
			'no_found_rows'          => true,
			'update_post_meta_cache' => false,
			'update_post_term_cache' => false,
			'meta_query'             => array(
				'relation' => 'AND',
				array(
					'key'     => '_et_enabled',
					'value'   => '1',
					'compare' => '=',
				),
				array(
					'key'     => "_{$layout_type}_id",
					'value'   => $layout_id,
					'compare' => '=',
				),
				array(
					'key'     => "_{$layout_type}_enabled",
					'value'   => '1',
					'compare' => '=',
				),
				array(
					'key'     => '_et_theme_builder_marked_as_unused',
					'compare' => 'NOT EXISTS',
				),
			),
		)
	);

	if ( ! $template->have_posts() ) {
		return;
	}

	$_                   = et_();
	$template_id         = $_->array_get( $template->posts, '0' );
	$template_use_on     = get_post_meta( $template_id, '_et_use_on', false );
	$is_template_default = '1' === get_post_meta( $template_id, '_et_default', true );

	// Unassigned Template - False or empty _et_use_on means it's unassigned.
	if ( empty( $template_use_on ) ) {
		// Clear All - If the template is 'default' because it's enabled globally.
		if ( $is_template_default ) {
			et_theme_builder_clear_wp_cache( 'all' );
		}
		return;
	}

	$target_post_ids = array();

	foreach ( $template_use_on as $location ) {
		$location_pieces = explode( ':', $location );
		$location_first  = $_->array_get( $location_pieces, '0' );
		$location_last   = end( $location_pieces );

		if ( in_array( $location_first, array( 'archive', '404' ) ) || 'all' === $location_last ) {
			// Path: archive:user:id:{user_id}, singular:post_type:{post_type_slug}:all,
			// archive:taxonomy:{taxonomy_name}:all, etc.
			// Clear All - If the template is being used on 'archive:' or ':all' posts.
			$target_post_ids = 'all';
			break;
		} elseif ( 'homepage' === $location_first ) {
			// Path: homepage
			$homepage_id       = (int) get_option( 'page_on_front' );
			$target_post_ids[] = $homepage_id;
			if ( ! $homepage_id ) {
				// Clear All - If the homepage is non static page.
				$target_post_ids = 'all';
				break;
			}
		} elseif ( 'singular' === $location_first ) {
			$singular_type = $_->array_get( $location_pieces, '3' );

			if ( 'id' === $singular_type ) {
				// Path: singular:post_type:{post_type_slug}:id:{post_id}
				$target_post_ids[] = (int) $_->array_get( $location_pieces, '4' );
			} elseif ( 'children' === $singular_type ) {
				// Path: singular:post_type:{post_type_slug}:children:id:{post_id}
				$parent_id       = (int) $_->array_get( $location_pieces, '5' );
				$children_ids    = get_children(
					array(
						'posts_per_page' => -1,
						'post_parent'    => $parent_id,
						'fields'         => 'ids',
					)
				);
				$target_post_ids = array_merge( $target_post_ids, $children_ids );
			} elseif ( 'term' === $singular_type ) {
				// Path: singular:taxonomy:{taxonomy_name}:term:id:{term_id}
				$taxonomy        = $_->array_get( $location_pieces, '2' );
				$taxonomy_object = get_taxonomy( $taxonomy );
				$taxonomy_type   = ! empty( $taxonomy_object->object_type ) ? $_->array_get( $taxonomy_object->object_type, '0' ) : 'post';
				$term_id         = (int) $_->array_get( $location_pieces, '5' );
				$posts_ids       = get_posts(
					array(
						'posts_per_page' => -1,
						'fields'         => 'ids',
						'post_type'      => $taxonomy_type,
						'tax_query'      => array(
							array(
								'taxonomy' => $taxonomy,
								'field'    => 'term_id',
								'terms'    => $term_id,
							),
						),
					)
				);
				$target_post_ids = array_merge( $target_post_ids, $posts_ids );
			}
		} elseif ( 'woocommerce' === $location_first && et_is_woocommerce_plugin_active() && function_exists( 'wc_get_page_id' ) ) {
			// Path: woocommerce:my_account, woocommerce:cart, etc.
			$woocommerce_page    = str_replace( '_', '', $_->array_get( $location_pieces, '1' ) );
			$woocommerce_page_id = wc_get_page_id( $woocommerce_page );
			if ( $woocommerce_page_id ) {
				$target_post_ids[] = $woocommerce_page_id;
			}
		}
	}

	// Remove duplicate posts.
	if ( is_array( $target_post_ids ) ) {
		$target_post_ids = array_unique( $target_post_ids );
	}

	et_theme_builder_clear_wp_cache( $target_post_ids );
}

add_action( 'et_save_post', 'et_theme_builder_clear_wp_post_cache' );

/**
 *
 * Update layout title for each template
 *
 * @param array $template Theme Builder Template.
 */
function et_theme_builder_update_layout_title( $template ) {
	if ( empty( $template['layouts'] ) ) {
		return;
	}

	foreach ( $template['layouts'] as $layout ) {
		$layout_id = (int) $layout['id'];

		if ( ! $layout_id ) {
			continue;
		}

		wp_update_post(
			array(
				'ID'         => $layout_id,
				'post_title' => sanitize_text_field( $template['title'] ),
			)
		);
	}
}
