playlist ID ) for a custom source. * - youwall_layout_html filter: return the inner HTML for a custom layout. * - youwall_panel_top action: print controls above a grid. * * @package YouWall */ defined( 'ABSPATH' ) || exit; /** * Feed renderer. */ class YouWall_Render { /** * Register assets and the shortcode. */ public static function init() { $css = YOUWALL_DIR . 'assets/frontend.css'; $js = YOUWALL_DIR . 'assets/frontend.js'; wp_register_style( 'youwall', YOUWALL_URL . 'assets/frontend.css', array(), YOUWALL_VERSION . '.' . filemtime( $css ) ); wp_add_inline_style( 'youwall', self::style_vars() ); wp_register_script( 'youwall', YOUWALL_URL . 'assets/frontend.js', array(), YOUWALL_VERSION . '.' . filemtime( $js ), true ); wp_localize_script( 'youwall', 'youwallVars', array( 'restUrl' => esc_url_raw( rest_url( YouWall_REST::NS . '/items' ) ), 'embed' => YouWall_Settings::get( 'privacy_mode' ) ? 'https://www.youtube-nocookie.com/embed/' : 'https://www.youtube.com/embed/', 'i18n' => array( 'player' => __( 'Video player', 'youwall' ), 'close' => __( 'Close video', 'youwall' ), 'loading' => __( 'Loading…', 'youwall' ), 'error' => __( 'Could not load more videos. Please try again.', 'youwall' ), 'loaded' => __( 'More videos loaded.', 'youwall' ), /* translators: %d: slide number. */ 'slide' => __( 'Go to slide %d', 'youwall' ), 'pause' => __( 'Pause slideshow', 'youwall' ), 'play' => __( 'Play slideshow', 'youwall' ), ), ) ); add_shortcode( 'youwall', array( __CLASS__, 'shortcode' ) ); } /** * CSS custom properties from the Style settings. * * @return string */ private static function style_vars() { $s = YouWall_Settings::all(); return sprintf( '.youwall,.youwall-lightbox{--youwall-gap:%dpx;--youwall-radius:%dpx;--youwall-border-width:%dpx;--youwall-border-color:%s;--youwall-tile-bg:%s;--youwall-btn-bg:%s;--youwall-btn-color:%s;--youwall-overlay:rgba(0,0,0,%s);}', absint( $s['gap'] ), absint( $s['border_radius'] ), absint( $s['tile_border_width'] ), sanitize_hex_color( $s['tile_border_color'] ), sanitize_hex_color( $s['tile_bg'] ), sanitize_hex_color( $s['btn_bg'] ), sanitize_hex_color( $s['btn_color'] ), number_format( min( 100, absint( $s['overlay_opacity'] ) ) / 100, 2, '.', '' ) ); } /** * [youwall] shortcode. * * @param array|string $atts Shortcode attributes. * @return string */ public static function shortcode( $atts ) { return self::render( is_array( $atts ) ? $atts : array() ); } /** * Attributes accepted by every entry point, with defaults from Settings. * * @return array */ public static function default_atts() { $s = YouWall_Settings::all(); return apply_filters( 'youwall_default_atts', array( 'source' => $s['source'], 'channel' => '', 'layout' => $s['layout'], 'columns' => $s['columns'], 'per_page' => $s['per_page'], 'per_view' => $s['slider_per_view'], 'autoplay' => $s['slider_autoplay'] ? 'yes' : 'no', 'arrows' => $s['slider_arrows'] ? 'yes' : 'no', 'dots' => $s['slider_dots'] ? 'yes' : 'no', ) ); } /** * Render a feed. * * @param array $atts Attributes (see default_atts()). * @return string HTML, escaped. */ public static function render( $atts ) { $a = shortcode_atts( self::default_atts(), $atts, 'youwall' ); $source = in_array( $a['source'], YouWall_Settings::sources(), true ) ? $a['source'] : 'shorts'; $layout = in_array( $a['layout'], YouWall_Settings::layouts(), true ) ? $a['layout'] : 'grid'; if ( 'both' === $source ) { $layout = 'grid'; // The Shorts + Videos tabs always use grids. } $view = apply_filters( 'youwall_view', array( 'source' => $source, 'layout' => $layout, 'columns' => max( 1, min( 6, absint( $a['columns'] ) ) ), 'per_page' => max( 1, min( 50, absint( $a['per_page'] ) ) ), 'per_view' => max( 1, min( 6, absint( $a['per_view'] ) ) ), 'autoplay' => self::yes( $a['autoplay'] ), 'arrows' => self::yes( $a['arrows'] ), 'dots' => self::yes( $a['dots'] ), 'sort' => false, ), $a ); $panels = self::panels( $source, $a ); if ( is_wp_error( $panels ) ) { return self::notice( $panels->get_error_message() ); } // First page of each panel. $api = new YouWall_API(); $data = array(); foreach ( $panels as $kind => $playlist_id ) { $page = $api->get_page( $playlist_id, $kind, $view['per_page'] ); if ( is_wp_error( $page ) ) { return self::notice( $page->get_error_message() ); } $data[ $kind ] = array( 'feed' => YouWall_REST::encode_feed( array( 'p' => $playlist_id, 'k' => $kind, 'n' => $view['per_page'], 's' => ! empty( $view['sort'] ), ) ), 'page' => $page, ); } $total = 0; foreach ( $data as $panel ) { $total += count( $panel['page']['items'] ); } if ( 0 === $total ) { return self::notice( __( 'No videos found yet. New uploads will show up here on their own.', 'youwall' ) ); } wp_enqueue_style( 'youwall' ); wp_enqueue_script( 'youwall' ); $inner = (string) apply_filters( 'youwall_layout_html', '', $layout, $data, $view ); if ( '' === $inner ) { ob_start(); if ( count( $data ) > 1 ) { self::tabs( $data, $view ); } elseif ( 'slider' === $layout ) { self::slider( current( $data ), key( $data ), $view ); } else { self::grid_panel( current( $data ), key( $data ), $view ); } $inner = (string) ob_get_clean(); } return sprintf( '
%s
', esc_attr( 'youwall youwall--' . $layout ), esc_attr( '--youwall-columns:' . $view['columns'] . ';--youwall-per-view:' . $view['per_view'] . ';' ), esc_attr( $layout ), $inner ); } /** * Which playlists a source shows, keyed by kind ('shorts' or 'videos'). * * @param string $source Source. * @param array $a Attributes. * @return array|WP_Error */ private static function panels( $source, $a ) { $custom = apply_filters( 'youwall_panels', null, $source, $a ); if ( null !== $custom ) { return $custom; } $channel = '' !== trim( (string) $a['channel'] ) ? (string) $a['channel'] : (string) YouWall_Settings::get( 'channel_id' ); if ( '' === $channel ) { return new WP_Error( 'youwall_no_channel', __( 'Add your YouTube channel in YouWall > Connect, or set a channel on this feed.', 'youwall' ) ); } $api = new YouWall_API(); $channel_id = $api->resolve_channel( $channel ); if ( is_wp_error( $channel_id ) ) { return $channel_id; } $panels = array(); foreach ( 'both' === $source ? array( 'shorts', 'videos' ) : array( $source ) as $kind ) { $panels[ $kind ] = YouWall_API::channel_playlist( $kind, $channel_id ); } return $panels; } /** * Data attributes that let the script load the next page. * * @param string $feed Encoded feed. * @param string $next Next page token. * @return string */ public static function feed_attrs( $feed, $next ) { return sprintf( ' data-feed="%s" data-sig="%s" data-sort="recent" data-next="%s" data-next-sig="%s"', esc_attr( $feed ), esc_attr( YouWall_REST::sign( $feed ) ), esc_attr( $next ), esc_attr( '' !== $next ? YouWall_REST::sign( $feed . '|recent|' . $next ) : '' ) ); } /** * Grid with a Load more button. * * @param array $panel { feed, page }. * @param string $kind 'shorts' or 'videos'. * @param array $view View settings. * @param string $id Optional element ID (tabs). * @param bool $hidden Whether the panel starts hidden (tabs). */ private static function grid_panel( $panel, $kind, $view, $id = '', $hidden = false ) { $next = $panel['page']['next']; $tab = $id ? sprintf( ' id="%s" role="tabpanel" aria-labelledby="%s"', esc_attr( $id ), esc_attr( $id . '-tab' ) ) : ''; ?>
>
>

__( 'Shorts', 'youwall' ), 'videos' => __( 'Videos', 'youwall' ), ); $first = true; echo '
'; foreach ( array_keys( $data ) as $kind ) { printf( '', esc_attr( $uid . '-' . $kind ), $first ? 'true' : 'false', $first ? '0' : '-1', esc_html( $labels[ $kind ] ) ); $first = false; } echo '
'; $first = true; foreach ( $data as $kind => $panel ) { self::grid_panel( $panel, $kind, $view, $uid . '-' . $kind, ! $first ); $first = false; } } /** * Slider. * * @param array $panel { feed, page }. * @param string $kind 'shorts' or 'videos'. * @param array $view View settings. */ private static function slider( $panel, $kind, $view ) { $label = 'shorts' === $kind ? __( 'Shorts', 'youwall' ) : __( 'Videos', 'youwall' ); ?>
elements. Also used by the REST endpoint for "Load more". * * @param array $items Items. * @param string $kind 'shorts' or 'videos'. * @param int $start Number of the first tile (for optional numbering). * @return string */ public static function items_html( $items, $kind, $start ) { $numbers = (bool) YouWall_Settings::get( 'show_numbers' ); $html = ''; $n = absint( $start ); foreach ( $items as $item ) { $duration = 'videos' === $kind ? YouWall_API::format_duration( $item['duration'] ) : ''; $fallback = 'shorts' === $kind ? 'https://i.ytimg.com/vi/' . $item['id'] . '/hqdefault.jpg' : ''; $meta = self::meta( $item ); $html .= '
  • '; $html .= sprintf( '
  • '; ++$n; } return $html; } /** * Tile shape for a kind. * * @param string $kind 'shorts' or 'videos'. * @return string */ private static function shape( $kind ) { return 'shorts' === $kind ? 'portrait' : 'landscape'; } /** * "1.2K views · 3 days ago". * * @param array $item Item. * @return string */ public static function meta( $item ) { return implode( ' · ', array_filter( array( YouWall_API::format_views( $item['views'] ), YouWall_API::time_ago( $item['published'] ), ) ) ); } /** * Setup or error message. Only editors see it; visitors see nothing. * * @param string $message Message. * @return string */ public static function notice( $message ) { if ( ! current_user_can( 'edit_posts' ) ) { return ''; } return sprintf( '
    %s %s %s
    ', esc_html__( 'YouWall:', 'youwall' ), esc_html( $message ), esc_html__( '(Only people who can edit the site see this message.)', 'youwall' ) ); } /** * Whether a yes/no attribute is on. * * @param mixed $value Value. * @return bool */ public static function yes( $value ) { return true === $value || in_array( strtolower( (string) $value ), array( '1', 'yes', 'true', 'on' ), true ); } /** * Small static SVG icons. * * @param string $name Icon name. * @return string */ public static function icon( $name ) { $paths = array( 'play' => '', 'prev' => '', 'next' => '', 'pause' => '', ); return ''; } }