Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
50.32% covered (warning)
50.32%
79 / 157
0.00% covered (danger)
0.00%
0 / 1
CRAP
n/a
0 / 0
shortcode_handler_bandcamp
51.30% covered (warning)
51.30%
79 / 154
0.00% covered (danger)
0.00%
0 / 1
290.42
1<?php
2/**
3 * Shortcode handler for [bandcamp], which inserts a bandcamp.com
4 * music player (iframe, html5)
5 *
6 * [bandcamp album=119385304]
7 * [bandcamp album=3462839126  bgcol=FFFFFF linkcol=4285BB size=venti]
8 * [bandcamp track=2446959313]
9 *
10 * @package automattic/jetpack
11 */
12
13if ( ! defined( 'ABSPATH' ) ) {
14    exit( 0 );
15}
16
17/**
18 * Display the Bandcamp shortcode.
19 *
20 * @param array $atts Shortcode attributes.
21 */
22function shortcode_handler_bandcamp( $atts ) {
23    $csswidth  = null;
24    $cssheight = null;
25    // there are no default values, but specify here anyway to explicitly list supported atts.
26    $attributes = shortcode_atts(
27        array(
28            'album'       => null,     // integer album id.
29            'track'       => null,     // integer track id.
30            'video'       => null,     // integer track id for video player.
31            'size'        => 'venti',  // one of the supported sizes.
32            'bgcol'       => 'FFFFFF', // hex, no '#' prefix.
33            'linkcol'     => null,     // hex, no '#' prefix.
34            'layout'      => null,     // encoded layout url.
35            'width'       => null,     // integer with optional "%".
36            'height'      => null,     // integer with optional "%".
37            'notracklist' => null,     // may be string "true" (defaults false).
38            'tracklist'   => null,     // may be string "false" (defaults true).
39            // phpcs:ignore Squiz.PHP.CommentedOutCode.Found -- false positive
40            'artwork'     => null,     // may be string "false" (alternately: "none") or "small" (default is large).
41            'minimal'     => null,     // may be string "true" (defaults false).
42            'theme'       => null,     // may be theme identifier string ("light"|"dark" so far).
43            'package'     => null,     // integer package id.
44            't'           => null,     // integer track number.
45            'tracks'      => null,     // comma-separated list of allowed tracks.
46            'esig'        => null,      // hex, no '#' prefix.
47        ),
48        $atts,
49        'bandcamp'
50    );
51
52    $sizes = array(
53        'venti'      => array(
54            'width'  => 400,
55            'height' => 100,
56        ),
57        'grande'     => array(
58            'width'  => 300,
59            'height' => 100,
60        ),
61        'grande2'    => array(
62            'width'  => 300,
63            'height' => 355,
64        ),
65        'grande3'    => array(
66            'width'  => 300,
67            'height' => 415,
68        ),
69        'tall_album' => array(
70            'width'  => 150,
71            'height' => 295,
72        ),
73        'tall_track' => array(
74            'width'  => 150,
75            'height' => 270,
76        ),
77        'tall2'      => array(
78            'width'  => 150,
79            'height' => 450,
80        ),
81        'short'      => array(
82            'width'  => 46,
83            'height' => 23,
84        ),
85        'large'      => array(
86            'width'  => 350,
87            'height' => 470,
88        ),
89        'medium'     => array(
90            'width'  => 450,
91            'height' => 120,
92        ),
93        'small'      => array(
94            'width'  => 350,
95            'height' => 42,
96        ),
97    );
98
99    $sizekey = $attributes['size'];
100    $height  = null;
101    $width   = null;
102
103    $is_video = false;
104
105    /*
106     * Build iframe url.  For audio players, args are appended as
107     * extra path segments for historical reasons having to
108     * do with an IE-only flash bug which required this URL
109     * to contain no querystring.  Delay the actual joining
110     * of args into a string until after we decide if it's
111     * a video player or an audio player
112     */
113    $argparts = array();
114
115    if ( ! isset( $attributes['album'] ) && ! isset( $attributes['track'] ) && ! isset( $attributes['video'] ) ) {
116        return "[bandcamp: shortcode must include 'track', 'album', or 'video' param]";
117    }
118
119    if ( isset( $attributes['track'] ) && is_numeric( $attributes['track'] ) ) {
120        $track = esc_attr( $attributes['track'] );
121        array_push( $argparts, "track={$track}" );
122    } elseif ( isset( $attributes['video'] ) && is_numeric( $attributes['video'] ) ) {
123        $track    = esc_attr( $attributes['video'] ); // videos are referenced by track id.
124        $url      = '//bandcamp.com/EmbeddedPlayer/v=2';
125        $is_video = true;
126        array_push( $argparts, "track={$track}" );
127    }
128    if ( isset( $attributes['album'] ) && is_numeric( $attributes['album'] ) ) {
129        $album = esc_attr( $attributes['album'] );
130        array_push( $argparts, "album={$album}" );
131    }
132
133    if ( 'tall' === $sizekey ) {
134        if ( isset( $attributes['album'] ) ) {
135            $sizekey .= '_album';
136        } else {
137            $sizekey .= '_track';
138        }
139    }
140
141    // if size specified that we don't recognize, fall back on venti.
142    if ( empty( $sizes[ $sizekey ] ) ) {
143        $sizekey            = 'venti';
144        $attributes['size'] = 'venti';
145    }
146
147    /*
148     * use strict regex for digits + optional % instead of absint for height/width
149     * 'width' and 'height' params in the iframe url get the exact string from the shortcode
150     * args, whereas the inline style attribute must have "px" added to it if it has no "%"
151     */
152    if ( isset( $attributes['width'] ) && preg_match( '|^([0-9]+)(%)?$|', $attributes['width'], $matches ) ) {
153        $width    = $attributes['width'];
154        $csswidth = $attributes['width'];
155        if ( count( $matches ) < 3 ) {
156            $csswidth .= 'px';
157        }
158    }
159    if ( isset( $attributes['height'] ) && preg_match( '|^([0-9]+)(%)?$|', $attributes['height'], $matches ) ) {
160        $height    = $attributes['height'];
161        $cssheight = $attributes['height'];
162        if ( count( $matches ) < 3 ) {
163            $cssheight .= 'px';
164        }
165    }
166
167    if ( ! $height ) {
168        $height    = $sizes[ $sizekey ]['height'];
169        $cssheight = $height . 'px';
170    }
171
172    if ( ! $width ) {
173        $width    = $sizes[ $sizekey ]['width'];
174        $csswidth = $width . 'px';
175    }
176
177    if ( isset( $attributes['layout'] ) ) {
178        array_push( $argparts, "layout={$attributes['layout']}" );
179    } elseif ( isset( $attributes['size'] ) && preg_match( '|^[a-zA-Z0-9]+$|', $attributes['size'] ) ) {
180        array_push( $argparts, "size={$attributes['size']}" );
181    }
182
183    if ( isset( $attributes['bgcol'] ) && preg_match( '|^[0-9A-Fa-f]+$|', $attributes['bgcol'] ) ) {
184        array_push( $argparts, "bgcol={$attributes['bgcol']}" );
185    }
186
187    if ( isset( $attributes['linkcol'] ) && preg_match( '|^[0-9A-Fa-f]+$|', $attributes['linkcol'] ) ) {
188        array_push( $argparts, "linkcol={$attributes['linkcol']}" );
189    }
190
191    if ( isset( $attributes['package'] ) && preg_match( '|^[0-9]+$|', $attributes['package'] ) ) {
192        array_push( $argparts, "package={$attributes['package']}" );
193    }
194
195    if ( isset( $attributes['t'] ) && preg_match( '|^[0-9]+$|', $attributes['t'] ) ) {
196        array_push( $argparts, "t={$attributes['t']}" );
197    }
198
199    if ( 'true' === $attributes['notracklist'] ) {
200        array_push( $argparts, 'notracklist=true' );
201    }
202
203    // 'tracklist' arg deprecates 'notracklist=true' to be less weird.  note, behavior
204    // if both are specified is undefined
205    switch ( $attributes['tracklist'] ) {
206        case 'false':
207        case 'none':
208            array_push( $argparts, 'tracklist=false' );
209            break;
210    }
211
212    switch ( $attributes['artwork'] ) {
213        case 'false':
214        case 'none':
215        case 'small':
216            array_push( $argparts, 'artwork=' . $attributes['artwork'] );
217            break;
218    }
219
220    if ( 'true' === $attributes['minimal'] ) {
221        array_push( $argparts, 'minimal=true' );
222    }
223
224    if ( isset( $attributes['theme'] ) && preg_match( '|^[a-zA-Z_]+$|', $attributes['theme'] ) ) {
225        array_push( $argparts, "theme={$attributes['theme']}" );
226    }
227
228    // param 'tracks' is signed digest param 'esig'.
229    if ( isset( $attributes['tracks'] ) && preg_match( '|^[0-9\,]+$|', $attributes['tracks'] ) ) {
230        if ( isset( $attributes['esig'] ) && preg_match( '|^[0-9A-Fa-f]+$|', $attributes['esig'] ) ) {
231            array_push( $argparts, "tracks={$attributes['tracks']}" );
232            array_push( $argparts, "esig={$attributes['esig']}" );
233        }
234    }
235
236    if ( $is_video ) {
237        $url         = '//bandcamp.com/VideoEmbed?' . implode( '&', $argparts );
238        $extra_attrs = " mozallowfullscreen='1' webkitallowfullscreen='1' allowfullscreen='1'";
239    } else {
240        $url         = '//bandcamp.com/EmbeddedPlayer/v=2/' . implode( '/', $argparts ) . '/';
241        $extra_attrs = '';
242    }
243
244    $iframe = '<iframe width="%s" height="%s" style="position: relative; display: block; width: %s; height: %s;" src="%s" allowtransparency="true" frameborder="0"%s></iframe>';
245    $iframe = sprintf( $iframe, esc_attr( $width ), esc_attr( $height ), esc_attr( $csswidth ), esc_attr( $cssheight ), esc_url( $url ), $extra_attrs );
246
247    return $iframe;
248}
249
250add_shortcode( 'bandcamp', 'shortcode_handler_bandcamp' );