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/module/helpers/ResponsiveOptions.php

1502 lines
53 KiB
PHP

<?php
if ( ! defined( 'ABSPATH' ) ) {
die( 'Direct access forbidden.' );
}
/**
* Responsive options helper methods.
*
* @since 3.23 Add more helper functions. Originally, this class is introduced on Options Harmony v2.
* @since 3.22
*
* Class ET_Builder_Module_Helper_ResponsiveOptions
*/
class ET_Builder_Module_Helper_ResponsiveOptions {
const DESKTOP = 'desktop';
const TABLET = 'tablet';
const PHONE = 'phone';
public static function instance() {
static $instance;
return $instance ? $instance : $instance = new self();
}
private function __construct() {
// Now call me if you can
}
/**
* Get value from an array based on key. However, we can force to return default value if key
* doesn't exist or value is empty.
*
* @since 3.23
*
* @param array $list Array of values.
* @param string $key Target key.
* @param mixed $default Default value, return if the target doesn't exist.
* @param mixed $default_on_empty Force to return default if value is empty.
* @return mixed Value.
*/
private function get( $list, $key, $default = null, $default_on_empty = false ) {
$value = isset( $list[ $key ] ) ? $list[ $key ] : $default;
// Return default if we need non empty value to be used.
if ( $default_on_empty && empty( $value ) ) {
$value = $default;
}
return $value;
}
/**
* Check if responsive settings is enabled or not on the option.
*
* Mostly used by FE.
*
* @since 3.23
*
* @param array $attrs All module attributes.
* @param string $name Option name.
* @return boolean Responsive settings status.
*/
public function is_responsive_enabled( $attrs, $name ) {
$last_edited = $this->get( $attrs, "{$name}_last_edited", '' );
return $this->get_responsive_status( $last_edited );
}
/**
* Check if responsive settings are enabled on one of the options list.
*
* @since 3.23
*
* @param array $attrs All module attributes.
* @param array $list Options list.
* @return boolean Responsive styles status.
*/
public function is_any_responsive_enabled( $attrs, $list ) {
// Ensure list is not empty and valid array.
if ( empty( $list ) || ! is_array( $list ) ) {
return false;
}
// Check the responsive status one by one.
$is_responsive_enabled = false;
foreach ( $list as $name ) {
if ( $this->is_responsive_enabled( $attrs, $name ) ) {
// Break early if current field enabled responsive is found.
$is_responsive_enabled = true;
break;
}
}
return $is_responsive_enabled;
}
/**
* Get responsive status based one last edited value.
*
* Parsed *_last_edited value and determine wheter the passed string means it has responsive value
* or not. *_last_edited holds two values (responsive status and last opened tabs) in the following
* format: status|last_opened_tab.
*
* Copy of et_pb_get_responsive_status() with a little modified and to organize the code.
*
* @param string $last_edited Last edited field value.
* @return bool Responsive field status.
*/
public function get_responsive_status( $last_edited ) {
if ( empty( $last_edited ) || ! is_string( $last_edited ) ) {
return false;
}
$parsed_last_edited = explode( '|', $last_edited );
return isset( $parsed_last_edited[0] ) ? 'on' === $parsed_last_edited[0] : false;
}
/**
* Generate video background markup.
*
* When background support responsive settings, the default callback will be replaced with
* get_video_background() function to retrieve all video values for desktop, hover, tablet,
* and phone.
*
* @since 3.23
*
* @param array $args Background values.
* @param array $conditional_tags Conditional tags.
* @param array $current_page Current page info.
* @return mixed Mixed background content generated as video markup.
*/
public static function get_video_background( $args = array(), $conditional_tags = array(), $current_page = array() ) {
$base_name = isset( $args['computed_variables'] ) && isset( $args['computed_variables']['base_name'] ) ? $args['computed_variables']['base_name'] : 'background';
$attr_prefix = "{$base_name}_";
// Build custom args.
$default_args = array(
"{$attr_prefix}video_mp4" => isset( $args[ "{$attr_prefix}video_mp4" ] ) ? $args[ "{$attr_prefix}video_mp4" ] : '',
"{$attr_prefix}video_webm" => isset( $args[ "{$attr_prefix}video_webm" ] ) ? $args[ "{$attr_prefix}video_webm" ] : '',
"{$attr_prefix}video_width" => isset( $args[ "{$attr_prefix}video_width" ] ) ? $args[ "{$attr_prefix}video_width" ] : '',
"{$attr_prefix}video_height" => isset( $args[ "{$attr_prefix}video_height" ] ) ? $args[ "{$attr_prefix}video_height" ] : '',
'computed_variables' => array(
'base_name' => $base_name,
),
);
$hover_args = array(
"{$attr_prefix}video_mp4__hover" => isset( $args[ "{$attr_prefix}video_mp4__hover" ] ) ? $args[ "{$attr_prefix}video_mp4__hover" ] : '',
"{$attr_prefix}video_webm__hover" => isset( $args[ "{$attr_prefix}video_webm__hover" ] ) ? $args[ "{$attr_prefix}video_webm__hover" ] : '',
"{$attr_prefix}video_width__hover" => isset( $args[ "{$attr_prefix}video_width__hover" ] ) ? $args[ "{$attr_prefix}video_width__hover" ] : '',
"{$attr_prefix}video_height__hover" => isset( $args[ "{$attr_prefix}video_height__hover" ] ) ? $args[ "{$attr_prefix}video_height__hover" ] : '',
'computed_variables' => array(
'base_name' => $base_name,
'device' => '_hover',
),
);
$tablet_args = array(
"{$attr_prefix}video_mp4_tablet" => isset( $args[ "{$attr_prefix}video_mp4_tablet" ] ) ? $args[ "{$attr_prefix}video_mp4_tablet" ] : '',
"{$attr_prefix}video_webm_tablet" => isset( $args[ "{$attr_prefix}video_webm_tablet" ] ) ? $args[ "{$attr_prefix}video_webm_tablet" ] : '',
"{$attr_prefix}video_width_tablet" => isset( $args[ "{$attr_prefix}video_width_tablet" ] ) ? $args[ "{$attr_prefix}video_width_tablet" ] : '',
"{$attr_prefix}video_height_tablet" => isset( $args[ "{$attr_prefix}video_height_tablet" ] ) ? $args[ "{$attr_prefix}video_height_tablet" ] : '',
'computed_variables' => array(
'base_name' => $base_name,
'device' => 'tablet',
),
);
$phone_args = array(
"{$attr_prefix}video_mp4_phone" => isset( $args[ "{$attr_prefix}video_mp4_phone" ] ) ? $args[ "{$attr_prefix}video_mp4_phone" ] : '',
"{$attr_prefix}video_webm_phone" => isset( $args[ "{$attr_prefix}video_webm_phone" ] ) ? $args[ "{$attr_prefix}video_webm_phone" ] : '',
"{$attr_prefix}video_width_phone" => isset( $args[ "{$attr_prefix}video_width_phone" ] ) ? $args[ "{$attr_prefix}video_width_phone" ] : '',
"{$attr_prefix}video_height_phone" => isset( $args[ "{$attr_prefix}video_height_phone" ] ) ? $args[ "{$attr_prefix}video_height_phone" ] : '',
'computed_variables' => array(
'base_name' => $base_name,
'device' => 'phone',
),
);
$video_backgrounds = array();
// Get video background markup.
$background_video = ET_Builder_Element::get_video_background( $default_args );
if ( $background_video ) {
$video_backgrounds['desktop'] = $background_video;
}
$background_video_hover = ET_Builder_Element::get_video_background( $hover_args );
if ( $background_video_hover ) {
$video_backgrounds['hover'] = $background_video_hover;
}
$background_video_tablet = ET_Builder_Element::get_video_background( $tablet_args );
if ( $background_video_tablet ) {
$video_backgrounds['tablet'] = $background_video_tablet;
}
$background_video_phone = ET_Builder_Element::get_video_background( $phone_args );
if ( $background_video_phone ) {
$video_backgrounds['phone'] = $background_video_phone;
}
return $video_backgrounds;
}
/**
* Returns the field original name by removing the `_tablet` or `_phone` suffix if it exists.
*
* Only remove tablet/phone string of the last setting name. Doesn't work for other format.
*
* @since 3.23
*
* @param string $name Setting name.
* @return string Base setting name.
*/
public function get_field_base_name( $name ) {
// Do not use rtim as it removes by character not by string. So, cases like `key_tablets`
// will be reduced to `key`.
$regex = '/(.*)(_tablet|_phone)$/';
$replace = '${1}';
return preg_replace( $regex, $replace, $name );
}
/**
* Returns the field responsive name by adding the `_tablet` or `_phone` suffix if it exists.
*
* @since 3.27.4
*
* @param string $name Setting name.
* @param string $device Device name.
* @return string Field setting name.
*/
public function get_field_name( $name, $device = 'desktop' ) {
// Field name should not be empty.
if ( empty( $name ) ) {
return $name;
}
// Ensure device is not empty.
$device = '' === $device ? 'desktop' : $device;
// Get device name.
return 'desktop' !== $device ? "{$name}_{$device}" : $name;
}
/**
* Returns the device name by removing the `name` prefix. If the result is one of tablet or phone,
* return it. But, if it's empty, return desktop.
*
* @since 3.23
*
* @param string $name Setting name.
* @return string Device name.
*/
public function get_device_name( $name ) {
// Do not use rtim as it removes by character not by string. So, cases like `key_tablets`
// will be reduced to `key`.
$regex = '/(.*)(tablet|phone)$/';
$replace = '${2}';
$result = preg_replace( $regex, $replace, $name );
return in_array( $result, array( 'tablet', 'phone' ) ) ? $result : 'desktop';
}
/**
* Get responsive value based on field base name and device.
*
* NOTE: Function get_single_value() is different with get_any_value(). It will return only
* current field value without checking the previous device value.
*
* For example: We have Title Text Font Size -> desktop 30px, tablet 10px, phone 10px. Fetch
* the value for phone, it will return pure 10px even the value is same with tablet.
* We have Subtitle Text Font Size -> desktop 20px, tablet 15px, phone ''. Fetch
* the value for phone, it will return pure '' or default even the value is empty.
*
* To get tablet or phone value:
* 1. You can pass only field base name and device name as the 4th argument. The parameters
* structure it's made like that to make it similar with other get* method we already have.
* For example: get_single_value( $this->props, 'title_text_font_size', '', 'tablet' ).
*
* 2. Or you can pass the actual field name with device. If the field name is already contains
* _tablet and _phone, don't pass device parameter because it will be added as suffix.
* For example: get_single_value( $this->props, 'title_text_font_size_tablet', '' ).
*
* @since 3.23
*
* @param array $attrs All module attributes.
* @param string $name Option name.
* @param array $default_value Default value.
* @param string $device Current device name.
* @return mixed Current option value based on active device.
*/
public function get_single_value( $attrs, $name = '', $default_value = '', $device = 'desktop' ) {
// Ensure $device is not empty.
$device = '' === $device ? 'desktop' : $device;
// Ensure always use device as suffix if device is not desktop or empty.
if ( 'desktop' !== $device ) {
$base_name = $this->get_field_base_name( $name );
$name = "{$base_name}_{$device}";
}
return $this->get( $attrs, $name, $default_value, true );
}
/**
* Get current active device value from attributes.
*
* NOTE: Function get_any_value() is different with get_value(). It also compare the value
* with the previous device value to avoid duplication. Or you can also force to return
* either current or previous default value if needed.
*
* For example: We have Title Text Font Size -> desktop 30px, tablet 30px, phone 10px. When
* we fetch the value for tablet, it will return pure empty string ('') because
* tablet value is equal with desktop value.
*
* We have Title Text Font Size -> desktop 30px, tablet '', phone ''. When
* we fetch the value for phone and force it to return any value, it will
* return 30px because phone and tablet value is empty and the function will
* look up to tablet or even desktop value.
*
* To get tablet or phone value:
* 1. You can pass only field base name and device name as the 4th argument. The parameters
* structure it's made like that to make it similar with other get* method we already have.
* For example: get_any_value( $this->props, 'title_text_font_size', '', 'tablet' ).
*
* 2. Or you can pass the actual field name with device. If the field name is already contains
* _tablet and _phone, don't pass device parameter because it will be added as suffix.
* For example: get_any_value( $this->props, 'title_text_font_size_tablet', '' ).
*
* 3. You can also force to return any value by passing true on the 5th argument. In some cases
* we need this to fill missing tablet/phone value with desktop value.
*
* @since 3.23
*
* @param array $attrs All module attributes.
* @param string $name Option name.
* @param array $default_value Default value.
* @param bool $force_return Force to return any value.
* @param string $device Current device name.
* @return mixed Current option value based on active device.
*/
public function get_any_value( $attrs, $name = '', $default_value = '', $force_return = false, $device = 'desktop' ) {
// Ensure $device is not empty.
$device = '' === $device ? 'desktop' : $device;
// Ensure always use device as suffix if device is not desktop/empty.
if ( 'desktop' !== $device ) {
$base_name = $this->get_field_base_name( $name );
$name = "{$base_name}_{$device}";
}
// Get current value.
$current_value = $this->get( $attrs, $name, '' );
// Get previous value to be compared.
$prev_value = $this->get_default_value( $attrs, $name, $default_value );
// Force to return any values given.
if ( $force_return ) {
return ! empty( $current_value ) ? $current_value : $prev_value;
}
// Ensure current value is different with the previous device or default.
if ( $current_value === $prev_value ) {
return '';
}
return $current_value;
}
/**
* Get property's values for requested device.
*
* This function is added to summarize how we fetch desktop/hover/tablet/phone value. This
* function still uses get_any_value to get current device values.
*
* @since 3.23
*
* @param array $attrs List of all attributes and values.
* @param string $name Property name.
* @param mixed $default_value Default value.
* @param string $device Device name.
* @param boolean $force_return Force to return any values found.
*
* @return array Pair of devices and the values.
*/
public function get_property_value( $attrs, $name, $default_value = '', $device = 'desktop', $force_return = false ) {
// Default values.
$default_value = esc_attr( $default_value );
// Ensure $device is not empty.
$device = '' === $device ? 'desktop' : $device;
// Ensure attrs (values list) and name (property name) are not empty.
if ( empty( $attrs ) || '' === $name ) {
return $default_value;
}
$is_enabled = 'desktop' !== $device ? $this->is_responsive_enabled( $attrs, $name ) : true;
$suffix = 'desktop' !== $device ? "_{$device}" : '';
$value = $is_enabled ? $this->get_any_value( $attrs, "{$name}{$suffix}", $default_value, $force_return ) : $default_value;
return esc_attr( $value );
}
/**
* Get all properties values for all devices.
*
* This function is added to summarize how we fetch desktop/hover, tablet, and phone values. This
* function still use get_any_value to get current device values.
*
* @since 3.23
*
* @param array $attrs List of all attributes and values.
* @param string $name Property name.
* @param mixed $default_value Default value.
* @param boolean $force_return Force to return any values found.
*
* @return array Pair of devices and the values.
*/
public function get_property_values( $attrs, $name, $default_value = '', $force_return = false ) {
// Default values.
$default_value = esc_attr( $default_value );
$values = array(
'desktop' => $default_value,
'tablet' => $default_value,
'phone' => $default_value,
);
// Ensure attrs (values list) and name (property name) are not empty.
if ( empty( $attrs ) || '' === $name ) {
return $values;
}
$is_responsive = $this->is_responsive_enabled( $attrs, $name );
// Get values for each devices.
$values['desktop'] = esc_html( $this->get_any_value( $attrs, $name, $default_value, $force_return ) );
if ( $is_responsive ) {
$values['tablet'] = esc_html( $this->get_any_value( $attrs, "{$name}_tablet", $default_value, $force_return ) );
$values['phone'] = esc_html( $this->get_any_value( $attrs, "{$name}_phone", $default_value, $force_return ) );
}
return $values;
}
/**
* Get property value after checking whether it uses responsive or not
*
* If responsive is used, automatically return array of all devices value.
* If responsive is not used, return string of desktop value
*
* @since 4.6.0
*
* @param array $attrs List of all attributes and values.
* @param string $name Property name.
* @param mixed $default_value Default value.
* @param boolean $force_return Force to return any values found.
*
* @return string|array String if not responsive, Pair of devices and the values if responsive.
*/
public function get_checked_property_value( $attrs, $name, $default_value = '', $force_return = false ) {
$is_responsive = $this->is_responsive_enabled( $attrs, $name );
return $is_responsive ?
$this->get_property_values( $attrs, $name, $default_value, $force_return ) :
$this->get_property_value( $attrs, $name, $default_value, 'desktop', $force_return );
}
/**
* Get composite property's value for requested device.
*
* This function is added to summarize how we fetch desktop/hover/tablet/phone value. This
* function still uses get_any_value to get current device values.
*
* @since 3.27.4
*
* @param array $attrs List of all attributes and values.
* @param string $composite_name Composite property name.
* @param string $name Property name.
* @param mixed $default_value Default value.
* @param string $device Device name.
* @param boolean $force_return Force to return any values found.
*
* @return array Pair of devices and the values.
*/
public function get_composite_property_value( $attrs, $composite_name, $name, $default_value = '', $device = 'desktop', $force_return = false ) {
// Default values.
$default_value = esc_attr( $default_value );
// Ensure $device is not empty.
$device = '' === $device ? 'desktop' : $device;
// Ensure attrs, composite name (parent property name), name (property name) are not empty.
if ( empty( $attrs ) || '' === $composite_name || '' === $name ) {
return $default_value;
}
$is_enabled = 'desktop' !== $device ? $this->is_responsive_enabled( $attrs, $composite_name ) : true;
$suffix = 'desktop' !== $device ? "_{$device}" : '';
$value = $is_enabled ? $this->get_any_value( $attrs, "{$name}{$suffix}", $default_value, $force_return ) : $default_value;
return esc_attr( $value );
}
/**
* Get all composite properties values for all devices.
*
* This function is added to summarize how we fetch desktop/hover, tablet, and phone values. This
* function still use get_any_value to get current device values.
*
* @since 3.27.4
*
* @param array $attrs List of all attributes and values.
* @param string $composite_name Composite property name.
* @param string $name Property name.
* @param mixed $default_value Default value.
* @param boolean $force_return Force to return any values found.
*
* @return array Pair of devices and the values.
*/
public function get_composite_property_values( $attrs, $composite_name, $name, $default_value = '', $force_return = false ) {
// Default values.
$default_value = esc_attr( $default_value );
$values = array(
'desktop' => $default_value,
'tablet' => $default_value,
'phone' => $default_value,
);
// Ensure attrs, composite name (parent property name), name (property name) are not empty.
if ( empty( $attrs ) || '' === $composite_name || '' === $name ) {
return $values;
}
$is_responsive = $this->is_responsive_enabled( $attrs, $composite_name );
// Get values for each devices.
$values['desktop'] = esc_attr( $this->get_any_value( $attrs, $name, $default_value, $force_return ) );
if ( $is_responsive ) {
$values['tablet'] = esc_attr( $this->get_any_value( $attrs, "{$name}_tablet", $default_value, $force_return ) );
$values['phone'] = esc_attr( $this->get_any_value( $attrs, "{$name}_phone", $default_value, $force_return ) );
}
return $values;
}
/**
* Get multiple attributes value from current active device.
*
* Basically, this function is combination of:
* - Get any value of attribute
* - Check attribute responsive status for tablet/phone
* - Only send non empty attributes, except you force to return any given value
* - Doing all of the process above for more than one fields
*
* @since 3.23
*
* @param array $attrs All module attributes.
* @param string $list List of options name. Name should be field base name.
* @param bool $force_return Force to return any value.
* @param string $device Current device name.
* @return array All option values.
*/
public function get_any_responsive_values( $attrs, $list, $force_return = false, $device = 'desktop' ) {
// Ensure list is not empty and valid array.
if ( empty( $list ) || ! is_array( $list ) ) {
return array();
}
// Ensure device is not empty.
$device = '' === $device ? 'desktop' : $device;
// Fetch each attribute and store it in $values.
$values = array();
foreach ( $list as $field_key => $field_value ) {
// Check responsive status if current device is tablet or phone.
if ( 'desktop' !== $device && ! $this->is_responsive_enabled( $attrs, $field_key ) ) {
continue;
}
// Get value.
$value = $this->get_any_value( $attrs, $field_key, $field_value, $force_return, $device );
// No need to save the value if it's empty and we don't force to return any value.
if ( ! $force_return && empty( $value ) ) {
continue;
}
$values[ $field_key ] = $value;
}
return $values;
}
/**
* Get default value of active device. Mechanism:
*
* - Desktop => Return default value.
* - Tablet => Return desktop value or default value.
* - Phone => Return tablet value or desktop value or default value.
*
* @since 3.23
*
* @param array $attrs All module attributes.
* @param string $name Option name.
* @param array $default_value All module advanced defaults.
* @return mixed Previous option value based on active device.
*/
public function get_default_value( $attrs, $name = '', $default_value = '' ) {
// Get option base name.
$base_name = $this->get_field_base_name( $name );
$device = $this->get_device_name( $name );
// Get default value and return it for Desktop.
if ( 'desktop' === $device ) {
return $default_value;
}
// Get tablet value and return it for Tablet.
$desktop_value = $this->get( $attrs, "{$base_name}", $default_value, true );
if ( 'tablet' === $device ) {
return $desktop_value;
}
// Get phone value and return it for Phone.
$tablet_value = $this->get( $attrs, "{$base_name}_tablet", $desktop_value, true );
if ( 'phone' === $device ) {
return $tablet_value;
}
return $default_value;
}
/**
* Returns responsive modes list from largest to narrow
*
* @return string[]
*/
public function get_modes() {
return array( self::DESKTOP, self::TABLET, self::PHONE );
}
/**
* Returns next wider mode then provided
*
* @param $mode
*
* @return null|string
*/
public function get_wider_mode( $mode ) {
$modes = $this->get_modes();
$key = array_search( $this->validate_mode( $mode ), $modes );
return false != $key ? et_()->array_get( $modes, '[' . ( -- $key ) . ']', null ) : null;
}
/**
* Returns next narrower mode then provided
*
* @param $mode
*
* @return null|string
*/
public function get_narrower_mode( $mode ) {
$modes = $this->get_modes();
$key = array_search( $this->validate_mode( $mode ), $modes );
return false !== $key && isset( $modes[ $key + 1 ] ) ? $modes[ $key + 1 ] : null;
}
/**
* Return default responsive mode
*
* @return string
*/
public function get_default_mode() {
return self::DESKTOP;
}
/**
* Returns setting field name by responsive mode
*
* @param $setting
* @param $mode
*
* @return string
*/
public function get_field( $setting, $mode ) {
return $setting . $this->mode_field( (string) $this->validate_mode( $mode ) );
}
/**
* Returns setting field name of the last edited mode
*
* @param string $setting
*
* @return string
*/
public function get_last_edited_field( $setting ) {
return "{$setting}_last_edited";
}
/**
* Checks if setting responsive mode is enabled
*
* @param $setting
* @param $props
*
* @return bool
*/
public function is_enabled( $setting, $props ) {
$value = et_builder_module_prop( $this->get_last_edited_field( $this->get_field_base_name( $setting ) ), $props, '' );
return et_pb_get_responsive_status( $value );
}
/**
* Returns the props value by mode
* If no mode provided, the default mode is used
*
* @param $setting
* @param $props
* @param null $mode
* @param string $default
*
* @return mixed
*/
public function get_value( $setting, $props, $mode = null, $default = '' ) {
$mode = $this->get_mode_or_default( $mode );
if ( $this->get_default_mode() != $mode && ! $this->is_enabled( $setting, $props ) ) {
return $default;
}
return et_builder_module_prop( $this->get_field( $setting, $mode ), $props, $default );
}
/**
* Is the implementation of get_value specifically for desktop mode
*
* Note: since the desktop mode is the default mode,
* this method would similar to get_value without providing mode,
* but can be used for a more explicit representation
*
* @param string $setting
* @param array $props
* @param mixed $default
*
* @return mixed
*/
public function get_desktop_value( $setting, $props, $default = null ) {
return $this->get_value( $setting, $props, self::DESKTOP, $default );
}
/**
* Is the implementation of get_value specifically for tablet mode
*
* @param string $setting
* @param array $props
* @param mixed $default
*
* @return mixed
*/
public function get_tablet_value( $setting, $props, $default = null ) {
return $this->get_value( $setting, $props, self::TABLET, $default );
}
/**
* Is the implementation of get_value specifically for phone mode
*
* @param string $setting
* @param array $props
* @param mixed $default
*
* @return mixed
*/
public function get_phone_value( $setting, $props, $default = null ) {
return $this->get_value( $setting, $props, self::PHONE, $default );
}
/**
* Returns the last edited responsive mode of the provided setting
* If not valid value is provided, default mode is returned
*
* @param $setting
* @param $props
*
* @return string
*/
public function get_last_edited( $setting, $props ) {
$value = et_builder_module_prop( $this->get_last_edited_field( $setting ), $props, '' );
$mode = et_()->array_get( explode( '|', $value ), '[1]' );
return $this->validate_mode( $mode ) ? $mode : $this->get_default_mode();
}
/**
* Get breakpoint minimum widths
*
* @since 4.3.2
*
* @return array
*/
public function get_breakpoint_min_widths() {
return array(
'desktop' => 980,
'tablet' => 768,
'phone' => 0,
);
}
/**
* Get breakpoint based on device name.
*
* @since 4.0
*
* @param string $device
*
* @return string
*/
public function get_breakpoint_by_device( $device = 'desktop' ) {
switch ( $device ) {
case 'desktop_only':
return 'min_width_981';
case 'tablet':
return 'max_width_980';
case 'tablet_only':
return '768_980';
case 'desktop_tablet_only':
return 'min_width_768';
case 'phone':
return 'max_width_767';
default:
return 'general';
}
}
/**
* @param $mode
*
* @return bool|string
*/
protected function validate_mode( $mode ) {
return in_array( strtolower( $mode ), $this->get_modes() ) ? strtolower( $mode ) : false;
}
protected function get_mode_or_default( $mode ) {
return $this->validate_mode( $mode ) ? strtolower( $mode ) : $this->get_default_mode();
}
/**
* Returns mode suffix
* The default mode suffix is empty
*
* @param $mode
*
* @return string
*/
protected function mode_field( $mode ) {
switch ( $mode ) {
case $this->get_default_mode():
return '';
default:
return "_$mode";
}
}
/**
* Generates the css code for responsive options.
*
* Uses array of values for each device as input parameter and css_selector with property to
* apply the css.
*
* Copy of et_pb_generate_responsive_css() with some modifications to improve.
*
* @since 3.23
*
* @param array $values_array All device values.
* @param mixed $css_selector CSS selector.
* @param string $css_property CSS property.
* @param string $function_name Module slug.
* @param string $additional_css Additional CSS.
* @param string $type Value type to determine need filter or not. Previously, it only
* accept value from range control and run a function to process
* range value.
* @param string $priority CSS style declaration priority.
*/
public function generate_responsive_css( $values_array, $css_selector, $css_property, $function_name, $additional_css = '', $type = 'range', $priority = '' ) {
if ( empty( $values_array ) ) {
return;
}
foreach ( $values_array as $device => $current_value ) {
if ( empty( $current_value ) ) {
continue;
}
// 1. Selector.
// There are some cases where selector is an object contains specific selector for
// each devices.
$selector = $css_selector;
if ( is_array( $css_selector ) ) {
$selector = ! empty( $css_selector[ $device ] ) ? $css_selector[ $device ] : '';
}
if ( empty( $selector ) ) {
continue;
}
// 2. Declare CSS style.
// There are some cases before we can declare the CSS style:
// 1. The value is an array contains pair of properties and values.
// 2. The value is single string but we have multiple properties exist.
// 3. The value is single string with only one property.
$declaration = '';
// Value can be provided as a string or array in following format:
// array(
// 'property_1' => 'value_1', 'property_2' => 'value_2', ... ,
// 'property_n' => 'value_n'
// )
if ( is_array( $current_value ) ) {
foreach ( $current_value as $this_property => $this_value ) {
if ( empty( $this_property ) || '' === $this_value ) {
continue;
}
// Get valid value. Previously, it only works for range control value and run
// et_builder_process_range_value function directly. Keep it as it is now for
// backward compatibility.
$valid_value = $this_value;
if ( 'range' === $type ) {
$valid_value = et_builder_process_range_value( $this_value, $this_property );
}
$declaration .= sprintf(
'%1$s: %2$s%3$s',
$this_property,
esc_html( $valid_value ),
'' !== $additional_css ? $additional_css : ';'
);
}
} elseif ( is_array( $css_property ) ) {
// Get valid value. Previously, it only works for range control value and run
// et_builder_process_range_value function directly.
$valid_value = $current_value;
foreach ( $css_property as $this_property ) {
if ( empty( $this_property ) ) {
continue;
}
if ( 'range' === $type ) {
$valid_value = et_builder_process_range_value( $current_value, $this_property );
}
$declaration .= sprintf(
'%1$s: %2$s%3$s',
$this_property,
esc_html( $valid_value ),
'' !== $additional_css ? $additional_css : ';'
);
}
} elseif ( ! empty( $css_property ) ) {
// Get valid value. Previously, it only works for range control value and run
// et_builder_process_range_value function directly.
$valid_value = $current_value;
if ( 'range' === $type ) {
$valid_value = et_builder_process_range_value( $current_value, $css_property );
}
$declaration = sprintf(
'%1$s: %2$s%3$s',
$css_property,
esc_html( $valid_value ),
'' !== $additional_css ? $additional_css : ';'
);
}
if ( '' === $declaration ) {
continue;
}
$style = array(
'selector' => $selector,
'declaration' => $declaration,
);
if ( 'desktop_only' === $device ) {
$style['media_query'] = ET_Builder_Element::get_media_query( 'min_width_981' );
} elseif ( 'desktop' !== $device ) {
$current_media_query = 'tablet' === $device ? 'max_width_980' : 'max_width_767';
$style['media_query'] = ET_Builder_Element::get_media_query( $current_media_query );
}
// Priority.
if ( '' !== $priority ) {
$style['priority'] = $priority;
}
ET_Builder_Element::set_style( $function_name, $style );
}
}
/**
* Generates the CSS code for responsive options based on existing declaration.
*
* Similar with generate_responsive_css(), but it's only used to declare the CSS and selector
* without set styles on ET_Builder_Element.
*
* @since 3.23
*
* @param array $values_array All device values.
* @param mixed $css_selector CSS selector.
* @param string $function_name Module slug.
* @param string $priority CSS style declaration priority.
*/
public function declare_responsive_css( $values_array, $css_selector, $function_name, $priority = '' ) {
if ( empty( $values_array ) ) {
return;
}
foreach ( $values_array as $device => $declaration ) {
if ( empty( $declaration ) ) {
continue;
}
$style = array(
'selector' => $css_selector,
'declaration' => $declaration,
);
// Media query.
if ( 'desktop_only' === $device ) {
$style['media_query'] = ET_Builder_Element::get_media_query( 'min_width_981' );
} elseif ( 'desktop' !== $device ) {
$current_media_query = 'tablet' === $device ? 'max_width_980' : 'max_width_767';
$style['media_query'] = ET_Builder_Element::get_media_query( $current_media_query );
}
// Priority.
if ( '' !== $priority ) {
$style['priority'] = $priority;
}
ET_Builder_Element::set_style( $function_name, $style );
}
}
/**
* Generate additional responsive fields such as _tablet, _phone, and _last_edited.
*
* @since 3.23
*
* @param string $field_name Base field name.
* @param string $toggle_slug Toggle slug name.
* @param string $tab_slug Tab slug name.
* @param array $field Field data in array.
* @return array Responsive fields set.
*/
public function generate_responsive_fields( $field_name, $toggle_slug, $tab_slug, $field = array() ) {
$responsive_options = array();
// Add fields with responsive suffix for each devices.
$responsive_options[ "{$field_name}_tablet" ] = array(
'type' => 'skip',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
);
$responsive_options[ "{$field_name}_phone" ] = array(
'type' => 'skip',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
);
$responsive_options[ "{$field_name}_last_edited" ] = array(
'type' => 'skip',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
);
// Add computed effect field on mobile and hover fields.
if ( ! empty( $field['computed_affects'] ) && isset( $field['affects_mobile'] ) && true === $field['affects_mobile'] ) {
$responsive_options[ "{$field_name}_tablet" ]['computed_affects'] = $field['computed_affects'];
$responsive_options[ "{$field_name}_phone" ]['computed_affects'] = $field['computed_affects'];
$responsive_options[ "{$field_name}__hover" ] = array(
'type' => 'skip',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'computed_affects' => $field['computed_affects'],
);
}
// Set default on tablet and phone if needed.
if ( ! empty( $field['default_on_mobile'] ) ) {
$default = ! empty( $field['default'] ) ? $field['default'] : '';
$default_mobile = ! empty( $field['default_on_mobile'] ) ? $field['default_on_mobile'] : $default;
$default_tablet = ! empty( $field['default_on_tablet'] ) ? $field['default_on_tablet'] : $default_mobile;
$default_phone = ! empty( $field['default_on_phone'] ) ? $field['default_on_phone'] : $default_mobile;
$responsive_options[ "{$field_name}_tablet" ]['default'] = $default_tablet;
$responsive_options[ "{$field_name}_phone" ]['default'] = $default_phone;
}
// Set default on hover if needed.
if ( ! empty( $field['default_on_hover'] ) ) {
$default = ! empty( $field['default'] ) ? $field['default'] : '';
$default_hover = ! empty( $field['default_on_hover'] ) ? $field['default_on_hover'] : $default_mobile;
$responsive_options[ "{$field_name}__hover" ]['default'] = $default_hover;
}
return $responsive_options;
}
/**
* Get main background value based on enabled status of current field. It's used to selectively
* get the correct color, gradient status, image, and video. It's introduced along with new
* enable fields to decide should we remove or inherit the value from larger device.
*
* @since 3.24.1
*
* @param array $attrs All module attributes.
* @param string $base_setting Setting need to be checked.
* @param string $preview_mode Current preview mode.
* @param string $background_base Background base name (background, button_bg, etc.)
* @param array $fields All module fields definition.
* @param string $value Active value.
* @param string $default_value Active default value.
*
* @return string New value.
*/
public function get_inheritance_background_value( $attrs, $base_setting, $preview_mode, $background_base = 'background', $fields = array(), $value = '', $default_value = '' ) {
// Default new value is same with the generated or active one.
$new_value = $value;
$enable_fields = array(
"{$background_base}_color" => "{$background_base}_enable_color",
'use_background_color_gradient' => 'use_background_color_gradient',
"{$background_base}_use_color_gradient" => "{$background_base}_enable_use_color_gradient",
"{$background_base}_image" => "{$background_base}_enable_image",
"video_{$background_base}_values" => "video_{$background_base}_values",
);
// Empty string is slug for desktop.
$map_slugs = array(
'desktop' => array( '' ),
'hover' => array( '__hover', '' ),
'sticky' => array( '__sticky', '' ),
'tablet' => array( '_tablet', '' ),
'phone' => array( '_phone', '_tablet', '' ),
);
// Start checking if current field is enabled or disabled.
$base_enable_field_name = ! empty( $enable_fields[ $base_setting ] ) ? $enable_fields[ $base_setting ] : '';
// Bail early if setting name is different.
if ( '' === $base_enable_field_name || ! isset( $map_slugs[ $preview_mode ] ) ) {
return $new_value;
}
$new_value = '';
$origin_mp4_enabled = '';
$origin_mp4_data = array();
$origin_webm_enabled = '';
$origin_webm_data = array();
foreach ( $map_slugs[ $preview_mode ] as $slug ) {
// BG Color.
if ( in_array( $base_setting, array( "{$background_base}_color", "{$background_base}_image" ) ) ) {
$base_type = str_replace( "{$background_base}_", '', $base_setting );
$enable_default = ! empty( $fields[ "{$background_base}_enable_{$base_type}{$slug}" ] ) && ! empty( $fields[ "{$background_base}_enable_{$base_type}{$slug}" ]['default'] ) ? $fields[ "{$background_base}_enable_{$base_type}{$slug}" ]['default'] : '';
$enable_value = ! empty( $attrs[ "{$background_base}_enable_{$base_type}{$slug}" ] ) ? $attrs[ "{$background_base}_enable_{$base_type}{$slug}" ] : $enable_default;
$bg_value = ! empty( $attrs[ "{$background_base}_{$base_type}{$slug}" ] ) ? $attrs[ "{$background_base}_{$base_type}{$slug}" ] : '';
$is_bg_enabled = 'off' !== $enable_value;
if ( '' !== $bg_value && $is_bg_enabled ) {
$new_value = $bg_value;
break;
} elseif ( ! $is_bg_enabled ) {
$new_value = '';
break;
}
// BG Gradient.
} elseif ( in_array( $base_setting, array( 'use_background_color_gradient', "{$background_base}_use_color_gradient" ) ) ) {
$new_value = 'off';
$field_map = array(
'use_background_color_gradient' => array(
'value' => "use_background_color_gradient{$slug}",
'start' => "{$background_base}_color_gradient_start{$slug}",
'end' => "{$background_base}_color_gradient_end{$slug}",
),
"{$background_base}_use_color_gradient" => array(
'value' => "{$background_base}_use_color_gradient{$slug}",
'start' => "{$background_base}_color_gradient_start{$slug}",
'end' => "{$background_base}_color_gradient_end{$slug}",
),
);
$field_value = '';
$field_start = '';
$field_end = '';
if ( ! empty( $field_map[ $base_setting ] ) ) {
$field_value = ! empty( $field_map[ $base_setting ]['value'] ) ? $field_map[ $base_setting ]['value'] : '';
$field_start = ! empty( $field_map[ $base_setting ]['start'] ) ? $field_map[ $base_setting ]['start'] : '';
$field_end = ! empty( $field_map[ $base_setting ]['end'] ) ? $field_map[ $base_setting ]['end'] : '';
}
$use_gradient_default = ! empty( $fields[ $field_value ] ) ? $fields[ $field_value ] : '';
$use_gradient_value = ! empty( $attrs[ $field_value ] ) ? $attrs[ $field_value ] : $use_gradient_default;
$gradient_start_value = ! empty( $attrs[ $field_start ] ) ? $attrs[ $field_start ] : '';
$gradient_end_value = ! empty( $attrs[ $field_end ] ) ? $attrs[ $field_end ] : '';
$is_gradient_enabled = 'off' !== $use_gradient_value;
if ( ( '' !== $gradient_start_value || '' !== $gradient_end_value ) && $is_gradient_enabled ) {
$new_value = 'on';
break;
} elseif ( ! $is_gradient_enabled ) {
$new_value = 'off';
break;
}
// BG Video.
} elseif ( "video_{$background_base}_values" === $base_setting ) {
$base_slug = preg_replace( '/[_]+/', '', $slug );
$current_mode = '' !== $base_slug ? $base_slug : 'desktop';
// Video markup.
$video_background = et_()->array_get( $attrs, "{$base_setting}.{$current_mode}", '' );
// MP4.
$enable_mp4_default = et_()->array_get( $fields, "{$background_base}_enable_video_mp4{$slug}", '' );
$enable_mp4_value = $this->get_any_value( $attrs, "{$background_base}_enable_video_mp4{$slug}", $enable_mp4_default, true );
$is_mp4_enabled = 'off' !== $enable_mp4_value;
$video_mp4_value = et_pb_responsive_options()->get_any_value( $attrs, "{$background_base}_video_mp4" );
if ( 'hover' === $current_mode ) {
$video_mp4_hover_value = et_()->array_get( $attrs, "{$background_base}_video_mp4__hover", '' );
$video_mp4_value = '' !== $video_mp4_hover_value ? $video_mp4_hover_value : $video_mp4_value;
} else {
$video_mp4_value = et_pb_responsive_options()->get_any_value( $attrs, "{$background_base}_video_mp4{$slug}", '', true );
}
// Check MP4 enabled and data status.
if ( '' === $origin_mp4_enabled ) {
if ( '' !== $video_mp4_value && $is_mp4_enabled ) {
$origin_mp4_enabled = 'enabled';
$origin_mp4_data = array(
'mode' => $current_mode,
'video_value' => $video_mp4_value,
'video_background' => $video_background,
'display' => ! empty( $video_background ) ? 'self' : 'inherit',
);
} elseif ( false === $is_mp4_enabled ) {
$origin_mp4_enabled = 'disabled';
$origin_mp4_data = array();
}
} elseif ( 'enabled' === $origin_mp4_enabled ) {
if ( isset( $origin_mp4_data['video_background'] ) && empty( $origin_mp4_data['video_background'] ) ) {
$origin_mp4_data['video_background'] = $video_background;
}
}
// Webm.
$enable_webm_default = et_()->array_get( $fields, "{$background_base}_enable_video_webm{$slug}", '' );
$enable_webm_value = $this->get_any_value( $attrs, "{$background_base}_enable_video_webm{$slug}", $enable_webm_default, true );
$is_webm_enabled = 'off' !== $enable_webm_value;
$video_webm_value = et_pb_responsive_options()->get_any_value( $attrs, "{$background_base}_video_webm" );
if ( 'hover' === $current_mode ) {
$video_webm_hover_value = et_()->array_get( $attrs, "{$background_base}_video_webm__hover", '' );
$video_webm_value = '' !== $video_webm_hover_value ? $video_webm_hover_value : $enable_webm_value;
} else {
$video_webm_value = et_pb_responsive_options()->get_any_value( $attrs, "{$background_base}_video_webm{$slug}", '', true );
}
// Check Webm enabled and data status.
if ( '' === $origin_webm_enabled ) {
if ( '' !== $video_webm_value && $is_webm_enabled ) {
$origin_webm_enabled = 'enabled';
$origin_webm_data = array(
'mode' => $current_mode,
'video_value' => $video_webm_value,
'video_background' => $video_background,
'display' => ! empty( $video_background ) ? 'self' : 'inherit',
);
} elseif ( ! $is_webm_enabled ) {
$origin_webm_enabled = 'disabled';
$origin_webm_data = array();
}
} elseif ( 'enabled' === $origin_webm_enabled ) {
if ( isset( $origin_webm_data['video_background'] ) && empty( $origin_webm_data['video_background'] ) ) {
$origin_webm_data['video_background'] = $video_background;
}
}
// Continue if current mode is not desktop.
if ( '' !== $slug ) {
continue;
}
// Decide to display the video or not.
if ( 'disabled' === $origin_mp4_enabled && 'disabled' === $origin_webm_enabled ) {
$new_value = array(
'display' => 'hide',
'video' => '',
);
} else {
// MP4 display and video status.
$mp4_enabled_display = et_()->array_get( $origin_mp4_data, 'display', '' );
$mp4_enabled_mode = et_()->array_get( $origin_mp4_data, 'mode', '' );
$mp4_video_background = '';
if ( $preview_mode === $mp4_enabled_mode ) {
$mp4_video_background = et_()->array_get( $origin_mp4_data, 'video_background', '' );
}
// Webm display and video status.
$webm_enabled_display = et_()->array_get( $origin_webm_data, 'display', '' );
$webm_enabled_mode = et_()->array_get( $origin_webm_data, 'mode', '' );
$webm_video_background = '';
if ( $preview_mode === $webm_enabled_mode ) {
$webm_video_background = et_()->array_get( $origin_webm_data, 'video_background', '' );
}
// Set display current video or not.
$new_video_display = 'hide';
if ( '' !== $mp4_enabled_display ) {
$new_video_display = $mp4_enabled_display;
} elseif ( '' !== $webm_enabled_display ) {
$new_video_display = $webm_enabled_display;
}
// Set video markup.
$new_video_background = '';
if ( '' !== $mp4_video_background ) {
$new_video_background = $mp4_video_background;
} elseif ( '' !== $webm_video_background ) {
$new_video_background = $webm_video_background;
}
$new_value = array(
'display' => $new_video_display,
'video' => $new_video_background,
);
}
}
}
return $new_value;
}
public function create( $additional_options ) {
// Add hover field indication
$additional_options['hover_enabled'] = array(
'type' => 'skip',
'default' => 0,
);
// Generate responsive fields for additional options (Design).
// There are 4 types where the mobile_options exist on the options.
// 1. Exist on the option definition.
// 2. Exist on the computed field type, just like point 1 but we threat it differently
// because there are some properties need to be updated and added.
// 3. Exist on the background-field.
// 4. Exist on the composite field.
foreach ( $additional_options as $field_name => $field ) {
$is_mobile_options = isset( $field['mobile_options'] ) && $field['mobile_options'];
$is_hover = isset( $field['hover'] ) && 'tabs' === $field['hover'];
$field_type = isset( $field['type'] ) ? $field['type'] : '';
$field_context = isset( $field['context'] ) ? $field['context'] : '';
$field_last_edited = isset( $field['last_edited'] ) ? $field['last_edited'] : '';
// Mobile options property maybe exist on the field.
if ( $is_mobile_options ) {
// Get tab and toggle slugs value.
$tab_slug = isset( $field['tab_slug'] ) ? $field['tab_slug'] : '';
$toggle_slug = isset( $field['toggle_slug'] ) ? $field['toggle_slug'] : '';
// 2. Mobile options property for computed fields.
if ( 'computed' === $field_type ) {
// Computed depends on. Add suffix after depends on info.
if ( ! empty( $field['computed_depends_on'] ) ) {
$computed_depends_on = $field['computed_depends_on'];
foreach ( $computed_depends_on as $depends_value ) {
if ( $is_hover ) {
array_push( $field['computed_depends_on'], "{$depends_value}_tablet", "{$depends_value}_phone", "{$depends_value}__hover" );
} else {
array_push( $field['computed_depends_on'], "{$depends_value}_tablet", "{$depends_value}_phone" );
}
}
}
// Computed minimum. Add suffix after minimum info.
if ( ! empty( $field['computed_minimum'] ) ) {
$computed_minimum = $field['computed_minimum'];
foreach ( $computed_minimum as $minimum_value ) {
if ( $is_hover ) {
array_push( $field['computed_minimum'], "{$minimum_value}_tablet", "{$minimum_value}_phone", "{$minimum_value}__hover" );
} else {
array_push( $field['computed_minimum'], "{$minimum_value}_tablet", "{$minimum_value}_phone" );
}
}
}
$additional_options[ "{$field_name}" ] = $field;
continue;
}
// 3. Mobile options property maybe exist under background field.
if ( 'background-field' === $field_type ) {
// Just in case current field is background-field and the mobile_options
// attributes are located in the fields. Ensure background fields is exist.
if ( ! empty( $field['background_fields'] ) ) {
// Fetch the fields and check for mobile_options.
foreach ( $field['background_fields'] as $background_name => $background_field ) {
if ( isset( $background_field['mobile_options'] ) && $background_field['mobile_options'] ) {
// Get tab and toggle slugs value.
$tab_slug = isset( $background_field['tab_slug'] ) ? $background_field['tab_slug'] : '';
$toggle_slug = isset( $background_field['toggle_slug'] ) ? $background_field['toggle_slug'] : '';
// Add fields with responsive suffix for each devices.
$additional_options = array_merge(
$additional_options,
et_pb_responsive_options()->generate_responsive_fields( $background_name, $toggle_slug, $tab_slug )
);
}
}
}
continue;
}
// 1. Mobile options property added directly on options definition. Add fields
// with responsive suffix for each devices.
$additional_options = array_merge(
$additional_options,
et_pb_responsive_options()->generate_responsive_fields( $field_name, $toggle_slug, $tab_slug, $field )
);
// Additional last edited field just in case we need more last edited field.
if ( ! empty( $field_last_edited ) ) {
$additional_options[ "{$field_last_edited}_last_edited" ] = array(
'type' => 'skip',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
);
}
continue;
}
// 4. Mobile options property maybe exist under composite field.
if ( 'composite' === $field_type ) {
// Just in case current field is composite and the mobile_options attributes
// are located in the controls. Ensure composite structure is exist.
$composite_structure = isset( $field['composite_structure'] ) ? $field['composite_structure'] : array();
if ( empty( $composite_structure ) ) {
continue;
}
foreach ( $composite_structure as $composite_field ) {
// Ensure composite field controls is exist and not empty.
$composite_field_controls = isset( $composite_field['controls'] ) ? $composite_field['controls'] : array();
if ( empty( $composite_field_controls ) ) {
continue;
}
// Fetch the controls and check for mobile_options.
foreach ( $composite_field_controls as $control_name => $control ) {
if ( isset( $control['mobile_options'] ) && $control['mobile_options'] ) {
// Get tab and toggle slugs value.
$tab_slug = isset( $control['tab_slug'] ) ? $control['tab_slug'] : '';
$toggle_slug = isset( $control['toggle_slug'] ) ? $control['toggle_slug'] : '';
// Add fields with responsive suffix for each devices.
$additional_options = array_merge(
$additional_options,
et_pb_responsive_options()->generate_responsive_fields( $control_name, $toggle_slug, $tab_slug )
);
}
}
}
}
}
return $additional_options;
}
}