: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* Get a user-friendly custom field label for the given meta key.
function et_builder_get_dynamic_content_custom_field_label( $key ) {
$label = str_replace( array( '_', '-' ), ' ', $key );
$label = ucwords( $label );
* Get all dynamic content fields in a given string.
function et_builder_get_dynamic_contents( $content ) {
$is_matched = preg_match_all( ET_THEME_BUILDER_DYNAMIC_CONTENT_REGEX, $content, $matches );
* Get all meta keys used as dynamic content in the content of a post.
* @param integer $post_id
function et_builder_get_used_dynamic_content_meta_keys( $post_id ) {
$transient = 'et_builder_dynamic_content_used_meta_keys_' . $post_id;
$used_meta_keys = get_transient( $transient );
if ( false !== $used_meta_keys ) {
// The most used meta keys will change from time to time so we will also retrieve the used meta keys in the layout
// content to make sure that the previously selected meta keys always stay in the list even if they are not in the
// most used meta keys list anymore.
$layout_post = get_post( $post_id );
$used_meta_keys = array();
$dynamic_contents = et_builder_get_dynamic_contents( $layout_post->post_content );
foreach ( $dynamic_contents as $dynamic_content ) {
$dynamic_content = et_builder_parse_dynamic_content( $dynamic_content );
$key = $dynamic_content->get_content();
if ( et_()->starts_with( $key, 'custom_meta_' ) ) {
$meta_key = substr( $key, strlen( 'custom_meta_' ) );
$used_meta_keys[] = $meta_key;
set_transient( $transient, $used_meta_keys, 5 * MINUTE_IN_SECONDS );
* Get most used meta keys on public post types.
* @param integer $post_id
function et_builder_get_most_used_post_meta_keys() {
$most_used_meta_keys = get_transient( 'et_builder_most_used_meta_keys' );
if ( false !== $most_used_meta_keys ) {
return $most_used_meta_keys;
$public_post_types = array_keys( et_builder_get_public_post_types() );
$post_type_placeholders = implode( ',', array_fill( 0, count( $public_post_types ), '%s' ) );
"SELECT DISTINCT pm.meta_key FROM {$wpdb->postmeta} pm
INNER JOIN {$wpdb->posts} p ON ( p.ID = pm.post_id AND p.post_type IN ({$post_type_placeholders}) )
WHERE pm.meta_key NOT LIKE '\_%'
ORDER BY COUNT(pm.meta_key) DESC
$most_used_meta_keys = $wpdb->get_col( $sql );
set_transient( 'et_builder_most_used_meta_keys', $most_used_meta_keys, 5 * MINUTE_IN_SECONDS );
return $most_used_meta_keys;
* Get custom dynamic content fields.
* @param integer $post_id
function et_builder_get_custom_dynamic_content_fields( $post_id ) {
$raw_custom_fields = get_post_meta( $post_id );
$raw_custom_fields = is_array( $raw_custom_fields ) ? $raw_custom_fields : array();
$custom_fields = array();
* Filter post meta accepted as custom field options in dynamic content.
* Post meta prefixed with `_` is considered hidden from dynamic content options by default
* due to its nature as "hidden meta keys". This filter allows third parties to
* circumvent this limitation.
* @param string[] $meta_keys
* @param integer $post_id
$display_hidden_meta_keys = apply_filters( 'et_builder_dynamic_content_display_hidden_meta_keys', array(), $post_id );
// Custom dynamic fields to be displayed on the TB.
if ( et_theme_builder_is_layout_post_type( get_post_type( $post_id ) ) ) {
$raw_custom_fields = array_merge(
array_flip( et_builder_get_most_used_post_meta_keys() ),
array_flip( et_builder_get_used_dynamic_content_meta_keys( $post_id ) )
foreach ( $raw_custom_fields as $key => $values ) {
if ( substr( $key, 0, 1 ) === '_' && ! in_array( $key, $display_hidden_meta_keys ) ) {
// Ignore hidden meta keys.
if ( substr( $key, 0, 3 ) === 'et_' ) {
// Ignore ET meta keys as they are not suitable for dynamic content use.
$label = et_builder_get_dynamic_content_custom_field_label( $key );
* Filter the display label for a custom field.
* @param string $meta_key
$label = apply_filters( 'et_builder_dynamic_content_custom_field_label', $label, $key );
'label' => et_builder_i18n( 'Before' ),
'label' => et_builder_i18n( 'After' ),
'group' => __( 'Custom Fields', 'et_builder' ),
if ( current_user_can( 'unfiltered_html' ) ) {
$field['fields']['enable_html'] = array(
'label' => esc_html__( 'Enable raw HTML', 'et_builder' ),
'type' => 'yes_no_button',
'on' => et_builder_i18n( 'Yes' ),
'off' => et_builder_i18n( 'No' ),
$custom_fields[ "custom_meta_{$key}" ] = $field;
* Filter available custom field options for dynamic content.
* @param array[] $custom_fields
* @param mixed[] $raw_custom_fields
$custom_fields = apply_filters( 'et_builder_custom_dynamic_content_fields', $custom_fields, $post_id, $raw_custom_fields );
* Get all dynamic content fields.
* @param integer $post_id
function et_builder_get_dynamic_content_fields( $post_id, $context ) {
global $__et_dynamic_content_fields_index_map;
$fields = et_builder_get_built_in_dynamic_content_fields( $post_id );
$custom_fields = array();
if ( 'display' === $context || et_pb_is_allowed( 'read_dynamic_content_custom_fields' ) ) {
$custom_fields = et_builder_get_custom_dynamic_content_fields( $post_id );
$all = array_merge( $fields, $custom_fields );
foreach ( $all as $id => $field ) {
$__et_dynamic_content_fields_index_map = array_flip( array_keys( $all ) );
uasort( $all, 'et_builder_sort_dynamic_content_fields' );
$__et_dynamic_content_fields_index_map = array();
* Sort dynamic content fields.
function et_builder_sort_dynamic_content_fields( $a, $b ) {
global $__et_dynamic_content_fields_index_map;
$top = array_flip( array(
__( 'Custom Fields', 'et_builder' ),
$a_group = et_()->array_get( $a, 'group', 'Default' );
$a_is_top = isset( $top[ $a_group ] );
$b_group = et_()->array_get( $b, 'group', 'Default' );
$b_is_top = isset( $top[ $b_group ] );
if ( $a_is_top && ! $b_is_top ) {
if ( ! $a_is_top && $b_is_top ) {
if ( $a_is_top && $b_is_top && $a_group !== $b_group ) {
return $top[ $a_group ] - $top[ $b_group ];
$a_index = $__et_dynamic_content_fields_index_map[ $a['id'] ];
$b_index = $__et_dynamic_content_fields_index_map[ $b['id'] ];
return $a_index - $b_index;
* Get default value for a dynamic content field's setting.
* @param integer $post_id
function et_builder_get_dynamic_attribute_field_default( $post_id, $field, $setting ) {
$_ = ET_Core_Data_Utils::instance();
$fields = et_builder_get_dynamic_content_fields( $post_id, 'edit' );
return $_->array_get( $fields, "$field.fields.$setting.default", '' );
* Resolve dynamic content to a simple value.
* @param integer $post_id
* @param array $overrides
function et_builder_resolve_dynamic_content( $name, $settings, $post_id, $context, $overrides = array(), $is_content = false ) {
* Generic filter for content resolution based on a given field and post.
* @param integer $post_id
* @param array $overrides
$content = apply_filters( 'et_builder_resolve_dynamic_content', '', $name, $settings, $post_id, $context, $overrides );
* Field-specific filter for content resolution based on a given field and post.
* @param integer $post_id
* @param array $overrides
$content = apply_filters( "et_builder_resolve_dynamic_content_{$name}", $content, $settings, $post_id, $context, $overrides );
$content = et_maybe_enable_embed_shortcode( $content, $is_content );
return $is_content ? do_shortcode( $content ) : $content;
* Wrap a dynamic content value with its before/after settings values.
* @param integer $post_id
function et_builder_wrap_dynamic_content( $post_id, $name, $value, $settings ) {
$_ = ET_Core_Data_Utils::instance();
$def = 'et_builder_get_dynamic_attribute_field_default';
$before = $_->array_get( $settings, 'before', $def( $post_id, $name, 'before' ) );
$after = $_->array_get( $settings, 'after', $def( $post_id, $name, 'after' ) );
$tb_post_id = ET_Builder_Element::get_theme_builder_layout_id();
$cap_post_id = $tb_post_id ? $tb_post_id : $post_id;
$user_id = get_post_field( 'post_author', $cap_post_id );
if ( ! user_can( $user_id, 'unfiltered_html' ) ) {
$allowlist = array_merge(
wp_kses_allowed_html( '' ),
$before = wp_kses( $before, $allowlist );
$after = wp_kses( $after, $allowlist );
return $before . $value . $after;
* Resolve built-in dynamic content fields.
* @param integer $post_id
function et_builder_filter_resolve_default_dynamic_content( $content, $name, $settings, $post_id, $context, $overrides ) {
global $shortname, $wp_query;
$_ = ET_Core_Data_Utils::instance();
$def = 'et_builder_get_dynamic_attribute_field_default';
$post = get_post( $post_id );
$author = get_userdata( $post->post_author );
} else if ( is_author() ) {
$author = get_queried_object();
case 'product_title': // Intentional fallthrough.
if ( isset( $overrides[ $name ] ) ) {
$content = $overrides[ $name ];
$content = et_builder_get_current_title( $post_id );
$content = et_core_intentionally_unescaped( $content, 'cap_based_sanitized' );
$words = (int) $_->array_get( $settings, 'words', $def( $post_id, $name, 'words' ) );
$read_more = $_->array_get( $settings, 'read_more_label', $def( $post_id, $name, 'read_more_label' ) );
$content = isset( $overrides[ $name ] ) ? $overrides[ $name ] : get_the_excerpt( $post_id );
$content = wp_trim_words( $content, $words );
if ( ! empty( $read_more ) ) {
' <a href="%1$s">%2$s</a>',
esc_url( get_permalink( $post_id ) ),
$format = $_->array_get( $settings, 'date_format', $def( $post_id, $name, 'date_format' ) );
$custom_format = $_->array_get( $settings, 'custom_date_format', $def( $post_id, $name, 'custom_date_format' ) );
if ( 'default' === $format ) {
if ( 'custom' === $format ) {
$format = $custom_format;
$content = esc_html( get_the_date( $format, $post_id ) );