: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
'button_classname' => array(),
'button_text_escaped' => false,
'custom_icon_tablet' => '',
'custom_icon_phone' => '',
'display_button' => true,
$args = wp_parse_args( $args, $defaults );
// Do not proceed if display_button argument is false.
if ( ! $args['display_button'] ) {
$button_text = $args['button_text_escaped'] ? $args['button_text'] : esc_html( $args['button_text'] );
// Do not proceed if button_text argument is empty and not having multi view value.
if ( '' === $button_text && ! $args['multi_view_data'] ) {
$button_classname = array( 'et_pb_button' );
if ( ( '' !== $args['custom_icon'] || '' !== $args['custom_icon_tablet'] || '' !== $args['custom_icon_phone'] ) && 'on' === $args['button_custom'] ) {
$button_classname[] = 'et_pb_custom_button_icon';
// Add multi view CSS hidden helper class when button text is empty on desktop mode.
if ( '' === $button_text && $args['multi_view_data'] ) {
$button_classname[] = 'et_multi_view_hidden';
if ( ! empty( $args['button_classname'] ) ) {
$button_classname = array_merge( $button_classname, $args['button_classname'] );
// Custom icon data attribute
$use_data_icon = '' !== $args['custom_icon'] && 'on' === $args['button_custom'];
$data_icon = $use_data_icon ? sprintf(
esc_attr( et_pb_process_font_icon( $args['custom_icon'] ) )
$use_data_icon_tablet = '' !== $args['custom_icon_tablet'] && 'on' === $args['button_custom'];
$data_icon_tablet = $use_data_icon_tablet ? sprintf(
' data-icon-tablet="%1$s"',
esc_attr( et_pb_process_font_icon( $args['custom_icon_tablet'] ) )
$use_data_icon_phone = '' !== $args['custom_icon_phone'] && 'on' === $args['button_custom'];
$data_icon_phone = $use_data_icon_phone ? sprintf(
' data-icon-phone="%1$s"',
esc_attr( et_pb_process_font_icon( $args['custom_icon_phone'] ) )
return sprintf( '%7$s<a%9$s class="%5$s" href="%1$s"%3$s%4$s%6$s%10$s%11$s%12$s>%2$s</a>%8$s',
esc_url( $args['button_url'] ),
et_core_esc_previously( $button_text ),
( 'on' === $args['url_new_window'] ? ' target="_blank"' : '' ),
et_core_esc_previously( $data_icon ),
esc_attr( implode( ' ', array_unique( $button_classname ) ) ), // #5
et_core_esc_previously( $this->get_rel_attributes( $args['button_rel'] ) ),
$args['has_wrapper'] ? '<div class="et_pb_button_wrapper">' : '',
$args['has_wrapper'] ? '</div>' : '',
'' !== $args['button_id'] ? sprintf( ' id="%1$s"', esc_attr( $args['button_id'] ) ) : '',
et_core_esc_previously( $data_icon_tablet ), // #10
et_core_esc_previously( $data_icon_phone ),
et_core_esc_previously( $args['multi_view_data'] )
public static function is_saving_cache() {
return apply_filters( 'et_builder_modules_is_saving_cache', false );
* Get array of attributes which have dynamic content enabled.
protected function _get_enabled_dynamic_attributes( $attrs ) {
$enabled_dynamic_attributes = isset( $attrs['_dynamic_attributes'] ) ? $attrs['_dynamic_attributes'] : '';
$enabled_dynamic_attributes = array_filter( explode( ',', $enabled_dynamic_attributes ) );
return $enabled_dynamic_attributes;
* Check if an attribute value is dynamic or not.
* @param string $attribute
* @param array $enabled_dynamic_attributes
protected function _is_dynamic_value( $attribute, $value, $enabled_dynamic_attributes ) {
if ( ! in_array( $attribute, $enabled_dynamic_attributes ) ) {
return et_builder_parse_dynamic_content( $value )->is_dynamic();
* Re-encode legacy dynamic content values in an attrs array.
* @param string[] $enabled_dynamic_attributes
protected function _encode_legacy_dynamic_content( $attrs, $enabled_dynamic_attributes ) {
if ( is_array( $attrs ) ) {
foreach ( $attrs as $field => $value ) {
$attrs[ $field ] = $this->_encode_legacy_dynamic_content_value( $field, $value, $enabled_dynamic_attributes );
* Re-encode legacy dynamic content value.
protected function _encode_legacy_dynamic_content_value( $field, $value, $enabled_dynamic_attributes ) {
if ( ! in_array( $field, $enabled_dynamic_attributes ) ) {
$json = et_builder_clean_dynamic_content( $value );
if ( preg_match( '/^@ET-DC@(.*?)@$/', $json ) ) {
return $this->_resolve_value_from_json( $field, $json, $enabled_dynamic_attributes );
* Resolve a value, be it static or dynamic to a static one.
* @param integer $post_id
* @param string[] $enabled_dynamic_attributes
* @param boolean $serialize
protected function _resolve_value( $post_id, $field, $value, $enabled_dynamic_attributes, $serialize ) {
if ( ! in_array( $field, $enabled_dynamic_attributes ) ) {
$builder_value = et_builder_parse_dynamic_content( $value );
return $builder_value->serialize();
$is_blog_query = isset( $wp_query->et_pb_blog_query ) && $wp_query->et_pb_blog_query;
if ( ! $is_blog_query && ! $wp_query->is_singular() ) {
return $builder_value->resolve( null );
return $builder_value->resolve( $post_id );
* Resolve a value from the legacy JSON format of dynamic content.
* This is essentially a migration but is implemented separately
* as it needs to parse every field of every module and do it
* before actual migrations are ran.
* @param integer $post_id
* @param string[] $enabled_dynamic_attributes
* @param boolean $serialize
protected function _resolve_value_from_json( $field, $value, $enabled_dynamic_attributes ) {
if ( ! in_array( $field, $enabled_dynamic_attributes ) ) {
$json = et_builder_clean_dynamic_content( $value );
// Replace encoded quotes.
$json = str_replace( array( '“', '”', '″', "%22" ), '"', $json );
// Strip <p></p> artifacts from wpautop in before/after settings. Example:
// {"dynamic":true,"content":"post_title","settings":{"before":"</p>
// This is a rough solution implemented due to time constraints.
("(?:before|after)":") # $1 = Anchor to the before/after settings.
(?: # Match cases where the value starts with the offending tag.
<\/?p> # The root of all evil.
[\r\n]+ # Whitespace follows the tag.
(?: # Match cases where the value ends with the offending tag.
([^"]*) # $2 = The preceeding value.
[\r\n]+ # Whitespace preceedes the tag.
<\/?p> # The root of all evil.
// Remove line-breaks which break the json strings.
$json = preg_replace( '/\r|\n/', '', $json );
$json_value = et_builder_parse_dynamic_content_json( $json );
if ( null === $json_value ) {
return $json_value->serialize();
* Escape an attribute's value.
* @param string $attribute
* @param string $html 'limited', 'full', 'none'
* @param string $predefined_value Predifined value need to escape.
protected function _esc_attr( $attribute, $html = 'none', $predefined_value = null ) {
$html = in_array( $html, array( 'limited', 'full' ), true ) ? $html : 'none';
$raw = isset( $this->attrs_unprocessed[ $attribute ] ) ? $this->attrs_unprocessed[ $attribute ] : '';
$formatted = isset( $this->props[ $attribute ] ) ? $this->props[ $attribute ] : '';
$dynamic_attributes = $this->_get_enabled_dynamic_attributes( $this->props );
// More often than not content is not an attribute so we need to handle that special case.
if ( 'content' === $attribute && ! isset( $this->attrs_unprocessed[ $attribute ] ) ) {
$raw = $this->content_unprocessed;
$formatted = $this->content;
if ( ! is_null( $predefined_value ) ) {
$formatted = $predefined_value;
if ( ! $this->_is_dynamic_value( $attribute, $raw, $dynamic_attributes ) ) {
if ( 'full' === $html ) {
return esc_html( $formatted );
if ( 'limited' === $html ) {
return wp_kses( $formatted, array(
'strong' => array( 'id' => array(), 'class' => array(), 'style' => array() ),
'em' => array( 'id' => array(), 'class' => array(), 'style' => array() ),
'i' => array( 'id' => array(), 'class' => array(), 'style' => array() ),
// Dynamic content values are escaped when they are resolved so we do not want to
// double-escape them when using them in the frontend, for example.
return et_core_esc_previously( $formatted );
* Get the current TB layout ID if we are rendering one or the current post ID instead.
public static function get_layout_id() {
$layout_id = self::get_theme_builder_layout_id();
$post_id = self::get_current_post_id_reverse();
return $layout_id ? $layout_id : $post_id;
* Get the current theme builder layout.
* Returns 'default' if no layout has been started.
public static function get_theme_builder_layout_type() {
$count = count( self::$theme_builder_layout );
return self::$theme_builder_layout[ $count - 1 ]['type'];
* Check if a module is rendered as normal post content or theme builder layout.
public static function is_theme_builder_layout() {
return 'default' !== self::get_theme_builder_layout_type();
* Get the current theme builder layout id.
* Returns 0 if no layout has been started.
public static function get_theme_builder_layout_id() {
$count = count( self::$theme_builder_layout );
return self::$theme_builder_layout[ $count - 1 ]['id'];
* Begin a theme builder layout.
* @param integer $layout_id
public static function begin_theme_builder_layout( $layout_id ) {
$type = get_post_type( $layout_id );
if ( ! et_theme_builder_is_layout_post_type( $type ) ) {
self::$theme_builder_layout[] = array(
'id' => (int) $layout_id,
* End the current theme builder layout.
public static function end_theme_builder_layout() {
array_pop(self::$theme_builder_layout);
* Get the order class suffix for the current theme builder layout, if any.
protected static function _get_theme_builder_order_class_suffix() {
$layout_type = self::get_theme_builder_layout_type();
ET_THEME_BUILDER_HEADER_LAYOUT_POST_TYPE => '_tb_header',
ET_THEME_BUILDER_BODY_LAYOUT_POST_TYPE => '_tb_body',
ET_THEME_BUILDER_FOOTER_LAYOUT_POST_TYPE => '_tb_footer',
if ( empty( $layout_type ) || ! isset( $type_map[ $layout_type ] ) ) {
return $type_map[ $layout_type ];
protected function field_to_css_prop( $field ) {
return str_replace( '_', '-', $field );
* Initialize Modules Cache
public static function init_cache() {
$cache = self::get_cache_filename();
if ( $cache && et_()->WPFS()->is_readable( $cache ) ) {
$result = @unserialize( et_()->WPFS()->get_contents( $cache ) );
if ( false !== $result ) {
if ( count( $result ) < 3 ) {
// Old cache format detected, delete everything
et_fb_delete_builder_assets();
if ( ! file_exists ( $cache ) ) {
// If cache has been successfully deleted, then init again.
list ( self::$_cache, self::$_fields_unprocessed ) = $result;
// Define option template variable instead of using list to avoid error that might
// happen when option template file exists (theme is updated) and frontend is
// accessed while static module field data hasn't been updated
$cached_option_template_data = et_()->array_get( $result, '2', array() );
$cached_option_template = et_()->array_get( $result, '3', array() );
$cached_option_template_tab_slug_maps = et_()->array_get( $result, '4', array() );
// init_cache() is called really early. $template property might not be available yet
if ( null === self::$option_template ) {
self::$option_template = et_pb_option_template();
// Set option template data from static cache if exist
if ( is_array( $cached_option_template_data ) && ! empty( $cached_option_template_data ) ) {
self::$option_template->set_data( $cached_option_template_data );
// Set option template from static cache if exist
if ( is_array( $cached_option_template ) && ! empty( $cached_option_template ) ) {
self::$option_template->set_templates( $cached_option_template );
// Set option template tab slug maps from static cache if exist
if ( is_array( $cached_option_template_tab_slug_maps ) && ! empty( $cached_option_template_tab_slug_maps ) ) {
self::$option_template->set_tab_slug_map( $cached_option_template_tab_slug_maps );
// Box Shadow sets WP hooks internally so we gotta load it anyway -> #blame_george.
ET_Builder_Module_Fields_Factory::get( 'BoxShadow' );
// Cache couldn't be unserialized, delete the file so it will be regenerated.
// Only save cache when a builder page is being rendered, needed because some data
// (e.g. mail provider defaults) is only generated in this case, hence saving while rendering
// a FE page or during AJAX call would result in cache missing data.
add_filter( 'et_builder_modules_is_saving_cache', '__return_true' );
add_action( 'et_builder_modules_loaded', array( 'ET_Builder_Element', 'save_cache' ) );
* Get Modules cache file name.
* @param mixed $post_type When set to `false`, autodetect.
public static function get_cache_filename( $post_type = false ) {
global $post, $et_builder_post_type;
$ajax_use_cache = apply_filters( 'et_builder_ajax_use_cache', false );