Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
50.33% covered (warning)
50.33%
151 / 300
22.22% covered (danger)
22.22%
6 / 27
CRAP
n/a
0 / 0
videopress_is_valid_guid
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
5.93
videopress_is_valid_preload
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
videopress_get_video_details
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
3.00
videopress_download_poster_image
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
create_local_media_library_for_videopress_guid
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
20
videopress_cleanup_media_library
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
20
videopress_cdn_file_url
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
videopress_get_transcoding_status
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
videopress_build_url
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
videopress_create_new_media_item
74.29% covered (warning)
74.29%
26 / 35
0.00% covered (danger)
0.00%
0 / 1
11.70
videopress_merge_file_status
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
72
videopress_is_finished_processing
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
3.03
videopress_update_meta_data
61.54% covered (warning)
61.54%
8 / 13
0.00% covered (danger)
0.00%
0 / 1
6.42
videopress_is_attachment_without_guid
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
is_videopress_attachment
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
videopress_make_video_get_path
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
videopress_make_media_upload_path
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
videopress_make_resumable_upload_path
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
video_get_info_by_blogpostid
100.00% covered (success)
100.00%
34 / 34
100.00% covered (success)
100.00%
1 / 1
9
video_format_done
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
30
video_image_url_by_guid
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
5.39
videopress_get_post_by_guid
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
videopress_get_post_id_by_guid
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
1 / 1
3
videopress_get_attachment_url
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
jetpack_videopress_flash_embed_filter
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
12
videopress_is_valid_video_rating
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
wp_startswith
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
30
VIDEOPRESS_PRIVACY
n/a
0 / 0
n/a
0 / 0
0
n/a
0 / 0
1<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2
3use Automattic\Jetpack\Connection\Client;
4
5if ( ! defined( 'ABSPATH' ) ) {
6    exit( 0 );
7}
8
9defined( 'VIDEOPRESS_MIN_WIDTH' ) || define( 'VIDEOPRESS_MIN_WIDTH', 60 );
10defined( 'VIDEOPRESS_DEFAULT_WIDTH' ) || define( 'VIDEOPRESS_DEFAULT_WIDTH', 640 );
11
12// phpcs:disable Universal.Files.SeparateFunctionsFromOO.Mixed -- TODO: Move classes to appropriately-named class files.
13
14/**
15 * VideoPress Privacy constants.
16 */
17abstract class VIDEOPRESS_PRIVACY {
18    const IS_PUBLIC    = 0;
19    const IS_PRIVATE   = 1;
20    const SITE_DEFAULT = 2;
21}
22
23/**
24 * Validate user-supplied guid values against expected inputs
25 *
26 * @since 1.1
27 * @param string $guid video identifier.
28 * @return bool true if passes validation test
29 */
30function videopress_is_valid_guid( $guid ) {
31    if ( ! empty( $guid ) && is_string( $guid ) && strlen( $guid ) === 8 && ctype_alnum( $guid ) ) {
32        return true;
33    }
34    return false;
35}
36
37/**
38 * Validates user-supplied video preload setting.
39 *
40 * @param mixed $value the preload value to validate.
41 * @return bool
42 */
43function videopress_is_valid_preload( $value ) {
44    return in_array( strtolower( $value ), array( 'auto', 'metadata', 'none' ), true );
45}
46
47/**
48 * Get details about a specific video by GUID:
49 *
50 * @param string $guid Video GUID.
51 * @return object
52 */
53function videopress_get_video_details( $guid ) {
54    if ( ! videopress_is_valid_guid( $guid ) ) {
55        return new WP_Error( 'bad-guid-format', __( 'Invalid Video GUID!', 'jetpack-videopress-pkg' ) );
56    }
57
58    $version   = '1.1';
59    $endpoint  = sprintf( '/videos/%1$s', $guid );
60    $query_url = sprintf(
61        'https://public-api.wordpress.com/rest/v%1$s%2$s',
62        $version,
63        $endpoint
64    );
65
66    // Look for data in our transient. If nothing, let's make a new query.
67    $data_from_cache = get_transient( 'jetpack_videopress_' . $guid );
68    if ( false === $data_from_cache ) {
69        $response = wp_remote_get( esc_url_raw( $query_url ) );
70        $data     = json_decode( wp_remote_retrieve_body( $response ) );
71
72        // Cache the response for an hour.
73        set_transient( 'jetpack_videopress_' . $guid, $data, HOUR_IN_SECONDS );
74    } else {
75        $data = $data_from_cache;
76    }
77
78    /**
79     * Allow functions to modify fetched video details.
80     *
81     * This filter allows third-party code to modify the return data
82     * about a given video.  It may involve swapping some data out or
83     * adding new parameters.
84     *
85     * @since 4.0.0
86     *
87     * @param object $data The data returned by the WPCOM API. See: https://developer.wordpress.com/docs/api/1.1/get/videos/%24guid/
88     * @param string $guid The GUID of the VideoPress video in question.
89     */
90    return apply_filters( 'videopress_get_video_details', $data, $guid );
91}
92
93/**
94 * Similar to `media_sideload_image` -- but returns an ID.
95 *
96 * @param string $url Image URL.
97 * @param int    $attachment_id Post ID.
98 *
99 * @return int|mixed|object|WP_Error
100 */
101function videopress_download_poster_image( $url, $attachment_id ) {
102    // Set variables for storage, fix file filename for query strings.
103    preg_match( '/[^\?]+\.(jpe?g|jpe|gif|png)\b/i', $url, $matches );
104    if ( ! $matches ) {
105        return new WP_Error( 'image_sideload_failed', __( 'Invalid image URL', 'jetpack-videopress-pkg' ) );
106    }
107
108    $file_array             = array();
109    $file_array['name']     = basename( $matches[0] );
110    $file_array['tmp_name'] = download_url( $url );
111
112    // If error storing temporarily, return the error.
113    if ( is_wp_error( $file_array['tmp_name'] ) ) {
114        return $file_array['tmp_name'];
115    }
116
117    // Do the validation and storage stuff.
118    $thumbnail_id = media_handle_sideload( $file_array, $attachment_id, null );
119
120    // Flag it as poster image, so we can exclude it from display.
121    update_post_meta( $thumbnail_id, 'videopress_poster_image', 1 );
122
123    return $thumbnail_id;
124}
125
126/**
127 * Creates a local media library item of a remote VideoPress video.
128 *
129 * @param string $guid Video GUID.
130 * @param int    $parent_id Parent post ID.
131 *
132 * @return int|object
133 */
134function create_local_media_library_for_videopress_guid( $guid, $parent_id = 0 ) {
135    $vp_data = videopress_get_video_details( $guid );
136    if ( ! $vp_data || is_wp_error( $vp_data ) ) {
137        return $vp_data;
138    }
139
140    $args = array(
141        'post_date'      => $vp_data->upload_date,
142        'post_title'     => wp_kses( $vp_data->title, array() ),
143        'post_content'   => wp_kses( $vp_data->description, array() ),
144        'post_mime_type' => 'video/videopress',
145        'guid'           => sprintf( 'https://videopress.com/v/%s', $guid ),
146    );
147
148    $attachment_id = wp_insert_attachment( $args, null, $parent_id );
149
150    if ( ! is_wp_error( $attachment_id ) ) {
151        update_post_meta( $attachment_id, 'videopress_guid', $guid );
152        wp_update_attachment_metadata(
153            $attachment_id,
154            array(
155                'width'  => $vp_data->width,
156                'height' => $vp_data->height,
157            )
158        );
159
160        $thumbnail_id = videopress_download_poster_image( $vp_data->poster, $attachment_id );
161        update_post_meta( $attachment_id, '_thumbnail_id', $thumbnail_id );
162    }
163
164    return $attachment_id;
165}
166
167/**
168 * Helper that will look for VideoPress media items that are more than 30 minutes old,
169 * that have not had anything attached to them by a wpcom upload and deletes the ghost
170 * attachment.
171 *
172 * These happen primarily because of failed upload attempts.
173 *
174 * @return int The number of items that were cleaned up.
175 */
176function videopress_cleanup_media_library() {
177    // phpcs:disable Squiz.PHP.NonExecutableCode.Unreachable -- Function is disabled currently.
178    // Disable this job for now.
179    return 0;
180    $query_args = array(
181        'post_type'      => 'attachment',
182        'post_status'    => 'inherit',
183        'post_mime_type' => 'video/videopress',
184        'meta_query'     => array(
185            array(
186                'key'   => 'videopress_status',
187                'value' => 'new',
188            ),
189        ),
190    );
191
192    $query = new WP_Query( $query_args );
193
194    $cleaned = 0;
195
196    $now = current_time( 'timestamp' ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested -- Probably should refactor, but this function is disabled.
197
198    if ( $query->have_posts() ) {
199        foreach ( $query->posts as $post ) {
200            $post_time = strtotime( $post->post_date_gmt );
201
202            // If the post is older than 30 minutes, it is safe to delete it.
203            if ( $now - $post_time > MINUTE_IN_SECONDS * 30 ) {
204                // Force delete the attachment, because we don't want it appearing in the trash.
205                wp_delete_attachment( $post->ID, true );
206
207                ++$cleaned;
208            }
209        }
210    }
211
212    return $cleaned;
213    // phpcs:enable Squiz.PHP.NonExecutableCode.Unreachable
214}
215
216/**
217 * Return an absolute URI for a given filename and guid on the CDN.
218 * No check is performed to ensure the guid exists or the file is present. Simple centralized string builder.
219 *
220 * @param string $guid     VideoPress identifier.
221 * @param string $filename name of file associated with the guid (video file name or thumbnail file name).
222 *
223 * @return string Absolute URL of VideoPress file for the given guid.
224 */
225function videopress_cdn_file_url( $guid, $filename ) {
226    return "https://videos.files.wordpress.com/{$guid}/{$filename}";
227}
228
229/**
230 * Get an array of the transcoding status for the given video post.
231 *
232 * @since 4.4
233 * @param int $post_id Post ID.
234 * @return array|bool Returns an array of statuses if this is a VideoPress post, otherwise it returns false.
235 */
236function videopress_get_transcoding_status( $post_id ) {
237    $meta = wp_get_attachment_metadata( $post_id );
238
239    // If this has not been processed by videopress, we can skip the rest.
240    if ( ! $meta || ! isset( $meta['file_statuses'] ) ) {
241        return false;
242    }
243
244    $info = (object) $meta['file_statuses'];
245
246    $status = array(
247        'std_mp4' => $info->mp4 ?? null,
248        'std_ogg' => $info->ogg ?? null,
249        'dvd_mp4' => $info->dvd ?? null,
250        'hd_mp4'  => $info->hd ?? null,
251    );
252
253    return $status;
254}
255
256/**
257 * Get the direct url to the video.
258 *
259 * @since 4.4
260 * @param string $guid VideoPress GUID.
261 * @return string
262 */
263function videopress_build_url( $guid ) {
264
265    // No guid, no videopress url.
266    if ( ! $guid ) {
267        return '';
268    }
269
270    return 'https://videopress.com/v/' . $guid;
271}
272
273/**
274 * Create an empty videopress media item that will be filled out later by an xmlrpc
275 * callback from the VideoPress servers.
276 *
277 * @since 4.4
278 * @since 0.58.0 Accepts a minimum attachment ID supplied by VideoPress.
279 * @param string      $title The post_title.
280 * @param string|null $guid Optional VideoPress guid.
281 * @param int         $minimum_attachment_id Optional lowest attachment ID allowed by VideoPress.
282 * @return int|WP_Error
283 */
284function videopress_create_new_media_item( $title, $guid = null, $minimum_attachment_id = 0 ) {
285    global $wpdb;
286
287    $minimum_attachment_id = filter_var(
288        $minimum_attachment_id,
289        FILTER_VALIDATE_INT,
290        array(
291            'options' => array(
292                'min_range' => 0,
293                'max_range' => PHP_INT_MAX - 1,
294            ),
295        )
296    );
297    if ( false === $minimum_attachment_id ) {
298        return new WP_Error( 'videopress_invalid_minimum_attachment_id', __( 'Unable to allocate a video attachment ID.', 'jetpack-videopress-pkg' ) );
299    }
300
301    $post = array(
302        'post_type'      => 'attachment',
303        'post_mime_type' => 'video/videopress',
304        'post_title'     => $title,
305        'post_content'   => '',
306        'guid'           => videopress_build_url( $guid ),
307    );
308
309    if ( $minimum_attachment_id > 1 ) {
310        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Allocation needs the current highest ID across all post types and statuses.
311        $maximum = $wpdb->get_var( "SELECT COALESCE(MAX(ID), 0) FROM $wpdb->posts" );
312        if ( null === $maximum || $wpdb->last_error ) {
313            return new WP_Error( 'videopress_attachment_id_lookup_failed', __( 'Unable to allocate a video attachment ID.', 'jetpack-videopress-pkg' ) );
314        }
315        if ( (int) $maximum < $minimum_attachment_id ) {
316            $post['import_id'] = $minimum_attachment_id;
317        }
318    }
319
320    $media_id = wp_insert_post( $post, true );
321    if ( is_wp_error( $media_id ) ) {
322        return $media_id;
323    }
324    // import_id is only a suggestion; verify the ID WordPress actually allocated.
325    if ( ! $media_id || $media_id < $minimum_attachment_id ) {
326        if ( $media_id ) {
327            wp_delete_attachment( $media_id, true ); // This call's new placeholder has no VideoPress binding yet.
328        }
329        return new WP_Error( 'videopress_attachment_id_allocation_failed', __( 'Unable to allocate a video attachment ID.', 'jetpack-videopress-pkg' ) );
330    }
331
332    add_post_meta( $media_id, 'videopress_status', 'initiated' );
333
334    add_post_meta( $media_id, 'videopress_guid', $guid );
335
336    return $media_id;
337}
338
339/**
340 * Merge VideoPress file status metadata.
341 *
342 * @param array $current_status The current status of the video.
343 * @param array $new_meta The new meta data to merge with the current status.
344 * @return array
345 */
346function videopress_merge_file_status( $current_status, $new_meta ) {
347    $new_statuses = array();
348
349    if ( isset( $new_meta['videopress']['files_status']['hd'] ) ) {
350        $new_statuses['hd'] = $new_meta['videopress']['files_status']['hd'];
351    }
352
353    if ( isset( $new_meta['videopress']['files_status']['dvd'] ) ) {
354        $new_statuses['dvd'] = $new_meta['videopress']['files_status']['dvd'];
355    }
356
357    if ( isset( $new_meta['videopress']['files_status']['std']['mp4'] ) ) {
358        $new_statuses['mp4'] = $new_meta['videopress']['files_status']['std']['mp4'];
359    }
360
361    if ( isset( $new_meta['videopress']['files_status']['std']['ogg'] ) ) {
362        $new_statuses['ogg'] = $new_meta['videopress']['files_status']['std']['ogg'];
363    }
364
365    foreach ( $new_statuses as $format => $status ) {
366        if ( ! isset( $current_status[ $format ] ) ) {
367            $current_status[ $format ] = $status;
368            continue;
369        }
370
371        if ( $current_status[ $format ] !== 'DONE' ) {
372            $current_status[ $format ] = $status;
373        }
374    }
375
376    return $current_status;
377}
378
379/**
380 * Check to see if a video has completed processing.
381 *
382 * @since 4.4
383 * @param int $post_id Post ID.
384 * @return bool
385 */
386function videopress_is_finished_processing( $post_id ) {
387    $post = get_post( $post_id );
388
389    if ( is_wp_error( $post ) ) {
390        return false;
391    }
392
393    $meta = wp_get_attachment_metadata( $post->ID );
394    if ( ! isset( $meta['videopress']['finished'] ) ) {
395        return false;
396    }
397
398    return $meta['videopress']['finished'];
399}
400
401/**
402 * Update the meta information  status for the given video post.
403 *
404 * @since 4.4
405 * @param int $post_id Post ID.
406 * @return bool
407 */
408function videopress_update_meta_data( $post_id ) {
409
410    $meta = wp_get_attachment_metadata( $post_id );
411
412    // If this has not been processed by VideoPress, we can skip the rest.
413    if ( ! $meta || ! isset( $meta['videopress'] ) ) {
414        return false;
415    }
416
417    $info = (object) $meta['videopress'];
418
419    // Without a guid there is no video to query for.
420    if ( ! isset( $info->guid ) ) {
421        return false;
422    }
423
424    $result = Client::wpcom_json_api_request_as_blog( 'videos/' . $info->guid );
425
426    if ( is_wp_error( $result ) ) {
427        return false;
428    }
429
430    $response = json_decode( $result['body'], true );
431
432    // Update the attachment metadata.
433    $meta['videopress'] = $response;
434
435    wp_update_attachment_metadata( $post_id, $meta );
436
437    return true;
438}
439
440/**
441 * Check to see if this is a VideoPress post that hasn't had a guid set yet.
442 *
443 * @param int $post_id Post ID.
444 * @return bool
445 */
446function videopress_is_attachment_without_guid( $post_id ) {
447    $post = get_post( $post_id );
448
449    if ( is_wp_error( $post ) ) {
450        return false;
451    }
452
453    if ( $post->post_mime_type !== 'video/videopress' ) {
454        return false;
455    }
456
457    $videopress_guid = get_post_meta( $post_id, 'videopress_guid', true );
458
459    if ( $videopress_guid ) {
460        return false;
461    }
462
463    return true;
464}
465
466/**
467 * Check to see if this is a VideoPress attachment.
468 *
469 * @param int $post_id Post ID.
470 * @return bool
471 */
472function is_videopress_attachment( $post_id ) {
473    $post = get_post( $post_id );
474
475    if ( ! $post instanceof WP_Post ) {
476        return false;
477    }
478
479    if ( $post->post_mime_type !== 'video/videopress' ) {
480        return false;
481    }
482
483    return true;
484}
485
486/**
487 * Get the video update path
488 *
489 * @since 4.4
490 * @param string $guid VideoPress GUID.
491 * @return string
492 */
493function videopress_make_video_get_path( $guid ) {
494    return sprintf(
495        '%s/rest/v%s/videos/%s',
496        JETPACK__WPCOM_JSON_API_BASE,
497        Client::WPCOM_JSON_API_VERSION,
498        $guid
499    );
500}
501
502/**
503 * Get the upload api path.
504 *
505 * @since 4.4
506 * @param int $blog_id The id of the blog we're uploading to.
507 * @return string
508 */
509function videopress_make_media_upload_path( $blog_id ) {
510    return sprintf(
511        'https://public-api.wordpress.com/rest/v1.1/sites/%s/media/new?locale=%s',
512        $blog_id,
513        get_locale()
514    );
515}
516
517/**
518 * Get the resumable upload api path.
519 *
520 * @since 4.4
521 * @param int $blog_id The id of the blog we're uploading to.
522 * @return string
523 */
524function videopress_make_resumable_upload_path( $blog_id ) {
525    return sprintf(
526        'https://public-api.wordpress.com/rest/v1.1/video-uploads/%s/',
527        $blog_id
528    );
529}
530
531/**
532 * This is a mock of the internal VideoPress method, which is meant to duplicate the functionality
533 * of the WPCOM API, so that the Jetpack REST API returns the same data with no modifications.
534 *
535 * @param int  $blog_id    Blog ID.
536 * @param int  $post_id    Post ID.
537 * @param bool $cache_bust Ignored. The real WPCOM function caches reads for 12 hours and this
538 *                         param forces a fresh read; the mock reads local postmeta directly.
539 *                         Mirrored here so IS_WPCOM call sites can pass it without the shared
540 *                         signature diverging from the real one.
541 * @return stdClass
542 */
543function video_get_info_by_blogpostid( $blog_id, $post_id, $cache_bust = false ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- see docblock.
544    $post = get_post( $post_id );
545
546    $video_info                  = new stdClass();
547    $video_info->post_id         = 0;
548    $video_info->description     = '';
549    $video_info->title           = '';
550    $video_info->caption         = '';
551    $video_info->blog_id         = $blog_id;
552    $video_info->guid            = null;
553    $video_info->finish_date_gmt = '0000-00-00 00:00:00';
554    $video_info->rating          = null;
555    $video_info->privacy_setting = VIDEOPRESS_PRIVACY::SITE_DEFAULT;
556
557    if ( ! $post ) {
558        return $video_info;
559    }
560
561    $video_info->post_id     = $post_id;
562    $video_info->description = $post->post_content;
563    $video_info->title       = $post->post_title;
564    $video_info->caption     = $post->post_excerpt;
565
566    if ( 'video/videopress' !== $post->post_mime_type ) {
567        return $video_info;
568    }
569
570    // Since this is a VideoPress post, lt's fill out the rest of the object.
571    $video_info->guid = get_post_meta( $post_id, 'videopress_guid', true );
572    $meta             = wp_get_attachment_metadata( $post_id );
573
574    if ( $meta && isset( $meta['videopress'] ) ) {
575        $videopress_meta             = $meta['videopress'];
576        $video_info->rating          = $videopress_meta['rating'] ?? null;
577        $video_info->allow_download  = $videopress_meta['allow_download'] ?? 0;
578        $video_info->display_embed   = $videopress_meta['display_embed'] ?? 0;
579        $video_info->privacy_setting = ! isset( $videopress_meta['privacy_setting'] ) ? VIDEOPRESS_PRIVACY::SITE_DEFAULT : $videopress_meta['privacy_setting'];
580
581        if ( ! empty( $videopress_meta['finished'] ) ) {
582            $video_info->finish_date_gmt = gmdate( 'Y-m-d H:i:s', (int) $videopress_meta['finished'] );
583        }
584    }
585
586    /** Make sure we are keeping some meta keys updated for filtering purposes */
587    if ( get_post_meta( $post_id, 'videopress_rating', true ) !== $video_info->rating ) {
588        update_post_meta( $post_id, 'videopress_rating', $video_info->rating );
589    }
590    if ( get_post_meta( $post_id, 'videopress_privacy_setting', true ) !== $video_info->privacy_setting ) {
591        update_post_meta( $post_id, 'videopress_privacy_setting', $video_info->privacy_setting );
592    }
593
594    return $video_info;
595}
596
597/**
598 * Check that a VideoPress video format has finished processing.
599 *
600 * This uses the info object, because that is what the WPCOM endpoint
601 * uses, however we don't have a complete info object in the same way
602 * WPCOM does, so we pull the meta information out of the post
603 * options instead.
604 *
605 * Note: This mimics the WPCOM function of the same name and helps the media
606 * API endpoint add all needed VideoPress data.
607 *
608 * @param stdClass $info Info object.
609 * @param string   $format Video format.
610 * @return bool
611 */
612function video_format_done( $info, $format ) {
613
614    // Avoids notice when a non-videopress item is found.
615    if ( ! is_object( $info ) ) {
616        return false;
617    }
618
619    $post_id = $info->post_id;
620
621    if ( get_post_mime_type( $post_id ) !== 'video/videopress' ) {
622        return false;
623    }
624
625    $post = get_post( $post_id );
626
627    if ( is_wp_error( $post ) ) {
628        return false;
629    }
630
631    $meta = wp_get_attachment_metadata( $post->ID );
632
633    $video_format = str_replace( array( 'fmt_', 'fmt1_' ), '', $format );
634
635    if ( 'ogg' === $video_format ) {
636        return isset( $meta['videopress']['files']['std']['ogg'] );
637    } else {
638        return isset( $meta['videopress']['files'][ $video_format ]['mp4'] );
639    }
640}
641
642/**
643 * Get the image URL for the given VideoPress GUID
644 *
645 * We look up by GUID, because that is what WPCOM does and this needs to be
646 * parameter compatible with that.
647 *
648 * Note: This mimics the WPCOM function of the same name and helps the media
649 * API endpoint add all needed VideoPress data.
650 *
651 * @param string $guid VideoPress GUID.
652 * @param string $format Video format.
653 * @return string|null The poster image URL, or null if it cannot be resolved.
654 */
655function video_image_url_by_guid( $guid, $format ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
656
657    $post = videopress_get_post_by_guid( $guid );
658
659    if ( is_wp_error( $post ) || ! $post ) {
660        return null;
661    }
662
663    $meta = wp_get_attachment_metadata( $post->ID );
664
665    if ( ! is_array( $meta ) || ! isset( $meta['videopress']['poster'] ) ) {
666        return null;
667    }
668
669    $poster = apply_filters( 'jetpack_photon_url', $meta['videopress']['poster'] );
670
671    return $poster;
672}
673
674/**
675 * Using a GUID, find a post.
676 *
677 * @param string $guid The post guid.
678 * @return WP_Post|false The post for that guid, or false if none is found.
679 */
680function videopress_get_post_by_guid( $guid ) {
681    $cache_key   = 'get_post_by_guid_' . $guid;
682    $cache_group = 'videopress';
683    $cached_post = wp_cache_get( $cache_key, $cache_group );
684
685    if ( is_object( $cached_post ) && 'WP_Post' === get_class( $cached_post ) ) {
686        return $cached_post;
687    }
688
689    $post_id = videopress_get_post_id_by_guid( $guid );
690
691    if ( is_int( $post_id ) ) {
692        $post = get_post( $post_id );
693        wp_cache_set( $cache_key, $post, $cache_group, HOUR_IN_SECONDS );
694
695        return $post;
696    }
697
698    return false;
699}
700
701/**
702 * Using a GUID, find the associated post ID.
703 *
704 * @since 8.4.0
705 * @param string $guid The guid to look for the post ID of.
706 * @return int|false The post ID for that guid, or false if none is found.
707 */
708function videopress_get_post_id_by_guid( $guid ) {
709    $cache_key = 'videopress_get_post_id_by_guid_' . $guid;
710    $cached_id = get_transient( $cache_key );
711
712    if ( is_int( $cached_id ) ) {
713        return $cached_id;
714    }
715
716    $args = array(
717        'post_type'              => 'attachment',
718        'post_mime_type'         => 'video/videopress',
719        'post_status'            => 'inherit',
720        'no_found_rows'          => true,
721        'fields'                 => 'ids',
722        // A GUID identifies the same attachment regardless of the current language.
723        'suppress_filters'       => true,
724        // Prevent WPML Media Translation from re-enabling attachment query filters.
725        'force_suppress_filters' => true,
726        'meta_query'             => array(
727            array(
728                'key'     => 'videopress_guid',
729                'value'   => $guid,
730                'compare' => '=',
731            ),
732        ),
733    );
734
735    $query = new WP_Query( $args );
736
737    if ( $query->have_posts() ) {
738        $post_id = $query->next_post();
739        set_transient( $cache_key, $post_id, HOUR_IN_SECONDS );
740
741        return $post_id;
742    }
743
744    return false;
745}
746
747/**
748 * From the given VideoPress post_id, return back the appropriate attachment URL.
749 *
750 * When the MP4 hasn't been processed yet or this is not a VideoPress video, this will return null.
751 *
752 * @param int $post_id Post ID of the attachment.
753 * @return string|null
754 */
755function videopress_get_attachment_url( $post_id ) {
756
757    // We only handle VideoPress attachments.
758    if ( get_post_mime_type( $post_id ) !== 'video/videopress' ) {
759        return null;
760    }
761
762    $meta = wp_get_attachment_metadata( $post_id );
763
764    // As of Jetpack 10.3 transcoded video files are reserved for the VideoPress player.
765    // All other video file requests will receive the originally uploaded file, stored on the wpcom cdn.
766    if ( ! isset( $meta['videopress']['original'] ) ) {
767        // Use the original file as the url if it isn't transcoded yet.
768        if ( isset( $meta['original'] ) ) {
769            $return = $meta['original'];
770        } else {
771            // Otherwise, there isn't much we can do.
772            return null;
773        }
774    } else {
775        $return = $meta['videopress']['original'];
776    }
777
778    // If the URL is a string, return it. Otherwise, we shouldn't to avoid errors downstream, so null.
779    return ( is_string( $return ) ) ? $return : null;
780}
781
782/**
783 * Converts VideoPress flash embeds into oEmbed-able URLs.
784 *
785 * Older VideoPress embed depended on Flash, which no longer work,
786 * so let us convert them to an URL that WordPress can oEmbed.
787 *
788 * Note that this file is always loaded via modules/module-extras.php and is not dependent on module status.
789 *
790 * @param string $content the content.
791 * @return string filtered content
792 */
793function jetpack_videopress_flash_embed_filter( $content ) {
794    // This receives data from the `the_content` filter, which unfortunately sometimes has bad content passed along as a param.
795    if ( ! is_string( $content ) ) {
796        return $content;
797    }
798    $regex   = '%<embed[^>]*+>(?:\s*</embed>)?%i';
799    $content = preg_replace_callback(
800        $regex,
801        function ( $matches ) {
802            $embed_code  = $matches[0];
803            $url_matches = array();
804
805            // get video ID from flash URL.
806            $url_matched = preg_match( '/src="http:\/\/v.wordpress.com\/([^"]+)"/', $embed_code, $url_matches );
807
808            if ( $url_matched ) {
809                $video_id = $url_matches[1];
810                return "https://videopress.com/v/$video_id";
811            }
812        },
813        $content
814    );
815    return $content;
816}
817
818/**
819 * Checks if the provided rating string is a valid VideoPress video rating value.
820 *
821 * @param mixed $rating The video rating to validate.
822 * @return bool
823 */
824function videopress_is_valid_video_rating( $rating ) {
825    return in_array( $rating, array( 'G', 'PG-13', 'R-17' ), true );
826}
827
828add_filter( 'the_content', 'jetpack_videopress_flash_embed_filter', 7 ); // Needs to be priority 7 to allow Core to oEmbed.
829
830if ( ! function_exists( 'wp_startswith' ) ) :
831    /**
832     * Check whether a string starts with a specific substring.
833     *
834     * @param string $haystack String we are filtering.
835     * @param string $needle The substring we are looking for.
836     * @return bool
837     */
838    function wp_startswith( $haystack, $needle ) {
839        if ( ! $haystack || ! $needle || ! is_scalar( $haystack ) || ! is_scalar( $needle ) ) {
840            return false;
841        }
842
843        $haystack = (string) $haystack;
844        $needle   = (string) $needle;
845
846        return str_starts_with( $haystack, $needle );
847    }
848endif;