Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 98
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
VideoPress_Video
0.00% covered (danger)
0.00%
0 / 96
0.00% covered (danger)
0.00%
0 / 4
2450
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 54
0.00% covered (danger)
0.00%
0 / 1
1122
 calculate_expiration
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 hostname
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_data
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
132
1<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2
3if ( ! defined( 'ABSPATH' ) ) {
4    exit( 0 );
5}
6
7/**
8 * VideoPress video object retrieved from VideoPress servers and parsed.
9 *
10 * @since 1.3
11 */
12class VideoPress_Video {
13    /**
14     * VideoPress version.
15     *
16     * @var int
17     */
18    public $version = 3;
19
20    /**
21     * Manifest version returned by remote service.
22     *
23     * @var string
24     * @since 1.3
25     */
26    const manifest_version = '1.5'; // phpcs:ignore Generic.NamingConventions.UpperCaseConstantName.ClassConstantNotUpperCase
27
28    /**
29     * Expiration of the video expressed in Unix time
30     *
31     * @var int
32     * @since 1.3
33     */
34    public $expires;
35
36    /**
37     * VideoPress unique identifier
38     *
39     * @var string
40     * @since 1.3
41     */
42    public $guid;
43
44    /**
45     * WordPress.com blog identifier
46     *
47     * @var int
48     * @since 1.5
49     */
50    public $blog_id;
51
52    /**
53     * Remote blog attachment identifier
54     *
55     * @var int
56     * @since 1.5
57     */
58    public $post_id;
59
60    /**
61     * Maximum desired width.
62     *
63     * @var int
64     * @since 1.3
65     */
66    public $maxwidth;
67
68    /**
69     * Video width calculated based on original video dimensions and the requested maxwidth
70     *
71     * @var int
72     * @since 1.3
73     */
74    public $calculated_width;
75
76    /**
77     * Video height calculated based on original video dimensions and the requested maxwidth
78     *
79     * @var int
80     * @since 1.3
81     */
82    public $calculated_height;
83
84    /**
85     * Video title
86     *
87     * @var string
88     * @since 1.3
89     */
90    public $title;
91
92    /**
93     * Video description
94     *
95     * @var string
96     * @since 4.4
97     */
98    public $description;
99
100    /**
101     * Directionality of title text. ltr or rtl
102     *
103     * @var string
104     * @since 1.3
105     */
106    public $text_direction;
107
108    /**
109     * Text and audio language as ISO 639-2 language code
110     *
111     * @var string
112     * @since 1.3
113     */
114    public $language;
115
116    /**
117     * Video duration in whole seconds
118     *
119     * @var int
120     * @since 1.3
121     */
122    public $duration;
123
124    /**
125     * Recommended minimum age of the viewer.
126     *
127     * @var int
128     * @since 1.3
129     */
130    public $age_rating;
131
132    /**
133     * Video author has restricted video embedding or sharing
134     *
135     * @var bool
136     * @since 1.3
137     */
138    public $restricted_embed;
139
140    /**
141     * Poster frame image URI for the given video guid and calculated dimensions.
142     *
143     * @var string
144     * @since 1.3
145     */
146    public $poster_frame_uri;
147
148    /**
149     * Video files associated with the given guid for the calculated dimensions.
150     *
151     * @var stdClass
152     * @since 1.3
153     */
154    public $videos;
155
156    /**
157     * Video player information
158     *
159     * @var stdClass
160     * @since 1.3
161     */
162    public $players;
163
164    /**
165     * Video player skinning preferences including background color and watermark
166     *
167     * @var array
168     * @since 1.5
169     */
170    public $skin;
171
172    /**
173     * Closed captions if available for the given video. Associative array of ISO 639-2 language code and a WebVTT URI
174     *
175     * @var array
176     * @since 1.5
177     */
178    public $captions;
179
180    /**
181     * Error data.
182     *
183     * @var mixed
184     */
185    public $error;
186
187    /**
188     * Setup the object.
189     * Request video information from VideoPress servers and process the response.
190     *
191     * @since 1.3
192     * @param string $guid VideoPress unique identifier.
193     * @param int    $maxwidth maximum requested video width. final width and height are calculated on VideoPress servers based on the aspect ratio of the original video upload.
194     */
195    public function __construct( $guid, $maxwidth = 640 ) {
196        $this->guid = $guid;
197
198        $maxwidth = absint( $maxwidth );
199        if ( $maxwidth > 0 ) {
200            $this->maxwidth = $maxwidth;
201        }
202
203        $data = $this->get_data();
204        if ( is_wp_error( $data ) || empty( $data ) ) {
205            /** This filter is documented in modules/videopress/class.videopress-player.php */
206            if ( ! apply_filters( 'jetpack_videopress_use_legacy_player', false ) ) {
207                // Unlike the Flash player, the new player does it's own error checking, age gate, etc.
208                $data = (object) array(
209                    'guid'   => $guid,
210                    'width'  => $maxwidth,
211                    'height' => $maxwidth / 16 * 9,
212                );
213            } else {
214                $this->error = $data;
215                return;
216            }
217        }
218
219        if ( isset( $data->blog_id ) ) {
220            $this->blog_id = absint( $data->blog_id );
221        }
222
223        if ( isset( $data->post_id ) ) {
224            $this->post_id = absint( $data->post_id );
225        }
226
227        if ( isset( $data->title ) && $data->title !== '' ) {
228            $this->title = trim( str_replace( '&nbsp;', ' ', $data->title ) );
229        }
230
231        if ( isset( $data->description ) && $data->description !== '' ) {
232            $this->description = trim( $data->description );
233        }
234
235        if ( isset( $data->text_direction ) && $data->text_direction === 'rtl' ) {
236            $this->text_direction = 'rtl';
237        } else {
238            $this->text_direction = 'ltr';
239        }
240
241        if ( isset( $data->language ) ) {
242            $this->language = $data->language;
243        }
244
245        if ( isset( $data->duration ) && $data->duration > 0 ) {
246            $this->duration = absint( $data->duration );
247        }
248
249        if ( isset( $data->width ) && $data->width > 0 ) {
250            $this->calculated_width = absint( $data->width );
251        }
252
253        if ( isset( $data->height ) && $data->height > 0 ) {
254            $this->calculated_height = absint( $data->height );
255        }
256
257        if ( isset( $data->age_rating ) ) {
258            $this->age_rating = absint( $this->age_rating );
259        }
260
261        if ( isset( $data->restricted_embed ) && $data->restricted_embed === true ) {
262            $this->restricted_embed = true;
263        } else {
264            $this->restricted_embed = false;
265        }
266
267        if ( isset( $data->posterframe ) && $data->posterframe !== '' ) {
268            $this->poster_frame_uri = esc_url_raw( $data->posterframe, array( 'http', 'https' ) );
269        }
270
271        if ( isset( $data->mp4 ) || isset( $data->ogv ) ) {
272            $this->videos = new stdClass();
273            if ( isset( $data->mp4 ) ) {
274                $this->videos->mp4 = $data->mp4;
275            }
276            if ( isset( $data->ogv ) ) {
277                $this->videos->ogv = $data->ogv;
278            }
279        }
280
281        if ( isset( $data->swf ) ) {
282            if ( ! isset( $this->players ) ) {
283                $this->players = new stdClass();
284            }
285            $this->players->swf = $data->swf;
286        }
287
288        if ( isset( $data->skin ) ) {
289            $this->skin = $data->skin;
290        }
291
292        if ( isset( $data->captions ) ) {
293            $this->captions = (array) $data->captions;
294        }
295    }
296
297    /**
298     * Convert an Expires HTTP header value into Unix time for use in WP Cache.
299     *
300     * @since 1.3
301     * @param string $expires_header Expires header value.
302     * @return int|bool Unix time or false
303     */
304    public static function calculate_expiration( $expires_header ) {
305        if ( empty( $expires_header ) || ! is_string( $expires_header ) ) {
306            return false;
307        }
308
309        $expires_date = DateTime::createFromFormat( 'D, d M Y H:i:s T', $expires_header, new DateTimeZone( 'UTC' ) );
310        if ( $expires_date instanceof DateTime ) {
311            return date_format( $expires_date, 'U' );
312        }
313        return false;
314    }
315
316    /**
317     * Extract the site's host domain for statistics and comparison against an allowed site list in the case of restricted embeds.
318     *
319     * @since 1.2
320     * @param string $url absolute URL.
321     * @return bool|string host component of the URL, or false if none found.
322     */
323    public static function hostname( $url ) {
324        return wp_parse_url( esc_url_raw( $url ), PHP_URL_HOST );
325    }
326
327    /**
328     * Request data from WordPress.com for the given guid, maxwidth, and calculated blog hostname.
329     *
330     * @since 1.3
331     * @return stdClass|WP_Error parsed JSON response or WP_Error if request unsuccessful
332     */
333    private function get_data() {
334        global $wp_version;
335
336        $domain         = self::hostname( home_url() );
337        $request_params = array(
338            'guid'   => $this->guid,
339            'domain' => $domain,
340        );
341        if ( isset( $this->maxwidth ) && $this->maxwidth > 0 ) {
342            $request_params['maxwidth'] = $this->maxwidth;
343        }
344
345        $url = 'https://v.wordpress.com/data/wordpress.json';
346
347        $response = wp_remote_get(
348            add_query_arg( $request_params, $url ),
349            array(
350                'redirection' => 1,
351                'user-agent'  => 'VideoPress plugin ' . $this->version . '; WordPress ' . $wp_version . ' (' . home_url( '/' ) . ')',
352            )
353        );
354
355        unset( $request_params );
356        unset( $url );
357        $response_body = wp_remote_retrieve_body( $response );
358        $response_code = absint( wp_remote_retrieve_response_code( $response ) );
359
360        if ( is_wp_error( $response ) ) {
361            return $response;
362        } elseif ( $response_code === 400 ) {
363            return new WP_Error( 'bad_config', __( 'The VideoPress plugin could not communicate with the VideoPress servers. This error is most likely caused by a misconfigured plugin. Please reinstall or upgrade.', 'jetpack' ) );
364        } elseif ( $response_code === 403 ) {
365            /* translators: %s URL of site trying to embed a VideoPress video */
366            return new WP_Error( 'http_forbidden', '<p>' . sprintf( __( '<strong>%s</strong> is not an allowed embed site.', 'jetpack' ), esc_html( $domain ) ) . '</p><p>' . __( 'Publisher limits playback of video embeds.', 'jetpack' ) . '</p>' );
367        } elseif ( $response_code === 404 ) {
368            /* translators: %s VideoPress object identifier */
369            return new WP_Error( 'http_not_found', '<p>' . sprintf( __( 'No data found for VideoPress identifier: <strong>%s</strong>.', 'jetpack' ), $this->guid ) . '</p>' );
370        } elseif ( $response_code !== 200 || empty( $response_body ) ) {
371            return;
372        } else {
373            $expires_header = wp_remote_retrieve_header( $response, 'Expires' );
374            if ( ! empty( $expires_header ) ) {
375                $expires = self::calculate_expiration( $expires_header );
376                if ( ! empty( $expires ) ) {
377                    $this->expires = $expires;
378                }
379            }
380            return json_decode( $response_body );
381        }
382    }
383}