Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
78.03% covered (warning)
78.03%
103 / 132
37.50% covered (danger)
37.50%
3 / 8
CRAP
n/a
0 / 0
jetpack_shortcode_get_vimeo_id
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
4.02
jetpack_shortcode_get_vimeo_dimensions
84.85% covered (warning)
84.85%
28 / 33
0.00% covered (danger)
0.00%
0 / 1
16.89
vimeo_shortcode
100.00% covered (success)
100.00%
48 / 48
100.00% covered (success)
100.00%
1 / 1
13
wpcom_vimeo_embed_url
n/a
0 / 0
n/a
0 / 0
2
wpcom_vimeo_embed_url_init
n/a
0 / 0
n/a
0 / 0
1
vimeo_embed_to_shortcode
90.48% covered (success)
90.48%
19 / 21
0.00% covered (danger)
0.00%
0 / 1
11.10
vimeo_link
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
vimeo_link_callback
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2/**
3 * Vimeo Shortcode.
4 *
5 * Examples:
6 * [vimeo 141358]
7 * [vimeo http://vimeo.com/141358]
8 * [vimeo 141358 h=500&w=350]
9 * [vimeo 141358 h=500 w=350]
10 * [vimeo id=141358 width=350 height=500]
11 *
12 * <iframe src="http://player.vimeo.com/video/18427511" width="400" height="225" frameborder="0"></iframe><p><a href="http://vimeo.com/18427511">Eskmo 'We Got More' (Official Video)</a> from <a href="http://vimeo.com/ninjatune">Ninja Tune</a> on <a href="http://vimeo.com">Vimeo</a>.</p>
13 *
14 * @package automattic/jetpack
15 */
16
17if ( ! defined( 'ABSPATH' ) ) {
18    exit( 0 );
19}
20
21/**
22 * Extract Vimeo ID from shortcode.
23 *
24 * @param array $atts Shortcode attributes.
25 */
26function jetpack_shortcode_get_vimeo_id( $atts ) {
27    if ( isset( $atts[0] ) ) {
28        $atts[0] = trim( $atts[0], '=' );
29        if ( is_numeric( $atts[0] ) ) {
30            return (int) $atts[0];
31        }
32
33        /**
34         * Extract Vimeo ID from the URL. For examples:
35         * https://vimeo.com/12345
36         * https://vimeo.com/289091934/cd1f466bcc
37         * https://vimeo.com/album/2838732/video/6342264
38         * https://vimeo.com/groups/758728/videos/897094040
39         * https://vimeo.com/channels/staffpicks/123456789
40         * https://vimeo.com/album/1234567/video/7654321
41         * https://player.vimeo.com/video/18427511
42         */
43        $pattern = '/(?:https?:\/\/)?vimeo\.com\/(?:groups\/\d+\/videos\/|album\/\d+\/video\/|video\/|channels\/[^\/]+\/videos\/|[^\/]+\/)?([0-9]+)(?:[^\'\"0-9<]|$)/i';
44        $match   = array();
45        if ( preg_match( $pattern, $atts[0], $match ) ) {
46            return (int) $match[1];
47        }
48    }
49
50    return 0;
51}
52
53/**
54 * Get video dimensions.
55 *
56 * @since 8.0.0
57 *
58 * @param array $attr     The attributes of the shortcode.
59 * @param array $old_attr Optional array of attributes from the old shortcode format.
60 *
61 * @return array Width and height.
62 */
63function jetpack_shortcode_get_vimeo_dimensions( $attr, $old_attr = array() ) {
64    global $content_width;
65
66    $default_width  = 600;
67    $default_height = 338;
68    $aspect_ratio   = $default_height / $default_width;
69
70    /*
71     * For width and height, we want to support both formats
72     * that can be provided in the new shortcode format:
73     * - for width: width or w
74     * - for height: height or h
75     *
76     * For each variation, the full word takes priority.
77     *
78     * If no variation is set, we default to the default width and height values set above.
79     */
80    if ( ! empty( $attr['width'] ) ) {
81        $width = absint( $attr['width'] );
82    } elseif ( ! empty( $attr['w'] ) ) {
83        $width = absint( $attr['w'] );
84    } else {
85        $width = $default_width;
86    }
87
88    if ( ! empty( $attr['height'] ) ) {
89        $height = absint( $attr['height'] );
90    } elseif ( ! empty( $attr['h'] ) ) {
91        $height = absint( $attr['h'] );
92    } else {
93        $height = $default_height;
94    }
95
96    /*
97     * Support w and h argument as fallbacks in old shortcode format.
98     */
99    if (
100        $default_width === $width
101        && ! empty( $old_attr['w'] )
102    ) {
103        $width = absint( $old_attr['w'] );
104
105        if (
106            $default_width === $width
107            && empty( $old_attr['h'] )
108        ) {
109            $height = round( $width * $aspect_ratio );
110        }
111    }
112
113    if (
114        $default_height === $height
115        && ! empty( $old_attr['h'] )
116    ) {
117        $height = absint( $old_attr['h'] );
118
119        if ( empty( $old_attr['w'] ) ) {
120            $width = round( $height * $aspect_ratio );
121        }
122    }
123
124    /*
125     * If we have a content width defined, let it be the new default.
126     */
127    if (
128        $default_width === $width
129        && ! empty( $content_width )
130    ) {
131        $width = absint( $content_width );
132    }
133
134    /*
135     * If we have a custom width, we need a custom height as well
136     * to maintain aspect ratio.
137     */
138    if (
139        $default_width !== $width
140        && $default_height === $height
141    ) {
142        $height = round( ( $width / 640 ) * 360 );
143    }
144
145    /**
146     * Filter the Vimeo player width.
147     *
148     * @module shortcodes
149     *
150     * @since 3.4.0
151     *
152     * @param int $width Width of the Vimeo player in pixels.
153     */
154    $width = (int) apply_filters( 'vimeo_width', $width );
155
156    /**
157     * Filter the Vimeo player height.
158     *
159     * @module shortcodes
160     *
161     * @since 3.4.0
162     *
163     * @param int $height Height of the Vimeo player in pixels.
164     */
165    $height = (int) apply_filters( 'vimeo_height', $height );
166
167    return array( $width, $height );
168}
169
170/**
171 * Convert a Vimeo shortcode into an embed code.
172 *
173 * @param array $atts An array of shortcode attributes.
174 *
175 * @return string The embed code for the Vimeo video.
176 */
177function vimeo_shortcode( $atts ) {
178    $attr = array_map(
179        'intval',
180        shortcode_atts(
181            array(
182                'id'       => 0,
183                'width'    => 0,
184                'height'   => 0,
185                'autoplay' => 0,
186                'loop'     => 0,
187                'w'        => 0,
188                'h'        => 0,
189            ),
190            $atts
191        )
192    );
193
194    if ( isset( $atts[0] ) ) {
195        $attr['id'] = jetpack_shortcode_get_vimeo_id( $atts );
196    }
197
198    if ( ! $attr['id'] ) {
199        return '<!-- vimeo error: not a vimeo video -->';
200    }
201
202    // Handle old shortcode params such as h=500&w=350.
203    $params = shortcode_new_to_old_params( $atts );
204    $params = str_replace( array( '&amp;', '&#038;' ), '&', $params );
205    parse_str( $params, $args );
206
207    list( $width, $height ) = jetpack_shortcode_get_vimeo_dimensions( $attr, $args );
208
209    $url = esc_url( 'https://player.vimeo.com/video/' . $attr['id'] );
210
211    // Handle autoplay and loop arguments.
212    if (
213        isset( $args['autoplay'] ) && '1' === $args['autoplay'] // Parsed from the embedded URL.
214        || $attr['autoplay']                                    // Parsed from shortcode arguments.
215        || in_array( 'autoplay', $atts, true )                  // Catch the argument passed without a value.
216    ) {
217        $url = add_query_arg( 'autoplay', 1, $url );
218    }
219
220    if (
221        isset( $args['loop'] ) && '1' === $args['loop'] // Parsed from the embedded URL.
222        || $attr['loop']                                // Parsed from shortcode arguments.
223        || in_array( 'loop', $atts, true )              // Catch the argument passed without a value.
224    ) {
225        $url = add_query_arg( 'loop', 1, $url );
226    }
227
228    if (
229        class_exists( 'Jetpack_AMP_Support' )
230        && Jetpack_AMP_Support::is_amp_request()
231    ) {
232        $html = sprintf(
233            '<amp-vimeo data-videoid="%1$s" layout="responsive" width="%2$d" height="%3$d"></amp-vimeo>',
234            esc_attr( $attr['id'] ),
235            absint( $width ),
236            absint( $height )
237        );
238    } else {
239        $html = sprintf(
240            '<div class="embed-vimeo" style="text-align: center;"><iframe src="%1$s" width="%2$u" height="%3$u" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe></div>',
241            esc_url( $url ),
242            esc_attr( $width ),
243            esc_attr( $height )
244        );
245    }
246
247    /**
248     * Filter the Vimeo player HTML.
249     *
250     * @module shortcodes
251     *
252     * @since 1.2.3
253     *
254     * @param string $html Embedded Vimeo player HTML.
255     */
256    $html = apply_filters( 'video_embed_html', $html );
257
258    return $html;
259}
260add_shortcode( 'vimeo', 'vimeo_shortcode' );
261
262/**
263 * Callback to modify output of embedded Vimeo video using Jetpack's shortcode.
264 *
265 * @since 3.9
266 * @deprecated since 13.8
267 *
268 * @param array $matches Regex partial matches against the URL passed.
269 * @param array $attr    Attributes received in embed response.
270 * @param array $url     Requested URL to be embedded.
271 *
272 * @return string Return output of Vimeo shortcode with the proper markup.
273 */
274function wpcom_vimeo_embed_url( $matches, $attr, $url ) {
275    _deprecated_function( __FUNCTION__, 'jetpack-13.8' );
276    $vimeo_info = array( $url );
277
278    // If we are able to extract a video ID, use it in the shortcode instead of the full URL.
279    if ( ! empty( $matches['video_id'] ) ) {
280        $vimeo_info = array( 'id' => $matches['video_id'] );
281    }
282
283    return vimeo_shortcode( $vimeo_info );
284}
285
286/**
287 * For bare URLs on their own line of the form.
288 *
289 * Accepted formats:
290 * https://vimeo.com/289091934/cd1f466bcc
291 * https://vimeo.com/album/2838732/video/6342264
292 * https://vimeo.com/6342264
293 * http://player.vimeo.com/video/18427511
294 *
295 * @since 3.9
296 * @deprecated since 13.8
297 *
298 * @uses wpcom_vimeo_embed_url
299 */
300function wpcom_vimeo_embed_url_init() {
301    _deprecated_function( __FUNCTION__, 'jetpack-13.8' );
302    wp_embed_register_handler( 'wpcom_vimeo_embed_url', '#https?://(?:[^/]+\.)?vimeo\.com/(?:album/(?<album_id>\d+)/)?(?:video/)?(?<video_id>\d+)(?:/.*)?$#i', 'wpcom_vimeo_embed_url' );
303}
304
305/**
306 * Transform a Vimeo embed iFrame into a Vimeo shortcode.
307 *
308 * @param string $content Post content.
309 */
310function vimeo_embed_to_shortcode( $content ) {
311    if ( ! is_string( $content ) || false === stripos( $content, 'player.vimeo.com/video/' ) ) {
312        return $content;
313    }
314
315    $regexp     = '!<iframe\s+src=[\'"](https?:)?//player\.vimeo\.com/video/(\d+)[\w=&;?]*[\'"]((?:\s+\w+=[\'"][^\'"]*[\'"])*)((?:[\s\w]*))></iframe>!i';
316    $regexp_ent = str_replace( '&amp;#0*58;', '&amp;#0*58;|&#0*58;', htmlspecialchars( $regexp, ENT_NOQUOTES ) );
317
318    foreach ( compact( 'regexp', 'regexp_ent' ) as $reg => $regexp ) {
319        if ( ! preg_match_all( $regexp, $content, $matches, PREG_SET_ORDER ) ) {
320            continue;
321        }
322
323        foreach ( $matches as $match ) {
324            $id = (int) $match[2];
325
326            $params = $match[3];
327
328            if ( 'regexp_ent' === $reg ) {
329                $params = html_entity_decode( $params, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 );
330            }
331
332            $params = wp_kses_hair( $params, array( 'http' ) );
333
334            $width  = isset( $params['width'] ) ? (int) $params['width']['value'] : 0;
335            $height = isset( $params['height'] ) ? (int) $params['height']['value'] : 0;
336
337            $wh = '';
338            if ( $width && $height ) {
339                $wh = ' w=' . $width . ' h=' . $height;
340            }
341
342            $shortcode = '[vimeo ' . $id . $wh . ']';
343            $content   = str_replace( $match[0], $shortcode, $content );
344        }
345    }
346
347    return $content;
348}
349
350if ( jetpack_shortcodes_should_hook_pre_kses() ) {
351    add_filter( 'pre_kses', 'vimeo_embed_to_shortcode' );
352}
353
354/**
355 * Replaces shortcodes and plain-text URLs to Vimeo videos with Vimeo embeds.
356 * Covers shortcode usage [vimeo 1234] | [vimeo https://vimeo.com/1234] | [vimeo http://vimeo.com/1234]
357 * Or plain text URLs https://vimeo.com/1234 | vimeo.com/1234 | //vimeo.com/1234
358 * Links are left intact.
359 *
360 * @since 3.7.0
361 * @since 3.9.5 One regular expression matches shortcodes and plain URLs.
362 *
363 * @param string $content HTML content.
364 *
365 * @return string The content with embeds instead of URLs
366 */
367function vimeo_link( $content ) {
368    /**
369     *  [vimeo 12345]
370     *  [vimeo http://vimeo.com/12345]
371     */
372    $shortcode = '(?:\[vimeo\s+[^0-9]*)([0-9]+)(?:\])';
373
374    /**
375     * Regex to look for a Vimeo link.
376     *
377     * - http://vimeo.com/12345
378     * - https://vimeo.com/12345
379     * - //vimeo.com/12345
380     * - vimeo.com/some/descender/12345
381     *
382     *  Should not capture inside HTML attributes
383     *  [Not] <a href="vimeo.com/12345">Cool Video</a>
384     *  [Not] <a href="https://vimeo.com/12345">vimeo.com/12345</a>
385     *
386     *  Could erroneously capture:
387     *  <a href="some.link/maybe/even/vimeo">This video (vimeo.com/12345) is teh cat's meow!</a>
388     */
389    $plain_url = "(?:[^'\">]?\/?(?:https?:\/\/)?vimeo\.com\/(?:groups\/\d+\/videos\/|album\/\d+\/video\/|video\/|channels\/[^\/]+\/videos\/|[^\/]+\/)?)([0-9]+)(?:[^'\"0-9<]|$)";
390
391    return jetpack_preg_replace_callback_outside_tags(
392        sprintf( '#%s|%s#i', $shortcode, $plain_url ),
393        'vimeo_link_callback',
394        $content,
395        'vimeo'
396    );
397}
398
399/**
400 * Callback function for the regex that replaces Vimeo URLs with Vimeo embeds.
401 *
402 * @since 3.7.0
403 *
404 * @param array $matches An array containing a Vimeo URL.
405 * @return string The Vimeo HTML embed code.
406 */
407function vimeo_link_callback( $matches ) {
408    $id = isset( $matches[2] ) ? $matches[2] : $matches[1];
409    if ( isset( $id ) && ctype_digit( $id ) ) {
410        return "\n" . vimeo_shortcode( array( 'id' => $id ) ) . "\n";
411    }
412    return $matches[0];
413}
414
415if (
416    ! is_admin()
417    /** This filter is documented in modules/shortcodes/youtube.php */
418    && apply_filters( 'jetpack_comments_allow_oembed', true )
419    // No need for this on WordPress.com, this is done for multiple shortcodes at a time there.
420    && ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM )
421) {
422    /*
423     * We attach wp_kses_post to comment_text in default-filters.php with priority of 10 anyway,
424     * so the iframe gets filtered out.
425     * Higher priority because we need it before auto-link and autop get to it
426     */
427    add_filter( 'comment_text', 'vimeo_link', 1 );
428}