sets for js output add_filter( 'fl_builder_shared_option_sets', 'FLBuilderArt::filter_shared_option_sets' ); add_filter( 'fl_builder_register_settings_form', 'FLBuilderArt::global_options', 10, 2 ); add_action( 'init', 'FLBuilderArt::register_global_form' ); add_action( 'init', 'FLBuilderArt::register_global_shapes' ); } /** * Register the system art and presets. Called by the fl_register_presets action (see FLBuilderSettingsPresets ) * * @return void */ static public function register_shapes() { $art_dir = FL_BUILDER_DIR . 'includes/shapes/'; self::register_shape(array( 'label' => __( 'Slanted Edge', 'fl-builder' ), 'name' => 'edge-slant', 'width' => 422, 'height' => 33.98, 'render' => $art_dir . 'edge-slant.svg.php', )); self::register_shape(array( 'label' => __( 'Waves', 'fl-builder' ), 'name' => 'wavy', 'width' => 800, 'height' => 102, 'render' => $art_dir . 'wavy.svg.php', )); self::register_shape( array( 'label' => __( 'Midpoint', 'fl-builder' ), 'name' => 'midpoint', 'width' => 800, 'height' => 50, 'render' => $art_dir . 'midpoint.svg.php', )); self::register_shape( array( 'label' => __( 'Triangle', 'fl-builder' ), 'name' => 'triangle', 'width' => 50, 'height' => 34, 'render' => $art_dir . 'triangle.svg.php', )); self::register_shape( array( 'label' => __( 'Circle', 'fl-builder' ), 'name' => 'circle', 'width' => 100, 'height' => 100, 'render' => $art_dir . 'circle.svg.php', )); self::register_shape( array( 'label' => __( 'Concave', 'fl-builder' ), 'name' => 'concave', 'width' => 800, 'height' => 50, 'render' => $art_dir . 'concave.svg.php', )); self::register_shape( array( 'label' => __( 'Spots', 'fl-builder' ), 'name' => 'dot-cluster', 'width' => 800, 'height' => 315, 'render' => $art_dir . 'dot-cluster.svg.php', )); self::register_shape( array( 'label' => __( 'Topography', 'fl-builder' ), 'name' => 'topography', 'width' => 600, 'height' => 600, 'render' => $art_dir . 'topography.svg.php', )); self::register_shape( array( 'label' => __( 'Rectangle', 'fl-builder' ), 'name' => 'rect', 'width' => 800, 'height' => 450, 'render' => $art_dir . 'rect.svg.php', )); /** * Trigger registration process for external shapes. * @see fl_register_art * @link https://docs.wpbeaverbuilder.com/beaver-builder/developer/tutorials-guides/add-a-custom-shape-layer/ */ do_action( 'fl_register_art' ); } /** * Register a new piece of SVG art into the system * * @param Array $args - the metadata for a piece of art * @return void */ static public function register_shape( $args = array() ) { $defaults = array( 'label' => __( 'Untitled Shape', 'fl-builder' ), 'name' => 'untitled-shape', 'x' => 0, 'y' => 0, 'width' => 0, 'height' => 0, 'preserve_aspect_ratio' => 'none', 'render' => '', 'preset_settings' => array(), 'shape_original' => '', ); $args = wp_parse_args( $args, $defaults ); /** * Filter shape args during shape_register() * @see fl_builder_art_register_shape * @since 2.2.5 */ $args = apply_filters( 'fl_builder_art_register_shape', $args ); $key = $args['name']; /** * Setup a preset to reference the shape's initial configuration later * This is so when you choose a shape, we can also setup other fields for the optimal initial appearance. */ FLBuilderSettingsPresets::register( 'shape', array( 'name' => $args['name'], 'label' => $args['label'], 'settings' => $args['preset_settings'], 'shape_original' => $args['shape_original'], 'data' => array( 'viewBox' => array( 'x' => $args['x'], 'y' => $args['y'], 'width' => $args['width'], 'height' => $args['height'], ), ), )); self::$artwork[ $key ] = $args; } /** * Return the array of registered artwork * * @param String $key - index key in the artwork array * @return Array */ static public function get_art( $key = null ) { /** * Array of all registered shapes * @see fl_shape_artwork */ $art = apply_filters( 'fl_shape_artwork', self::$artwork ); if ( $key ) { if ( isset( $art[ $key ] ) ) { return $art[ $key ]; } else { return false; } } return $art; } /** * Create option sets for each preset type and add to FLBuilderConfig.optionSets * * @param Array $option_sets - previously set option sets * @return Array */ static public function filter_shared_option_sets( $option_sets ) { $art = self::get_art(); $option_sets['shapes'] = array( '' => __( 'None', 'fl-builder' ), ); foreach ( $art as $handle => $shape ) { $option_sets['shapes'][ $handle ] = $shape['label']; } return $option_sets; } /** * Render the shape artwork with the current settings. * * @param Array $shape - the registered metadata for the current shape * @param Object $settings - the current node's settings object * @return String - the rendered string */ static public function render_art( $shape, $settings ) { $output = ''; // Render artwork into a buffer if ( $shape && isset( $shape['render'] ) ) { ob_start(); $render = $shape['render']; if ( is_string( $render ) && file_exists( $render ) ) { include $render; } else { echo $render; } $output = ob_get_clean(); } return $output; } /** * Get the node types that support layers * * @return Array */ static public function get_supported_node_types() { return self::$supported_node_types; } /** * Get any layers added to a node * * @param Object $node being rendered * @return Array of layer descriptions */ static public function get_node_layers( $node ) { $layers = array(); if ( in_array( $node->type, self::get_supported_node_types() ) ) { $settings = $node->settings; if ( ! empty( $settings->{'top_edge_shape'} ) ) { $layers['top'] = array( 'label' => __( 'Top Shape Layer', 'fl-builder' ), 'type' => 'shape', 'prefix' => 'top_edge_', 'position' => 'top', ); } if ( ! empty( $settings->{'bottom_edge_shape'} ) ) { $layers['bottom'] = array( 'label' => __( 'Bottom Shape Layer', 'fl-builder' ), 'type' => 'shape', 'prefix' => 'bottom_edge_', 'position' => 'bottom', ); } } return $layers; } /** * Render any layers a node has * * @param Object $node * @return void */ static public function render_node_layers( $node ) { $layers = self::get_node_layers( $node ); if ( ! empty( $layers ) ) { foreach ( $layers as $key => $layer ) { self::render_node_layer( $layer, $node ); } } } /** * Render a single layer into a node * * @param Array $layer meta * @param Object $node * @return void */ static public function render_node_layer( $layer, $node ) { if ( 'shape' === $layer['type'] ) { self::render_node_shape_layer( $layer, $node ); return; } } /** * Render a shape layer into a node * * @param Array $layer meta * @param Object $node * @return void */ static public function render_node_shape_layer( $layer, $node ) { $settings = $node->settings; $id = $node->node; $position = $layer['position']; $prefix = $layer['prefix']; $shape_name = $settings->{ $prefix . 'shape' }; $shape_args = self::get_art( $shape_name ); $content = self::render_art( $shape_args, $settings ); if ( ! $shape_args ) { return false; } $x = $shape_args['x']; $y = $shape_args['y']; $width = $shape_args['width']; $height = $shape_args['height']; $view_box = "$x $y $width $height"; $preserve_aspect_ratio = $shape_args['preserve_aspect_ratio']; $align = $settings->{ $prefix . 'align' }; $ending = str_replace( ' ', '-', $align ); $svg_class = 'fl-builder-layer-align-' . $ending; include FL_BUILDER_DIR . 'includes/shape-layer.php'; } /** * Get the settings form for shapes * * @return void */ static public function get_shape_settings_sections() { $sections = array(); $layers = array( 'top' => __( 'Top', 'fl-builder' ), 'bottom' => __( 'Bottom', 'fl-builder' ), ); foreach ( $layers as $position => $position_label ) { $prefix = $position . '_edge_'; // Preset & Shape Section $sections[ $prefix . 'shape' ] = array( /* translators: %s: position label */ 'title' => sprintf( __( '%s Shape', 'fl-builder' ), $position_label ), 'fields' => array( $prefix . 'shape' => array( 'type' => 'select', 'label' => __( 'Shape', 'fl-builder' ), 'options' => 'shapes', 'hide' => array( '' => array( 'sections' => array( $prefix . 'style', ), 'fields' => array( $prefix . 'size', $prefix . 'align', $prefix . 'z_pos', ), ), ), 'preview' => array( 'type' => 'callback', 'callback' => 'previewShape', 'prefix' => $prefix, 'position' => $position, ), ), $prefix . 'size' => array( 'type' => 'dimension', 'label' => __( 'Size', 'fl-builder' ), 'units' => array( 'px', 'vw', 'vh', '%' ), 'slider' => array( 'width' => array( 'px' => array( 'min' => 0, 'max' => 5000, 'step' => 10, ), 'vw' => array( 'min' => 0, 'max' => 500, ), 'vh' => array( 'min' => 0, 'max' => 500, ), '%' => array( 'min' => 0, 'max' => 300, ), ), 'height' => array( 'px' => array( 'min' => 0, 'max' => 2000, 'step' => 10, ), 'vw' => array( 'min' => 0, 'max' => 200, ), 'vh' => array( 'min' => 0, 'max' => 200, ), '%' => array( 'min' => 0, 'max' => 100, ), ), 'top' => array( 'px' => array( 'min' => -500, 'max' => 500, ), 'vw' => array( 'min' => -20, 'max' => 20, ), 'vh' => array( 'min' => -20, 'max' => 20, ), '%' => array( 'min' => 0, 'max' => 100, ), ), ), 'keys' => array( 'width' => __( 'Width', 'fl-builder' ), 'height' => __( 'Height', 'fl-builder' ), 'top' => __( 'Y Offset', 'fl-builder' ), ), 'preview' => array( 'type' => 'callback', 'callback' => 'previewShapeLayerSize', 'prefix' => $prefix, 'position' => $position, ), ), $prefix . 'align' => array( 'type' => 'select', 'label' => __( 'Align', 'fl-builder' ), 'default' => $position . ' center', 'options' => array( 'top left' => __( 'Top Left', 'fl-builder' ), 'top center' => __( 'Top Center', 'fl-builder' ), 'top right' => __( 'Top Right', 'fl-builder' ), 'center left' => __( 'Center Left', 'fl-builder' ), 'center center' => __( 'Center', 'fl-builder' ), 'center right' => __( 'Center Right', 'fl-builder' ), 'bottom left' => __( 'Bottom Left', 'fl-builder' ), 'bottom center' => __( 'Bottom Center', 'fl-builder' ), 'bottom right' => __( 'Bottom Right', 'fl-builder' ), ), 'preview' => array( 'type' => 'callback', 'callback' => 'previewShapeAlign', 'prefix' => $prefix, 'selector' => ".fl-builder-$position-edge-layer > *", ), ), ), ); // Shape Styles $sections[ $prefix . 'style' ] = array( /* translators: %s: position label */ 'title' => sprintf( __( '%s Shape Style', 'fl-builder' ), $position_label ), 'fields' => array( $prefix . 'fill_style' => array( 'type' => 'button-group', 'options' => array( 'color' => __( 'Color Fill', 'fl-builder' ), 'gradient' => __( 'Gradient Fill', 'fl-builder' ), ), 'default' => 'color', 'preview' => array( 'type' => 'callback', 'callback' => 'previewShapeFillStyle', 'position' => $position, 'prefix' => $prefix, 'selector' => ".fl-builder-$position-edge-layer .fl-shape-content .fl-shape", ), 'toggle' => array( 'color' => array( 'fields' => array( $prefix . 'fill_color', ), ), 'gradient' => array( 'fields' => array( $prefix . 'fill_gradient', ), ), ), ), $prefix . 'fill_color' => array( 'type' => 'color', 'connections' => array( 'color' ), 'label' => __( 'Color', 'fl-builder' ), 'show_reset' => true, 'show_alpha' => true, 'responsive' => true, 'default' => 'aaa', 'preview' => array( 'type' => 'css', 'selector' => ".fl-builder-$position-edge-layer .fl-shape-content .fl-shape", 'property' => 'fill', ), ), $prefix . 'fill_gradient' => array( 'type' => 'gradient', 'label' => __( 'Gradient', 'fl-builder' ), 'default' => '', 'preview' => array( 'type' => 'callback', 'callback' => 'previewShapeGradientFill', 'position' => $position, 'prefix' => $prefix, ), ), $prefix . 'transform' => array( 'type' => 'shape-transform', 'label' => __( 'Transform', 'fl-builder' ), 'preview' => array( 'type' => 'callback', 'callback' => 'previewShapeTransform', 'selector' => ".fl-builder-$position-edge-layer", 'position' => $position, ), ), ), ); } $sections['shapes_container'] = array( 'title' => __( 'Shape Container', 'fl-builder' ), 'fields' => array( 'container_overflow' => array( 'type' => 'select', 'label' => __( 'Clip Within Container', 'fl-builder' ), 'options' => array( '' => __( 'No Clip', 'fl-builder' ), 'hidden' => __( 'Clip Contents', 'fl-builder' ), ), 'preview' => array( 'type' => 'css', 'selector' => '.fl-row-content-wrap', 'property' => 'overflow', ), ), ), ); return $sections; } /** * Render the CSS for any shape layers set on a given node * * @param Object $node - the current node * @return void */ static public function render_shape_layers_css( $node ) { $settings = $node->settings; $id = $node->node; $layers = array( 'top', 'bottom' ); foreach ( $layers as $position ) { $prefix = $position . '_edge_'; if ( ! empty( $settings->{ $prefix . 'shape' } ) ) { $shape_name = $settings->{ $prefix . 'shape' }; $presets = FLBuilderSettingsPresets::get_presets(); $preset = ( isset( $presets['shape'][ $shape_name ] ) ) ? $presets['shape'][ $shape_name ] : false; if ( ! $preset ) { continue; } FLBuilderCSS::rule( array( 'selector' => ".fl-node-$id .fl-builder-$position-edge-layer", 'enabled' => $settings->{ $prefix . 'size_top'} && $settings->{ $prefix . 'size_unit' }, 'props' => array( $position => $settings->{ $prefix . 'size_top'} . $settings->{ $prefix . 'size_unit' }, ), ) ); // Width, Height & Align $shape_selector = ".fl-node-$id .fl-builder-$position-edge-layer > *"; $shape_align = explode( ' ', $settings->{ $prefix . 'align' } ); $align_y = $shape_align[0]; $align_x = $shape_align[1]; $width = $settings->{ $prefix . 'size_width'}; $height = $settings->{ $prefix . 'size_height' }; $size_unit = $settings->{ $prefix . 'size_unit' }; // Defaults $shape_size_rule = array( 'selector' => $shape_selector, 'enabled' => true, 'props' => array(), ); $size_props = array( 'width' => '100%', 'left' => 'auto', 'right' => 'auto', 'height' => 'auto', 'top' => 'auto', 'bottom' => 'auto', ); if ( ! empty( $width ) ) { $size_props['width'] = $width . $size_unit; $width_offset = ( $width / 2 ) . $size_unit; switch ( $align_x ) { case 'left': $size_props['left'] = '0'; break; case 'right': $size_props['right'] = '0'; break; case 'center': $size_props['left'] = "calc( 50% - $width_offset )"; break; } } $height_offset = ''; if ( ! empty( $height ) ) { $height_offset = ( $height / 2 ) . $size_unit; $size_props['height'] = $height . $size_unit; } elseif ( $width ) { $view_box_height = $preset['data']['viewBox']['width']; $implied_height = ( $width / $view_box_height ) * 100; $height_offset = ( $implied_height / 2 ) . $size_unit; } switch ( $align_y ) { case 'top': $size_props['top'] = '0'; break; case 'bottom': $size_props['bottom'] = '0'; $size_props['top'] = 'auto'; break; case 'center': $size_props['top'] = "calc( 50% - $height_offset )"; break; } $shape_size_rule['props'] = $size_props; FLBuilderCSS::rule( $shape_size_rule ); // Shape Transforms $transforms = $settings->{ $prefix . 'transform' }; $layer_transforms = array(); $shape_transforms = array(); $sign = ''; if ( ! empty( $transforms ) ) { foreach ( $transforms as $prop => $value ) { switch ( $prop ) { case 'scaleXSign': case 'scaleYSign': break; case 'scaleX': case 'scaleY': if ( empty( $value ) ) { $value = 1; } // Positive or negative? if ( 'scaleX' === $prop ) { if ( isset( $transforms['scaleXSign'] ) ) { $sign = $transforms['scaleXSign']; } } else { if ( isset( $transforms['scaleYSign'] ) ) { $sign = $transforms['scaleYSign']; } } if ( 'invert' === $sign ) { $value = -abs( $value ); } else { $value = abs( $value ); } $value = $prop . '(' . $value . ')'; $shape_transforms[] = $value; break; case 'translateX': case 'translateY': if ( ! empty( $value ) ) { $value = $prop . '(' . $value . 'px)'; $shape_transforms[] = $value; } break; case 'skewX': case 'skewY': if ( ! empty( $value ) ) { $shape_transforms[] = $prop . '(' . $value . 'deg)'; } break; case 'rotate': if ( ! empty( $value ) ) { $shape_transforms[] = 'rotate(' . $value . 'deg)'; } break; } } // Shape Transforms FLBuilderCSS::rule( array( 'settings' => $settings, 'enabled' => ! empty( $shape_transforms ), 'selector' => ".fl-node-$id .fl-builder-$position-edge-layer > *", 'props' => array( 'transform' => implode( ' ', $shape_transforms ), ), ) ); } // Shape Fill if ( ! empty( $settings->{ $prefix . 'fill_style' } ) ) { switch ( $settings->{ $prefix . 'fill_style' } ) { case 'color': FLBuilderCSS::responsive_rule( array( 'settings' => $settings, 'setting_name' => $prefix . 'fill_color', 'selector' => ".fl-node-$id .fl-builder-$position-edge-layer .fl-shape-content .fl-shape", 'prop' => 'fill', ) ); break; case 'gradient': $gradient_type = $settings->{ $prefix . 'fill_gradient' }['type']; $gradient_id = "fl-row-$id-$prefix-$gradient_type-gradient"; FLBuilderCSS::rule( array( 'selector' => ".fl-node-$id .fl-builder-$position-edge-layer .fl-shape", 'enabled' => $settings->{ $prefix . 'fill_gradient' }, 'props' => array( 'fill' => 'url(#' . $gradient_id . ')', ), ) ); break; case 'pattern': $pattern_id = "fl-row-$id-$prefix-pattern"; FLBuilderCSS::rule( array( 'selector' => ".fl-node-$id .fl-builder-$position-edge-layer .fl-shape-content .fl-shape", 'enabled' => true, 'props' => array( 'fill' => 'url(#' . $pattern_id . ')', ), ) ); FLBuilderCSS::rule( array( 'selector' => ".fl-node-$id .fl-builder-$position-edge-layer pattern .fl-shape", 'enabled' => true, 'props' => array( 'fill' => $settings->{ $prefix . 'fill_pattern_shape_color' }, ), ) ); break; } } } } // Shared styles FLBuilderCSS::responsive_rule( array( 'settings' => $settings, 'setting_name' => 'container_overflow', 'selector' => ".fl-node-$id .fl-row-content-wrap", 'prop' => 'overflow', ) ); } /** * Convert a position keyword ( left, right, center, ... ) to a position integer ( 0.0 - 1.0 ) * * @param String $position * @return Int | Null */ static public function get_int_for_position_name( $position = '' ) { switch ( $position ) { case 'left': case 'top': return 0; case 'center': return .5; case 'right': case 'bottom': return 1; default: return null; } } static public function register_global_shapes( $global_settings = false ) { $width = '800'; $height = '100'; if ( FLBuilderAJAX::doing_ajax() ) { return false; } if ( ! $global_settings ) { $global_settings = FLBuilderModel::get_global_settings(); } if ( isset( $global_settings->shape_form ) && ! empty( $global_settings->shape_form ) ) { foreach ( (array) $global_settings->shape_form as $i => $shape ) { if ( ! isset( $shape->shape ) || '' == $shape->shape_name || '' == $shape->shape ) { continue; } $svg = $shape->shape; $name = $shape->shape_name; // find width/height $sizes = preg_match( '/viewBox="[0-9]+\s[0-9]+\s([0-9]+)\s([0-9]+)/', $svg, $matches ); if ( isset( $matches[1] ) && isset( $matches[2] ) ) { $width = $matches[1]; $height = $matches[2]; } // here we just want the internals of the svg everything else is stripped. $svg = preg_replace( '/^<\?xml[^>]*>/', '', $svg ); $svg = preg_replace( '/]*>/', '', $svg ); $svg = preg_replace( '/(<[a-z]+)/', '$1 class="fl-shape" ', ltrim( $svg ) ); $svg = str_replace( '', '', $svg ); $args = array( 'render' => $svg, 'width' => $width, 'height' => $height, 'name' => '' !== $shape->shape_original ? $shape->shape_original : 'global-shapes-' . sanitize_title( $name ), 'label' => $name, 'shape_original' => '' !== $shape->shape_original ? $shape->shape_original : 'global-shapes-' . sanitize_title( $name ), ); self::register_shape( $args ); } } } static public function global_options( $form, $id ) { if ( 'global' == $id ) { $form['tabs']['shapes'] = array( 'title' => 'Shapes', 'description' => 'Paste your SVG code to add new row shapes to the shape dropdown.', 'sections' => array( 'shapes' => array( 'title' => 'Row SVG Shapes', 'fields' => array( 'shape_form' => array( 'type' => 'form', 'label' => 'Shape', 'form' => 'global_shapes_form', 'preview_text' => 'shape_name', 'multiple' => true, ), ), ), ), ); } return $form; } public static function register_global_form() { FLBuilder::register_settings_form('global_shapes_form', array( 'title' => __( 'Add Shape', 'fl-builder' ), 'tabs' => array( 'general' => array( 'title' => __( 'General', 'fl-builder' ), // Tab title 'sections' => array( 'general' => array( 'title' => '', 'fields' => array( 'shape_name' => array( 'type' => 'text', 'label' => 'Name', 'required' => true, ), 'shape' => array( 'label' => 'SVG Code', 'type' => 'textarea', 'rows' => 10, 'required' => true, ), 'shape_original' => array( 'type' => 'text', ), ), ), ), ), ), )); } } FLBuilderArt::init();