This repository has been archived on 2022-06-23. You can view files and clone it, but cannot push or open issues or pull requests.
divi/includes/builder/feature/dynamic-content.php

1891 lines
55 KiB
PHP

<?php
/**
* Handle dynamic content.
*
* @package Builder
*/
/**
* Gets the dynamic content fields related to Product post type.
*
* @since 3.29
*
* @return array
*/
function et_builder_get_product_dynamic_content_fields() {
return array(
'product_breadcrumb' => array(
'label' => esc_html__( 'Product Breadcrumb', 'et_builder' ),
'type' => 'text',
),
'product_price' => array(
'label' => esc_html__( 'Product Price', 'et_builder' ),
'type' => 'text',
),
'product_description' => array(
'label' => esc_html__( 'Product Description', 'et_builder' ),
'type' => 'text',
),
'product_short_description' => array(
'label' => esc_html__( 'Product Short Description', 'et_builder' ),
'type' => 'text',
),
'product_reviews_count' => array(
'label' => esc_html__( 'Product Reviews Count', 'et_builder' ),
'type' => 'text',
),
'product_sku' => array(
'label' => esc_html__( 'Product SKU', 'et_builder' ),
'type' => 'text',
),
'product_reviews' => array(
'label' => esc_html__( 'Product Reviews', 'et_builder' ),
'type' => 'text',
'fields' => array(
'enable_title' => array(
'label' => esc_html__( 'Enable Title', 'et_builder' ),
'type' => 'yes_no_button',
'options' => array(
'on' => et_builder_i18n( 'Yes' ),
'off' => et_builder_i18n( 'No' ),
),
'default' => 'on',
),
),
),
'product_additional_information' => array(
'label' => esc_html__( 'Product Additional Information', 'et_builder' ),
'type' => 'text',
'fields' => array(
'enable_title' => array(
'label' => esc_html__( 'Enable Title', 'et_builder' ),
'type' => 'yes_no_button',
'options' => array(
'on' => et_builder_i18n( 'Yes' ),
'off' => et_builder_i18n( 'No' ),
),
'default' => 'on',
),
),
),
'product_reviews_tab' => array(
'label' => esc_html__( 'Product Reviews', 'et_builder' ),
'type' => 'url',
),
);
}
/**
* Get built-in dynamic content fields.
*
* @since 3.17.2
*
* @param integer $post_id Post Id.
*
* @return array[]
*/
function et_builder_get_built_in_dynamic_content_fields( $post_id ) {
$cache_key = 'et_builder_get_built_in_dynamic_content_fields';
if ( et_core_cache_has( $cache_key ) ) {
return et_core_cache_get( $cache_key );
}
$post_type = get_post_type( $post_id );
$post_type = $post_type ? $post_type : 'post';
$post_type_object = get_post_type_object( $post_type );
$post_type_label = $post_type_object->labels->singular_name;
$post_taxonomy_types = et_builder_get_taxonomy_types( $post_type );
$tag_taxonomy_post_type = $post_type;
$fields = array();
$before_after_field_types = array( 'text', 'any' );
if ( et_theme_builder_is_layout_post_type( $post_type ) ) {
$post_type_label = esc_html__( 'Post', 'et_builder' );
$tag_taxonomy_post_type = 'post';
$public_post_types = array_keys( et_builder_get_public_post_types() );
foreach ( $public_post_types as $public_post_type ) {
$post_taxonomy_types = array_merge(
$post_taxonomy_types,
et_builder_get_taxonomy_types( $public_post_type )
);
}
}
$default_category_type = 'post' === $post_type ? 'category' : "${post_type}_category";
if ( ! isset( $post_taxonomy_types[ $default_category_type ] ) ) {
$default_category_type = 'category';
if ( ! empty( $post_taxonomy_types ) ) {
// Use the 1st available taxonomy as the default value.
$default_category_type = array_keys( $post_taxonomy_types );
$default_category_type = $default_category_type[0];
}
}
$date_format_options = array(
'default' => et_builder_i18n( 'Default' ),
'M j, Y' => esc_html__( 'Aug 6, 1999 (M j, Y)', 'et_builder' ),
'F d, Y' => esc_html__( 'August 06, 1999 (F d, Y)', 'et_builder' ),
'm/d/Y' => esc_html__( '08/06/1999 (m/d/Y)', 'et_builder' ),
'm.d.Y' => esc_html__( '08.06.1999 (m.d.Y)', 'et_builder' ),
'j M, Y' => esc_html__( '6 Aug, 1999 (j M, Y)', 'et_builder' ),
'l, M d' => esc_html__( 'Tuesday, Aug 06 (l, M d)', 'et_builder' ),
'custom' => esc_html__( 'Custom', 'et_builder' ),
);
$fields['post_title'] = array(
// Translators: %1$s: Post type name.
'label' => esc_html( sprintf( __( '%1$s/Archive Title', 'et_builder' ), $post_type_label ) ),
'type' => 'text',
);
$fields['post_excerpt'] = array(
// Translators: %1$s: Post type name.
'label' => esc_html( sprintf( __( '%1$s Excerpt', 'et_builder' ), $post_type_label ) ),
'type' => 'text',
'fields' => array(
'words' => array(
'label' => esc_html__( 'Number of Words', 'et_builder' ),
'type' => 'text',
'default' => '',
),
'read_more_label' => array(
'label' => esc_html__( 'Read More Text', 'et_builder' ),
'type' => 'text',
'default' => '',
),
),
);
$fields['post_date'] = array(
// Translators: %1$s: Post type name.
'label' => esc_html( sprintf( __( '%1$s Publish Date', 'et_builder' ), $post_type_label ) ),
'type' => 'text',
'fields' => array(
'date_format' => array(
'label' => esc_html__( 'Date Format', 'et_builder' ),
'type' => 'select',
'options' => $date_format_options,
'default' => 'default',
),
'custom_date_format' => array(
'label' => esc_html__( 'Custom Date Format', 'et_builder' ),
'type' => 'text',
'default' => '',
'show_if' => array(
'date_format' => 'custom',
),
),
),
);
$fields['post_comment_count'] = array(
// Translators: %1$s: Post type name.
'label' => esc_html( sprintf( __( '%1$s Comment Count', 'et_builder' ), $post_type_label ) ),
'type' => 'text',
'fields' => array(
'link_to_comments_page' => array(
'label' => esc_html__( 'Link to Comments Area', 'et_builder' ),
'type' => 'yes_no_button',
'options' => array(
'on' => et_builder_i18n( 'Yes' ),
'off' => et_builder_i18n( 'No' ),
),
'default' => 'on',
),
),
);
if ( ! empty( $post_taxonomy_types ) ) {
$fields['post_categories'] = array(
// Translators: %1$s: Post type name.
'label' => esc_html( sprintf( __( '%1$s Categories', 'et_builder' ), $post_type_label ) ),
'type' => 'text',
'fields' => array(
'link_to_term_page' => array(
'label' => esc_html__( 'Link to Category Index Pages', 'et_builder' ),
'type' => 'yes_no_button',
'options' => array(
'on' => et_builder_i18n( 'Yes' ),
'off' => et_builder_i18n( 'No' ),
),
'default' => 'on',
),
'separator' => array(
'label' => esc_html__( 'Categories Separator', 'et_builder' ),
'type' => 'text',
'default' => ' | ',
),
'category_type' => array(
'label' => esc_html__( 'Category Type', 'et_builder' ),
'type' => 'select',
'options' => $post_taxonomy_types,
'default' => $default_category_type,
),
),
);
}
// Fill in tag taxonomies.
if ( isset( $post_taxonomy_types[ "{$tag_taxonomy_post_type}_tag" ] ) ) {
$fields['post_tags'] = array(
// Translators: %1$s: Post type name.
'label' => esc_html( sprintf( __( '%1$s Tags', 'et_builder' ), $post_type_label ) ),
'type' => 'text',
'fields' => array(
'link_to_term_page' => array(
'label' => esc_html__( 'Link to Tag Index Pages', 'et_builder' ),
'type' => 'yes_no_button',
'options' => array(
'on' => et_builder_i18n( 'Yes' ),
'off' => et_builder_i18n( 'No' ),
),
'default' => 'on',
),
'separator' => array(
'label' => esc_html__( 'Tags Separator', 'et_builder' ),
'type' => 'text',
'default' => ' | ',
),
'category_type' => array(
'label' => esc_html__( 'Category Type', 'et_builder' ),
'type' => 'select',
'options' => $post_taxonomy_types,
'default' => "{$tag_taxonomy_post_type}_tag",
),
),
);
}
$fields['post_link'] = array(
// Translators: %1$s: Post type name.
'label' => esc_html( sprintf( __( '%1$s Link', 'et_builder' ), $post_type_label ) ),
'type' => 'text',
'fields' => array(
'text' => array(
'label' => esc_html__( 'Link Text', 'et_builder' ),
'type' => 'select',
'options' => array(
// Translators: %1$s: Post type name.
'post_title' => esc_html( sprintf( __( '%1$s Title', 'et_builder' ), $post_type_label ) ),
'custom' => esc_html__( 'Custom', 'et_builder' ),
),
'default' => 'post_title',
),
'custom_text' => array(
'label' => esc_html__( 'Custom Link Text', 'et_builder' ),
'type' => 'text',
'default' => '',
'show_if' => array(
'text' => 'custom',
),
),
),
);
$fields['post_author'] = array(
// Translators: %1$s: Post type name.
'label' => esc_html( sprintf( __( '%1$s Author', 'et_builder' ), $post_type_label ) ),
'type' => 'text',
'fields' => array(
'name_format' => array(
'label' => esc_html__( 'Name Format', 'et_builder' ),
'type' => 'select',
'options' => array(
'display_name' => esc_html__( 'Public Display Name', 'et_builder' ),
'first_last_name' => esc_html__( 'First & Last Name', 'et_builder' ),
'last_first_name' => esc_html__( 'Last, First Name', 'et_builder' ),
'first_name' => esc_html__( 'First Name', 'et_builder' ),
'last_name' => esc_html__( 'Last Name', 'et_builder' ),
'nickname' => esc_html__( 'Nickname', 'et_builder' ),
'username' => esc_html__( 'Username', 'et_builder' ),
),
'default' => 'display_name',
),
'link' => array(
'label' => esc_html__( 'Link Name', 'et_builder' ),
'type' => 'yes_no_button',
'options' => array(
'on' => et_builder_i18n( 'Yes' ),
'off' => et_builder_i18n( 'No' ),
),
'default' => 'off',
),
'link_destination' => array(
'label' => esc_html__( 'Link Destination', 'et_builder' ),
'type' => 'select',
'options' => array(
'author_archive' => esc_html__( 'Author Archive Page', 'et_builder' ),
'author_website' => esc_html__( 'Author Website', 'et_builder' ),
),
'default' => 'author_archive',
'show_if' => array(
'link' => 'on',
),
),
),
);
$fields['post_author_bio'] = array(
'label' => esc_html__( 'Author Bio', 'et_builder' ),
'type' => 'text',
);
if ( et_builder_tb_enabled() ) {
$fields['term_description'] = array(
'label' => esc_html__( 'Category Description', 'et_builder' ),
'type' => 'text',
);
}
$fields['site_title'] = array(
'label' => esc_html__( 'Site Title', 'et_builder' ),
'type' => 'text',
);
$fields['site_tagline'] = array(
'label' => esc_html__( 'Site Tagline', 'et_builder' ),
'type' => 'text',
);
$fields['current_date'] = array(
'label' => esc_html__( 'Current Date', 'et_builder' ),
'type' => 'text',
'fields' => array(
'date_format' => array(
'label' => esc_html__( 'Date Format', 'et_builder' ),
'type' => 'select',
'options' => $date_format_options,
'default' => 'default',
),
'custom_date_format' => array(
'label' => esc_html__( 'Custom Date Format', 'et_builder' ),
'type' => 'text',
'default' => '',
'show_if' => array(
'date_format' => 'custom',
),
),
),
);
$fields['post_link_url'] = array(
// Translators: %1$s: Post type name.
'label' => esc_html( sprintf( __( 'Current %1$s Link', 'et_builder' ), $post_type_label ) ),
'type' => 'url',
);
$fields['post_author_url'] = array(
'label' => esc_html__( 'Author Page Link', 'et_builder' ),
'type' => 'url',
);
$fields['home_url'] = array(
'label' => esc_html__( 'Homepage Link', 'et_builder' ),
'type' => 'url',
);
// Fill in post type URL options.
$post_types = et_builder_get_public_post_types();
foreach ( $post_types as $public_post_type ) {
$public_post_type_label = $public_post_type->labels->singular_name;
$key = 'post_link_url_' . $public_post_type->name;
$fields[ $key ] = array(
// Translators: %1$s: Post type name.
'label' => esc_html( sprintf( __( '%1$s Link', 'et_builder' ), $public_post_type_label ) ),
'type' => 'url',
'fields' => array(
'post_id' => array(
'label' => $public_post_type_label,
'type' => 'select_post',
'post_type' => $public_post_type->name,
'default' => '',
),
),
);
}
$fields['post_featured_image'] = array(
'label' => esc_html__( 'Featured Image', 'et_builder' ),
'type' => 'image',
);
$fields['post_author_profile_picture'] = array(
// Translators: %1$s: Post type name.
'label' => esc_html__( 'Author Profile Picture', 'et_builder' ),
'type' => 'image',
);
$fields['site_logo'] = array(
'label' => esc_html__( 'Site Logo', 'et_builder' ),
'type' => 'image',
);
$fields['post_meta_key'] = array(
'label' => esc_html__( 'Manual Custom Field Name', 'et_builder' ),
'type' => 'any',
'group' => esc_html__( 'Custom Fields', 'et_builder' ),
'fields' => array(
'meta_key' => array(
'label' => esc_html__( 'Field Name', 'et_builder' ),
'type' => 'text',
),
),
);
if ( current_user_can( 'unfiltered_html' ) ) {
$fields['post_meta_key']['fields']['enable_html'] = array(
'label' => esc_html__( 'Enable raw HTML', 'et_builder' ),
'type' => 'yes_no_button',
'options' => array(
'on' => et_builder_i18n( 'Yes' ),
'off' => et_builder_i18n( 'No' ),
),
'default' => 'off',
'show_on' => 'text',
);
}
/*
* Include Product dynamic fields on Product post type.
*
* This is enforced based on the discussion at
*
* @see https://github.com/elegantthemes/Divi/issues/15921#issuecomment-512707471
*/
if ( et_is_woocommerce_plugin_active() && ( 'product' === $post_type || et_theme_builder_is_layout_post_type( $post_type ) ) ) {
$fields = array_merge( $fields, et_builder_get_product_dynamic_content_fields() );
}
// Fill in boilerplate.
foreach ( $fields as $key => $field ) {
$fields[ $key ]['custom'] = false;
$fields[ $key ]['group'] = et_()->array_get( $fields, "{$key}.group", 'Default' );
if ( in_array( $field['type'], $before_after_field_types, true ) ) {
$settings = isset( $field['fields'] ) ? $field['fields'] : array();
$settings = array_merge(
array(
'before' => array(
'label' => et_builder_i18n( 'Before' ),
'type' => 'text',
'default' => '',
),
'after' => array(
'label' => et_builder_i18n( 'After' ),
'type' => 'text',
'default' => '',
),
),
$settings
);
$fields[ $key ]['fields'] = $settings;
}
}
et_core_cache_add( $cache_key, $fields );
return $fields;
}
/**
* Clear dynamic content fields cache whenever a custom post type is registered.
*
* @since 3.26.7
*
* @return void
*/
function et_builder_clear_get_built_in_dynamic_content_fields_cache() {
et_core_cache_delete( 'et_builder_get_built_in_dynamic_content_fields' );
}
add_action( 'registered_post_type', 'et_builder_clear_get_built_in_dynamic_content_fields_cache' );
/**
* Get all public taxonomies associated with a given post type.
*
* @since 3.17.2
*
* @param string $post_type Post type.
*
* @return array
*/
function et_builder_get_taxonomy_types( $post_type ) {
$taxonomies = get_object_taxonomies( $post_type, 'object' );
$list = array();
if ( empty( $taxonomies ) ) {
return $list;
}
foreach ( $taxonomies as $taxonomy ) {
if ( ! empty( $taxonomy ) && $taxonomy->public && $taxonomy->show_ui ) {
$list[ $taxonomy->name ] = $taxonomy->label;
}
}
return $list;
}
/**
* Get a user-friendly custom field label for the given meta key.
*
* @since 4.4.4
*
* @param string $key Post meta key.
*
* @return string
*/
function et_builder_get_dynamic_content_custom_field_label( $key ) {
$label = str_replace( array( '_', '-' ), ' ', $key );
$label = ucwords( $label );
$label = trim( $label );
return $label;
}
/**
* Get all dynamic content fields in a given string.
*
* @since 4.4.4
*
* @param string $content Value content.
*
* @return array
*/
function et_builder_get_dynamic_contents( $content ) {
$is_matched = preg_match_all( ET_THEME_BUILDER_DYNAMIC_CONTENT_REGEX, $content, $matches );
if ( ! $is_matched ) {
return array();
}
return $matches[0];
}
/**
* Get all meta keys used as dynamic content in the content of a post.
*
* @param integer $post_id Post Id.
*
* @return array
*/
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 ) {
return $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 );
return $used_meta_keys;
}
/**
* Get most used meta keys on public post types.
*
* @since 4.4.4
*
* @return string[]
*/
function et_builder_get_most_used_post_meta_keys() {
global $wpdb;
$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_types = "'" . implode( "','", esc_sql( $public_post_types ) ) . "'";
$sql = "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_types}) )
WHERE pm.meta_key NOT LIKE '\_%'
GROUP BY pm.meta_key
ORDER BY COUNT(pm.meta_key) DESC
LIMIT 50";
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $sql query does not use users/visitor input
$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.
*
* @since 3.17.2
*
* @param integer $post_id Post Id.
*
* @return array[]
*/
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.
*
* @since 3.17.2
*
* @param string[] $meta_keys
* @param integer $post_id
*
* @return string[]
*/
$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(
$raw_custom_fields,
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, true ) ) {
// Ignore hidden meta keys.
continue;
}
if ( substr( $key, 0, 3 ) === 'et_' ) {
// Ignore ET meta keys as they are not suitable for dynamic content use.
continue;
}
$label = et_builder_get_dynamic_content_custom_field_label( $key );
/**
* Filter the display label for a custom field.
*
* @since 3.17.2
*
* @param string $label
* @param string $meta_key
*/
$label = apply_filters( 'et_builder_dynamic_content_custom_field_label', $label, $key );
$field = array(
'label' => $label,
'type' => 'any',
'fields' => array(
'before' => array(
'label' => et_builder_i18n( 'Before' ),
'type' => 'text',
'default' => '',
'show_on' => 'text',
),
'after' => array(
'label' => et_builder_i18n( 'After' ),
'type' => 'text',
'default' => '',
'show_on' => 'text',
),
),
'meta_key' => $key,
'custom' => true,
'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',
'options' => array(
'on' => et_builder_i18n( 'Yes' ),
'off' => et_builder_i18n( 'No' ),
),
'default' => 'off',
'show_on' => 'text',
);
}
$custom_fields[ "custom_meta_{$key}" ] = $field;
}
/**
* Filter available custom field options for dynamic content.
*
* @since 3.17.2
*
* @param array[] $custom_fields
* @param int $post_id
* @param mixed[] $raw_custom_fields
*
* @return array[]
*/
$custom_fields = apply_filters( 'et_builder_custom_dynamic_content_fields', $custom_fields, $post_id, $raw_custom_fields );
return $custom_fields;
}
/**
* Get all dynamic content fields.
*
* @since 3.17.2
*
* @param integer $post_id Post Id.
* @param string $context Context e.g `edit`, `display`.
*
* @return array[]
*/
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 ) {
$all[ $id ]['id'] = $id;
}
$__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();
return $all;
}
/**
* Sort dynamic content fields.
*
* @since 4.0
*
* @param array $a First field.
* @param array $b Second field.
*
* @return integer
*/
function et_builder_sort_dynamic_content_fields( $a, $b ) {
global $__et_dynamic_content_fields_index_map;
$top = array_flip(
array(
'Default',
__( '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 ) {
return -1;
}
if ( ! $a_is_top && $b_is_top ) {
return 1;
}
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.
*
* @since 3.17.2
*
* @param integer $post_id Post Id.
* @param string $field Custom field name.
* @param string $setting Array of dynamic content settings.
*
* @return string
*/
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 string $name Custom field name.
* @param array $settings Array of dynamic content settings.
* @param integer $post_id Post Id.
* @param string $context Context e.g `edit`, `display`.
* @param array $overrides An associative array of field_name => value to override field value.
* @param bool $is_content Whether dynamic content used in module's main_content field {@see et_builder_ajax_resolve_post_content()}.
*
* @return string
* @since 3.17.2
*/
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.
*
* @since 3.17.2
*
* @param string $content
* @param string $name
* @param array $settings
* @param integer $post_id
* @param string $context
* @param array $overrides
*
* @return string
*/
$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.
*
* @since 3.17.2
*
* @param string $content
* @param array $settings
* @param integer $post_id
* @param string $context
* @param array $overrides
*
* @return string
*/
$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.
*
* @since 3.17.2
*
* @param integer $post_id Post Id.
* @param string $name Custom field name.
* @param string $value Value content.
* @param array $settings Array of dynamic content settings.
*
* @return string
*/
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( '' ),
array(
'h1' => array(),
'h2' => array(),
'h3' => array(),
'h4' => array(),
'h5' => array(),
'h6' => array(),
'ol' => array(),
'ul' => array(),
'li' => array(),
'span' => array(),
'p' => array(),
)
);
$before = wp_kses( $before, $allowlist );
$after = wp_kses( $after, $allowlist );
}
return $before . $value . $after;
}
/**
* Resolve built-in dynamic content fields.
*
* @param string $content Value content.
* @param string $name Custom field name.
* @param array $settings Array of dynamic content settings.
* @param integer $post_id Post Id.
* @param string $context Context e.g `edit`, `display`.
* @param array $overrides An associative array of field_name => value to override field value.
*
* @return string
* @since 3.17.2
*/
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 = null;
$wrapped = false;
$is_woo = false;
if ( $post ) {
$author = get_userdata( $post->post_author );
} elseif ( is_author() ) {
$author = get_queried_object();
}
switch ( $name ) {
case 'product_title': // Intentional fallthrough.
case 'post_title':
if ( isset( $overrides[ $name ] ) ) {
$content = $overrides[ $name ];
} else {
$content = et_builder_get_current_title( $post_id );
}
$content = et_core_intentionally_unescaped( $content, 'cap_based_sanitized' );
break;
case 'post_excerpt':
if ( ! $post ) {
break;
}
$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 );
if ( $words > 0 ) {
$content = wp_trim_words( $content, $words );
}
if ( ! empty( $read_more ) ) {
$content .= sprintf(
' <a href="%1$s">%2$s</a>',
esc_url( get_permalink( $post_id ) ),
esc_html( $read_more )
);
}
break;
case 'post_date':
if ( ! $post ) {
break;
}
$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 ) {
$format = '';
}
if ( 'custom' === $format ) {
$format = $custom_format;
}
$content = esc_html( get_the_date( $format, $post_id ) );
break;
case 'post_comment_count':
if ( ! $post ) {
break;
}
$link = $_->array_get( $settings, 'link_to_comments_page', $def( $post_id, $name, 'link_to_comments_page' ) );
$link = 'on' === $link;
$content = esc_html( get_comments_number( $post_id ) );
if ( $link ) {
$content = sprintf(
'<a href="%1$s">%2$s</a>',
esc_url( get_comments_link( $post_id ) ),
et_core_esc_previously( et_builder_wrap_dynamic_content( $post_id, $name, $content, $settings ) )
);
$wrapped = true;
}
break;
case 'post_categories': // Intentional fallthrough.
case 'post_tags':
if ( ! $post ) {
break;
}
$overrides_map = array(
'category' => 'post_categories',
'post_tag' => 'post_tags',
);
$post_taxonomies = et_builder_get_taxonomy_types( get_post_type( $post_id ) );
$taxonomy = $_->array_get( $settings, 'category_type', '' );
if ( in_array( $taxonomy, array( 'et_header_layout_category', 'et_body_layout_category', 'et_footer_layout_category' ), true ) ) {
// TB layouts were storing an invalid taxonomy in <= 4.0.3 so we have to correct it:.
$taxonomy = $def( $post_id, $name, 'category_type' );
}
if ( ! isset( $post_taxonomies[ $taxonomy ] ) ) {
break;
}
$link = $_->array_get( $settings, 'link_to_term_page', $def( $post_id, $name, 'link_to_category_page' ) );
$link = 'on' === $link;
$separator = $_->array_get( $settings, 'separator', $def( $post_id, $name, 'separator' ) );
$separator = ! empty( $separator ) ? $separator : $def( $post_id, $name, 'separator' );
$ids_key = isset( $overrides_map[ $taxonomy ] ) ? $overrides_map[ $taxonomy ] : '';
$ids = isset( $overrides[ $ids_key ] ) ? array_filter( array_map( 'intval', explode( ',', $overrides[ $ids_key ] ) ) ) : array();
$terms = ! empty( $ids ) ? get_terms(
array(
'taxonomy' => $taxonomy,
'include' => $ids,
)
) : get_the_terms( $post_id, $taxonomy );
if ( is_array( $terms ) ) {
$content = et_builder_list_terms( $terms, $link, $separator );
} else {
$content = '';
}
break;
case 'post_link':
if ( ! $post ) {
break;
}
$text = $_->array_get( $settings, 'text', $def( $post_id, $name, 'text' ) );
$custom_text = $_->array_get( $settings, 'custom_text', $def( $post_id, $name, 'custom_text' ) );
$label = 'custom' === $text ? $custom_text : get_the_title( $post_id );
$content = sprintf(
'<a href="%1$s">%2$s</a>',
esc_url( get_permalink( $post_id ) ),
esc_html( $label )
);
break;
case 'post_author':
$name_format = $_->array_get( $settings, 'name_format', $def( $post_id, $name, 'name_format' ) );
$link = $_->array_get( $settings, 'link', $def( $post_id, $name, 'link' ) );
$link = 'on' === $link;
$link_destination = $_->array_get( $settings, 'link_destination', $def( $post_id, $name, 'link_destination' ) );
$link_target = 'author_archive' === $link_destination ? '_self' : '_blank';
$label = '';
$url = '';
if ( ! $author ) {
$content = '';
break;
}
switch ( $name_format ) {
case 'display_name':
$label = $author->display_name;
break;
case 'first_last_name':
$label = $author->first_name . ' ' . $author->last_name;
break;
case 'last_first_name':
$label = $author->last_name . ', ' . $author->first_name;
break;
case 'first_name':
$label = $author->first_name;
break;
case 'last_name':
$label = $author->last_name;
break;
case 'nickname':
$label = $author->nickname;
break;
case 'username':
$label = $author->user_login;
break;
}
switch ( $link_destination ) {
case 'author_archive':
$url = get_author_posts_url( $author->ID );
break;
case 'author_website':
$url = $author->user_url;
break;
}
$content = esc_html( $label );
if ( $link && ! empty( $url ) ) {
$content = sprintf(
'<a href="%1$s" target="%2$s">%3$s</a>',
esc_url( $url ),
esc_attr( $link_target ),
et_core_esc_previously( $content )
);
}
break;
case 'post_author_bio':
if ( ! $author ) {
break;
}
$content = et_core_intentionally_unescaped( $author->description, 'cap_based_sanitized' );
break;
case 'term_description':
$content = et_core_intentionally_unescaped( term_description(), 'cap_based_sanitized' );
break;
case 'site_title':
$content = esc_html( get_bloginfo( 'name' ) );
break;
case 'site_tagline':
$content = esc_html( get_bloginfo( 'description' ) );
break;
case 'current_date':
$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 ) {
$format = get_option( 'date_format' );
}
if ( 'custom' === $format ) {
$format = $custom_format;
}
$content = esc_html( date_i18n( $format ) );
break;
case 'post_link_url':
if ( ! $post ) {
break;
}
$content = esc_url( get_permalink( $post_id ) );
break;
case 'post_author_url':
if ( ! $author ) {
break;
}
$content = esc_url( get_author_posts_url( $author->ID ) );
break;
case 'home_url':
$content = esc_url( home_url( '/' ) );
break;
case 'any_post_link_url':
$selected_post_id = $_->array_get( $settings, 'post_id', $def( $post_id, $name, 'post_id' ) );
$content = esc_url( get_permalink( $selected_post_id ) );
break;
case 'product_reviews_tab':
$content = '#product_reviews_tab';
break;
case 'post_featured_image':
$is_blog_query = isset( $wp_query->et_pb_blog_query ) && $wp_query->et_pb_blog_query;
if ( isset( $overrides[ $name ] ) ) {
$id = (int) $overrides[ $name ];
$content = wp_get_attachment_image_url( $id, 'full' );
break;
}
if ( ! $is_blog_query && ( is_category() || is_tag() || is_tax() ) ) {
$term_id = (int) get_queried_object_id();
$attachment_id = (int) get_term_meta( $term_id, 'thumbnail_id', true );
$url = wp_get_attachment_image_url( $attachment_id, 'full' );
$content = $url ? esc_url( $url ) : '';
break;
}
if ( $post ) {
$url = get_the_post_thumbnail_url( $post_id, 'full' );
$content = $url ? esc_url( $url ) : '';
break;
}
break;
case 'post_featured_image_alt_text':
$is_blog_query = isset( $wp_query->et_pb_blog_query ) && $wp_query->et_pb_blog_query;
if ( isset( $overrides[ $name ] ) ) {
$id = (int) $overrides[ $name ];
$img_alt = $id ? get_post_meta( $id, '_wp_attachment_image_alt', true ) : '';
$content = $img_alt ? esc_attr( $img_alt ) : '';
break;
}
if ( ! $is_blog_query && ( is_category() || is_tag() || is_tax() ) ) {
$term_id = (int) get_queried_object_id();
$attachment_id = (int) get_term_meta( $term_id, 'thumbnail_id', true );
$img_alt = $attachment_id ? get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) : '';
$content = $img_alt ? esc_attr( $img_alt ) : '';
break;
}
if ( $post ) {
$img_alt = get_post_thumbnail_id() ? get_post_meta( get_post_thumbnail_id(), '_wp_attachment_image_alt', true ) : '';
$content = $img_alt ? esc_attr( $img_alt ) : '';
break;
}
break;
case 'post_featured_image_title_text':
$is_blog_query = isset( $wp_query->et_pb_blog_query ) && $wp_query->et_pb_blog_query;
if ( isset( $overrides[ $name ] ) ) {
$id = (int) $overrides[ $name ];
$img_title = $id ? get_the_title( $id ) : '';
$content = $img_title ? esc_attr( $img_title ) : '';
break;
}
if ( ! $is_blog_query && ( is_category() || is_tag() || is_tax() ) ) {
$term_id = (int) get_queried_object_id();
$attachment_id = (int) get_term_meta( $term_id, 'thumbnail_id', true );
$img_title = $attachment_id ? get_the_title( $attachment_id ) : '';
$content = $img_title ? esc_attr( $img_title ) : '';
break;
}
if ( $post ) {
$img_title = get_post_thumbnail_id() ? get_the_title( get_post_thumbnail_id() ) : '';
$content = $img_title ? esc_attr( $img_title ) : '';
break;
}
break;
case 'post_author_profile_picture':
if ( ! $author ) {
break;
}
$content = get_avatar_url( $author->ID );
break;
case 'site_logo':
$logo = et_get_option( $shortname . '_logo' );
$content = '';
if ( ! empty( $logo ) ) {
$content = esc_url( $logo );
}
break;
case 'product_breadcrumb':
if ( ! $post ) {
break;
}
$dynamic_product = ET_Builder_Module_Helper_Woocommerce_Modules::get_product( $post_id );
if ( $dynamic_product ) {
$is_woo = true;
$content = ET_Builder_Module_Woocommerce_Breadcrumb::get_breadcrumb(
array(
'product' => $dynamic_product->get_id(),
)
);
} else {
$content = '';
}
break;
case 'product_price':
if ( ! $post ) {
break;
}
$dynamic_product = ET_Builder_Module_Helper_Woocommerce_Modules::get_product( $post_id );
if ( $dynamic_product ) {
$is_woo = true;
$content = ET_Builder_Module_Woocommerce_Price::get_price(
array(
'product' => $dynamic_product->get_id(),
)
);
} else {
$content = '';
}
break;
case 'product_description':
if ( ! $post ) {
break;
}
$dynamic_product = ET_Builder_Module_Helper_Woocommerce_Modules::get_product( $post_id );
if ( $dynamic_product ) {
$is_woo = true;
$content = ET_Builder_Module_Woocommerce_Description::get_description(
array(
'product' => $dynamic_product->get_id(),
'description_type' => 'description',
)
);
} else {
$content = '';
}
break;
case 'product_short_description':
if ( ! $post ) {
break;
}
$dynamic_product = ET_Builder_Module_Helper_Woocommerce_Modules::get_product( $post_id );
if ( $dynamic_product ) {
$is_woo = true;
$content = ET_Builder_Module_Woocommerce_Description::get_description(
array(
'product' => $dynamic_product->get_id(),
'description_type' => 'short_description',
)
);
} else {
$content = '';
}
break;
case 'product_reviews_count':
if ( ! $post ) {
break;
}
$dynamic_product = ET_Builder_Module_Helper_Woocommerce_Modules::get_product( $post_id );
if ( $dynamic_product ) {
$is_woo = true;
$content = $dynamic_product->get_review_count();
} else {
$content = 0;
}
break;
case 'product_sku':
if ( ! $post ) {
break;
}
$dynamic_product = ET_Builder_Module_Helper_Woocommerce_Modules::get_product( $post_id );
if ( $dynamic_product ) {
$is_woo = true;
$content = $dynamic_product->get_sku();
} else {
$content = '';
}
break;
case 'product_reviews':
if ( ! $post ) {
break;
}
$dynamic_product = ET_Builder_Module_Helper_Woocommerce_Modules::get_product( $post_id );
if ( ! $dynamic_product ) {
$content = '';
break;
}
// Return early if comments are closed.
if ( ! comments_open( $dynamic_product->get_id() ) ) {
$content = '';
break;
}
$is_woo = true;
// Product description refers to Product short description.
// Product short description is nothing but post excerpt.
$args = array( 'post_id' => $dynamic_product->get_id() );
$comments = get_comments( $args );
$total_pages = get_comment_pages_count( $comments );
$content = wp_list_comments(
array(
'callback' => 'woocommerce_comments',
'echo' => false,
),
$comments
);
// Pass $dynamic_product, $reviews to unify the flow of data.
$reviews_title = ET_Builder_Module_Helper_Woocommerce_Modules::get_reviews_title( $dynamic_product );
$reviews_comment_form = ET_Builder_Module_Helper_Woocommerce_Modules::get_reviews_comment_form( $dynamic_product, $comments );
$no_reviews_text = sprintf(
'<p class="woocommerce-noreviews">%s</p>',
esc_html__( 'There are no reviews yet.', 'et_builder' )
);
$no_reviews = is_array( $comments ) && count( $comments ) > 0 ? '' : $no_reviews_text;
$is_show_title = 'on' === $_->array_get( $settings, 'enable_title', 'on' );
if ( wp_doing_ajax() ) {
$page = get_query_var( 'cpage' );
if ( ! $page ) {
$page = 1;
}
$args = array(
'base' => add_query_arg( 'cpage', '%#%' ),
'format' => '',
'total' => $total_pages,
'current' => $page,
'echo' => false,
'add_fragment' => '#comments',
'type' => 'list',
);
global $wp_rewrite;
if ( $wp_rewrite->using_permalinks() ) {
$args['base'] = user_trailingslashit( trailingslashit( get_permalink() ) . $wp_rewrite->comments_pagination_base . '-%#%', 'commentpaged' );
}
$pagination = paginate_links( $args );
} else {
$pagination = paginate_comments_links(
array(
'echo' => false,
'type' => 'list',
'total' => $total_pages,
)
);
}
$title = $is_show_title
? sprintf( '<h2 class="woocommerce-Reviews-title">%s</h2>', et_core_esc_previously( $reviews_title ) )
: '';
$content = sprintf(
'
<div id="reviews" class="woocommerce-Reviews">
%1$s
<div id="comments">
<ol class="commentlist">
%2$s
</ol>
<nav class="woocommerce-pagination">
%5$s
</nav>
%4$s
</div>
<div id="review_form_wrapper">
%3$s
</div>
</div>
',
/* 1$s */ et_core_esc_previously( $title ),
/* 2$s */ et_core_esc_previously( $content ),
/* 3$s */ et_core_esc_previously( $reviews_comment_form ),
/* 4$s */ et_core_esc_previously( $no_reviews ),
/* 5$s */ et_core_esc_previously( $pagination )
);
$wrapped = true;
break;
case 'product_additional_information':
if ( ! $post ) {
break;
}
$dynamic_product = ET_Builder_Module_Helper_Woocommerce_Modules::get_product( $post_id );
$show_title = $_->array_get( $settings, 'enable_title', 'on' );
if ( $dynamic_product ) {
$is_woo = true;
$content = ET_Builder_Module_Woocommerce_Additional_Info::get_additional_info(
array(
'product' => $dynamic_product->get_id(),
'show_title' => $show_title,
)
);
} else {
$content = '';
}
break;
case 'post_meta_key':
$meta_key = $_->array_get( $settings, 'meta_key' );
$content = get_post_meta( $post_id, $meta_key, true );
$is_fe = 'fe' === et_builder_get_current_builder_type() ? true : false;
if ( ( $is_fe && empty( $content ) ) || empty( $meta_key ) ) {
$content = '';
break;
}
if ( empty( $content ) ) {
$content = et_builder_get_dynamic_content_custom_field_label( $meta_key );
} else {
$enable_html = $_->array_get( $settings, 'enable_html' );
if ( 'on' !== $enable_html ) {
$content = esc_html( $content );
}
}
break;
}
// Handle in post type URL options.
$post_types = et_builder_get_public_post_types();
foreach ( $post_types as $public_post_type ) {
$key = 'post_link_url_' . $public_post_type->name;
if ( $key !== $name ) {
continue;
}
$selected_post_id = $_->array_get( $settings, 'post_id', $def( $post_id, $name, 'post_id' ) );
$content = esc_url( get_permalink( $selected_post_id ) );
break;
}
// Wrap non plain text woo data to add custom selector for styling inheritance.
// It works by checking is the content has HTML tag.
if ( $is_woo && $content && preg_match( '/<\s?[^\>]*\/?\s?>/i', $content ) ) {
$content = sprintf( '<div class="woocommerce et-dynamic-content-woo et-dynamic-content-woo--%2$s">%1$s</div>', $content, $name );
}
if ( ! $wrapped ) {
$content = et_builder_wrap_dynamic_content( $post_id, $name, $content, $settings );
$wrapped = true;
}
return $content;
}
add_filter( 'et_builder_resolve_dynamic_content', 'et_builder_filter_resolve_default_dynamic_content', 10, 6 );
/**
* Resolve custom field dynamic content fields.
*
* @param string $content Value content.
* @param string $name Custom field name.
* @param array $settings Array of dynamic content settings.
* @param integer $post_id Post Id.
* @param string $context Context e.g `edit`, `display`.
* @param array $overrides An associative array of field_name => value to override field value.
*
* @return string
* @since 3.17.2
*/
function et_builder_filter_resolve_custom_field_dynamic_content( $content, $name, $settings, $post_id, $context, $overrides ) {
$post = get_post( $post_id );
$fields = et_builder_get_dynamic_content_fields( $post_id, $context );
if ( empty( $fields[ $name ]['meta_key'] ) ) {
return $content;
}
if ( 'edit' === $context && ! et_pb_is_allowed( 'read_dynamic_content_custom_fields' ) ) {
if ( 'text' === $fields[ $name ]['type'] ) {
return esc_html__( 'You don\'t have sufficient permissions to access this content.', 'et_builder' );
}
return '';
}
$_ = ET_Core_Data_Utils::instance();
$def = 'et_builder_get_dynamic_attribute_field_default';
$enable_html = $_->array_get( $settings, 'enable_html', $def( $post_id, $name, 'enable_html' ) );
if ( $post ) {
$content = get_post_meta( $post_id, $fields[ $name ]['meta_key'], true );
}
/**
* Provide a hook for third party compatibility purposes of formatting meta values.
*
* @since 3.17.2
*
* @param string $meta_value
* @param string $meta_key
* @param integer $post_id
*/
$content = apply_filters( 'et_builder_dynamic_content_meta_value', $content, $fields[ $name ]['meta_key'], $post_id );
// Sanitize HTML contents.
$content = wp_kses_post( $content );
if ( 'on' !== $enable_html ) {
$content = esc_html( $content );
}
$content = et_builder_wrap_dynamic_content( $post_id, $name, $content, $settings );
return $content;
}
add_filter( 'et_builder_resolve_dynamic_content', 'et_builder_filter_resolve_custom_field_dynamic_content', 10, 6 );
/**
* Resolve a dynamic group post content field for use during editing.
*
* @since 3.17.2
*
* @param string $field Custom field name.
* @param array $settings Array of dynamic content settings.
* @param integer $post_id Post Id.
* @param array $overrides An associative array of field_name => value to override field value.
* @param boolean $is_content Whether dynamic content used in module's main_content field {@see et_builder_ajax_resolve_post_content()}.
*
* @return string
*/
function et_builder_filter_resolve_dynamic_post_content_field( $field, $settings, $post_id, $overrides = array(), $is_content = false ) {
return et_builder_resolve_dynamic_content( $field, $settings, $post_id, 'edit', $overrides, $is_content );
}
add_action( 'et_builder_resolve_dynamic_post_content_field', 'et_builder_filter_resolve_dynamic_post_content_field', 10, 5 );
/**
* Clean potential dynamic content from filter artifacts.
*
* @since 3.20.2
*
* @param string $value Content.
*
* @return string
*/
function et_builder_clean_dynamic_content( $value ) {
// Strip wrapping <p></p> tag as it appears in shortcode content in certain cases (e.g. BB preview).
$value = preg_replace( '/^<p>(.*)<\/p>$/i', '$1', trim( $value ) );
return $value;
}
/**
* Parse a JSON-encoded string into an ET_Builder_Value instance or null on failure.
*
* @since 3.20.2
*
* @param string $json JSON-encoded string.
*
* @return ET_Builder_Value|null
*/
function et_builder_parse_dynamic_content_json( $json ) {
// phpcs:disable WordPress.Security.NonceVerification -- This function does not change any stats, hence CSRF ok.
$post_types = array_keys( et_builder_get_public_post_types() );
$dynamic_content = json_decode( $json, true );
$is_dynamic_content = is_array( $dynamic_content ) && isset( $dynamic_content['dynamic'] ) && (bool) $dynamic_content['dynamic'];
$has_content = is_array( $dynamic_content ) && isset( $dynamic_content['content'] ) && is_string( $dynamic_content['content'] );
$has_settings = is_array( $dynamic_content ) && isset( $dynamic_content['settings'] ) && is_array( $dynamic_content['settings'] );
$has_category_type = is_array( $dynamic_content ) && isset( $dynamic_content['settings'] ) && isset( $dynamic_content['settings']['category_type'] );
// When adding a section from library get_post_type() will not work, and post type has to be fetched from $_POST.
$is_added_from_library = isset( $_POST['et_post_type'] );
if ( ! $is_dynamic_content || ! $has_content || ! $has_settings ) {
return null;
}
// Replaces layout_category with proper category_type depending on the post type on which the layout is added.
if ( $has_category_type && 'post_categories' === $dynamic_content['content'] && ! 0 === substr_compare( $dynamic_content['settings']['category_type'], '_tag', - 4 ) ) {
if ( $is_added_from_library ) {
$correct_post_type = sanitize_text_field( $_POST['et_post_type'] );
$correct_post_type = in_array( $correct_post_type, $post_types, true ) ? $correct_post_type : 'post';
} else {
$correct_post_type = get_post_type();
$correct_post_type = in_array( $correct_post_type, $post_types, true ) ? $correct_post_type : 'post';
}
if ( 'post' === $correct_post_type ) {
$dynamic_content['settings']['category_type'] = 'category';
} else {
$dynamic_content['settings']['category_type'] = $correct_post_type . '_category';
}
}
return new ET_Builder_Value(
(bool) $dynamic_content['dynamic'],
sanitize_text_field( $dynamic_content['content'] ),
array_map( 'wp_kses_post', $dynamic_content['settings'] )
);
// phpcs:enable
}
/**
* Convert a value to an ET_Builder_Value representation.
*
* @since 3.17.2
*
* @param string $content Value content.
*
* @return ET_Builder_Value
*/
function et_builder_parse_dynamic_content( $content ) {
$json = et_builder_clean_dynamic_content( $content );
$json = preg_replace( '/^@ET-DC@(.*?)@$/', '$1', $json );
$dynamic_content = et_builder_parse_dynamic_content_json( $json );
if ( null === $dynamic_content ) {
$json = base64_decode( $json ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- `base64_decode` is used to unserialize dynamic content.
$dynamic_content = et_builder_parse_dynamic_content_json( $json );
}
if ( null === $dynamic_content ) {
return new ET_Builder_Value( false, wp_kses_post( $content ), array() );
}
return $dynamic_content;
}
/**
* Serialize dynamic content.
*
* @since 3.20.2
*
* @param bool $dynamic Whether the value is static or dynamic.
* @param string $content Value content. Represents the dynamic content type when dynamic.
* @param mixed[] $settings Array of dynamic content settings.
*
* @return string
*/
function et_builder_serialize_dynamic_content( $dynamic, $content, $settings ) {
// JSON_UNESCAPED_SLASHES is only supported from 5.4.
$options = defined( 'JSON_UNESCAPED_SLASHES' ) ? JSON_UNESCAPED_SLASHES : 0;
$result = wp_json_encode(
array(
'dynamic' => $dynamic,
'content' => $content,
// Force object type for keyed arrays as empty arrays will be encoded to
// javascript arrays instead of empty objects.
'settings' => (object) $settings,
),
$options
);
// Use fallback if needed.
$result = 0 === $options ? str_replace( '\/', '/', $result ) : $result;
return '@ET-DC@' . base64_encode( $result ) . '@'; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions -- `base64_encode` is used to serialize dynamic content.
}
/**
* Strip dynamic content.
*
* @since 4.0.9
*
* @param string $content Post Content.
*
* @return string
*/
function et_builder_strip_dynamic_content( $content ) {
return preg_replace( '/@ET-DC@(.*?)@/', '', $content );
}
/**
* Reencode legacy dynamic content in post excerpts.
*
* @since 3.20.2
*
* @param string $post_excerpt Post Excerpt.
* @param integer $post_id Post Id.
*
* @return string
*/
function et_builder_reencode_legacy_dynamic_content_in_excerpt( $post_excerpt, $post_id ) {
$json = '/
\{ # { character
(?: # non-capturing group
[^{}] # anything that is not a { or }
| # OR
(?R) # recurse the entire pattern
)* # previous group zero or more times
\} # } character
/x';
return preg_replace_callback( $json, 'et_builder_reencode_legacy_dynamic_content_in_excerpt_callback', $post_excerpt );
}
add_filter( 'et_truncate_post', 'et_builder_reencode_legacy_dynamic_content_in_excerpt', 10, 2 );
/**
* Callback to reencode legacy dynamic content for preg_replace_callback.
*
* @since 3.20.2
*
* @param array $matches PCRE match.
*
* @return string
*/
function et_builder_reencode_legacy_dynamic_content_in_excerpt_callback( $matches ) {
$value = et_builder_parse_dynamic_content_json( $matches[0] );
return null === $value ? $matches[0] : $value->serialize();
}
/**
* Resolve dynamic content in post excerpts instead of showing raw JSON.
*
* @since 3.17.2
*
* @param string $post_excerpt Post excerpt.
* @param integer $post_id Post Id.
*
* @return string
*/
function et_builder_resolve_dynamic_content_in_excerpt( $post_excerpt, $post_id ) {
// Use an obscure acronym named global variable instead of an anonymous function as we are
// targeting PHP 5.2.
global $_et_brdcie_post_id;
$_et_brdcie_post_id = $post_id;
$post_excerpt = preg_replace_callback( '/@ET-DC@.*?@/', 'et_builder_resolve_dynamic_content_in_excerpt_callback', $post_excerpt );
$_et_brdcie_post_id = 0;
return $post_excerpt;
}
add_filter( 'et_truncate_post', 'et_builder_resolve_dynamic_content_in_excerpt', 10, 2 );
/**
* Callback to resolve dynamic content for preg_replace_callback.
*
* @since 3.17.2
*
* @param array $matches PCRE match.
*
* @return string
*/
function et_builder_resolve_dynamic_content_in_excerpt_callback( $matches ) {
global $_et_brdcie_post_id;
return et_builder_parse_dynamic_content( $matches[0] )->resolve( $_et_brdcie_post_id );
}