Custom CSS` toggle.
*
* @var bool
*/
public $custom_css_tab;
/**
* Whether module support visual builder. e.g `on` or `off`.
*
* @var string
*/
public $vb_support = 'off';
/**
* Options list to not replace %22 with double quotes while rendering.
*
* @var array
*/
public $dbl_quote_exception_options = array( 'et_pb_font_icon', 'et_pb_button_one_icon', 'et_pb_button_two_icon', 'et_pb_button_icon', 'et_pb_content' );
/**
* Module's settings modal custom tabs.
*
* @var array|array[]
*/
public $settings_modal_tabs = array();
/**
* Module's settings modal toggles. e.x `background`, `custom css`.
*
* @var array|mixed
*/
public $settings_modal_toggles = array();
/**
* Whether module support post featured image background.
*
* @var bool
*/
public $featured_image_background = false;
/**
* All CSS classes name the module has.
*
* @var array
*/
public $classname = array();
/**
* Module's help video configuration array.
*
* @var array
*/
public $help_videos = array();
/**
* Whether `ET_Builder_Module_Settings_Migration` class initialized.
*
* @var bool
*/
public static $settings_migrations_initialized = false;
/**
* Unused var. @todo Remove this unused var.
*
* @var bool
*/
public static $setting_advanced_styles = false;
/**
* An array of modules where `module_classname()` used.
*
* @var array
*/
public static $uses_module_classname = array();
/**
* Unique field definitions across all modules.
*
* @var array
*/
protected static $_fields_unprocessed = array();
/**
* Default props of each modules.
*
* @var array
*/
protected static $_default_props = array();
/**
* Slugs of modules for which an option template has been rebuilt.
*
* @var array
*/
protected static $_has_rebuilt_option_template = array();
/**
* Modules cache.
*
* @var bool|array
*/
private static $_cache = false;
/**
* Keys map of unique BB templates.
*
* @var array
*/
private static $_unique_bb_keys_map = array();
/**
* List of unique BB templates.
*
* @var array
*/
private static $_unique_bb_keys_values = array();
/**
* List of tabs/newlines characters.
*
* @var array
*/
private static $_unique_bb_strip = array( "\t", "\r", "\n" );
/**
* Scroll effects fields of all modules.
*
* @var array
*/
public static $_scroll_effects_fields = array(
'desktop' => array(),
'tablet' => array(),
'phone' => array(),
);
/**
* Sticky element configuration
*
* @since 4.6.0
*
* @var array
*/
public static $sticky_elements = array();
/**
* Number of times {@see self::render()} has been executed.
*
* @var int
*/
private $_render_count;
/**
* Number of times {@see self::render()} has been executed for the shop module.
*
* @var int
*/
private static $_shop_render_count = 0;
/**
* Slug of a module whose render count should also be bumped when this module's is bumped.
*
* @var string
*/
protected $_bumps_render_count;
/**
* Priority number applied to some CSS rules.
*
* @var int
*/
protected $_style_priority;
/**
* Nnly needed for BB + hover.
*
* @var bool
*/
protected $is_background = false;
/**
* Whether module is official divi module.
*
* @var bool
*/
private $_is_official_module;
/**
* Whether module is woocommerce module.
*
* @var bool
*/
private $_is_woocommerce_module;
/**
* Uses for ligatures disabling at elements with letter-spacing CSS property.
*
* @var array
*/
private $letter_spacing_fix_selectors = array();
/**
* Holds module styles for the current request.
*
* @var array
*/
private static $styles = array();
/**
* Holds internal module styles for the current module.
* e.x In the Blog post module, {@see $internal_modules_styles} will hold style of all posts.
*
* @var array
*/
private static $internal_modules_styles = array();
/**
* Whether to save styles to the {@see $internal_modules_styles}.
*
* @var bool
*/
private static $prepare_internal_styles = false;
/**
* Internal modules counter.
*
* @var int
*/
private static $internal_modules_counter = 10000;
/**
* Media queries key value pairs. {@see get_media_quries()}
*
* @var array
*/
private static $media_queries = array();
/**
* List of all modules instance that extends this class.
*
* @var array
*/
private static $modules = array();
/**
* List of all parent modules instance that extends this class.
* e.x Accordion, BarCounters.
*
* @var array
*/
private static $parent_modules = array();
/**
* List of all child modules instance that extends this class.
* e.x AccordionItem, BarCountersItems.
*
* @var array
*/
private static $child_modules = array();
/**
* List of all woocommerce modules instance that extends this class.
*
* @var array
*/
private static $woocommerce_modules = array();
/**
* Hold current module index while loading backbone templates in batch.
*
* @var int
*/
private static $current_module_index = 0;
/**
* List of all structure modules objects.
* e.x Section, Row, Row Inner, Columns.
*
* @var array
*/
private static $structure_modules = array();
/**
* List of all structure modules slugs.
*
* @var array
*/
private static $structure_module_slugs = array();
/**
* List of all modules slugs by post type.
*
* @var array
*/
private static $_module_slugs_by_post_type = array();
/**
* Module Icons displayed in Add Module modals.
*
* @var array
*/
private static $module_icons = array();
/**
* List of all modules help videos.
*
* @var array
*/
private static $module_help_videos = array();
/**
* Parent module's motion/scroll effects options settings.
*
* @var array
*/
private static $parent_motion_effects = array();
/**
* A stack of the current active theme builder layout post type.
*
* @var string[]
*/
protected static $theme_builder_layout = array();
/**
* Compile list of modules that has rich editor option.
*
* @var array
*/
protected static $has_content_modules = array();
/**
* Whether loading backbone templates.
*
* @var bool
*/
private static $loading_backbone_templates = false;
/**
* Instance of `ET_Core_Data_Utils`.
*
* @var ET_Core_Data_Utils
*/
protected static $_ = null;
/**
* `ET_Core_PageResource` class instance.
*
* @var ET_Core_PageResource
*/
public static $advanced_styles_manager = null;
/**
* `ET_Core_PageResource` class instance.
*
* @var ET_Core_PageResource
*/
public static $deferred_styles_manager = null;
/**
* Whether to force inline styles.
*
* @var bool
*/
public static $forced_inline_styles = false;
/**
* `ET_Core_Data_Utils` instance.
*
* @var ET_Core_Data_Utils
*/
public static $data_utils = null;
/**
* `ET_Builder_Module_Helper_OptionTemplate` instance.
*
* @var ET_Builder_Module_Helper_OptionTemplate
*/
public static $option_template = null;
/**
* Composite field dependencies settings.
* e.g `show_if`, `show_if_not`
*
* @var array
*/
public static $field_dependencies = array();
/**
* Whether element indexes can be reset or not.
*
* @var bool
*/
public static $can_reset_element_indexes = true;
const DEFAULT_PRIORITY = 10;
const HIDE_ON_MOBILE = 'et-hide-mobile';
/**
* Credits of all custom modules.
*
* @var array
*/
protected $module_credits;
/**
* Store various indices for modules etc.
*
* @since 4.0
*
* @var array $indices {
* Module Indices By Post Type
*
* @type array $post_type {
* Module Indices
*
* @type int|int[] $index_type Current index value(s) {
* Index Values By Module Slug
*
* @type int $slug Index value
* }
* }
* }
*/
protected static $_indices = array();
const INDEX_SECTION = 'section';
const INDEX_ROW = 'row';
const INDEX_ROW_INNER = 'row_inner';
const INDEX_COLUMN = 'column';
const INDEX_COLUMN_INNER = 'column_inner';
const INDEX_MODULE = 'module';
const INDEX_MODULE_ITEM = 'module_item';
const INDEX_MODULE_ORDER = 'module_order';
const INDEX_INNER_MODULE_ORDER = 'inner_module_order';
/**
* Instance of `ET_Builder_Global_Presets_Settings`.
*
* @var ET_Builder_Global_Presets_Settings
*/
protected static $global_presets_manager = null;
/**
* Flag whether the module is rendering.
*
* @var boolean
*/
protected $is_rendering = false;
/**
* Flag if current module is sticky or not
*
* @var boolean
*/
protected $is_sticky_module = false;
/**
* List of props keys that need to inherit the value
*
* Intended to be used in ET_Builder_Module_Helper_MultiViewOptions helper
*
* @since 4.0.2
*
* @var array
*/
public $mv_inherited_props = array();
/**
* Background related values generated by process_advanced_background_options()
* Disabled by default; activated by setting $save_processed_background property to true
* Only gradient related value is saved right now; more can be added later if needed
*
* @since 4.3.3
*
* @var array
*/
protected $processed_background = array();
/**
* Set true to save processed background so it can be modified & reapplied on another element.
*
* @var bool
*/
protected $save_processed_background = false;
/**
* Holds active position origin/location for all devices.
*
* @var array
*/
protected $position_locations = array();
/**
* Module settings that are exposed so layout block previewer can correctly render it
* despites tweaks performed to make block preview correctly aligned
*
* @var array
*/
protected static $layout_block_assistive_settings = array();
/**
* Holds feature manager object.
*
* @var object
*/
protected $_features_manager;
/**
* All module slugs.
*
* @since 4.10.0
*
* @var array
*/
public static $all_module_slugs = array();
/**
* Whether current module uses unique ID or not.
*
* The unique ID will be added once the module is added via Divi Builder.
*
* @since ??
*
* @var boolean
*/
protected $_use_unique_id = false;
/**
* ET_Builder_Element constructor.
*/
public function __construct() {
self::$current_module_index++;
if ( ! self::$_deprecations ) {
self::$_deprecations = require_once ET_BUILDER_DIR . 'deprecations.php';
self::$_deprecations = self::$_deprecations['classes']['\ET_Builder_Module_Blurb'];
}
if ( ! self::$settings_migrations_initialized ) {
self::$settings_migrations_initialized = true;
require_once 'module/settings/Migration.php';
ET_Builder_Module_Settings_Migration::init();
add_filter( 'the_content', array( get_class( $this ), 'reset_element_indexes' ), 9999 );
add_filter( 'et_builder_render_layout', array( get_class( $this ), 'reset_element_indexes' ), 9999 );
}
if ( self::$loading_backbone_templates || et_admin_backbone_templates_being_loaded() ) {
if ( ! self::$loading_backbone_templates ) {
self::$loading_backbone_templates = true;
}
// phpcs:disable WordPress.Security.NonceVerification -- The `$_POST` values has not been saved in db, and is therefore not susceptible to CSRF.
$start_from = isset( $_POST['et_templates_start_from'] ) ? (int) sanitize_text_field( $_POST['et_templates_start_from'] ) : 0;
$post_type = isset( $_POST['et_post_type'] ) ? sanitize_text_field( $_POST['et_post_type'] ) : '';
// phpcs:enable
if ( 'layout' === $post_type ) {
// need - 2 to include the et_pb_section and et_pb_row modules.
$start_from = self::get_modules_count( 'page' ) - 2;
}
$current_module_index = self::$current_module_index - 1;
if ( ! ( $current_module_index >= $start_from && $current_module_index < ( ET_BUILDER_AJAX_TEMPLATES_AMOUNT + $start_from ) ) ) {
return;
}
}
if ( null === self::$advanced_styles_manager && ! is_admin() && ! et_fb_is_enabled() ) {
$result = self::setup_advanced_styles_manager();
self::$advanced_styles_manager = $result['manager'];
if ( isset( $result['deferred'] ) ) {
self::$deferred_styles_manager = $result['deferred'];
}
if ( $result['add_hooks'] ) {
// Schedule callback to run in the footer so we can pass the module design styles to the page resource.
add_action( 'wp_footer', array( 'ET_Builder_Element', 'set_advanced_styles' ), 19 );
// Add filter for the resource data so we can prevent theme customizer css from being
// included with the builder css inline on first-load (since its in the head already).
add_filter( 'et_core_page_resource_get_data', array( 'ET_Builder_Element', 'filter_page_resource_data' ), 10, 3 );
}
add_action( 'wp_footer', array( 'ET_Builder_Element', 'maybe_force_inline_styles' ), 19 );
}
if ( null === self::$data_utils ) {
self::$data_utils = ET_Core_Data_Utils::instance();
self::$_ = self::$data_utils;
// Disable wptexturize for code modules because it break things.
// Doing that here so it only happen once.
add_filter( 'no_texturize_shortcodes', array( $this, 'disable_wptexturize' ) );
}
if ( null === self::$global_presets_manager ) {
self::$global_presets_manager = ET_Builder_Global_Presets_Settings::instance();
}
if ( null === self::$option_template ) {
self::$option_template = et_pb_option_template();
}
$this->init();
$this->settings_modal_tabs = $this->get_settings_modal_tabs();
$this->settings_modal_toggles = $this->get_settings_modal_toggles();
$this->custom_css_fields = $this->get_custom_css_fields_config();
$this->_set_advanced_fields_config();
$current_class = get_class( $this );
$this->_is_official_module = self::_is_official_module( $current_class );
$this->_is_woocommerce_module = self::_is_woocommerce_module( $current_class );
$this->make_options_filterable();
if ( et_fb_is_builder_ajax() ) {
// Ensure `et_fb_is_enabled` returns true while setting fields to avoid
// 3rd party modules using the function to generate different
// definitions when they are updated via the AJAX call.
add_filter( 'et_fb_is_enabled', '__return_true' );
$this->set_fields();
remove_filter( 'et_fb_is_enabled', '__return_true' );
} else {
$this->set_fields();
}
$this->set_factory_objects();
$this->_additional_fields_options = array();
$slug = $this->slug;
// Use module cache compression only when we sure we can also decompress.
$use_compression = function_exists( 'gzinflate' ) && function_exists( 'gzdeflate' );
// Disable compression when debugging.
if ( function_exists( 'et_builder_definition_sort' ) ) {
$use_compression = false;
}
// Need to instantiate this early so that it can add its hooks.
$this->_features_manager = ET_Builder_Module_Features::instance();
if ( ! empty( self::$_cache[ $slug ] ) ) {
// We got sum cache, let's use it.
$cache = self::$_cache[ $slug ];
$fields_unprocessed = array();
$fields_map = $cache['fields_unprocessed'];
// Since arrays in PHP 5.x require more memory, we have to rely (again)
// on COW (copy on write) to limit RAM usage....
if ( is_array( $fields_map ) ) {
// Old cache storage format (array).
foreach ( $fields_map as $field => $key ) {
$fields_unprocessed[ $field ] = self::$_fields_unprocessed[ $key ];
}
} else {
// New cache storage format (string) key1,field1\n ... keyN,fieldN
// Decompress data when possible.
if ( $use_compression ) {
$fields_map = gzinflate( $fields_map );
}
$fields_map = explode( "\n", $fields_map );
foreach ( $fields_map as $data ) {
list ( $key, $field ) = explode( ':', $data );
$fields_unprocessed[ $field ] = self::$_fields_unprocessed[ $key ];
}
}
$this->has_advanced_fields = ! empty( $cache['advanced_fields'] );
$this->advanced_fields = $cache['advanced_fields'];
$this->fields_unprocessed = $fields_unprocessed;
$this->settings_modal_toggles = $cache['settings_modal_toggles'];
$this->custom_css_fields = $cache['custom_css_fields'];
// Claim some RAM not needed anymore.
unset( self::$_cache[ $slug ] );
} else {
// Compute expensive stuff.
$this->_add_additional_fields();
$this->_add_custom_css_fields();
$this->_maybe_add_global_defaults();
$this->_finalize_all_fields();
// Consider caching only official modules.
if ( $this->_is_official_module && false !== self::$_cache ) {
// We got no cache, let's create it.
$fields_unprocessed = '';
// Since arrays in PHP 5.x require more memory, we can't store
// fields_unprocessed as is but have to replace values with hashes
// when saving the cache and reverse the process when loading it.
foreach ( $this->fields_unprocessed as $field => $definition ) {
$key = md5( serialize( $definition ) );
$fields_unprocessed .= "$key:$field\n";
}
// Trim last newline.
$fields_unprocessed = trim( $fields_unprocessed );
// Compress data when possible.
if ( $use_compression ) {
$fields_unprocessed = gzdeflate( $fields_unprocessed );
}
self::$_cache[ $slug ] = array(
'advanced_fields' => $this->advanced_fields,
'fields_unprocessed' => $fields_unprocessed,
'settings_modal_toggles' => $this->settings_modal_toggles,
'custom_css_fields' => $this->custom_css_fields,
);
}
}
if ( ! isset( $this->main_css_element ) ) {
$this->main_css_element = '%%order_class%%';
}
$this->_render_count = 0;
$this->type = isset( $this->type ) ? $this->type : '';
$this->_style_priority = (int) self::DEFAULT_PRIORITY;
if ( isset( $this->type ) && 'child' === $this->type ) {
$this->_style_priority = $this->_style_priority + 1;
} else {
// add default toggles.
$default_general_toggles = array(
'admin_label' => array(
'title' => et_builder_i18n( 'Admin Label' ),
'priority' => 99,
),
);
$this->_add_settings_modal_toggles( 'general', $default_general_toggles );
}
$this->_add_settings_modal_toggles(
'custom_css',
array(
'visibility' => array(
'title' => et_builder_i18n( 'Visibility' ),
'priority' => 99,
),
)
);
$i18n =& self::$i18n;
if ( ! isset( $i18n['conditions'] ) ) {
$i18n['conditions'] = array(
'label' => esc_html__( 'Conditions', 'et_builder' ),
);
}
$this->_add_settings_modal_toggles(
'custom_css',
array(
'conditions' => array(
'title' => $i18n['conditions']['label'],
'priority' => 98,
),
)
);
if ( ! isset( $i18n['toggles'] ) ) {
// phpcs:disable WordPress.WP.I18n.MissingTranslatorsComment
$i18n['toggles'] = array(
'scroll' => esc_html__( 'Scroll Effects', 'et_builder' ),
);
// phpcs:enable
}
$this->_add_settings_modal_toggles(
'custom_css',
array(
'scroll_effects' => array(
'title' => $i18n['toggles']['scroll'],
'priority' => 200,
),
)
);
$this->main_tabs = $this->get_main_tabs();
$this->custom_css_tab = isset( $this->custom_css_tab ) ? $this->custom_css_tab : true;
self::$modules[ $this->slug ] = $this;
$post_types = ! empty( $this->post_types ) ? $this->post_types : et_builder_get_builder_post_types();
// all modules should be assigned for et_pb_layout post type to work in the library.
if ( ! in_array( 'et_pb_layout', $post_types, true ) ) {
$post_types[] = 'et_pb_layout';
}
$this->post_types = apply_filters( 'et_builder_module_post_types', $post_types, $this->slug, $this->post_types );
foreach ( $this->post_types as $post_type ) {
if ( ! in_array( $post_type, $this->post_types, true ) ) {
$this->register_post_type( $post_type );
}
if ( ! isset( self::$_module_slugs_by_post_type[ $post_type ] ) ) {
self::$_module_slugs_by_post_type[ $post_type ] = array();
}
if ( ! in_array( $this->slug, self::$_module_slugs_by_post_type[ $post_type ], true ) ) {
self::$_module_slugs_by_post_type[ $post_type ][] = $this->slug;
}
if ( isset( $this->additional_shortcode ) && ! in_array( $this->additional_shortcode, self::$_module_slugs_by_post_type[ $post_type ], true ) ) {
self::$_module_slugs_by_post_type[ $post_type ][] = $this->additional_shortcode;
}
if ( isset( $this->additional_shortcode_slugs ) ) {
foreach ( $this->additional_shortcode_slugs as $additional_shortcode_slug ) {
if ( ! in_array( $additional_shortcode_slug, self::$_module_slugs_by_post_type[ $post_type ], true ) ) {
self::$_module_slugs_by_post_type[ $post_type ][] = $additional_shortcode_slug;
}
}
}
if ( 'child' === $this->type ) {
self::$child_modules[ $post_type ][ $this->slug ] = $this;
if ( isset( $this->additional_shortcode_slugs ) ) {
foreach ( $this->additional_shortcode_slugs as $additional_slug ) {
self::$child_modules[ $post_type ][ $additional_slug ] = $this;
}
}
} else {
self::$parent_modules[ $post_type ][ $this->slug ] = $this;
}
}
// WooCommerce modules data only deterimine whether a module is WooCommerce modules or not
// it doesn't need to be aware whether it is available in certain CPT or not.
if ( $this->_is_woocommerce_module ) {
self::$woocommerce_modules[] = $this->slug;
}
if ( ! isset( $this->no_render ) ) {
$shortcode_slugs = array( $this->slug );
if ( ! empty( $this->additional_shortcode_slugs ) ) {
$shortcode_slugs = array_merge( $shortcode_slugs, $this->additional_shortcode_slugs );
}
foreach ( $shortcode_slugs as $shortcode_slug ) {
add_shortcode( $shortcode_slug, array( $this, '_render' ) );
}
if ( isset( $this->additional_shortcode ) ) {
add_shortcode( $this->additional_shortcode, array( $this, 'additional_render' ) );
}
}
if ( isset( $this->icon ) ) {
self::$_->array_set( self::$module_icons, "{$this->slug}.icon", $this->icon );
}
if ( isset( $this->icon_path ) ) {
self::$_->array_set( self::$module_icons, "{$this->slug}.icon_path", $this->icon_path );
}
// Push module's help videos to all help videos array if there's any.
if ( ! empty( $this->help_videos ) ) {
// Automatically add design tab and library tutorial. DRY.
if ( 'et_pb_column' !== $this->slug ) {
// Adding next tabs (design & tab) helps.
$next_tabs_help = array(
'id' => '1iqjhnHVA9Y',
'name' => esc_html__( 'Design Settings and Advanced Module Settings', 'et_builder' ),
);
// Adjust row name.
if ( in_array( $this->slug, array( 'et_pb_row', 'et_pb_row_inner' ), true ) ) {
$next_tabs_help['name'] = esc_html__( 'Design Settings and Advanced Row Settings', 'et_builder' );
}
// Adjust section name.
if ( 'et_pb_section' === $this->slug ) {
$next_tabs_help['name'] = esc_html__( 'Design Settings and Advanced Section Settings', 'et_builder' );
}
$this->help_videos[] = $next_tabs_help;
// Adding Divi Library helps.
$this->help_videos[] = array(
'id' => 'boNZZ0MYU0E',
'name' => esc_html__( 'Saving and loading from the library', 'et_builder' ),
);
}
self::$module_help_videos[ $this->slug ] = $this->help_videos;
}
// Push module slug if this module has content option. These modules' content option need
// to be autop-ed during saving process to avoid unstyled body content in Divi Builder Plugin due
// to content not having
tag because it doesn't wrapped by newline during saving process.
if ( ! $this->use_raw_content && ! $this->child_slug && 'tiny_mce' === self::$_->array_get( $this->get_fields(), 'content.type' ) ) {
self::$has_content_modules[] = $this->slug;
}
}
/**
* Return all module slugs.
*
* @return array
*
* @since 4.10.0
*/
public static function get_all_module_slugs() {
// cache this only after et_builder_ready has run.
if ( ! did_action( 'et_builder_ready' ) || empty( self::$all_module_slugs ) ) {
self::$all_module_slugs = array_merge( self::get_structure_module_slugs(), self::get_module_slugs_by_post_type() );
}
return self::$all_module_slugs;
}
/**
* Make private/protected methods readable.
*
* @param string $name Method to call.
* @param array $args Arguments to pass when calling.
* @return mixed|bool Return value of the callback, false otherwise.
*/
public function __call( $name, $args ) {
$class = get_class( $this );
$message = "You're Doing It Wrong!";
$is_deprecated = array_key_exists( $name, self::$_deprecations['methods'] );
$value = null;
$old_method_exists = method_exists( $this, $name );
if ( $old_method_exists && ! $is_deprecated ) {
// Inaccessible method (protected or private) that isn't deprecated.
et_debug( "{$message} Attempted to call {$class}::{$name}() from out of scope.", 4, false );
return $value;
}
$message .= " {$class}::{$name}()";
if ( ! $is_deprecated ) {
$message .= " doesn't exist.";
} else {
$message .= ' is deprecated.';
$new_method = self::$_deprecations['methods'][ $name ];
if ( ! is_string( $new_method ) ) {
// Default value for a method that has no replacement.
$value = $new_method;
} elseif ( method_exists( $this, $new_method ) && ! $old_method_exists ) {
$message .= " Use {$class}::{$new_method}() instead.";
// @phpcs:ignore Generic.PHP.ForbiddenFunctions.Found
$value = call_user_func_array( array( $this, $new_method ), $args );
} elseif ( $old_method_exists && function_exists( $new_method ) ) {
// New method is a function.
$message .= " Use {$new_method}() instead.";
// @phpcs:ignore Generic.PHP.ForbiddenFunctions.Found
$value = call_user_func_array( $new_method, $args );
} elseif ( $old_method_exists ) {
// Ensure that our current caller is not the same as the method we're about to call.
// as that would cause an infinite recursion situation. It happens when a child class
// method which has been deprecated calls itself on the parent class (using parent::)
// This also happens when we use $this->__call() to call a deprecated method from its
// replacement method so that a deprecation notice will be output.
$trace = debug_backtrace();
$callers = array(
self::$_->array_get( $trace, '1.function' ),
self::$_->array_get( $trace, '2.function' ),
);
if ( ! in_array( $name, $callers, true ) ) {
$message .= " Use {$class}::{$new_method}() instead.";
// @phpcs:ignore Generic.PHP.ForbiddenFunctions.Found
$value = call_user_func_array( array( $this, $name ), $args );
}
}
}
et_debug( $message, 4, false );
return $value;
}
/**
* Makes private properties readable.
*
* @param string $name Property name.
*
* @return mixed|string|null
*/
public function &__get( $name ) {
$class = get_class( $this );
$message = "You're Doing It Wrong!";
$is_deprecated = array_key_exists( $name, self::$_deprecations['properties'] );
$value = null;
if ( property_exists( $this, $name ) && ! $is_deprecated ) {
// Inaccessible property (protected or private) that isn't deprecated.
et_debug( "{$message} Attempted to access {$class}::\${$name} from out of scope.", 4, false );
return $value;
}
$message .= " {$class}::\${$name}";
if ( ! $is_deprecated ) {
$message .= " doesn't exist.";
$should_set_value = true;
} else {
$message .= ' is deprecated.';
$new_prop = self::$_deprecations['properties'][ $name ];
if ( $new_prop && is_string( $new_prop ) && property_exists( $this, $new_prop ) ) {
$message .= " Use {$class}::\${$new_prop} instead.";
$value = &$this->$new_prop;
} elseif ( ! is_string( $new_prop ) || ! $new_prop ) {
// Default value.
$value = $new_prop;
$should_set_value = true;
}
}
if ( isset( $should_set_value ) ) {
// Create the property so we can return a reference to it which allows it to be
// used like this: $this->name[] = 'something'.
$this->$name = $value;
$value = &$this->$name;
}
et_debug( $message, 4, false );
return $value;
}
/**
* Make private properties checkable.
*
* @param string $name Property to check if set.
* @return bool Whether the property is set.
*/
public function __isset( $name ) {
$prop_name = array_key_exists( $name, self::$_deprecations['properties'] ) ? self::$_deprecations['properties'][ $name ] : $name;
if ( ! $prop_name || ! is_string( $prop_name ) ) {
return false;
}
return property_exists( $this, $prop_name );
}
/**
* Set a property's value.
*
* @param string $name Property key.
* @param mixed $value Property value.
*/
public function __set( $name, $value ) {
$class = get_class( $this );
$message = "You're Doing It Wrong!";
$is_deprecated = array_key_exists( $name, self::$_deprecations['properties'] );
$property_exists = property_exists( $this, $name );
$has_replacement = $property_exists && is_string( self::$_deprecations['properties'][ $name ] ) && self::$_deprecations['properties'][ $name ];
if ( $property_exists && ! $is_deprecated ) {
// Inaccessible property (protected or private) that isn't deprecated.
et_debug( "{$message} Attempted to access {$class}::\${$name} from out of scope.", 4, false );
return;
}
if ( ( ! $property_exists && ! $is_deprecated ) || ! $has_replacement ) {
// Always allow setting values for properties that are undeclared.
$this->$name = $value;
}
if ( ! $is_deprecated ) {
return;
}
$message = " {$class}::\${$name} is deprecated.";
$replacement = self::$_deprecations['properties'][ $name ];
if ( $replacement && is_string( $replacement ) ) {
$message .= " Use {$class}::\${$replacement} instead.";
$this->$replacement = $value;
// Unset deprecated property so next time it's updated we process it again.
unset( $this->$name );
}
et_debug( $message, 4, false );
}
/**
* Determine whether class is Divi official module or not.
*
* @param string $class_name Module class name.
*
* @return bool
*/
private static function _is_official_module( $class_name ) {
try {
$reflection = new ReflectionClass( $class_name );
$is_official = self::$_->includes( $reflection->getFileName(), ET_BUILDER_DIR_RESOLVED_PATH );
} catch ( Exception $err ) {
$is_official = false;
}
return $is_official;
}
/**
* Determine whether class is WooCommerce module or not.
*
* @param string $class_name Module class name.
*
* @return bool
*/
private static function _is_woocommerce_module( $class_name ) {
try {
$reflection = new ReflectionClass( $class_name );
$is_woocommerce = self::$_->includes( $reflection->getFileName(), ET_BUILDER_DIR_RESOLVED_PATH . '/module/woocommerce' );
} catch ( Exception $err ) {
$is_woocommerce = false;
}
return $is_woocommerce;
}
/**
* Set configuration for module's advanced fields.
*/
protected function _set_advanced_fields_config() {
$this->advanced_fields = $this->get_advanced_fields_config();
// 3rd-Party Backwards Compatability
if ( isset( $this->advanced_fields['custom_margin_padding'] ) ) {
$this->advanced_fields['margin_padding'] = $this->advanced_fields['custom_margin_padding'];
unset( $this->advanced_fields['custom_margin_padding'] );
}
}
/**
* Get whether third party post interference should be respected.
* Current use case is for plugins like Toolset that render a
* loop within a layout which renders another layout for
* each post - in this case we must NOT override the
* current post so the loop works as expected.
*
* @since 4.0.6
*
* @return boolean
*/
protected static function _should_respect_post_interference() {
$post = ET_Post_Stack::get();
return null !== $post && get_the_ID() !== $post->ID;
}
/**
* Retrieve the main query post id.
* Accounts for third party interference with the current post.
*
* @since 4.0.6
*
* @return integer|boolean
*/
protected static function _get_main_post_id() {
if ( self::_should_respect_post_interference() ) {
return get_the_ID();
}
return ET_Post_Stack::get_main_post_id();
}
/**
* Retrieve Post ID from 1 of 4 sources depending on which exists:
* - $_POST['current_page']['id']
* - $_POST['et_post_id']
* - $_GET['post']
* - get_the_ID()
*
* @since 3.17.2
*
* @return int|bool
*/
public static function get_current_post_id() {
// Getting correct post id in computed_callback request.
// phpcs:disable WordPress.Security.NonceVerification -- This function does not change any state, and is therefore not susceptible to CSRF.
if ( wp_doing_ajax() && self::$_->array_get( $_POST, 'current_page.id' ) ) {
return absint( self::$_->array_get( $_POST, 'current_page.id' ) );
}
if ( wp_doing_ajax() && isset( $_POST['et_post_id'] ) ) {
return absint( $_POST['et_post_id'] );
}
if ( isset( $_POST['post'] ) ) {
return absint( $_POST['post'] );
}
return self::_get_main_post_id();
// phpcs:enable
}
/**
* Retrieve Post ID from 1 of 3 sources depending on which exists:
* - get_the_ID()
* - $_GET['post']
* - $_POST['et_post_id']
*
* @since 4.0
*
* @return int|bool
*/
public static function get_current_post_id_reverse() {
// phpcs:disable WordPress.Security.NonceVerification -- This function does not change any state, and is therefore not susceptible to CSRF.
$post_id = self::_get_main_post_id();
// try to get post id from get_post_ID().
if ( false !== $post_id ) {
return $post_id;
}
if ( wp_doing_ajax() ) {
// get the post ID if loading data for VB.
return isset( $_POST['et_post_id'] ) ? absint( $_POST['et_post_id'] ) : false;
}
// fallback to $_GET['post'] to cover the BB data loading.
return isset( $_GET['post'] ) ? absint( $_GET['post'] ) : false;
// phpcs:enable
}
/**
* Get the current ID depending on the current request.
*
* @since 4.0
*
* @return int|bool
*/
public function get_the_ID() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName -- This function name is consistent with WP core `get_the_ID()` function
return self::get_current_post_id_reverse();
}
/**
* Setup the advanced styles manager
*
* @param int $post_id Post id.
*
* @return array
* @since 4.0 Made public.
*
* {@internal
* Before the styles manager was implemented, the advanced styles were output inline in the footer.
* That resulted in them being the last styles parsed by the browser, thus giving them higher
* priority than other styles on the page. With the styles manager, the advanced styles are
* enqueued at the very end of the
. This is for backwards compatibility (to maintain
* the same priority for the styles as before).}}
*/
public static function setup_advanced_styles_manager( $post_id = 0 ) {
if ( 0 === $post_id && et_core_page_resource_is_singular() ) {
// It doesn't matter if post id is 0 because we're going to force inline styles.
$post_id = et_core_page_resource_get_the_ID();
}
/** This filter is documented in frontend-builder/theme-builder/frontend.php */
$is_critical_enabled = apply_filters( 'et_builder_critical_css_enabled', false );
$deferred = false;
$is_preview = is_preview() || is_et_pb_preview();
$forced_in_footer = $post_id && et_builder_setting_is_on( 'et_pb_css_in_footer', $post_id );
$forced_inline = ! $post_id || $is_preview || $forced_in_footer || et_builder_setting_is_off( 'et_pb_static_css_file', $post_id ) || et_core_is_safe_mode_active() || ET_GB_Block_Layout::is_layout_block_preview();
$unified_styles = ! $forced_inline && ! $forced_in_footer;
$resource_owner = $unified_styles ? 'core' : 'builder';
$resource_slug = $unified_styles ? 'unified' : 'module-design';
$resource_slug .= $unified_styles && et_builder_post_is_of_custom_post_type( $post_id ) ? '-cpt' : '';
$resource_slug = et_theme_builder_decorate_page_resource_slug( $post_id, $resource_slug );
// If the post is password protected and a password has not been provided yet,
// no content (including any custom style) will be printed.
// When static css file option is enabled this will result in missing styles.
if ( ! $forced_inline && post_password_required( $post_id ? $post_id : null ) ) {
$forced_inline = true;
}
if ( $is_preview ) {
// Don't let previews cause existing saved static css files to be modified.
$resource_slug .= '-preview';
}
$manager = et_core_page_resource_get( $resource_owner, $resource_slug, $post_id, 40 );
$has_file = $manager->has_file();
$manager_data = [
'manager' => $manager,
'add_hooks' => true,
];
if ( $is_critical_enabled ) {
$deferred = et_core_page_resource_get( $resource_owner, $resource_slug . '-deferred', $post_id, 40 );
$has_file = $has_file && $deferred->has_file();
$manager_data['deferred'] = $deferred;
}
if ( ! $forced_inline && ! $forced_in_footer && $has_file ) {
// This post currently has a fully configured styles manager.
$manager_data['add_hooks'] = false;
/**
* Filters the Style Managers used to output Critical/Deferred Builder CSS.
*
* @since 4.10.0
*
* @param array $manager_data Style Managers.
*/
$manager_data = apply_filters( 'et_builder_module_style_manager', $manager_data );
return $manager_data;
}
$manager->forced_inline = $forced_inline;
$manager->write_file_location = 'footer';
if ( $deferred ) {
$deferred->forced_inline = $forced_inline;
$deferred->write_file_location = 'footer';
}
if ( $forced_in_footer || $forced_inline ) {
// Restore legacy behavior--output inline styles in the footer.
$manager->set_output_location( 'footer' );
if ( $deferred ) {
$deferred->set_output_location( 'footer' );
}
}
/** This filter is documented in class-et-builder-element.php */
$manager_data = apply_filters( 'et_builder_module_style_manager', $manager_data );
return $manager_data;
}
/**
* Passes the module design styles for the current page to the advanced styles manager.
* {@see 'wp_footer' (19) Must run before the style manager's footer callback}
*/
public static function set_advanced_styles() {
$styles = '';
$custom = '';
if ( et_core_is_builder_used_on_current_request() ) {
$custom = et_pb_get_page_custom_css();
}
// Pass styles to page resource which will handle their output.
/** This filter is documented in frontend-builder/theme-builder/frontend.php */
$is_critical_enabled = apply_filters( 'et_builder_critical_css_enabled', false );
$critical = $is_critical_enabled ? self::get_style( false, 0, true ) . self::get_style( true, 0, true ) : [];
$styles = self::get_style() . self::get_style( true );
if ( empty( $critical ) ) {
// No critical styles defined, just enqueue everything as usual.
$styles = $custom . $styles;
if ( ! empty( $styles ) ) {
if ( isset( self::$deferred_styles_manager ) ) {
self::$deferred_styles_manager->set_data( $styles, 40 );
} else {
self::$advanced_styles_manager->set_data( $styles, 40 );
}
}
} else {
// Add page css to the critical section.
$critical = $custom . $critical;
self::$advanced_styles_manager->set_data( $critical, 40 );
if ( ! empty( $styles ) ) {
// Defer everything else.
self::$deferred_styles_manager->set_data( $styles, 40 );
}
}
}
/**
* Set {@see ET_Builder_Element::$advanced_styles_manager} to force inline styles.
*/
public static function maybe_force_inline_styles() {
if ( et_core_is_fb_enabled() || self::$advanced_styles_manager->forced_inline || ! self::$forced_inline_styles ) {
return;
}
self::$advanced_styles_manager->forced_inline = true;
self::$advanced_styles_manager->write_file_location = 'footer';
self::$advanced_styles_manager->set_output_location( 'footer' );
if ( isset( self::$deferred_styles_manager ) ) {
self::$deferred_styles_manager->forced_inline = true;
self::$deferred_styles_manager->write_file_location = 'footer';
self::$deferred_styles_manager->set_output_location( 'footer' );
}
}
/**
* Filters the unified page resource data. The data is an array of arrays of strings keyed by
* priority. The builder's styles are set with a priority of 40. Here we want to make sure
* only the builder's styles are output in the footer on first-page load so we aren't
* duplicating the customizer and custom css styles which are already in the .
* {@see 'et_core_page_resource_get_data'}
*
* @param array[] $data {
* Arrays of strings keyed by priority.
*
* @type string[] $priority Resource data.
* ...
* }.
* @param string $context Where the data will be used. Accepts 'inline', 'file'.
* @param ET_Core_PageResource $resource The resource instance.
* @return array
*/
public static function filter_page_resource_data( $data, $context, $resource ) {
global $wp_current_filter;
if ( 'inline' !== $context || ! in_array( 'wp_footer', $wp_current_filter, true ) ) {
return $data;
}
if ( false === strpos( $resource->slug, 'unified' ) ) {
return $data;
}
if ( 'footer' !== $resource->location ) {
// This is the first load of a page that doesn't currently have a unified static css file.
// The theme customizer and custom css have already been inlined in the using the
// unified resource's ID. It's invalid HTML to have duplicated IDs on the page so we'll
// fix that here since it only applies to this page load anyway.
$resource->slug = $resource->slug . '-2';
}
return isset( $data[40] ) ? array( 40 => $data[40] ) : array();
}
/**
* Get the slugs for all current builder modules.
*
* @since 3.0.85
*
* @param string $post_type Get module slugs for this post type. If falsy, all slugs are returned.
*
* @return array
*/
public static function get_module_slugs_by_post_type( $post_type = 'post' ) {
if ( $post_type ) {
if ( isset( self::$_module_slugs_by_post_type[ $post_type ] ) ) {
$slugs = self::$_module_slugs_by_post_type[ $post_type ];
} else {
// We get all modules when post type is not enabled so that posts that have
// had their post type support disabled still load all necessary modules.
$slugs = array_keys( self::get_modules() );
}
/**
* Filters the module slugs and post type.
*
* @since 4.10.0
*
* @param array $slugs Module Slugs.
* @param string $post_type Current post type.
*/
return apply_filters( 'et_builder_get_module_slugs_by_post_type', $slugs, $post_type );
}
return self::$_module_slugs_by_post_type;
}
/**
* Get whether the module has Visual Builder support or not
*
* @since 3.1
*
* @return bool
*/
public function has_vb_support() {
return 'off' !== $this->vb_support;
}
/**
* Create Factory objects
*
* @since 3.23 Add margin padding fields object.
*
* @return void
*/
public function set_factory_objects() {
// Load features fields.
$this->text_shadow = ET_Builder_Module_Fields_Factory::get( 'TextShadow' );
$this->margin_padding = ET_Builder_Module_Fields_Factory::get( 'MarginPadding' );
}
/**
* Populates {@see $fields_unprocessed}.
*
* @param array $fields Fields.
*/
protected function _set_fields_unprocessed( $fields ) {
$unprocessed = &self::$_fields_unprocessed;
foreach ( $fields as $field => $definition ) {
if ( true === $definition ) {
continue;
}
// Have to use md5 now because needed by modules cache.
$key = md5( serialize( $definition ) );
if ( ! isset( $unprocessed[ $key ] ) ) {
$unprocessed[ $key ] = $definition;
}
$this->fields_unprocessed[ $field ] = $unprocessed[ $key ];
}
}
/**
* Populates {@see self::$fields_unprocessed}.
*/
public function set_fields() {
$fields_unprocessed = $this->get_complete_fields();
// Add _builder_version field to all modules.
$fields_unprocessed['_builder_version'] = array( 'type' => 'skip' );
// Add _dynamic_attributes field to all modules.
$fields_unprocessed['_dynamic_attributes'] = array( 'type' => 'skip' );
// Add support for the style presets.
$fields_unprocessed['_module_preset'] = array( 'type' => 'skip' );
// Add support for unique ID. Initially added for Contact Form module.
if ( $this->_use_unique_id ) {
$fields_unprocessed['_unique_id'] = array( 'type' => 'skip' );
}
if ( function_exists( 'et_builder_definition_sort' ) ) {
et_builder_definition_sort( $fields_unprocessed );
}
if ( $this->_is_official_module ) {
$this->_set_fields_unprocessed( $fields_unprocessed );
return;
}
// 3rd-Party module backwards compatability starts here
foreach ( $fields_unprocessed as $field => $info ) {
if ( isset( $info['depends_to'] ) ) {
$fields_unprocessed[ $field ]['depends_on'] = $info['depends_to'];
}
if ( isset( $info['depends_default'] ) && $info['depends_default'] && ! isset( $info['depends_show_if'] ) ) {
$fields_unprocessed[ $field ]['depends_show_if'] = 'on';
$message = "You're Doing It Wrong! Setting definition for {$field} includes deprecated parameter: 'depends_default'. Use 'show_if' instead.";
et_debug( $message );
}
// Process renderer of string type only.
if ( isset( $info['renderer'] ) && is_string( $info['renderer'] ) ) {
$original_renderer = $info['renderer'];
$updated_field_type = $info['renderer'];
// convert renderer into type.
switch ( $info['renderer'] ) {
case 'et_builder_include_categories_option':
case 'et_builder_include_categories_shop_option':
$updated_field_type = 'categories';
break;
case 'et_builder_get_widget_areas':
$updated_field_type = 'select_sidebar';
break;
case 'et_pb_get_font_icon_list':
case 'et_pb_get_font_down_icon_list':
$updated_field_type = 'select_icon';
break;
case 'et_builder_get_gallery_settings':
$updated_field_type = 'upload_gallery';
break;
case 'et_builder_generate_center_map_setting':
$updated_field_type = 'center_map';
break;
}
$fields_unprocessed[ $field ]['type'] = $updated_field_type;
if ( 'et_pb_get_font_down_icon_list' === $info['renderer'] ) {
$fields_unprocessed[ $field ]['renderer_options'] = array( 'icons_list' => 'icon_down' );
}
// Output developer warning if renderer was converted to type.
if ( $original_renderer !== $updated_field_type ) {
$message = "You're Doing It Wrong! Module setting definition for {$field} has a deprecated value: ";
$message .= "'{$original_renderer}' for parameter 'renderer'. Use '{$updated_field_type}' instead.";
et_debug( $message );
}
}
// Normalize `affects` field names if needed.
if ( isset( $info['affects'] ) ) {
$affects_original = $fields_unprocessed[ $field ]['affects'];
$fields_unprocessed[ $field ]['affects'] = array();
// BB supports comma separated list of affected fields, convert it to array of fields if this is the case.
// Some plugins use combination of various lists, handle all of them.
foreach ( $affects_original as $affect_item ) {
if ( strpos( $affect_item, ',' ) !== false ) {
$fields_unprocessed[ $field ]['affects'] = array_merge( $fields_unprocessed[ $field ]['affects'], explode( ',', str_replace( ' ', '', $affect_item ) ) );
} else {
$fields_unprocessed[ $field ]['affects'][] = $affect_item;
}
}
array_walk( $fields_unprocessed[ $field ]['affects'], array( $this, 'normalize_affect_fields' ) );
}
if ( 'content_new' === $field ) {
$fields_unprocessed['content'] = $fields_unprocessed['content_new'];
unset( $fields_unprocessed['content_new'] );
$message = "You're Doing It Wrong! Setting definition for {$field} includes deprecated parameter: 'content_new'. Use 'content' instead.";
et_debug( $message );
}
// convert old color pickers to the new ones supporting alpha channel.
if ( 'color' === self::$_->array_get( $info, 'type' ) ) {
$info['type'] = 'color-alpha';
$fields_unprocessed[ $field ] = $info;
$message = "You're Doing It Wrong! You're using wrong type for the '" . $field . "'. It should be 'color-alpha' instead of 'color'.";
et_debug( $message, 4, false );
}
// convert input type to text.
if ( 'input' === self::$_->array_get( $info, 'type' ) ) {
$info['type'] = 'text';
$fields_unprocessed[ $field ] = $info;
$message = "You're Doing It Wrong! Setting definition for {$field} has a deprecated value: 'input' for parameter: 'type'. Use 'text' instead.";
et_debug( $message );
}
// Normalize default values.
if ( isset( $info['default'] ) ) {
$fields_unprocessed[ $field ]['default'] = $this->_normalize_field_default( $field, $info['default'], $fields_unprocessed[ $field ]['type'] );
}
}
// Set default values in field definitions based on the legacy defaults "rules".
if ( isset( $this->fields_defaults ) ) {
foreach ( $this->fields_defaults as $field => $value ) {
if ( ! isset( $fields_unprocessed[ $field ] ) ) {
continue;
}
$condition = is_array( $value ) ? self::$_->array_get( $value, '1' ) : false;
$set_default_on_front = 'only_default_setting' !== $condition;
$default = $this->_normalize_field_default( $field, $value, $fields_unprocessed[ $field ]['type'] );
// Always set default value if exists. Only default_on_front should be conditional.
$fields_unprocessed[ $field ]['default'] = $default;
if ( ! $set_default_on_front ) {
continue;
}
$has_default = isset( $fields_unprocessed[ $field ]['default'] );
if ( ! $has_default || $fields_unprocessed[ $field ]['default'] !== $default ) {
$fields_unprocessed[ $field ]['default_on_front'] = $default;
}
}
}
// Legacy Defaults Rule #4 (AKA: longest-running undetected bug in the codebase):
// Fields listed in allowlisted_fields that aren't in fields_defaults lose their definitions.
if ( isset( $this->allowlisted_fields ) ) {
$disable_allowlisted_fields = isset( $this->force_unallowlisted_fields ) && $this->force_unallowlisted_fields;
if ( ! $disable_allowlisted_fields && ! is_admin() && ! et_fb_is_enabled() ) {
foreach ( $this->allowlisted_fields as $field ) {
if ( isset( $this->fields_defaults ) && array_key_exists( $field, $this->fields_defaults ) ) {
continue;
}
$fields_unprocessed[ $field ] = array();
}
}
}
$this->_set_fields_unprocessed( $fields_unprocessed );
}
/**
* Normalize default value depends on field type.
*
* @param string $field Field.
* @param mixed $default_value Default value.
* @param string $type Field type.
*
* @return mixed|string
*/
protected function _normalize_field_default( $field, $default_value, $type = '' ) {
$normalized_value = is_array( $default_value ) ? $default_value[0] : $default_value;
// normalize default value depends on field type.
switch ( $type ) {
case 'yes_no_button':
if ( is_numeric( $normalized_value ) ) {
$normalized_value = (bool) $normalized_value ? 'on' : 'off';
$message = "You're Doing It Wrong! You're using wrong value for '{$field}' default value. It should be either 'on' or 'off'.";
et_debug( $message, 4, false );
}
break;
case 'color-alpha':
if ( is_numeric( $normalized_value ) ) {
$normalized_value = '';
$message = "You're Doing It Wrong! You're using wrong value for '{$field}' default value. It should be string value.";
et_debug( $message, 4, false );
}
// Make sure provided HEX code is a valid color code.
if ( strpos( $normalized_value, '#' ) === 0 && ! in_array( strlen( $normalized_value ), array( 4, 7 ), true ) ) {
$normalized_value = '';
$message = "You're Doing It Wrong! You're using wrong value for '{$field}' default value. It should be valid hex color code.";
et_debug( $message, 4, false );
}
break;
}
return $normalized_value;
}
/**
* Normalize `affects` fields name if needed.
* Some 3rd party modules use `#et_pb_` format which is wrong and doesn't work in VB, but works in BB.
* Convert it to correct format and output notice for developer
*
* @param string $field_name Field name.
*
* @return void
*/
public function normalize_affect_fields( &$field_name ) {
if ( strpos( $field_name, '#et_pb_' ) !== false ) {
// Truncate field name from the string wherever it's placed.
$new_field_name = substr( $field_name, strpos( $field_name, '#et_pb_' ) + 7 );
$message = "You're Doing It Wrong! You're using wrong name for 'affects' attribute. It should be '" . $new_field_name . "' instead of '" . $field_name . "'";
$field_name = $new_field_name;
et_debug( $message, 4, false );
}
// content_new renamed to content, so rename it in affected fields list as well.
if ( 'content_new' === $field_name ) {
$field_name = 'content';
}
}
/**
* Finalizes the configuration of {@see self::$fields_unprocessed}.
* Includes filter and fields processing for Visual Builder
*
* @return void
*/
protected function _finalize_all_fields() {
$fields_unprocessed = $this->fields_unprocessed;
$fields_before_filter = $fields_unprocessed;
/**
* Filters module fields.
*
* @since 3.1
*
* @param array $fields_unprocessed See {@see self::$fields_unprocessed}.
*/
$fields_unprocessed = apply_filters( "et_pb_all_fields_unprocessed_{$this->slug}", $fields_unprocessed );
$is_saving_modules_cache = et_core_is_saving_builder_modules_cache();
$need_dynamic_assets = et_core_is_fb_enabled() && ! et_fb_dynamic_asset_exists( 'definitions' );
// Check if this is an AJAX request since this is how VB and BB loads the initial module data et_core_is_fb_enabled() always returns `false` here
// Make exception for requests that are regenerating modules cache and
// VB page which has no dynamic definitions asset so it can cache the definitions correctly.
if ( ! wp_doing_ajax() && ! $is_saving_modules_cache && ! $need_dynamic_assets ) {
$this->_set_fields_unprocessed( $fields_unprocessed );
return;
}
foreach ( array_keys( $fields_unprocessed ) as $field_name ) {
$field_info = $fields_unprocessed[ $field_name ];
$affected_fields = self::$_->array_get( $field_info, 'affects', array() );
foreach ( $affected_fields as $affected_field ) {
if ( ! isset( $fields_unprocessed[ $affected_field ] ) ) {
continue;
}
if ( ! isset( $fields_unprocessed[ $affected_field ]['depends_on'] ) ) {
$fields_unprocessed[ $affected_field ]['depends_on'] = array();
}
// Avoid value duplication.
if ( ! in_array( $field_name, $fields_unprocessed[ $affected_field ]['depends_on'], true ) ) {
$fields_unprocessed[ $affected_field ]['depends_on'][] = $field_name;
}
// Set `depends_show_if = on` if no condition defined for the affected field for backward compatibility with old plugins.
if ( ! isset( $fields_unprocessed[ $affected_field ]['depends_show_if'] ) && ! isset( $fields_unprocessed[ $affected_field ]['depends_show_if_not'] ) ) {
// Deprecation notice has already been logged for this.
$fields_unprocessed[ $affected_field ]['depends_show_if'] = 'on';
}
}
// Unset renderer to avoid errors in VB because of errors in 3rd party plugins
// BB compat. Still need this data, so leave it for BB.
if ( ( self::is_loading_vb_data() || et_fb_is_enabled() ) && isset( $fields_unprocessed[ $field_name ]['renderer'] ) ) {
unset( $fields_unprocessed[ $field_name ]['renderer'] );
}
if ( isset( $fields_unprocessed[ $field_name ]['use_plugin_main'] ) ) {
$fields_unprocessed[ $field_name ]['use_limited_main'] = $fields_unprocessed[ $field_name ]['use_plugin_main'];
unset( $fields_unprocessed[ $field_name ]['use_plugin_main'] );
$message = "You're Doing It Wrong! Setting definition for {$field_name} includes deprecated parameter: 'use_plugin_main'. Use 'use_limited_main' instead.";
et_debug( $message );
}
if ( isset( $fields_unprocessed[ $field_name ]['plugin_main'] ) ) {
$fields_unprocessed[ $field_name ]['limited_main'] = $fields_unprocessed[ $field_name ]['plugin_main'];
unset( $fields_unprocessed[ $field_name ]['plugin_main'] );
$message = "You're Doing It Wrong! Setting definition for {$field_name} includes deprecated parameter: 'plugin_main'. Use 'limited_main' instead.";
et_debug( $message );
}
}
// determine custom fields added via filter and add specific flag to identify them in VB.
$keys_before_filter = array_keys( $fields_before_filter );
$keys_after_filter = array_keys( $fields_unprocessed );
$added_fields = array_diff( $keys_after_filter, $keys_before_filter );
if ( ! empty( $added_fields ) ) {
foreach ( $added_fields as $key ) {
$fields_unprocessed[ $key ]['vb_support'] = false;
}
}
$this->_set_fields_unprocessed( $fields_unprocessed );
}
/**
* Register builder enabled post types.
*
* @param string $post_type Post type.
*/
private function register_post_type( $post_type ) {
$this->post_types[] = $post_type;
self::$parent_modules[ $post_type ] = array();
self::$child_modules[ $post_type ] = array();
}
/**
* Double quote are saved as "%22" in shortcode attributes.
* Decode them back into "
*
* @param string[] $enabled_dynamic_attributes Attributes which have dynamic content enabled.
* @param bool $et_fb_processing_shortcode_object FB processing shortcode flag.
*
* @return void
*/
private function _decode_double_quotes( $enabled_dynamic_attributes, $et_fb_processing_shortcode_object ) {
if ( ! isset( $this->props ) ) {
return;
}
// need to encode HTML entities in Admin Area( for BB ) if Visual Editor disabled for the user.
$need_html_entities_decode = is_admin() && ! user_can_richedit();
$shortcode_attributes = array();
$font_icon_options = et_pb_get_font_icon_field_names();
$font_icon_options_as_keys = array_flip( $font_icon_options );
$url_options = array( 'url', 'button_link', 'button_url' );
$url_options_as_keys = array_flip( $url_options );
foreach ( $this->props as $attribute_key => $attribute_value ) {
if ( $et_fb_processing_shortcode_object && ! empty( $enabled_dynamic_attributes ) && in_array( $attribute_key, $enabled_dynamic_attributes, true ) ) {
// Do not decode dynamic content values when preparing them for VB.
$shortcode_attributes[ $attribute_key ] = $attribute_value;
continue;
}
// decode HTML entities and remove trailing and leading quote if needed.
$processed_attr_value = $need_html_entities_decode ? trim( htmlspecialchars_decode( $attribute_value, ENT_QUOTES ), '"' ) : $attribute_value;
$field_type = empty( $this->fields_unprocessed[ $attribute_key ]['type'] ) ? '' : $this->fields_unprocessed[ $attribute_key ]['type'];
// the icon shortcodes are fine.
if ( isset( $font_icon_options_as_keys[ $attribute_key ] ) || 'select_icon' === $field_type ) {
$shortcode_attributes[ $attribute_key ] = $processed_attr_value;
// icon attributes must not be str_replaced.
// Add responsive and hover types attributes on the font icon options list. Just
// assign empty string value because we just need the key.
$font_icon_options_as_keys[ "{$attribute_key}_tablet" ] = '';
$font_icon_options_as_keys[ "{$attribute_key}_phone" ] = '';
$font_icon_options_as_keys[ "{$attribute_key}__hover" ] = '';
continue;
}
// Set empty TinyMCE content '<br /> ' as empty string.
if ( 'tiny_mce' === $field_type && 'ltbrgtbr' === preg_replace( '/[^a-z]/', '', $processed_attr_value ) ) {
$processed_attr_value = '';
}
// URLs are weird since they can allow non-ascii characters so we escape those separately.
if ( isset( $url_options_as_keys[ $attribute_key ] ) ) {
$url = $processed_attr_value;
$url_parsed = wp_parse_url( $url );
if ( isset( $url_parsed['query'] ) ) {
$replace = str_replace(
array( '%91', '%93' ),
array( '[', ']' ),
$url_parsed['query']
);
$url = str_replace( $url_parsed['query'], $replace, $url );
}
$shortcode_attributes[ $attribute_key ] = esc_url_raw( $url );
} else {
$processed_attr_value = str_replace( array( '%22', '%92', '%91', '%93', '%5c' ), array( '"', '\\', '[', ']', '\\' ), $processed_attr_value );
$shortcode_attributes[ $attribute_key ] = $processed_attr_value;
}
}
$this->props = $shortcode_attributes;
}
/**
* Provide a way for sub-class to access $this->_render_count without a chance to alter its value
*
* @return int
*/
protected function render_count() {
return $this->_render_count;
}
/**
* Bumps the render count for this module instance and the module instance whose slug is
* set as {@see self::$_bumps_render_count} (if any).
*
* @since 3.10
*/
protected function _bump_render_count() {
$this->_render_count++;
if ( $this->_bumps_render_count ) {
$module = self::get_module( $this->_bumps_render_count, $this->get_post_type() );
$module->_render_count++;
}
}
/**
* Check whether ab testing enabled for current module and calculate whether it should be displayed currently or not
*
* @param array $shortcode_atts Shortcode attributes.
*
* @return bool
*/
private function _is_display_module( $shortcode_atts ) {
$ab_subject_id = isset( $shortcode_atts['ab_subject_id'] ) && '' !== $shortcode_atts['ab_subject_id'] ? $shortcode_atts['ab_subject_id'] : false;
// return true if testing is disabled or current module has no subject id.
if ( ! $ab_subject_id ) {
return true;
}
return $this->_check_ab_test_subject( $ab_subject_id );
}
/**
* Check whether the current module should be displayed or not.
*
* @param bool|integer $ab_subject_id subject id.
*
* @return bool
*/
private function _check_ab_test_subject( $ab_subject_id = false ) {
global $et_pb_ab_subject;
if ( ! $ab_subject_id ) {
return true;
}
return et_()->array_get( $et_pb_ab_subject, self::get_layout_id(), '' ) === $ab_subject_id;
}
/**
* Get an index.
*
* @since 4.0
*
* @param string $key The path in the array.
*
* @return mixed
*/
protected static function _get_index( $key ) {
$theme_builder_group = self::get_theme_builder_layout_type();
$key = array_merge( array( $theme_builder_group ), (array) $key );
return et_()->array_get( self::$_indices, $key, -1 );
}
/**
* Set an index.
*
* @since 4.0
*
* @param string $key The path in the array.
* @param mixed $index The value to set.
*
* @return void
*/
protected static function _set_index( $key, $index ) {
$theme_builder_group = self::get_theme_builder_layout_type();
$key = array_merge( array( $theme_builder_group ), (array) $key );
et_()->array_set( self::$_indices, $key, $index );
}
/**
* Resets indexes used when generating element addresses.
*
* @param string $content Element content.
* @param bool $force Whether forcefully reset indexes even when not the main query or
* {@see self::$can_reset_element_indexes} is false.
*
* @return string
*/
public static function reset_element_indexes( $content = '', $force = false ) {
if ( ! $force && ( ! self::$can_reset_element_indexes || ! is_main_query() ) ) {
return $content;
}
$slugs = self::get_parent_slugs_regex();
if ( $content && ! preg_match( "/{$slugs}/", $content ) ) {
// At least one builder element should be present.
return $content;
}
global $wp_current_filter;
if ( in_array( 'the_content', $wp_current_filter, true ) ) {
$call_counts = array_count_values( $wp_current_filter );
if ( $call_counts['the_content'] > 1 ) {
// This is a nested call. We only want to reset indexes after the top-most call.
return $content;
}
}
self::_set_index( self::INDEX_SECTION, -1 );
self::_set_index( self::INDEX_ROW, -1 );
self::_set_index( self::INDEX_ROW_INNER, -1 );
self::_set_index( self::INDEX_COLUMN, -1 );
self::_set_index( self::INDEX_COLUMN_INNER, -1 );
self::_set_index( self::INDEX_MODULE, -1 );
self::_set_index( self::INDEX_MODULE_ITEM, -1 );
if ( $force ) {
// Reset module order classes.
self::_set_index( self::INDEX_MODULE_ORDER, array() );
self::_set_index( self::INDEX_INNER_MODULE_ORDER, array() );
}
return $content;
}
/**
* Generates the element's address. Every builder element on the page is assigned an address
* based on it's index and those of it's parents using the following format:
*
* `$section.$row.$column.$module[.$module_item]`
*
* For example, if a module is the forth module in the first column in the third row in the
* second section on the page, it's address would be: `1.2.0.3` (indexes are zero-based).
*
* @since 3.1 Renamed from `_get_current_shortcode_address()` to `generate_element_address()`
* @since 3.0.60
*
* @param string $render_slug render slug.
*
* @return string
*/
public function generate_element_address( $render_slug = '' ) {
// Flag child module. $this->type isn't accurate in this context since some modules reuse other
// modules' render() method for rendering their output (ie. accordion item).
// Even though Column and Column Inner are child elements of Row they shouldn't be processed as child items.
$is_child_module = in_array( $render_slug, self::get_child_slugs( $this->get_post_type() ), true ) && false === strpos( $render_slug, '_column_inner' ) && false === strpos( $render_slug, '_column' );
if ( false !== strpos( $render_slug, '_section' ) ) {
self::_set_index( self::INDEX_SECTION, self::_get_index( self::INDEX_SECTION ) + 1 );
// Reset every module index inside section.
self::_set_index( self::INDEX_ROW, -1 );
self::_set_index( self::INDEX_ROW_INNER, -1 );
self::_set_index( self::INDEX_COLUMN, -1 );
self::_set_index( self::INDEX_COLUMN_INNER, -1 );
self::_set_index( self::INDEX_MODULE, -1 );
self::_set_index( self::INDEX_MODULE_ITEM, -1 );
} elseif ( false !== strpos( $render_slug, '_row_inner' ) ) {
self::_set_index( self::INDEX_ROW_INNER, self::_get_index( self::INDEX_ROW_INNER ) + 1 );
// Reset every module index inside row inner.
self::_set_index( self::INDEX_COLUMN_INNER, -1 );
self::_set_index( self::INDEX_MODULE, -1 );
self::_set_index( self::INDEX_MODULE_ITEM, -1 );
} elseif ( false !== strpos( $render_slug, '_row' ) ) {
self::_set_index( self::INDEX_ROW, self::_get_index( self::INDEX_ROW ) + 1 );
// Reset every module index inside row.
self::_set_index( self::INDEX_COLUMN, -1 );
self::_set_index( self::INDEX_MODULE, -1 );
self::_set_index( self::INDEX_MODULE_ITEM, -1 );
} elseif ( false !== strpos( $render_slug, '_column_inner' ) ) {
self::_set_index( self::INDEX_COLUMN_INNER, self::_get_index( self::INDEX_COLUMN_INNER ) + 1 );
// Reset every module index inside column inner.
self::_set_index( self::INDEX_MODULE, -1 );
self::_set_index( self::INDEX_MODULE_ITEM, -1 );
} elseif ( false !== strpos( $render_slug, '_column' ) && -1 === self::_get_index( self::INDEX_ROW ) ) {
self::_set_index( self::INDEX_COLUMN, self::_get_index( self::INDEX_COLUMN ) + 1 );
// Reset every module index inside column of specialty section.
self::_set_index( self::INDEX_ROW_INNER, -1 );
self::_set_index( self::INDEX_COLUMN_INNER, -1 );
self::_set_index( self::INDEX_MODULE, -1 );
self::_set_index( self::INDEX_MODULE_ITEM, -1 );
} elseif ( false !== strpos( $render_slug, '_column' ) ) {
self::_set_index( self::INDEX_COLUMN, self::_get_index( self::INDEX_COLUMN ) + 1 );
// Reset every module index inside column of regular section.
self::_set_index( self::INDEX_MODULE, -1 );
self::_set_index( self::INDEX_MODULE_ITEM, -1 );
} elseif ( $is_child_module ) {
self::_set_index( self::INDEX_MODULE_ITEM, self::_get_index( self::INDEX_MODULE_ITEM ) + 1 );
} else {
self::_set_index( self::INDEX_MODULE, self::_get_index( self::INDEX_MODULE ) + 1 );
// Reset module item index inside module.
self::_set_index( self::INDEX_MODULE_ITEM, -1 );
}
$address = self::_get_index( self::INDEX_SECTION );
if ( -1 === self::_get_index( self::INDEX_ROW ) && -1 === self::_get_index( self::INDEX_ROW_INNER ) ) {
// Fullwidth & Specialty (without column inner) Section's module.
$parts = array( self::_get_index( self::INDEX_COLUMN ), self::_get_index( self::INDEX_MODULE ) );
} elseif ( 0 <= self::_get_index( self::INDEX_ROW_INNER ) ) {
// Specialty (inside column inner) Section's module.
$parts = array( self::_get_index( self::INDEX_COLUMN ), self::_get_index( self::INDEX_ROW_INNER ), self::_get_index( self::INDEX_COLUMN_INNER ), self::_get_index( self::INDEX_MODULE ) );
} else {
// Regular section's module.
$parts = array( self::_get_index( self::INDEX_ROW ), self::_get_index( self::INDEX_COLUMN ), self::_get_index( self::INDEX_MODULE ) );
}
foreach ( $parts as $part ) {
if ( $part > -1 ) {
$address .= ".{$part}";
}
}
if ( $is_child_module ) {
$address .= '.' . self::_get_index( self::INDEX_MODULE_ITEM );
}
return $address;
}
/**
* Resolves conditional defaults
*
* @param array $values Fields.
* @param string $render_slug Module slug.
*
* @return array
*/
public function resolve_conditional_defaults( $values, $render_slug = '' ) {
// Resolve conditional defaults for the FE.
$resolved = $this->get_default_props();
if ( $render_slug && $render_slug !== $this->slug ) {
$module = self::get_module( $render_slug, $this->get_post_type() );
if ( $module ) {
$resolved = array_merge( $resolved, $module->get_default_props() );
}
}
foreach ( $resolved as $field_name => $field_default ) {
if ( is_array( $field_default ) && 2 === count( $field_default ) && ! empty( $field_default[0] ) ) {
if ( is_array( $field_default[1] ) ) {
// Looks like we have a conditional default
// Get $depend_field value or use the first default if undefined.
list ( $depend_field, $conditional_defaults ) = $field_default;
reset( $conditional_defaults );
$default_key = isset( $values[ $depend_field ] ) ? $values[ $depend_field ] : key( $conditional_defaults );
// Set the resolved default.
$resolved[ $field_name ] = isset( $conditional_defaults[ $default_key ] ) ? $conditional_defaults[ $default_key ] : null;
} elseif ( 'filter' === $field_default[0] ) {
$resolved[ $field_name ] = apply_filters( $field_default[1], $field_name );
}
}
}
// Add hover attributes.
if ( ! is_array( $values ) ) {
return $resolved;
}
foreach ( $values as $attr => $value ) {
// Inject module props with suffixes __hover|__hover_enabled|__sticky|__sticky_enabled|_last_edited|_tablet|_phone.
if ( ! preg_match( ET_Builder_Module_Helper_MultiViewOptions::get_regex_suffix(), $attr ) ) {
continue;
}
$resolved[ $attr ] = $value;
}
$skip_base_names = array(
'fb_built',
'_builder_version',
'hover_enabled',
'sticky_enabled',
);
$base_names = array();
foreach ( array_keys( $values ) as $attr ) {
$base_name = 0 === strpos( $attr, 'content' ) ? 'content' : ET_Builder_Module_Helper_MultiViewOptions::get_name_base( $attr );
if ( in_array( $base_name, $skip_base_names, true ) ) {
continue;
}
$base_names[ $base_name ] = $base_name;
}
// Set the props list that the value need to be inherited.
// to get the responsive content able to display content for tablet/phone/hover only mode.
foreach ( $base_names as $base_name ) {
foreach ( array( 'hover', 'tablet', 'phone' ) as $mode ) {
$name_by_mode = ET_Builder_Module_Helper_MultiViewOptions::get_name_by_mode( $base_name, $mode );
if ( ! isset( $values[ $name_by_mode ] ) && ! isset( $resolved[ $name_by_mode ] ) ) {
// Set value inheritance flag for hover mode.
$this->mv_inherited_props[ $name_by_mode ] = $name_by_mode;
} elseif ( ! isset( $values[ $name_by_mode ] ) && isset( $resolved[ $name_by_mode ] ) && '' === $resolved[ $name_by_mode ] ) {
// Set value inheritance flag for tablet & phone mode.
$this->mv_inherited_props[ $name_by_mode ] = $name_by_mode;
}
}
}
return $resolved;
}
/**
* Get wrapper settings. Combining module-defined wrapper settings with default wrapper settings
*
* @since 3.1
*
* @param string $render_slug module slug.
*
* @return array
*/
protected function get_wrapper_settings( $render_slug = '' ) {
global $et_fb_processing_shortcode_object;
// The following defaults are used on both frontend & builder.
$defaults = array(
'parallax_background' => '',
'video_background' => '',
'attrs' => array(),
'inner_attrs' => array(
'class' => 'et_pb_module_inner',
),
);
// The following defaults are only used on frontend. VB handles these on ETBuilderInjectedComponent based on live props
// Note: get_parallax_image_background() and video_background() have to be called before module_classname().
if ( ! $et_fb_processing_shortcode_object ) {
$use_background_image = self::$_->array_get( $this->advanced_fields, 'background.use_background_image', false );
$use_background_video = self::$_->array_get( $this->advanced_fields, 'background.use_background_video', false );
$use_module_id = self::$_->array_get( $this->props, 'module_id', '' );
// Module might disable image background.
if ( $use_background_image ) {
$defaults['parallax_background'] = $this->get_parallax_image_background();
}
// Module might disable video background.
if ( $use_background_video ) {
$defaults['video_background'] = $this->video_background();
}
// Module might intentionally has custom id fields (ie. Module items).
if ( $use_module_id ) {
$defaults['attrs']['id'] = $this->module_id( false );
}
$defaults['attrs']['class'] = $this->module_classname( $render_slug );
}
if ( ! $defaults['attrs'] ) {
// Make sure we get an empty object when this is output as JSON later.
$defaults['attrs'] = new stdClass();
}
// Fill empty argument attributes by default values.
return wp_parse_args( $this->wrapper_settings, $defaults );
}
/**
* Wrap module's rendered output with proper module wrapper. Ensuring module has consistent
* wrapper output which compatible with module attribute and background insertion.
*
* @since 3.1
*
* @param string $output Module's rendered output.
* @param string $render_slug Slug of module that is used for rendering output.
*
* @return string
*/
protected function _render_module_wrapper( $output = '', $render_slug = '' ) {
$wrapper_settings = $this->get_wrapper_settings( $render_slug );
$slug = $render_slug;
$outer_wrapper_attrs = $wrapper_settings['attrs'];
$inner_wrapper_attrs = $wrapper_settings['inner_attrs'];
/**
* Filters the HTML attributes for the module's outer wrapper. The dynamic portion of the
* filter name, '$slug', corresponds to the module's slug.
*
* @since 3.23 Add support for responsive video background.
* @since 3.1
*
* @param string[] $outer_wrapper_attrs
* @param ET_Builder_Element $module_instance
*/
$outer_wrapper_attrs = apply_filters( "et_builder_module_{$slug}_outer_wrapper_attrs", $outer_wrapper_attrs, $this );
/**
* Filters the HTML attributes for the module's inner wrapper. The dynamic portion of the
* filter name, '$slug', corresponds to the module's slug.
*
* @since 3.1
*
* @param string[] $inner_wrapper_attrs
* @param ET_Builder_Element $module_instance
*/
$inner_wrapper_attrs = apply_filters( "et_builder_module_{$slug}_inner_wrapper_attrs", $inner_wrapper_attrs, $this );
return sprintf(
'
%2$s
%3$s
%6$s
%7$s
%5$s
',
et_html_attrs( $outer_wrapper_attrs ),
$wrapper_settings['parallax_background'],
$wrapper_settings['video_background'],
et_html_attrs( $inner_wrapper_attrs ),
$output,
et_()->array_get( $wrapper_settings, 'video_background_tablet', '' ),
et_()->array_get( $wrapper_settings, 'video_background_phone', '' )
);
}
/**
* Resolves the values for dynamic attributes.
*
* @since 3.17.2
*
* @param array $original_attrs List of attributes.
*
* @return array Processed attributes with resolved dynamic values.
*/
public function process_dynamic_attrs( $original_attrs ) {
global $et_fb_processing_shortcode_object;
$attrs = $original_attrs;
$enabled_dynamic_attributes = $this->_get_enabled_dynamic_attributes( $attrs );
if ( is_array( $attrs ) ) {
foreach ( $attrs as $key => $value ) {
$attrs[ $key ] = $this->_resolve_value(
$this->get_the_ID(),
$key,
$value,
$enabled_dynamic_attributes,
$et_fb_processing_shortcode_object
);
}
}
return $attrs;
}
/**
* Prepares for and then calls the module's {@see self::render()} method.
*
* @param array $attrs List of attributes.
* @param string $content Content being processed.
* @param string $render_slug Slug of module that is used for rendering output.
* @param string $parent_address Parent address.
* @param string $global_parent Global parent.
* @param string $global_parent_type Global parent type.
* @param string $parent_type Parent type.
*
* @since 3.23 Add support for generating responsive animation.
* @since 3.1 Renamed from `_shortcode_callback()` to `_render()`.
* @since 1.0
*
* @return string The module's HTML output.
*/
public function _render( $attrs, $content, $render_slug, $parent_address = '', $global_parent = '', $global_parent_type = '', $parent_type = '', $theme_builder_area = '' ) {
global $et_fb_processing_shortcode_object, $et_pb_current_parent_type, $et_pb_parent_section_type, $is_parent_sticky_module, $is_inside_sticky_module;
if ( $this->is_rendering ) {
// Every module instance is a singleton so the TB Post Content module
// can cause a section, row and/or column to call _render() multiple
// times - once for each respective shortcode found in the content
// rendered by the Post Content module.
// Since this _render() method changes object state this leads to
// props being messed up between renders so we have to clone the
// base instance every time we try to render while the base
// instance is still rendering.
$clone = clone $this;
$clone->is_rendering = false;
// @phpcs:ignore Generic.PHP.ForbiddenFunctions.Found
return call_user_func_array( array( $clone, '_render' ), func_get_args() );
}
self::set_order_class( $render_slug );
$this->_maybe_rebuild_option_template();
$attrs = $this->_maybe_add_global_presets_settings( $attrs, $render_slug );
// Use the current layout or post ID for AB testing. This is not guaranteed to be the real
// current post ID if we are rendering a TB layout.
$post_interference = self::_should_respect_post_interference();
$post_id = apply_filters( 'et_is_ab_testing_active_post_id', self::get_layout_id() );
$is_main_post = $this->get_the_ID() === $post_id;
if ( ! $post_interference ) {
ET_Post_Stack::replace( ET_Post_Stack::get_main_post() );
}
$enabled_dynamic_attributes = $this->_get_enabled_dynamic_attributes( $attrs );
$attrs = $this->_encode_legacy_dynamic_content( $attrs, $enabled_dynamic_attributes );
$this->attrs_unprocessed = $attrs;
$attrs = $this->process_dynamic_attrs( $attrs );
$this->props = shortcode_atts( $this->resolve_conditional_defaults( $attrs, $render_slug ), $attrs );
// Only process if it's not a "global module's parent",
// the render method in the global module's parent will just call the `do_shortcode()` for the global module content.
// So processing the sticky should be done in the global module content render instead.
$should_process_sticky = ! in_array( $render_slug, array( 'et_pb_section', 'et_pb_row', 'et_pb_row_inner' ), true ) || empty( $this->props['global_module'] );
if ( $should_process_sticky ) {
// Check if current module is inside sticky module based on previous $is_parent_sticky_module.
$is_inside_sticky_module = $is_parent_sticky_module;
// Flag if current module rendering is sticky module or not.
// This is frequently used on sticky style rendering for modifying selectors.
$this->is_sticky_module = et_pb_sticky_options()->is_sticky_module( $this->props );
// If current module is sticky module, update $is_parent_sticky_module global; Module inside
// current module will check $is_parent_sticky_module global before $this->is_sticky_module
// property check to see if current module is inside another sticky module or not.
if ( $this->is_sticky_module ) {
// IMPORTANT: DO NOT use $is_parent_sticky_module global to check whether current module
// is inside another sticky or not. Use is_inside_sticky_module() util instead.
$is_parent_sticky_module = $this->is_sticky_module;
}
} else {
$this->is_sticky_module = false;
}
$this->_decode_double_quotes( $enabled_dynamic_attributes, $et_fb_processing_shortcode_object );
$this->_maybe_remove_global_default_values_from_props();
// Some module items need to inherit value from its module parent
// This inheritance needs to be done before migration to make it compatible with migration process.
$this->maybe_inherit_values();
$_address = $this->generate_element_address( $render_slug );
/**
* Filters Module Props.
*
* @param array $props Array of processed props.
* @param array $attrs Array of original shortcode attrs
* @param string $slug Module slug
* @param string $_address Module Address
* @param string $content Module content
*/
$this->props = apply_filters( 'et_pb_module_shortcode_attributes', $this->props, $attrs, $render_slug, $_address, $content );
$global_content = false;
$ab_testing_enabled = et_is_ab_testing_active( $post_id );
$hide_subject_module = false;
$hide_subject_module_cached = $hide_subject_module;
$global_module_id = $this->props['global_module'];
// If the section/row/module is disabled, hide it.
if ( isset( $this->props['disabled'] ) && 'on' === $this->props['disabled'] && ! $et_fb_processing_shortcode_object ) {
if ( ! $post_interference ) {
ET_Post_Stack::restore();
}
return;
}
// need to perform additional check and some modifications in case AB testing enabled
// skip for VB since it's handled on VB side.
if ( $ab_testing_enabled && ( ! $is_main_post || ! $et_fb_processing_shortcode_object ) ) {
// check if ab testing enabled for this module and if it shouldn't be displayed currently.
$hide_subject_module = ( ! $is_main_post || ! $et_fb_processing_shortcode_object ) && ! $this->_is_display_module( $this->props ) && ! et_pb_detect_cache_plugins();
// add class to the AB testing subject if needed.
if ( isset( $this->props['ab_subject_id'] ) && '' !== $this->props['ab_subject_id'] ) {
$subject_class = sprintf(
' et_pb_ab_subject et_pb_ab_subject_id-%1$s_%2$s',
esc_attr( $post_id ),
esc_attr( $this->props['ab_subject_id'] )
);
$this->props['module_class'] = isset( $this->props['module_class'] ) && '' !== $this->props['module_class'] ? $this->props['module_class'] . $subject_class : $subject_class;
if ( et_pb_detect_cache_plugins() ) {
$hide_subject_module_cached = true;
}
}
// add class to the AB testing goal if needed.
if ( isset( $this->props['ab_goal'] ) && 'on' === $this->props['ab_goal'] ) {
$goal_class = sprintf( ' et_pb_ab_goal et_pb_ab_goal_id-%1$s', esc_attr( $post_id ) );
$this->props['module_class'] = isset( $this->props['module_class'] ) && '' !== $this->props['module_class'] ? $this->props['module_class'] . $goal_class : $goal_class;
}
}
// override module attributes for global module. Skip that step while processing Frontend Builder object.
if ( ! empty( $global_module_id ) && ! $et_fb_processing_shortcode_object ) {
// Update render_slug when rendering global rows inside Specialty sections.
$render_slug = 'et_pb_specialty_column' === $et_pb_current_parent_type && 'et_pb_row' === $render_slug ? 'et_pb_row_inner' : $render_slug;
$global_module_data = et_pb_load_global_module( $global_module_id, $render_slug );
if ( '' !== $global_module_data ) {
$unsynced_global_attributes = get_post_meta( $global_module_id, '_et_pb_excluded_global_options' );
$use_updated_global_sync_method = ! empty( $unsynced_global_attributes );
$unsynced_options = ! empty( $unsynced_global_attributes[0] ) ? json_decode( $unsynced_global_attributes[0], true ) : array();
$content_synced = $use_updated_global_sync_method && ! in_array( 'et_pb_content_field', $unsynced_options, true );
// support legacy selective sync system.
if ( ! $use_updated_global_sync_method ) {
$content_synced = ! isset( $this->props['saved_tabs'] ) || false !== strpos( $this->props['saved_tabs'], 'general' ) || 'all' === $this->props['saved_tabs'];
}
if ( $content_synced ) {
// Set the flag showing if we load inner row.
$load_inner_row = 'et_pb_row_inner' === $render_slug;
$global_content = et_pb_get_global_module_content( $global_module_data, $render_slug, $load_inner_row );
}
// cleanup the shortcode string to avoid the attributes messing with content.
$global_content_processed = false !== $global_content ? str_replace( $global_content, '', $global_module_data ) : $global_module_data;
$global_atts = shortcode_parse_atts( et_pb_remove_shortcode_content( $global_content_processed, $this->slug ) );
$global_atts = $this->_encode_legacy_dynamic_content( $global_atts, $enabled_dynamic_attributes );
// Additional content processing required for Code Modules.
if ( in_array( $render_slug, array( 'et_pb_code', 'et_pb_fullwidth_code' ), true ) ) {
$global_content_processed = _et_pb_code_module_prep_content( $global_content_processed );
}
// reset module addresses because global items will be processed once again and address will be incremented wrongly.
if ( false !== strpos( $render_slug, '_section' ) ) {
self::_set_index( self::INDEX_SECTION, self::_get_index( self::INDEX_SECTION ) - 1 );
self::_set_index( self::INDEX_ROW, -1 );
self::_set_index( self::INDEX_ROW_INNER, -1 );
self::_set_index( self::INDEX_COLUMN, -1 );
self::_set_index( self::INDEX_COLUMN_INNER, -1 );
self::_set_index( self::INDEX_MODULE, -1 );
self::_set_index( self::INDEX_MODULE_ITEM, -1 );
} elseif ( false !== strpos( $render_slug, '_row_inner' ) ) {
self::_set_index( self::INDEX_ROW, self::_get_index( self::INDEX_ROW ) - 1 );
self::_set_index( self::INDEX_COLUMN_INNER, -1 );
self::_set_index( self::INDEX_MODULE, -1 );
self::_set_index( self::INDEX_MODULE_ITEM, -1 );
} elseif ( false !== strpos( $render_slug, '_row' ) ) {
self::_set_index( self::INDEX_ROW, self::_get_index( self::INDEX_ROW ) - 1 );
self::_set_index( self::INDEX_COLUMN, -1 );
self::_set_index( self::INDEX_MODULE, -1 );
self::_set_index( self::INDEX_MODULE_ITEM, -1 );
} else {
self::_set_index( self::INDEX_MODULE, self::_get_index( self::INDEX_MODULE ) - 1 );
self::_set_index( self::INDEX_MODULE_ITEM, -1 );
}
// Always unsync 'next_background_color' and 'prev_background_color' options for global sections
// They should be dynamic and reflect color of top and bottom sections.
if ( 'et_pb_section' === $render_slug ) {
$unsynced_options = array_merge( $unsynced_options, array( 'next_background_color', 'prev_background_color' ) );
}
foreach ( $this->props as $single_attr => $value ) {
if ( isset( $global_atts[ $single_attr ] ) && ! in_array( $single_attr, $unsynced_options, true ) ) {
// replace %22 with double quotes in options to make sure it's rendered correctly.
$this->props[ $single_attr ] = is_string( $global_atts[ $single_attr ] ) && ! array_intersect( array( "et_pb_{$single_attr}", $single_attr ), $this->dbl_quote_exception_options ) ? str_replace( '%22', '"', $global_atts[ $single_attr ] ) : $global_atts[ $single_attr ];
}
}
$this->props = $this->process_dynamic_attrs( $this->props );
$this->_decode_double_quotes( array(), $et_fb_processing_shortcode_object );
}
}
$this->before_render();
$this->content_unprocessed = $this->_encode_legacy_dynamic_content_value(
'content',
false !== $global_content ? $global_content : $content,
$enabled_dynamic_attributes
);
$content = $this->_resolve_value(
$this->get_the_ID(),
'content',
$this->content_unprocessed,
$enabled_dynamic_attributes,
$et_fb_processing_shortcode_object
);
// Process sticky elements earlier to preserve the modules hierarchy during processing.
if ( $should_process_sticky ) {
$this->process_sticky( $render_slug );
}
// Process scroll effects earlier to preserve the modules hierarchy during processing.
$this->process_scroll_effects( $render_slug );
$content = apply_filters( 'et_pb_module_content', $content, $this->props, $attrs, $render_slug, $_address, $global_content );
// Set empty TinyMCE content '<br /> ' as empty string.
if ( 'ltbrgtbr' === preg_replace( '/[^a-z]/', '', $content ) ) {
$content = '';
}
if ( $et_fb_processing_shortcode_object ) {
$this->content = et_pb_fix_shortcodes( $content, $this->use_raw_content );
} else {
// Line breaks should be converted before do_shortcode to avoid legit rendered shortcode
// line breaks being trimmed into one line and causing issue like broken javascript code.
if ( $this->use_raw_content ) {
$content = et_builder_convert_line_breaks( et_builder_replace_code_content_entities( $content ) );
}
if ( ! ( isset( $this->is_structure_element ) && $this->is_structure_element ) ) {
$content = et_pb_fix_shortcodes( $content, $this->use_raw_content );
$content = et_maybe_enable_embed_shortcode( $content, true );
$this->content = do_shortcode( $content );
} else {
$this->content = '';
}
$this->props['content'] = $this->content;
}
// Restart classname on shortcode callback. Module class is only called once, not on every
// shortcode module appearance. Thus classname construction need to be restarted on each
// module callback.
$this->classname = array();
if ( method_exists( $this, 'shortcode_atts' ) ) {
// Deprecated. Do not use this!
$this->shortcode_atts();
}
if ( ! $et_fb_processing_shortcode_object ) {
$this->process_global_colors();
}
$this->process_additional_options( $render_slug );
$this->process_custom_css_fields( $render_slug );
// load inline fonts if needed.
if ( isset( $this->props['inline_fonts'] ) ) {
$this->process_inline_fonts_option( $this->props['inline_fonts'] );
}
// Automatically add slug as classname for module that uses other module's shortcode callback
// This has to be added first because some classname is position-sensitive and used for
// JS-based calculation (i.e. .et_pb_column in column inner).
if ( $this->slug !== $render_slug ) {
$this->add_classname( $this->slug );
// Apply classnames added to the module that uses other module's shortcode callback
// (i.e. `process_additional_options` for the column inner).
$module = self::get_module( $render_slug, $this->get_post_type() );
$this->add_classname( $module->classname );
}
// Automatically add default classnames.
$this->add_classname(
array(
'et_pb_module',
$render_slug,
self::get_module_order_class( $render_slug ),
)
);
// Automatically added user-defined classname if there's any.
if ( isset( $this->props['module_class'] ) && '' !== $this->props['module_class'] ) {
$this->add_classname( explode( ' ', $this->props['module_class'] ) );
}
// Animation Styles.
$animation_style = isset( $this->props['animation_style'] ) && '' !== $this->props['animation_style'] ? $this->props['animation_style'] : false;
$animation_repeat = isset( $this->props['animation_repeat'] ) && '' !== $this->props['animation_repeat'] ? $this->props['animation_repeat'] : 'once';
$animation_direction = isset( $this->props['animation_direction'] ) && '' !== $this->props['animation_direction'] ? $this->props['animation_direction'] : 'center';
$animation_duration = isset( $this->props['animation_duration'] ) && '' !== $this->props['animation_duration'] ? $this->props['animation_duration'] : '500ms';
$animation_delay = isset( $this->props['animation_delay'] ) && '' !== $this->props['animation_delay'] ? $this->props['animation_delay'] : '0ms';
$animation_intensity = isset( $this->props[ "animation_intensity_{$animation_style }" ] ) && '' !== $this->props[ "animation_intensity_{$animation_style }" ] ? $this->props[ "animation_intensity_{$animation_style }" ] : '50%';
$animation_starting_opacity = isset( $this->props['animation_starting_opacity'] ) && '' !== $this->props['animation_starting_opacity'] ? $this->props['animation_starting_opacity'] : '0%';
$animation_speed_curve = isset( $this->props['animation_speed_curve'] ) && '' !== $this->props['animation_speed_curve'] ? $this->props['animation_speed_curve'] : 'ease-in-out';
// Check if animation is enabled.
$animation_enabled = $this->_features_manager->get(
// Has animation enabled.
'anim',
function() use ( $animation_style, $animation_direction ) {
return ! empty( $animation_style ) || ! empty( $animation_direction );
}
);
if ( $animation_enabled ) {
// Animation style and direction values for Tablet & Phone. Basically, style for tablet and
// phone are same with the desktop because we only edit responsive settings for the affected
// fields under animation style. Variable $animation_style_responsive need to be kept as
// unmodified variable because it will be used by animation intensity.
$animation_style_responsive = $animation_style;
$animation_style_tablet = $animation_style;
$animation_style_phone = $animation_style;
$animation_direction_tablet = et_pb_responsive_options()->get_any_value( $this->props, 'animation_direction_tablet' );
$animation_direction_phone = et_pb_responsive_options()->get_any_value( $this->props, 'animation_direction_phone' );
// Check if this is an AJAX request since this is how VB loads the initial module data
// et_core_is_fb_enabled() always returns `false` here.
if ( $animation_style && 'none' !== $animation_style && ! wp_doing_ajax() ) {
$transformed_animations = array(
'desktop' => false,
'tablet' => false,
'phone' => false,
);
// Fade doesn't have direction.
if ( 'fade' === $animation_style ) {
$animation_direction_tablet = '';
$animation_direction_phone = '';
} else {
$directions_list = array( 'top', 'right', 'bottom', 'left' );
if ( in_array( $animation_direction, $directions_list, true ) ) {
$animation_style .= ucfirst( $animation_direction );
}
// avoid custom animation on button because animation is applied to the wrapper so transforms do not need to combine.
if ( 'et_pb_button' !== $render_slug ) {
foreach ( preg_grep( '/(transform_)/', array_keys( $this->props ) ) as $index => $key ) {
if ( strpos( $key, 'link' ) !== false || strpos( $key, 'hover' ) !== false || strpos( $key, 'last_edited' ) !== false ) {
continue;
}
if ( ! empty( $this->props[ $key ] ) ) {
if ( ! $transformed_animations['desktop'] && strpos( $key, 'tablet' ) === false && strpos( $key, 'phone' ) === false ) {
$transformed_animations['desktop'] = true;
$transformed_animations['tablet'] = true;
$transformed_animations['phone'] = true;
} elseif ( ! $transformed_animations['tablet'] && strpos( $key, 'tablet' ) !== false ) {
$transformed_animations['tablet'] = true;
$transformed_animations['phone'] = true;
} elseif ( ! $transformed_animations['phone'] && strpos( $key, 'phone' ) !== false ) {
$transformed_animations['phone'] = true;
}
if ( $transformed_animations['desktop'] && $transformed_animations['tablet'] && $transformed_animations['phone'] ) {
break;
}
}
}
}
}
$module_class = self::get_module_order_class( $render_slug );
if ( $module_class ) {
// Desktop animation data.
$animation_data = array(
'class' => esc_attr( trim( $module_class ) ),
'style' => esc_html( $animation_style ),
'repeat' => esc_html( $animation_repeat ),
'duration' => esc_html( $animation_duration ),
'delay' => esc_html( $animation_delay ),
'intensity' => esc_html( $animation_intensity ),
'starting_opacity' => esc_html( $animation_starting_opacity ),
'speed_curve' => esc_html( $animation_speed_curve ),
);
// Being save to generate Tablet & Phone data attributes. As default, tablet
// default value will inherit desktop value and phone default value will inherit
// tablet value. Ensure to pass the value only if it's different compared to
// desktop value to avoid duplicate values.
$animation_attributes = array(
'repeat' => 'animation_repeat',
'duration' => 'animation_duration',
'delay' => 'animation_delay',
'intensity' => "animation_intensity_{$animation_style_responsive}",
'starting_opacity' => 'animation_starting_opacity',
'speed_curve' => 'animation_speed_curve',
);
foreach ( $animation_attributes as $animation_key => $animation_attribute ) {
$animation_attribute_tablet = '';
$animation_attribute_phone = '';
// Ensure responsive status for current attribute is activated.
if ( ! et_pb_responsive_options()->is_responsive_enabled( $this->props, $animation_attribute ) ) {
continue;
}
// Tablet animation value.
$animation_attribute_tablet = et_pb_responsive_options()->get_any_value( $this->props, "{$animation_attribute}_tablet", $animation_data[ $animation_key ] );
if ( ! empty( $animation_attribute_tablet ) ) {
$animation_data[ "{$animation_key}_tablet" ] = $animation_attribute_tablet;
}
// Phone animation value.
$animation_attribute_phone = et_pb_responsive_options()->get_any_value( $this->props, "{$animation_attribute}_phone", $animation_data[ $animation_key ] );
if ( ! empty( $animation_attribute_phone ) ) {
$animation_data[ "{$animation_key}_phone" ] = $animation_attribute_phone;
}
}
// Animation style is little bit different. We need to check the direction to get
// the correct style. We need to ensure the direction is valid, then add it as
// suffix for the animation style.
if ( et_pb_responsive_options()->is_responsive_enabled( $this->props, 'animation_direction' ) ) {
// Tablet animation style.
if ( ! empty( $animation_direction_tablet ) ) {
$animation_style_tablet_suffix = in_array( $animation_direction_tablet, $directions_list, true ) ? ucfirst( $animation_direction_tablet ) : '';
$animation_data['style_tablet'] = $animation_style_tablet . $animation_style_tablet_suffix;
}
// Phone animation style.
if ( ! empty( $animation_direction_phone ) ) {
$animation_style_phone_suffix = in_array( $animation_direction_phone, $directions_list, true ) ? ucfirst( $animation_direction_phone ) : '';
$animation_data['style_phone'] = $animation_style_phone . $animation_style_phone_suffix;
} elseif ( ! empty( $animation_data['style_tablet'] ) ) {
$animation_data['style_phone'] = $animation_data['style_tablet'];
}
}
// overwrite animation name to match the custom animation generated on transforms options processing.
if ( $transformed_animations['desktop'] ) {
$animation_data['style'] = 'transformAnim';
}
if ( $transformed_animations['tablet'] ) {
$animation_data['style_tablet'] = 'transformAnim';
}
if ( $transformed_animations['phone'] ) {
$animation_data['style_phone'] = 'transformAnim';
}
et_builder_handle_animation_data( $animation_data );
}
// Try to apply old method for plugins without vb support.
if ( ! $et_fb_processing_shortcode_object && 'on' !== $this->vb_support ) {
add_filter( "{$render_slug}_shortcode_output", array( $this, 'add_et_animated_class' ), 10, 2 );
}
// Only print et_animated on front-end. Avoid adding it on computed callback of post slider(s)
// and modules because it'll cause the module to be visually hidden.
if ( ! et_core_is_fb_enabled() ) {
$this->add_classname( 'et_animated' );
}
}
}
$has_hover_enabled = $this->_features_manager->get(
// Has hover enabled.
'hov',
function() {
return et_has_hover_enabled( $this->props );
}
);
// Add "et_hover_enabled" class to elements that have at least one hover prop enabled.
if ( $has_hover_enabled ) {
$this->add_classname( 'et_hover_enabled' );
}
// Add sticky element module classname to determine nested sticky module.
$needs_sticky_class = $this->_features_manager->get(
// Needs sticky class.
'sti',
function() use ( $et_fb_processing_shortcode_object, $render_slug ) {
return ! $et_fb_processing_shortcode_object && et_()->array_get( self::$sticky_elements, self::get_module_order_class( $render_slug ), false );
}
);
if ( $needs_sticky_class ) {
$this->add_classname( 'et_pb_sticky_module' );
}
// Setup link options.
$link_option_url = isset( $this->props['link_option_url'] ) ? $this->props['link_option_url'] : '';
$link_option_url_new_window = isset( $this->props['link_option_url_new_window'] ) ? $this->props['link_option_url_new_window'] : false;
if ( '' !== $link_option_url ) {
$module_class = self::get_module_order_class( $render_slug );
if ( $module_class ) {
et_builder_handle_link_options_data(
array(
'class' => trim( $module_class ),
'url' => esc_url_raw( $link_option_url ),
'target' => 'on' === $link_option_url_new_window ? '_blank' : '_self',
)
);
}
$this->add_classname( 'et_clickable' );
}
// Hide module on specific screens if needed.
if ( isset( $this->props['disabled_on'] ) && '' !== $this->props['disabled_on'] ) {
$disabled_on_array = explode( '|', $this->props['disabled_on'] );
$i = 0;
$current_media_query = 'max_width_767';
foreach ( $disabled_on_array as $value ) {
if ( 'on' === $value ) {
// Added specific selector and declaration to fix the problem when
// Video module is hidden for desktop the fullscreen
// won't work on mobile screen size.
$selector = 'et_pb_video' === $render_slug ? '.et_pb_video%%order_class%%' : '%%order_class%%';
$declaration = 'et_pb_video' === $render_slug ? 'min-height: 0; height: 0; margin: 0 !important; padding: 0; overflow: hidden;' : 'display: none !important;';
$el_style = array(
'selector' => $selector,
'declaration' => $declaration,
'media_query' => self::get_media_query( $current_media_query ),
);
ET_Builder_Module::set_style( $render_slug, $el_style );
}
$i++;
$current_media_query = 1 === $i ? '768_980' : 'min_width_981';
}
}
if ( ! $et_fb_processing_shortcode_object ) {
if ( 'et_pb_section' === $render_slug ) {
$et_pb_current_parent_type = isset( $this->props['specialty'] ) && 'on' === $this->props['specialty'] ? 'et_pb_specialty_section' : 'et_pb_section';
$et_pb_parent_section_type = $et_pb_current_parent_type;
} elseif ( 'et_pb_specialty_section' === $et_pb_current_parent_type && 'et_pb_column' === $render_slug ) {
$et_pb_current_parent_type = 'et_pb_specialty_column';
}
// Make sure content of Specialty Section is valid and has correct structure. Fix inner shortcode tags if needed.
if ( 'et_pb_specialty_section' === $et_pb_current_parent_type ) {
$content = $this->et_pb_maybe_fix_specialty_columns( $content );
}
}
$this->is_rendering = true;
$render_method = $et_fb_processing_shortcode_object ? 'render_as_builder_data' : 'render';
// Render the module as we normally would.
$output = $this->{$render_method}( $attrs, $content, $render_slug, $parent_address, $global_parent, $global_parent_type, $parent_type, $theme_builder_area );
/**
* Filters every rendered module output for processing "Display Conditions" option group.
*
* @since ??
*
* @param string $output HTML output of the rendered module.
* @param string $render_method The render method used to render the module.
* @param ET_Builder_Element $this The current instance of ET_Builder_Element.
*/
$output = apply_filters( 'et_module_process_display_conditions', $output, $render_method, $this );
$this->is_rendering = false;
// Wrap 3rd party module rendered output with proper module wrapper
// @TODO implement module wrapper on official module.
if ( 'on' === $this->vb_support && 'render' === $render_method && ! $this->_is_official_module ) {
$output = $this->_render_module_wrapper( $output, $render_slug );
}
/**
* Filters every builder modules shortcode output.
*
* @since 3.1
*
* @param string $output
* @param string $module_slug
* @param object $this
*/
$output = apply_filters( 'et_module_shortcode_output', $output, $render_slug, $this );
/**
* Filters builder module shortcode output. The dynamic portion of the filter name, `$render_slug`,
* refers to the slug of the module for which the shortcode output was generated.
*
* @since 3.0.87
*
* @param string $output
* @param string $module_slug
*/
$output = apply_filters( "{$render_slug}_shortcode_output", $output, $render_slug );
$this->_bump_render_count();
// Reset is_parent_sticky_module global once content of current module is done rendered.
if ( $this->is_sticky_module ) {
$is_parent_sticky_module = false;
}
if ( ! $post_interference ) {
ET_Post_Stack::restore();
}
if ( $hide_subject_module ) {
return '';
}
if ( $hide_subject_module_cached ) {
$previous_subjects_cache = get_post_meta( $post_id, 'et_pb_subjects_cache', true );
if ( empty( $previous_subjects_cache ) ) {
$previous_subjects_cache = array();
}
if ( empty( $this->template_name ) ) {
$previous_subjects_cache[ $this->props['ab_subject_id'] ] = $output;
} else {
$previous_subjects_cache[ $this->props['ab_subject_id'] ] = $this->output();
}
// update the subjects cache in post meta to use it later.
update_post_meta( $post_id, 'et_pb_subjects_cache', $previous_subjects_cache );
// generate the placeholder to output on front-end instead of actual content.
$subject_placeholder = sprintf(
'',
esc_attr( $post_id ),
esc_attr( $this->props['ab_subject_id'] )
);
return $subject_placeholder;
}
// Do not use `template_name` while processing object for VB.
if ( $et_fb_processing_shortcode_object || empty( $this->template_name ) ) {
return $output;
}
return $this->output();
}
/**
* Add "et_animated" class using filter. Obsolete method and only applied to old 3rd party modules without `modules_classname()` method
*
* @param string $output Shortcode output.
* @param string $module_slug Module slug.
*
* @return string
*/
public function add_et_animated_class( $output, $module_slug ) {
if ( ! is_string( $output ) || in_array( $module_slug, self::$uses_module_classname, true ) ) {
return $output;
}
remove_filter( "{$module_slug}_shortcode_output", array( $this, 'add_et_animated_class' ), 10 );
return preg_replace( "/class=\"(.*?{$module_slug}_\d+.*?)\"/", 'class="$1 et_animated"', $output, 1 );
}
/**
* Delete attribute values that are equal to the global default value (if one exists).
*
* @return void
*/
protected function _maybe_remove_global_default_values_from_props() {
$fields = $this->fields_unprocessed;
$must_print_fields = array( 'text_orientation' );
/**
* Filters Must Print attributes array.
* Must Print attributes - attributes which defaults should always be printed on Front End
*
* @deprecated
*
* @param array $must_print_fields Array of attribute names.
*/
$must_print_fields = apply_filters( $this->slug . '_must_print_attributes', $must_print_fields );
$slug = isset( $this->global_settings_slug ) ? $this->global_settings_slug : $this->slug;
$module_slug = self::$global_presets_manager->maybe_convert_module_type( $this->slug, $this->props );
$module_preset_settings = self::$global_presets_manager->get_module_presets_settings( $module_slug, $this->props );
$global_default_fields = $this->_features_manager->get(
// Global default fields.
'glde',
function() use ( $fields, $slug, $must_print_fields ) {
$global_default_fields = [];
foreach ( $fields as $field_key => $field_settings ) {
$global_setting_name = "$slug-$field_key";
$global_setting_value = ET_Global_Settings::get_value( $global_setting_name );
if ( ! $global_setting_value || in_array( $field_key, $must_print_fields, true ) ) {
continue;
}
$global_default_fields[ $field_key ] = $global_setting_value;
}
return $global_default_fields;
}
);
// make sure its at least an array if feature manager had returned false.
$global_default_fields = $global_default_fields ?: []; // phpcs:ignore WordPress.PHP.DisallowShortTernary.Found -- It's PHP 5.3+ compat, so it's fine to use.
foreach ( $global_default_fields as $field_key => $global_setting_value ) {
$attr_value = ! empty( $this->props[ $field_key ] ) ? $this->props[ $field_key ] : '';
if ( $attr_value && $attr_value === $global_setting_value && ! array_key_exists( $field_key, $module_preset_settings ) ) {
$this->props[ $field_key ] = '';
}
}
}
/**
* Intended to be overridden as needed.
*/
public function maybe_inherit_values() {}
/**
* Like {@see self::render()}, but sources the output from a template file. The template name
* should be set in {@see self::$template_name}.
*
* Note: this functionality is not currently supported by the Visual Builder. Pages containing
* modules that use this method to render their output cannot be edited using the Visual Builder
* at this time. However, full support will be added in the coming months.
*
* @since 3.1 Renamed from `shortcode_output()` to `output()`
* @since 2.4.6
*
* @return string
*/
public function output() {
if ( empty( $this->template_name ) ) {
return '';
}
if ( method_exists( $this, 'shortcode_output' ) ) {
// Backwards compatibility.
return $this->__call( 'shortcode_output', array() );
}
$this->props['content'] = $this->content;
// phpcs:ignore WordPress.PHP.DontExtract -- `extract` can not be removed, template depend on props variables.
extract( $this->props );
ob_start();
require locate_template( $this->template_name . '.php' );
return ob_get_clean();
}
/**
* Generates HTML data attributes from an array of props.
*
* @since 3.1 Rename from `shortcode_atts_to_data_atts()` to `props_to_html_data_attrs()`
* @since 1.0
*
* @param array $props Propeties to be use in data attributes.
*
* @return string
*/
public function props_to_html_data_attrs( $props = array() ) {
if ( empty( $props ) ) {
return '';
}
$output = array();
foreach ( $props as $attr ) {
$output[] = 'data-' . esc_attr( $attr ) . '="' . esc_attr( $this->props[ $attr ] ) . '"';
}
return implode( ' ', $output );
}
/**
* This method is called before {@self::_render()} for rows, columns, and modules. It can
* be overridden by elements that need to perform any tasks before rendering begins.
*
* @since 3.1 Renamed from `pre_shortcode_content()` to `before_render()`.
* @since 1.0
*/
public function before_render() {
if ( method_exists( $this, 'pre_shortcode_content' ) ) {
// Backwards compatibility.
$this->__call( 'pre_shortcode_content', array() );
}
}
/**
* Generates the module's HTML output based on {@see self::$props}. This method should be
* overridden in module classes.
*
* @since 3.1 Renamed from `shortcode_callback()` to `render()`.
* @since 1.0
*
* @param array $attrs List of unprocessed attributes.
* @param string $content Content being processed.
* @param string $render_slug Slug of module that is used for rendering output.
*
* @return string The module's HTML output.
*/
public function render( $attrs, $content, $render_slug ) {
if ( method_exists( $this, 'shortcode_callback' ) ) {
// Backwards compatibility.
return $this->__call( 'shortcode_callback', array( $attrs, $content, $render_slug ) );
}
return '';
}
/**
* Replace the et_pb_row with et_pb_row_inner and et_pb_column with et_pb_column_inner.
* Used as a callback function in {@self::et_pb_maybe_fix_specialty_columns} when fixing content of Specialty Sections
*
* @param string $rows Row content.
*
* @return string Shortcode string.
* @since 3.19.16
*/
public function et_pb_fix_specialty_columns( $rows ) {
$sanitized_shortcode = str_replace( array( 'et_pb_row ', 'et_pb_row]' ), array( 'et_pb_row_inner ', 'et_pb_row_inner]' ), $rows[0] );
$sanitized_shortcode = str_replace( array( 'et_pb_column ', 'et_pb_column]' ), array( 'et_pb_column_inner ', 'et_pb_column_inner]' ), $rows[0] );
return $sanitized_shortcode;
}
/**
* Run regex against the Specialty Section content to find and fix invalid inner shortcodes
*
* @param string $section_content Section content.
*
* @return string Shortcode string.
* @since 3.19.16
*/
public function et_pb_maybe_fix_specialty_columns( $section_content ) {
return preg_replace_callback( '/(\[et_pb_(row |row_inner) .*?\].*\[\/et_pb_(row |row_inner)\])/mis', array( $this, 'et_pb_fix_specialty_columns' ), $section_content );
}
/**
* Generates data used to render the module in the builder.
* See {@see self::render()} for parameter info.
*
* @param array $atts Module attributes.
* @param string|null $content Module content.
* @param string $render_slug Module slug.
* @param string $parent_address [description].
* @param string $global_parent [description].
* @param string $global_parent_type [description].
* @param string $parent_type [description].
*
* @return array|string An array when called during AJAX request, an empty string otherwise.
* @since 3.1 Renamed from `_shortcode_passthru_callback()` to `render_as_builder_data()`
* @since 3.0.0
*/
public function render_as_builder_data( $atts, $content, $render_slug, $parent_address = '', $global_parent = '', $global_parent_type = '', $parent_type = '', $theme_builder_area = '' ) {
global $post;
// this is called during pageload, but we want to ignore that round, as this data will be built and returned on separate ajax request instead.
et_core_nonce_verified_previously();
if ( ! ( isset( $_POST['action'] ) || apply_filters( 'et_builder_module_force_render', false ) ) ) {
return '';
}
$attrs = array();
$fields = $this->process_fields( $this->fields_unprocessed );
$global_content = false;
$function_name_processed = et_fb_prepare_tag( $render_slug );
$unsynced_global_attributes = array();
$use_updated_global_sync_method = false;
$global_module_id = isset( $atts['global_module'] ) ? $atts['global_module'] : false;
$is_specialty_placeholder = isset( $atts['template_type'] ) && 'section' === $atts['template_type'] && isset( $atts['specialty'] ) && 'on' === $atts['specialty'] && ( ! $content || '' === trim( $content ) );
$is_global_template = false;
$real_parent_type = $parent_type;
if ( $render_slug && $render_slug !== $this->slug ) {
$rendering_module = self::get_module( $render_slug, $this->get_post_type() );
if ( $rendering_module ) {
$fields = array_merge( $fields, $this->process_fields( $rendering_module->fields_unprocessed ) );
}
}
$output_render_slug = $render_slug;
// When rendering specialty columns we should make sure correct tags are used for inner content
// Global Rows inside may break it in some cases, so handle it.
if ( 'et_pb_specialty_column' === $parent_type && 'et_pb_row' === $render_slug ) {
$output_render_slug = 'et_pb_row_inner';
$function_name_processed = 'et_pb_row_inner';
}
if ( 'et_pb_row_inner' === $parent_type && 'et_pb_column' === $render_slug ) {
$output_render_slug = 'et_pb_column_inner';
$function_name_processed = 'et_pb_column_inner';
}
$post_id = isset( $post->ID ) ? $post->ID : intval( self::$_->array_get( $_POST, 'et_post_id' ) );
$post_type = isset( $post->post_type ) ? $post->post_type : sanitize_text_field( self::$_->array_get( $_POST, 'et_post_type' ) );
$layout_type = isset( $post_type, $post_id ) && 'et_pb_layout' === $post_type ? et_fb_get_layout_type( $post_id ) : '';
if ( 'module' === $layout_type ) {
// Add support of new selective sync feature for library modules in VB.
$template_scope = wp_get_object_terms( $post_id, 'scope' );
$is_global_template = ! empty( $template_scope[0] ) && 'global' === $template_scope[0]->slug;
if ( $is_global_template ) {
$global_module_id = $post_id;
}
}
// override module attributes for global module.
if ( ! empty( $global_module_id ) ) {
if ( ! in_array( $render_slug, array( 'et_pb_section', 'et_pb_row', 'et_pb_row_inner', 'et_pb_column', 'et_pb_column_inner' ), true ) ) {
$processing_global_module = $global_module_id;
$unsynced_global_attributes = get_post_meta( $processing_global_module, '_et_pb_excluded_global_options' );
$use_updated_global_sync_method = ! empty( $unsynced_global_attributes );
}
$global_module_data = et_pb_load_global_module( $global_module_id, $function_name_processed );
if ( '' !== $global_module_data ) {
$unsynced_options = ! empty( $unsynced_global_attributes[0] ) ? json_decode( $unsynced_global_attributes[0], true ) : array();
$content_synced = $use_updated_global_sync_method && ! in_array( 'et_pb_content_field', $unsynced_options, true );
$is_module_fully_global = $use_updated_global_sync_method && empty( $unsynced_options );
$unsynced_legacy_options = array();
// support legacy selective sync system.
if ( ! $use_updated_global_sync_method ) {
$content_synced = ! isset( $atts['saved_tabs'] ) || false !== strpos( $atts['saved_tabs'], 'general' ) || 'all' === $atts['saved_tabs'];
$is_module_fully_global = ! isset( $atts['saved_tabs'] ) || 'all' === $atts['saved_tabs'];
}
if ( $content_synced && ! $is_global_template ) {
$global_content = et_pb_get_global_module_content( $global_module_data, $function_name_processed );
// When saving global rows from specialty sections, they get saved as et_pb_row instead of et_pb_row_inner.
// Handle this special case when parsing to avoid empty global row content.
if ( empty( $global_content ) && 'et_pb_row_inner' === $function_name_processed ) {
$global_content = et_pb_get_global_module_content( $global_module_data, 'et_pb_row', true );
}
}
// remove the shortcode content to avoid conflicts of parent attributes with similar attrs from child modules.
if ( false !== $global_content ) {
$global_content_processed = str_replace( $global_content, '', $global_module_data );
} else {
$global_content_processed = $global_module_data;
}
// Ensuring that all possible attributes exist to avoid remaining child attributes being used by global parents' attributes
// Do that only in case the module is fully global.
if ( $is_module_fully_global ) {
$global_atts = shortcode_parse_atts( et_pb_remove_shortcode_content( $global_content_processed, $this->slug ) );
} else {
$global_atts = shortcode_parse_atts( $global_content_processed );
}
// Run et_pb_module_shortcode_attributes filter to apply migration system on attributes of global module.
$global_atts = apply_filters( 'et_pb_module_shortcode_attributes', $global_atts, $atts, $this->slug, $this->generate_element_address( $render_slug ), $content );
// Parse dynamic content in global attributes.
$enabled_dynamic_attributes = $this->_get_enabled_dynamic_attributes( $global_atts );
$global_atts = $this->_encode_legacy_dynamic_content( $global_atts, $enabled_dynamic_attributes );
$global_atts = $this->process_dynamic_attrs( $global_atts );
// Parse dynamic content in global content.
if ( false !== $global_content ) {
$global_content = $this->_encode_legacy_dynamic_content_value(
'content',
$global_content,
$enabled_dynamic_attributes
);
$global_content = $this->_resolve_value(
$this->get_the_ID(),
'content',
$global_content,
$this->_get_enabled_dynamic_attributes( $global_atts ),
true
);
}
foreach ( $this->props as $single_attr => $value ) {
if ( isset( $global_atts[ $single_attr ] ) && ! in_array( $single_attr, $unsynced_options, true ) ) {
// replace %22 with double quotes in options to make sure it's rendered correctly.
if ( ! $is_global_template ) {
$this->props[ $single_attr ] = is_string( $global_atts[ $single_attr ] ) && ! array_intersect( array( "et_pb_{$single_attr}", $single_attr ), $this->dbl_quote_exception_options ) ? str_replace( '%22', '"', $global_atts[ $single_attr ] ) : $global_atts[ $single_attr ];
}
if ( 'global_colors_info' === $single_attr ) {
$this->props[ $single_attr ] = str_replace( array( '%91', '%93' ), array( '[', ']' ), $this->props[ $single_attr ] );
}
} elseif ( ! $use_updated_global_sync_method ) {
// prepare array of unsynced options to migrate the legacy modules to new system.
$unsynced_legacy_options[] = $single_attr;
} else {
$unsynced_global_attributes[0] = $unsynced_options;
}
}
// migrate unsynced options to the new selective sync method.
if ( ! $use_updated_global_sync_method ) {
$unsynced_global_attributes[0] = $unsynced_legacy_options;
// check the content and add it into list if needed.
if ( ! $content_synced ) {
$unsynced_global_attributes[0][] = 'et_pb_content_field';
}
} else {
$unsynced_global_attributes[0] = $unsynced_options;
}
} else {
// remove global_module attr if it doesn't exist in DB.
$this->props['global_module'] = '';
$global_parent = '';
}
}
$module_slug = self::$global_presets_manager->maybe_convert_module_type( $this->slug, $this->props );
$module_preset_settings = self::$global_presets_manager->get_module_presets_settings( $module_slug, $this->props );
foreach ( $this->props as $shortcode_attr_key => $shortcode_attr_value ) {
$value = $shortcode_attr_value;
// don't set the default, unless, lol, the value is literally 'default'.
if ( 'default' !== $value ) {
$has_preset_value = isset( $module_preset_settings[ $shortcode_attr_key ] );
if ( $has_preset_value && isset( $atts[ $shortcode_attr_key ] ) ) {
$is_equal_to_preset_value = $atts[ $shortcode_attr_key ] === $module_preset_settings[ $shortcode_attr_key ];
$value = $is_equal_to_preset_value ? '' : $atts[ $shortcode_attr_key ];
} else {
// handle 'preset' type of attributes.
if ( isset( $fields[ $shortcode_attr_key ]['default'] ) && is_array( $fields[ $shortcode_attr_key ]['default'] ) ) {
$field = $fields[ $shortcode_attr_key ];
$preset_attribute_name = $field['default'][0];
if ( 'filter' === $preset_attribute_name ) {
// Functional default.
if ( apply_filters( $field['default'][1], $shortcode_attr_key ) === $value ) {
$value = '';
}
} else {
$preset_default_value = et_()->array_get( $fields[ $preset_attribute_name ], 'default', 'none' );
$preset_attribute_value = et_()->array_get( $this->props, $preset_attribute_name, $preset_default_value );
if ( ! empty( $preset_attribute_value ) ) {
$value_from_preset = et_()->array_get( $fields[ $shortcode_attr_key ]['default'][1], $preset_attribute_value, '' );
if ( $value === $value_from_preset ) {
$value = '';
}
}
}
} else {
$is_equal_to_default = isset( $fields[ $shortcode_attr_key ]['default'] ) && $value === $fields[ $shortcode_attr_key ]['default'];
$is_equal_to_default_on_front = isset( $fields[ $shortcode_attr_key ]['default_on_front'] ) && $value === $fields[ $shortcode_attr_key ]['default_on_front'];
if ( $is_equal_to_default || $is_equal_to_default_on_front ) {
$value = '';
}
}
}
} else {
if ( '_module_preset' !== $shortcode_attr_key ) {
$value = '';
}
}
// generic override, disabled=off is an unspoken default.
if ( 'disabled' === $shortcode_attr_key && 'off' === $shortcode_attr_value ) {
$value = '';
}
// this override is necessary becuase et_pb_column and et_pb_column_inner type default is 4_4 and will get stomped
// above since its default, but we need it explicitly set anyways, so we force set it.
if ( in_array( $render_slug, array( 'et_pb_column', 'et_pb_column_inner' ), true ) && 'type' === $shortcode_attr_key ) {
$value = $shortcode_attr_value;
}
$is_include_attr = false;
if ( '' === $value
&& et_pb_hover_options()->get_field_base_name( $shortcode_attr_key ) !== $shortcode_attr_key
&& et_pb_hover_options()->is_enabled( et_pb_hover_options()->get_field_base_name( $shortcode_attr_key ), $atts ) ) {
$is_include_attr = true;
}
if ( '' === $value
&& et_pb_responsive_options()->get_field_base_name( $shortcode_attr_key ) !== $shortcode_attr_key
&& et_pb_responsive_options()->is_enabled( et_pb_responsive_options()->get_field_base_name( $shortcode_attr_key ), $atts ) ) {
$is_include_attr = true;
}
if ( '' !== $value ) {
$is_include_attr = true;
}
if ( $is_include_attr ) {
// Process Icon fields with a special `icons converting\decoding`.
if ( ! empty( $fields[ $shortcode_attr_key ] ) && 'select_icon' === $fields[ $shortcode_attr_key ]['type'] && is_string( $value ) ) {
$attrs[ $shortcode_attr_key ] = et_pb_check_and_convert_icon_raw_value( $value );
} else {
$attrs[ $shortcode_attr_key ] = is_string( $value ) ? html_entity_decode( $value ) : $value;
}
}
}
// Decode all characters inside json object so it can be parsed correctly.
if ( ! empty( $attrs['global_colors_info'] ) ) {
$attrs['global_colors_info'] = str_replace( array( '%91', '%93', '%22' ), array( '[', ']', '"' ), $attrs['global_colors_info'] );
}
// Format FB component path
// TODO, move this to class method and property, and allow both to be overridden.
$component_path = str_replace( 'et_pb_', '', $function_name_processed );
$component_path = str_replace( '_', '-', $component_path );
$_i = isset( $atts['_i'] ) ? $atts['_i'] : 0;
$address = isset( $atts['_address'] ) ? $atts['_address'] : '0';
// set the global parent if exists.
if ( ( ! isset( $attrs['global_module'] ) || '' === $attrs['global_module'] ) && '' !== $global_parent ) {
$attrs['global_parent'] = $global_parent;
}
// add theme_builder_area parameter to attributes, so we know to strip this section when saving the post.
if ( isset( $theme_builder_area ) && '' !== $theme_builder_area ) {
$attrs['theme_builder_area'] = $theme_builder_area;
}
if ( isset( $this->is_structure_element ) && $this->is_structure_element ) {
$this->vb_support = 'on';
}
$processed_content = false !== $global_content ? $global_content : $this->content;
// Determine the parent type to send it down the tree while processing shortcode
// Main purpose is to know when we rendering Specialty Section content.
if ( 'et_pb_section' === $render_slug ) {
$parent_type = isset( $attrs['specialty'] ) && 'on' === $attrs['specialty'] ? 'et_pb_specialty_section' : 'et_pb_section';
} elseif ( 'et_pb_specialty_section' === $parent_type && 'et_pb_column' === $render_slug ) {
$parent_type = 'et_pb_specialty_column';
} else {
$parent_type = $render_slug;
}
// Make sure content of Specialty Section is valid and has correct structure. Fix inner shortcode tags if needed.
if ( 'et_pb_specialty_section' === $parent_type ) {
$processed_content = $this->et_pb_maybe_fix_specialty_columns( $processed_content );
}
$content = array_key_exists( 'content', $this->fields_unprocessed ) || 'et_pb_code' === $function_name_processed || 'et_pb_fullwidth_code' === $function_name_processed ? $processed_content : et_fb_process_shortcode( $processed_content, $address, $global_parent, $global_parent_type, $parent_type );
// Global Code module content should be decoded before passing to VB.
$is_global_code = in_array( $function_name_processed, array( 'et_pb_code', 'et_pb_fullwidth_code' ), true );
$prepared_content = $content;
if ( ( ! is_array( $content ) && 'on' !== $this->vb_support && ! $this->has_line_breaks( $content ) ) || $is_global_code ) {
$prepared_content = html_entity_decode( $content, ENT_COMPAT, 'UTF-8' );
}
if ( empty( $attrs ) ) {
// Visual Builder expects $attrs to be an object.
// Associative array converted to an object by wp_json_encode correctly, but empty array is not and it causes issues.
$attrs = new stdClass();
}
$is_child_module = in_array( $render_slug, self::get_child_slugs( $this->get_post_type() ), true ) && false === strpos( $render_slug, '_column_inner' ) && false === strpos( $render_slug, '_column' );
$module_type = $this->type;
$render_count = $is_child_module ? self::_get_index( self::INDEX_MODULE_ITEM ) : self::_get_index( array( self::INDEX_MODULE_ORDER, $function_name_processed ) );
$child_title_var = isset( $this->child_title_var ) ? $this->child_title_var : '';
$child_title_fallback_var = isset( $this->child_title_fallback_var ) ? $this->child_title_fallback_var : '';
$advanced_setting_title_text = isset( $this->advanced_setting_title_text ) ? $this->advanced_setting_title_text : '';
// If this is a shop module use the Shop module render count
// Shop module creates a new class instance which resets the $_render_count value
// ( see get_shop_html() method of ET_Builder_Module_Shop class in main-modules.php )
// so we use a static property to track its proper render count.
if ( 'et_pb_shop' === $render_slug ) {
$render_count = self::$_shop_render_count;
self::$_shop_render_count++;
}
// Ensuring that module which uses another module's template (i.e. accordion item uses toggle's
// component) has correct values for class properties where it makes a difference. This is covered on front-end, but it causes inheriting
// module uses its template's value on render_as_builder_data().
if ( isset( $rendering_module, $rendering_module->type ) ) {
$module_type = $rendering_module->type;
$child_title_var = isset( $rendering_module->child_title_var ) ? $rendering_module->child_title_var : $child_title_var;
$child_title_fallback_var = isset( $rendering_module->child_title_fallback_var ) ? $rendering_module->child_title_fallback_var : $child_title_fallback_var;
$advanced_setting_title_text = isset( $rendering_module->advanced_setting_title_text ) ? $rendering_module->advanced_setting_title_text : $advanced_setting_title_text;
}
// Build object.
$object = array(
'_i' => $_i,
'_order' => $_i,
// TODO make address be _address, its conflicting with 'address' prop in map module... (not sure how though, they are in diffent places...).
'address' => $address,
'child_slug' => $this->child_slug,
'parent_slug' => $real_parent_type,
'vb_support' => $this->vb_support,
'parent_address' => $parent_address,
'shortcode_index' => $render_count,
'type' => $output_render_slug,
'theme_builder_suffix' => self::_get_theme_builder_order_class_suffix(),
'component_path' => $component_path,
'main_css_element' => $this->main_css_element,
'attrs' => $attrs,
'content' => $prepared_content,
'is_module_child' => 'child' === $module_type,
'is_structure_element' => ! empty( $this->is_structure_element ),
'is_specialty_placeholder' => $is_specialty_placeholder,
'is_official_module' => $this->_is_official_module,
'child_title_var' => $child_title_var,
'child_title_fallback_var' => $child_title_fallback_var,
'advanced_setting_title_text' => $advanced_setting_title_text,
'wrapper_settings' => $this->get_wrapper_settings( $render_slug ),
);
if ( ! empty( $unsynced_global_attributes ) ) {
$object['unsyncedGlobalSettings'] = $unsynced_global_attributes[0];
}
if ( $is_global_template ) {
$object['libraryModuleScope'] = 'global';
}
if ( isset( $this->module_items_config ) ) {
$object['module_items_config'] = $this->module_items_config;
}
return $object;
}
/**
* Determine if provided string contain line-breaks (`\r\n`)
*
* @param string $content String to check.
*
* @return bool
*/
public function has_line_breaks( $content ) {
return count( preg_split( '/\r\n*\n/', trim( $content ), -1, PREG_SPLIT_NO_EMPTY ) ) > 1;
}
/**
* Additional shortcode render callback.
*
* Intended to be overridden as needed.
*
* @param array $attrs Attributes.
* @param null $content Shortcode content.
* @param string $render_slug Shortcode tag.
*/
public function additional_render( $attrs, $content, $render_slug ) {
if ( method_exists( $this, 'additional_shortcode_callback' ) ) {
// Backwards compatibility.
$this->__call( 'additional_shortcode_callback', array( $attrs, $content, $render_slug ) );
}
}
/**
* Intended to be overridden as needed.
*/
public function predefined_child_modules(){}
/**
* Generate global setting name
*
* @param string $option_slug Option slug.
*
* @return string Global setting name in the following format: "module_slug-option_slug"
*/
public function get_global_setting_name( $option_slug ) {
$global_setting_name = sprintf(
'%1$s-%2$s',
isset( $this->global_settings_slug ) ? $this->global_settings_slug : $this->slug,
$option_slug
);
return $global_setting_name;
}
/**
* Add global default values to all fields, if they don't have defaults set
*
* @return void
*/
protected function _maybe_add_global_defaults() {
// Don't add default settings to "child" modules.
if ( 'child' === $this->type ) {
return;
}
$fields = $this->fields_unprocessed;
$ignored_keys = array(
'custom_margin',
'custom_padding',
);
// Font color settings have custom_color set to true, so add them to ignored keys array.
if ( isset( $this->advanced_fields['fonts'] ) && is_array( $this->advanced_fields['fonts'] ) ) {
foreach ( $this->advanced_fields['fonts'] as $font_key => $font_settings ) {
$ignored_keys[] = sprintf( '%1$s_text_color', $font_key );
}
}
$ignored_keys = apply_filters( 'et_builder_add_defaults_ignored_keys', $ignored_keys );
foreach ( $fields as $field_key => $field_settings ) {
if ( in_array( $field_key, $ignored_keys, true ) ) {
continue;
}
$global_setting_name = $this->get_global_setting_name( $field_key );
$global_setting_value = ET_Global_Settings::get_value( $global_setting_name );
if ( ! isset( $field_settings['default'] ) && $global_setting_value ) {
$fields[ $field_key ]['default'] = $global_setting_value;
// Mark this default as global so VB won't print it to replicate FE behaviour.
$fields[ $field_key ]['is_global_default'] = true;
}
}
$this->fields_unprocessed = $fields;
}
/**
* Rebuild option template in $this->fields_unprocessed property into actual field if needed.
*
* @since 3.28
*/
protected function _maybe_rebuild_option_template() {
// Once module's option template inside $this->fields_unprocessed is rebuilt, the next
// module's `_render()` won't need it. Thus, skip this to speed up performance.
if ( in_array( $this->slug, self::$_has_rebuilt_option_template, true ) ) {
return;
}
foreach ( $this->fields_unprocessed as $field_name => $field ) {
// If first two field name matches template prefix, it is safely assume that current
// unprocessed fields is reference to option template.
if ( self::$option_template->is_enabled() && self::$option_template->is_option_template_field( $field_name ) ) {
// Rebuild fields.
$rebuilt_fields = self::$option_template->rebuild_field_template( $field_name );
// Assign rebuilt fields to module's unprocessed fields.
foreach ( $rebuilt_fields as $rebuilt_field_name => $rebuilt_field ) {
$this->fields_unprocessed[ $rebuilt_field_name ] = $rebuilt_field;
}
// Remove option template field.
unset( $this->fields_unprocessed[ $field_name ] );
}
}
self::$_has_rebuilt_option_template[] = $this->slug;
}
/**
* Adds Global Presets settings.
*
* @since 3.26
*
* @param array $attrs The list of a module attributes.
* @param string $render_slug The real slug from the shortcode.
*
* @return array
*/
protected function _maybe_add_global_presets_settings( $attrs, $render_slug ) {
if ( ( et_fb_is_enabled() || et_builder_bfb_enabled() ) && ! self::is_theme_builder_layout() ) {
return $attrs;
}
$render_slug = self::$global_presets_manager->maybe_convert_module_type( $render_slug, $attrs );
$module_preset_settings = self::$global_presets_manager->get_module_presets_settings( $render_slug, $attrs );
if ( is_array( $attrs ) ) {
// We need a special handler for social media child items module background color setting
// as it has different default background color for each network.
if ( 'et_pb_social_media_follow_network' === $render_slug
&& ! empty( $attrs['social_network'] )
&& ! empty( $attrs['background_color'] )
&& ! empty( $module_preset_settings['background_color'] )
&& $attrs['background_color'] !== $module_preset_settings['background_color']
) {
$background_color_definition = self::$_->array_get( $this->get_fields(), "social_network.options.{$attrs['social_network']}.data.color" );
if ( $background_color_definition === $attrs['background_color'] ) {
// Unset the background color attrs if it was default based on selected network.
unset( $attrs['background_color'] );
}
}
// Unset the custom_margin that only has | as the value.
if ( ! empty( $attrs['custom_margin'] ) && '' === str_replace( '|', '', $attrs['custom_margin'] ) ) {
unset( $attrs['custom_margin'] );
}
// Unset the custom_padding that only has | as the value.
if ( ! empty( $attrs['custom_padding'] ) && '' === str_replace( '|', '', $attrs['custom_padding'] ) ) {
unset( $attrs['custom_padding'] );
}
return array_merge( $module_preset_settings, $attrs );
}
return $module_preset_settings;
}
/**
* Add additional option fields.
*
* @since 3.23 Introduce form field options set. Also, add codes to generate responsive options
* set with suffix automatically. It also supports mobile_options on composite, bg
* field, and computed fields as well.
*/
protected function _add_additional_fields() {
// Setup has_advanced_fields property to adjust advanced options visibility on
// module that has no VB support to avoid sudden advanced options appearances.
$this->has_advanced_fields = isset( $this->advanced_fields );
// Advanced options are added by default unless module explicitly disabled it.
$this->advanced_fields = $this->has_advanced_fields ? $this->advanced_fields : array();
// Advanced options have to be array.
if ( ! is_array( $this->advanced_fields ) ) {
return;
}
// Add form field options set to modules that use form as main part.
$this->_add_form_field_fields();
$this->_add_font_fields();
$this->_add_background_fields();
$this->_add_borders_fields();
$this->_add_button_fields();
$this->_add_image_icon_fields();
$this->_add_box_shadow_fields();
$this->_add_transforms_fields();
$this->_add_position_fields();
$this->_add_text_fields();
$this->_add_sizing_fields();
$this->_add_overflow_fields();
$this->_add_display_conditions_fields();
$this->_add_margin_padding_fields();
// Add filter fields to modules.
$this->_add_filter_fields();
// Add divider fields to section modules.
$this->_add_divider_fields();
// Add animation fields to all modules.
$this->_add_animation_fields();
$this->_add_additional_transition_fields();
// Add text shadow fields to all modules.
$this->_add_text_shadow_fields();
// Add link options to all modules.
$this->_add_link_options_fields();
$this->_add_sticky_fields();
$this->_add_scroll_effects_fields();
if ( ! isset( $this->_additional_fields_options ) ) {
return false;
}
$additional_options = $this->_additional_fields_options;
$this->_additional_fields_options = array();
$is_column_module = in_array( $this->slug, array( 'et_pb_column', 'et_pb_column_inner' ), true );
// delete second level advanced options default values.
if ( isset( $this->type ) && 'child' === $this->type && ! $is_column_module && apply_filters( 'et_pb_remove_child_module_defaults', true ) ) {
foreach ( $additional_options as $name => $settings ) {
if ( isset( $additional_options[ $name ]['default'] ) && ! isset( $additional_options[ $name ]['default_on_child'] ) ) {
$additional_options[ $name ]['default'] = '';
}
}
}
if ( function_exists( 'et_builder_definition_sort' ) ) {
et_builder_definition_sort( $additional_options );
}
$additional_options = et_pb_responsive_options()->create( $additional_options );
$this->_set_fields_unprocessed( $additional_options );
}
/**
* Set i18n used by font fields.
*
* @since 4.4.9
*
* @return void
*/
protected function set_i18n_font() {
// Cache results so that translation/escaping only happens once.
$i18n =& self::$i18n;
if ( ! isset( $i18n['font'] ) ) {
// phpcs:disable WordPress.WP.I18n.MissingTranslatorsComment
$i18n['font'] = array(
'letter_spacing' => array(
'label' => esc_html__( '%1$s Letter Spacing', 'et_builder' ),
'description' => esc_html__( 'Letter spacing adjusts the distance between each letter in the %1$s.', 'et_builder' ),
),
'size' => array(
'label' => esc_html__( '%1$s Text Size', 'et_builder' ),
'description' => esc_html__( 'Increase or decrease the size of the %1$s text.', 'et_builder' ),
),
'font' => array(
'label' => esc_html__( '%1$s Font', 'et_builder' ),
'description' => esc_html__( 'Choose a custom font to use for the %1$s. All Google web fonts are available, or you can upload your own custom font files.', 'et_builder' ),
),
'color' => array(
'label' => esc_html__( '%1$s Text Color', 'et_builder' ),
'description' => esc_html__( 'Pick a color to be used for the %1$s text.', 'et_builder' ),
),
'line_height' => array(
'label' => esc_html__( '%1$s Line Height', 'et_builder' ),
'description' => esc_html__( 'Line height adjusts the distance between each line of the %1$s text. This becomes noticeable if the %1$s is long and wraps onto multiple lines.', 'et_builder' ),
),
'text_align' => array(
'label' => esc_html__( '%1$s Text Alignment', 'et_builder' ),
'description' => esc_html__( 'Align the %1$s to the left, right, center or justify.', 'et_builder' ),
),
);
// phpcs:enable
}
}
/**
* Add font option fields.
*
* @since 3.23 Introduce block elements sub options group. Add responsive settings for font set,
* text color, text alignment, and text-shadow options set.
*/
protected function _add_font_fields() {
// Font fields are added by default if module has partial or full VB support.
if ( $this->has_vb_support() ) {
$this->advanced_fields['fonts'] = self::$_->array_get(
$this->advanced_fields,
'fonts',
array(
'module' => array(
'label' => esc_html__( 'Module', 'custom_module' ),
'line_height' => array(
'default' => floatval( et_get_option( 'body_font_height', '1.7' ) ) . 'em',
),
'font_size' => array(
'default' => absint( et_get_option( 'body_font_size', '14' ) ) . 'px',
),
),
)
);
} elseif ( ! $this->has_advanced_fields ) {
// Disable if module doesn't set advanced_fields property and has no VB support.
return;
}
// Font settings have to be array.
if ( ! is_array( self::$_->array_get( $this->advanced_fields, 'fonts' ) ) ) {
return;
}
$advanced_font_options = $this->advanced_fields['fonts'];
$additional_options = array();
$defaults = array(
'all_caps' => 'off',
);
$this->set_i18n_font();
$i18n =& self::$i18n;
foreach ( $advanced_font_options as $option_name => $option_settings ) {
$advanced_font_options[ $option_name ]['defaults'] = $defaults;
// Continue if toggle is disabled.
$toggle_disabled = isset( $option_settings['disable_toggle'] ) && $option_settings['disable_toggle'];
if ( $toggle_disabled ) {
continue;
}
// Block Elements - 1. Add block elements settings to fonts.
// We need to add link, ul, ol, and quote settings to fonts. We also need to convert
// current font setting with block_elements to be sub toggle of P.
if ( isset( $option_settings['block_elements'] ) && is_array( $option_settings['block_elements'] ) ) {
// Ensure target font option is exist.
if ( ! isset( $advanced_font_options[ $option_name ] ) ) {
continue;
}
// Get current block elements selector.
$block_default_selector = isset( $option_settings['css']['main'] ) ? $option_settings['css']['main'] : '';
$block_elements_css = isset( $option_settings['block_elements']['css'] ) ? $option_settings['block_elements']['css'] : array();
$block_elements_selector = isset( $block_elements_css['main'] ) ? $block_elements_css['main'] : $block_default_selector;
// Ensure block elements selector exist and not empty.
if ( empty( $block_elements_selector ) ) {
// Don't forget to disable block elements, so no sub toggles will be added.
$advanced_font_options[ $option_name ]['block_elements'] = false;
continue;
}
// Block element default settings will be used by the following sub toggles. Special
// for P sub toggle, we have to use existing font settings.
$existing_text_settings = $advanced_font_options[ $option_name ];
$block_elements_default_settings = array(
'line_height' => array(
'default' => '1em',
),
'font_size' => array(
'default' => '14px',
),
'toggle_slug' => $option_name,
);
// Check if current module is child or not. Then append default_on_child argument.
$is_child_module = isset( $this->type ) && 'child' === $this->type;
if ( $is_child_module ) {
// Tell font options to set default_on_child. Use fields prefix to avoid confusion.
$existing_text_settings['fields_default_on_child'] = true;
$block_elements_default_settings['fields_default_on_child'] = true;
}
// a. Paragraph - Convert main text as sub toggle P.
// Convert font settings with block_elements property to be sub toggle of P as
// default. So, we can avoid migration because no settings changed after we added
// block elements. We also need to set default line_height and font_size.
$advanced_font_options[ $option_name ] = array_merge(
$existing_text_settings,
array(
'line_height' => array(
'default' => floatval( et_get_option( 'body_font_height', '1.7' ) ) . 'em',
),
'font_size' => array(
'default' => absint( et_get_option( 'body_font_size', '14' ) ) . 'px',
),
'sub_toggle' => 'p',
)
);
// b. Link.
$link_element_selector = isset( $block_elements_css['link'] ) ? $block_elements_css['link'] : "{$block_elements_selector} a";
$advanced_font_options[ "{$option_name}_link" ] = array_merge(
$block_elements_default_settings,
array(
'label' => et_builder_i18n( 'Link' ),
'css' => array(
'main' => $link_element_selector,
),
'font_size' => array(
'default' => absint( et_get_option( 'body_font_size', '14' ) ) . 'px',
),
'sub_toggle' => 'a',
)
);
// c. Unordered List.
$ul_element_selector = et_()->array_get( $block_elements_css, 'ul', "{$block_elements_selector} ul" );
$ul_li_element_selector = et_()->array_get( $block_elements_css, 'ul_li', "{$ul_element_selector} li" );
$ul_item_indent_selector = et_()->array_get( $block_elements_css, 'ul_item_indent', $ul_element_selector );
$advanced_font_options[ "{$option_name}_ul" ] = array_merge(
$block_elements_default_settings,
array(
'label' => esc_html__( 'Unordered List', 'et_builder' ),
'css' => array(
'main' => $ul_li_element_selector,
'item_indent' => $ul_item_indent_selector,
),
'sub_toggle' => 'ul',
)
);
// d. Ordered List.
$ol_element_selector = et_()->array_get( $block_elements_css, 'ol', "{$block_elements_selector} ol" );
$ol_li_element_selector = et_()->array_get( $block_elements_css, 'ol_li', "{$ol_element_selector} li" );
$ol_item_indent_selector = et_()->array_get( $block_elements_css, 'ol_item_indent', $ol_element_selector );
$advanced_font_options[ "{$option_name}_ol" ] = array_merge(
$block_elements_default_settings,
array(
'label' => esc_html__( 'Ordered List', 'et_builder' ),
'css' => array(
'main' => $ol_li_element_selector,
'item_indent' => $ol_item_indent_selector,
),
'sub_toggle' => 'ol',
)
);
// e. Quote.
$quote_element_selector = isset( $block_elements_css['quote'] ) ? $block_elements_css['quote'] : "{$block_elements_selector} blockquote";
$advanced_font_options[ "{$option_name}_quote" ] = array_merge(
$block_elements_default_settings,
array(
'label' => esc_html__( 'Blockquote', 'et_builder' ),
'css' => array(
'main' => $quote_element_selector,
),
'sub_toggle' => 'quote',
)
);
}
}
$this->advanced_fields['fonts'] = $advanced_font_options;
$font_options_count = 0;
foreach ( $advanced_font_options as $option_name => $option_settings ) {
$font_options_count++;
$option_settings = wp_parse_args(
$option_settings,
array(
'label' => '',
'font_size' => array(),
'letter_spacing' => array(),
'font' => array(),
'text_align' => array(),
)
);
$toggle_disabled = isset( $option_settings['disable_toggle'] ) && $option_settings['disable_toggle'];
$tab_slug = isset( $option_settings['tab_slug'] ) ? $option_settings['tab_slug'] : 'advanced';
$toggle_slug = '';
if ( ! $toggle_disabled ) {
$toggle_slug = isset( $option_settings['toggle_slug'] ) ? $option_settings['toggle_slug'] : $option_name;
$sub_toggle = isset( $option_settings['sub_toggle'] ) ? $option_settings['sub_toggle'] : '';
if ( ! isset( $option_settings['toggle_slug'] ) ) {
$font_toggle = array(
$option_name => array(
'title' => sprintf( '%1$s %2$s', esc_html( $option_settings['label'] ), et_builder_i18n( 'Text' ) ),
'priority' => 50 + $font_options_count,
),
);
$this->_add_settings_modal_toggles( $tab_slug, $font_toggle );
}
}
if ( isset( $option_settings['header_level'] ) ) {
$additional_options[ "{$option_name}_level" ] = array(
'label' => sprintf( esc_html__( '%1$s Heading Level', 'et_builder' ), $option_settings['label'] ),
'description' => sprintf( esc_html__( 'Module %1$s are created using HTML headings. You can change the heading level for this module by choosing anything from H1 through H6. Higher heading levels are smaller and less significant.', 'et_builder' ), $option_settings['label'] ),
'type' => 'multiple_buttons',
'option_category' => 'font_option',
'options' => array(
'h1' => array(
'title' => 'H1',
'icon' => 'text-h1',
),
'h2' => array(
'title' => 'H2',
'icon' => 'text-h2',
),
'h3' => array(
'title' => 'H3',
'icon' => 'text-h3',
),
'h4' => array(
'title' => 'H4',
'icon' => 'text-h4',
),
'h5' => array(
'title' => 'H5',
'icon' => 'text-h5',
),
'h6' => array(
'title' => 'H6',
'icon' => 'text-h6',
),
),
'default' => isset( $option_settings['header_level']['default'] ) ? $option_settings['header_level']['default'] : 'h2',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'sub_toggle' => $sub_toggle,
'advanced_fields' => true,
);
if ( isset( $option_settings['header_level']['computed_affects'] ) ) {
$additional_options[ "{$option_name}_level" ]['computed_affects'] = $option_settings['header_level']['computed_affects'];
}
}
if ( ! isset( $option_settings['hide_font'] ) || ! $option_settings['hide_font'] ) {
$additional_options[ "{$option_name}_font" ] = wp_parse_args(
$option_settings['font'],
array(
'label' => sprintf( $i18n['font']['font']['label'], $option_settings['label'] ),
'description' => sprintf( $i18n['font']['font']['description'], $option_settings['label'] ),
'type' => 'font',
'group_label' => et_core_esc_previously( $option_settings['label'] ),
'option_category' => 'font_option',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'sub_toggle' => $sub_toggle,
'mobile_options' => true,
)
);
// add reference to the obsolete "all caps" option if needed.
if ( isset( $option_settings['use_all_caps'] ) && $option_settings['use_all_caps'] ) {
$additional_options[ "{$option_name}_font" ]['attributes'] = array( 'data-old-option-ref' => "{$option_name}_all_caps" );
}
// set the depends_show_if parameter if needed.
if ( isset( $option_settings['depends_show_if'] ) ) {
$additional_options[ "{$option_name}_font" ]['depends_show_if'] = $option_settings['depends_show_if'];
}
// Set default font settings.
if ( ! empty( $option_settings['font']['default'] ) ) {
$additional_options[ "{$option_name}_font" ]['default'] = $option_settings['font']['default'];
}
// Set default on child font settings.
if ( ! empty( $option_settings['fields_default_on_child'] ) ) {
$additional_options[ "{$option_name}_font" ]['default_on_child'] = true;
}
}
if ( ! isset( $option_settings['hide_text_align'] ) || ! $option_settings['hide_text_align'] ) {
$additional_options[ "{$option_name}_text_align" ] = wp_parse_args(
$option_settings['text_align'],
array(
'label' => sprintf( $i18n['font']['text_align']['label'], $option_settings['label'] ),
'description' => sprintf( $i18n['font']['text_align']['description'], $option_settings['label'] ),
'type' => 'text_align',
'option_category' => 'layout',
'options' => et_builder_get_text_orientation_options( array( 'justified' ), array( 'justify' => 'Justified' ) ),
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'sub_toggle' => $sub_toggle,
'advanced_fields' => true,
'mobile_options' => true,
)
);
// Set default on child font settings.
if ( ! empty( $option_settings['fields_default_on_child'] ) ) {
$additional_options[ "{$option_name}_text_align" ]['default_on_child'] = true;
}
}
if ( ! isset( $option_settings['hide_text_color'] ) || ! $option_settings['hide_text_color'] ) {
$label = et_()->array_get( $option_settings, 'text_color.label', false )
? $option_settings['text_color']['label']
: sprintf( $i18n['font']['color']['label'], $option_settings['label'] );
$additional_options[ "{$option_name}_text_color" ] = array(
'label' => $label,
'description' => sprintf( $i18n['font']['color']['description'], $option_settings['label'] ),
'type' => 'color-alpha',
'option_category' => 'font_option',
'custom_color' => true,
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'sub_toggle' => $sub_toggle,
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
);
// add reference to the obsolete color option if needed.
if ( self::$_->array_get( $option_settings, 'text_color.old_option_ref' ) ) {
$additional_options[ "{$option_name}_text_color" ]['attributes'] = array( 'data-old-option-ref' => "{$option_settings['text_color']['old_option_ref']}" );
}
// set default value if defined.
if ( self::$_->array_get( $option_settings, 'text_color.default' ) ) {
$additional_options[ "{$option_name}_text_color" ]['default'] = $option_settings['text_color']['default'];
}
// set the depends_show_if parameter if needed.
if ( isset( $option_settings['depends_show_if'] ) ) {
$additional_options[ "{$option_name}_text_color" ]['depends_show_if'] = $option_settings['depends_show_if'];
}
// Set default on child font settings.
if ( ! empty( $option_settings['fields_default_on_child'] ) ) {
$additional_options[ "{$option_name}_text_color" ]['default_on_child'] = true;
}
}
if ( ! isset( $option_settings['hide_font_size'] ) || ! $option_settings['hide_font_size'] ) {
$additional_options[ "{$option_name}_font_size" ] = wp_parse_args(
$option_settings['font_size'],
array(
'label' => sprintf( $i18n['font']['size']['label'], $option_settings['label'] ),
'description' => sprintf( $i18n['font']['size']['description'], $option_settings['label'] ),
'type' => 'range',
'option_category' => 'font_option',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'sub_toggle' => $sub_toggle,
'allowed_units' => array( '%', 'em', 'rem', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'ex', 'vh', 'vw' ),
'default_unit' => 'px',
'mobile_options' => true,
'sticky' => true,
'range_settings' => array(
'min' => '1',
'max' => '100',
'step' => '1',
),
'hover' => 'tabs',
)
);
// set the depends_show_if parameter if needed.
if ( isset( $option_settings['depends_show_if'] ) ) {
$additional_options[ "{$option_name}_font_size" ]['depends_show_if'] = $option_settings['depends_show_if'];
}
if ( isset( $option_settings['header_level'] ) ) {
$header_level_default = isset( $option_settings['header_level']['default'] ) ? $option_settings['header_level']['default'] : 'h2';
$additional_options[ "{$option_name}_font_size" ]['default_value_depends'] = "{$option_name}_level";
$additional_options[ "{$option_name}_font_size" ]['default_values_mapping'] = array(
'h1' => '30px',
'h2' => '26px',
'h3' => '22px',
'h4' => '18px',
'h5' => '16px',
'h6' => '14px',
);
// remove default font-size for default header level to use option default.
unset( $additional_options[ "{$option_name}_font_size" ]['default_values_mapping'][ $header_level_default ] );
}
// Set default on child font settings.
if ( ! empty( $option_settings['fields_default_on_child'] ) ) {
$additional_options[ "{$option_name}_font_size" ]['default_on_child'] = true;
}
}
if ( ! isset( $option_settings['hide_letter_spacing'] ) || ! $option_settings['hide_letter_spacing'] ) {
$additional_options[ "{$option_name}_letter_spacing" ] = wp_parse_args(
$option_settings['letter_spacing'],
array(
'label' => sprintf( $i18n['font']['letter_spacing']['label'], $option_settings['label'] ),
'description' => sprintf( $i18n['font']['letter_spacing']['description'], $option_settings['label'] ),
'type' => 'range',
'mobile_options' => true,
'sticky' => true,
'option_category' => 'font_option',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'sub_toggle' => $sub_toggle,
'default' => '0px',
'default_unit' => 'px',
'allowed_units' => array( 'em', 'rem', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'ex', 'vh', 'vw' ),
'range_settings' => array(
'min' => '0',
'max' => '100',
'step' => '1',
),
'hover' => 'tabs',
)
);
// set the depends_show_if parameter if needed.
if ( isset( $option_settings['depends_show_if'] ) ) {
$additional_options[ "{$option_name}_letter_spacing" ]['depends_show_if'] = $option_settings['depends_show_if'];
}
// Set default on child font settings.
if ( ! empty( $option_settings['fields_default_on_child'] ) ) {
$additional_options[ "{$option_name}_letter_spacing" ]['default_on_child'] = true;
}
}
if ( ! isset( $option_settings['hide_line_height'] ) || ! $option_settings['hide_line_height'] ) {
$default_option_line_height = array(
'label' => sprintf( $i18n['font']['line_height']['label'], $option_settings['label'] ),
'description' => sprintf( $i18n['font']['line_height']['description'], $option_settings['label'] ),
'type' => 'range',
'mobile_options' => true,
'sticky' => true,
'option_category' => 'font_option',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'sub_toggle' => $sub_toggle,
'default_unit' => 'em',
'allowed_units' => array( '%', 'em', 'rem', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'ex', 'vh', 'vw' ),
'range_settings' => array(
'min' => '1',
'max' => '3',
'step' => '0.1',
),
'hover' => 'tabs',
);
if ( isset( $option_settings['line_height'] ) ) {
$additional_options[ "{$option_name}_line_height" ] = wp_parse_args(
$option_settings['line_height'],
$default_option_line_height
);
} else {
$additional_options[ "{$option_name}_line_height" ] = $default_option_line_height;
}
// set the depends_show_if parameter if needed.
if ( isset( $option_settings['depends_show_if'] ) ) {
$additional_options[ "{$option_name}_line_height" ]['depends_show_if'] = $option_settings['depends_show_if'];
}
// Set default on child font settings.
if ( ! empty( $option_settings['fields_default_on_child'] ) ) {
$additional_options[ "{$option_name}_line_height" ]['default_on_child'] = true;
}
}
// Add text-shadow to font options.
if ( ! isset( $option_settings['hide_text_shadow'] ) || ! $option_settings['hide_text_shadow'] ) {
$option = $this->text_shadow->get_fields(
array(
// Don't use an additional label for 'text' or else we'll end up with 'Text Text Shadow....'.
'label' => 'text' === $option_name ? '' : $option_settings['label'],
'prefix' => $option_name,
'option_category' => 'font_option',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'sub_toggle' => $sub_toggle,
'mobile_options' => true,
)
);
$additional_options = array_merge( $additional_options, $option );
};
// The below option is obsolete. This code is for backward compatibility.
if ( isset( $option_settings['use_all_caps'] ) && $option_settings['use_all_caps'] ) {
$additional_options[ "{$option_name}_all_caps" ] = array(
'type' => 'hidden',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'sub_toggle' => $sub_toggle,
);
}
// Set options priority if it's exist and not empty. Mostly used to push a setting to
// the top of font settings. For example: reorder Text Color to the top of font settings.
if ( isset( $option_settings['options_priority'] ) && is_array( $option_settings['options_priority'] ) ) {
$options_priority = ! empty( $option_settings['options_priority'] ) ? $option_settings['options_priority'] : array();
$temporary_options = array();
foreach ( $options_priority as $option_key => $option_priority ) {
// Ensure the target is exist before adding priority.
if ( isset( $additional_options[ $option_key ] ) ) {
$additional_options[ $option_key ]['priority'] = $option_priority;
// Keep it on temporary options and remove it from additional options.
// It's needed because priority doesn't work for font settings with no
// sub toggle. Basically, we will reorder the array element of font
// settings here to make it works.
$temporary_options[ $option_key ] = $additional_options[ $option_key ];
unset( $additional_options[ $option_key ] );
}
}
// Merge temporary options with additional options.
$additional_options = array_merge( $temporary_options, $additional_options );
}
if ( isset( $option_settings['block_elements'] ) && is_array( $option_settings['block_elements'] ) ) {
// Block Elements - 2. Set sub toggles for block elements.
// Add p, a, ul, ol, and quote as sub toggle of current font settings. We also
// need to add tabbed_subtoggles property there.
$block_elements = array(
'p' => array(
'name' => 'P',
'icon' => 'text-left',
),
'a' => array(
'name' => 'A',
'icon' => 'text-link',
),
'ul' => array(
'name' => 'UL',
'icon' => 'list',
),
'ol' => array(
'name' => 'OL',
'icon' => 'numbered-list',
),
'quote' => array(
'name' => 'QUOTE',
'icon' => 'text-quote',
),
);
// Tabbed toggle & BB icons support status.
$tabbed_subtoggles = isset( $option_settings['block_elements']['tabbed_subtoggles'] ) ? $option_settings['block_elements']['tabbed_subtoggles'] : false;
$bb_icons_support = isset( $option_settings['block_elements']['bb_icons_support'] ) ? $option_settings['block_elements']['bb_icons_support'] : false;
$this->_add_settings_modal_sub_toggles( $tab_slug, $toggle_slug, $block_elements, $tabbed_subtoggles, $bb_icons_support );
// Block Elements - 3. Set additional options for ul/ol/qoute sub toggles.
// a. UL - Type, Position, and Indent.
$additional_options[ "{$option_name}_ul_type" ] = array(
'label' => esc_html__( 'Unordered List Style Type', 'et_builder' ),
'description' => esc_html__( 'This setting adjusts the shape of the bullet point that begins each list item.', 'et_builder' ),
'type' => 'select',
'option_category' => 'configuration',
'options' => array(
'disc' => et_builder_i18n( 'Disc' ),
'circle' => et_builder_i18n( 'Circle' ),
'square' => et_builder_i18n( 'Square' ),
'none' => et_builder_i18n( 'None' ),
),
'priority' => 80,
'default' => 'disc',
'default_on_front' => '',
'tab_slug' => 'advanced',
'toggle_slug' => $option_name,
'sub_toggle' => 'ul',
'mobile_options' => true,
);
$additional_options[ "{$option_name}_ul_position" ] = array(
'label' => esc_html__( 'Unordered List Style Position', 'et_builder' ),
'description' => esc_html__( 'The bullet point that begins each list item can be placed either inside or outside the parent list wrapper. Placing list items inside will indent them further within the list.', 'et_builder' ),
'type' => 'select',
'option_category' => 'configuration',
'options' => array(
'outside' => et_builder_i18n( 'Outside' ),
'inside' => et_builder_i18n( 'Inside' ),
),
'priority' => 85,
'default' => 'outside',
'default_on_front' => '',
'tab_slug' => 'advanced',
'toggle_slug' => $option_name,
'sub_toggle' => 'ul',
'mobile_options' => true,
);
$additional_options[ "{$option_name}_ul_item_indent" ] = array(
'label' => esc_html__( 'Unordered List Item Indent', 'et_builder' ),
'description' => esc_html__( 'Increasing indentation will push list items further towards the center of the text content, giving the list more visible separation from the the rest of the text.', 'et_builder' ),
'type' => 'range',
'option_category' => 'configuration',
'tab_slug' => 'advanced',
'toggle_slug' => $option_name,
'sub_toggle' => 'ul',
'priority' => 90,
'allowed_units' => array( '%', 'em', 'rem', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'ex', 'vh', 'vw' ),
'default' => '0px',
'default_unit' => 'px',
'default_on_front' => '',
'range_settings' => array(
'min' => '0',
'max' => '100',
'step' => '1',
),
'mobile_options' => true,
);
// b. OL - Type, Position, and Indent.
$additional_options[ "{$option_name}_ol_type" ] = array(
'label' => esc_html__( 'Ordered List Style Type', 'et_builder' ),
'description' => esc_html__( 'Here you can choose which types of characters are used to distinguish between each item in the ordered list.', 'et_builder' ),
'type' => 'select',
'option_category' => 'configuration',
'options' => array(
'decimal' => 'decimal',
'armenian' => 'armenian',
'cjk-ideographic' => 'cjk-ideographic',
'decimal-leading-zero' => 'decimal-leading-zero',
'georgian' => 'georgian',
'hebrew' => 'hebrew',
'hiragana' => 'hiragana',
'hiragana-iroha' => 'hiragana-iroha',
'katakana' => 'katakana',
'katakana-iroha' => 'katakana-iroha',
'lower-alpha' => 'lower-alpha',
'lower-greek' => 'lower-greek',
'lower-latin' => 'lower-latin',
'lower-roman' => 'lower-roman',
'upper-alpha' => 'upper-alpha',
'upper-greek' => 'upper-greek',
'upper-latin' => 'upper-latin',
'upper-roman' => 'upper-roman',
'none' => 'none',
),
'priority' => 80,
'default' => 'decimal',
'default_on_front' => '',
'tab_slug' => 'advanced',
'toggle_slug' => $option_name,
'sub_toggle' => 'ol',
'mobile_options' => true,
);
$additional_options[ "{$option_name}_ol_position" ] = array(
'label' => esc_html__( 'Ordered List Style Position', 'et_builder' ),
'description' => esc_html__( 'The characters that begins each list item can be placed either inside or outside the parent list wrapper. Placing list items inside will indent them further within the list.', 'et_builder' ),
'type' => 'select',
'option_category' => 'configuration',
'options' => array(
'inside' => et_builder_i18n( 'Inside' ),
'outside' => et_builder_i18n( 'Outside' ),
),
'priority' => 85,
'default' => 'inside',
'default_on_front' => '',
'tab_slug' => 'advanced',
'toggle_slug' => $option_name,
'sub_toggle' => 'ol',
'mobile_options' => true,
);
$additional_options[ "{$option_name}_ol_item_indent" ] = array(
'label' => esc_html__( 'Ordered List Item Indent', 'et_builder' ),
'description' => esc_html__( 'Increasing indentation will push list items further towards the center of the text content, giving the list more visible separation from the the rest of the text.', 'et_builder' ),
'type' => 'range',
'option_category' => 'configuration',
'tab_slug' => 'advanced',
'toggle_slug' => $option_name,
'sub_toggle' => 'ol',
'priority' => 90,
'allowed_units' => array( '%', 'em', 'rem', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'ex', 'vh', 'vw' ),
'default' => '0px',
'default_unit' => 'px',
'default_on_front' => '',
'range_settings' => array(
'min' => '0',
'max' => '100',
'step' => '1',
),
'mobile_options' => true,
);
// c. Quote - Border Weight and Border Color.
$additional_options[ "{$option_name}_quote_border_weight" ] = array(
'label' => esc_html__( 'Blockquote Border Weight', 'et_builder' ),
'description' => esc_html__( 'Block quotes are given a border to separate them from normal text. You can increase or decrease the size of that border using this setting.', 'et_builder' ),
'type' => 'range',
'option_category' => 'configuration',
'tab_slug' => 'advanced',
'toggle_slug' => $option_name,
'sub_toggle' => 'quote',
'priority' => 85,
'allowed_units' => array( 'em', 'rem', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'ex', 'vh', 'vw' ),
'default' => '5px',
'default_unit' => 'px',
'default_on_front' => '',
'range_settings' => array(
'min' => '0',
'max' => '100',
'step' => '1',
),
'mobile_options' => true,
'sticky' => true,
'hover' => 'tabs',
);
$additional_options[ "{$option_name}_quote_border_color" ] = array(
'label' => esc_html__( 'Blockquote Border Color', 'et_builder' ),
'description' => esc_html__( 'Block quotes are given a border to separate them from normal text. Pick a color to use for that border.', 'et_builder' ),
'type' => 'color-alpha',
'option_category' => 'configuration',
'custom_color' => true,
'tab_slug' => 'advanced',
'toggle_slug' => $option_name,
'sub_toggle' => 'quote',
'field_template' => 'color',
'priority' => 90,
'mobile_options' => true,
'sticky' => true,
'hover' => 'tabs',
);
// Set default on child font settings.
if ( ! empty( $option_settings['fields_default_on_child'] ) ) {
$additional_options[ "{$option_name}_ul_type" ]['default_on_child'] = true;
$additional_options[ "{$option_name}_ul_position" ]['default_on_child'] = true;
$additional_options[ "{$option_name}_ul_item_indent" ]['default_on_child'] = true;
$additional_options[ "{$option_name}_ol_type" ]['default_on_child'] = true;
$additional_options[ "{$option_name}_ol_position" ]['default_on_child'] = true;
$additional_options[ "{$option_name}_ol_item_indent" ]['default_on_child'] = true;
$additional_options[ "{$option_name}_quote_border_weight" ]['default_on_child'] = true;
$additional_options[ "{$option_name}_quote_border_color" ]['default_on_child'] = true;
}
}
}
$this->_additional_fields_options = array_merge( $this->_additional_fields_options, $additional_options );
}
/**
* Add background option fields.
*
* @since 3.23 Add responsive settings for background settings.
*/
protected function _add_background_fields() {
// Background fields are added by default if module has partial or full VB support.
if ( $this->has_vb_support() ) {
$this->advanced_fields['background'] = self::$_->array_get( $this->advanced_fields, 'background', array() );
} elseif ( ! $this->has_advanced_fields ) {
// Disable if module doesn't set advanced_fields property and has no VB support.
return;
}
// Background settings have to be array.
if ( ! is_array( self::$_->array_get( $this->advanced_fields, 'background' ) ) ) {
return;
}
$toggle_disabled = self::$_->array_get( $this->advanced_fields, 'background.settings.disable_toggle', false );
$tab_slug = self::$_->array_get( $this->advanced_fields, 'background.settings.tab_slug', 'general' );
$toggle_slug = '';
if ( ! $toggle_disabled ) {
$toggle_slug = self::$_->array_get( $this->advanced_fields, 'background.settings.toggle_slug', 'background' );
$background_toggle = array(
'background' => array(
'title' => et_builder_i18n( 'Background' ),
'priority' => 80,
),
);
$this->_add_settings_modal_toggles( $tab_slug, $background_toggle );
}
$background_field_name = 'background';
// Possible values for use_* attributes: true, false, or 'fields_only'.
$defaults = array(
'has_background_color_toggle' => false,
'use_background_color' => true,
'use_background_color_gradient' => true,
'use_background_image' => true,
'use_background_image_parallax' => true,
'use_background_video' => true,
'use_background_color_reset' => true,
);
$this->advanced_fields['background'] = wp_parse_args( $this->advanced_fields['background'], $defaults );
$additional_options = array();
if ( $this->advanced_fields['background']['use_background_color'] ) {
$additional_options = array_merge(
$additional_options,
$this->generate_background_options( 'background', 'color', $tab_slug, $toggle_slug, null )
);
}
// Use background color toggle was added on pre color-alpha era. Added for backward
// compatibility. This option's output is printed manually on render().
if ( $this->advanced_fields['background']['has_background_color_toggle'] ) {
$additional_options['use_background_color'] = self::background_field_template(
'use_color',
array(
'label' => esc_html__( 'Use Background Color', 'et_builder' ),
'type' => 'yes_no_button',
'option_category' => 'color_option',
'options' => array(
'on' => et_builder_i18n( 'Yes' ),
'off' => et_builder_i18n( 'No' ),
),
'affects' => array(
'background_color',
),
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'description' => esc_html__( 'Here you can choose whether background color setting above should be used or not.', 'et_builder' ),
'mobile_options' => true,
'sticky' => true,
'hover' => 'tabs',
)
);
}
if ( $this->advanced_fields['background']['use_background_color_gradient'] ) {
$additional_options = array_merge(
$additional_options,
$this->generate_background_options( 'background', 'gradient', $tab_slug, $toggle_slug, null )
);
}
if ( $this->advanced_fields['background']['use_background_image'] ) {
$background_options_to_skip = ! $this->advanced_fields['background']['use_background_image_parallax'] ? array( 'parallax' ) : array();
$additional_options = array_merge(
$additional_options,
$this->generate_background_options( 'background', 'image', $tab_slug, $toggle_slug, null, $background_options_to_skip )
);
}
if ( $this->advanced_fields['background']['use_background_video'] ) {
$additional_options = array_merge(
$additional_options,
$this->generate_background_options( 'background', 'video', $tab_slug, $toggle_slug, null )
);
}
// Allow module to configure specific options.
$background_options = self::$_->array_get( $this->advanced_fields, 'background.options', false );
if ( $background_options ) {
foreach ( $background_options as $option_slug => $options ) {
if ( ! is_array( $options ) ) {
continue;
}
foreach ( $options as $option_name => $option_value ) {
$additional_options[ $option_slug ][ $option_name ] = $option_value;
}
}
}
$this->_additional_fields_options = array_merge( $this->_additional_fields_options, $additional_options );
}
/**
* Add text option fields.
*
* @since 3.23 Add responsive settings for text orientation and layout settings.
*/
protected function _add_text_fields() {
// Text fields are added by default if module has partial or full VB support.
if ( $this->has_vb_support() ) {
$this->advanced_fields['text'] = self::$_->array_get( $this->advanced_fields, 'text', array() );
} elseif ( ! $this->has_advanced_fields ) {
// Disable if module doesn't set advanced_fields property and has no VB support.
return;
}
// Text settings have to be array.
if ( ! is_array( self::$_->array_get( $this->advanced_fields, 'text' ) ) ) {
return;
}
$text_settings = $this->advanced_fields['text'];
$tab_slug = isset( $text_settings['tab_slug'] ) ? $text_settings['tab_slug'] : 'advanced';
$toggle_slug = isset( $text_settings['toggle_slug'] ) ? $text_settings['toggle_slug'] : 'text';
$sub_toggle = isset( $text_settings['sub_toggle'] ) ? $text_settings['sub_toggle'] : '';
$orientation_exclude_options = isset( $text_settings['text_orientation'] ) && isset( $text_settings['text_orientation']['exclude_options'] ) ? $text_settings['text_orientation']['exclude_options'] : array();
// Make sure we can exclude text_orientation from Advanced/Text.
$setting_defaults = array(
'use_text_orientation' => true,
'use_background_layout' => false,
);
$text_settings = wp_parse_args( $text_settings, $setting_defaults );
$this->_add_settings_modal_toggles(
$tab_slug,
array(
$toggle_slug => array(
'title' => et_builder_i18n( 'Text' ),
'priority' => 49,
),
)
);
$additional_options = array();
if ( $text_settings['use_text_orientation'] ) {
$default_on_front = self::$_->array_get( $text_settings, 'options.text_orientation.default_on_front', '' );
$additional_options = array(
'text_orientation' => wp_parse_args(
self::$_->array_get( $text_settings, 'options.text_orientation', array() ),
array(
'label' => esc_html__( 'Text Alignment', 'et_builder' ),
'type' => 'text_align',
'option_category' => 'layout',
'options' => et_builder_get_text_orientation_options( $orientation_exclude_options ),
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'description' => esc_html__( 'This controls how your text is aligned within the module.', 'et_builder' ),
'advanced_fields' => true,
'default' => self::$_->array_get( $text_settings, 'options.text_orientation.default', $default_on_front ),
'mobile_options' => true,
)
),
);
if ( '' !== $sub_toggle ) {
$additional_options['text_orientation']['sub_toggle'] = $sub_toggle;
}
}
// Background layout works by setting text to light/dark color. This was added before text
// color has its own colorpicker as a simple mechanism for picking color.
// New module should not use this option. This is kept for backward compatibility.
if ( $text_settings['use_background_layout'] ) {
$additional_options['background_layout'] = array(
'label' => esc_html__( 'Text Color', 'et_builder' ),
'type' => 'select',
'option_category' => 'color_option',
'options' => array(
'dark' => et_builder_i18n( 'Light' ),
'light' => et_builder_i18n( 'Dark' ),
),
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'hover' => 'tabs',
'description' => esc_html__( 'Here you can choose whether your text should be light or dark. If you are working with a dark background, then your text should be light. If your background is light, then your text should be set to dark.', 'et_builder' ),
'mobile_options' => true,
'sticky' => true,
);
if ( '' !== $sub_toggle ) {
$additional_options['background_layout']['sub_toggle'] = $sub_toggle;
}
}
// Allow module to configure specific options.
if ( isset( $text_settings['options'] ) && is_array( $text_settings['options'] ) ) {
foreach ( $text_settings['options'] as $option_slug => $options ) {
if ( ! is_array( $options ) ) {
continue;
}
foreach ( $options as $option_name => $option_value ) {
if ( isset( $additional_options[ $option_slug ] ) ) {
$additional_options[ $option_slug ][ $option_name ] = $option_value;
}
}
}
}
$this->_additional_fields_options = array_merge( $this->_additional_fields_options, $additional_options );
}
/**
* Add Border & Border Radius fields to each module. Default borders option are added on with
* Borders fields group on Design tab. However, module can add more borders field by adding
* more settings on $this->advanced_fields['borders']
*
* @since 3.1
*
* {@internal
* border options are initially defined via _add_additional_border_fields() method and adding
* more border options require overwriting it on module's class. This is repetitive so
* the fields registration mechanics were simplified mimicing advanced fonts field mechanism.}
*/
protected function _add_borders_fields() {
// Disable if module doesn't set advanced_fields property and has no VB support.
if ( ! $this->has_vb_support() && ! $this->has_advanced_fields ) {
return;
}
// Get borders settings. Fallback to default if needed. Borders are added to all modules by default
// unless the module explicitly disabled it
// Backward compatibility. Use `border` settings as default if exist.
$legacy_border = self::$_->array_get( $this->advanced_fields, 'border', array() );
$borders_fields = self::$_->array_get(
$this->advanced_fields,
'borders',
array(
'default' => $legacy_border,
)
);
// Borders settings have to be array.
if ( ! is_array( $borders_fields ) ) {
return;
}
$i18n =& self::$i18n;
if ( ! isset( $i18n['border'] ) ) {
// phpcs:disable WordPress.WP.I18n.MissingTranslatorsComment
$i18n['border'] = array(
'title' => esc_html__( 'Border', 'et_builder' ),
);
// phpcs:enable
}
// Loop border settings, enable multiple border fields declaration in one place.
foreach ( $borders_fields as $border_fields_name => $border_fields ) {
// Enable module to disable border options by setting it to false.
if ( false === $border_fields ) {
continue;
}
// Make sure that border fields has minimum attribute required.
$border_fields_defaults = array(
'tab_slug' => 'advanced',
'toggle_slug' => 'border',
);
$border_fields = wp_parse_args( $border_fields, $border_fields_defaults );
// Check for default border options.
$is_default_border_options = 'default' === $border_fields_name;
if ( $is_default_border_options ) {
// Default border fields doesn't have toggle for itself, thus register new toggle.
$this->_add_settings_modal_toggles(
$border_fields['tab_slug'],
array(
$border_fields['toggle_slug'] => array(
'title' => $i18n['border']['title'],
'priority' => 95,
),
)
);
}
// Add suffix to border fields settings.
$suffix = $is_default_border_options ? '' : "_{$border_fields_name}";
$border_fields['suffix'] = $suffix;
// Assign CSS setting to advanced options.
if ( isset( $border_fields['css'] ) ) {
$this->advanced_fields[ "border{$suffix}" ]['css'] = $border_fields['css'];
}
// Add border fields to advanced_fields. Each border fields (style + radii) has its own attribute
// registered on $this->advanced_fields.
self::$_->array_set( $this->advanced_fields, "border{$suffix}", $border_fields );
$this->_additional_fields_options = array_merge(
$this->_additional_fields_options,
ET_Builder_Module_Fields_Factory::get( 'Border' )->get_fields( $border_fields )
);
// Add module defined fields that needs to be added after existing border options.
if ( isset( $border_fields['fields_after'] ) ) {
$this->_additional_fields_options = array_merge(
$this->_additional_fields_options,
$border_fields['fields_after']
);
}
// Loop radii and styles and add fields to advanced_fields.
foreach ( array( 'border_radii', 'border_styles' ) as $border_key ) {
$border_key_name = $border_key . $suffix;
if ( isset( $this->advanced_fields[ "border{$suffix}" ][ $border_key_name ] ) ) {
// Backward compatibility. Properly handle existing 3rd party module that
// directly defines border via direct $this->advanced_fields["border{$suffix}"].
$this->advanced_fields[ "border{$suffix}" ][ $border_key_name ] = array_merge(
$this->advanced_fields[ "border{$suffix}" ][ $border_key_name ],
$this->_additional_fields_options[ $border_key_name ]
);
$message = "You're Doing It Wrong! You shouldn't define border settings in 'advanced_fields' directly. All the Border settings should be defined via provided API";
et_debug( $message );
}
// Border used to rely on $this->advanced_fields complete configuration for
// rendering. Since option template update, border style rendering fetches
// border setting based on rebuilt fields on demand for performance reason.
}
}
if ( method_exists( $this, '_add_additional_border_fields' ) ) {
// Backwards compatibility should go after all the fields added to emulate behavior of old version.
$this->_add_additional_border_fields();
$message = "You're Doing It Wrong! '_add_additional_border_fields' is deprecated. All the Border settings should be defined via provided API";
et_debug( $message );
}
}
/**
* Add transform fields.
*/
protected function _add_transforms_fields() {
$i18n =& self::$i18n;
if ( ! isset( $i18n['transforms'] ) ) {
// phpcs:disable WordPress.WP.I18n.MissingTranslatorsComment
$i18n['transforms'] = array(
'title' => esc_html__( 'Transform', 'et_builder' ),
);
// phpcs:enable
}
$this->advanced_fields['transform'] = self::$_->array_get( $this->advanced_fields, 'transform', array() );
// Transforms Disabled.
if ( false === $this->advanced_fields['transform'] ) {
return;
}
// Transforms settings have to be array.
if ( ! is_array( $this->advanced_fields['transform'] ) ) {
return;
}
$this->settings_modal_toggles['advanced']['toggles']['transform'] = array(
'title' => $i18n['transforms']['title'],
'priority' => 109,
);
$this->_additional_fields_options = array_merge(
$this->_additional_fields_options,
/** Get transform fields. @see ET_Builder_Module_Field_Transform::get_fields() */
ET_Builder_Module_Fields_Factory::get( 'Transform' )->get_fields()
);
}
/**
* Add sizing option fields.
*/
protected function _add_sizing_fields() {
// Maybe someone did overwrite this function.
$this->_add_max_width_fields();
$additional_options = array();
$features = array(
'max_width' => 'MaxWidth',
'height' => 'Height',
);
foreach ( $features as $name => $fields_name ) {
if ( $this->has_vb_support() ) {
$this->advanced_fields[ $name ] = self::$_->array_get( $this->advanced_fields, $name, array() );
} elseif ( ! $this->has_advanced_fields ) {
return;
}
if ( ! is_array( self::$_->array_get( $this->advanced_fields, $name ) ) ) {
return;
}
$extra = self::$_->array_get( $this->advanced_fields[ $name ], 'extra', array() );
$fields = array_merge( array( '' => $this->advanced_fields[ $name ] ), $extra );
foreach ( $fields as $prefix => $settings ) {
$prefix = et_builder_add_prefix( $prefix, '' );
$tab_slug = isset( $settings['tab_slug'] ) ? $settings['tab_slug'] : 'advanced';
$toggle_slug = isset( $settings['toggle_slug'] ) ? $settings['toggle_slug'] : 'width';
$toggle_title = isset( $settings['toggle_title'] ) ? $settings['toggle_title'] : et_builder_i18n( 'Sizing' );
$toggle_priority = isset( $settings['toggle_priority'] ) ? $settings['toggle_priority'] : 80;
$settings['prefix'] = $prefix;
$this->_add_settings_modal_toggles(
$tab_slug,
array(
$toggle_slug => array(
'title' => $toggle_title,
'priority' => $toggle_priority,
),
)
);
$additional_options = array_merge(
$additional_options,
ET_Builder_Module_Fields_Factory::get( $fields_name )->get_fields( $settings )
);
// Allow module to configure specific options.
if ( isset( $settings['options'] ) && is_array( $settings['options'] ) ) {
foreach ( $settings['options'] as $option_slug => $options ) {
if ( ! is_array( $options ) ) {
continue;
}
foreach ( $options as $option_name => $option_value ) {
$additional_options[ $prefix . $option_slug ][ $option_name ] = $option_value;
}
}
}
}
$this->_additional_fields_options = array_merge( $this->_additional_fields_options, $additional_options );
}
}
// phpcs:ignore Generic.Commenting.DocComment -- Deprecated function.
/**
* @deprecated
*/
public function _add_max_width_fields() {
}
/**
* Add overflow option fields.
*/
protected function _add_overflow_fields() {
if ( is_array( self::$_->array_get( $this->advanced_fields, 'overflow', array() ) ) ) {
$default_overflow = self::$_->array_get( $this->advanced_fields, 'overflow.default', ET_Builder_Module_Helper_Overflow::OVERFLOW_DEFAULT );
$this->_additional_fields_options = array_merge(
$this->_additional_fields_options,
ET_Builder_Module_Fields_Factory::get( 'Overflow' )->get_fields( array( 'default' => $default_overflow ) )
);
}
}
/**
* Add display conditions option fields.
*/
protected function _add_display_conditions_fields() {
/**
* Filters "Display Conditions" option visibility to determine whether to add its field to the Visual Builder or not.
*
* Useful for displaying/hiding the option on the Visual Builder.
*
* @since ??
*
* @param boolean True to make the option visible on VB, False to make it hidden.
*/
$is_display_conditions_enabled = apply_filters( 'et_is_display_conditions_option_visible', true );
if ( ! $is_display_conditions_enabled ) {
return;
}
if ( is_array( et_()->array_get( $this->advanced_fields, 'display_conditions', array() ) ) ) {
$this->_additional_fields_options = array_merge(
$this->_additional_fields_options,
ET_Builder_Module_Fields_Factory::get( 'DisplayConditions' )->get_fields()
);
}
}
/**
* Return Scroll effects option fields.
*
* @return array
*/
public function get_scroll_effects_options() {
// cache translations.
$prefix = 'scroll_';
$i18n =& self::$i18n;
if ( ! isset( $i18n['motion'] ) ) {
// phpcs:disable WordPress.WP.I18n.MissingTranslatorsComment
$i18n['motion'] = array(
'vertical' => array(
'label' => __( 'Vertical Motion', 'et_builder' ),
'description' => __( 'Give this element vertical motion so that is moves faster or slower than the elements around it as the viewer scrolls through the page.', 'et_builder' ),
'startValueTitle' => __( 'Starting Offset', 'et_builder' ),
'middleValueTitle' => __( 'Mid Offset', 'et_builder' ),
'endValueTitle' => __( 'Ending Offset', 'et_builder' ),
),
'horizontal' => array(
'label' => __( 'Horizontal Motion', 'et_builder' ),
'description' => __( 'Give this element horizontal motion so that it slides left or right as the viewer scrolls through the page.', 'et_builder' ),
'startValueTitle' => __( 'Starting Offset', 'et_builder' ),
'middleValueTitle' => __( 'Mid Offset', 'et_builder' ),
'endValueTitle' => __( 'Ending Offset', 'et_builder' ),
),
'fade' => array(
'label' => __( 'Fading In and Out', 'et_builder' ),
'description' => __( 'Give this element an opacity effect so that it fades in and out as the viewer scrolls through the page.', 'et_builder' ),
'startValueTitle' => __( 'Starting Opacity', 'et_builder' ),
'middleValueTitle' => __( 'Mid Opacity', 'et_builder' ),
'endValueTitle' => __( 'Ending Opacity', 'et_builder' ),
),
'scaling' => array(
'label' => __( 'Scaling Up and Down', 'et_builder' ),
'description' => __( 'Give this element a scale effect so that it grows and shrinks as the viewer scrolls through the page.', 'et_builder' ),
'startValueTitle' => __( 'Starting Scale', 'et_builder' ),
'middleValueTitle' => __( 'Mid Scale', 'et_builder' ),
'endValueTitle' => __( 'Ending Scale', 'et_builder' ),
),
'rotating' => array(
'label' => __( 'Rotating', 'et_builder' ),
'description' => __( 'Give this element rotating motion so that it spins as the viewer scrolls through the page.', 'et_builder' ),
'startValueTitle' => __( 'Starting Rotation', 'et_builder' ),
'middleValueTitle' => __( 'Mid Rotation', 'et_builder' ),
'endValueTitle' => __( 'Ending Rotation', 'et_builder' ),
),
'blur' => array(
'label' => __( 'Blur', 'et_builder' ),
'description' => __( 'Give this element a blur effect so that it moves in and out of focus as the viewer scrolls through the page.', 'et_builder' ),
'startValueTitle' => __( 'Starting Blur', 'et_builder' ),
'middleValueTitle' => __( 'Mid Blur', 'et_builder' ),
'endValueTitle' => __( 'Ending Blur', 'et_builder' ),
),
);
// phpcs:enable
}
return array(
"${prefix}vertical_motion" => array(
'label' => $i18n['motion']['vertical']['label'],
'description' => $i18n['motion']['vertical']['description'],
'startValueTitle' => $i18n['motion']['vertical']['startValueTitle'],
'middleValueTitle' => $i18n['motion']['vertical']['middleValueTitle'],
'endValueTitle' => $i18n['motion']['vertical']['endValueTitle'],
'icon' => 'vertical-motion',
'resolver' => 'translateY',
'default' => '0|50|50|100|4|0|-4',
),
"{$prefix}horizontal_motion" => array(
'label' => $i18n['motion']['horizontal']['label'],
'description' => $i18n['motion']['horizontal']['description'],
'startValueTitle' => $i18n['motion']['horizontal']['startValueTitle'],
'middleValueTitle' => $i18n['motion']['horizontal']['middleValueTitle'],
'endValueTitle' => $i18n['motion']['horizontal']['endValueTitle'],
'icon' => 'horizontal-motion',
'resolver' => 'translateX',
'default' => '0|50|50|100|4|0|-4',
),
"{$prefix}fade" => array(
'label' => $i18n['motion']['fade']['label'],
'description' => $i18n['motion']['fade']['description'],
'startValueTitle' => $i18n['motion']['fade']['startValueTitle'],
'middleValueTitle' => $i18n['motion']['fade']['middleValueTitle'],
'endValueTitle' => $i18n['motion']['fade']['endValueTitle'],
'icon' => 'animation-fade',
'resolver' => 'opacity',
'default' => '0|50|50|100|0|100|100',
),
"{$prefix}scaling" => array(
'label' => $i18n['motion']['scaling']['label'],
'description' => $i18n['motion']['scaling']['description'],
'startValueTitle' => $i18n['motion']['scaling']['startValueTitle'],
'middleValueTitle' => $i18n['motion']['scaling']['middleValueTitle'],
'endValueTitle' => $i18n['motion']['scaling']['endValueTitle'],
'icon' => 'resize',
'resolver' => 'scale',
'default' => '0|50|50|100|70|100|100',
),
"{$prefix}rotating" => array(
'label' => $i18n['motion']['rotating']['label'],
'description' => $i18n['motion']['rotating']['description'],
'startValueTitle' => $i18n['motion']['rotating']['startValueTitle'],
'middleValueTitle' => $i18n['motion']['rotating']['middleValueTitle'],
'endValueTitle' => $i18n['motion']['rotating']['endValueTitle'],
'icon' => 'rotate',
'resolver' => 'rotate',
'default' => '0|50|50|100|90|0|0',
),
"{$prefix}blur" => array(
'label' => $i18n['motion']['blur']['label'],
'description' => $i18n['motion']['blur']['description'],
'startValueTitle' => $i18n['motion']['blur']['startValueTitle'],
'middleValueTitle' => $i18n['motion']['blur']['middleValueTitle'],
'endValueTitle' => $i18n['motion']['blur']['endValueTitle'],
'icon' => 'blur',
'resolver' => 'blur',
'default' => '0|40|60|100|10|0|0',
),
);
}
/**
* Add sticky fields to the additional fields options.
*
* @since 4.6.0
*
* @return void
*/
protected function _add_sticky_fields() {
if ( is_array( self::$_->array_get( $this->advanced_fields, 'sticky', array() ) ) ) {
$this->_additional_fields_options = array_merge(
$this->_additional_fields_options,
ET_Builder_Module_Fields_Factory::get( 'Sticky' )->get_fields(
array(
'module_slug' => $this->slug,
)
)
);
}
}
/**
* Add scroll effects option fields.
*/
protected function _add_scroll_effects_fields() {
if ( is_array( self::$_->array_get( $this->advanced_fields, 'scroll_effects', array() ) ) ) {
$this->_additional_fields_options = array_merge(
$this->_additional_fields_options,
ET_Builder_Module_Fields_Factory::get( 'Scroll' )->get_fields(
array(
'options' => $this->get_scroll_effects_options(),
'grid_support' => self::$_->array_get( $this->advanced_fields, 'scroll_effects.grid_support', 'no' ),
)
)
);
}
}
/**
* Add margin & padding option fields.
*
* @since 3.23 Add allowed CSS units for margin and padding.
*/
protected function _add_margin_padding_fields() {
// Margin-Padding fields are added by default if module has partial or full VB support.
if ( $this->has_vb_support() ) {
$this->advanced_fields['margin_padding'] = self::$_->array_get( $this->advanced_fields, 'margin_padding', array() );
} elseif ( ! $this->has_advanced_fields ) {
// Disable if module doesn't set advanced_fields property and has no VB support.
return;
}
// Margin settings have to be array.
if ( ! is_array( self::$_->array_get( $this->advanced_fields, 'margin_padding' ) ) ) {
return;
}
$additional_options = array();
$defaults = array(
'use_margin' => true,
'draggable_margin' => true,
'use_padding' => true,
'draggable_padding' => true,
);
$this->advanced_fields['margin_padding'] = wp_parse_args( $this->advanced_fields['margin_padding'], $defaults );
$tab_slug = isset( $this->advanced_fields['margin_padding']['tab_slug'] ) ? $this->advanced_fields['margin_padding']['tab_slug'] : 'advanced';
$toggle_disabled = isset( $this->advanced_fields['margin_padding']['disable_toggle'] ) && $this->advanced_fields['margin_padding']['disable_toggle'];
$toggle_slug = isset( $this->advanced_fields['margin_padding']['toggle_slug'] ) ? $this->advanced_fields['margin_padding']['toggle_slug'] : 'margin_padding';
$sub_toggle = et_()->array_get( $this->advanced_fields['margin_padding'], 'sub_toggle', null );
$i18n =& self::$i18n;
if ( ! isset( $i18n['margin'] ) ) {
// phpcs:disable WordPress.WP.I18n.MissingTranslatorsComment
$i18n['margin'] = array(
'toggle' => array(
'title' => esc_html__( 'Spacing', 'et_builder' ),
),
'margin' => array(
'label' => esc_html__( 'Margin', 'et_builder' ),
'description' => esc_html__( 'Margin adds extra space to the outside of the element, increasing the distance between the element and other items on the page.', 'et_builder' ),
),
'padding' => array(
'label' => esc_html__( 'Padding', 'et_builder' ),
'description' => esc_html__( 'Padding adds extra space to the inside of the element, increasing the distance between the edge of the element and its inner contents.', 'et_builder' ),
),
);
// phpcs:enable
}
if ( ! $toggle_disabled ) {
$margin_toggle = array(
$toggle_slug => array(
'title' => $i18n['margin']['toggle']['title'],
'priority' => 90,
),
);
$this->_add_settings_modal_toggles( $tab_slug, $margin_toggle );
}
if ( $this->advanced_fields['margin_padding']['use_margin'] ) {
$additional_options['custom_margin'] = array(
'label' => $i18n['margin']['margin']['label'],
'description' => $i18n['margin']['margin']['description'],
'type' => 'custom_margin',
'mobile_options' => true,
'sticky' => true,
'option_category' => 'layout',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'hover' => 'tabs',
'allowed_units' => array( '%', 'em', 'rem', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'ex', 'vh', 'vw' ),
);
$additional_options['custom_margin_tablet'] = array(
'type' => 'skip',
'tab_slug' => $tab_slug,
);
$additional_options['custom_margin_phone'] = array(
'type' => 'skip',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
);
// Only adds `sub_toggle` for the margin settings when it's not empty.
if ( ! empty( $sub_toggle ) ) {
$additional_options['custom_margin']['sub_toggle'] = $sub_toggle;
}
// make it possible to override/add options.
if ( ! empty( $this->advanced_fields['margin_padding']['custom_margin'] ) ) {
$additional_options['custom_margin'] = array_merge( $additional_options['custom_margin'], $this->advanced_fields['margin_padding']['custom_margin'] );
}
$additional_options['custom_margin_last_edited'] = array(
'type' => 'skip',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
);
$additional_options['padding_1_last_edited'] = array(
'type' => 'skip',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
);
$additional_options['padding_2_last_edited'] = array(
'type' => 'skip',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
);
$additional_options['padding_3_last_edited'] = array(
'type' => 'skip',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
);
$additional_options['padding_4_last_edited'] = array(
'type' => 'skip',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
);
}
if ( $this->advanced_fields['margin_padding']['use_padding'] ) {
$additional_options['custom_padding'] = array(
'label' => $i18n['margin']['padding']['label'],
'description' => $i18n['margin']['padding']['description'],
'type' => 'custom_padding',
'mobile_options' => true,
'sticky' => true,
'option_category' => 'layout',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'hover' => 'tabs',
'allowed_units' => array( '%', 'em', 'rem', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'ex', 'vh', 'vw' ),
);
$additional_options['custom_padding_tablet'] = array(
'type' => 'skip',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
);
$additional_options['custom_padding_phone'] = array(
'type' => 'skip',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
);
// Only adds `sub_toggle` for the padding settings when it's not empty.
if ( ! empty( $sub_toggle ) ) {
$additional_options['custom_padding']['sub_toggle'] = $sub_toggle;
}
// make it possible to override/add options.
if ( ! empty( $this->advanced_fields['margin_padding']['custom_padding'] ) ) {
$additional_options['custom_padding'] = array_merge( $additional_options['custom_padding'], $this->advanced_fields['margin_padding']['custom_padding'] );
}
$additional_options['custom_padding_last_edited'] = array(
'type' => 'skip',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
);
}
$this->_additional_fields_options = array_merge( $this->_additional_fields_options, $additional_options );
}
/**
* Add button option fields.
*
* @since 3.23 Add custom padding for button options set. Add allowed CSS units. Set custom
* default for text size and border width. Add responsive settings for button
* settings. Set custom group label. Add ability hide and show the icon settings.
*/
protected function _add_button_fields() {
// Disable if module doesn't set advanced_fields property and has no VB support.
if ( ! $this->has_advanced_fields ) {
return;
}
// Button settings have to be array.
if ( ! is_array( self::$_->array_get( $this->advanced_fields, 'button' ) ) ) {
return;
}
$this->set_i18n_font();
$i18n =& self::$i18n;
// Auto-add attributes toggle.
$toggles_custom_css_tab = isset( $this->settings_modal_toggles['custom_css'] ) ? $this->settings_modal_toggles['custom_css'] : array();
if ( ! isset( $toggles_custom_css_tab['toggles'] ) || ! isset( $toggles_custom_css_tab['toggles']['attributes'] ) ) {
$this->_add_settings_modal_toggles(
'custom_css',
array(
'attributes' => array(
'title' => esc_html__( 'Attributes', 'et_builder' ),
'priority' => 95,
),
)
);
}
$additional_options = array();
$hover = et_pb_hover_options();
foreach ( $this->advanced_fields['button'] as $option_name => $option_settings ) {
$tab_slug = isset( $option_settings['tab_slug'] ) ? $option_settings['tab_slug'] : 'advanced';
$toggle_disabled = isset( $option_settings['disable_toggle'] ) && $option_settings['disable_toggle'];
$toggle_slug = '';
if ( ! $toggle_disabled ) {
$toggle_slug = isset( $option_settings['toggle_slug'] ) ? $option_settings['toggle_slug'] : $option_name;
$button_toggle = array(
$option_name => array(
'title' => esc_html( $option_settings['label'] ),
'priority' => 70,
),
);
$this->_add_settings_modal_toggles( $tab_slug, $button_toggle );
}
// Custom default values defined on module.
$text_size_default = self::$_->array_get( $option_settings, 'text_size.default', '' );
$border_width_default = self::$_->array_get( $option_settings, 'border_width.default', '' );
$additional_options[ "custom_{$option_name}" ] = array(
'label' => sprintf( esc_html__( 'Use Custom Styles For %1$s ', 'et_builder' ), $option_settings['label'] ),
'description' => esc_html__( "If you would like to customize the appearance of this module's button, you must first enable custom button styles.", 'et_builder' ),
'type' => 'yes_no_button',
'option_category' => 'button',
'options' => array(
'off' => et_builder_i18n( 'No' ),
'on' => et_builder_i18n( 'Yes' ),
),
'affects' => array(
"{$option_name}_text_color",
"{$option_name}_text_size",
"{$option_name}_border_width",
"{$option_name}_border_radius",
"{$option_name}_letter_spacing",
"{$option_name}_spacing",
"{$option_name}_bg_color",
"{$option_name}_border_color",
"{$option_name}_use_icon",
"{$option_name}_font",
$hover->get_hover_field( "{$option_name}_text_color" ),
$hover->get_hover_field( "{$option_name}_border_color" ),
$hover->get_hover_field( "{$option_name}_border_radius" ),
$hover->get_hover_field( "{$option_name}_letter_spacing" ),
"{$option_name}_text_shadow_style", // Add Text Shadow to button options.
"{$option_name}_custom_margin",
"{$option_name}_custom_padding",
),
'default_on_front' => 'off',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
);
$additional_options[ "{$option_name}_text_size" ] = array(
'label' => sprintf( $i18n['font']['size']['label'], $option_settings['label'] ),
'description' => esc_html__( 'Increase or decrease the size of the button text.', 'et_builder' ),
'type' => 'range',
'range_settings' => array(
'min' => '1',
'max' => '100',
'step' => '1',
),
'option_category' => 'button',
'allowed_units' => array( '%', 'em', 'rem', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'ex', 'vh', 'vw' ),
'default' => ! empty( $text_size_default ) ? $text_size_default : ET_Global_Settings::get_value( 'all_buttons_font_size' ),
'default_unit' => 'px',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'mobile_options' => true,
'sticky' => true,
'depends_show_if' => 'on',
'hover' => 'tabs',
);
$additional_options[ "{$option_name}_text_color" ] = array(
'label' => sprintf( $i18n['font']['color']['label'], $option_settings['label'] ),
'description' => esc_html__( 'Pick a color to be used for the button text.', 'et_builder' ),
'type' => 'color-alpha',
'option_category' => 'button',
'custom_color' => true,
'default' => '',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'depends_show_if' => 'on',
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
);
$additional_options[ "{$option_name}_bg_color" ] = array(
'label' => sprintf( esc_html__( '%1$s Background', 'et_builder' ), $option_settings['label'] ),
'description' => esc_html__( 'Adjust the background style of the button by customizing the background color, gradient, and image.', 'et_builder' ),
'type' => 'background-field',
'base_name' => "{$option_name}_bg",
'context' => "{$option_name}_bg_color",
'option_category' => 'button',
'custom_color' => true,
'default' => ET_Global_Settings::get_value( 'all_buttons_bg_color' ),
'default_on_front' => '',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'depends_show_if' => 'on',
'background_fields' => $this->generate_background_options( "{$option_name}_bg", 'button', $tab_slug, $toggle_slug, "{$option_name}_bg_color" ),
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
);
$additional_options[ "{$option_name}_bg_color" ]['background_fields'][ "{$option_name}_bg_color" ]['default'] = ET_Global_Settings::get_value( 'all_buttons_bg_color' );
$additional_options = array_merge( $additional_options, $this->generate_background_options( "{$option_name}_bg", 'skip', $tab_slug, $toggle_slug, "{$option_name}_bg_color" ) );
$additional_options[ "{$option_name}_border_width" ] = array(
'label' => sprintf( esc_html__( '%1$s Border Width', 'et_builder' ), $option_settings['label'] ),
'description' => esc_html__( 'Increase or decrease the thickness of the border around the button. Setting this value to 0 will remove the border entirely.', 'et_builder' ),
'type' => 'range',
'option_category' => 'button',
'default' => ! empty( $border_width_default ) ? $border_width_default : ET_Global_Settings::get_value( 'all_buttons_border_width' ),
'default_unit' => 'px',
'default_on_front' => '',
'allowed_units' => array( 'em', 'rem', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'ex', 'vh', 'vw' ),
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'depends_show_if' => 'on',
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
);
$additional_options[ "{$option_name}_border_color" ] = array(
'label' => sprintf( esc_html__( '%1$s Border Color', 'et_builder' ), $option_settings['label'] ),
'description' => esc_html__( 'Pick a color to be used for the button border.', 'et_builder' ),
'type' => 'color-alpha',
'option_category' => 'button',
'custom_color' => true,
'default' => '',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'depends_show_if' => 'on',
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
);
$additional_options[ "{$option_name}_border_radius" ] = array(
'label' => sprintf( esc_html__( '%1$s Border Radius', 'et_builder' ), $option_settings['label'] ),
'description' => esc_html__( "Increasing the border radius will increase the roundness of the button's corners. Setting this value to 0 will result in squared corners.", 'et_builder' ),
'type' => 'range',
'option_category' => 'button',
'default' => ET_Global_Settings::get_value( 'all_buttons_border_radius' ),
'default_unit' => 'px',
'default_on_front' => '',
'allowed_units' => array( '%', 'em', 'rem', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'ex', 'vh', 'vw' ),
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'depends_show_if' => 'on',
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
);
$additional_options[ "{$option_name}_letter_spacing" ] = array(
'label' => sprintf( $i18n['font']['letter_spacing']['label'], $option_settings['label'] ),
'description' => esc_html__( 'Letter spacing adjusts the distance between each letter in the button.', 'et_builder' ),
'type' => 'range',
'option_category' => 'button',
'default' => ET_Global_Settings::get_value( 'all_buttons_spacing' ),
'default_unit' => 'px',
'default_on_front' => '',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'mobile_options' => true,
'sticky' => true,
'depends_show_if' => 'on',
'hover' => 'tabs',
);
$additional_options[ "{$option_name}_font" ] = array(
'label' => sprintf( $i18n['font']['font']['label'], $option_settings['label'] ),
'description' => esc_html__( 'Choose a custom font to use for the button. All Google web fonts are available, or you can upload your own custom font files.', 'et_builder' ),
'group_label' => esc_html( $option_settings['label'] ),
'type' => 'font',
'option_category' => 'button',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'depends_show_if' => 'on',
'mobile_options' => true,
);
// Hide show button icon.
$hide_icon = isset( $option_settings['hide_icon'] ) ? $option_settings['hide_icon'] : false;
if ( false === $hide_icon ) {
$additional_options[ "{$option_name}_use_icon" ] = array(
'label' => sprintf( esc_html__( 'Show %1$s Icon', 'et_builder' ), $option_settings['label'] ),
'description' => esc_html__( 'When enabled, this will add a custom icon within the button.', 'et_builder' ),
'type' => 'yes_no_button',
'option_category' => 'button',
'default' => 'on',
'options' => array(
'on' => et_builder_i18n( 'Yes' ),
'off' => et_builder_i18n( 'No' ),
),
'affects' => array(
"{$option_name}_icon_color",
"{$option_name}_icon_placement",
"{$option_name}_on_hover",
"{$option_name}_icon",
),
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'depends_show_if' => 'on',
);
$additional_options[ "{$option_name}_icon" ] = array(
'label' => sprintf( esc_html__( '%1$s Icon', 'et_builder' ), $option_settings['label'] ),
'description' => esc_html__( 'Pick an icon to be used for the button.', 'et_builder' ),
'type' => 'select_icon',
'option_category' => 'button',
'class' => array( 'et-pb-font-icon' ),
'default' => '',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'depends_show_if_not' => 'off',
'mobile_options' => true,
);
$additional_options[ "{$option_name}_icon_color" ] = array(
'label' => sprintf( esc_html__( '%1$s Icon Color', 'et_builder' ), $option_settings['label'] ),
'description' => esc_html__( 'Here you can define a custom color for the button icon.', 'et_builder' ),
'type' => 'color-alpha',
'option_category' => 'button',
'custom_color' => true,
'default' => '',
'hover' => 'tabs',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'depends_show_if_not' => 'off',
'mobile_options' => true,
'sticky' => true,
);
$additional_options[ "{$option_name}_icon_placement" ] = array(
'label' => sprintf( esc_html__( '%1$s Icon Placement', 'et_builder' ), $option_settings['label'] ),
'description' => esc_html__( 'Choose where the button icon should be displayed within the button.', 'et_builder' ),
'type' => 'select',
'option_category' => 'button',
'options' => array(
'right' => et_builder_i18n( 'Right' ),
'left' => et_builder_i18n( 'Left' ),
),
'default' => 'right',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'depends_show_if_not' => 'off',
'mobile_options' => true,
);
$additional_options[ "{$option_name}_on_hover" ] = array(
'label' => sprintf( esc_html__( 'Only Show Icon On Hover for %1$s', 'et_builder' ), $option_settings['label'] ),
'description' => esc_html__( 'By default, button icons are displayed on hover. If you would like button icons to always be displayed, then you can enable this option.', 'et_builder' ),
'type' => 'yes_no_button',
'option_category' => 'button',
'default' => 'on',
'options' => array(
'on' => et_builder_i18n( 'Yes' ),
'off' => et_builder_i18n( 'No' ),
),
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'depends_show_if_not' => 'off',
'mobile_options' => true,
);
}
if ( isset( $option_settings['use_alignment'] ) && $option_settings['use_alignment'] ) {
$additional_options[ "{$option_name}_alignment" ] = array(
'label' => esc_html__( 'Button Alignment', 'et_builder' ),
'description' => esc_html__( 'Align your button to the left, right or center of the module.', 'et_builder' ),
'type' => 'text_align',
'option_category' => 'layout',
'options' => et_builder_get_text_orientation_options( array( 'justified' ) ),
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'mobile_options' => true,
);
}
// The configurable rel attribute field is added by default.
if ( ! isset( $option_settings['no_rel_attr'] ) ) {
$additional_options[ "{$option_name}_rel" ] = array(
'label' => sprintf( esc_html__( '%1$s Relationship', 'et_builder' ), $option_settings['label'] ),
'type' => 'multiple_checkboxes',
'option_category' => 'configuration',
'options' => $this->get_rel_values(),
'description' => et_get_safe_localization( __( "Specify the value of your link's rel attribute. The rel attribute specifies the relationship between the current document and the linked document. Tip: Search engines can use this attribute to get more information about a link.", 'et_builder' ) ),
'tab_slug' => 'custom_css',
'toggle_slug' => 'attributes',
'shortcut_index' => $option_name,
);
}
// Add text-shadow to button options.
$option = $this->text_shadow->get_fields(
array(
'label' => $option_settings['label'],
'prefix' => $option_name,
'option_category' => 'font_option',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'depends_show_if' => 'on',
'show_if' => array(
"custom_{$option_name}" => 'on',
),
)
);
$additional_options = array_merge( $additional_options, $option );
// Conditionally add box-shadow options to button options. Get box shadow settings for advanced button fields.
$button_box_shadow_options = self::$_->array_get( $option_settings, 'box_shadow', array() );
// Enable module to remove box shadow from advanced button fields by declaring false value to box
// shadow attribute (i.e. button module).
if ( false !== $button_box_shadow_options ) {
$button_box_shadow_options = wp_parse_args(
$button_box_shadow_options,
array(
'label' => esc_html__( 'Button Box Shadow', 'et_builder' ),
'option_category' => 'layout',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'show_if' => array(
"custom_{$option_name}" => 'on',
),
)
);
// Only print box shadow styling if custom_* attribute is equal to "on" by adding show_iff attribute.
$button_visibility_condition = array( "custom_{$option_name}" => 'on' );
self::$_->array_set( $button_box_shadow_options, 'css.show_if', $button_visibility_condition );
// Automatically add default box shadow fields if box shadow attribute hasn't even defined yet.
// No attribute found is considered true for default thus if this about to add the first advanced
// box shadow, add the default first.
if ( ! isset( $this->advanced_fields['box_shadow'] ) ) {
$button_box_shadow_options_default = array();
self::$_->array_set( $this->advanced_fields, 'box_shadow.default', $button_box_shadow_options_default );
}
// Box shadow fields are generated after button fields being added. Thus, adding $this->advanced_fields
// is sufficient to insert the box shadow fields.
self::$_->array_set( $this->advanced_fields, "box_shadow.{$option_name}", $button_box_shadow_options );
}
// Add custom margin-padding to form field options.
$margin_padding = self::$_->array_get( $option_settings, 'margin_padding', true );
if ( $margin_padding ) {
$margin_padding_module_args = is_array( $margin_padding ) ? $margin_padding : array();
$margin_padding_args = wp_parse_args(
$margin_padding_module_args,
array(
'label' => $option_settings['label'],
'prefix' => $option_name,
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
)
);
$margin_padding_options = $this->margin_padding->get_fields( $margin_padding_args );
$additional_options = array_merge( $additional_options, $margin_padding_options );
}
}
$this->_additional_fields_options = array_merge( $this->_additional_fields_options, $additional_options );
}
/**
* Add special image_icon option fields.
*
* @since ? Add custom margin-padding for image_icon field.
*/
protected function _add_image_icon_fields() {
// Disable if module doesn't set advanced_fields property and has no VB support.
if ( ! $this->has_advanced_fields ) {
return;
}
// image_icon settings have to be array.
if ( ! is_array( self::$_->array_get( $this->advanced_fields, 'image_icon' ) ) ) {
return;
}
$this->set_i18n_font();
$i18n =& self::$i18n;
$additional_options = array();
$hover = et_pb_hover_options();
foreach ( $this->advanced_fields['image_icon'] as $option_name => $option_settings ) {
$tab_slug = isset( $option_settings['tab_slug'] ) ? $option_settings['tab_slug'] : 'advanced';
$toggle_disabled = isset( $option_settings['disable_toggle'] ) && $option_settings['disable_toggle'];
$toggle_slug = '';
if ( ! $toggle_disabled ) {
$toggle_slug = isset( $option_settings['toggle_slug'] ) ? $option_settings['toggle_slug'] : $option_name;
$image_icon_toggle = array(
$option_name => array(
'title' => esc_html( $option_settings['label'] ),
'priority' => 70,
),
);
$this->_add_settings_modal_toggles( $tab_slug, $image_icon_toggle );
}
// Add custom margin-padding to form field options.
$margin_padding = self::$_->array_get( $option_settings, 'margin_padding', true );
if ( $margin_padding ) {
$margin_padding_module_args = is_array( $margin_padding ) ? $margin_padding : array();
$margin_padding_args = wp_parse_args(
$margin_padding_module_args,
array(
'label' => $option_settings['label'],
'prefix' => $option_name,
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
)
);
$margin_padding_options = $this->margin_padding->get_fields( $margin_padding_args );
$additional_options = array_merge( $additional_options, $margin_padding_options );
}
}
$this->_additional_fields_options = array_merge( $this->_additional_fields_options, $additional_options );
}
/**
* Add animation option fields.
*
* @since 3.23 Introduce responsive settings on all animation options. Rename Animation label
* for et_pb_team_member module as Image Animation. Reorder animation repeat option
* to the bottom of animation settings.
*/
protected function _add_animation_fields() {
// Animation fields are added by default on all module.
$this->advanced_fields['animation'] = self::$_->array_get( $this->advanced_fields, 'animation', array() );
// Animation Disabled.
if ( false === $this->advanced_fields['animation'] ) {
return;
}
$classname = get_class( $this );
// Child modules do not support the Animation settings except for Columns.
if ( isset( $this->type ) && 'child' === $this->type && ! in_array( $this->slug, array( 'et_pb_column', 'et_pb_column_inner' ), true ) ) {
return;
}
// Cache results so that translation/escaping only happens once.
$i18n =& self::$i18n;
if ( ! isset( $i18n['animation'] ) ) {
// phpcs:disable WordPress.WP.I18n.MissingTranslatorsComment
$i18n['animation'] = array(
'toggle' => array(
'title' => esc_html__( 'Animation', 'et_builder' ),
),
'style' => array(
'label' => esc_html__( 'Animation Style', 'et_builder' ),
'description' => esc_html__( 'Pick an animation style to enable animations for this element. Once enabled, you will be able to customize your animation style further. To disable animations, choose the None option.', 'et_builder' ),
'options' => array(
'fade' => et_builder_i18n( 'Fade' ),
'slide' => et_builder_i18n( 'Slide' ),
'bounce' => esc_html__( 'Bounce', 'et_builder' ),
'zoom' => esc_html__( 'Zoom', 'et_builder' ),
'flip' => et_builder_i18n( 'Flip' ),
'fold' => esc_html__( 'Fold', 'et_builder' ),
'roll' => esc_html__( 'Roll', 'et_builder' ),
),
),
'direction' => array(
'label' => esc_html__( 'Animation Direction', 'et_builder' ),
'description' => esc_html__( 'Pick from up to five different animation directions, each of which will adjust the starting and ending position of your animated element.', 'et_builder' ),
),
'duration' => array(
'label' => esc_html__( 'Animation Duration', 'et_builder' ),
'description' => esc_html__( 'Speed up or slow down your animation by adjusting the animation duration. Units are in milliseconds and the default animation duration is one second.', 'et_builder' ),
),
'delay' => array(
'label' => esc_html__( 'Animation Delay', 'et_builder' ),
'description' => esc_html__( 'If you would like to add a delay before your animation runs you can designate that delay here in milliseconds. This can be useful when using multiple animated modules together.', 'et_builder' ),
),
'opacity' => array(
'label' => esc_html__( 'Animation Starting Opacity', 'et_builder' ),
'description' => esc_html__( 'By increasing the starting opacity, you can reduce or remove the fade effect that is applied to all animation styles.', 'et_builder' ),
),
'speed' => array(
'label' => esc_html__( 'Animation Speed Curve', 'et_builder' ),
'description' => esc_html__( 'Here you can adjust the easing method of your animation. Easing your animation in and out will create a smoother effect when compared to a linear speed curve.', 'et_builder' ),
),
'repeat' => array(
'label' => esc_html__( 'Animation Repeat', 'et_builder' ),
'description' => esc_html__( 'By default, animations will only play once. If you would like to loop your animation continuously you can choose the Loop option here.', 'et_builder' ),
'options' => array(
'once' => esc_html__( 'Once', 'et_builder' ),
'loop' => esc_html__( 'Loop', 'et_builder' ),
),
),
'menu' => array(
'label' => esc_html__( 'Dropdown Menu Animation', 'et_builder' ),
'description' => esc_html__( 'Select an animation to be used when dropdown menus appear. Dropdown menus appear when hovering over links with sub items.', 'et_builder' ),
),
'intensity' => array(
'label' => esc_html__( 'Animation Intensity', 'et_builder' ),
'description' => esc_html__( 'Intensity effects how subtle or aggressive your animation will be. Lowering the intensity will create a smoother and more subtle animation while increasing the intensity will create a snappier more aggressive animation.', 'et_builder' ),
),
);
// phpcs:enable
}
$this->settings_modal_toggles['advanced']['toggles']['animation'] = array(
'title' => $i18n['animation']['toggle']['title'],
'priority' => 110,
);
$additional_options = array();
$animations_intensity_fields = array(
'animation_intensity_slide',
'animation_intensity_zoom',
'animation_intensity_flip',
'animation_intensity_fold',
'animation_intensity_roll',
);
$additional_options['animation_style'] = array(
'label' => $i18n['animation']['style']['label'],
'description' => $i18n['animation']['style']['description'],
'type' => 'select_animation',
'option_category' => 'configuration',
'default' => 'none',
'options' => array(
'none' => et_builder_i18n( 'None' ),
'fade' => $i18n['animation']['style']['options']['fade'],
'slide' => $i18n['animation']['style']['options']['slide'],
'bounce' => $i18n['animation']['style']['options']['bounce'],
'zoom' => $i18n['animation']['style']['options']['zoom'],
'flip' => $i18n['animation']['style']['options']['flip'],
'fold' => $i18n['animation']['style']['options']['fold'],
'roll' => $i18n['animation']['style']['options']['roll'],
),
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'affects' => array_merge(
array(
'animation_repeat',
'animation_direction',
'animation_duration',
'animation_delay',
'animation_starting_opacity',
'animation_speed_curve',
),
$animations_intensity_fields
),
);
$additional_options['animation_direction'] = array(
'label' => $i18n['animation']['direction']['label'],
'description' => $i18n['animation']['direction']['description'],
'type' => 'select',
'option_category' => 'configuration',
'default' => 'center',
'options' => array(
'center' => et_builder_i18n( 'Center' ),
'left' => et_builder_i18n( 'Right' ),
'right' => et_builder_i18n( 'Left' ),
'bottom' => et_builder_i18n( 'Up' ),
'top' => et_builder_i18n( 'Down' ),
),
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'depends_show_if_not' => array( 'none', 'fade' ),
'mobile_options' => true,
);
$additional_options['animation_duration'] = array(
'label' => $i18n['animation']['duration']['label'],
'description' => $i18n['animation']['duration']['description'],
'type' => 'range',
'option_category' => 'configuration',
'range_settings' => array(
'min' => 0,
'max' => 2000,
'step' => 50,
),
'default' => '1000ms',
'validate_unit' => true,
'fixed_unit' => 'ms',
'fixed_range' => true,
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'depends_show_if_not' => 'none',
'reset_animation' => true,
'mobile_options' => true,
);
$additional_options['animation_delay'] = array(
'label' => $i18n['animation']['delay']['label'],
'description' => $i18n['animation']['delay']['description'],
'type' => 'range',
'option_category' => 'configuration',
'range_settings' => array(
'min' => 0,
'max' => 3000,
'step' => 50,
),
'default' => '0ms',
'validate_unit' => true,
'fixed_unit' => 'ms',
'fixed_range' => true,
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'depends_show_if_not' => 'none',
'reset_animation' => true,
'mobile_options' => true,
);
foreach ( $animations_intensity_fields as $animations_intensity_field ) {
$animation_style = str_replace( 'animation_intensity_', '', $animations_intensity_field );
$additional_options[ $animations_intensity_field ] = array(
'label' => $i18n['animation']['intensity']['label'],
'description' => $i18n['animation']['intensity']['description'],
'type' => 'range',
'option_category' => 'configuration',
'range_settings' => array(
'min' => 0,
'max' => 100,
'step' => 1,
),
'default' => '50%',
'validate_unit' => true,
'fixed_unit' => '%',
'fixed_range' => true,
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'depends_show_if' => $animation_style,
'reset_animation' => true,
'mobile_options' => true,
);
}
$additional_options['animation_starting_opacity'] = array(
'label' => $i18n['animation']['opacity']['label'],
'description' => $i18n['animation']['opacity']['description'],
'type' => 'range',
'option_category' => 'configuration',
'range_settings' => array(
'min' => 0,
'max' => 100,
'step' => 1,
'min_limit' => 0,
'max_limit' => 100,
),
'default' => '0%',
'validate_unit' => true,
'fixed_unit' => '%',
'fixed_range' => true,
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'depends_show_if_not' => 'none',
'reset_animation' => true,
'mobile_options' => true,
);
$additional_options['animation_speed_curve'] = array(
'label' => $i18n['animation']['speed']['label'],
'description' => $i18n['animation']['speed']['description'],
'type' => 'select',
'option_category' => 'configuration',
'default' => 'ease-in-out',
'options' => array(
'ease-in-out' => et_builder_i18n( 'Ease-In-Out' ),
'ease' => et_builder_i18n( 'Ease' ),
'ease-in' => et_builder_i18n( 'Ease-In' ),
'ease-out' => et_builder_i18n( 'Ease-Out' ),
'linear' => et_builder_i18n( 'Linear' ),
),
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'depends_show_if_not' => 'none',
'mobile_options' => true,
);
$additional_options['animation_repeat'] = array(
'label' => $i18n['animation']['repeat']['label'],
'description' => $i18n['animation']['repeat']['description'],
'type' => 'select',
'option_category' => 'configuration',
'default' => 'once',
'options' => array(
'once' => $i18n['animation']['repeat']['options']['once'],
'loop' => $i18n['animation']['repeat']['options']['loop'],
),
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'depends_show_if_not' => 'none',
'mobile_options' => true,
);
if ( isset( $this->slug ) && in_array( $this->slug, array( 'et_pb_menu', 'et_pb_fullwidth_menu' ), true ) ) {
$additional_options['dropdown_menu_animation'] = array(
'label' => $i18n['animation']['menu']['label'],
'description' => $i18n['animation']['menu']['description'],
'type' => 'select',
'option_category' => 'configuration',
'options' => array(
'fade' => et_builder_i18n( 'Fade' ),
'expand' => et_builder_i18n( 'Expand' ),
'slide' => et_builder_i18n( 'Slide' ),
'flip' => et_builder_i18n( 'Flip' ),
),
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'default' => 'fade',
);
}
// Move existing "Animation" section fields under the new animations UI.
if ( isset( $this->slug ) && 'et_pb_fullwidth_portfolio' === $this->slug ) {
$additional_options['auto'] = array(
'label' => esc_html__( 'Automatic Carousel Rotation', 'et_builder' ),
'description' => esc_html__( 'If you the carousel layout option is chosen and you would like the carousel to slide automatically, without the visitor having to click the next button, enable this option and then adjust the rotation speed below if desired.', 'et_builder' ),
'type' => 'yes_no_button',
'option_category' => 'configuration',
'options' => array(
'off' => et_builder_i18n( 'Off' ),
'on' => et_builder_i18n( 'On' ),
),
'affects' => array(
'auto_speed',
),
'depends_show_if' => 'on',
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'default' => 'off',
);
$additional_options['auto_speed'] = array(
'label' => esc_html__( 'Automatic Carousel Rotation Speed (in ms)', 'et_builder' ),
'type' => 'text',
'option_category' => 'configuration',
'depends_show_if' => 'on',
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'description' => esc_html__( "Here you can designate how fast the carousel rotates, if 'Automatic Carousel Rotation' option is enabled above. The higher the number the longer the pause between each rotation. (Ex. 1000 = 1 sec)", 'et_builder' ),
'default' => '7000',
);
}
if ( isset( $this->slug ) && 'et_pb_fullwidth_slider' === $this->slug ) {
$additional_options['auto'] = array(
'label' => esc_html__( 'Automatic Animation', 'et_builder' ),
'type' => 'yes_no_button',
'option_category' => 'configuration',
'options' => array(
'off' => et_builder_i18n( 'Off' ),
'on' => et_builder_i18n( 'On' ),
),
'affects' => array(
'auto_speed',
'auto_ignore_hover',
),
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'description' => esc_html__( 'If you would like the slider to slide automatically, without the visitor having to click the next button, enable this option and then adjust the rotation speed below if desired.', 'et_builder' ),
'default' => 'off',
);
$additional_options['auto_speed'] = array(
'label' => esc_html__( 'Automatic Animation Speed (in ms)', 'et_builder' ),
'type' => 'text',
'option_category' => 'configuration',
'depends_show_if' => 'on',
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'description' => esc_html__( "Here you can designate how fast the slider fades between each slide, if 'Automatic Animation' option is enabled above. The higher the number the longer the pause between each rotation.", 'et_builder' ),
'default' => '7000',
);
$additional_options['auto_ignore_hover'] = array(
'label' => esc_html__( 'Continue Automatic Slide on Hover', 'et_builder' ),
'type' => 'yes_no_button',
'option_category' => 'configuration',
'depends_show_if' => 'on',
'options' => array(
'off' => et_builder_i18n( 'Off' ),
'on' => et_builder_i18n( 'On' ),
),
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'description' => esc_html__( 'Turning this on will allow automatic sliding to continue on mouse hover.', 'et_builder' ),
'default' => 'off',
);
}
if ( isset( $this->slug ) && 'et_pb_fullwidth_post_slider' === $this->slug ) {
$additional_options['auto'] = array(
'label' => esc_html__( 'Automatic Animation', 'et_builder' ),
'type' => 'yes_no_button',
'option_category' => 'configuration',
'options' => array(
'off' => et_builder_i18n( 'Off' ),
'on' => et_builder_i18n( 'On' ),
),
'affects' => array(
'auto_speed',
'auto_ignore_hover',
),
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'description' => esc_html__( 'If you would like the slider to slide automatically, without the visitor having to click the next button, enable this option and then adjust the rotation speed below if desired.', 'et_builder' ),
'default' => 'off',
);
$additional_options['auto_speed'] = array(
'label' => esc_html__( 'Automatic Animation Speed (in ms)', 'et_builder' ),
'type' => 'text',
'option_category' => 'configuration',
'depends_show_if' => 'on',
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'description' => esc_html__( "Here you can designate how fast the slider fades between each slide, if 'Automatic Animation' option is enabled above. The higher the number the longer the pause between each rotation.", 'et_builder' ),
'default' => '7000',
);
$additional_options['auto_ignore_hover'] = array(
'label' => esc_html__( 'Continue Automatic Slide on Hover', 'et_builder' ),
'type' => 'yes_no_button',
'option_category' => 'configuration',
'depends_show_if' => 'on',
'options' => array(
'off' => et_builder_i18n( 'Off' ),
'on' => et_builder_i18n( 'On' ),
),
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'description' => esc_html__( 'Turning this on will allow automatic sliding to continue on mouse hover.', 'et_builder' ),
'default' => 'off',
);
}
if ( isset( $this->slug ) && in_array( $this->slug, array( 'et_pb_gallery', 'et_pb_wc_images', 'et_pb_wc_gallery' ), true ) ) {
$additional_options['auto'] = array(
'label' => esc_html__( 'Automatic Animation', 'et_builder' ),
'type' => 'yes_no_button',
'option_category' => 'configuration',
'options' => array(
'off' => et_builder_i18n( 'Off' ),
'on' => et_builder_i18n( 'On' ),
),
'affects' => array(
'auto_speed',
),
'depends_show_if' => 'on',
'depends_on' => array(
'fullwidth',
),
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'description' => esc_html__( 'If you would like the slider to slide automatically, without the visitor having to click the next button, enable this option and then adjust the rotation speed below if desired.', 'et_builder' ),
'default' => 'off',
);
$additional_options['auto_speed'] = array(
'label' => esc_html__( 'Automatic Animation Speed (in ms)', 'et_builder' ),
'type' => 'text',
'option_category' => 'configuration',
'depends_show_if' => 'on',
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'description' => esc_html__( "Here you can designate how fast the slider fades between each slide, if 'Automatic Animation' option is enabled above. The higher the number the longer the pause between each rotation.", 'et_builder' ),
'default' => '7000',
);
}
if ( isset( $this->slug ) && 'et_pb_blurb' === $this->slug ) {
$additional_options['animation'] = array(
'label' => esc_html__( 'Image/Icon Animation', 'et_builder' ),
'type' => 'select',
'option_category' => 'configuration',
'options' => array(
'top' => esc_html__( 'Top To Bottom', 'et_builder' ),
'left' => esc_html__( 'Left To Right', 'et_builder' ),
'right' => esc_html__( 'Right To Left', 'et_builder' ),
'bottom' => esc_html__( 'Bottom To Top', 'et_builder' ),
'off' => esc_html__( 'No Animation', 'et_builder' ),
),
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'description' => esc_html__( 'This controls the direction of the lazy-loading animation.', 'et_builder' ),
'default' => 'top',
'mobile_options' => true,
);
}
if ( isset( $this->slug ) && 'et_pb_slider' === $this->slug ) {
$additional_options['auto'] = array(
'label' => esc_html__( 'Automatic Animation', 'et_builder' ),
'type' => 'yes_no_button',
'option_category' => 'configuration',
'options' => array(
'off' => et_builder_i18n( 'Off' ),
'on' => et_builder_i18n( 'On' ),
),
'affects' => array(
'auto_speed',
'auto_ignore_hover',
),
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'description' => esc_html__( 'If you would like the slider to slide automatically, without the visitor having to click the next button, enable this option and then adjust the rotation speed below if desired.', 'et_builder' ),
'default' => 'off',
);
$additional_options['auto_speed'] = array(
'label' => esc_html__( 'Automatic Animation Speed (in ms)', 'et_builder' ),
'type' => 'text',
'option_category' => 'configuration',
'depends_show_if' => 'on',
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'description' => esc_html__( "Here you can designate how fast the slider fades between each slide, if 'Automatic Animation' option is enabled above. The higher the number the longer the pause between each rotation.", 'et_builder' ),
'default' => '7000',
);
$additional_options['auto_ignore_hover'] = array(
'label' => esc_html__( 'Continue Automatic Slide on Hover', 'et_builder' ),
'type' => 'yes_no_button',
'option_category' => 'configuration',
'depends_show_if' => 'on',
'options' => array(
'off' => et_builder_i18n( 'Off' ),
'on' => et_builder_i18n( 'On' ),
),
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'description' => esc_html__( 'Turning this on will allow automatic sliding to continue on mouse hover.', 'et_builder' ),
'default' => 'off',
);
}
if ( isset( $this->slug ) && 'et_pb_post_slider' === $this->slug ) {
$additional_options['auto'] = array(
'label' => esc_html__( 'Automatic Animation', 'et_builder' ),
'type' => 'yes_no_button',
'option_category' => 'configuration',
'options' => array(
'off' => et_builder_i18n( 'Off' ),
'on' => et_builder_i18n( 'On' ),
),
'affects' => array(
'auto_speed',
'auto_ignore_hover',
),
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'description' => esc_html__( 'If you would like the slider to slide automatically, without the visitor having to click the next button, enable this option and then adjust the rotation speed below if desired.', 'et_builder' ),
'default' => 'off',
);
$additional_options['auto_speed'] = array(
'label' => esc_html__( 'Automatic Animation Speed (in ms)', 'et_builder' ),
'type' => 'text',
'option_category' => 'configuration',
'depends_show_if' => 'on',
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'description' => esc_html__( "Here you can designate how fast the slider fades between each slide, if 'Automatic Animation' option is enabled above. The higher the number the longer the pause between each rotation.", 'et_builder' ),
'default' => '7000',
);
$additional_options['auto_ignore_hover'] = array(
'label' => esc_html__( 'Continue Automatic Slide on Hover', 'et_builder' ),
'type' => 'yes_no_button',
'option_category' => 'configuration',
'depends_show_if' => 'on',
'options' => array(
'off' => et_builder_i18n( 'Off' ),
'on' => et_builder_i18n( 'On' ),
),
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'description' => esc_html__( 'Turning this on will allow automatic sliding to continue on mouse hover.', 'et_builder' ),
'default' => 'off',
);
}
if ( isset( $this->slug ) && 'et_pb_team_member' === $this->slug ) {
$additional_options['animation'] = array(
'label' => esc_html__( 'Image Animation', 'et_builder' ),
'type' => 'select',
'option_category' => 'configuration',
'options' => array(
'off' => esc_html__( 'No Animation', 'et_builder' ),
'fade_in' => esc_html__( 'Fade In', 'et_builder' ),
'left' => esc_html__( 'Left To Right', 'et_builder' ),
'right' => esc_html__( 'Right To Left', 'et_builder' ),
'top' => esc_html__( 'Top To Bottom', 'et_builder' ),
'bottom' => esc_html__( 'Bottom To Top', 'et_builder' ),
),
'tab_slug' => 'advanced',
'toggle_slug' => 'animation',
'description' => esc_html__( 'This controls the direction of the lazy-loading animation.', 'et_builder' ),
'default' => 'off',
);
}
$this->_additional_fields_options = array_merge( $this->_additional_fields_options, $additional_options );
}
/**
* Add additional transition fields. e.x hover transition fields.
*/
private function _add_additional_transition_fields() {
$i18n =& self::$i18n;
if ( ! isset( $i18n['transition'] ) ) {
// phpcs:disable WordPress.WP.I18n.MissingTranslatorsComment
$i18n['transition'] = array(
'toggle' => array(
'title' => esc_html__( 'Transitions', 'et_builder' ),
),
'duration' => array(
'label' => esc_html__( 'Transition Duration', 'et_builder' ),
'description' => esc_html__( 'This controls the transition duration of the hover animation.', 'et_builder' ),
),
'delay' => array(
'label' => esc_html__( 'Transition Delay', 'et_builder' ),
'description' => esc_html__( 'This controls the transition delay of the hover animation.', 'et_builder' ),
),
'curve' => array(
'label' => esc_html__( 'Transition Speed Curve', 'et_builder' ),
'description' => esc_html__( 'This controls the transition speed curve of the hover animation.', 'et_builder' ),
),
);
// phpcs:enable
}
$this->settings_modal_toggles['custom_css']['toggles']['hover_transitions'] = array(
'title' => $i18n['transition']['toggle']['title'],
'priority' => 120,
);
$additional_options = array();
$additional_options['hover_transition_duration'] = array(
'label' => $i18n['transition']['duration']['label'],
'description' => $i18n['transition']['duration']['description'],
'type' => 'range',
'option_category' => 'layout',
'range_settings' => array(
'min' => 0,
'max' => 2000,
'step' => 50,
),
'default' => '300ms',
'default_on_child' => true,
'validate_unit' => true,
'fixed_unit' => 'ms',
'fixed_range' => true,
'tab_slug' => 'custom_css',
'toggle_slug' => 'hover_transitions',
'depends_default' => null,
'mobile_options' => true,
);
$additional_options['hover_transition_delay'] = array(
'label' => $i18n['transition']['delay']['label'],
'description' => $i18n['transition']['delay']['description'],
'type' => 'range',
'option_category' => 'layout',
'range_settings' => array(
'min' => 0,
'max' => 300,
'step' => 50,
),
'default' => '0ms',
'default_on_child' => true,
'validate_unit' => true,
'fixed_unit' => 'ms',
'fixed_range' => true,
'tab_slug' => 'custom_css',
'toggle_slug' => 'hover_transitions',
'depends_default' => null,
'mobile_options' => true,
);
$additional_options['hover_transition_speed_curve'] = array(
'label' => $i18n['transition']['curve']['label'],
'description' => $i18n['transition']['curve']['description'],
'type' => 'select',
'option_category' => 'layout',
'default' => 'ease',
'default_on_child' => true,
'options' => array(
'ease-in-out' => et_builder_i18n( 'Ease-In-Out' ),
'ease' => et_builder_i18n( 'Ease' ),
'ease-in' => et_builder_i18n( 'Ease-In' ),
'ease-out' => et_builder_i18n( 'Ease-Out' ),
'linear' => et_builder_i18n( 'Linear' ),
),
'tab_slug' => 'custom_css',
'toggle_slug' => 'hover_transitions',
'depends_default' => null,
'mobile_options' => true,
);
$this->_additional_fields_options = array_merge( $this->_additional_fields_options, $additional_options );
}
/**
* Add CSS position controls affects top,right,bottom,left,position and transform translate CSS properties.
*
* @return void
* @since 4.2
*/
private function _add_position_fields() {
/** Position field class instance. @var $class ET_Builder_Module_Field_Position */
$class = ET_Builder_Module_Fields_Factory::get( 'Position' );
$this->advanced_fields[ $class::TOGGLE_SLUG ] = self::$_->array_get( $this->advanced_fields, $class::TOGGLE_SLUG, array() );
$this->advanced_fields['z_index'] = self::$_->array_get( $this->advanced_fields, 'z_index', array() );
// Position and Z Index Disabled.
if ( ! is_array( $this->advanced_fields[ $class::TOGGLE_SLUG ] ) && ! is_array( $this->advanced_fields['z_index'] ) ) {
return;
}
$this->settings_modal_toggles[ $class::TAB_SLUG ]['toggles'][ $class::TOGGLE_SLUG ] = array(
'title' => et_builder_i18n( 'Position' ),
'priority' => 190,
);
$default_position = self::$_->array_get( $this->advanced_fields[ $class::TOGGLE_SLUG ], 'default', 'none' );
$default_z_index = self::$_->array_get( $this->advanced_fields['z_index'], 'default', '' );
$args = array(
'defaults' => array(
'positioning' => $default_position, // none | relative | absolute | fixed.
'position_origin' => 'top_left',
'vertical_offset' => '',
'horizontal_offset' => '',
'z_index' => $default_z_index,
),
'hide_position_fields' => false === $this->advanced_fields[ $class::TOGGLE_SLUG ],
'hide_z_index_fields' => false === $this->advanced_fields['z_index'],
);
$this->_additional_fields_options = array_merge( $this->_additional_fields_options, $class->get_fields( $args ) );
}
/**
* Add CSS filter controls (i.e. saturation, brightness, opacity) to the `_additional_fields_options` array.
*
* @since 3.23 Introduce responsive settings on all animation options. Add allowed CSS unit for
* Blur option.
*
* @return void
*/
protected function _add_filter_fields() {
// Filter fields are added by default if module has partial or full VB support.
if ( $this->has_vb_support() ) {
$this->advanced_fields['filters'] = self::$_->array_get( $this->advanced_fields, 'filters', array() );
} elseif ( ! $this->has_advanced_fields ) {
// Disable if module doesn't set advanced_fields property and has no VB support.
return;
}
// Module has to explicitly set false to disable filters options.
if ( false === self::$_->array_get( $this->advanced_fields, 'filters', false ) ) {
return;
}
$i18n =& self::$i18n;
if ( ! isset( $i18n['filter'] ) ) {
// phpcs:disable WordPress.WP.I18n.MissingTranslatorsComment
$i18n['filter'] = array(
'toggle' => array(
'title' => esc_html__( 'Filters', 'et_builder' ),
),
'hue' => array(
'description' => esc_html__( 'Shift all colors by this amount.', 'et_builder' ),
),
'saturate' => array(
'description' => esc_html__( 'Define how intense the color saturation should be.', 'et_builder' ),
),
'brightness' => array(
'label' => esc_html__( 'Brightness', 'et_builder' ),
'description' => esc_html__( 'Define how bright the colors should be.', 'et_builder' ),
),
'contrast' => array(
'label' => esc_html__( 'Contrast', 'et_builder' ),
'description' => esc_html__( 'Define how distinct bright and dark areas should be.', 'et_builder' ),
),
'invert' => array(
'label' => esc_html__( 'Invert', 'et_builder' ),
'description' => esc_html__( 'Invert the hue, saturation, and brightness by this amount.', 'et_builder' ),
),
'sepia' => array(
'label' => esc_html__( 'Sepia', 'et_builder' ),
'description' => esc_html__( 'Travel back in time by this amount.', 'et_builder' ),
),
'opacity' => array(
'label' => esc_html__( 'Opacity', 'et_builder' ),
'description' => esc_html__( 'Define how transparent or opaque this should be.', 'et_builder' ),
),
'blur' => array(
'description' => esc_html__( 'Blur by this amount.', 'et_builder' ),
),
'blend' => array(
'label' => esc_html__( 'Blend Mode', 'et_builder' ),
'description' => esc_html__( 'Modify how this element blends with any layers beneath it. To reset, choose the "Normal" option.', 'et_builder' ),
),
);
// phpcs:enable
}
$filter_settings = self::$_->array_get( $this->advanced_fields, 'filters' );
$tab_slug = self::$_->array_get( $filter_settings, 'tab_slug', 'advanced' );
$toggle_slug = self::$_->array_get( $filter_settings, 'toggle_slug', 'filters' );
$toggle_name = self::$_->array_get( $filter_settings, 'toggle_name', $i18n['filter']['toggle']['title'] );
$sub_toggle = self::$_->array_get( $filter_settings, 'sub_toggle', 'filters' );
$modal_toggles = array(
$toggle_slug => array(
'title' => $toggle_name,
'priority' => 105,
),
);
$this->_add_settings_modal_toggles( $tab_slug, $modal_toggles );
$additional_options = array();
$additional_options['filter_hue_rotate'] = array(
'label' => et_builder_i18n( 'Hue' ),
'description' => $i18n['filter']['hue']['description'],
'type' => 'range',
'option_category' => 'layout',
'range_settings' => array(
'min' => 0,
'max' => 359,
'step' => 1,
),
'default' => '0deg',
'default_on_child' => true,
'validate_unit' => true,
'fixed_unit' => 'deg',
'fixed_range' => true,
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'reset_animation' => false,
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
);
$additional_options['filter_saturate'] = array(
'label' => et_builder_i18n( 'Saturation' ),
'description' => $i18n['filter']['saturate']['description'],
'type' => 'range',
'option_category' => 'layout',
'range_settings' => array(
'min' => 0,
'max' => 200,
'step' => 1,
),
'default' => '100%',
'default_on_child' => true,
'validate_unit' => true,
'fixed_unit' => '%',
'fixed_range' => true,
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'reset_animation' => false,
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
);
$additional_options['filter_brightness'] = array(
'label' => $i18n['filter']['brightness']['label'],
'description' => $i18n['filter']['brightness']['description'],
'type' => 'range',
'option_category' => 'layout',
'range_settings' => array(
'min' => 0,
'max' => 200,
'step' => 1,
),
'default' => '100%',
'default_on_child' => true,
'validate_unit' => true,
'fixed_unit' => '%',
'fixed_range' => true,
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'reset_animation' => false,
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
);
$additional_options['filter_contrast'] = array(
'label' => $i18n['filter']['contrast']['label'],
'description' => $i18n['filter']['contrast']['description'],
'type' => 'range',
'option_category' => 'layout',
'range_settings' => array(
'min' => 0,
'max' => 200,
'step' => 1,
),
'default' => '100%',
'default_on_child' => true,
'validate_unit' => true,
'fixed_unit' => '%',
'fixed_range' => true,
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'reset_animation' => false,
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
);
$additional_options['filter_invert'] = array(
'label' => $i18n['filter']['invert']['label'],
'description' => $i18n['filter']['invert']['description'],
'type' => 'range',
'option_category' => 'layout',
'range_settings' => array(
'min' => 0,
'max' => 100,
'step' => 1,
),
'default' => '0%',
'default_on_child' => true,
'validate_unit' => true,
'fixed_unit' => '%',
'fixed_range' => true,
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'reset_animation' => false,
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
);
$additional_options['filter_sepia'] = array(
'label' => $i18n['filter']['sepia']['label'],
'description' => $i18n['filter']['sepia']['description'],
'type' => 'range',
'option_category' => 'layout',
'range_settings' => array(
'min' => 0,
'max' => 100,
'step' => 1,
),
'default' => '0%',
'default_on_child' => true,
'validate_unit' => true,
'fixed_unit' => '%',
'fixed_range' => true,
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'reset_animation' => false,
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
);
$additional_options['filter_opacity'] = array(
'label' => $i18n['filter']['opacity']['label'],
'description' => $i18n['filter']['opacity']['description'],
'type' => 'range',
'option_category' => 'layout',
'range_settings' => array(
'min' => 0,
'max' => 100,
'step' => 1,
'min_limit' => 0,
'max_limit' => 100,
),
'default' => '100%',
'default_on_child' => true,
'validate_unit' => true,
'fixed_unit' => '%',
'fixed_range' => true,
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'reset_animation' => false,
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
);
$additional_options['filter_blur'] = array(
'label' => et_builder_i18n( 'Blur' ),
'description' => $i18n['filter']['blur']['description'],
'type' => 'range',
'option_category' => 'layout',
'range_settings' => array(
'min' => 0,
'max' => 50,
'step' => 1,
),
'default' => '0px',
'default_unit' => 'px',
'default_on_child' => true,
'validate_unit' => true,
'allowed_units' => array( 'em', 'rem', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'ex', 'vh', 'vw' ),
'default_unit' => 'px',
'fixed_range' => true,
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'reset_animation' => false,
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
);
$additional_options['mix_blend_mode'] = array(
'label' => $i18n['filter']['blend']['label'],
'description' => $i18n['filter']['blend']['description'],
'type' => 'select',
'option_category' => 'layout',
'default' => 'normal',
'default_on_child' => true,
'options' => array(
'normal' => et_builder_i18n( 'Normal' ),
'multiply' => et_builder_i18n( 'Multiply' ),
'screen' => et_builder_i18n( 'Screen' ),
'overlay' => et_builder_i18n( 'Overlay' ),
'darken' => et_builder_i18n( 'Darken' ),
'lighten' => et_builder_i18n( 'Lighten' ),
'color-dodge' => et_builder_i18n( 'Color Dodge' ),
'color-burn' => et_builder_i18n( 'Color Burn' ),
'hard-light' => et_builder_i18n( 'Hard Light' ),
'soft-light' => et_builder_i18n( 'Soft Light' ),
'difference' => et_builder_i18n( 'Difference' ),
'exclusion' => et_builder_i18n( 'Exclusion' ),
'hue' => et_builder_i18n( 'Hue' ),
'saturation' => et_builder_i18n( 'Saturation' ),
'color' => et_builder_i18n( 'Color' ),
'luminosity' => et_builder_i18n( 'Luminosity' ),
),
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'reset_animation' => false,
'mobile_options' => true,
);
$this->_additional_fields_options = array_merge( $this->_additional_fields_options, $additional_options );
// Maybe add child filters (i.e. targeting only images within a module).
if ( ! isset( $this->advanced_fields['filters']['child_filters_target'] ) ) {
return;
}
$child_filter = $this->advanced_fields['filters']['child_filters_target'];
// Allow to modify child filter options label. Default is Image.
$child_filter_label = isset( $child_filter['label'] ) ? $child_filter['label'] : et_builder_i18n( 'Image' );
$child_filter_sub_toggle = et_()->array_get( $child_filter, 'sub_toggle', null );
$additional_child_options = array(
'child_filter_hue_rotate' => array(
'label' => $child_filter_label . ' ' . et_builder_i18n( 'Hue' ),
'description' => $i18n['filter']['hue']['description'],
'type' => 'range',
'option_category' => 'layout',
'range_settings' => array(
'min' => 0,
'max' => 359,
'step' => 1,
),
'default' => '0deg',
'default_on_child' => true,
'validate_unit' => true,
'fixed_unit' => 'deg',
'fixed_range' => true,
'tab_slug' => $child_filter['tab_slug'],
'toggle_slug' => $child_filter['toggle_slug'],
'sub_toggle' => $child_filter_sub_toggle,
'reset_animation' => false,
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
),
'child_filter_saturate' => array(
'label' => $child_filter_label . ' ' . et_builder_i18n( 'Saturation' ),
'description' => $i18n['filter']['saturate']['description'],
'type' => 'range',
'option_category' => 'layout',
'range_settings' => array(
'min' => 0,
'max' => 200,
'step' => 1,
),
'default' => '100%',
'default_on_child' => true,
'validate_unit' => true,
'fixed_unit' => '%',
'fixed_range' => true,
'tab_slug' => $child_filter['tab_slug'],
'toggle_slug' => $child_filter['toggle_slug'],
'sub_toggle' => $child_filter_sub_toggle,
'reset_animation' => false,
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
),
'child_filter_brightness' => array(
'label' => $child_filter_label . ' ' . $i18n['filter']['brightness']['label'],
'description' => $i18n['filter']['brightness']['description'],
'type' => 'range',
'option_category' => 'layout',
'range_settings' => array(
'min' => 0,
'max' => 200,
'step' => 1,
),
'default' => '100%',
'default_on_child' => true,
'validate_unit' => true,
'fixed_unit' => '%',
'fixed_range' => true,
'tab_slug' => $child_filter['tab_slug'],
'toggle_slug' => $child_filter['toggle_slug'],
'sub_toggle' => $child_filter_sub_toggle,
'reset_animation' => false,
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
),
'child_filter_contrast' => array(
'label' => $child_filter_label . ' ' . $i18n['filter']['contrast']['label'],
'description' => $i18n['filter']['contrast']['description'],
'type' => 'range',
'option_category' => 'layout',
'range_settings' => array(
'min' => 0,
'max' => 200,
'step' => 1,
),
'default' => '100%',
'default_on_child' => true,
'validate_unit' => true,
'fixed_unit' => '%',
'fixed_range' => true,
'tab_slug' => $child_filter['tab_slug'],
'toggle_slug' => $child_filter['toggle_slug'],
'sub_toggle' => $child_filter_sub_toggle,
'reset_animation' => false,
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
),
'child_filter_invert' => array(
'label' => $child_filter_label . ' ' . $i18n['filter']['invert']['label'],
'description' => $i18n['filter']['invert']['description'],
'type' => 'range',
'option_category' => 'layout',
'range_settings' => array(
'min' => 0,
'max' => 100,
'step' => 1,
),
'default' => '0%',
'default_on_child' => true,
'validate_unit' => true,
'fixed_unit' => '%',
'fixed_range' => true,
'tab_slug' => $child_filter['tab_slug'],
'toggle_slug' => $child_filter['toggle_slug'],
'sub_toggle' => $child_filter_sub_toggle,
'reset_animation' => false,
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
),
'child_filter_sepia' => array(
'label' => $child_filter_label . ' ' . $i18n['filter']['sepia']['label'],
'description' => $i18n['filter']['sepia']['description'],
'type' => 'range',
'option_category' => 'layout',
'range_settings' => array(
'min' => 0,
'max' => 100,
'step' => 1,
),
'default' => '0%',
'default_on_child' => true,
'validate_unit' => true,
'fixed_unit' => '%',
'fixed_range' => true,
'tab_slug' => $child_filter['tab_slug'],
'toggle_slug' => $child_filter['toggle_slug'],
'sub_toggle' => $child_filter_sub_toggle,
'reset_animation' => false,
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
),
'child_filter_opacity' => array(
'label' => $child_filter_label . ' ' . $i18n['filter']['opacity']['label'],
'description' => $i18n['filter']['opacity']['description'],
'type' => 'range',
'option_category' => 'layout',
'range_settings' => array(
'min' => 0,
'max' => 100,
'step' => 1,
'min_limit' => 0,
'max_limit' => 100,
),
'default' => '100%',
'default_on_child' => true,
'validate_unit' => true,
'fixed_unit' => '%',
'fixed_range' => true,
'tab_slug' => $child_filter['tab_slug'],
'toggle_slug' => $child_filter['toggle_slug'],
'sub_toggle' => $child_filter_sub_toggle,
'reset_animation' => false,
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
),
'child_filter_blur' => array(
'label' => $child_filter_label . ' ' . et_builder_i18n( 'Blur' ),
'description' => $i18n['filter']['blur']['description'],
'type' => 'range',
'option_category' => 'layout',
'range_settings' => array(
'min' => 0,
'max' => 50,
'step' => 1,
),
'default' => '0px',
'default_on_child' => true,
'validate_unit' => true,
'allowed_units' => array( 'em', 'rem', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'ex', 'vh', 'vw' ),
'default_unit' => 'px',
'fixed_range' => true,
'tab_slug' => $child_filter['tab_slug'],
'toggle_slug' => $child_filter['toggle_slug'],
'sub_toggle' => $child_filter_sub_toggle,
'reset_animation' => false,
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
),
'child_mix_blend_mode' => array(
'label' => $child_filter_label . ' ' . $i18n['filter']['blend']['label'],
'description' => $i18n['filter']['blend']['description'],
'type' => 'select',
'option_category' => 'layout',
'default' => 'normal',
'default_on_child' => true,
'options' => array(
'normal' => et_builder_i18n( 'Normal' ),
'multiply' => et_builder_i18n( 'Multiply' ),
'screen' => et_builder_i18n( 'Screen' ),
'overlay' => et_builder_i18n( 'Overlay' ),
'darken' => et_builder_i18n( 'Darken' ),
'lighten' => et_builder_i18n( 'Lighten' ),
'color-dodge' => et_builder_i18n( 'Color Dodge' ),
'color-burn' => et_builder_i18n( 'Color Burn' ),
'hard-light' => et_builder_i18n( 'Hard Light' ),
'soft-light' => et_builder_i18n( 'Soft Light' ),
'difference' => et_builder_i18n( 'Difference' ),
'exclusion' => et_builder_i18n( 'Exclusion' ),
'hue' => et_builder_i18n( 'Hue' ),
'saturation' => et_builder_i18n( 'Saturation' ),
'color' => et_builder_i18n( 'Color' ),
'luminosity' => et_builder_i18n( 'Luminosity' ),
),
'tab_slug' => $child_filter['tab_slug'],
'toggle_slug' => $child_filter['toggle_slug'],
'sub_toggle' => $child_filter_sub_toggle,
'reset_animation' => false,
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => false,
),
);
if ( isset( $child_filter['depends_show_if'] ) ) {
foreach ( $additional_child_options as $option => $value ) {
$additional_child_options[ $option ]['depends_show_if'] = $child_filter['depends_show_if'];
}
}
$this->_additional_fields_options = array_merge( $this->_additional_fields_options, $additional_child_options );
}
/**
* Add the divider options to the additional_fields_options array.
*/
protected function _add_divider_fields() {
if ( ! $this->has_vb_support() && ! $this->has_advanced_fields ) {
return;
}
// Make sure we only add this to sections.
if ( 'et_pb_section' !== $this->slug ) {
return;
}
$tab_slug = 'advanced';
$toggle_slug = 'dividers';
$divider_toggle = array(
$toggle_slug => array(
'title' => esc_html__( 'Dividers', 'et_builder' ),
'priority' => 65,
),
);
// Add the toggle sections.
$this->_add_settings_modal_toggles( $tab_slug, $divider_toggle );
if ( ! isset( $this->advanced_fields['dividers'] ) ) {
$this->advanced_fields['dividers'] = array();
}
$additional_options = ET_Builder_Module_Fields_Factory::get( 'Divider' )->get_fields(
array(
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
)
);
// Return our merged options and toggles.
$this->_additional_fields_options = array_merge( $this->_additional_fields_options, $additional_options );
}
/**
* Add additional Text Shadow fields to all modules
*/
protected function _add_text_shadow_fields() {
// Get text shadow settings. Fallback to default if needed.
$this->advanced_fields['text_shadow'] = self::$_->array_get(
$this->advanced_fields,
'text_shadow',
array(
'default' => array(),
)
);
// Text shadow settings have to be array.
if ( ! is_array( $this->advanced_fields['text_shadow'] ) ) {
return;
}
// Loop test settings, do multiple text shadow field declaration in one palce.
foreach ( $this->advanced_fields['text_shadow'] as $text_shadow_name => $text_shadow_fields ) {
// Enable module to disable text shadow. Also disable text shadow if no text group is
// found because default text shadow lives on text group.
if ( 'default' === $text_shadow_name && ( false === $text_shadow_fields || empty( $this->settings_modal_toggles['advanced']['toggles']['text'] ) ) ) {
return;
}
if ( 'default' !== $text_shadow_name ) {
// Automatically add prefix and toggle slug.
$text_shadow_fields['prefix'] = $text_shadow_name;
$text_shadow_fields['toggle_slug'] = $text_shadow_name;
}
// Add text shadow fields.
$this->_additional_fields_options = array_merge(
$this->_additional_fields_options,
$this->text_shadow->get_fields( $text_shadow_fields )
);
}
}
/**
* Add box shadow fields based on configuration on $this->advanced_fields['box_shadow']
*
* @since 3.1
*/
protected function _add_box_shadow_fields() {
// BOX shadow fields are added by default to all modules.
$this->advanced_fields['box_shadow'] = self::$_->array_get(
$this->advanced_fields,
'box_shadow',
array(
'default' => array(),
)
);
// Box shadow settings have to be array.
if ( ! is_array( $this->advanced_fields['box_shadow'] ) ) {
return;
}
$i18n =& self::$i18n;
if ( ! isset( $i18n['box_shadow'] ) ) {
// phpcs:disable WordPress.WP.I18n.MissingTranslatorsComment
$i18n['box_shadow'] = array(
'title' => esc_html__( 'Box Shadow', 'et_builder' ),
);
// phpcs:enable
}
// Loop box shadow settings.
foreach ( $this->advanced_fields['box_shadow'] as $fields_name => $settings ) {
// Enable module to disable box shadow.
if ( false === $settings ) {
continue;
}
$is_box_shadow_default = 'default' === $fields_name;
// Add Box Shadow toggle for default Box Shadow fields.
if ( $is_box_shadow_default ) {
$this->settings_modal_toggles['advanced']['toggles']['box_shadow'] = array(
'title' => $i18n['box_shadow']['title'],
'priority' => 100,
);
}
// Ensure box settings has minimum settings required.
$default_settings = array(
'option_category' => 'layout',
'tab_slug' => 'advanced',
'toggle_slug' => 'box_shadow',
);
$settings = wp_parse_args( $settings, $default_settings );
// Automatically add suffix attribute.
$settings['suffix'] = $is_box_shadow_default ? '' : "_{$fields_name}";
// Add default Box Shadow fields.
$this->_additional_fields_options = array_merge(
$this->_additional_fields_options,
ET_Builder_Module_Fields_Factory::get( 'BoxShadow' )->get_fields( $settings )
);
}
}
/**
* Add form field fields based on configuration on $this->advanced_fields['field'].
*
* @since 3.23
*/
protected function _add_form_field_fields() {
// Disable if module doesn't set advanced_fields property and has no VB support.
if ( ! $this->has_advanced_fields ) {
return;
}
// Form field settings have to be an array.
if ( ! is_array( self::$_->array_get( $this->advanced_fields, 'form_field' ) ) ) {
return;
}
$additional_options = array();
$hover = et_pb_hover_options();
$this->set_i18n_font();
$i18n =& self::$i18n;
// Fetch the form field.
foreach ( $this->advanced_fields['form_field'] as $option_name => $option_settings ) {
$toggle_slug = '';
$tab_slug = isset( $option_settings['tab_slug'] ) ? $option_settings['tab_slug'] : 'advanced';
$toggle_disabled = isset( $option_settings['disable_toggle'] ) && $option_settings['disable_toggle'];
// Add form field options group if it's enabled.
if ( ! $toggle_disabled ) {
$toggle_slug = isset( $option_settings['toggle_slug'] ) ? $option_settings['toggle_slug'] : $option_name;
$toggle_priority = isset( $option_settings['toggle_priority'] ) ? $option_settings['toggle_priority'] : 20;
$field_toggle = array(
$option_name => array(
'title' => esc_html( $option_settings['label'] ),
'priority' => $toggle_priority,
),
);
$this->_add_settings_modal_toggles( $tab_slug, $field_toggle );
}
// Background Color.
$bg_color_options = isset( $option_settings['background_color'] ) ? $option_settings['background_color'] : true;
if ( $bg_color_options ) {
$bg_color_args = is_array( $bg_color_options ) ? $bg_color_options : array();
$additional_options[ "{$option_name}_background_color" ] = array_merge(
array(
'label' => sprintf( esc_html__( '%1$s Background Color', 'et_builder' ), $option_settings['label'] ),
'description' => esc_html__( 'Pick a color to fill the module\'s input fields.', 'et_builder' ),
'type' => 'color-alpha',
'option_category' => 'field',
'custom_color' => true,
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
),
$bg_color_args
);
}
// Text Color.
$additional_options[ "{$option_name}_text_color" ] = array(
'label' => sprintf( $i18n['font']['color']['label'], $option_settings['label'] ),
'description' => esc_html__( 'Pick a color to be used for the text written inside input fields.', 'et_builder' ),
'type' => 'color-alpha',
'option_category' => 'field',
'custom_color' => true,
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
);
// Focus Background Color.
$additional_options[ "{$option_name}_focus_background_color" ] = array(
'label' => sprintf( esc_html__( '%1$s Focus Background Color', 'et_builder' ), $option_settings['label'] ),
'description' => esc_html__( 'When a visitor clicks into an input field, it becomes focused. You can pick a color to be used for the input field background while focused.', 'et_builder' ),
'type' => 'color-alpha',
'option_category' => 'field',
'custom_color' => true,
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
);
// Focus Text Color.
$additional_options[ "{$option_name}_focus_text_color" ] = array(
'label' => sprintf( esc_html__( '%1$s Focus Text Color', 'et_builder' ), $option_settings['label'] ),
'description' => esc_html__( 'When a visitor clicks into an input field, it becomes focused. You can pick a color to be used for the input text while focused.', 'et_builder' ),
'type' => 'color-alpha',
'option_category' => 'field',
'custom_color' => true,
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'hover' => 'tabs',
'mobile_options' => true,
'sticky' => true,
);
// Font - Add current font settings into advanced fields. The font_field is basically
// combination of fonts (options group) + fields (type), but plural suffix is removed
// because there are some case we just need one field declaration for child module.
$font_options = isset( $option_settings['font_field'] ) ? $option_settings['font_field'] : true;
if ( $font_options ) {
$font_args = is_array( $font_options ) ? $font_options : array();
$font_settings = array_merge(
array(
'label' => esc_html( $option_settings['label'] ),
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
// Text color will be handled by form field function.
'hide_text_color' => true,
),
$font_args
);
self::$_->array_set( $this->advanced_fields, "fonts.{$option_name}", $font_settings );
}
// Add custom margin-padding to form field options.
$margin_padding_options = isset( $option_settings['margin_padding'] ) ? $option_settings['margin_padding'] : true;
if ( $margin_padding_options ) {
$margin_padding_args = is_array( $margin_padding_options ) ? $margin_padding_options : array();
$margin_padding_settings = array_merge(
array(
'label' => $option_settings['label'],
'prefix' => $option_name,
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
),
$margin_padding_args
);
$additional_options = array_merge( $additional_options, $this->margin_padding->get_fields( $margin_padding_settings ) );
}
// Border Styles - Ensure borders attribute is exist in advanced fields. If it's not,
// add borders property and set empty default.
$borders_options = isset( $option_settings['border_styles'] ) ? $option_settings['border_styles'] : true;
if ( $borders_options ) {
if ( ! isset( $this->advanced_fields['borders'] ) ) {
self::$_->array_set( $this->advanced_fields, 'borders.default', array() );
}
// Border Styles - Add current borders settings into advanced fields.
$border_style_options = self::$_->array_get( $option_settings, "border_styles.{$option_name}", array() );
$border_style_name = ! empty( $border_style_options['name'] ) ? $border_style_options['name'] : $option_name;
$border_style_settings = array_merge(
array(
'option_category' => 'field',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'defaults' => array(
'border_radii' => 'on|3px|3px|3px|3px',
'border_styles' => array(
'width' => '0px',
'color' => '#333333',
'style' => 'solid',
),
),
'fields_after' => array(
'use_focus_border_color' => array(
'label' => esc_html__( 'Use Focus Borders', 'et_builder' ),
'description' => esc_html__( 'Enabling this option will add borders to input fields when focused.', 'et_builder' ),
'type' => 'yes_no_button',
'option_category' => 'color_option',
'options' => array(
'off' => et_builder_i18n( 'No' ),
'on' => et_builder_i18n( 'Yes' ),
),
'affects' => array(
"border_radii_{$toggle_slug}_focus",
"border_styles_{$toggle_slug}_focus",
),
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'default_on_front' => 'off',
),
),
),
$border_style_options
);
self::$_->array_set( $this->advanced_fields, "borders.{$border_style_name}", $border_style_settings );
// Border Styles Focus - Add current borders focus settings into advanced fields.
$border_style_focus_options = self::$_->array_get( $option_settings, "border_styles.{$option_name}_focus", array() );
$border_style_focus_settings = array_merge(
array(
'option_category' => 'field',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
'depends_on' => array( 'use_focus_border_color' ),
'depends_show_if' => 'on',
'defaults' => array(
'border_radii' => 'on|3px|3px|3px|3px',
'border_styles' => array(
'width' => '0px',
'color' => '#333333',
'style' => 'solid',
),
),
),
$border_style_focus_options
);
self::$_->array_set( $this->advanced_fields, "borders.{$border_style_name}_focus", $border_style_focus_settings );
}
// Box Shadow - Ensure box shadow attribute is exist in advanced fields. If it's not,
// add box_shadow property and set empty default.
$box_shadow_options = isset( $option_settings['box_shadow'] ) ? $option_settings['box_shadow'] : true;
if ( $box_shadow_options ) {
if ( ! isset( $this->advanced_fields['box_shadow'] ) ) {
self::$_->array_set( $this->advanced_fields, 'box_shadow.default', array() );
}
$box_shadow_args = is_array( $box_shadow_options ) ? $box_shadow_options : array();
$box_shadow_name = ! empty( $box_shadow_options['name'] ) ? $box_shadow_options['name'] : $option_name;
// Box Shadow - Add current box shadow settings into advanced fields.
$box_shadow_settings = array_merge(
array(
'label' => sprintf( esc_html__( '%1$s Box Shadow', 'et_builder' ), $option_settings['label'] ),
'option_category' => 'layout',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
),
$box_shadow_args
);
self::$_->array_set( $this->advanced_fields, "box_shadow.{$box_shadow_name}", $box_shadow_settings );
}
}
$this->_additional_fields_options = array_merge( $this->_additional_fields_options, $additional_options );
}
/**
* Get css transition properties for box shadow fields.
*
* @param string|null $module Module slug.
*
* @return array
*/
public function get_transition_box_shadow_fields_css_props( $module = 'default' ) {
/**
* Instance of box shadow field class. @var ET_Builder_Module_Field_BoxShadow $box_shadow
*/
$box_shadow = ET_Builder_Module_Fields_Factory::get( 'BoxShadow' );
$selector = self::$_->array_get( $this->advanced_fields, "box_shadow.$module.css.main", '%%order_class%%' );
$overlay = self::$_->array_get( $this->advanced_fields, "box_shadow.$module.css.overlay", false );
$suffix = 'default' === $module ? '' : "_$module";
if ( in_array( $overlay, array( 'inset', 'always' ), true ) ) {
$selector .= ', ' . $box_shadow->get_overlay_selector( $selector );
}
return array(
"box_shadow_horizontal{$suffix}" => array( 'box-shadow' => $selector ),
"box_shadow_vertical{$suffix}" => array( 'box-shadow' => $selector ),
"box_shadow_blur{$suffix}" => array( 'box-shadow' => $selector ),
"box_shadow_spread{$suffix}" => array( 'box-shadow' => $selector ),
"box_shadow_color{$suffix}" => array( 'box-shadow' => $selector ),
);
}
/**
* Get css transition properties for text shadow fields.
*
* @param string|null $module Module slug.
*
* @return array
*/
public function get_transition_text_shadow_fields_css_props( $module = null ) {
$source = null === $module ? 'text.css' : "fonts.$module.css";
$default = self::$_->array_get( $this->advanced_fields, "$source.main", '%%order_class%%' );
$selector = self::$_->array_get( $this->advanced_fields, "$source.text_shadow", $default );
$prefix = null === $module ? '' : "{$module}_";
return array(
"{$prefix}text_shadow_horizontal_length" => array( 'text-shadow' => $selector ),
"{$prefix}text_shadow_vertical_length" => array( 'text-shadow' => $selector ),
"{$prefix}text_shadow_blur_strength" => array( 'text-shadow' => $selector ),
"{$prefix}text_shadow_color" => array( 'text-shadow' => $selector ),
);
}
/**
* Get css transition properties for filters fields.
*
* @param string|null $module Module slug.
*
* @return array
*/
public function get_transition_filters_fields_css_props( $module = null ) {
$slug = empty( $module ) ? 'filter' : 'child_filter';
$source = empty( $module ) ? 'filters.css.main' : "$module.css.main";
$filters = array( 'hue_rotate', 'saturate', 'brightness', 'contrast', 'invert', 'sepia', 'opacity', 'blur' );
$fields = array();
$main = self::$_->array_get( $this->advanced_fields, $source, '%%order_class%%' );
$selector = $module ? self::$_->array_get( $this->advanced_fields, 'filters.child_filters_target.css.main', $main ) : $main;
foreach ( $filters as $filter ) {
$fields[ "{$slug}_{$filter}" ] = array( 'filter' => $selector );
}
return $fields;
}
/**
* Get css transition properties for borders fields.
*
* @param string|null $module Module slug.
*
* @return array
*/
public function get_transition_borders_fields_css_props( $module = 'default' ) {
$suffix = 'default' === $module ? '' : "_$module";
$radius = self::$_->array_get( $this->advanced_fields, "borders.$module.css.main.border_radii", '%%order_class%%' );
$style = self::$_->array_get( $this->advanced_fields, "borders.$module.css.main.border_styles", '%%order_class%%' );
return array(
"border_radii{$suffix}" => array( 'border-radius' => implode( ', ', array( $radius ) ) ),
"border_width_all{$suffix}" => array( 'border' => implode( ', ', array( $style ) ) ),
"border_color_all{$suffix}" => array( 'border' => implode( ', ', array( $style ) ) ),
"border_width_top{$suffix}" => array( 'border' => implode( ', ', array( $style ) ) ),
"border_color_top{$suffix}" => array( 'border' => implode( ', ', array( $style ) ) ),
"border_width_right{$suffix}" => array( 'border' => implode( ', ', array( $style ) ) ),
"border_color_right{$suffix}" => array( 'border' => implode( ', ', array( $style ) ) ),
"border_width_bottom{$suffix}" => array( 'border' => implode( ', ', array( $style ) ) ),
"border_color_bottom{$suffix}" => array( 'border' => implode( ', ', array( $style ) ) ),
"border_width_left{$suffix}" => array( 'border' => implode( ', ', array( $style ) ) ),
"border_color_left{$suffix}" => array( 'border' => implode( ', ', array( $style ) ) ),
);
}
/**
* Get margin and padding transition css properties.
*
* @param string|null $module Module slug.
*
* @return array
*/
public function get_transition_margin_padding_fields_css_props( $module = null ) {
$key = empty( $module ) ? '' : "$module.";
$suffix = empty( $module ) ? '' : "_$module";
$margin = self::$_->array_get( $this->advanced_fields, "margin_padding.{$key}css.margin", '%%order_class%%' );
$padding = self::$_->array_get( $this->advanced_fields, "margin_padding.{$key}css.padding", '%%order_class%%' );
return array(
"custom_margin{$suffix}" => array( 'margin' => implode( ', ', array( $margin ) ) ),
"custom_padding{$suffix}" => array( 'padding' => implode( ', ', array( $padding ) ) ),
);
}
/**
* Get transform transition css properties.
*
* @param string|null $module Module slug.
*
* @return array
*/
public function get_transition_transform_css_props( $module = null ) {
$key = empty( $module ) ? '' : "$module.";
$suffix = empty( $module ) ? '' : "_$module";
$selector = self::$_->array_get( $this->advanced_fields, "transform.{$key}css.main", '%%order_class%%' );
/** Transform field. @see ET_Builder_Module_Field_Transform */
$defaults = array( 'scale', 'translate', 'rotate', 'skew', 'origin' );
$fields = array();
foreach ( $defaults as $name ) {
$fields += array( "transform_{$name}{$suffix}" => array( 'transform' => implode( ', ', (array) $selector ) ) );
}
return $fields;
}
/**
* Get position transition css properties.
*
* @param string|null $module Module slug.
*
* @return array
*/
public function get_transition_position_css_props( $module = null ) {
$key = empty( $module ) ? '' : "$module.";
$suffix = empty( $module ) ? '' : "_$module";
$selector = self::$_->array_get( $this->advanced_fields, "position_fields.{$key}css.main", '%%order_class%%' );
$string_selector = implode( ', ', (array) $selector );
$fields = array();
$fields += array(
"horizontal_offset{$suffix}" => array(
'left' => $string_selector,
'right' => $string_selector,
),
);
$fields += array(
"vertical_offset{$suffix}" => array(
'top' => $string_selector,
'bottom' => $string_selector,
),
);
return $fields;
}
/**
* Get font transition css properties.
*
* @return array
*/
public function get_transition_font_fields_css_props() {
$items = ! empty( $this->advanced_fields['fonts'] ) ? $this->advanced_fields['fonts'] : false;
if ( ! $items ) {
return array();
}
$font_options = array(
array(
'option' => 'text_color',
'slug' => 'color',
'prop' => 'color',
),
array(
'option' => 'font_size',
'slug' => 'font_size',
'prop' => 'font-size',
),
array(
'option' => 'line_height',
'slug' => 'line_height',
'prop' => 'line-height',
),
array(
'option' => 'letter_spacing',
'slug' => 'letter_spacing',
'prop' => 'letter-spacing',
),
array(
'option' => 'text_shadow_horizontal_length',
'slug' => 'text_shadow',
'prop' => 'text-shadow',
),
array(
'option' => 'text_shadow_vertical_length',
'slug' => 'text_shadow',
'prop' => 'text-shadow',
),
array(
'option' => 'text_shadow_blur_strength',
'slug' => 'text_shadow',
'prop' => 'text-shadow',
),
array(
'option' => 'text_shadow_color',
'slug' => 'text_shadow',
'prop' => 'text-shadow',
),
array(
'option' => 'border_weight',
'slug' => 'quote',
'prop' => 'border-width',
),
array(
'option' => 'border_color',
'slug' => 'quote',
'prop' => 'border-color',
),
);
$fields = array();
foreach ( $items as $item => $field ) {
foreach ( $font_options as $key ) {
$default = ! empty( $field['css'] ) && ! empty( $field['css']['main'] ) ? $field['css']['main'] : '%%order_class%%';
$value = ! empty( $field['css'] ) && ! empty( $field['css'][ $key['slug'] ] ) ? $field['css'][ $key['slug'] ] : $default;
$fields[ "{$item}_{$key['option']}" ] = array(
$key['prop'] => $value,
);
}
}
return $fields;
}
/**
* Get height transition css properties.
*
* @param string $prefix The prefix string that may be added to field name.
*
* @return array
*/
public function get_transition_height_fields_css_props( $prefix = '' ) {
$options = self::$_->array_get( $this->advanced_fields, 'height' );
if ( ! is_array( $options ) ) {
return array();
}
$height = et_pb_height_options( $prefix );
$max_height = et_pb_max_height_options( $prefix );
$selector = self::$_->array_get( $options, 'css.main', '%%order_class%%' );
return array(
$height->get_field() => array( 'height' => $selector ),
$max_height->get_field() => array( 'max-height' => $selector ),
);
}
/**
* Get css transition properties for image fields.
*
* @return array
*/
public function get_transition_image_fields_css_props() {
$fields = array();
$fields = array_merge( $this->get_transition_filters_fields_css_props( 'image' ), $fields );
$fields = array_merge( $this->get_transition_borders_fields_css_props( 'image' ), $fields );
$fields = array_merge( $this->get_transition_box_shadow_fields_css_props( 'image' ), $fields );
return $fields;
}
/**
* Get css transition properties for button fields.
* *
*
* @return array
*/
public function get_transition_button_fields_css_props() {
$buttons = self::$_->array_get( $this->advanced_fields, 'button', array() );
$fields = array();
if ( empty( $buttons ) ) {
return array();
}
foreach ( $buttons as $key => $button ) {
$selector = self::$_->array_get( $button, 'css.main', '%%order_class%%' );
$box_shadow_style = array(
$selector,
$this->add_suffix_to_selectors( ' > .box-shadow-overlay', $selector ),
);
$field = array(
"{$key}_text_color" => array( 'color' => $selector ),
"{$key}_text_size" => array(
'font-size' => $selector,
'line-height' => $selector,
'padding' => $selector,
),
"{$key}_bg_color" => array( 'background-color' => $selector ),
"{$key}_border_width" => array( 'border' => $selector ),
"{$key}_border_color" => array( 'border' => $selector ),
"{$key}_border_radius" => array( 'border-radius' => $selector ),
"{$key}_letter_spacing" => array( 'letter-spacing' => $selector ),
"{$key}text_shadow_horizontal_length" => array( 'text-shadow' => $selector ),
"{$key}text_shadow_vertical_length" => array( 'text-shadow' => $selector ),
"{$key}text_shadow_blur_strength" => array( 'text-shadow' => $selector ),
"{$key}text_shadow_color" => array( 'text-shadow' => $selector ),
"box_shadow_style_$key" => array( 'box-shadow' => implode( ', ', $box_shadow_style ) ),
);
$fields = array_merge( $fields, $field );
}
return $fields;
}
/**
* Get transition form field CSS props.
*
* @since 3.23
*
* @return array Selector for each fields.
*/
public function get_transition_form_field_fields_css_props() {
$fields_input = self::$_->array_get( $this->advanced_fields, 'form_field', array() );
$fields = array();
// Ensure fields input is exist.
if ( empty( $fields_input ) ) {
return array();
}
foreach ( $fields_input as $key => $form_field ) {
$selector = self::$_->array_get( $form_field, 'css.main', '%%order_class%% input' );
$placeholders = "$selector::placeholder, $selector::-webkit-input-placeholder, $selector::-moz-placeholder, $selector::-ms-input-placeholder";
// Set all individual fields that need transition during hover event.
$fields = array_merge(
$fields,
array(
"{$key}_background_color" => array( 'background-color' => $selector ),
"{$key}_text_color" => array( 'color' => implode( ', ', array( $placeholders, $selector ) ) ),
"{$key}_focus_background_color" => array( 'background-color' => $selector ),
"{$key}_focus_text_color" => array( 'color' => implode( ', ', array( $placeholders, $selector ) ) ),
"{$key}_custom_margin" => array( 'margin' => $selector ),
"{$key}_custom_padding" => array( 'padding' => $selector ),
)
);
// Merge group fields such as borders, box shadow, and text shadow.
$fields = array_merge(
$fields,
$this->get_transition_borders_fields_css_props( $key ),
$this->get_transition_borders_fields_css_props( "{$key}_focus" ),
$this->get_transition_box_shadow_fields_css_props( $key )
);
}
return $fields;
}
/**
* Get css transition properties for gutter wudth fields.
* *
*
* @return array
*/
public function get_transition_gutter_fields_css_props() {
$gutter_selector = 'et_pb_section' === $this->slug ? '%%order_class%% .et_pb_gutter_hover *' : '%%order_class%%.et_pb_gutter_hover *';
// animate width, padding and margin if gutter width has hover options.
return array(
'gutter_width' => array(
'width' => $gutter_selector,
'margin' => $gutter_selector,
'padding' => $gutter_selector,
),
);
}
/**
* Get CSS fields transition.
*
* @since 3.23 Add form field options group and background image on the fields list.
*/
public function get_transition_fields_css_props() {
$default = $this->main_css_element;
$text_main = self::$_->array_get( $this->advanced_fields, 'text.css.main', $default );
$fields = array(
'background_layout' => array( 'color' => $text_main ),
'background' => array(
'background-color' => self::$_->array_get(
$this->advanced_fields,
'background.css.main',
$default
),
'background-image' => self::$_->array_get(
$this->advanced_fields,
'background.css.main',
$default
),
),
'max_width' => array( 'max-width' => $default ),
'width' => array( 'width' => $default ),
'text_color' => array(
'color' => self::$_->array_get(
$this->advanced_fields,
'text.css.color',
$text_main
),
),
);
$fields = array_merge( $this->get_transition_filters_fields_css_props(), $fields );
$fields = array_merge( $this->get_transition_box_shadow_fields_css_props(), $fields );
$fields = array_merge( $this->get_transition_text_shadow_fields_css_props(), $fields );
$fields = array_merge( $this->get_transition_image_fields_css_props(), $fields );
$fields = array_merge( $this->get_transition_borders_fields_css_props(), $fields );
$fields = array_merge( $this->get_transition_margin_padding_fields_css_props(), $fields );
$fields = array_merge( $this->get_transition_button_fields_css_props(), $fields );
$fields = array_merge( $this->get_transition_form_field_fields_css_props(), $fields );
$fields = array_merge( $this->get_transition_font_fields_css_props(), $fields );
$fields = array_merge( $this->get_transition_gutter_fields_css_props(), $fields );
$fields = array_merge( $this->get_transition_height_fields_css_props(), $fields );
$fields = array_merge( $this->get_transition_transform_css_props(), $fields );
$fields = array_merge( $this->get_transition_position_css_props(), $fields );
return apply_filters( 'et_builder_hover_transitions_map', $fields );
}
/**
* Add link options fields to all modules
*
* @since 3.15.1
*/
protected function _add_link_options_fields() {
// Link Options are added by default if module has partial or full VB support.
if ( $this->has_vb_support() ) {
$this->advanced_fields['link_options'] = self::$_->array_get( $this->advanced_fields, 'link_options', array() );
} elseif ( ! $this->has_advanced_fields ) {
// Disable if module doesn't set advanced_fields property and has no VB support.
return;
}
// Set link_options to false to disable Link options.
if ( false === self::$_->array_get( $this->advanced_fields, 'link_options' ) ) {
return;
}
// Link options settings have to be array.
if ( ! is_array( self::$_->array_get( $this->advanced_fields, 'link_options' ) ) ) {
return;
}
$this->settings_modal_toggles['general']['toggles']['link_options'] = array(
'title' => et_builder_i18n( 'Link' ),
'priority' => 70,
);
$additional_options = array();
$i18n =& self::$i18n;
if ( ! isset( $i18n['link'] ) ) {
// phpcs:disable WordPress.WP.I18n.MissingTranslatorsComment
$i18n['link'] = array(
'url' => array(
'label' => esc_html__( 'Module Link URL', 'et_builder' ),
'description' => esc_html__( 'When clicked the module will link to this URL.', 'et_builder' ),
),
'target' => array(
'label' => esc_html__( 'Module Link Target', 'et_builder' ),
'description' => esc_html__( 'Here you can choose whether or not your link opens in a new window', 'et_builder' ),
'options' => array(
'off' => esc_html__( 'In The Same Window', 'et_builder' ),
'on' => esc_html__( 'In The New Tab', 'et_builder' ),
),
),
);
// phpcs:enable
}
// Translate the whole label as a phrase instead of replacing placeholder with section / row / module translation
// Less error prone for translator and the translation. Phrase might be structured differently in some language.
switch ( $this->slug ) {
case 'et_pb_section':
$url_label = esc_html__( 'Section Link URL', 'et_builder' );
$target_label = esc_html__( 'Section Link Target', 'et_builder' );
break;
case 'et_pb_row':
case 'et_pb_row_inner':
$url_label = esc_html__( 'Row Link URL', 'et_builder' );
$target_label = esc_html__( 'Row Link Target', 'et_builder' );
break;
case 'et_pb_column':
case 'et_pb_column_inner':
$url_label = esc_html__( 'Column Link URL', 'et_builder' );
$target_label = esc_html__( 'Column Link Target', 'et_builder' );
break;
default:
$url_label = $i18n['link']['url']['label'];
$target_label = $i18n['link']['target']['label'];
break;
}
$additional_options['link_option_url'] = array(
'label' => $url_label,
'description' => $i18n['link']['url']['description'],
'type' => 'text',
'option_category' => 'configuration',
'toggle_slug' => 'link_options',
'dynamic_content' => 'url',
);
$additional_options['link_option_url_new_window'] = array(
'label' => $target_label,
'description' => $i18n['link']['target']['description'],
'type' => 'select',
'option_category' => 'configuration',
'options' => array(
'off' => $i18n['link']['target']['options']['off'],
'on' => $i18n['link']['target']['options']['on'],
),
'toggle_slug' => 'link_options',
'default_on_front' => 'off',
);
$this->_additional_fields_options = array_merge( $this->_additional_fields_options, $additional_options );
}
/**
* Get transition style.
*
* @param array $props Transition css properties.
* @param string $device Device.
*
* @since 3.23 Add $device parameter to support responsive settings.
*
* @return string
*/
public function get_transition_style( array $props = array(), $device = 'desktop' ) {
$duration = et_pb_transition_options()->get_duration( $this->props, $device );
$easing = et_pb_transition_options()->get_easing( $this->props, $device );
$delay = et_pb_transition_options()->get_delay( $this->props, $device );
$transition_css = array();
foreach ( $props as $prop ) {
$transition_css[] = sprintf(
'%1$s %2$s %3$s %4$s',
esc_attr( $prop ),
esc_attr( $duration ),
esc_attr( $easing ),
esc_attr( $delay )
);
}
// Sticky module that has custom width for sticky style will need its `left` property to
// be animated as well due to fixed positioning used for sticky state.
if ( $this->is_sticky_module && in_array( 'width', $props, true ) ) {
$transition_css[] = sprintf(
'left %1$s %2$s %3$s',
esc_attr( $duration ),
esc_attr( $easing ),
esc_attr( $delay )
);
}
return 'transition: ' . implode( ', ', $transition_css ) . ';';
}
/**
* Setup hover transitions.
*
* @param string $function_name Function name.
*
* @since 4.6.0 Add sticky style support
*/
public function setup_hover_transitions( $function_name ) {
$transitions_map = $this->get_transition_fields_css_props();
$selectors = array();
$transitions = array();
$hover = et_pb_hover_options();
$hover_suffix = $hover->get_suffix();
$sticky = et_pb_sticky_options();
$sticky_suffix = $sticky->get_suffix();
// we need to loop transitions array so cases of prefixed prop names can also be caught.
foreach ( $transitions_map as $prop_name => $css_props ) {
$prop_name_hover = "{$prop_name}{$hover_suffix}";
$prop_name_sticky = "{$prop_name}{$sticky_suffix}";
// background is a special case because it also contains the "background_color" property.
if ( 'background' === $prop_name ) {
// we can continue if hover background color or background image is not set.
if (
! $hover->get_value( 'background_color', $this->props )
&& ! $hover->get_value( 'background_image', $this->props )
&& ! $sticky->get_value( 'background_color', $this->props )
&& ! $sticky->get_value( 'background_image', $this->props )
) {
continue;
}
} elseif ( empty( $this->props[ $prop_name_hover ] ) && empty( $this->props[ $prop_name_sticky ] ) ) {
// continue if hover or sticky value is empty.
continue;
}
// continue if sticky / hover option is not enabled.
$is_hover_option_enabled = $hover->is_enabled( $prop_name, $this->props );
$is_sticky_option_enabled = $sticky->is_enabled( $prop_name, $this->props );
$is_sticky_transition_enabled = 'on' === et_()->array_get( $this->props, 'sticky_transition' );
if ( ! $is_hover_option_enabled && ! ( $is_sticky_transition_enabled && $is_sticky_option_enabled ) ) {
continue;
}
// add the css property for the transition.
$transitions = array_merge( $transitions, array_keys( $css_props ) );
foreach ( $css_props as $selector ) {
$selector = is_array( $selector ) ? $selector : array( $selector );
$selectors = array_merge( $selectors, $selector );
}
}
return [ $transitions, $selectors ];
}
/**
* Process hover transitions.
*
* @param string $function_name Module alias.
*
* @since 4.6.0 add sticky style support
*/
public function process_hover_transitions( $function_name ) {
list( $transitions, $selectors ) = $this->setup_hover_transitions( $function_name );
// don't apply transitions if none are needed.
if ( empty( $transitions ) ) {
return;
}
$transitions = array_unique( $transitions );
$transition_selectors = implode( ', ', array_unique( $selectors ) );
$transition_style = $this->get_transition_style( $transitions );
$el_style = array(
'selector' => $transition_selectors,
'declaration' => esc_html( $transition_style ),
);
self::set_style( $function_name, $el_style );
// Tablet.
$transition_style_tablet = $this->get_transition_style( $transitions, 'tablet' );
if ( $transition_style_tablet !== $transition_style ) {
$el_style = array(
'selector' => $transition_selectors,
'declaration' => esc_html( $transition_style_tablet ),
'media_query' => self::get_media_query( 'max_width_980' ),
);
self::set_style( $function_name, $el_style );
}
// Phone.
$transition_style_phone = $this->get_transition_style( $transitions, 'phone' );
if ( $transition_style_phone !== $transition_style || $transition_style_phone !== $transition_style_tablet ) {
$el_style = array(
'selector' => $transition_selectors,
'declaration' => esc_html( $transition_style_phone ),
'media_query' => self::get_media_query( 'max_width_767' ),
);
self::set_style( $function_name, $el_style );
}
}
/**
* Add custom css fields. e.g before, main_element and after.
*/
protected function _add_custom_css_fields() {
if ( isset( $this->custom_css_tab ) && ! $this->custom_css_tab ) {
return;
}
$custom_css_fields_processed = array();
$current_module_unique_class = '.' . $this->slug . '_' . "<%= typeof( module_order ) !== 'undefined' ? module_order : '' %>";
$main_css_element_output = isset( $this->main_css_element ) ? $this->main_css_element : '%%order_class%%';
$main_css_element_output = str_replace( '%%order_class%%', $current_module_unique_class, $main_css_element_output );
$custom_css_default_options = array(
'before' => array(
'label' => et_builder_i18n( 'Before' ),
'selector' => ':before',
'no_space_before_selector' => true,
),
'main_element' => array(
'label' => et_builder_i18n( 'Main Element' ),
),
'after' => array(
'label' => et_builder_i18n( 'After' ),
'selector' => ':after',
'no_space_before_selector' => true,
),
);
$custom_css_fields = apply_filters( 'et_default_custom_css_fields', $custom_css_default_options );
$this->custom_css_fields = $this->get_custom_css_fields_config();
if ( $this->custom_css_fields ) {
$custom_css_fields = array_merge( $custom_css_fields, $this->custom_css_fields );
}
$this->custom_css_fields = apply_filters( 'et_custom_css_fields_' . $this->slug, $custom_css_fields );
// optional settings names in custom css options.
$additional_option_slugs = array( 'description', 'priority' );
foreach ( $custom_css_fields as $slug => $option ) {
$selector_value = isset( $option['selector'] ) ? $option['selector'] : '';
$selector_contains_module_class = false !== strpos( $selector_value, '%%order_class%%' ) ? true : false;
$selector_output = '' !== $selector_value ? str_replace( '%%order_class%%', $current_module_unique_class, $option['selector'] ) : '';
$custom_css_fields_processed[ "custom_css_{$slug}" ] = array(
'label' => sprintf(
'%1$s:%2$s%3$s%4$s',
$option['label'],
! $selector_contains_module_class ? $main_css_element_output : '',
! isset( $option['no_space_before_selector'] ) && isset( $option['selector'] ) ? ' ' : '',
$selector_output
),
'type' => 'custom_css',
'tab_slug' => 'custom_css',
'toggle_slug' => 'custom_css',
'option_category' => 'layout',
'no_colon' => true,
);
// update toggle slug and option category for $this->custom_css_fields.
$this->custom_css_fields[ $slug ]['toggle_slug'] = 'custom_css';
$this->custom_css_fields[ $slug ]['option_category'] = 'layout';
// add optional settings if needed.
foreach ( $additional_option_slugs as $option_slug ) {
if ( isset( $option[ $option_slug ] ) ) {
$custom_css_fields_processed[ "custom_css_{$slug}" ][ $option_slug ] = $option[ $option_slug ];
}
}
}
if ( ! empty( $custom_css_fields_processed ) ) {
$this->fields_unprocessed = array_merge( $this->fields_unprocessed, $custom_css_fields_processed );
}
$i18n =& self::$i18n;
if ( ! isset( $i18n['css'] ) ) {
// phpcs:disable WordPress.WP.I18n.MissingTranslatorsComment
$i18n['css'] = array(
'classes' => esc_html__( 'CSS ID & Classes', 'et_builder' ),
);
// phpcs:enable
}
$default_custom_css_toggles = array(
'classes' => $i18n['css']['classes'],
'custom_css' => et_builder_i18n( 'Custom CSS' ),
);
$this->_add_settings_modal_toggles( 'custom_css', $default_custom_css_toggles );
}
/**
* Add toggles in settings modal.
*
* @param string $tab_slug Toggle tab slug.
* @param string $toggles_array Toggles.
*/
protected function _add_settings_modal_toggles( $tab_slug, $toggles_array ) {
if ( ! isset( $this->settings_modal_toggles[ $tab_slug ] ) ) {
$this->settings_modal_toggles[ $tab_slug ] = array();
}
if ( ! isset( $this->settings_modal_toggles[ $tab_slug ]['toggles'] ) ) {
$this->settings_modal_toggles[ $tab_slug ]['toggles'] = array();
}
// get the only toggles which do not exist.
$processed_toggles = array_diff_key( $toggles_array, $this->settings_modal_toggles[ $tab_slug ]['toggles'] );
$this->settings_modal_toggles[ $tab_slug ]['toggles'] = array_merge( $this->settings_modal_toggles[ $tab_slug ]['toggles'], $processed_toggles );
}
/**
* Add settings under sub toggles.
*
* @since 3.23
* @since 3.26.7 Add support to set custom icons on sub toggles.
*
* @param string $tab_slug Current tab slug.
* @param string $toggle_slug Current toggle slug.
* @param array $sub_toggle_items Sub toggles settings need to be added.
* @param boolean $tabbed_subtoggles Tabbed sub toggle status.
* @param boolean $bb_icons_support BB icons support status.
*/
protected function _add_settings_modal_sub_toggles( $tab_slug, $toggle_slug, $sub_toggle_items, $tabbed_subtoggles = false, $bb_icons_support = false ) {
// Ensure tab slug is exist.
if ( ! isset( $this->settings_modal_toggles[ $tab_slug ] ) ) {
$this->settings_modal_toggles[ $tab_slug ] = array();
}
// Ensure toggles is exist.
if ( ! isset( $this->settings_modal_toggles[ $tab_slug ]['toggles'] ) ) {
$this->settings_modal_toggles[ $tab_slug ]['toggles'] = array();
}
// Stop the process here if the toggle slug doesn't exist. It should exist before we add
// sub toggles.
if ( ! isset( $this->settings_modal_toggles[ $tab_slug ]['toggles'][ $toggle_slug ] ) ) {
return;
}
// Don't replace existing sub toggles.
$toggle = $this->settings_modal_toggles[ $tab_slug ]['toggles'][ $toggle_slug ];
$sub_toggles = isset( $toggle['sub_toggles'] ) ? $toggle['sub_toggles'] : array();
if ( ! empty( $sub_toggles ) ) {
return;
}
// Set sub toggles.
$this->settings_modal_toggles[ $tab_slug ]['toggles'][ $toggle_slug ]['sub_toggles'] = $sub_toggle_items;
// Set tabbed sub toggles status.
if ( $tabbed_subtoggles ) {
$this->settings_modal_toggles[ $tab_slug ]['toggles'][ $toggle_slug ]['tabbed_subtoggles'] = $tabbed_subtoggles;
}
// Set BB icons support status.
if ( $bb_icons_support ) {
$this->settings_modal_toggles[ $tab_slug ]['toggles'][ $toggle_slug ]['bb_icons_support'] = $bb_icons_support;
}
}
/**
* Get all the fields.
*
* @return array|mixed|void
*/
private function _get_fields() {
$this->fields = array();
$this->fields = $this->fields_unprocessed;
$this->fields = $this->process_fields( $this->fields );
$this->fields = apply_filters( 'et_builder_module_fields_' . $this->slug, $this->fields );
foreach ( $this->fields as $field_name => $field ) {
$this->fields[ $field_name ] = apply_filters( 'et_builder_module_fields_' . $this->slug . '_field_' . $field_name, $field );
// Option template replaces field's array configuration into string which refers to
// saved template data & template id.
if ( is_array( $this->fields[ $field_name ] ) ) {
$this->fields[ $field_name ]['name'] = $field_name;
}
}
return $this->fields;
}
/**
* Checks if the field value equals its default value
*
* @param string $name Field name.
* @param mixed $value Field value.
*
* @return bool
*/
protected function _is_field_default( $name, $value ) {
if ( ! isset( $this->fields_unprocessed[ $name ] ) ) {
// field does not exist.
return false;
}
$field = $this->fields_unprocessed[ $name ];
$default = self::$_->array_get( $field, 'default', '' );
$default_on_front = self::$_->array_get( $field, 'default_on_front', 'not_found' );
if ( 'not_found' !== $default_on_front ) {
return $default_on_front === $value;
}
if ( is_array( $default ) && ! empty( $default[0] ) && is_array( $default[1] ) ) {
// This is a conditional default. Let's try to resolve it.
list ( $depend_field, $conditional_defaults ) = $default;
$default_key = self::$_->array_get( $this->props, $depend_field, key( $conditional_defaults ) );
$default = self::$_->array_get( $conditional_defaults, $default_key, null );
}
return $default === $value;
}
/**
* Intended to be overridden as needed.
*
* @param array $fields Module fields.
*
* @return mixed|void
*/
public function process_fields( $fields ) {
return apply_filters( 'et_pb_module_processed_fields', $fields, $this->slug );
}
/**
* Get the settings fields data for this element.
*
* @since 1.0
* @todo Finish documenting return value's structure.
*
* @return array[] {
* Settings Fields
*
* @type mixed[] $setting_field_key {
* Setting Field Data
*
* @type string $type Setting field type.
* @type string $id CSS id for the setting.
* @type string $label Text label for the setting. Translatable.
* @type string $description Description for the settings. Translatable.
* @type string $class Optional. Css class for the settings.
* @type string[] $affects Optional. The keys of all settings that depend on this setting.
* @type string[] $depends_on Optional. The keys of all settings that this setting depends on.
* @type string $depends_show_if Optional. Only show this setting when the settings
* on which it depends has a value equal to this.
* @type string $depends_show_if_not Optional. Only show this setting when the settings
* on which it depends has a value that is not equal to this.
* ...
* }
* ...
* }
*/
public function get_fields() {
return array(); }
/**
* Returns props value by provided key, if the value is empty, returns the default value
*
* @param string $prop Prop key.
* @param mixed $default Default value.
*
* @return mixed|null
*/
public function prop( $prop, $default = null ) {
return et_builder_module_prop( $prop, $this->props, $default );
}
/**
* Get module defined fields + automatically generated fields
*
* @since 3.23 Add auto generate responsive settings suffix based on mobile_options parameter.
*
* @internal Added to make get_fields() lighter. Initially added during BFB's 3rd party support
*
* @return array
*/
public function get_complete_fields() {
$fields = $this->get_fields();
$responsive_suffixes = array( 'tablet', 'phone', 'last_edited' );
// Loop fields and modify it if needed.
foreach ( $fields as $field_name => $field ) {
// Automatically generate responsive fields.
$supports_responsive = ( isset( $field['responsive'] ) && $field['responsive'] ) || ( isset( $field['mobile_options'] ) && $field['mobile_options'] );
if ( $supports_responsive ) {
// Get tab and toggle slugs value.
$tab_slug = isset( $field['tab_slug'] ) ? $field['tab_slug'] : '';
$toggle_slug = isset( $field['toggle_slug'] ) ? $field['toggle_slug'] : '';
foreach ( $responsive_suffixes as $responsive_suffix ) {
$responsive_field_name = "{$field_name}_{$responsive_suffix}";
$fields[ $responsive_field_name ] = array(
'type' => 'skip',
'tab_slug' => $tab_slug,
'toggle_slug' => $toggle_slug,
);
}
}
}
// Add general fields for modules including Columns.
if ( ( ! isset( $this->type ) || 'child' !== $this->type ) || in_array( $this->slug, array( 'et_pb_column', 'et_pb_column_inner' ), true ) ) {
$i18n =& self::$i18n;
if ( ! isset( $i18n['complete'] ) ) {
// phpcs:disable WordPress.WP.I18n.MissingTranslatorsComment
$i18n['complete'] = array(
'section' => esc_html__( 'section', 'et_builder' ),
'row' => esc_html__( 'row', 'et_builder' ),
'module' => esc_html__( 'module', 'et_builder' ),
'disabled' => array(
'label' => esc_html__( 'Disable on', 'et_builder' ),
'description' => esc_html__( 'This will disable the %1$s on selected devices', 'et_builder' ),
),
'admin' => array(
'description' => esc_html__( 'This will change the label of the module in the builder for easy identification.', 'et_builder' ),
),
'id' => array(
'label' => esc_html__( 'CSS ID', 'et_builder' ),
'description' => esc_html__( "Assign a unique CSS ID to the element which can be used to assign custom CSS styles from within your child theme or from within Divi's custom CSS inputs.", 'et_builder' ),
),
'class' => array(
'label' => esc_html__( 'CSS Class', 'et_builder' ),
'description' => esc_html__( "Assign any number of CSS Classes to the element, separated by spaces, which can be used to assign custom CSS styles from within your child theme or from within Divi's custom CSS inputs.", 'et_builder' ),
),
'' => array(),
);
// phpcs:enable
}
$disabled_on_fields = array();
$slug_labels = array(
'et_pb_section' => $i18n['complete']['section'],
'et_pb_row' => $i18n['complete']['row'],
);
$disable_label = isset( $slug_labels[ $this->slug ] ) ? $slug_labels[ $this->slug ] : $i18n['complete']['module'];
$disabled_on_fields = array(
'disabled_on' => array(
'label' => $i18n['complete']['disabled']['label'],
'description' => sprintf( $i18n['complete']['disabled']['description'], $disable_label ),
'type' => 'multiple_checkboxes',
'options' => array(
'phone' => et_builder_i18n( 'Phone' ),
'tablet' => et_builder_i18n( 'Tablet' ),
'desktop' => et_builder_i18n( 'Desktop' ),
),
'additional_att' => 'disable_on',
'option_category' => 'configuration',
'tab_slug' => 'custom_css',
'toggle_slug' => 'visibility',
),
);
$common_general_fields = array(
'admin_label' => array(
'label' => et_builder_i18n( 'Admin Label' ),
'description' => $i18n['complete']['admin']['description'],
'type' => 'text',
'option_category' => 'configuration',
'toggle_slug' => 'admin_label',
),
'module_id' => array(
'label' => $i18n['complete']['id']['label'],
'description' => $i18n['complete']['id']['description'],
'type' => 'text',
'option_category' => 'configuration',
'tab_slug' => 'custom_css',
'toggle_slug' => 'classes',
'option_class' => 'et_pb_custom_css_regular',
),
'module_class' => array(
'label' => $i18n['complete']['class']['label'],
'description' => $i18n['complete']['class']['description'],
'type' => 'text',
'option_category' => 'configuration',
'tab_slug' => 'custom_css',
'toggle_slug' => 'classes',
'option_class' => 'et_pb_custom_css_regular',
),
);
$general_fields = array_merge( $disabled_on_fields, $common_general_fields );
$fields = array_merge( $fields, apply_filters( 'et_builder_module_general_fields', $general_fields ) );
}
return $fields;
}
/**
* Get configuration for module's advanced fields. This method is meant to be overridden in module classes.
*
* @since 3.1
*
* @return array[] {@see self::$advanced_fields}
*/
public function get_advanced_fields_config() {
return $this->advanced_fields;
}
/**
* Get configuration for module's custom css fields. This method is meant to be overridden in module classes.
*
* @since 3.1
*
* @return array[] {@see self::$custom_css_fields}
*/
public function get_custom_css_fields_config() {
return $this->custom_css_fields;
}
/**
* Returns Global Presets settings
*/
public static function get_global_presets() {
return self::$global_presets_manager->get_global_presets();
}
/**
* Get custom tabs for the module's settings modal. This method is meant to be overridden in module classes.
*
* @since 3.1
*
* @return array[] {@see self::$settings_modal_tabs}
*/
public function get_settings_modal_tabs() {
return $this->settings_modal_tabs;
}
/**
* Get toggles for the module's settings modal. This method is meant to be overridden in module classes.
*
* @since 3.1
*
* @return array[] {@see self::$settings_modal_toggles}
*/
public function get_settings_modal_toggles() {
return $this->settings_modal_toggles;
}
/**
* Generate column fields.
*
* @param integer $column_number number of column.
* @param array $base_fields base fields for column.
*
* @return array column fields
*/
public function get_column_fields( $column_number = 1, $base_fields = array() ) {
$fields = array();
// Loop column's base fields.
foreach ( $base_fields as $field_name => $field ) {
// Loop (number of column) times.
for ( $index = 1; $index <= $column_number; $index++ ) {
// Some attribute's id is not located at the bottom of the attribute name.
if ( isset( $field['has_custom_index_location'] ) && $field['has_custom_index_location'] ) {
$column_name = str_replace( '%column_index%', $index, $field_name );
} else {
$column_name = "{$field_name}_{$index}";
}
$fields[ $column_name ] = array(
'type' => 'skip',
);
// Most column field is an empty-type attribute. Non-empty attribute are likely
// attribute for computed field which needs to have suffix ID.
if ( ! empty( $field ) ) {
// Append suffix to the module variable.
foreach ( $field as $attr_name => $attr_value ) {
if ( 'has_custom_index_location' === $attr_name ) {
continue;
}
if ( is_array( $attr_value ) && 'computed_callback' !== $attr_name ) {
$attr_value = $this->_append_suffix( $attr_value, $index );
}
$fields[ $column_name ][ $attr_name ] = $attr_value;
}
}
}
}
return $fields;
}
/**
* Append suffix to simple array value.
*
* @param array $values array value.
* @param string $suffix intended suffix for output's array.
*
* @return array suffixed value
*/
public function _append_suffix( $values, $suffix ) {
$output = array();
foreach ( $values as $value ) {
$output[] = "{$value}_{$suffix}";
}
return $output;
}
/**
* Returns module style priority.
*
* @return int
*/
public function get_style_priority() {
return $this->_style_priority;
}
/**
* Get current post's post type.
*
* @return string
*/
public function get_post_type() {
global $post, $et_builder_post_type;
// phpcs:disable WordPress.Security.NonceVerification -- This function does not change any state, and is therefore not susceptible to CSRF.
if ( isset( $_POST['et_post_type'] ) && ! $et_builder_post_type ) {
$et_builder_post_type = sanitize_text_field( $_POST['et_post_type'] );
}
// phpcs:enable
if ( is_a( $post, 'WP_POST' ) && ( is_admin() || ! isset( $et_builder_post_type ) ) ) {
return $post->post_type;
} else {
$layout_type = self::get_theme_builder_layout_type();
if ( $layout_type ) {
return $layout_type;
}
return isset( $et_builder_post_type ) ? $et_builder_post_type : 'post';
}
}
/**
* Removed extra tabs/newlines from template.
*
* @param string $content Template content.
*
* @return string|string[]
*/
public static function optimize_bb_chunk( $content ) {
if ( ! ET_BUILDER_OPTIMIZE_TEMPLATES ) {
return $content;
}
return str_replace( self::$_unique_bb_strip, '', $content );
}
/**
* Optimize template content.
*
* @param string $content Template content.
*
* @return string|string[]
*/
public static function get_unique_bb_key( $content ) {
if ( ! ET_BUILDER_OPTIMIZE_TEMPLATES ) {
return $content;
}
$content = self::optimize_bb_chunk( $content );
if ( isset( self::$_unique_bb_keys_map[ $content ] ) ) {
$key = self::$_unique_bb_keys_map[ $content ];
} else {
self::$_unique_bb_keys_values[] = $content;
$key = count( self::$_unique_bb_keys_values ) - 1;
self::$_unique_bb_keys_map[ $content ] = $key;
}
$content = "";
return $content;
}
/**
* Wrap settings option in wrapper div e.g `.et-pb-option-standard`.
*
* @param string $option_output Option markup.
* @param array $field Field settings.
* @param string $name Field name.
*
* @return string|string[]
*/
public function wrap_settings_option( $option_output, $field, $name = '' ) {
// Option template convert array field into string id; return early to prevent error.
if ( is_string( $field ) ) {
return self::get_unique_bb_key( $option_output );
}
$depends = false;
$new_depends = isset( $field['show_if'] ) || isset( $field['show_if_not'] );
$depends_attr = '';
if ( ! $new_depends && ( isset( $field['depends_show_if'] ) || isset( $field['depends_show_if_not'] ) ) ) {
$depends = true;
if ( isset( $field['depends_show_if_not'] ) ) {
$depends_show_if_not = is_array( $field['depends_show_if_not'] ) ? implode( ',', $field['depends_show_if_not'] ) : $field['depends_show_if_not'];
$depends_attr = sprintf( ' data-depends_show_if_not="%s"', esc_attr( $depends_show_if_not ) );
} else {
$depends_attr = sprintf( ' data-depends_show_if="%s"', esc_attr( $field['depends_show_if'] ) );
}
}
if ( isset( $field['depends_on_responsive'] ) ) {
$depends_attr .= sprintf( ' data-depends_on_responsive="%s"', esc_attr( implode( ',', $field['depends_on_responsive'] ) ) );
}
// Overriding background color's attribute, turning it into appropriate background attributes.
if ( isset( $field['type'] ) && isset( $field['name'] ) && 'background_color' === $field['name'] && ! self::$_->array_get( $field, 'skip_background_ui' ) ) {
$field['type'] = 'background';
// Removing depends default variable which hides background color for unified background field UI.
if ( isset( $field['depends_show_if'] ) ) {
unset( $field['depends_show_if'] );
}
}
$output = sprintf(
'%6$s