true, 'id' => true, 'style' => true, 'role' => true, 'title' => true, 'tabindex' => true, 'aria-*' => true, 'data-*' => true, ); $allowed = array( 'div' => $common, 'span' => $common, 'button' => array_merge( $common, array( 'type' => true, 'disabled' => true ) ), 'a' => array_merge( $common, array( 'href' => true, 'target' => true, 'rel' => true ) ), 'img' => array_merge( $common, array( 'src' => true, 'srcset' => true, 'sizes' => true, 'alt' => true, 'width' => true, 'height' => true, 'loading' => true, 'decoding' => true, 'fetchpriority' => true, ) ), 'ul' => $common, 'li' => $common, 'p' => $common, ); return $allowed; } /** * Escape slider markup with its restricted HTML allowlist. * * @param string $html Slider HTML. * @return string Safe HTML. */ public static function escape_slider_html( $html ) { add_filter( 'safe_style_css', array( __CLASS__, 'allow_style_properties' ) ); $safe = wp_kses( (string) $html, self::allowed_slider_html() ); remove_filter( 'safe_style_css', array( __CLASS__, 'allow_style_properties' ) ); return $safe; } /** * Allow the slider's own CSS custom properties through wp_kses(). * * Hooked only while escape_slider_html() runs. * * @param string[] $properties Allowed CSS properties. * @return string[] */ public static function allow_style_properties( $properties ) { return array_merge( (array) $properties, array( '--developersd-slides-desktop', '--developersd-slides-tablet', '--developersd-slides-mobile', '--developersd-logo-gap', '--developersd-image-width', '--developersd-image-height', '--developersd-image-fit', '--developersd-item-radius', '--developersd-item-padding', '--developersd-opacity', '--developersd-hover-opacity', '--developersd-hover-scale', '--developersd-nav-size', '--developersd-nav-icon-size', '--developersd-dots-size', '--developersd-item-background', '--developersd-container-width', ) ); } /** * Render a slider by ID. * * @param int $slider_id Slider post ID. * @param array $overrides Optional setting overrides (e.g. from a shortcode * attribute or builder widget control). Only keys * that exist in the settings schema are honored. * @return string Rendered HTML, or an empty string if the slider is * invalid, inactive, or has no usable logos. */ public static function render( $slider_id, $overrides = array() ) { $slider_id = absint( $slider_id ); if ( ! $slider_id ) { return ''; } $post = get_post( $slider_id ); if ( ! $post || DeveloperSD_LS_Post_Type::POST_TYPE !== $post->post_type || 'publish' !== $post->post_status ) { return ''; } $slider = new DeveloperSD_LS_Slider( $slider_id ); if ( ! $slider->is_active() ) { return ''; } $logos = $slider->get_logos(); if ( empty( $logos ) ) { return ''; } $settings = $slider->get_settings(); if ( ! empty( $overrides ) && is_array( $overrides ) ) { $settings = DeveloperSD_LS_Security::sanitize_settings( array_merge( $settings, $overrides ), $settings ); } /** * Filters the final settings used to render a slider. * * @param array $settings Sanitized settings. * @param int $slider_id Slider post ID. */ $settings = apply_filters( 'developersd_logo_slider_args', $settings, $slider_id ); /** * Filters the logos used to render a slider. * * @param array $logos Sanitized logos. * @param int $slider_id Slider post ID. */ $logos = apply_filters( 'developersd_logo_slider_logo_data', $logos, $slider_id ); DeveloperSD_LS_Assets::enqueue_frontend_assets(); return self::escape_slider_html( self::get_template( $slider_id, $logos, $settings ) ); } /** * Load and return the buffered slider template output. * * @param int $slider_id Slider post ID. * @param array $logos Sanitized logos. * @param array $settings Sanitized settings. * @return string */ protected static function get_template( $slider_id, $logos, $settings ) { $template_path = DEVELOPERSD_LS_PATH . 'templates/slider.php'; /** * Filters the template path used to render a slider, allowing themes * to override it (e.g. via `locate_template()` in their own filter). * * @param string $template_path Absolute path to the template file. * @param int $slider_id Slider post ID. */ $template_path = apply_filters( 'developersd_logo_slider_template', $template_path, $slider_id ); if ( ! file_exists( $template_path ) ) { return ''; } ob_start(); /** * Fires immediately before a slider is output. * * @param int $slider_id Slider post ID. * @param array $settings Sanitized settings. */ do_action( 'developersd_logo_slider_before', $slider_id, $settings ); include $template_path; // Variables $slider_id, $logos, $settings are used inside the template. /** * Fires immediately after a slider is output. * * @param int $slider_id Slider post ID. * @param array $settings Sanitized settings. */ do_action( 'developersd_logo_slider_after', $slider_id, $settings ); $output = ob_get_clean(); /** * Filters the final rendered HTML output of a slider. * * @param string $output Rendered HTML. * @param int $slider_id Slider post ID. * @param array $settings Sanitized settings. */ return self::escape_slider_html( apply_filters( 'developersd_logo_slider_output', $output, $slider_id, $settings ) ); } /** * Render a slider ignoring its active/inactive status. Used only by the * admin-side "Preview" action, which already performs its own * capability and nonce checks before calling this method. * * @param int $slider_id Slider post ID. * @return string */ public static function render_forced( $slider_id ) { $slider_id = absint( $slider_id ); $post = get_post( $slider_id ); if ( ! $post || DeveloperSD_LS_Post_Type::POST_TYPE !== $post->post_type ) { return ''; } $slider = new DeveloperSD_LS_Slider( $slider_id ); $logos = $slider->get_logos(); if ( empty( $logos ) ) { return ''; } $settings = $slider->get_settings(); DeveloperSD_LS_Assets::enqueue_frontend_assets(); return self::escape_slider_html( self::get_template( $slider_id, $logos, $settings ) ); } /** * Render a slider from raw, not-yet-saved logos/settings data. Used by * the admin-side live preview AJAX handler so editors can see changes * before saving. The caller is responsible for capability/nonce checks. * * @param int $slider_id Existing slider post ID (for title/aria-label only). * @param array $logos Raw logos array. * @param array $settings Raw settings array. * @return string */ public static function render_from_data( $slider_id, $logos, $settings ) { $logos = DeveloperSD_LS_Security::sanitize_logos( $logos ); if ( empty( $logos ) ) { return ''; } $settings = DeveloperSD_LS_Security::sanitize_settings( $settings, DeveloperSD_LS_Slider::default_settings() ); DeveloperSD_LS_Assets::enqueue_frontend_assets(); return self::escape_slider_html( self::get_template( absint( $slider_id ), $logos, $settings ) ); } /** * Build the sanitized list of CSS classes for the slider root element. * * @param array $settings Sanitized settings. * @return string */ public static function get_root_classes( $settings ) { /** * Filters the CSS classes applied to a slider's root element. * * @param string[] $classes Array of class names. * @param array $settings Sanitized settings. */ $classes = apply_filters( 'developersd_logo_slider_classes', array_filter( array( 'developersd-logo-slider', 'developersd-logo-slider--' . sanitize_html_class( $settings['alignment'] ), 'developersd-logo-slider--nav-' . sanitize_html_class( $settings['nav_position'] ), 'developersd-logo-slider--dots-' . sanitize_html_class( $settings['dots_position'] ), ! empty( $settings['grayscale'] ) ? 'developersd-logo-slider--grayscale' : '', 'developersd-logo-slider--hover-' . sanitize_html_class( $settings['hover_effect'] ), ! empty( $settings['custom_class'] ) ? $settings['custom_class'] : '', ) ), $settings ); return implode( ' ', array_map( 'sanitize_html_class', $classes ) ); } }