: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
// Parse incoming $args into an array and merge it with $defaults.
$contexts = wp_parse_args( $contexts, $defaults );
// Set custom props data.
if ( $contexts['custom_props'] && is_array( $contexts['custom_props'] ) ) {
$this->set_custom_props( $contexts['custom_props'] );
unset( $contexts['custom_props'] );
$tag = et_core_sanitize_element_tag( $contexts['tag'] );
// Bail early when the tag is invalid.
if ( ! $tag || is_wp_error( $tag ) ) {
// Bail early when required props is not fulfilled.
if ( ! $this->is_required_props_fulfilled( $contexts ) ) {
// Populate the element data.
$data = $this->populate_data( $contexts );
// Bail early when data is empty.
$desktop_styles = array();
$desktop_classes = array();
// Generate desktop attribute.
foreach ( et_()->array_get( $data, 'attrs.desktop', array() ) as $attr_key => $attr_value ) {
if ( 'style' === $attr_key ) {
foreach ( explode( ';', $attr_value ) as $inline_style ) {
$inline_styles = explode( ':', $inline_style );
if ( count( $inline_styles ) === 2 ) {
$desktop_styles[ $inline_styles[0] ] = $inline_styles[1];
} elseif ( 'class' === $attr_key ) {
if ( is_string( $attr_value ) ) {
$desktop_classes = array_merge( $desktop_classes, explode( ' ', $attr_value ) );
} elseif ( is_array( $attr_value ) ) {
$desktop_classes = array_merge( $desktop_classes, $attr_value );
if ( ! is_string( $attr_value ) ) {
$attr_value = esc_attr( wp_json_encode( $attr_value ) );
* Hide image tag instead showing broken image tag output
* This is needed because there is a case image
* just displayed on non desktop mode only.
if ( 'src' === $attr_key && ! $attr_value ) {
$desktop_classes[] = 'et_multi_view_hidden_image';
$desktop_attrs .= ' ' . esc_attr( $attr_key ) . '="' . et_core_esc_previously( $attr_value ) . '"';
// Inject desktop inline style attribute.
foreach ( et_()->array_get( $data, 'styles.desktop', array() ) as $style_key => $style_value ) {
$desktop_styles[ $style_key ] = $style_value;
foreach ( $desktop_styles as $style_key => $style_value ) {
$styles[] = esc_attr( $style_key ) . ':' . et_core_esc_previously( $style_value );
$desktop_attrs .= ' style="' . implode( ';', $styles ) . '"';
// Inject desktop class attribute.
foreach ( et_()->array_get( $data, 'classes.desktop', array() ) as $class_action => $class_names ) {
foreach ( $class_names as $class_name ) {
if ( 'remove' === $class_action && in_array( $class_name, $desktop_classes, true ) ) {
$desktop_classes = array_diff( $desktop_classes, array( $class_name ) );
if ( 'add' === $class_action && ! in_array( $class_name, $desktop_classes, true ) ) {
$desktop_classes[] = $class_name;
// Inject desktop visibility class attribute.
if ( ! et_()->array_get( $data, 'visibility.desktop', true ) ) {
$desktop_classes[] = 'et_multi_view_hidden';
if ( $desktop_classes ) {
$desktop_attrs .= ' class="' . implode( ' ', array_unique( $desktop_classes ) ) . '"';
if ( $this->is_self_closing_tag( $tag ) ) {
et_core_esc_previously( $tag ), // #1
et_core_esc_previously( $desktop_attrs ), // #2
'target' => $contexts['target'],
'hover_selector' => $contexts['hover_selector'],
'render_slug' => $contexts['render_slug'],
'<%1$s%2$s%3$s>%4$s</%1$s>',
et_core_esc_previously( $tag ), // #1
et_core_esc_previously( $desktop_attrs ), // #2
'target' => $contexts['target'],
'hover_selector' => $contexts['hover_selector'],
'render_slug' => $contexts['render_slug'],
et_core_esc_previously( et_()->array_get( $data, 'content.desktop', '' ) ) // #4
echo et_core_esc_previously( $output );
* Get or render the multi content attribute.
* @param array $contexts {
* @type string $content Param that will be used to populate the content data.
* Use props name wrapped with 2 curly brackets within the value for find & replace wildcard: {{props_name}}
* @type array $attrs Param that will be used to populate the attributes data.
* Associative array key used as attribute name and the value will be used as attribute value.
* Special case for 'class' and 'style' attribute name will only generating output for desktop mode.
* Use 'styles' or 'classes' context for multi modes usage.
* Use props name wrapped with 2 curly brackets within the value for find & replace wildcard: {{props_name}}
* @type array $styles Param that will be used to populate the inline style attributes data.
* Associative array key used as style property name and the value will be used as inline style property value.
* Use props name wrapped with 2 curly brackets within the value for find & replace wildcard: {{props_name}}
* @type array $classes Param that will be used to populate the class data.
* Associative array key used as class name and the value is associative array as the conditional check compared with prop value.
* The conditional check array key used as the prop name and the value used as the conditional check compared with prop value.
* The class will be added if all conditional check is true and will be removed if any of conditional check is false.
* @type array $visibility Param that will be used to populate the visibility data.
* Associative array key used as the prop name and the value used as the conditional check compared with prop value.
* The element will visible if all conditional check is true and will be hidden if any of conditional check is false.
* @type string $target HTML element selector target which the element will be modified. Default is empty string.
* Dynamic module order class wildcard string is accepted: %%order_class%%
* @type string $hover_selector HTML element selector which trigger the hover event. Default is empty string.
* Dynamic module order class wildcard string is accepted: %%order_class%%
* @type string $render_slug Render slug that will be used to calculate the module order class. Default is current module slug.
* @type array $custom_props Defined custom props data.
* @type array $conditional_values Defined data sources for data toggle.
* @type array $required List of required props key to render the element.
* Will returning empty string if any required props is empty.
* Default is empty array it will try to gather any props name set in the 'content' context.
* Set to false to disable conditional check.
* @param bool $echo Whether to print the output instead returning it.
* @param array $populated_data Pre populated data in case just need to format the attributes output.
* @param bool $as_array Whether to return the output as array or string.
public function render_attrs( $contexts = array(), $echo = false, $populated_data = null, $as_array = false ) {
// Define the array of defaults.
'custom_props' => array(),
// Parse incoming $args into an array and merge it with $defaults.
$contexts = wp_parse_args( $contexts, $defaults );
if ( $contexts['custom_props'] && is_array( $contexts['custom_props'] ) ) {
$this->set_custom_props( $contexts['custom_props'] );
unset( $contexts['custom_props'] );
$data = is_null( $populated_data ) ? $this->populate_data( $contexts ) : $populated_data;
foreach ( $data as $context => $modes ) {
// Distinct the values to omit duplicate values across modes.
$data[ $context ] = $this->distinct_values( $modes );
// Remove context data if there is only desktop mode data available.
// This intended to avoid unnecessary multi view attribute rendered if there is only desktop
// mode data is available.
if ( 1 === count( $data[ $context ] ) && isset( $data[ $context ]['desktop'] ) ) {
unset( $data[ $context ] );
if ( isset( $data['content'] ) ) {
foreach ( $data['content'] as $mode => $content ) {
$content = str_replace( '<', htmlentities( '<' ), $content );
$content = str_replace( '>', htmlentities( '>' ), $content );
$data['content'][ $mode ] = $content;
$content_desktop = et_()->array_get( $data, 'content.desktop', null );
$content_tablet = et_()->array_get( $data, 'content.tablet', null );
$content_phone = et_()->array_get( $data, 'content.phone', null );
$visibility_desktop = et_()->array_get( $data, 'visibility.desktop', null );
$visibility_tablet = et_()->array_get( $data, 'visibility.tablet', null );
$visibility_phone = et_()->array_get( $data, 'visibility.phone', null );
$is_hidden_on_load_tablet = false;
if ( ! is_null( $content_tablet ) && $content_desktop !== $content_tablet ) {
$is_hidden_on_load_tablet = true;
if ( ! is_null( $visibility_tablet ) && $visibility_desktop !== $visibility_tablet ) {
$is_hidden_on_load_tablet = true;
$is_hidden_on_load_phone = false;
if ( ! is_null( $content_phone ) && $content_desktop !== $content_phone ) {
$is_hidden_on_load_phone = true;
if ( ! is_null( $visibility_phone ) && $visibility_desktop !== $visibility_phone ) {
$is_hidden_on_load_phone = true;
if ( ! empty( $contexts['target'] ) ) {
if ( false !== strpos( $contexts['target'], '%%order_class%%' ) ) {
$render_slug = ! empty( $contexts['render_slug'] ) ? $contexts['render_slug'] : $this->slug;
$order_class = ET_Builder_Element::get_module_order_class( $render_slug );
$data['target'] = str_replace( '%%order_class%%', ".{$order_class}", $contexts['target'] );
$data['target'] = $contexts['target'];
if ( ! empty( $contexts['hover_selector'] ) ) {
if ( false !== strpos( $contexts['hover_selector'], '%%order_class%%' ) ) {
$render_slug = ! empty( $contexts['render_slug'] ) ? $contexts['render_slug'] : $this->slug;
$order_class = ET_Builder_Element::get_module_order_class( $render_slug );
$data['hover_selector'] = str_replace( '%%order_class%%', ".{$order_class}", $contexts['hover_selector'] );
$data['hover_selector'] = $contexts['hover_selector'];
$data_attr_key = esc_attr( $this->data_attr_key );
$output[ $data_attr_key ] = esc_attr( wp_json_encode( $data ) );
if ( $is_hidden_on_load_tablet ) {
$output[ $data_attr_key. '-load-tablet-hidden'] = 'true';
if ( $is_hidden_on_load_phone ) {
$output[ $data_attr_key. '-load-phone-hidden'] = 'true';
// Format the html data attribute output.
$output = sprintf( ' %1$s="%2$s"', $data_attr_key, esc_attr( wp_json_encode( $data ) ) );
if ( $is_hidden_on_load_tablet ) {
$output .= sprintf( ' %1$s="%2$s"', $data_attr_key . '-load-tablet-hidden', 'true' );
if ( $is_hidden_on_load_phone ) {
$output .= sprintf( ' %1$s="%2$s"', $data_attr_key . '-load-phone-hidden', 'true' );
if ( ! $echo || $as_array ) {
echo et_core_esc_previously( $output );
* Populate the multi view data.
* @param array $contexts {
* @type string $content Param that will be used to populate the content data.
* Use props name wrapped with 2 curly brackets within the value for find & replace wildcard: {{props_name}}
* @type array $attrs Param that will be used to populate the attributes data.
* Associative array key used as attribute name and the value will be used as attribute value.
* Special case for 'class' and 'style' attribute name will only generating output for desktop mode.
* Use 'styles' or 'classes' context for multi modes usage.
* Use props name wrapped with 2 curly brackets within the value for find & replace wildcard: {{props_name}}
* @type array $styles Param that will be used to populate the inline style attributes data.
* Associative array key used as style property name and the value will be used as inline style property value.
* Use props name wrapped with 2 curly brackets within the value for find & replace wildcard: {{props_name}}
* @type array $classes Param that will be used to populate the class data.
* Associative array key used as class name and the value is associative array as the conditional check compared with prop value.
* The conditional check array key used as the prop name and the value used as the conditional check compared with prop value.
* The class will be added if all conditional check is true and will be removed if any of conditional check is false.
* @type array $visibility Param that will be used to populate the visibility data.
* Associative array key used as the prop name and the value used as the conditional check compared with prop value.
* The element will visible if all conditional check is true and will be hidden if any of conditional check is false.
public function populate_data( $contexts = array() ) {
// Define the array of defaults.
// Parse incoming $args into an array and merge it with $defaults.
$contexts = wp_parse_args( $contexts, $defaults );
foreach ( $contexts as $context => $context_args ) {
// Skip if the context is not listed as default.
if ( ( ! isset( $defaults[ $context ] ) ) ) {
$callback = array( $this, "populate_data__{$context}" );
// Skip if the context has no callback handler.
if ( ! is_callable( $callback ) ) {
// @phpcs:ignore Generic.PHP.ForbiddenFunctions.Found
$context_data = call_user_func( $callback, $context_args );
// Skip if the context data is empty or WP_Error object.
if ( ! $context_data || is_wp_error( $context_data ) ) {
// Set the context data for each breakpoints.
foreach ( $context_data as $mode => $context_value ) {
$data[ $context ][ $mode ] = $context_value;
return $this->filter_data( $data );
* Populate content data context.
* @param string $content Data contexts.
protected function populate_data__content( $content ) {
if ( ! $content || ! is_string( $content ) ) {
if ( preg_match_all( $this->pattern, $content, $matches, PREG_SET_ORDER, 0 ) ) {
foreach ( $matches as $match ) {
if ( ! isset( $match[1] ) ) {
$values = $this->get_values( $match[1] );
$replacements[ $match[0] ] = array(
foreach ( $replacements as $find => $replacement ) {
foreach ( $replacement['values'] as $mode => $value ) {
// Manipulate the value if needed.
$value = $this->filter_value(
if ( ! is_wp_error( $value ) ) {
if ( ! isset( $data[ $mode ] ) ) {
$data[ $mode ] = $content;
$data[ $mode ] = str_replace( $find, $value, $data[ $mode ] );
// Manipulate the value if needed.
$value = $this->filter_value(