`: plain `streamsera_` would * take over StreamSera's own shortcodes ([streamsera_now_playing], [streamsera_station] …) on Divi sites. The player keeps * `streamsera_player` (no shortcode of that name exists) so layouts saved with the earlier module keep working. */ abstract class Divi_Element_Module extends \ET_Builder_Module { const ELEMENT = ''; public $vb_support = 'partial'; protected $module_credits = array( 'module_uri' => 'https://wpsera.com/streamsera/', 'author' => 'WPSera', 'author_uri' => 'https://wpsera.com/', ); /** * Element id → module class. * * @return array */ public static function classes() { return array( 'player' => Divi_Module_Player::class, 'listen-button' => Divi_Module_Listen_Button::class, 'now-playing' => Divi_Module_Now_Playing::class, 'station' => Divi_Module_Station::class, 'station-list' => Divi_Module_Station_List::class, ); } /** * Element definition. * * @return array */ protected function element() { $el = Elements::get( static::ELEMENT ); return $el ? $el : array( 'label' => static::ELEMENT, 'description' => '', 'fields' => array() ); } public function init() { /* translators: %s: element name, e.g. "Station". */ $this->name = esc_html( sprintf( __( 'StreamSera · %s', 'streamsera' ), $this->element()['label'] ) ); $this->plural = $this->name; // One icon per element (assets/divi/icons/.svg, drawn from Divi5::icons()); StreamSera mark as fallback. /** * Filter the SVG file used as a Divi 4 module icon (add-ons point at icons for their own elements). * * @param string $file Path. * @param string $id Element id. */ $icon = (string) apply_filters( 'streamsera_divi_icon_file', STREAMSERA_PATH . 'assets/divi/icons/' . static::ELEMENT . '.svg', static::ELEMENT ); $this->icon_path = file_exists( $icon ) ? $icon : STREAMSERA_PATH . 'assets/divi/streamsera.svg'; // Theme-wide font/text/button presets target generic selectors and would fight the widgets' own styles. $this->advanced_fields = array( 'fonts' => false, 'text' => false, 'button' => false, 'link_options' => false, ); } public function get_settings_modal_toggles() { return array( 'general' => array( 'toggles' => array( 'main_content' => esc_html__( 'Content', 'streamsera' ), 'streamsera_display' => esc_html__( 'Display', 'streamsera' ), ), ), 'advanced' => array( 'toggles' => array( 'streamsera_colors' => esc_html__( 'StreamSera colours', 'streamsera' ), ), ), ); } public function get_fields() { $out = array(); foreach ( $this->element()['fields'] as $key => $field ) { $out[ $key ] = $this->field( $field ); } if ( ! Elements::has_own_styles( static::ELEMENT ) ) { foreach ( $this->color_fields() as $key => $label ) { $out[ $key ] = array( 'label' => $label, 'type' => 'color-alpha', 'custom_color' => true, 'tab_slug' => 'advanced', 'toggle_slug' => 'streamsera_colors', ); } $out['streamsera_radius'] = array( 'label' => esc_html__( 'Corner radius', 'streamsera' ), 'type' => 'range', 'range_settings' => array( 'min' => 0, 'max' => 32, 'step' => 1 ), 'default_unit' => 'px', 'tab_slug' => 'advanced', 'toggle_slug' => 'streamsera_colors', ); } return $out; } /** * Colour field keys → labels. * * @return array */ private function color_fields() { return array( 'streamsera_accent' => esc_html__( 'Accent', 'streamsera' ), 'streamsera_bg' => esc_html__( 'Card background', 'streamsera' ), 'streamsera_border' => esc_html__( 'Border', 'streamsera' ), 'streamsera_text' => esc_html__( 'Text', 'streamsera' ), ); } /** * Divi field args for an element field. * * @param array $field Field. * @return array */ private function field( $field ) { $base = array( 'label' => $field['label'], 'description' => $field['help'], 'option_category' => 'basic_option', 'toggle_slug' => 'display' === $field['section'] ? 'streamsera_display' : 'main_content', ); switch ( $field['type'] ) { case 'toggle': return $base + array( 'type' => 'yes_no_button', 'options' => array( 'on' => esc_html__( 'Yes', 'streamsera' ), 'off' => esc_html__( 'No', 'streamsera' ), ), 'default' => $field['default'] ? 'on' : 'off', ); case 'number': return $base + array( 'type' => 'range', 'range_settings' => array( 'min' => $field['min'] ?? 0, 'max' => $field['max'] ?? 100, 'step' => 1, ), 'unitless' => true, 'validate_unit' => false, 'default' => (string) (int) $field['default'], ); case 'text': return $base + array( 'type' => 'text', 'default' => (string) $field['default'], ); default: $options = Elements::options( $field ); return $base + array( 'type' => 'select', 'options' => $options, 'default' => isset( $options[ (string) $field['default'] ] ) ? (string) $field['default'] : (string) array_key_first( $options ), ); } } public function render( $attrs, $content = null, $render_slug = '' ) { $args = array(); foreach ( $this->element()['fields'] as $key => $field ) { if ( ! isset( $this->props[ $key ] ) ) { continue; } $args[ $key ] = 'toggle' === $field['type'] ? ( 'on' === $this->props[ $key ] ) : $this->props[ $key ]; } $html = Elements::render( static::ELEMENT, $args ); $vars = array( 'streamsera_accent' => '--ss-widget-accent', 'streamsera_bg' => '--ss-widget-bg', 'streamsera_border' => '--ss-widget-border', 'streamsera_text' => 'color', ); $css = ''; foreach ( $vars as $key => $prop ) { $value = isset( $this->props[ $key ] ) ? (string) $this->props[ $key ] : ''; if ( '' !== $value && preg_match( '/^[#a-z0-9(),.%\s]+$/i', $value ) ) { $css .= $prop . ':' . $value . ';'; } } if ( ! empty( $this->props['streamsera_radius'] ) && preg_match( '/^\d+(px)?$/', (string) $this->props['streamsera_radius'] ) ) { $css .= '--ss-widget-radius:' . ( (int) $this->props['streamsera_radius'] ) . 'px;'; } if ( '' === $html ) { return ''; } $html = '
' . wp_kses( $html, streamsera_allowed_html() ) . '
'; // Divi only adds the standard module wrapper (et_pb_module + order class) itself for vb_support 'on'. // Partial-support modules must do it: it carries the Design-tab styles and spacing, and Divi 5's // Visual Builder uses it to find the module in its preview render. return method_exists( $this, '_render_module_wrapper' ) ? $this->_render_module_wrapper( $html, $render_slug ) : $html; } } // phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound, Squiz.Commenting.ClassComment.Missing final class Divi_Module_Player extends Divi_Element_Module { const ELEMENT = 'player'; public $slug = 'streamsera_player'; } final class Divi_Module_Listen_Button extends Divi_Element_Module { const ELEMENT = 'listen-button'; public $slug = 'streamsera_divi_listen_button'; } final class Divi_Module_Now_Playing extends Divi_Element_Module { const ELEMENT = 'now-playing'; public $slug = 'streamsera_divi_now_playing'; } final class Divi_Module_Station extends Divi_Element_Module { const ELEMENT = 'station'; public $slug = 'streamsera_divi_station'; } final class Divi_Module_Station_List extends Divi_Element_Module { const ELEMENT = 'station-list'; public $slug = 'streamsera_divi_station_list'; } // phpcs:enable