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 '';
}
}