文件操作 - base.php
返回文件管理
返回主菜单
删除本文件
文件: /storage/v12552/stanleysteamer/public_html/wp-content/themes/bricks/includes/elements/base.php
编辑文件内容
<?php namespace Bricks; if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly abstract class Element { /** * Gutenberg block name: 'core/heading', etc. * * Mapping of Gutenberg block to Bricks element to load block post_content in Bricks and save Bricks data as WordPress post_content. */ public $block = null; // Builder public $element; public $category; public $name; public $label; public $keywords; public $icon; public $controls; public $control_groups; public $control_options; public $css_selector; public $scripts = []; public $post_id = 0; public $draggable = true; // false to prevent dragging over entire element in builder public $deprecated = false; // true to hide element in panel (editing of existing deprecated element still works) public $panel_condition = []; // array conditions to show the element in the panel // Frontend public $id; public $cid; // Component ID (@since 1.12) public $uid; // Unique ID for element inside component {id-instanceId} (@since 2.0) public $tag = 'div'; public $attributes = []; public $settings; public $theme_styles = []; public $is_frontend = false; /** * Custom attributes * * true: renders custom attributes on element '_root' (= default) * false: handle custom attributes in element render_attributes( 'xxx', true ) function (e.g. Nav Menu) * * @since 1.3 */ public $custom_attributes = true; /** * Nestable elements * * @since 1.5 */ public $nestable = false; // true to allow to insert child elements (e.g. Container, Div) public $nestable_item; // First child of nestable element (Use as blueprint for nestable children & when adding repeater item) public $nestable_children; // Array of children elements that are added inside nestable element when it's added to the canvas. public $nestable_html = ''; // Nestable HTML with placeholder for element 'children' public $vue_component; // Set specific Vue component to render element in builder (e.g. 'bricks-nestable' for Section, Container, Div) public $original_query = ''; public $support_masonry = false; // @since 1.11.1 public function __construct( $element = null ) { $this->element = $element; $this->label = $this->get_label(); $this->keywords = $this->get_keywords(); $this->is_frontend = isset( $element['is_frontend'] ) ? $element['is_frontend'] : bricks_is_frontend(); // Ensure the ID is a string as it could be 6-digit number (@since 1.12) $this->id = ! empty( $element['id'] ) ? (string) $element['id'] : (string) Helpers::generate_random_id( false ); $this->uid = $this->id; $this->cid = ! empty( $element['cid'] ) ? $element['cid'] : ''; $this->settings = ! empty( $element['settings'] ) ? $element['settings'] : []; /** * Is component instance * * Get the first 6 chacacters of the $this->id only. * Syntax: '{componentElement.id}:{element.id}' (6 characters) * * @since 1.12 */ // Direct slot component roots keep their local ID so query/pagination IDs resolve locally. // (#86ca4tpmq; @since 2.3.8) if ( ! empty( $element['instanceId'] ) && empty( $element['slotInstanceId'] ) ) { $this->id = substr( $this->id, 0, 6 ); $this->uid = "{$this->id}-{$this->element['instanceId']}"; // Nested component instance (#86c4zfhru; @since 2.3) if ( ! empty( $this->cid ) ) { $this->uid = "{$this->cid}-{$this->element['instanceId']}"; } } // Not a component: Get nestable item and children (@since 1.12) if ( empty( $element['cid'] ) ) { $this->nestable_item = $this->get_nestable_item(); $this->nestable_children = $this->get_nestable_children(); } // To distinguish non-layout nestables (slider-nested, etc.) in Vue render if ( $this->nestable && ! $this->is_layout_element() ) { $this->nestable_html = true; } $this->support_masonry = $this->support_masonry_element(); // Element-specific theme style settings if ( ! empty( $element['themeStyles'] ) ) { $this->theme_styles = $element['themeStyles']; } elseif ( Theme_Styles::get_setting_by_key( $this->name ) ) { $this->theme_styles = Theme_Styles::get_setting_by_key( $this->name ); } $this->tag = $this->get_tag(); } /** * Populate element data (when element is requested) * * Builder: Load all elements * Frontend: Load only requested elements * * @since 1.0 */ public function load() { $this->control_options = Setup::$control_options; // Control groups $this->control_groups = []; $this->set_common_control_groups(); $this->set_control_groups(); // @see: https://academy.bricksbuilder.io/article/filter-bricks-elements-element_name-control_groups $this->control_groups = apply_filters( "bricks/elements/$this->name/control_groups", $this->control_groups ); // Controls $this->controls = []; $this->set_controls_before(); $this->set_controls(); $this->set_controls_after(); // @see: https://academy.bricksbuilder.io/article/filter-bricks-elements-element_name-controls $this->controls = apply_filters( "bricks/elements/$this->name/controls", $this->controls ); // Set CSS selector if ( ! empty( $this->css_selector ) ) { $this->set_css_selector( $this->css_selector ); } // NOTE: Undocumented @see: https://academy.bricksbuilder.io/article/filter-bricks-elements-element_name-scripts (@since 1.5.5) $this->scripts = apply_filters( "bricks/elements/$this->name/scripts", $this->scripts ); // Frontend $this->add_actions(); $this->add_filters(); } /** * Add element-specific WordPress actions to run in constructor * * @since 1.0 */ public function add_actions() {} /** * Add element-specific WordPress filters to run in constructor * * E.g. 'nav_menu_item_title' filter in Element_Nav_Menu * * @since 1.0 */ public function add_filters() {} /** * Set default CSS selector of each control with 'css' property * * To target specific element child tag (such as 'a' in 'button' etc.) * Avoids having to set CSS selector manually for each element control. * * @since 1.0 */ public function set_css_selector( $custom_css_selector ) { foreach ( $this->controls as $key => $value ) { if ( isset( $this->controls[ $key ]['css'] ) && is_array( $this->controls[ $key ]['css'] ) ) { foreach ( $this->controls[ $key ]['css'] as $index => $value ) { if ( ! isset( $this->controls[ $key ]['css'][ $index ]['selector'] ) ) { $this->controls[ $key ]['css'][ $index ]['selector'] = $custom_css_selector; } } } } } public function get_label() { // Fallback: Use element name if element class has no get_label() defined return str_replace( '-', ' ', $this->name ); } public function get_keywords() { return []; } /** * Return element tag * * Default: 'div' * Next: $tag set in theme styles * Last: $tag set in element settings * * Custom tag: Check element 'tag' and 'customTag' settings. * * @since 1.4 */ public function get_tag() { $tag = $this->tag ?? 'div'; // Get 'tag' from theme styles (@see element-heading.php) if ( ! empty( $this->theme_styles['tag'] ) ) { $tag = $this->theme_styles['tag']; } $settings = $this->settings; // Get 'tag' from element setting $tag = Helpers::get_html_tag_from_element_settings( $settings, $tag ); return $tag; } /** * Element-specific control groups * * @since 1.0 */ public function set_control_groups() {} /** * Element-specific controls * * @since 1.0 */ public function set_controls() {} /** * Control groups used by all elements under 'style' tab * * @since 1.0 */ public function set_common_control_groups() { $this->control_groups['_layout'] = [ 'title' => esc_html__( 'Layout', 'bricks' ), 'tab' => 'style', 'icon' => 'tab-layout', ]; $this->control_groups['_typography'] = [ 'title' => esc_html__( 'Typography', 'bricks' ), 'tab' => 'style', 'icon' => 'tab-typography', ]; $this->control_groups['_background'] = [ 'title' => esc_html__( 'Background', 'bricks' ), 'tab' => 'style', 'icon' => 'tab-background', ]; $this->control_groups['_border'] = [ 'title' => esc_html__( 'Border / Box Shadow', 'bricks' ), 'tab' => 'style', 'icon' => 'tab-border', ]; $this->control_groups['_gradient'] = [ 'title' => esc_html__( 'Gradient / Overlay', 'bricks' ), 'tab' => 'style', 'icon' => 'tab-gradient', ]; if ( $this->is_layout_element() ) { $this->control_groups['_shapes'] = [ 'title' => esc_html__( 'Shape Dividers', 'bricks' ), 'tab' => 'style', 'icon' => 'cursor', ]; } $this->control_groups['_transform'] = [ 'title' => esc_html__( 'Transform', 'bricks' ), 'tab' => 'style', 'icon' => 'tab-transform', ]; $this->control_groups['_css'] = [ 'title' => 'CSS', 'tab' => 'style', 'icon' => 'css3', ]; $this->control_groups['_attributes'] = [ 'title' => esc_html__( 'Attributes', 'bricks' ), 'tab' => 'style', 'icon' => 'html', ]; } /** * Controls used by all elements under 'style' tab * * @since 1.0 */ public function set_controls_before() { // For pseudo-elements like :before & :after $this->controls['_content'] = [ 'tab' => 'content', 'label' => esc_html__( 'Content', 'bricks' ), 'type' => 'text', 'hidden' => true, 'css' => [ [ 'property' => 'content', 'value' => '%s', // Wrap 'content' in double quotes if needed in CSS rule ] ], ]; // LAYOUT // Spacing $this->controls['_spacingSeparator'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Spacing', 'bricks' ), 'type' => 'separator', ]; $this->controls['_margin'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Margin', 'bricks' ), 'type' => 'spacing', 'css' => [ [ 'property' => 'margin', 'selector' => '', ] ], ]; $this->controls['_padding'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Padding', 'bricks' ), 'type' => 'spacing', 'css' => [ [ 'property' => 'padding', ] ], ]; // Sizing: (width, height) $this->controls['_sizingSeparator'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Sizing', 'bricks' ), 'type' => 'separator', ]; $this->controls['_width'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Width', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'width', 'selector' => '', ], ], ]; $this->controls['_widthMin'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Min. width', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'min-width', 'selector' => '', ], ], ]; /** * Use max-width: 100% by default for all elements * Avoid horizontal scrollbar when setting 'width' instead of 'max-width'. */ $this->controls['_widthMax'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Max. width', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'max-width', 'selector' => '', ], ], // 'placeholder' => '100%', // Outcommented (@since 1.8.2) ]; $this->controls['_height'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Height', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'height', ], ], ]; $this->controls['_heightMin'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Min. height', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'min-height', ], ], ]; $this->controls['_heightMax'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Max. height', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'max-height', ], ], ]; // aspect-ratio control (@since 1.9) $this->controls['_aspectRatio'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Aspect ratio', 'bricks' ), 'type' => 'text', 'inline' => true, 'hasDynamicData' => false, 'hasVariables' => true, 'placeholder' => '', 'css' => [ [ 'property' => 'aspect-ratio', ], ], ]; // POSITIONING $this->controls['_positionSeparator'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Positioning', 'bricks' ), 'type' => 'separator', ]; $this->controls['_position'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Position', 'bricks' ), 'type' => 'select', 'options' => Setup::$control_options['position'], 'css' => [ [ 'property' => 'position', 'selector' => '', ], ], 'inline' => true, ]; $this->controls['_positionInfo'] = [ 'type' => 'info', 'content' => esc_html__( 'Set "Top" value to make this element "sticky".', 'bricks' ), 'tab' => 'style', 'group' => '_layout', 'required' => [ '_position', '=', 'sticky' ], ]; $this->controls['_top'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Top', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'top', 'selector' => '', ], ], ]; $this->controls['_right'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Right', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'right', 'selector' => '', ], ], ]; $this->controls['_bottom'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Bottom', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'bottom', 'selector' => '', ], ], ]; $this->controls['_left'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Left', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'left', 'selector' => '', ], ], ]; $this->controls['_zIndex'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Z-index', 'bricks' ), 'type' => 'number', 'css' => [ [ 'property' => 'z-index', 'selector' => '', ], ], 'min' => -999, 'placeholder' => 0, ]; if ( ! $this->is_layout_element() ) { $this->controls['_order'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Order', 'bricks' ), 'type' => 'number', 'css' => [ [ 'selector' => '', 'property' => 'order', ], ], 'min' => -999, 'placeholder' => 0, ]; } /** * Scroll snap * * https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Scroll_Snap * * @since 1.9.3 */ if ( ! isset( Settings::$controls['page'] ) ) { Settings::set_controls(); } $page_settings = Settings::$controls['page'] ?? []; $page_setting_controls = $page_settings['controls'] ?? []; // Use page settings to set scroll snap controls foreach ( $page_setting_controls as $key => $value ) { if ( strpos( $key, 'scrollSnap' ) === 0 && $key !== 'scrollSnapSelector' ) { $key = "_$key"; $this->controls[ $key ] = $value; $this->controls[ $key ]['group'] = '_layout'; if ( $key === '_scrollSnapType' ) { if ( ! empty( $this->controls[ $key ]['options'] ) ) { $this->controls[ $key ]['options']['x mandatory'] = 'Mandatory (' . esc_html__( 'x-axis', 'bricks' ) . ')'; $this->controls[ $key ]['options']['x proximity'] = 'Proximity (' . esc_html__( 'x-axis', 'bricks' ) . ')'; } $this->controls[ $key ]['css'] = [ [ 'property' => 'scroll-snap-type', ] ]; } if ( $key === '_scrollSnapAlign' && isset( $this->controls[ $key ]['placeholder'] ) ) { unset( $this->controls[ $key ]['placeholder'] ); } // Remove CSS 'selector' property if ( isset( $this->controls[ $key ]['css'] ) ) { foreach ( $this->controls[ $key ]['css'] as $index => $value ) { if ( isset( $this->controls[ $key ]['css'][ $index ]['selector'] ) ) { unset( $this->controls[ $key ]['css'][ $index ]['selector'] ); } } } } } // Misc $this->controls['_miscSeparator'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Misc', 'bricks' ), 'type' => 'separator', ]; if ( ! $this->is_layout_element() ) { $this->controls['_display'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Display', 'bricks' ), 'type' => 'select', 'options' => [ 'flex' => 'flex', 'inline-flex' => 'inline-flex', 'block' => 'block', 'inline-block' => 'inline-block', 'inline' => 'inline', 'none' => 'none', ], 'add' => true, 'inline' => true, 'lowercase' => true, 'css' => [ [ 'selector' => '', 'property' => 'display', ], /** * Use 'required' property to add CSS rule if display is set to 'grid' * * @prev 1.7.2: Used .brx-grid class on nestable to set align-items to initial. * * @since 1.7.2 */ [ 'selector' => '', 'property' => 'align-items', 'value' => 'initial', 'required' => 'grid', ], ], ]; } $this->controls['_visibility'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Visibility', 'bricks' ), 'type' => 'select', 'inline' => true, 'options' => [ 'visible' => 'visible', 'hidden' => 'hidden', 'collapse' => 'collapse', ], 'css' => [ [ 'property' => 'visibility', ] ], ]; $this->controls['_overflow'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Overflow', 'bricks' ), 'type' => 'text', 'css' => [ [ 'property' => 'overflow', ] ], 'inline' => true, 'hasDynamicData' => false, 'placeholder' => 'visible', ]; $this->controls['_opacity'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Opacity', 'bricks' ), 'type' => 'number', 'step' => '.01', 'min' => '0', 'max' => '1', 'placeholder' => 1, 'css' => [ [ 'property' => 'opacity', ] ], ]; $this->controls['_cursor'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Cursor', 'bricks' ), 'type' => 'select', 'options' => [ 'generalGroupTitle' => esc_html__( 'General', 'bricks' ), 'auto' => 'auto', 'default' => 'default', 'none' => 'none', 'linkGroupTitle' => esc_html__( 'Link & status', 'bricks' ), 'pointer' => 'pointer', 'context-menu' => 'context-menu', 'help' => 'help', 'progress' => 'progress', 'wait' => 'wait', 'selectionGroupTitle' => esc_html__( 'Selection', 'bricks' ), 'cell' => 'cell', 'crosshair' => 'crosshair', 'text' => 'text', 'vertical-text' => 'vertical-text', 'dndGroupTitle' => esc_html__( 'Drag & drop', 'bricks' ), 'alias' => 'alias', 'copy' => 'copy', 'move' => 'move', 'no-drop' => 'no-drop', 'not-allowed' => 'not-allowed', 'grab' => 'grab', 'grabbing' => 'grabbing', 'zoomGroupTitle' => esc_html__( 'Zoom', 'bricks' ), 'zoom-in' => 'zoom-in', 'zoom-out' => 'zoom-out', 'scrollGroupTitle' => esc_html__( 'Resize', 'bricks' ), 'col-resize' => 'col-resize', 'row-resize' => 'row-resize', 'n-resize' => 'n-resize', 'e-resize' => 'e-resize', 's-resize' => 's-resize', 'w-resize' => 'w-resize', 'ne-resize' => 'ne-resize', 'nw-resize' => 'nw-resize', 'se-resize' => 'se-resize', 'sw-resize' => 'sw-resize', 'ew-resize' => 'ew-resize', 'ns-resize' => 'ns-resize', 'nesw-resize' => 'nesw-resize', 'nwse-resize' => 'nwse-resize', 'all-scroll' => 'all-scroll', ], 'css' => [ [ 'selector' => '', 'property' => 'cursor', ] ], 'inline' => true, 'placeholder' => 'auto', ]; // isolation select control (@since 1.9) $this->controls['_isolation'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Isolation', 'bricks' ), 'type' => 'select', 'inline' => true, 'options' => [ 'auto' => 'auto', 'isolate' => 'isolate', ], 'css' => [ [ 'property' => 'isolation', ] ], ]; // mix-blend-mode control (@since 1.9) $this->controls['_mixBlendMode'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => 'Mix blend mode', // Don't localize 'type' => 'select', 'inline' => true, 'options' => Setup::$control_options['blendMode'], 'css' => [ [ 'property' => 'mix-blend-mode', ] ], ]; $this->controls['_pointerEvents'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => 'Pointer events', // Don't localize 'type' => 'text', 'inline' => true, 'dd' => false, 'css' => [ [ 'property' => 'pointer-events', ] ], ]; // perspective control (@since 2.3) $this->controls['_perspective'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => 'Perspective', // Don't localize 'type' => 'number', 'units' => true, 'inline' => true, 'hasDynamicData' => false, 'css' => [ [ 'property' => 'perspective', ] ], ]; // perspective-origin control (@since 2.3) $this->controls['_perspectiveOrigin'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => 'Perspective origin', // Don't localize 'type' => 'text', 'inline' => true, 'hasDynamicData' => false, 'css' => [ [ 'property' => 'perspective-origin', ] ] ]; // Grid & flex controls (for non-layout elements: no section, container, div) if ( ! $this->is_layout_element() ) { $this->controls['_gridItemSeparator'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Grid item', 'bricks' ), 'type' => 'separator', ]; $this->controls['_gridItemJustifySelf'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Justify self', 'bricks' ), 'type' => 'align-items', 'hasDynamicData' => false, 'css' => [ [ 'property' => 'justify-self', ], ], ]; $this->controls['_flexSeparator'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Flex', 'bricks' ), 'type' => 'separator', ]; $this->controls['_flexDirection'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Direction', 'bricks' ), 'tooltip' => [ 'content' => 'flex-direction', 'position' => 'top-left', ], 'type' => 'direction', 'css' => [ [ 'selector' => '', 'property' => 'flex-direction', ], ], 'inline' => true, 'rerender' => true, 'required' => [ '_display', '=', 'flex' ], ]; $this->controls['_alignSelf'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Align self', 'bricks' ), 'type' => 'align-items', 'tooltip' => [ 'content' => 'align-self', 'position' => 'top-left', ], 'css' => [ [ 'selector' => '', 'property' => 'align-self', ], ], ]; $this->controls['_justifyContent'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Align main axis', 'bricks' ), 'tooltip' => [ 'content' => 'justify-content', 'position' => 'top-left', ], 'type' => 'justify-content', 'css' => [ [ 'selector' => '', 'property' => 'justify-content', ], ], 'required' => [ '_display', '=', [ 'flex', 'inline-flex' ] ], ]; $this->controls['_alignItems'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Align cross axis', 'bricks' ), 'tooltip' => [ 'content' => 'align-items', 'position' => 'top-left', ], 'type' => 'align-items', 'css' => [ [ 'selector' => '', 'property' => 'align-items', ], ], 'required' => [ '_display', '=', [ 'flex', 'inline-flex' ] ], ]; $this->controls['_gap'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Gap', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'gap', 'selector' => '', ], ], 'required' => [ '_display', '=', [ 'flex', 'inline-flex' ] ], ]; $this->controls['_flexGrow'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Flex grow', 'bricks' ), 'type' => 'number', 'tooltip' => [ 'content' => 'flex-grow', 'position' => 'top-left', ], 'css' => [ [ 'selector' => '', 'property' => 'flex-grow', ], ], 'min' => 0, 'placeholder' => 0, ]; $this->controls['_flexShrink'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Flex shrink', 'bricks' ), 'type' => 'number', 'tooltip' => [ 'content' => 'flex-shrink', 'position' => 'top-left', ], 'css' => [ [ 'selector' => '', 'property' => 'flex-shrink', ], ], 'min' => 0, 'placeholder' => 1, ]; $this->controls['_flexBasis'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Flex basis', 'bricks' ), 'type' => 'text', 'tooltip' => [ 'content' => 'flex-basis', 'position' => 'top-left', ], 'css' => [ [ 'selector' => '', 'property' => 'flex-basis', ], ], 'inline' => true, 'hasDynamicData' => false, 'hasVariables' => true, 'placeholder' => 'auto', ]; } // MASONRY (@since 1.11.1) if ( $this->support_masonry_element() ) { $this->controls['_masonrySep'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => 'Masonry', 'type' => 'separator', ]; // Masonry active info (@since 1.11.1) $this->controls['_useMasonryInfo2'] = [ 'tab' => 'style', 'group' => '_layout', 'type' => 'info', // translators: %s: Masonry layout is active. 'content' => sprintf( '%s. %s', // translators: %s: Masonry sprintf( esc_html__( '%s layout is active' ), esc_html__( 'Masonry', 'bricks' ) ), esc_html__( 'Ensure that no conflicting CSS styles are applied to this element.', 'bricks' ) ), 'required' => [ '_useMasonry', '=', true ], ]; $this->controls['_useMasonry'] = [ 'tab' => 'style', 'group' => '_layout', // translators: %s: Masonry 'label' => sprintf( esc_html__( '%s layout', 'bricks' ), 'Masonry' ), 'type' => 'checkbox', ]; $this->controls['_masonryColumn'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Columns', 'bricks' ), 'type' => 'number', 'min' => 1, 'css' => [ [ 'property' => '--columns', 'selector' => '', ], ], 'placeholder' => 3, 'required' => [ '_useMasonry', '=', true ], ]; $this->controls['_masonryGutter'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Spacing', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => '--gutter', 'selector' => '', ], ], 'placeholder' => '10px', 'required' => [ '_useMasonry', '=', true ], ]; $this->controls['_masonryHorizontalOrder'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Horizontal order', 'bricks' ), 'type' => 'checkbox', 'required' => [ '_useMasonry', '=', true ], ]; $this->controls['_masonryTransitionDuration'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Transition', 'bricks' ) . ': ' . esc_html__( 'Duration', 'bricks' ), 'type' => 'number', 'units' => true, 'placeholder' => '0.4s', 'description' => esc_html__( 'Set to "0" to disable default animations.', 'bricks' ), 'required' => [ '_useMasonry', '=', true ], ]; $this->controls['_masonryTransitionMode'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Reveal animation', 'bricks' ), 'type' => 'select', 'options' => [ 'scale' => esc_html__( 'Scale', 'bricks' ), 'fade' => esc_html__( 'Fade', 'bricks' ), 'slideLeft' => esc_html__( 'Slide from left', 'bricks' ), 'slideRight' => esc_html__( 'Slide from right', 'bricks' ), 'skew' => esc_html__( 'Skew', 'bricks' ), ], 'placeholder' => esc_html__( 'Scale', 'bricks' ), 'description' => esc_html__( 'Only applies to new items added to the DOM.', 'bricks' ), 'required' => [ [ '_useMasonry', '=', true ], [ '_masonryTransitionDuration', '!=', [ '0', '0s' ] ], ], ]; } // TYPOGRAPHY $this->controls['_typography'] = [ 'tab' => 'style', 'group' => '_typography', 'type' => 'typography', 'popup' => false, 'css' => [ [ 'property' => 'font', ], ], ]; // BACKGROUND $this->controls['_background'] = [ 'tab' => 'style', 'group' => '_background', 'type' => 'background', 'popup' => false, 'css' => [ [ 'property' => 'background', ] ], ]; // SHAPES if ( $this->is_layout_element() ) { $this->controls['_shapeDividers'] = [ 'tab' => 'style', 'group' => '_shapes', 'placeholder' => esc_html__( 'Shape', 'bricks' ), 'type' => 'repeater', 'pasteStyles' => true, 'titleProperty' => 'shape', 'fields' => [ 'shape' => [ 'type' => 'select', 'searchable' => true, 'options' => [ 'custom' => esc_html__( 'Custom', 'bricks' ), 'cloud' => esc_html__( 'Cloud', 'bricks' ), 'drops' => esc_html__( 'Drops', 'bricks' ), 'grid-round' => esc_html__( 'Grid (Round)', 'bricks' ), 'grid-square' => esc_html__( 'Grid (Square)', 'bricks' ), 'round' => esc_html__( 'Round', 'bricks' ), 'square' => esc_html__( 'Square', 'bricks' ), 'stroke' => esc_html__( 'Stroke', 'bricks' ), 'stroke-2' => esc_html__( 'Stroke #2', 'bricks' ), 'tilt' => esc_html__( 'Tilt', 'bricks' ), 'triangle' => esc_html__( 'Triangle', 'bricks' ), 'triangle-concave' => esc_html__( 'Triangle concave', 'bricks' ), 'triangle-convex' => esc_html__( 'Triangle convex', 'bricks' ), 'triangle-double' => esc_html__( 'Triangle double', 'bricks' ), 'wave' => esc_html__( 'Wave', 'bricks' ), 'waves' => esc_html__( 'Waves', 'bricks' ), 'wave-brush' => esc_html__( 'Wave brush', 'bricks' ), 'zigzag' => esc_html__( 'Zigzag', 'bricks' ), 'vertical-cloud' => esc_html__( 'Vertical - Cloud', 'bricks' ), 'vertical-drops' => esc_html__( 'Vertical - Drops', 'bricks' ), 'vertical-pixels' => esc_html__( 'Vertical - Pixels', 'bricks' ), 'vertical-stroke' => esc_html__( 'Vertical - Stroke', 'bricks' ), 'vertical-stroke-2' => esc_html__( 'Vertical - Stroke #2', 'bricks' ), 'vertical-tilt' => esc_html__( 'Vertical - Tilt', 'bricks' ), 'vertical-triangle' => esc_html__( 'Vertical - Triangle', 'bricks' ), 'vertical-triangle-double' => esc_html__( 'Vertical - Triangle double', 'bricks' ), 'vertical-wave' => esc_html__( 'Vertical - Wave', 'bricks' ), 'vertical-waves' => esc_html__( 'Vertical - Waves', 'bricks' ), 'vertical-wave-brush' => esc_html__( 'Vertical - Wave brush', 'bricks' ), 'vertical-zigzag' => esc_html__( 'Vertical - Zigzag', 'bricks' ), // 'custom' => esc_html__( 'Custom', 'bricks' ), // MAYBE: add custom SVG control ], 'placeholder' => esc_html__( 'Select shape', 'bricks' ), ], 'shapeCustom' => [ 'label' => esc_html__( 'Custom shape', 'bricks' ) . ' (SVG)', 'type' => 'svg', 'description' => sprintf( // translators: %s: link to MDN esc_html__( 'If the shape doesn\'t take up all available space add %s to the "svg" tag.', 'bricks' ), '<a href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio" target="_blank">preserveAspectRatio="none"</a>' ), 'required' => [ 'shape', '=', 'custom' ], ], 'fill' => [ 'label' => esc_html__( 'Fill color', 'bricks' ), 'type' => 'color', 'required' => [ 'shape', '!=', '' ], ], 'front' => [ 'label' => esc_html__( 'Front', 'bricks' ), 'type' => 'checkbox', 'inline' => true, 'required' => [ 'shape', '!=', '' ], ], 'flipHorizontal' => [ 'label' => esc_html__( 'Flip', 'bricks' ) . ' ' . esc_html__( 'x-axis', 'bricks' ), 'type' => 'checkbox', 'inline' => true, 'small' => true, 'required' => [ 'shape', '!=', '' ], ], 'flipVertical' => [ 'label' => esc_html__( 'Flip', 'bricks' ) . ' ' . esc_html__( 'y-axis', 'bricks' ), 'type' => 'checkbox', 'inline' => true, 'small' => true, 'required' => [ 'shape', '!=', '' ], ], 'overflow' => [ 'label' => esc_html__( 'Overflow', 'bricks' ), 'type' => 'checkbox', 'inline' => true, 'small' => true, 'required' => [ 'shape', '!=', '' ], ], 'height' => [ 'label' => esc_html__( 'Height', 'bricks' ), 'type' => 'number', 'units' => true, 'hasVariables' => true, 'required' => [ 'shape', '!=', '' ], ], 'width' => [ 'label' => esc_html__( 'Width', 'bricks' ), 'type' => 'number', 'units' => true, 'hasVariables' => true, 'required' => [ 'shape', '!=', '' ], ], 'rotate' => [ 'label' => esc_html__( 'Rotate', 'bricks' ) . ' °', 'type' => 'number', 'unit' => 'deg', 'hasVariables' => true, 'required' => [ 'shape', '!=', '' ], ], 'horizontalAlign' => [ 'label' => esc_html__( 'Horizontal align', 'bricks' ), 'type' => 'align-items', 'exclude' => 'stretch', 'inline' => true, 'placeholder' => esc_html__( 'Select', 'bricks' ), 'required' => [ 'shape', '!=', '' ], ], 'verticalAlign' => [ 'label' => esc_html__( 'Vertical align', 'bricks' ), 'type' => 'justify-content', 'exclude' => 'space', 'inline' => true, 'placeholder' => esc_html__( 'Select', 'bricks' ), 'required' => [ 'shape', '!=', '' ], ], 'top' => [ 'label' => esc_html__( 'Top', 'bricks' ), 'type' => 'number', 'units' => true, 'hasVariables' => true, 'required' => [ 'shape', '!=', '' ], ], 'right' => [ 'label' => esc_html__( 'Right', 'bricks' ), 'type' => 'number', 'units' => true, 'hasVariables' => true, 'required' => [ 'shape', '!=', '' ], ], 'bottom' => [ 'label' => esc_html__( 'Bottom', 'bricks' ), 'type' => 'number', 'units' => true, 'hasVariables' => true, 'required' => [ 'shape', '!=', '' ], ], 'left' => [ 'label' => esc_html__( 'Left', 'bricks' ), 'type' => 'number', 'units' => true, 'hasVariables' => true, 'required' => [ 'shape', '!=', '' ], ], ], ]; } // Exclude background video control from non-layout elements if ( ! $this->is_layout_element() ) { $this->controls['_background']['exclude'] = 'video'; } // GRADIENT $this->controls['_gradient'] = [ 'tab' => 'style', 'group' => '_gradient', 'type' => 'gradient', 'css' => [ [ 'property' => 'background-image', ], ], ]; // BORDER $this->controls['_border'] = [ 'tab' => 'style', 'group' => '_border', 'type' => 'border', 'label' => esc_html__( 'Border', 'bricks' ), 'css' => [ [ 'property' => 'border', ], ], ]; $this->controls['_boxShadow'] = [ 'tab' => 'style', 'group' => '_border', 'label' => esc_html__( 'Box shadow', 'bricks' ), 'type' => 'box-shadow', 'css' => [ [ 'property' => 'box-shadow', ], ], ]; // TRANSFORM $this->controls['_transform'] = [ 'tab' => 'style', 'group' => '_transform', 'type' => 'transform', 'label' => esc_html__( 'Transform', 'bricks' ), 'css' => [ [ 'property' => 'transform', ], ], 'inline' => true, 'small' => true, 'description' => sprintf( '<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transform" target="_blank" rel="noopener">%s</a>', esc_html__( 'Learn more about CSS transform', 'bricks' ) ), ]; $this->controls['_transformOrigin'] = [ 'tab' => 'style', 'group' => '_transform', 'type' => 'text', 'label' => esc_html__( 'Transform origin', 'bricks' ), 'css' => [ [ 'property' => 'transform-origin', ], ], 'inline' => true, 'hasDynamicData' => false, 'description' => sprintf( '<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/transform-origin" target="_blank" rel="noopener">%s</a>', esc_html__( 'Learn more about CSS transform-origin', 'bricks' ) ), 'placeholder' => 'center', ]; // Parallax controls (@since 2.3) $this->controls['_motionParallaxSeparator'] = [ 'tab' => 'style', 'group' => '_transform', 'label' => esc_html__( 'Parallax', 'bricks' ), 'type' => 'separator', ]; $this->controls['_motionParallaxBuilderInfo'] = [ 'tab' => 'style', 'group' => '_transform', 'type' => 'info', 'content' => esc_html__( 'Parallax effects are not active in the builder preview.', 'bricks' ), ]; $this->controls['_motionElementParallax'] = [ 'tab' => 'style', 'group' => '_transform', 'label' => esc_html__( 'Element parallax', 'bricks' ), 'type' => 'checkbox', 'description' => esc_html__( 'Move this element while scrolling.', 'bricks' ), ]; $this->controls['_motionElementParallaxSpeedX'] = [ 'tab' => 'style', 'group' => '_transform', 'label' => esc_html__( 'Horizontal speed', 'bricks' ) . ' (%)', 'type' => 'number', 'placeholder' => 0, 'inline' => true, 'small' => true, 'hasVariables' => false, 'css' => [ [ 'property' => '--brx-motion-parallax-speed-x', ] ], 'required' => [ '_motionElementParallax', '=', true ], ]; $this->controls['_motionElementParallaxSpeedY'] = [ 'tab' => 'style', 'group' => '_transform', 'label' => esc_html__( 'Vertical speed', 'bricks' ) . ' (%)', 'type' => 'number', 'placeholder' => 0, 'inline' => true, 'small' => true, 'hasVariables' => false, 'css' => [ [ 'property' => '--brx-motion-parallax-speed-y', ] ], 'required' => [ '_motionElementParallax', '=', true ], ]; $this->controls['_motionBackgroundParallax'] = [ 'tab' => 'style', 'group' => '_transform', 'label' => esc_html__( 'Background parallax', 'bricks' ), 'type' => 'checkbox', 'description' => esc_html__( 'Move the background image while scrolling.', 'bricks' ), ]; $this->controls['_motionBackgroundParallaxSpeed'] = [ 'tab' => 'style', 'group' => '_transform', 'label' => esc_html__( 'Background speed', 'bricks' ) . ' (%)', 'type' => 'number', 'inline' => true, 'small' => true, 'placeholder' => 0, 'hasVariables' => false, 'css' => [ [ 'property' => '--brx-motion-background-speed', ] ], 'required' => [ '_motionBackgroundParallax', '=', true ], ]; $this->controls['_motionStartVisiblePercent'] = [ 'tab' => 'style', 'group' => '_transform', 'label' => esc_html__( 'Parallax start point', 'bricks' ) . ' (%)', 'type' => 'number', 'min' => 0, 'max' => 100, 'placeholder' => 0, 'hasVariables' => false, 'inline' => true, 'small' => true, 'description' => esc_html__( 'Set where the parallax effect starts based on the viewport scroll progress (0 = when entering, 50 = near center).', 'bricks' ), ]; // CSS $this->controls['_cssCustom'] = [ 'tab' => 'style', 'group' => '_css', 'label' => esc_html__( 'Custom CSS', 'bricks' ), 'type' => 'code', 'mode' => 'css', 'hasVariables' => true, 'pasteStyles' => true, 'css' => [], // NOTE: Undocumented (@since 1.5.1) return true instead of array with 'property' and 'selector' data to output as plain CSS 'description' => esc_html__( 'Use "%root%" to target the element wrapper.', 'bricks' ) . ' ' . esc_html__( 'Add "%root%" via keyboard shortcut "r + TAB".', 'bricks' ), 'placeholder' => "%root% {\n color: firebrick;\n}", ]; $this->controls['_cssClasses'] = [ 'tab' => 'style', 'group' => '_css', 'label' => esc_html__( 'CSS classes', 'bricks' ), 'class' => 'ltr', 'type' => 'text', 'description' => esc_html__( 'Separated by space. Without class dot.', 'bricks' ), ]; $this->controls['_cssId'] = [ 'tab' => 'style', 'group' => '_css', 'label' => esc_html__( 'CSS ID', 'bricks' ), 'class' => 'ltr', 'type' => 'text', 'description' => esc_html__( 'No spaces. No pound (#) sign.', 'bricks' ), ]; // @since 2.1 $this->controls['_cssIdComponentInfo'] = [ 'tab' => 'style', 'group' => '_css', 'type' => 'info', 'content' => esc_html__( 'Connect the CSS ID to a property to set it on an per-instance basis to avoid same ID conflicts.', 'bricks' ), 'required' => [ [ '_cssId', '!=', '' ], [ 'id', '!=', '', 'activeComponent' ], // Show when editing component ], ]; $this->controls['_cssFilters'] = [ 'tab' => 'style', 'group' => '_css', 'label' => esc_html__( 'CSS Filters', 'bricks' ), 'titleProperty' => 'type', 'type' => 'filters', 'inline' => true, 'small' => true, 'css' => [ [ 'property' => 'filter', ], ], 'description' => sprintf( '<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/filter#Syntax">%s</a>', esc_html__( 'Learn more about CSS filters', 'bricks' ) ), ]; $this->controls['_cssTransition'] = [ 'tab' => 'style', 'group' => '_css', 'label' => esc_html__( 'Transition', 'bricks' ), 'class' => 'ltr', 'css' => [ [ 'property' => 'transition', 'selector' => isset( $this->css_selector ) ? $this->css_selector : '', ], ], 'type' => 'text', 'hasVariables' => true, 'hasDynamicData' => false, 'description' => sprintf( '<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions" target="_blank">%s</a>', esc_html__( 'Learn more about CSS transitions', 'bricks' ) ), ]; // ATTRIBUTES $this->controls['infoAttributes'] = [ 'tab' => 'style', 'group' => '_attributes', // translators: %s: link to article 'content' => sprintf( esc_html__( '%s will be added to the most relevant HTML node.', 'bricks' ), Helpers::article_link( 'custom-attributes', esc_html__( 'Custom attributes', 'bricks' ) ) ), 'type' => 'info', ]; $this->controls['_attributes'] = [ 'tab' => 'style', 'group' => '_attributes', 'placeholder' => esc_html__( 'Attributes', 'bricks' ), 'type' => 'repeater', 'titleProperty' => 'name', 'fields' => [ 'name' => [ 'label' => esc_html__( 'Name', 'bricks' ), 'type' => 'text', 'rerender' => false, ], 'value' => [ 'label' => esc_html__( 'Value', 'bricks' ), 'type' => 'text', 'rerender' => false, ], ], ]; } /** * Controls used by all elements under 'style' tab * * @since 1.0 */ public function set_controls_after() { // Custom HTML tag validation: To show error message in builder (@since 1.10.3) if ( ! empty( $this->controls['customTag'] ) ) { $this->controls['customTag']['validate'] = $this->get_in_builder_html_tag_validation_rules(); } // NOTE: Entry animations are deprecated @since 1.6 in favor of element interactions: Run new converter option! $this->controls['_animationSeparator'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Animation', 'bricks' ), 'type' => 'separator', 'deprecated' => true, ]; $this->controls['_animationInfo'] = [ 'tab' => 'style', 'group' => '_layout', 'content' => 'The "Entry animation" settings below are deprecated since 1.6. Please convert them under "Bricks > Settings > General > Converter", and use the new <a href="https://academy.bricksbuilder.io/article/interactions/" target="_blank">Interactions</a> for all new animations.', 'required' => [ '_animation', '!=', '' ], 'type' => 'info', ]; $this->controls['_animation'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Entry animation', 'bricks' ), 'type' => 'select', 'searchable' => true, 'options' => Setup::$control_options['animationTypes'], 'inline' => true, 'placeholder' => esc_html__( 'None', 'bricks' ), 'deprecated' => true, ]; $this->controls['_animationDuration'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Animation duration', 'bricks' ), 'type' => 'select', 'searchable' => true, 'options' => [ 'very-slow' => esc_html__( 'Very slow', 'bricks' ), 'slow' => esc_html__( 'Slow', 'bricks' ), 'normal' => esc_html__( 'Normal', 'bricks' ), 'fast' => esc_html__( 'Fast', 'bricks' ), 'very-fast' => esc_html__( 'Very fast', 'bricks' ), 'custom' => esc_html__( 'Custom', 'bricks' ), ], 'inline' => true, 'hasDynamicData' => false, 'placeholder' => esc_html__( 'Normal', 'bricks' ) . ' (1s)', 'required' => [ '_animation', '!=', '' ], 'deprecated' => true, ]; $this->controls['_animationDurationCustom'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Animation duration', 'bricks' ) . ' (' . esc_html__( 'Custom', 'bricks' ) . ')', 'type' => 'text', 'inline' => true, 'hasDynamicData' => false, 'info' => '500ms | 1s', 'required' => [ '_animationDuration', '=', 'custom' ], 'deprecated' => true, ]; $this->controls['_animationDelay'] = [ 'tab' => 'style', 'group' => '_layout', 'label' => esc_html__( 'Animation delay', 'bricks' ), 'type' => 'text', 'inline' => true, 'placeholder' => 0, 'info' => '500ms | -2.5s', 'required' => [ '_animation', '!=', '' ], 'deprecated' => true, ]; } /** * Builder: Helper function to get HTML tag validation rules * * @since 1.10.3 */ public function get_in_builder_html_tag_validation_rules() { // Builder: Get allowed HTML tags if ( bricks_is_builder() ) { return [ 'allowed' => Helpers::get_allowed_html_tags(), 'error' => esc_html__( 'HTML tag not allowed', 'bricks' ) . '. ' . esc_html__( 'Extend allowed tags through filter', 'bricks' ) . ' ' . Helpers::article_link( 'filter-bricks-allowed_html_tags', 'bricks/allowed_html_tags' ), ]; } return []; } /** * Get default data * * @since 1.0 */ public function get_default_data() { return [ 'label' => $this->label, 'name' => $this->name, 'controls' => $this->controls, 'controlGroups' => $this->control_groups, ]; } /** * Builder: Element placeholder HTML * * @since 1.0 */ final public function render_element_placeholder( $data = [], $type = 'info' ) { if ( $this->is_frontend ) { return; } if ( ! isset( $data['icon-class'] ) ) { $data['icon-class'] = $this->icon; } // For custom context menu $data['id'] = $this->id; echo Helpers::get_element_placeholder( $data, $type ); } /** * Return element attribute: id * * @since 1.5 * * @since 1.7.1: Parse dynamic data for _cssId (same for _cssClasses) */ public function get_element_attribute_id() { return Helpers::get_element_attribute_id( $this->id, $this->settings ); } /** * Set element root attributes (element ID, classes, etc.) * * @since 1.4 */ public function set_root_attributes() { $element = $this->element; $nestable = $this->nestable; $element_id = $this->id; $element_name = $this->name; $settings = $this->settings; $attributes = []; $has_css_settings = self::has_css_settings( $settings ); // Check element.selectors (@since 2.0) if ( ! empty( $element['selectors'] ) ) { $has_css_settings = true; } // Parent element is 'slider-nested' & 'pagination' is enabled: Ensure slide 'id' is added (needed for 'aria-controls' a11y) if ( $nestable ) { $parent_id = $element['parent'] ?? false; if ( $parent_id ) { $parent_element = Frontend::$elements[ $parent_id ] ?? false; if ( $parent_element ) { if ( isset( $parent_element['name'] ) && $parent_element['name'] === 'slider-nested' && isset( $parent_element['settings']['pagination'] ) ) { $has_css_settings = true; } } } } /** * STEP: Add element 'id' attribute * * IF: * - Custom 'id' set * - Is Offcanvas element (to ensure it works with 'Selector' setting of the Toggle element) * * OR: * - Not inside query loop && has CSS setting && not a global element && not a component (@since 1.12) */ $global_element_id = Helpers::get_global_element( $element, 'global' ); $component_id = $element['cid'] ?? $element['parentComponent'] ?? false; if ( ! empty( $settings['_cssId'] ) || ( ! Query::is_looping() && $has_css_settings && ! $global_element_id && ! $component_id ) || $element_name === 'offcanvas' // Offcanvas: Always add 'id' attribute to ensure it works with 'Selector' setting of the Toggle element ) { $attributes['id'] = $this->get_element_attribute_id(); } // STEP: Add element classes $classes = []; /** * Use component ID as class name instead of ID as main selector (every component needs to have the same CSS and non-repeating IDs) * * @since 1.12 */ if ( $component_id ) { // Get element 'name' from component (could have been converted in builder context-menu) $component_id = $element['cid'] ?? false; $component_root = $component_id ? Helpers::get_component_element_by_id( $component_id ) : false; if ( ! empty( $component_root['name'] ) ) { $this->name = $component_root['name']; } // Is component root element: Use component ID as class name if ( ! empty( $element['cid'] ) && $element['cid'] === $component_id ) { $classes[] = "brxe-{$component_id}"; } // Is component child element: Use element ID as class name else { $classes[] = "brxe-{$element_id}"; } } /** * Set main element selector brxe-{element.id} as class name instead of ID * * Query loop item: Use class name instead of ID as main selector (every item uses same styling rules) */ elseif ( Query::is_looping() ) { $classes[] = "brxe-{$element_id}"; } // Global element: Use class name instead of ID as main selector (global element can occur multiple times on a page) if ( $global_element_id && $has_css_settings ) { $classes[] = "brxe-{$global_element_id}"; } $classes[] = sanitize_html_class( "brxe-{$this->name}" ); // IS CSS grid (@since 1.6.1) if ( ! empty( $settings['_display'] ) && $settings['_display'] === 'grid' ) { $classes[] = 'brx-grid'; } // Element global classes if ( ! empty( $settings['_cssGlobalClasses'] ) ) { $classes = array_merge( $classes, self::get_element_global_classes( $settings['_cssGlobalClasses'] ) ); } // STEP: data-loop if ( Query::is_looping() ) { // Custom element ID, transform it into a class if ( ! empty( $settings['_cssId'] ) ) { $classes[] = $this->get_element_attribute_id(); } } /** * Add link attributes (class, href, rel, target) to layout element (section, container, block, div, etc.) * * If HTML tag is "a" (@since 1.9) * * @since 1.2.1 */ if ( $this->is_layout_element() && ! empty( $settings['link']['type'] ) && $this->tag === 'a' ) { $this->set_link_attributes( 'link', $settings['link'] ); $container_link = isset( $this->attributes['link'] ) ? $this->attributes['link'] : []; foreach ( $container_link as $key => $value ) { if ( $key === 'class' ) { $classes = array_merge( $classes, $value ); continue; } if ( is_array( $value ) && count( $value ) ) { $value = $value[0]; } $attributes[ $key ] = $value; } } // Custom classes (Control group: CSS) if ( ! empty( $settings['_cssClasses'] ) ) { $classes[] = $settings['_cssClasses']; } // Custom '_hidden' classes (@since 1.5) if ( ! empty( $settings['_hidden']['_cssClasses'] ) ) { $classes[] = $settings['_hidden']['_cssClasses']; } /** * Set & use 'data-script-id' attribute to init scripts with (bricksSplide, bricksSwiper, etc.) * * Generate & use random script ID inside query loop. * * @since 1.4 */ if ( ! empty( $this->scripts ) || $this->enabled_masonry() ) { $attributes['data-script-id'] = Query::is_any_looping() ? Helpers::generate_random_id( false ) : $this->uid; } /** * Masonry layout: Add classes & data attributes * * @since 1.11.1 */ if ( $this->enabled_masonry() ) { $classes[] = 'isotope'; $classes[] = 'bricks-layout-wrapper'; $classes[] = 'isotope-before-init'; // Avoid unstyled content visible (@since 1.12) $classes[] = 'bricks-masonry'; $attributes['data-brx-masonry-json'] = wp_json_encode( [ 'transitionMode' => isset( $settings['_masonryTransitionMode'] ) ? $settings['_masonryTransitionMode'] : 'scale', 'transitionDuration' => isset( $settings['_masonryTransitionDuration'] ) ? $settings['_masonryTransitionDuration'] : '0.4s', 'horizontalOrder' => isset( $settings['_masonryHorizontalOrder'] ) ? 1 : 0, ] ); } // Frontend: Lazy load nestable background images (section, container, block, div, etc.) if ( $this->lazy_load() && $nestable ) { $classes[] = 'bricks-lazy-hidden'; } // Setting up motion parallax classes and attributes if enabled (@since 2.3) $motion_parallax_settings = $this->get_motion_parallax_settings(); if ( ! empty( $motion_parallax_settings ) ) { $attributes['data-brx-motion-parallax'] = wp_json_encode( $motion_parallax_settings ); } /** * STEP: Check if array item is an array and merge into main array * * Component property type "class" with muliple options. * * @since 2.0.2 */ foreach ( $classes as $class_index => $class_name ) { if ( is_array( $class_name ) ) { $classes = array_merge( $classes, $class_name ); // Remove class index unset( $classes[ $class_index ] ); // Re-index array $classes = array_values( $classes ); } } // Parse CSS classes for dynamic data (@since 1.7.1) foreach ( $classes as $class_index => $class_name ) { if ( strpos( $class_name, '{' ) !== false && strpos( $class_name, '}' ) !== false ) { $classes[ $class_index ] = bricks_render_dynamic_data( $class_name ); } } $attributes['class'] = $classes; /** * Add custom attributes (unless element has $custom_attributes = false like "Nav Menu") */ if ( ! empty( $settings['_attributes'] ) && $this->custom_attributes === true ) { // Add custom attributes (overwrites existing $attributes if needed) $custom_attributes = $this->get_custom_attributes( $settings ); if ( is_array( $custom_attributes ) ) { foreach ( $custom_attributes as $att_key => $att_val ) { // Trim key $att_key = $att_key ? trim( $att_key ) : ''; // Replace white space with dash $att_key = str_replace( ' ', '-', $att_key ); if ( $att_key ) { // Ensure custom classes are added to existing attribute as an array value (@since 1.11.1) if ( isset( $attributes[ $att_key ] ) && is_array( $attributes[ $att_key ] ) ) { $att_val = is_string( $att_val ) ? [ $att_val ] : $att_val; $attributes[ $att_key ] = array_merge( $attributes[ $att_key ], $att_val ); } else { $attributes[ $att_key ] = $att_val; } } } } } // @see https://academy.bricksbuilder.io/article/filter-bricks-set_root_attributes/ $attributes = apply_filters( 'bricks/element/set_root_attributes', $attributes, $this ); // Add data-brx-variant attribute for component instances with selected variant (@since 2.2) if ( ! empty( $this->element['variant'] ) && ! empty( $this->element['cid'] ) ) { // Strip 'variant-' prefix for cleaner markup $attributes['data-brx-variant'] = str_replace( 'variant-', '', $this->element['variant'] ); } $this->attributes['_root'] = $attributes; } /** * Helper function to get motion parallax settings. * * @since 2.3 */ protected function get_motion_parallax_settings() { $settings = $this->settings; $element_parallax = ! empty( $settings['_motionElementParallax'] ); $background_parallax = ! empty( $settings['_motionBackgroundParallax'] ); $motion_parallax_settings = []; // Return empty array if no parallax settings are set if ( ! $element_parallax && ! $background_parallax ) { return $motion_parallax_settings; } if ( $element_parallax ) { $motion_parallax_settings['element'] = true; } if ( $background_parallax ) { $motion_parallax_settings['background'] = true; } $motion_progress_start = $settings['_motionStartVisiblePercent'] ?? 0; $motion_progress_start = is_numeric( $motion_progress_start ) ? (float) $motion_progress_start : 0; $motion_progress_start = min( 100, max( 0, $motion_progress_start ) ); $motion_parallax_settings['startVisiblePercent'] = $motion_progress_start; return $motion_parallax_settings; } /** * Return true if element has 'css' settings * * @return boolean * * @since 1.5 */ public function has_css_settings( $settings ) { // Builder: Always add element 'id' & class (needed in query loop to render 2..last items) if ( bricks_is_builder_call() ) { return true; } /** * Always add element 'id' for the following elements: * * Nav menu: to add 'mobileMenu' <style> tag to <head> which contain element 'id' * * @since 1.5.1 * * @since 1.8 'nav-nested' * * @since 1.9.8 'pagination', frontend AJAX need to use ID to identify the correct DOM when replacement (#86bxet3c3) */ if ( in_array( $this->name, [ 'nav-menu', 'nav-nested', 'pagination' ] ) ) { return true; } // Experimental element ID & class setting not enabled: Always add element ID & class if ( ! isset( Database::$global_settings['elementAttsAsNeeded'] ) ) { return true; } $has_css_settings = false; $element_attribute_id = $this->get_element_attribute_id(); // STEP: Check for 'css' setting foreach ( $settings as $key => $value ) { // Remove pseudo class & breapkoint keys to get plain control if ( $key && strpos( $key, ':' ) ) { $control_key_parts = explode( ':', $key ); // First part is plain control key if ( count( $control_key_parts ) > 1 ) { $key = $control_key_parts[0]; } } $control = ! empty( $this->controls[ $key ] ) ? $this->controls[ $key ] : false; // Check for breakpoint settings if ( ! $control ) { foreach ( Breakpoints::$breakpoints as $bp ) { $breakpoint_key = $bp['key']; // Setting contains breakpoint key (e.g.: "_background:tablet_portrait") if ( $breakpoint_key !== 'desktop' && strpos( $key, ":$breakpoint_key" ) ) { $has_css_settings = true; break; } } } if ( ! $control ) { continue; } // Loop over repeater items to see if it contains any CSS settings if ( ! empty( $control['type'] ) && $control['type'] === 'repeater' ) { if ( is_array( $value ) ) { foreach ( $value as $repeater_item ) { if ( is_array( $repeater_item ) ) { foreach ( $repeater_item as $repeater_key => $repeater_value ) { $repeater_control_key = $repeater_key; // Normalize repeater item key to plain field key (remove breakpoint & pseudo-class suffixes). if ( strpos( $repeater_control_key, ':' ) ) { $repeater_control_key_parts = explode( ':', $repeater_control_key ); if ( ! empty( $repeater_control_key_parts[0] ) ) { $repeater_control_key = $repeater_control_key_parts[0]; } } // Fallback for legacy syntax (@pre 1.3.5). foreach ( Breakpoints::$breakpoints as $bp ) { $breakpoint_key = $bp['key']; $repeater_control_key = str_replace( "_$breakpoint_key", '', $repeater_control_key ); } $repeater_control_key = str_replace( '_hover', '', $repeater_control_key ); $repeater_control = ! empty( $this->controls[ $key ]['fields'][ $repeater_control_key ] ) ? $this->controls[ $key ]['fields'][ $repeater_control_key ] : false; // Return true: Property besides 'library' and 'svg' set (e.g. height, width, etc.) if ( ! empty( $repeater_value['svg'] ) && count( $repeater_value['svg'] ) > 2 ) { $has_css_settings = true; break; } if ( isset( $repeater_control['css'] ) ) { $has_css_settings = true; break; } } } } } } // Icon has 'css' property, but only used if 'svg' option is selected if ( ! empty( $control['type'] ) && $control['type'] === 'icon' ) { // Return true: Property besides 'library' and 'svg' set (e.g. height, width, etc.) if ( ! empty( $value['svg'] ) && count( $value['svg'] ) > 2 ) { $has_css_settings = true; break; } } // Is CSS control if ( isset( $control['css'] ) ) { $has_css_settings = true; break; } // Check for element ID use in custom CSS if ( $key === '_cssCustom' ) { if ( strpos( $value, $element_attribute_id ) !== false ) { $has_css_settings = true; break; } } } if ( $has_css_settings ) { return true; } // STEP: Global settings 'customCss' contain element ID if ( ! empty( Database::$global_settings['customCss'] ) && strpos( Database::$global_settings['customCss'], $element_attribute_id ) !== false ) { return true; } // STEP: Page settings 'customCss' contain element ID if ( ! empty( Database::$page_settings['customCss'] ) && strpos( Database::$page_settings['customCss'], $element_attribute_id ) !== false ) { return true; } return false; } /** * Convert the global classes ids into the classes names * * @param array $class_ids The global classes ids. * * @return array */ public static function get_element_global_classes( $class_ids ) { $global_classes = Database::$global_data['globalClasses']; if ( ! is_array( $global_classes ) || empty( $global_classes ) || empty( $class_ids ) || ! is_array( $class_ids ) ) { return []; } $element_classes = []; $class_ids_names = wp_list_pluck( $global_classes, 'name', 'id' ); foreach ( $class_ids as $class_id ) { if ( ! isset( $class_ids_names[ $class_id ] ) ) { continue; } $element_classes[] = $class_ids_names[ $class_id ]; } return $element_classes; } /** * Set HTML element attribute + value(s) * * @param string $key Element identifier. * @param string $attribute Attribute to set value(s) for. * @param string|array $value Set single value (string) or values (array). * * @since 1.0 */ public function set_attribute( $key, $attribute, $value = null ) { if ( is_array( $value ) ) { foreach ( $value as $val ) { $this->attributes[ $key ][ $attribute ][] = $val; } return; } if ( empty( $value ) && ! is_numeric( $value ) ) { $this->attributes[ $key ][ $attribute ] = ''; return; } // Attribute with value already exists, but is not an array: Convert to array first and add new value if ( isset( $this->attributes[ $key ][ $attribute ] ) && ! is_array( $this->attributes[ $key ][ $attribute ] ) ) { $this->attributes[ $key ][ $attribute ] = [ $this->attributes[ $key ][ $attribute ], $value, ]; return; } $this->attributes[ $key ][ $attribute ][] = $value; } /** * Set link attributes * * Helper to set attributes for control type 'link' * * @since 1.0 * * @param string $attribute_key Desired key for set_attribute. * @param string $link_settings Element control type 'link' settings. */ public function set_link_attributes( $attribute_key, $link_settings ) { $link_type = $link_settings['type'] ?? ''; if ( ! $link_type ) { return; } // Use correct post_id when parsing dynamic tag on blog posts page (@since 1.10) $post_id = is_home() || ( Woocommerce::is_woocommerce_active() && is_shop() ) ? $this->post_id : get_the_ID(); // Save the final generated link URL (@since 2.0) $link_url = ''; // Internal link if ( $link_type === 'internal' && isset( $link_settings['postId'] ) ) { $permalink = get_the_permalink( $link_settings['postId'] ); // Add URL parameters (@since 1.10.2) if ( ! empty( $link_settings['urlParams'] ) ) { $url_params = $this->render_dynamic_data( $link_settings['urlParams'] ); $permalink = esc_url( $permalink . $url_params ); // Escape after adding URL parameters } $link_url = $permalink; // Skip 'href' attribute if the value is empty (@since 2.1) if ( ! empty( $permalink ) ) { $this->set_attribute( $attribute_key, 'href', $permalink ); } } // Taxonomy term link (@since 1.10.2) elseif ( $link_type === 'taxonomy' && isset( $link_settings['term'] ) ) { // Split termData into array (format: "taxonomy::termId") $term_id_parts = explode( '::', $link_settings['term'] ); if ( count( $term_id_parts ) === 2 ) { $term_id = $term_id_parts[1]; $taxonomy = $term_id_parts[0]; $term = get_term( $term_id, $taxonomy ); if ( $term && ! is_wp_error( $term ) ) { $link_url = get_term_link( $term ); // Skip 'href' attribute if the value is empty (@since 2.1) if ( ! empty( $link_url ) ) { $this->set_attribute( $attribute_key, 'href', $link_url ); } } } } // External link: Use same logic as dynamic data link (@since 1.12) elseif ( $link_type === 'external' && isset( $link_settings['url'] ) ) { // Check for the old dynamic data format $raw_href = (string) $link_settings['url'] ?? ''; // It is a composed link e.g. "https://my-domain.com/?p={post_id}" if ( strpos( $raw_href, '{' ) !== 0 || substr_count( $raw_href, '}' ) > 1 ) { $context = 'text'; } // It is a dynamic data tag only e.g. "{post_url}" else { $context = 'link'; } $href = bricks_render_dynamic_data( $raw_href, $post_id, $context ); $link_url = $href; // Skip 'href' attribute if the value is empty (@since 2.1) if ( ! empty( $href ) ) { $this->set_attribute( $attribute_key, 'href', $href ); } } // Lightbox image or video: Set lightbox ID through 'data-pswp-id' attribute if ( strpos( $link_type, 'lightbox' ) !== false && isset( $link_settings['lightboxId'] ) ) { $this->set_attribute( $attribute_key, 'data-pswp-id', $link_settings['lightboxId'] ); } /** * Lightbox * * Photoswipe required width & height * * Lightbox image: Uses whatever intrinsic width & height the image has. * Lightbox video: Default is 1280x720 (16:9). */ $lightbox_width = Theme_Styles::get_setting_by_key( 'general', 'lightboxWidth' ) ?? 1280; $lightbox_height = Theme_Styles::get_setting_by_key( 'general', 'lightboxHeight' ) ?? 720; // Lightbox image if ( $link_type === 'lightboxImage' ) { $lightbox_image = $link_settings['lightboxImage'] ?? false; $image_size = $lightbox_image['size'] ?? BRICKS_DEFAULT_IMAGE_SIZE; $image_url = $lightbox_image['url'] ?? false; $image_id = $lightbox_image['id'] ?? 0; $image = $image_id ? wp_get_attachment_image_src( $image_id, $image_size ) : false; $lightbox_anim = $link_settings['lightboxAnimationType'] ?? 'zoom'; // Dynamic data lightbox image if ( ! empty( $lightbox_image['useDynamicData'] ) ) { $image = Integrations\Dynamic_Data\Providers::render_tag( $lightbox_image['useDynamicData'], $post_id, 'image', [ 'size' => $image_size ] ); if ( ! empty( $image[0] ) ) { $image_url = $image[0]; // DD is image ID, not URL if ( is_numeric( $image[0] ) ) { $image = wp_get_attachment_image_src( $image[0], $image_size ); } } } if ( $image ) { $image_url = $image[0] ?? ''; $lightbox_width = $image[1] ?? ''; $lightbox_height = $image[2] ?? ''; } // Skip 'href' attribute if the value is empty (@since 2.1) if ( ! empty( $image_url ) ) { $this->set_attribute( $attribute_key, 'href', $image_url ); } $this->set_attribute( $attribute_key, 'class', 'bricks-lightbox' ); $this->set_attribute( $attribute_key, 'data-pswp-src', $image_url ); $this->set_attribute( $attribute_key, 'data-pswp-width', $lightbox_width ); $this->set_attribute( $attribute_key, 'data-pswp-height', $lightbox_height ); $this->set_attribute( $attribute_key, 'data-animation-type', $lightbox_anim ); } // Lightbox video if ( $link_type === 'lightboxVideo' && isset( $link_settings['lightboxVideo'] ) ) { $video_url = bricks_render_dynamic_data( $link_settings['lightboxVideo'], $this->post_id, 'link' ); // We expect a URL, so set context to "link" to ensure correct rendering of dynamic data (@since 2.3) // Skip 'href' attribute if the value is empty (@since 2.1) if ( ! empty( $video_url ) ) { $this->set_attribute( $attribute_key, 'href', $video_url ); } $this->set_attribute( $attribute_key, 'class', 'bricks-lightbox' ); $this->set_attribute( $attribute_key, 'data-pswp-width', $lightbox_width ); $this->set_attribute( $attribute_key, 'data-pswp-height', $lightbox_height ); $this->set_attribute( $attribute_key, 'data-pswp-video-url', $video_url ); // Disable controls (@since 1.10.3) if ( isset( $link_settings['lightboxVideoNoControls'] ) ) { $this->set_attribute( $attribute_key, 'data-no-controls', 1 ); } // Mute video (@since 2.1) if ( isset( $link_settings['lightboxVideoMuted'] ) ) { $this->set_attribute( $attribute_key, 'data-muted', 1 ); } } // Dynamic data link if ( $link_type === 'meta' && ! empty( $link_settings['useDynamicData'] ) ) { // Check for the old dynamic data format $link_dd_tag = $link_settings['useDynamicData']['name'] ?? (string) $link_settings['useDynamicData']; // It is a composed link e.g. "https://my-domain.com/?p={post_id}" (@since 1.5.4) if ( strpos( $link_dd_tag, '{' ) !== 0 || substr_count( $link_dd_tag, '}' ) > 1 ) { $context = 'text'; } // It is a dynamic data tag only e.g. "{post_url}" else { $context = 'link'; } $href = bricks_render_dynamic_data( $link_dd_tag, $post_id, $context ); $link_url = $href; // Skip 'href' attribute if the value is empty (@since 2.1) if ( ! empty( $href ) ) { $this->set_attribute( $attribute_key, 'href', $href ); } } // Media link if ( $link_type === 'media' && isset( $link_settings['mediaData']['id'] ) ) { $link_url = wp_get_attachment_url( $link_settings['mediaData']['id'] ); // Skip 'href' attribute if the value is empty (@since 2.1) if ( ! empty( $link_url ) ) { $this->set_attribute( $attribute_key, 'href', $link_url ); } } if ( isset( $link_settings['rel'] ) ) { $rel = bricks_render_dynamic_data( $link_settings['rel'], $post_id ); // Dynamic data (@since 1.12.3) $this->set_attribute( $attribute_key, 'rel', $rel ); } if ( isset( $link_settings['newTab'] ) ) { $this->set_attribute( $attribute_key, 'target', '_blank' ); } if ( isset( $link_settings['title'] ) ) { $title = bricks_render_dynamic_data( $link_settings['title'], $post_id ); // Dynamic data (@since 1.12.3) $this->set_attribute( $attribute_key, 'title', $title ); } if ( isset( $link_settings['ariaLabel'] ) ) { $aria_label = bricks_render_dynamic_data( $link_settings['ariaLabel'], $post_id ); // Dynamic data (@since 1.12.3) $this->set_attribute( $attribute_key, 'aria-label', $aria_label ); } // STEP: Set data-brx-anchor and aria-current attributes. Ensure it is not an empty string or "#" if ( empty( $link_url ) || $link_url === '#' || ! is_string( $link_url ) ) { return; } /** * Is anchor link: Add 'data-brx-anchor' attribute to anchor link for frontend JS * * @since 1.11 */ if ( strpos( $link_url, '#' ) !== false ) { $this->set_attribute( $attribute_key, 'data-brx-anchor', 'true' ); } // Not an anchor link: Set aria-current="page" attribute to the link if it points to the current page (@since 1.8) elseif ( $link_url && Helpers::maybe_set_aria_current_page( $link_url ) ) { $this->set_attribute( $attribute_key, 'aria-current', 'page' ); } } /** * Remove attribute * * @param string $key Element identifier. * @param string $attribute Attribute to remove. * @param string|null $value Set to remove single value instead of entire attribute. * * @since 1.0 */ public function remove_attribute( $key, $attribute, $value = null ) { if ( ! isset( $this->attributes[ $key ] ) || ! is_array( $this->attributes[ $key ] ) ) { return; } if ( isset( $value ) ) { // Remove single attribute value $key_to_remove = array_search( $value, $this->attributes[ $key ][ $attribute ] ); array_splice( $this->attributes[ $key ][ $attribute ], $key_to_remove, 1 ); } else { // Remove entire attribute $key_to_remove = array_search( $attribute, $this->attributes[ $key ] ); array_splice( $this->attributes[ $key ], $key_to_remove, 1 ); } } /** * Render HTML attributes for specific element * * @param string $key Attribute identifier. * @param boolean $add_custom_attributes true to get custom atts for elements where we don't add them to the wrapper (Nav Menu). * * @since 1.0 */ public function render_attributes( $key, $add_custom_attributes = false ) { // @see: https://academy.bricksbuilder.io/article/filter-bricks-element-render_attributes/ $attributes = apply_filters( 'bricks/element/render_attributes', $this->attributes, $key, $this ); // Return: No attributes set for this element if ( ! isset( $attributes[ $key ] ) ) { return; } // Builder: Custom attributes (@since 1.10) if ( is_array( $attributes ) && count( $attributes ) ) { $builder_attributes = $attributes; $builder_attributes['_original'] = $this->attributes; // Store original attributes before any modification (#86c6uagww; @since 2.2) // Store custom attribute names for this element to be used in the builder to populate correct attributes (#86c8v6que; @since 2.3.1) $builder_attributes['_customAttrNames'] = []; $custom_attributes = $this->get_custom_attributes( $this->settings ); if ( is_array( $custom_attributes ) && count( $custom_attributes ) ) { foreach ( array_keys( $custom_attributes ) as $att_key ) { // Keep same normalization as set_root_attributes/getElementAttributes to avoid key mismatches. $att_key = $att_key ? trim( $att_key ) : ''; $att_key = str_replace( ' ', '-', $att_key ); if ( $att_key && Helpers::is_valid_html_attribute_name( $att_key ) ) { $builder_attributes['_customAttrNames'][] = $att_key; } } $builder_attributes['_customAttrNames'] = array_values( array_unique( $builder_attributes['_customAttrNames'] ) ); } // Use element UID as key to consider multiple instances of same element (#86c6tayw9; @since 2.2) Builder::$html_attributes[ $this->uid ] = $builder_attributes; } $attribute_strings = []; // Add custom attributes and overwrite existing $attributes if ( $add_custom_attributes ) { $custom_attributes = $this->get_custom_attributes( $this->settings ); if ( is_array( $custom_attributes ) ) { foreach ( $custom_attributes as $att_key => $att_val ) { if ( Helpers::is_valid_html_attribute_name( $att_key ) ) { $attributes[ $key ][ $att_key ] = $att_val; } } } } foreach ( $attributes[ $key ] as $key => $value ) { // Skip invalid HTML attribute name if ( ! Helpers::is_valid_html_attribute_name( $key ) ) { continue; } // Empty, non-numeric value if ( empty( $value ) && ! is_numeric( $value ) ) { $attribute_strings[] = $key; } // Array value else { if ( is_array( $value ) ) { // Filter out empty values $value = array_filter( $value, function( $val ) { return ! empty( $val ) || is_numeric( $val ); } ); $value = join( ' ', $value ); } // STEP: Escape HTML attribute value according to its type // File URL if ( is_string( $value ) && strpos( $value, 'file:' ) === 0 ) { $value = htmlspecialchars( $value ); } // URL elseif ( filter_var( $value, FILTER_VALIDATE_URL ) ) { $value = esc_url( $value ); } // HTML else { $value = esc_attr( $value ); } $attribute_strings[] = "$key=\"$value\""; } } return join( ' ', $attribute_strings ); } /** * Calculate element custom attributes based on settings (dynamic data too) * * @since 1.3 */ public function get_custom_attributes( $settings = [] ) { $attributes = ! empty( $settings['_attributes'] ) && is_array( $settings['_attributes'] ) ? $settings['_attributes'] : []; // Return: No attributes set if ( empty( $attributes ) ) { return []; } $custom_attributes = []; foreach ( $attributes as $field ) { if ( ! empty( $field['name'] ) ) { $attribute_id = $field['id'] ?? ''; // Use 'esc_attr' instead of 'sanitize_title' to avoid removing ':' (e.g. AlpineJS) $attribute_name = esc_attr( $field['name'] ); $attribute_value = isset( $field['value'] ) ? bricks_render_dynamic_data( $field['value'], $this->post_id ) : ''; // Get attribute value from setting "_attributes|{property.id}|value" if ( $attribute_value === '' ) { $attribute_value = isset( $settings[ "_attributes|$attribute_id|value" ] ) ? $settings[ "_attributes|$attribute_id|value" ] : ''; } $custom_attributes[ $attribute_name ] = $attribute_value; } } return $custom_attributes; } public static function stringify_attributes( $attributes = [] ) { $string = []; foreach ( $attributes as $key => $values ) { $value = is_array( $values ) ? join( ' ', $values ) : $values; $string[] = $key . '="' . esc_attr( $value ) . '"'; } return join( ' ', $string ); } /** * Enqueue element-specific styles and scripts * * @since 1.0 */ public function enqueue_scripts() {} /** * Element HTML render * * @since 1.0 */ public function render() {} /** * Element HTML render in builder via x-template * * @since 1.0 */ public static function render_builder() {} /** * Builder: Get nestable item * * Use as blueprint for nestable children & when adding repeater item. * * @since 1.5 */ public function get_nestable_item() {} /** * Builder: Array of child elements added when inserting new nestable element * * @since 1.5 */ public function get_nestable_children() {} /** * Frontend: Lazy load (images, videos) * * Global settings 'disableLazyLoad': Disable lazy load altogether * Page settings 'disableLazyLoad': Disable lazy load on this page (@since 1.8.6) * Element settings 'disableLazyLoad': Carousel, slider, testimonials (= bricksSwiper) (@since 1.4) * * @since 1.0 */ public function lazy_load() { // Skip lazy load: Custom HTML attribute set to loading=eager (@since 1.6) $custom_attributes = ! empty( $this->settings['_attributes'] ) ? $this->settings['_attributes'] : []; $skip_lazy_load = false; if ( is_array( $custom_attributes ) ) { foreach ( $custom_attributes as $attr ) { if ( isset( $attr['name'] ) && $attr['name'] === 'loading' && isset( $attr['value'] ) && $attr['value'] === 'eager' ) { $skip_lazy_load = true; } } // Skip loading=eager if ( $skip_lazy_load ) { return false; } } return $this->is_frontend && ! bricks_is_ajax_call() && ! bricks_is_rest_call() && ! isset( Database::$global_settings['disableLazyLoad'] ) && ! isset( Database::$page_settings['disableLazyLoad'] ) && ! isset( $this->settings['disableLazyLoad'] ); } /** * Enqueue element scripts & styles, set attributes, render * * @since 1.0 */ public function init() { // Dynamic data providers need the active element to resolve component-scoped // query IDs (#86c4yu6kr; @since 2.3.8) Integrations\Dynamic_Data\Providers::push_render_context( [ 'element' => $this->element, ] ); try { $is_builder_call = bricks_is_builder() || bricks_is_builder_call(); $hide_element_in_builder = ! empty( $this->settings['_hideElementBuilder'] ) && $is_builder_call; $hide_element_in_frontend = ! empty( $this->settings['_hideElementFrontend'] ) && ! $is_builder_call; // Enqueue scripts & styles if element is going to be rendered if ( ! $hide_element_in_frontend ) { $this->enqueue_scripts(); } // Enqueue Masonry scripts (@since 1.11.1) $this->maybe_enqueue_masonry_scripts(); // Set global $post with builder AJAX/REST API submitted postId to retrieve correct post object (unless it is looping) if ( Query::is_looping() && Query::get_loop_object_type() == 'post' ) { $post_id = Query::get_loop_object_id(); } else { /** * Changed from Database::$page_data['preview_or_post_id'] to Database::$page_data['original_post_id'] to ensure setup_query runs inside of a template * * NOTE: Undocumented */ $post_id = apply_filters( 'bricks/builder/data_post_id', isset( Database::$page_data['original_post_id'] ) ? Database::$page_data['original_post_id'] : Database::$page_data['preview_or_post_id'] ); } $this->set_post_id( $post_id ); /** * Populate repeater items with component property settings * * Run before 'set_root_attributes' as _attributes can contain connected properties. * * @since 1.12 */ if ( ! empty( $this->settings ) && is_array( $this->settings ) ) { foreach ( $this->settings as $key => $repeater_items ) { $control_type = $this->controls[ $key ]['type'] ?? false; if ( $control_type === 'repeater' && $key !== '_children' ) { foreach ( $repeater_items as $repeater_item_index => $repeater_item_value ) { $this->settings[ $key ][ $repeater_item_index ] = $this->get_component_repeater_item_settings( $repeater_item_value, $key ); } } } } /** * Set root attributes * * Need to run before we apply the 'bricks/element/render' filter. * NOTE TODO: 'bricks/element/settings' filter should be applied before 'set_root_attributes' * as you could programmatically set the '_attributes' setting using the 'bricks/element/settings' filter. * * @since 1.10.1 */ $this->set_root_attributes(); /** * Setup query if post or page direct edit with Bricks (#861m48kv4) * * If bricks_is_builder_call(), shouldn't setup query if looping. */ $setup_preview_query = Helpers::is_bricks_template( $post_id ) || ( bricks_is_builder_call() && ! Query::is_looping() ); if ( $setup_preview_query ) { $this->setup_query( $post_id ); } $render_element = true; // Hide element in builder: Do not render as long as it's a builder call (@since 2.0.1) // Hide element in frontend: Do not render as long as it's not builder call (@since 2.0.1) if ( $hide_element_in_builder || $hide_element_in_frontend ) { $render_element = false; } // Check element conditions. Use _conditions before changes via bricks/element/settings hook elseif ( ! empty( $this->settings['_conditions'] ) ) { $render_element = Conditions::check( $this->settings['_conditions'], $this ); } // Always render element if it is the queried element in REST API call (#86c5ruqqz; @since 2.1) if ( Api::is_bricks_rest_request() && (string) Api::$query_element_id === (string) $this->id ) { $render_element = true; } // https://academy.bricksbuilder.io/article/filter-bricks-element-render/ (@since 1.5 to interject element render) $render_element = apply_filters( 'bricks/element/render', $render_element, $this ); if ( $render_element ) { /** * Interject element settings for translation plugins, etc. * * https://academy.bricksbuilder.io/article/filter-bricks-element-settings/ */ $this->settings = apply_filters( 'bricks/element/settings', $this->settings, $this ); // Update $this->element['settings'] as some function such as Helpers::populate_query_vars_for_element() use $element['settings'] (@since 1.10) $this->element['settings'] = $this->settings; $this->render(); } // Restore query if post or page direct edit with Bricks if ( $setup_preview_query ) { $this->restore_query(); } } finally { // Always unwind the provider context so a failed render cannot leak // component state into the next element (#86c4yu6kr; @since 2.3.8) Integrations\Dynamic_Data\Providers::pop_render_context(); } } /** * Calculate column width */ public function calc_column_width( $columns_count = 1, $max = false ) { $column_width = floor( 100 / intval( $columns_count ) ); if ( is_int( $max ) && $columns_count > $max ) { return floor( 100 / intval( $max ) ); } return $column_width; } /** * Column width calculator * * @param int $columns Number of columns. * @param int $count Total amount of items. */ public function column_width( $columns, $count ) { // If more columns are requested than available use $count instead of $columns if ( $columns > $count ) { $width = 100 / $count; } else { $width = 100 / $columns; } return $width; } /** * Post fields * * Shared between elements: Carousel, Posts, Products, etc. * * @since 1.0 */ public function get_post_fields() { $post_controls = []; $post_controls['fields'] = [ 'tab' => 'content', 'group' => 'fields', 'placeholder' => esc_html__( 'Field', 'bricks' ), 'type' => 'repeater', 'selector' => 'fieldId', 'titleProperty' => 'dynamicData', 'fields' => [ 'dynamicData' => [ 'label' => esc_html__( 'Dynamic data', 'bricks' ), 'type' => 'text', ], 'tag' => [ 'label' => esc_html__( 'HTML tag', 'bricks' ), 'type' => 'select', 'options' => [ 'div' => 'div', 'p' => 'p', 'h1' => 'h1', 'h2' => 'h2', 'h3' => 'h3', 'h4' => 'h4', 'h5' => 'h5', 'h6' => 'h6', ], 'inline' => true, 'placeholder' => 'div', ], 'width' => [ 'label' => esc_html__( 'Width', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'width', ], ], ], 'dynamicMargin' => [ 'label' => esc_html__( 'Margin', 'bricks' ), 'type' => 'spacing', 'css' => [ [ 'property' => 'margin', ], ], ], 'dynamicPadding' => [ 'label' => esc_html__( 'Padding', 'bricks' ), 'type' => 'spacing', 'css' => [ [ 'property' => 'padding', ], ], ], 'dynamicBackground' => [ 'label' => esc_html__( 'Background color', 'bricks' ), 'type' => 'color', 'css' => [ [ 'property' => 'background-color', ], ], ], 'dynamicBorder' => [ 'label' => esc_html__( 'Border', 'bricks' ), 'type' => 'border', 'css' => [ [ 'property' => 'border', ], ], ], 'dynamicTypography' => [ 'label' => esc_html__( 'Typography', 'bricks' ), 'type' => 'typography', 'css' => [ [ 'property' => 'font', ], ], ], // Overlay 'overlay' => [ 'label' => esc_html__( 'Overlay', 'bricks' ), 'type' => 'checkbox', 'inline' => true, 'description' => esc_html__( 'Precedes "Link Image" setting.', 'bricks' ), ], ], 'default' => [ [ 'dynamicData' => '{post_title:link}', 'tag' => 'h3', 'dynamicMargin' => [ 'top' => 20, 'right' => 0, 'bottom' => 20, 'left' => 0, ], 'id' => Helpers::generate_random_id( false ), ], [ 'dynamicData' => '{post_excerpt:20}', 'id' => Helpers::generate_random_id( false ), ], ], ]; return $post_controls; } /** * Post content * * Shared between elements: Carousel, Posts * * @since 1.0 */ public function get_post_content() { $post_content = []; $post_content['contentAlign'] = [ 'tab' => 'content', 'group' => 'content', 'type' => 'select', 'label' => esc_html__( 'Alignment', 'bricks' ), 'options' => [ 'top left' => esc_html__( 'Top left', 'bricks' ), 'top center' => esc_html__( 'Top center', 'bricks' ), 'top right' => esc_html__( 'Top right', 'bricks' ), 'middle left' => esc_html__( 'Middle left', 'bricks' ), 'middle center' => esc_html__( 'Middle center', 'bricks' ), 'middle right' => esc_html__( 'Middle right', 'bricks' ), 'bottom left' => esc_html__( 'Bottom left', 'bricks' ), 'bottom center' => esc_html__( 'Bottom center', 'bricks' ), 'bottom right' => esc_html__( 'Bottom right', 'bricks' ), ], 'inline' => true, ]; // NOTE: Necessary as Isotope doesn't play nice with flexbox, but float // https://github.com/metafizzy/isotope/issues/1234 $post_content['contentHeight'] = [ 'tab' => 'content', 'group' => 'content', 'label' => esc_html__( 'Min. height', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'min-height', 'selector' => '.content-wrapper', ], ], 'rerender' => true, ]; $post_content['contentMargin'] = [ 'tab' => 'content', 'group' => 'content', 'type' => 'spacing', 'label' => esc_html__( 'Margin', 'bricks' ), 'css' => [ [ 'property' => 'margin', 'selector' => '.content-wrapper', ], ], ]; $post_content['contentPadding'] = [ 'tab' => 'content', 'group' => 'content', 'type' => 'spacing', 'label' => esc_html__( 'Padding', 'bricks' ), 'css' => [ [ 'property' => 'padding', 'selector' => '.content-wrapper', ], ], ]; $post_content['contentBackground'] = [ 'tab' => 'content', 'group' => 'content', 'type' => 'color', 'label' => esc_html__( 'Background color', 'bricks' ), 'css' => [ [ 'property' => 'background-color', 'selector' => '.content-wrapper', ], ], ]; return $post_content; } /** * Post overlay * * Shared between elements: Carousel, Posts * * @since 1.0 */ public function get_post_overlay() { $post_overlay = []; $post_overlay['overlayOnHover'] = [ 'tab' => 'content', 'group' => 'overlay', 'label' => esc_html__( 'Show on hover', 'bricks' ), 'type' => 'checkbox', 'inline' => true, 'small' => true, 'description' => esc_html__( 'Always shows in builder for editing.', 'bricks' ), ]; $post_overlay['overlayAnimation'] = [ 'tab' => 'content', 'group' => 'overlay', 'label' => esc_html__( 'Fade in animation', 'bricks' ), 'type' => 'select', 'options' => [ 'fade-in-up' => esc_html__( 'Fade in up', 'bricks' ), 'fade-in-right' => esc_html__( 'Fade in right', 'bricks' ), 'fade-in-down' => esc_html__( 'Fade in down', 'bricks' ), 'fade-in-left' => esc_html__( 'Fade in left', 'bricks' ), 'zoom-in' => esc_html__( 'Zoom in', 'bricks' ), 'zoom-out' => esc_html__( 'Zoom out', 'bricks' ), ], 'inline' => true, 'required' => [ 'overlayOnHover', '!=', '' ], ]; $post_overlay['overlayAlign'] = [ 'tab' => 'content', 'group' => 'overlay', 'type' => 'select', 'label' => esc_html__( 'Alignment', 'bricks' ), 'options' => [ 'top left' => esc_html__( 'Top left', 'bricks' ), 'top center' => esc_html__( 'Top center', 'bricks' ), 'top right' => esc_html__( 'Top right', 'bricks' ), 'middle left' => esc_html__( 'Middle left', 'bricks' ), 'middle center' => esc_html__( 'Middle center', 'bricks' ), 'middle right' => esc_html__( 'Middle right', 'bricks' ), 'bottom left' => esc_html__( 'Bottom left', 'bricks' ), 'bottom center' => esc_html__( 'Bottom center', 'bricks' ), 'bottom right' => esc_html__( 'Bottom right', 'bricks' ), ], 'inline' => true, ]; $post_overlay['overlayMargin'] = [ 'tab' => 'content', 'group' => 'overlay', 'type' => 'spacing', 'label' => esc_html__( 'Margin', 'bricks' ), 'css' => [ [ 'property' => 'margin', 'selector' => '.overlay-wrapper', ], ], ]; $post_overlay['overlayPadding'] = [ 'tab' => 'content', 'group' => 'overlay', 'type' => 'spacing', 'label' => esc_html__( 'Padding', 'bricks' ), 'css' => [ [ 'property' => 'padding', 'selector' => '.overlay-wrapper', ], ], ]; $post_overlay['overlayBackground'] = [ 'tab' => 'content', 'group' => 'overlay', 'type' => 'color', 'label' => esc_html__( 'Background color', 'bricks' ), 'css' => [ [ 'property' => 'background-color', 'selector' => '.overlay-wrapper', ], ], ]; $post_overlay['overlayInnerBackground'] = [ 'tab' => 'content', 'group' => 'overlay', 'type' => 'color', 'label' => esc_html__( 'Inner background color', 'bricks' ), 'css' => [ [ 'property' => 'background-color', 'selector' => '.overlay-inner', ], ], ]; return $post_overlay; } /** * Get swiper controls * * Elements: Carousel, Slider, Team Members. * * @since 1.0 */ public static function get_swiper_controls() { $controls['height'] = [ 'tab' => 'content', 'group' => 'settings', 'label' => esc_html__( 'Height', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'height', 'selector' => '.swiper-slide', ], ], 'placeholder' => 300, ]; $controls['gutter'] = [ 'tab' => 'content', 'group' => 'settings', 'label' => esc_html__( 'Spacing', 'bricks' ) . ' (px)', 'type' => 'number', 'placeholder' => 0, ]; $controls['imageRatio'] = [ 'tab' => 'content', 'group' => 'settings', 'label' => esc_html__( 'Image ratio', 'bricks' ), 'type' => 'text', 'inline' => true, 'css' => [ [ 'selector' => '.image', 'property' => 'aspect-ratio', ], ], ]; $controls['initialSlide'] = [ 'tab' => 'content', 'group' => 'settings', 'label' => esc_html__( 'Initial slide', 'bricks' ), 'type' => 'number', 'min' => 0, 'max' => 10, 'placeholder' => 0, ]; $controls['slidesToShow'] = [ 'tab' => 'content', 'group' => 'settings', 'label' => esc_html__( 'Items to show', 'bricks' ), 'type' => 'number', 'min' => 1, 'max' => 10, 'placeholder' => 1, 'breakpoints' => true, // NOTE: Undocumented (allows setting non-CSS settings per breakpoint: Carousel, Slider, etc.) 'required' => [ 'effect', '!=', [ 'fade', 'cube', 'flip' ] ], ]; $controls['slidesToScroll'] = [ 'tab' => 'content', 'group' => 'settings', 'label' => esc_html__( 'Items to scroll', 'bricks' ), 'type' => 'number', 'min' => 1, 'max' => 10, 'placeholder' => 1, 'breakpoints' => true, 'required' => [ 'effect', '!=', [ 'fade', 'cube', 'flip' ] ], ]; $controls['effect'] = [ 'tab' => 'content', 'group' => 'settings', 'label' => esc_html__( 'Effect', 'bricks' ), 'type' => 'select', 'options' => [ 'slide' => esc_html__( 'Slide', 'bricks' ), 'fade' => esc_html__( 'Fade', 'bricks' ), 'cube' => esc_html__( 'Cube', 'bricks' ), 'coverflow' => esc_html__( 'Coverflow', 'bricks' ), 'flip' => esc_html__( 'Flip', 'bricks' ), ], 'inline' => true, 'placeholder' => esc_html__( 'Slide', 'bricks' ), ]; // @since 1.9.3 $controls['swiperLoop'] = [ 'tab' => 'content', 'group' => 'settings', 'label' => esc_html__( 'Loop', 'bricks' ), 'placeholder' => esc_html__( 'Enable', 'bricks' ), 'type' => 'select', 'inline' => true, 'options' => [ 'enable' => esc_html__( 'Enable', 'bricks' ), 'disable' => esc_html__( 'Disable', 'bricks' ), ], ]; $controls['infinite'] = [ 'tab' => 'content', 'group' => 'settings', 'label' => esc_html__( 'Loop', 'bricks' ), 'type' => 'checkbox', 'default' => true, ]; $controls['centerMode'] = [ 'tab' => 'style', 'group' => 'settings', 'label' => esc_html__( 'Center mode', 'bricks' ), 'type' => 'checkbox', ]; $controls['disableLazyLoad'] = [ 'tab' => 'style', 'group' => 'settings', 'label' => esc_html__( 'Disable lazy load', 'bricks' ), 'type' => 'checkbox', ]; $controls['adaptiveHeight'] = [ 'tab' => 'content', 'group' => 'settings', 'label' => esc_html__( 'Adaptive height', 'bricks' ), 'type' => 'checkbox', ]; $controls['autoplay'] = [ 'tab' => 'content', 'group' => 'settings', 'label' => esc_html__( 'Autoplay', 'bricks' ), 'type' => 'checkbox', ]; $controls['pauseOnHover'] = [ 'tab' => 'content', 'group' => 'settings', 'label' => esc_html__( 'Pause on hover', 'bricks' ), 'type' => 'checkbox', 'required' => [ 'autoplay', '!=', '' ], ]; $controls['stopOnLastSlide'] = [ 'tab' => 'content', 'group' => 'settings', 'label' => esc_html__( 'Stop on last slide', 'bricks' ), 'type' => 'checkbox', 'info' => esc_html__( 'No effect with loop enabled', 'bricks' ), 'required' => [ 'autoplay', '!=', '' ], ]; $controls['autoplaySpeed'] = [ 'tab' => 'content', 'group' => 'settings', 'label' => esc_html__( 'Autoplay delay in ms', 'bricks' ), 'type' => 'number', 'required' => [ 'autoplay', '!=', '' ], 'placeholder' => 3000, ]; $controls['speed'] = [ 'tab' => 'content', 'group' => 'settings', 'label' => esc_html__( 'Animation speed in ms', 'bricks' ), 'type' => 'number', 'min' => 1, 'placeholder' => 300, ]; // Arrows $controls['arrows'] = [ 'tab' => 'content', 'group' => 'arrows', 'label' => esc_html__( 'Show arrows', 'bricks' ), 'type' => 'checkbox', 'rerender' => true, 'default' => true, ]; $controls['arrowHeight'] = [ 'tab' => 'content', 'group' => 'arrows', 'label' => esc_html__( 'Height', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'height', 'selector' => '.swiper-button', ], ], 'placeholder' => 50, 'required' => [ 'arrows', '!=', '' ], ]; $controls['arrowWidth'] = [ 'tab' => 'content', 'group' => 'arrows', 'label' => esc_html__( 'Width', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'width', 'selector' => '.swiper-button', ], ], 'placeholder' => 50, 'required' => [ 'arrows', '!=', '' ], ]; $controls['arrowBackground'] = [ 'tab' => 'content', 'group' => 'arrows', 'label' => esc_html__( 'Background', 'bricks' ), 'type' => 'color', 'css' => [ [ 'property' => 'background-color', 'selector' => '.swiper-button', ], ], 'required' => [ 'arrows', '!=', '' ], ]; $controls['arrowBorder'] = [ 'tab' => 'content', 'group' => 'arrows', 'label' => esc_html__( 'Border', 'bricks' ), 'type' => 'border', 'css' => [ [ 'property' => 'border', 'selector' => '.swiper-button', ], ], 'required' => [ 'arrows', '!=', '' ], ]; $controls['arrowTypography'] = [ 'tab' => 'content', 'group' => 'arrows', 'label' => esc_html__( 'Typography', 'bricks' ), 'type' => 'typography', 'css' => [ [ 'property' => 'font', 'selector' => '.swiper-button', ], ], 'exclude' => [ 'font-family', 'font-weight', 'font-style', 'text-align', 'letter-spacing', 'line-height', 'text-transform', ], 'required' => [ 'arrows', '!=', '' ], ]; $controls['prevArrowSeparator'] = [ 'tab' => 'content', 'group' => 'arrows', 'label' => esc_html__( 'Prev arrow', 'bricks' ), 'type' => 'separator', 'required' => [ 'arrows', '!=', '' ], ]; $controls['prevArrow'] = [ 'tab' => 'content', 'group' => 'arrows', 'label' => esc_html__( 'Prev arrow', 'bricks' ), 'type' => 'icon', 'default' => [ 'library' => 'ionicons', 'icon' => 'ion-ios-arrow-back', ], 'css' => [ [ 'selector' => '.bricks-swiper-button-prev > *', ], ], 'required' => [ 'arrows', '!=', '' ], 'rerender' => true, ]; $controls['prevArrowTop'] = [ 'tab' => 'content', 'group' => 'arrows', 'label' => esc_html__( 'Top', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'top', 'selector' => '.bricks-swiper-button-prev', ], ], 'placeholder' => '50%', 'required' => [ 'arrows', '!=', '' ], ]; $controls['prevArrowRight'] = [ 'tab' => 'content', 'group' => 'arrows', 'label' => esc_html__( 'Right', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'right', 'selector' => '.bricks-swiper-button-prev', ], ], 'required' => [ 'arrows', '!=', '' ], ]; $controls['prevArrowBottom'] = [ 'tab' => 'content', 'group' => 'arrows', 'label' => esc_html__( 'Bottom', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'bottom', 'selector' => '.bricks-swiper-button-prev', ], ], 'required' => [ 'arrows', '!=', '' ], ]; $controls['prevArrowLeft'] = [ 'tab' => 'content', 'group' => 'arrows', 'label' => esc_html__( 'Left', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'left', 'selector' => '.bricks-swiper-button-prev', ], ], 'placeholder' => '50px', 'required' => [ 'arrows', '!=', '' ], ]; $controls['prevArrowTransform'] = [ 'tab' => 'content', 'group' => 'arrows', 'label' => esc_html__( 'Transform', 'bricks' ), 'type' => 'transform', 'css' => [ [ 'property' => 'transform', 'selector' => '.bricks-swiper-button-prev', ], ], 'required' => [ 'arrows', '!=', '' ], ]; $controls['nextArrowSeparator'] = [ 'tab' => 'content', 'group' => 'arrows', 'label' => esc_html__( 'Next arrow', 'bricks' ), 'type' => 'separator', 'required' => [ 'arrows', '!=', '' ], ]; $controls['nextArrow'] = [ 'tab' => 'content', 'group' => 'arrows', 'label' => esc_html__( 'Next arrow', 'bricks' ), 'type' => 'icon', 'default' => [ 'library' => 'ionicons', 'icon' => 'ion-ios-arrow-forward', ], 'css' => [ [ 'selector' => '.bricks-swiper-button-next > *', ], ], 'required' => [ 'arrows', '!=', '' ], 'rerender' => true, ]; $controls['nextArrowTop'] = [ 'tab' => 'content', 'group' => 'arrows', 'label' => esc_html__( 'Top', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'top', 'selector' => '.bricks-swiper-button-next', ], ], 'required' => [ 'arrows', '!=', '' ], ]; $controls['nextArrowRight'] = [ 'tab' => 'content', 'group' => 'arrows', 'label' => esc_html__( 'Right', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'right', 'selector' => '.bricks-swiper-button-next', ], ], 'placeholder' => '50px', 'required' => [ 'arrows', '!=', '' ], ]; $controls['nextArrowBottom'] = [ 'tab' => 'content', 'group' => 'arrows', 'label' => esc_html__( 'Bottom', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'bottom', 'selector' => '.bricks-swiper-button-next', ], ], 'required' => [ 'arrows', '!=', '' ], ]; $controls['nextArrowLeft'] = [ 'tab' => 'content', 'group' => 'arrows', 'label' => esc_html__( 'Left', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'left', 'selector' => '.bricks-swiper-button-next', ], ], 'required' => [ 'arrows', '!=', '' ], ]; $controls['nextArrowTransform'] = [ 'tab' => 'content', 'group' => 'arrows', 'label' => esc_html__( 'Transform', 'bricks' ), 'type' => 'transform', 'css' => [ [ 'property' => 'transform', 'selector' => '.bricks-swiper-button-next', ], ], 'required' => [ 'arrows', '!=', '' ], ]; // Dots $controls['dots'] = [ 'tab' => 'content', 'group' => 'dots', 'label' => esc_html__( 'Show dots', 'bricks' ), 'type' => 'checkbox', 'inline' => true, 'rerender' => true, ]; $controls['dotsDynamic'] = [ 'tab' => 'content', 'group' => 'dots', 'label' => esc_html__( 'Dynamic dots', 'bricks' ), 'type' => 'checkbox', 'inline' => true, 'required' => [ 'dots', '!=', '' ], ]; $controls['dotsVertical'] = [ 'tab' => 'content', 'group' => 'dots', 'label' => esc_html__( 'Vertical', 'bricks' ), 'type' => 'checkbox', 'inline' => true, 'css' => [ [ 'property' => 'flex-direction', 'selector' => '.swiper-pagination-bullets', 'value' => 'column', ], ], 'rerender' => true, 'required' => [ 'dots', '!=', '' ], ]; $controls['dotsHeight'] = [ 'tab' => 'content', 'group' => 'dots', 'label' => esc_html__( 'Height', 'bricks' ), 'type' => 'number', 'units' => true, 'units' => [ 'px' => [ 'min' => 1, 'max' => 100, ], ], 'css' => [ [ 'property' => 'height', 'selector' => '.swiper-pagination-bullet', ], ], 'required' => [ 'dots', '!=', '' ], ]; $controls['dotsWidth'] = [ 'tab' => 'content', 'group' => 'dots', 'label' => esc_html__( 'Width', 'bricks' ), 'type' => 'number', 'units' => true, 'units' => [ 'px' => [ 'min' => 1, 'max' => 100, ], ], 'css' => [ [ 'property' => 'width', 'selector' => '.swiper-pagination-bullet', ], ], 'required' => [ 'dots', '!=', '' ], ]; $controls['dotsTop'] = [ 'tab' => 'content', 'group' => 'dots', 'label' => esc_html__( 'Top', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'top', 'selector' => '.bricks-swiper-container + .swiper-pagination-bullets', ], ], 'required' => [ 'dots', '!=', '' ], ]; $controls['dotsRight'] = [ 'tab' => 'content', 'group' => 'dots', 'label' => esc_html__( 'Right', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'right', 'selector' => '.bricks-swiper-container + .swiper-pagination-bullets', ], [ 'property' => 'left', 'value' => 'auto', 'selector' => '.bricks-swiper-container + .swiper-pagination-bullets', ], [ 'property' => 'transform', 'selector' => '.bricks-swiper-container + .swiper-pagination-bullets', 'value' => 'translateX(0)', ], ], 'required' => [ 'dots', '!=', '' ], ]; $controls['dotsBottom'] = [ 'tab' => 'content', 'group' => 'dots', 'label' => esc_html__( 'Bottom', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'bottom', 'selector' => '.bricks-swiper-container + .swiper-pagination-bullets', ], ], 'required' => [ 'dots', '!=', '' ], ]; $controls['dotsLeft'] = [ 'tab' => 'content', 'group' => 'dots', 'label' => esc_html__( 'Left', 'bricks' ), 'type' => 'number', 'units' => true, 'css' => [ [ 'property' => 'left', 'selector' => '.bricks-swiper-container + .swiper-pagination-bullets', ], [ 'property' => 'transform', 'selector' => '.bricks-swiper-container + .swiper-pagination-bullets', 'value' => 'translateX(0)', ], ], 'required' => [ 'dots', '!=', '' ], ]; $controls['dotsBorder'] = [ 'tab' => 'content', 'group' => 'dots', 'label' => esc_html__( 'Border', 'bricks' ), 'type' => 'border', 'css' => [ [ 'property' => 'border', 'selector' => '.swiper-pagination-bullet', ], ], 'required' => [ 'dots', '!=', '' ], ]; $controls['dotsColor'] = [ 'tab' => 'content', 'group' => 'dots', 'label' => esc_html__( 'Color', 'bricks' ), 'type' => 'color', 'css' => [ [ 'property' => 'background-color', 'selector' => '.swiper-pagination-bullet', ], [ 'property' => 'color', 'selector' => '.swiper-pagination-bullet', ], ], 'required' => [ 'dots', '!=', '' ], ]; $controls['dotsActiveColor'] = [ 'tab' => 'content', 'group' => 'dots', 'label' => esc_html__( 'Active color', 'bricks' ), 'type' => 'color', 'css' => [ [ 'property' => 'background-color', 'selector' => '.swiper-pagination-bullet-active', ], [ 'property' => 'color', 'selector' => '.swiper-pagination-bullet-active', ], ], 'required' => [ 'dots', '!=', '' ], ]; $controls['dotsSpacing'] = [ 'tab' => 'content', 'group' => 'dots', 'label' => esc_html__( 'Margin', 'bricks' ), 'type' => 'spacing', 'css' => [ [ 'property' => 'margin', 'selector' => '.swiper-pagination-bullet', ], ], 'required' => [ 'dots', '!=', '' ], ]; return $controls; } /** * Render swiper nav: Navigation (arrows) & pagination (dots) * * Elements: Carousel, Slider, Team Members. * * @param array $options SwiperJS options. * * @since 1.4 */ public function render_swiper_nav( $options = false ) { $options = $options ? $options : $this->settings; $output = ''; // Dots (pagination) if ( isset( $options['dots'] ) ) { $output .= '<div class="swiper-pagination"></div>'; } // ARROWS (navigation) if ( isset( $options['arrows'] ) ) { // Prev arrow $prev_arrow = false; // Check: Element & theme style settings if ( ! empty( $options['prevArrow'] ) ) { $prev_arrow = self::render_icon( $options['prevArrow'] ); } elseif ( ! empty( Theme_Styles::get_setting_by_key( $this->name, 'prevArrow' ) ) ) { $prev_arrow = self::render_icon( Theme_Styles::get_setting_by_key( $this->name, 'prevArrow' ) ); } if ( $prev_arrow ) { $output .= '<div class="swiper-button bricks-swiper-button-prev">' . $prev_arrow . '</div>'; } // Next arrow $next_arrow = false; // Check: Element & theme style settings if ( ! empty( $options['nextArrow'] ) ) { $next_arrow = self::render_icon( $options['nextArrow'] ); } elseif ( ! empty( Theme_Styles::get_setting_by_key( $this->name, 'nextArrow' ) ) ) { $next_arrow = self::render_icon( Theme_Styles::get_setting_by_key( $this->name, 'nextArrow' ) ); } if ( $next_arrow ) { $output .= '<div class="swiper-button bricks-swiper-button-next">' . $next_arrow . '</div>'; } } return $output; } /** * Custom loop builder controls * * Shared between Container, Template, ... * * @since 1.3.7 */ public function get_loop_builder_controls( $group = '' ) { $controls = []; $controls['hasLoop'] = [ 'tab' => 'content', 'label' => esc_html__( 'Query loop', 'bricks' ), 'type' => 'checkbox', ]; $controls['query'] = [ 'tab' => 'content', 'label' => esc_html__( 'Query', 'bricks' ), 'type' => 'query', 'popup' => true, 'inline' => true, 'required' => [ 'hasLoop', '!=', '' ], ]; if ( ! empty( $group ) ) { foreach ( $controls as $key => $control ) { $controls[ $key ]['group'] = $group; } } return $controls; } /** * Render the query loop trail * * Trail enables infinite scroll * * @since 1.5 * * @param Query $query The query object. * @param string $node_key The element key to add the query data attributes (used in the posts element). * * @return string */ public function render_query_loop_trail( $query, $node_key = '' ) { $render = $this->is_frontend && ! bricks_is_rest_call(); // @see https://academy.bricksbuilder.io/article/filter-bricks-render_query_loop_trail/ (@since 1.11.1) $render = apply_filters( 'bricks/render_query_loop_trail', $render, $this, $query ); if ( ! $render ) { return ''; } // Global Query loop elements only store the query ID locally, so use the // resolved Query settings for trail attributes. $settings = ! empty( $query->settings ) ? $query->settings : ( $this->element['settings'] ?? [] ); $render = empty( $node_key ); $node_key = empty( $node_key ) ? 'trail' : $node_key; $page = isset( $query->query_vars['paged'] ) ? $query->query_vars['paged'] : 1; // Query trail class (load more or infinite scroll) $this->set_attribute( $node_key, 'class', 'brx-query-trail' ); // Is Live Search: So JavaScript will hide it's results container if input value is empty if ( isset( $settings['query']['is_live_search'] ) ) { $this->set_attribute( $node_key, 'data-brx-live-search', true ); } // Disable URL Param Filter (@since 2.0) if ( isset( $settings['query']['disable_url_params'] ) ) { $this->set_attribute( $node_key, 'data-brx-disable-url-params', true ); } // Infinite scroll class if ( isset( $settings['query']['infinite_scroll'] ) ) { $this->set_attribute( $node_key, 'class', 'brx-infinite-scroll' ); } // AJAX loader (@since 1.9) if ( ! empty( $settings['query']['ajax_loader_animation'] ) ) { $ajax_loader_data = [ 'animation' => $settings['query']['ajax_loader_animation'], 'selector' => isset( $settings['query']['ajax_loader_selector'] ) ? $settings['query']['ajax_loader_selector'] : '', 'color' => isset( $settings['query']['ajax_loader_color'] ) ? Assets::generate_css_color( $settings['query']['ajax_loader_color'] ) : '', 'scale' => isset( $settings['query']['ajax_loader_scale'] ) ? $settings['query']['ajax_loader_scale'] : '', ]; $this->set_attribute( $node_key, 'data-brx-ajax-loader', wp_json_encode( $ajax_loader_data ) ); } // Set target Query ID. If it is inside a component instance (not root), combine with instanceId (@since 1.12.2) $this->set_attribute( $node_key, 'data-query-element-id', $this->uid ); // Component ID (@since 1.12) if ( $this->cid ) { $this->set_attribute( $node_key, 'data-query-component-id', $this->cid ); } // Unset 'queryEditor' value as not needed in the frontend if ( isset( $query->query_vars['queryEditor'] ) ) { unset( $query->query_vars['queryEditor'] ); } // Query vars: needed to make sure the context is the same if the query was merged with the global query $this->set_attribute( $node_key, 'data-query-vars', wp_json_encode( $query->query_vars ) ); // Original query vars before merge with filter query vars (@since 1.11.1) if ( Helpers::enabled_query_filters() ) { $original_query = isset( Query_Filters::$query_vars_before_merge[ $this->id ] ) ? Query_Filters::$query_vars_before_merge[ $this->id ] : []; $this->set_attribute( $node_key, 'data-original-query-vars', wp_json_encode( $original_query ) ); } // Pagination $this->set_attribute( $node_key, 'data-page', $page ); $this->set_attribute( $node_key, 'data-max-pages', $query->max_num_pages ); // Query Results summary, to register in queryLoopInstances (@since 1.12.2) $this->set_attribute( $node_key, 'data-start', $query->start ); $this->set_attribute( $node_key, 'data-end', $query->end ); // Observer margin (only px or %) if ( ! empty( $settings['query']['infinite_scroll_margin'] ) ) { $offset = $settings['query']['infinite_scroll_margin']; if ( strpos( $offset, 'px' ) === false && strpos( $offset, '%' ) === false ) { $offset = intval( $offset ) . 'px'; } $this->set_attribute( $node_key, 'data-observer-margin', $offset ); } // Infinite scroll delay (@since 1.12) if ( ! empty( $settings['query']['infinite_scroll_delay'] ) && ! empty( $settings['query']['infinite_scroll'] ) ) { $this->set_attribute( $node_key, 'data-observer-delay', $settings['query']['infinite_scroll_delay'] ); } if ( $render ) { // Use the tag of the element instead of a hardcoded div $tag = $this->get_tag(); // Is 'a' tag: Add attributes for SEO (@since 1.11) if ( $tag === 'a' ) { $this->set_attribute( $node_key, 'role', 'presentation' ); $this->set_attribute( $node_key, 'href', '#' ); $this->set_attribute( $node_key, 'onclick', 'return false;' ); } echo "<$tag {$this->render_attributes( 'trail' )}></$tag>"; } } /** * Get the dynamic data for a specific tag * * @param string $tag Dynamic data tag. * @param string $context text, image, media, link. * @param array $args Needed to set size for avatar image. * @param string $post_id Post ID. * * @return mixed */ public function render_dynamic_data_tag( $tag = '', $context = 'text', $args = [], $post_id = 0 ) { if ( ! $post_id ) { $post_id = Query::is_looping() && Query::get_loop_object_type() == 'post' ? Query::get_loop_object_id() : $this->post_id; } return Integrations\Dynamic_Data\Providers::render_tag( $tag, $post_id, $context, $args ); } /** * Render dynamic data tags on a string * * @param string $content * * @return mixed */ public function render_dynamic_data( $content = '' ) { $post_id = Query::is_looping() && Query::get_loop_object_type() == 'post' ? Query::get_loop_object_id() : $this->post_id; return bricks_render_dynamic_data( $content, $post_id ); } /** * Set Post ID * * @param int $post_id * * @return void */ public function set_post_id( $post_id = 0 ) { $this->post_id = $post_id; } /** * Setup query for templates according to 'templatePreviewType' * * To alter builder template and template preview query. NOT the frontend! * * 1. Set element $post_id * 2. Populate query_args from"Populate content" settings and set it to global $wp_query * * @param integer $post_id * * @since 1.0 */ public function setup_query( $post_id = 0 ) { if ( ! $post_id ) { $post_id = get_the_ID(); } // STEP: Set post ID to template preview ID if direct edit or single template preview $template_settings = Helpers::get_template_settings( $post_id ); $template_preview_type = Helpers::get_template_setting( 'templatePreviewType', $post_id ); // @since 1.8 - Set preview type if direct edit page or post with Bricks (#861m48kv4) if ( bricks_is_builder_call() && empty( $template_settings ) && ! Helpers::is_bricks_template( $post_id ) ) { $template_preview_type = 'direct-edit'; } if ( in_array( $template_preview_type, [ 'direct-edit', 'single' ] ) ) { // @since 1.8 - If direct edit page or post with Bricks, use the $post_id (#861m48kv4) $template_preview_post_id = ( $template_preview_type === 'direct-edit' ) ? $post_id : Helpers::get_template_setting( 'templatePreviewPostId', $post_id ); if ( $template_preview_post_id ) { // Set the global $post to affect the entire WP environment (needed for WooCommerce) global $post; $post = get_post( $template_preview_post_id ); setup_postdata( $post ); // Set the preview ID as the Post ID before render this element (@since 1.5.7) $this->set_post_id( $template_preview_post_id ); } } // STEP: Populate query_args from populate content settings. Moved the logic to helpers class (@since 1.9.1) $query_args = Helpers::get_template_preview_query_vars( $post_id ); // Init query with template preview args if ( ! empty( $query_args ) && is_array( $query_args ) ) { // Store $wp_query in $original_query to restore it via restore_query() after element has been rendered global $wp_query; $this->original_query = $wp_query; // This is still needed in template preview (i.e. Pagination element if targeting main query) $wp_query = new \WP_Query( $query_args ); } } /** * Restore custom query after element render() * * @since 1.0 */ public function restore_query() { if ( ! $this->original_query ) { return; } global $wp_query; $wp_query = $this->original_query; // Need to reset the global $post environment because on setup_query() we might have change it wp_reset_postdata(); } /** * Render control 'icon' HTML (either font icon 'i' or 'svg' HTML) * * @param array $icon Contains either 'icon' CSS class or 'svg' URL data. * @param array $attributes Additional icon HTML attributes. * * @see ControlIcon.vue * @return string SVG HMTL string * * @since 1.2.1 */ public static function render_icon( $icon, $attributes = [] ) { if ( ! is_array( $attributes ) ) { $attributes = []; } // Is flat array (key is index, not an attribute name): Items are list of class names if ( isset( $attributes[0] ) ) { $attributes = [ 'class' => $attributes, ]; } $classes = []; // STEP: Render SVG $svg_data = $icon['svg'] ?? []; // @since 1.12.2 $svg_id = $svg_data['id'] ?? false; if ( $svg_id ) { // Check if SVG is from remote/different website (@since 1.12.2) $remote_svg_url = $svg_data['url'] ?? ''; $current_host = parse_url( home_url(), PHP_URL_HOST ); $remote_host = parse_url( $remote_svg_url, PHP_URL_HOST ); $compare_remote = $remote_host && $remote_host !== $current_host; // If it's SVG from remote, we need to compare the filenames with local one, // to avoid fetching wrong file from local media library $svg_path = get_attached_file( $svg_id ); if ( $compare_remote && $svg_path !== false ) { $svg_filename = $svg_data['filename'] ?? basename( $remote_svg_url ); $svg_filename_local = basename( $svg_path ); // Return unrendered: SVG name is different, we expect that it's a different SVG file if ( $svg_filename !== $svg_filename_local ) { return; } } $svg = Helpers::file_get_contents( $svg_path ); if ( ! $svg ) { return; } if ( isset( $icon['fill'] ) ) { $classes[] = 'fill'; } if ( isset( $icon['stroke'] ) ) { $classes[] = 'stroke'; } $attributes['class'] = empty( $attributes['class'] ) ? $classes : array_merge( $classes, $attributes['class'] ); // Add attributes to SVG HTML string return self::render_svg( $svg, $attributes ); } // STEP: Render dynamic data tag. Check library is 'dynamicData' (##86c4beudf; @since 2.0) elseif ( ! empty( $icon['dynamicData'] ) && ! empty( $icon['library'] ) && $icon['library'] === 'dynamicData' ) { $rendered = bricks_render_dynamic_data( $icon['dynamicData'] ); // If it's a SVG, render it if ( Helpers::is_valid_svg( $rendered ) ) { return self::render_svg( $rendered, $attributes ); } // If it's a font icon, update attributes (@since 2.0) // e.g., <i class="dashicons dashicons-admin-post"></i> to <i class="dashicons dashicons-admin-post brxe-icon demo" id="brxe-abcdef" ></i> else { // Find a tag $element_tag = strpos( $rendered, '<' ) === 0 ? substr( $rendered, 1, strpos( $rendered, ' ' ) - 1 ) : ''; // Read "class" attribute from the rendered icon $icon_classes = []; preg_match( '/class="([^"]*)"/', $rendered, $icon_classes ); // Remove the "class" attribute from the rendered icon $rendered = preg_replace( '/class="([^"]*)"/', '', $rendered ); // Explode the classes into an array (so we can use merge it with the attributes) $icon_classes = isset( $icon_classes[1] ) ? explode( ' ', $icon_classes[1] ) : []; $attributes['class'] = empty( $attributes['class'] ) ? $icon_classes : array_merge( $attributes['class'], $icon_classes ); $attributes = self::stringify_attributes( $attributes ); $rendered = str_replace( $element_tag, $element_tag . ' ' . $attributes, $rendered ); return $rendered; } } // STEP: Render icon font elseif ( ! empty( $icon['icon'] ) ) { $classes[] = $icon['icon']; $attributes['class'] = empty( $attributes['class'] ) ? $classes : array_merge( $classes, $attributes['class'] ); $attributes = self::stringify_attributes( $attributes ); return "<i {$attributes}></i>"; } } /** * Add attributes to SVG HTML string * * @since 1.4 */ public static function render_svg( $svg = '', $attributes = [] ) { // STEP: Remove any potential "<xml " code before <svg $svg_tag_start = strpos( $svg, '<svg ' ); $svg = substr_replace( $svg, '', 0, $svg_tag_start ); // STEP: Remove the custom HTML ID (if any) to avoid conflict with the default element ID preg_match( '/<svg ([a-z][a-z0-9]*)[^>]*?(\/?)>/i', $svg, $matches ); $svg_tag = ! empty( $matches[0] ) ? $matches[0] : false; if ( $svg_tag ) { $svg_without_id = preg_replace( '/id="([\w-]*)"/i', '', $svg_tag ); $svg = str_replace( $svg_tag, $svg_without_id, $svg ); } // STEP: Add the new attributes foreach ( $attributes as $key => $values ) { $start = strpos( $svg, $key . '="' ); $end = strpos( $svg, '>' ); $value = esc_attr( is_array( $values ) ? join( ' ', $values ) : $values ); // Add values to existing attribute if ( $start && $start < $end ) { $value_start = $start + strlen( $key ) + 2; // Special case for "aria-hidden": Replace the existing value instead of adding a new one, to avoid issues with screen readers (@since 2.3.5) if ( $key === 'aria-hidden' ) { $value_end = strpos( $svg, '"', $value_start ); if ( $value_end && $value_end < $end ) { $svg = substr_replace( $svg, $value, $value_start, $value_end - $value_start ); } } else { $svg = substr_replace( $svg, "$value ", $value_start, 0 ); } } // Create attribute + value on node else { $attribute_string = $key . '="' . $value . '" '; $svg = substr_replace( $svg, $attribute_string, 5, 0 ); } } return trim( $svg ); } /** * Change query if we are previewing a CPT archive template (set in-builder via "Populated Content") * * @since 1.4 */ public function maybe_set_preview_query( $query_vars, $settings, $element_id ) { $post_id = $this->post_id; // Return: Not a template OR no 'post_type' condition set if ( ! Helpers::is_bricks_template( $post_id ) || ! empty( $query_vars['post_type'] ) ) { return $query_vars; } $preview_type = Helpers::get_template_setting( 'templatePreviewType', $post_id ); if ( $preview_type === 'archive-cpt' ) { $preview_post_type = Helpers::get_template_setting( 'templatePreviewPostType', $post_id ); if ( $preview_post_type ) { $query_vars['post_type'] = $preview_post_type; } } return $query_vars; } /** * Is layout element: Section, Container, Block, Div * * For element control visibility in builder (flex controls, shape divider, etc.) * * @return boolean * * @since 1.5 */ public function is_layout_element() { $layout_element_names = [ 'section', 'container', 'block', 'div' ]; // NOTE: Undocumented $layout_element_names = apply_filters( 'bricks/is_layout_element', $layout_element_names ); return in_array( $this->name, $layout_element_names ); } /** * Generate breakpoint-specific @media rules for nav menu & mobile menu toggle * * If not set to 'always' or 'never' * * @since 1.5.1 * @since 2.2: Support passing just breakpoint width as parameter, not only full breakpoint array */ public function generate_mobile_menu_inline_css( $settings = [], $breakpoint = '' ) { if ( is_numeric( $breakpoint ) ) { $breakpoint_width = intval( $breakpoint ); } else { $breakpoint_width = ! empty( $breakpoint['width'] ) ? intval( $breakpoint['width'] ) : 0; } $base_width = Breakpoints::$base_width; $nav_menu_inline_css = ''; if ( $breakpoint_width ) { if ( $breakpoint_width > $base_width ) { if ( Breakpoints::$is_mobile_first ) { $nav_menu_inline_css .= "@media (max-width: {$breakpoint_width}px) {\n"; } else { $nav_menu_inline_css .= "@media (min-width: {$breakpoint_width}px) {\n"; } } else { if ( Breakpoints::$is_mobile_first ) { $nav_menu_inline_css .= "@media (min-width: {$breakpoint_width}px) {\n"; } else { $nav_menu_inline_css .= "@media (max-width: {$breakpoint_width}px) {\n"; } } $element_id = $this->get_element_attribute_id(); // Is component: Use .brxe- class instead of ID (@since 1.12) $components = Database::$global_data['components']; // Stringify components array $components_string = wp_json_encode( $components ); // Check if element 'id' is in components string $is_component = strpos( $components_string, $this->id ) !== false; $nav_menu_selector = $is_component ? ".brxe-{$this->id}" : "#{$element_id}"; // Component instance as root (#86c3aq36e) if ( ! empty( $this->element['cid'] ) ) { $nav_menu_selector = ".brxe-{$this->element['cid']}"; } // Nav menu if ( $this->name === 'nav-menu' ) { $nav_menu_inline_css .= "$nav_menu_selector .bricks-nav-menu-wrapper { display: none; }\n"; $nav_menu_inline_css .= "$nav_menu_selector .bricks-mobile-menu-toggle { display: block; }\n"; } // Nav nested elseif ( $this->name === 'nav-nested' ) { $nav_menu_inline_css .= "$nav_menu_selector .brx-toggle-div { display: inline-flex; }\n"; $nav_menu_inline_css .= "$nav_menu_selector .brxe-toggle { display: inline-flex; }\n"; // NOTE: Using element ID doesn't allow "Nav items" settings to overwrite it $nav_menu_inline_css .= "[data-script-id=\"{$this->uid}\"] .brx-nav-nested-items { opacity: 0; visibility: hidden; gap: 0; position: fixed; z-index: 1001; top: 0; right: 0; bottom: 0; left: 0; display: flex; align-items: center; justify-content: center; flex-direction: column; background-color: #fff; overflow-y: scroll; flex-wrap: nowrap; }\n"; $nav_menu_inline_css .= "{$nav_menu_selector}.brx-open .brx-nav-nested-items { opacity: 1; visibility: visible; }\n"; } $nav_menu_inline_css .= '}'; } // Wrap in cascade layer if not disabled (@since 2.0) if ( ! Database::get_setting( 'disableBricksCascadeLayer' ) && $nav_menu_inline_css ) { $nav_menu_inline_css = "@layer bricks {\n" . $nav_menu_inline_css . "\n}"; } return $nav_menu_inline_css; } /** * Return true if any of the element classes contains a match * * @param array $values_to_check Array of values to check the global class settings for. * * @see image.php 'popupOverlay', video.php 'overlay', etc. * * @since 1.7.1 */ public function element_classes_have( $values_to_check = [] ) { if ( ! is_array( $values_to_check ) ) { $values_to_check = [ $values_to_check ]; } $element_classes = $this->settings['_cssGlobalClasses'] ?? false; $global_classes = Database::$global_data['globalClasses'] ?? false; if ( ! $element_classes || ! $global_classes ) { return false; } $class_has = false; // Loop over element class settings if ( is_array( $global_classes ) && ! empty( $global_classes ) ) { foreach ( $element_classes as $element_class ) { $class_index = array_search( $element_class, array_column( $global_classes, 'id' ) ); if ( empty( $global_classes[ $class_index ] ) ) { continue; } foreach ( $values_to_check as $value ) { // Global class has setting with $value if ( strpos( wp_json_encode( $global_classes[ $class_index ] ), $value ) ) { $class_has = true; } } } } return $class_has; } /** * Enqueue Masonry scripts * * @since 1.11.1 */ public function maybe_enqueue_masonry_scripts() { if ( $this->enabled_masonry() ) { wp_enqueue_script( 'bricks-isotope' ); wp_enqueue_style( 'bricks-isotope' ); } } /** * Support masonry layout * * @since 1.11.1 */ public function support_masonry_element() { $element_names = [ 'section', 'container', 'block', 'div' ]; // NOTE: Undocumented $element_names = apply_filters( 'bricks/support_masonry_element', $element_names ); return in_array( $this->name, $element_names ); } public function element_has_flow_spacing() { $element_names = [ 'section', 'container', 'block', 'div' ]; return in_array( $this->name, $element_names ); } public function enabled_masonry() { return isset( $this->settings['_useMasonry'] ) && $this->support_masonry_element(); } public function maybe_masonry_trail_nodes() { $nodes = ''; if ( $this->enabled_masonry() ) { $nodes .= "<li class='bricks-isotope-sizer'></li>"; $nodes .= "<li class='bricks-gutter-sizer'></li>"; } return $nodes; } /** * Get component repeater item settings (by repeater item.id) * * Merge settings of repeater item with component settings. * * @since 1.12 */ public function get_component_repeater_item_settings( $settings = [], $control_key = '' ) { // Return settings: If it's not inside component instance or component itself if ( empty( $this->element['instanceId'] ) && empty( $this->element['cid'] ) ) { return $settings; } // Get repeater item ID $repeater_item_id = $settings['id'] ?? false; if ( ! $repeater_item_id ) { return $settings; } $component_instance_settings = Helpers::get_component_instance( $this->element, 'settings' ); // Get in-builder repeater item real-time/un-saved settings from element settings foreach ( $this->settings as $key => $value ) { // Setting key belongs to the current repeater item if ( strpos( $key, "$control_key|$repeater_item_id|" ) !== false ) { $component_instance_settings[ $key ] = $value; } } if ( is_array( $component_instance_settings ) && ! empty( $component_instance_settings ) ) { // Get property defaults or custom value for current repeater item (use repeater item 'id') foreach ( $component_instance_settings as $key => $value ) { // Setting key belongs to the current repeater item if ( strpos( $key, "$control_key|$repeater_item_id|" ) !== false ) { // Remove repeater control key && repeater item ID from the key to match the settings key of the repeater item $setting_key = str_replace( "$control_key|$repeater_item_id|", '', $key ); $settings[ $setting_key ] = $value; } } } return $settings; } /** * Helper function to get map marker controls * - Used in Map element (main & repeater), Map connector element * - Not include markerType control as select type control not supported dynamic data (Not useable if using Query loop) * * @since 2.0 */ public function get_map_marker_controls( $group = '', $overwrite = [], $is_main = false ) { $controls = []; $controls['markerAriaLabel'] = [ 'label' => esc_html__( 'Label', 'bricks' ), 'type' => 'text', 'description' => esc_html__( 'Screen reader label for the marker. Set for better accessibility.', 'bricks' ), ]; // MARKER TEXT $controls['markerTextSeparator'] = [ 'type' => 'separator', 'label' => esc_html__( 'Marker', 'bricks' ) . ': ' . esc_html__( 'Text', 'bricks' ), ]; $controls['markerText'] = [ 'type' => 'text', 'placeholder' => esc_html__( 'Marker', 'bricks' ), ]; $controls['markerTextMaxWidth'] = [ 'label' => esc_html__( 'Max. width', 'bricks' ), 'type' => 'number', 'inline' => true, 'units' => true, 'css' => [ [ 'property' => 'max-width', 'selector' => '.brx-marker-text', ], ], 'mainOnly' => true, ]; $controls['markerTextTypography'] = [ 'label' => esc_html__( 'Typography', 'bricks' ), 'type' => 'typography', 'css' => [ [ 'property' => 'font', 'selector' => '.brx-marker-text', ], ], 'mainOnly' => true, ]; $controls['markerTextBackgroundColor'] = [ 'label' => esc_html__( 'Background color', 'bricks' ), 'type' => 'color', 'css' => [ [ 'property' => 'background-color', 'selector' => '.brx-marker-text', ], ], 'mainOnly' => true, ]; $controls['markerTextBorder'] = [ 'label' => esc_html__( 'Border', 'bricks' ), 'type' => 'border', 'css' => [ [ 'property' => 'border', 'selector' => '.brx-marker-text', ], ], 'mainOnly' => true, ]; $controls['markerTextBoxShadow'] = [ 'label' => esc_html__( 'Box shadow', 'bricks' ), 'type' => 'box-shadow', 'css' => [ [ 'property' => 'box-shadow', 'selector' => '.brx-marker-text', ], ], 'mainOnly' => true, ]; $controls['markerTextPadding'] = [ 'label' => esc_html__( 'Padding', 'bricks' ), 'type' => 'spacing', 'css' => [ [ 'property' => 'padding', 'selector' => '.brx-marker-text', ], ], 'mainOnly' => true, ]; $controls['markerTextActiveSeparator'] = [ 'type' => 'separator', 'label' => esc_html__( 'Marker', 'bricks' ) . ': ' . esc_html__( 'Text', 'bricks' ) . ' (' . esc_html__( 'Active', 'bricks' ) . ')', // 'mainOnly' => true, ]; $controls['markerTextActive'] = [ 'type' => 'text', 'placeholder' => esc_html__( 'Marker', 'bricks' ), ]; $controls['markerTextActiveTypography'] = [ 'label' => esc_html__( 'Typography', 'bricks' ), 'type' => 'typography', 'css' => [ [ 'property' => 'font', 'selector' => '.brx-marker-text.active', ], ], 'mainOnly' => true, ]; $controls['markerTextActiveBackgroundColor'] = [ 'label' => esc_html__( 'Background color', 'bricks' ), 'type' => 'color', 'css' => [ [ 'property' => 'background-color', 'selector' => '.brx-marker-text.active', ], ], 'mainOnly' => true, ]; $controls['markerTextActiveBorder'] = [ 'label' => esc_html__( 'Border', 'bricks' ), 'type' => 'border', 'css' => [ [ 'property' => 'border', 'selector' => '.brx-marker-text.active', ], ], 'mainOnly' => true, ]; $controls['markerTextActiveBoxShadow'] = [ 'label' => esc_html__( 'Box shadow', 'bricks' ), 'type' => 'box-shadow', 'css' => [ [ 'property' => 'box-shadow', 'selector' => '.brx-marker-text.active', ], ], 'mainOnly' => true, ]; $controls['markerTextActivePadding'] = [ 'label' => esc_html__( 'Padding', 'bricks' ), 'type' => 'spacing', 'css' => [ [ 'property' => 'padding', 'selector' => '.brx-marker-text.active', ], ], 'mainOnly' => true, ]; // MARKER IMAGE $controls['markerImageSeparator'] = [ 'type' => 'separator', 'label' => esc_html__( 'Marker', 'bricks' ) . ': ' . esc_html__( 'Image', 'bricks' ), ]; $controls['marker'] = [ 'type' => 'image', 'unsplash' => false, ]; $controls['markerHeight'] = [ 'label' => esc_html__( 'Height', 'bricks' ) . ' (px)', 'type' => 'number', 'placeholder' => '40', 'mainOnly' => true, ]; $controls['markerWidth'] = [ 'label' => esc_html__( 'Width', 'bricks' ) . ' (px)', 'type' => 'number', 'placeholder' => '40', 'mainOnly' => true, ]; $controls['markerBorder'] = [ 'label' => esc_html__( 'Border', 'bricks' ), 'type' => 'border', 'css' => [ [ 'property' => 'border', 'selector' => '.brx-marker-img', ], ], 'mainOnly' => true, ]; $controls['markerBoxShadow'] = [ 'label' => esc_html__( 'Box shadow', 'bricks' ), 'type' => 'box-shadow', 'css' => [ [ 'property' => 'box-shadow', 'selector' => '.brx-marker-img', ], ], 'mainOnly' => true, ]; // MARKER IMAGE ACTIVE $controls['markerImageActiveSeparator'] = [ 'type' => 'separator', 'label' => esc_html__( 'Marker', 'bricks' ) . ': ' . esc_html__( 'Image', 'bricks' ) . ' (' . esc_html__( 'Active', 'bricks' ) . ')', ]; $controls['markerActive'] = [ 'type' => 'image', 'unsplash' => false, ]; $controls['markerActiveHeight'] = [ 'label' => esc_html__( 'Height', 'bricks' ) . ' (px)', 'type' => 'number', 'placeholder' => '40', 'mainOnly' => true, ]; $controls['markerActiveWidth'] = [ 'label' => esc_html__( 'Width', 'bricks' ) . ' (px)', 'type' => 'number', 'placeholder' => '40', 'mainOnly' => true, ]; $controls['markerActiveBorder'] = [ 'label' => esc_html__( 'Border', 'bricks' ), 'type' => 'border', 'css' => [ [ 'property' => 'border', 'selector' => '.brx-google-marker .brx-marker-img.active', ], ], 'mainOnly' => true, ]; $controls['markerActiveBoxShadow'] = [ 'label' => esc_html__( 'Box shadow', 'bricks' ), 'type' => 'box-shadow', 'css' => [ [ 'property' => 'box-shadow', 'selector' => '.brx-google-marker .brx-marker-img.active', ], ], 'mainOnly' => true, ]; // Exclude controls if not main controls if ( ! $is_main ) { $controls = array_filter( $controls, function ( $control ) { return empty( $control['mainOnly'] ); } ); } // Always unset 'mainOnly' key (temp flag) foreach ( $controls as $key => $control ) { if ( isset( $controls[ $key ]['mainOnly'] ) ) { unset( $controls[ $key ]['mainOnly'] ); } } // Add group to controls if ( ! empty( $group ) ) { foreach ( $controls as $key => $control ) { $controls[ $key ]['group'] = $group; } } // Overwrite controls with custom values if ( ! empty( $overwrite ) && is_array( $overwrite ) ) { foreach ( $overwrite as $control_key => $settings ) { // Skip if control key is not exists if ( ! isset( $controls[ $control_key ] ) ) { continue; } // Loop over settings to overwrite control settings foreach ( $settings as $setting_key => $value ) { if ( isset( $controls[ $control_key ][ $setting_key ] ) ) { $controls[ $control_key ][ $setting_key ] = $value; } else { // Add new setting to the control $controls[ $control_key ][ $setting_key ] = $value; } } } } return $controls; } /** * Generate map marker image settings. For map (Google) element and map connector element. * * @param array $image Marker image control value. * * @return array|string * * @since 2.3.6 #86c9weevz; */ public function get_map_marker_image( $image ) { $process_data = function( $value ) use ( &$process_data ) { if ( is_array( $value ) ) { return array_map( $process_data, $value ); } return $this->render_dynamic_data( $value ); }; $images = Helpers::get_normalized_image_settings( $this, [ 'items' => $image ] ); if ( ! empty( $images['items']['images'] ) && isset( $images['items']['images'][0] ) ) { $marker_image = $images['items']['images'][0]; if ( ! is_array( $marker_image ) ) { return ''; } if ( ! empty( $marker_image['id'] ) && ! is_numeric( $marker_image['id'] ) && empty( $marker_image['url'] ) ) { $marker_image['url'] = $marker_image['id']; unset( $marker_image['id'] ); } return ! empty( $marker_image['url'] ) ? $marker_image : ''; } // When the image is using custom URL (@since 2.2) if ( ! empty( $images['items']['images'] ) && isset( $images['items']['images']['url'] ) ) { return $process_data( $images['items']['images'] ); } return ''; } /** * Search criteria controls * $type: 'element' | 'template' * * Used by filter-search element & search results template * * @since 2.2 */ public static function search_criteria_controls( $type = 'element' ) { $controls = []; $controls['searchCriteriaCustom'] = [ 'label' => esc_html__( 'Custom search criteria', 'bricks' ), 'group' => 'search-criteria', 'type' => 'checkbox', 'inline' => true, 'desc' => esc_html__( 'Enable to customize the default search criteria as used by WordPress.', 'bricks' ), ]; $controls['useWeightScore'] = [ 'group' => 'search-criteria', 'label' => esc_html__( 'Use weight score', 'bricks' ), 'type' => 'checkbox', 'required' => [ 'searchCriteriaCustom', '=', true ], // translators: %s: orderby (query argument) 'description' => sprintf( esc_html__( 'Enable to assign weight scores to search criteria. This affects the "%s" argument of the target query.', 'bricks' ), 'orderby' ), ]; $controls['searchPostSep'] = [ 'label' => esc_html__( 'Post', 'bricks' ) . ' (' . esc_html__( 'Query search', 'bricks' ) . ')', 'group' => 'search-criteria', 'type' => 'separator', 'required' => [ 'searchCriteriaCustom', '=', true ], ]; $controls['searchPostFields'] = [ 'label' => esc_html__( 'Search post fields', 'bricks' ), 'group' => 'search-criteria', 'type' => 'checkbox', 'required' => [ 'searchCriteriaCustom', '=', true ], ]; $controls['searchPostQuery'] = [ 'label' => esc_html__( 'Post fields', 'bricks' ), 'group' => 'search-criteria', 'titleProperty' => 'field', 'type' => 'repeater', 'fields' => [ 'field' => [ 'type' => 'select', 'options' => [ 'title' => esc_html__( 'Title', 'bricks' ), 'content' => esc_html__( 'Content', 'bricks' ), 'excerpt' => esc_html__( 'Excerpt', 'bricks' ), ], 'placeholder' => esc_html__( 'None', 'bricks' ), ], 'weightScore' => [ 'type' => 'number', 'label' => esc_html__( 'Weight score', 'bricks' ), 'placeholder' => 1, 'description' => esc_html__( 'Higher score shows first. Integer only. Minimum: 1. Only takes effect when "Use weight score" is enabled.', 'bricks' ), ], ], 'description' => esc_html__( 'Leave post fields empty to use the default search in post title, content and excerpt.', 'bricks' ), 'required' => [ [ 'searchCriteriaCustom', '=', true ], [ 'searchPostFields', '=', true ], ], ]; $controls['searchPostMeta'] = [ 'label' => esc_html__( 'Search post meta fields', 'bricks' ), 'group' => 'search-criteria', 'type' => 'checkbox', 'required' => [ 'searchCriteriaCustom', '=', true ], ]; $controls['searchPostMetaKeys'] = [ 'label' => esc_html__( 'Post meta keys', 'bricks' ), 'group' => 'search-criteria', 'type' => 'repeater', 'titleProperty' => 'metaKey', 'fields' => [ 'metaKey' => [ 'type' => 'text', 'label' => esc_html__( 'Meta key', 'bricks' ), ], 'weightScore' => [ 'type' => 'number', 'label' => esc_html__( 'Weight score', 'bricks' ), 'placeholder' => 1, 'description' => esc_html__( 'Higher score shows first. Integer only. Minimum: 1. Only takes effect when "Use weight score" is enabled.', 'bricks' ), ], ], 'description' => esc_html__( 'Every added field impacts the query performance.', 'bricks' ), 'required' => [ [ 'searchCriteriaCustom', '=', true ], [ 'searchPostMeta', '=', true ], ], ]; $controls['searchPostTerms'] = [ 'group' => 'search-criteria', 'type' => 'checkbox', 'label' => esc_html__( 'Search post terms', 'bricks' ), 'description' => esc_html__( 'Search assigned terms (slug, name and description) of the selected taxonomies.', 'bricks' ), 'required' => [ [ 'searchCriteriaCustom', '=', true ], ], ]; $controls['searchPostTaxonomies'] = [ 'group' => 'search-criteria', 'type' => 'repeater', 'label' => esc_html__( 'Taxonomies', 'bricks' ), 'titleProperty' => 'taxonomy', 'fields' => [ 'taxonomy' => [ 'type' => 'select', 'label' => esc_html__( 'Taxonomy', 'bricks' ), 'options' => Setup::$control_options['taxonomies'], 'searchable' => true, 'placeholder' => esc_html__( 'Select', 'bricks' ), ], 'weightScore' => [ 'type' => 'number', 'label' => esc_html__( 'Weight score', 'bricks' ), 'placeholder' => 1, 'description' => esc_html__( 'Higher score shows first. Integer only. Minimum: 1. Only takes effect when "Use weight score" is enabled.', 'bricks' ), ], ], 'description' => esc_html__( 'Every added taxonomy impacts the query performance.', 'bricks' ), 'required' => [ [ 'searchCriteriaCustom', '=', true ], [ 'searchPostTerms', '=', true ], ], ]; // Do not add term/user search controls for template type if ( $type === 'template' ) { return $controls; } $controls['searchTermSep'] = [ 'label' => esc_html__( 'Term', 'bricks' ) . ' (' . esc_html__( 'Query search', 'bricks' ) . ')', 'group' => 'search-criteria', 'type' => 'separator', 'required' => [ 'searchCriteriaCustom', '=', true ], ]; $controls['searchTermFields'] = [ 'label' => esc_html__( 'Search term fields', 'bricks' ), 'group' => 'search-criteria', 'type' => 'checkbox', 'required' => [ 'searchCriteriaCustom', '=', true ], ]; $controls['searchTermQuery'] = [ 'label' => esc_html__( 'Term fields', 'bricks' ), 'group' => 'search-criteria', 'titleProperty' => 'field', 'type' => 'repeater', 'fields' => [ 'field' => [ 'type' => 'select', 'options' => [ 'name' => esc_html__( 'Name', 'bricks' ), 'slug' => esc_html__( 'Slug', 'bricks' ), 'description' => esc_html__( 'Description', 'bricks' ), ], 'placeholder' => esc_html__( 'None', 'bricks' ), ], 'weightScore' => [ 'type' => 'number', 'label' => esc_html__( 'Weight score', 'bricks' ), 'placeholder' => 1, 'label' => esc_html__( 'Post', 'bricks' ) . ' (' . esc_html__( 'Query search', 'bricks' ) . ')', ], ], 'description' => esc_html__( 'If none configured, name and slug will be searched.', 'bricks' ), 'required' => [ [ 'searchCriteriaCustom', '=', true ], [ 'searchTermFields', '=', true ], ], ]; $controls['searchTermMeta'] = [ 'label' => esc_html__( 'Search term meta fields', 'bricks' ), 'group' => 'search-criteria', 'type' => 'checkbox', 'required' => [ 'searchCriteriaCustom', '=', true ], ]; $controls['searchTermMetaKeys'] = [ 'label' => esc_html__( 'Term meta keys', 'bricks' ), 'group' => 'search-criteria', 'type' => 'repeater', 'titleProperty' => 'metaKey', 'fields' => [ 'metaKey' => [ 'type' => 'text', 'label' => esc_html__( 'Meta key', 'bricks' ), ], 'weightScore' => [ 'type' => 'number', 'label' => esc_html__( 'Weight score', 'bricks' ), 'placeholder' => 1, 'description' => esc_html__( 'Higher score shows first. Integer only. Minimum: 1. Only takes effect when "Use weight score" is enabled.', 'bricks' ), ], ], 'description' => esc_html__( 'Too many fields may slow down the query performance.', 'bricks' ), 'required' => [ [ 'searchCriteriaCustom', '=', true ], [ 'searchTermMeta', '=', true ], ], ]; $controls['searchUserSep'] = [ 'label' => esc_html__( 'User', 'bricks' ) . ' (' . esc_html__( 'Query search', 'bricks' ) . ')', 'group' => 'search-criteria', 'type' => 'separator', 'required' => [ 'searchCriteriaCustom', '=', true ], ]; $controls['searchUserFields'] = [ 'label' => esc_html__( 'Search user fields', 'bricks' ), 'group' => 'search-criteria', 'type' => 'checkbox', 'required' => [ 'searchCriteriaCustom', '=', true ], ]; $controls['searchUserQuery'] = [ 'label' => esc_html__( 'User fields', 'bricks' ), 'group' => 'search-criteria', 'titleProperty' => 'field', 'type' => 'repeater', 'fields' => [ 'field' => [ 'type' => 'select', 'options' => [ 'user_login' => esc_html__( 'Username', 'bricks' ), 'user_nicename' => esc_html__( 'Nicename', 'bricks' ), 'user_email' => esc_html__( 'Email', 'bricks' ), 'user_url' => esc_html__( 'URL', 'bricks' ), 'display_name' => esc_html__( 'Display name', 'bricks' ), ], 'placeholder' => esc_html__( 'None', 'bricks' ), ], 'weightScore' => [ 'type' => 'number', 'label' => esc_html__( 'Weight score', 'bricks' ), 'placeholder' => 1, 'description' => esc_html__( 'Higher score shows first. Integer only. Minimum: 1. Only takes effect when "Use weight score" is enabled.', 'bricks' ), ], ], 'description' => esc_html__( 'If none configured, username, nicename, email, URL, Display name will be searched.', 'bricks' ), 'required' => [ [ 'searchCriteriaCustom', '=', true ], [ 'searchUserFields', '=', true ], ], ]; $controls['searchUserMeta'] = [ 'label' => esc_html__( 'Search user meta fields', 'bricks' ), 'group' => 'search-criteria', 'type' => 'checkbox', 'required' => [ 'searchCriteriaCustom', '=', true ], ]; $controls['searchUserMetaKeys'] = [ 'label' => esc_html__( 'User meta keys', 'bricks' ), 'group' => 'search-criteria', 'type' => 'repeater', 'titleProperty' => 'metaKey', 'fields' => [ 'metaKey' => [ 'type' => 'text', 'label' => esc_html__( 'Meta key', 'bricks' ), ], 'weightScore' => [ 'type' => 'number', 'label' => esc_html__( 'Weight score', 'bricks' ), 'placeholder' => 1, 'description' => esc_html__( 'Higher score shows first. Integer only. Minimum: 1. Only takes effect when "Use weight score" is enabled.', 'bricks' ), ], ], 'description' => esc_html__( 'Too many fields may slow down the query performance.', 'bricks' ), 'required' => [ [ 'searchCriteriaCustom', '=', true ], [ 'searchUserMeta', '=', true ], ], ]; // Add [ 'filterNiceName', '!=', 's' ], to each control's required condition to avoid user set search criteria on element level if the URL param is 's' (Not for template controls) foreach ( $controls as $key => &$control ) { if ( isset( $control['required'] ) && is_array( $control['required'] ) ) { $control['required'][] = [ 'filterNiceName', '!=', 's' ]; } else { $control['required'] = [ 'filterNiceName', '!=', 's' ]; } } return $controls; } }
修改文件时间
将文件时间修改为当前时间的前一年
删除文件