PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.3-beta
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 All 508 releases
jetpack / jetpack_vendor / automattic / jetpack-videopress / src / class-channel.php

class-channel.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-beta, at jetpack_vendor/automattic/jetpack-videopress/src/class-channel.php

278 lines 8.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * VideoPress Channel: a page for every video in the media library.
4 *
5 * A video is a VideoPress attachment; nothing has to be written around it.
6 * `/videopress?v=<guid>` renders the theme's `videopress-video` block template
7 * for that attachment, where a video block with `useQueriedVideo` plays it.
8 * Titles, posters and the rest of the video's metadata come from VideoPress
9 * at render time and are never stored on the blog.
10 *
11 * Enabled when the active theme declares `add_theme_support( 'videopress-channel' )`
12 * or the `videopress_channel_enabled` filter returns true.
13 *
14 * @package automattic/jetpack-videopress
15 */
16
17 namespace Automattic\Jetpack\VideoPress;
18
19 use WP;
20 use WP_Post;
21
22 /**
23 * VideoPress Channel feature.
24 */
25 class Channel {
26
27 const MIME_TYPE = 'video/videopress';
28 const ENDPOINT = 'videopress';
29 const QUERY_VAR = 'videopress_video';
30 const TEMPLATE = 'videopress-video';
31
32 /**
33 * Whether the feature has been wired up in this request.
34 *
35 * @var bool
36 */
37 private static $initialized = false;
38
39 /**
40 * Hook the feature up. Registration itself waits for `init`, when the
41 * active theme is known.
42 *
43 * @return void
44 */
45 public static function init() {
46 if ( self::$initialized ) {
47 return;
48 }
49 self::$initialized = true;
50
51 add_action( 'init', array( __CLASS__, 'register' ), 9 );
52 }
53
54 /**
55 * Whether the channel feature is enabled for this site.
56 *
57 * @return bool
58 */
59 public static function is_enabled() {
60 $enabled = function_exists( 'current_theme_supports' ) && current_theme_supports( 'videopress-channel' );
61
62 /**
63 * Filters whether the VideoPress Channel feature ( video pages ) is enabled.
64 *
65 * @since 0.55.0
66 *
67 * @param bool $enabled True when the active theme supports `videopress-channel`.
68 */
69 return (bool) apply_filters( 'videopress_channel_enabled', $enabled );
70 }
71
72 /**
73 * Register the video page routing and the block hooks.
74 *
75 * @return void
76 */
77 public static function register() {
78 if ( ! self::is_enabled() ) {
79 return;
80 }
81
82 add_filter( 'query_vars', array( __CLASS__, 'query_vars' ) );
83 add_action( 'parse_request', array( __CLASS__, 'route_video_page' ) );
84 add_filter( 'attachment_template_hierarchy', array( __CLASS__, 'template_hierarchy' ) );
85 add_filter( 'attachment_link', array( __CLASS__, 'attachment_link' ), 10, 2 );
86 add_filter( 'videopress_playlist_entry_url', array( __CLASS__, 'playlist_entry_url' ), 10, 2 );
87 add_filter( 'render_block_data', array( __CLASS__, 'queried_video_block' ) );
88 }
89
90 /**
91 * The VideoPress GUID of an attachment, or an empty string.
92 *
93 * Jetpack sites store it in attachment meta; WordPress.com Simple keeps the
94 * uploaded file's attachment ( still `video/mp4` ) and maps it in the
95 * platform's videos table.
96 *
97 * @param int|WP_Post $attachment The attachment.
98 * @return string
99 */
100 public static function guid( $attachment ) {
101 $post = get_post( $attachment );
102 if ( ! $post instanceof WP_Post || 'attachment' !== $post->post_type || 0 !== strpos( (string) $post->post_mime_type, 'video/' ) ) {
103 return '';
104 }
105 $guid = (string) get_post_meta( $post->ID, 'videopress_guid', true );
106 if ( '' === $guid ) {
107 $meta = wp_get_attachment_metadata( $post->ID );
108 $guid = isset( $meta['videopress']['guid'] ) ? (string) $meta['videopress']['guid'] : '';
109 }
110 if ( '' === $guid && function_exists( 'video_get_info_by_blogpostid' ) ) {
111 // WordPress.com's videos table; the package shims this from post meta elsewhere.
112 $info = video_get_info_by_blogpostid( get_current_blog_id(), $post->ID );
113 $guid = is_object( $info ) && ! empty( $info->guid ) ? (string) $info->guid : '';
114 }
115 return preg_match( '/^[a-zA-Z0-9]{8}$/', $guid ) ? $guid : '';
116 }
117
118 /**
119 * The attachment a VideoPress GUID belongs to on this site, or 0.
120 *
121 * @param string $guid The GUID.
122 * @return int
123 */
124 public static function attachment_id( $guid ) {
125 if ( ! is_string( $guid ) || ! preg_match( '/^[a-zA-Z0-9]{8}$/', $guid ) ) {
126 return 0;
127 }
128 $id = function_exists( 'videopress_get_post_id_by_guid' ) ? videopress_get_post_id_by_guid( $guid ) : false;
129 if ( ! is_int( $id ) && function_exists( 'video_get_info_by_guid' ) ) {
130 $info = video_get_info_by_guid( $guid );
131 if ( is_object( $info ) && ! empty( $info->post_id ) && (int) $info->blog_id === get_current_blog_id() ) {
132 $id = (int) $info->post_id;
133 }
134 }
135 return is_int( $id ) && $id > 0 ? $id : 0;
136 }
137
138 /**
139 * The channel page of a video: `/videopress?v=<guid>`.
140 *
141 * @param int|WP_Post $attachment The attachment.
142 * @return string Empty when the attachment is not a VideoPress video.
143 */
144 public static function video_url( $attachment ) {
145 $guid = self::guid( $attachment );
146 return '' === $guid ? '' : add_query_arg( 'v', $guid, user_trailingslashit( home_url( '/' . self::ENDPOINT ) ) );
147 }
148
149 /**
150 * Public query vars: `v` selects the video, `videopress_video` marks the page.
151 *
152 * @param string[] $vars Query vars.
153 * @return string[]
154 */
155 public static function query_vars( $vars ) {
156 $vars[] = 'v';
157 $vars[] = self::QUERY_VAR;
158 return $vars;
159 }
160
161 /**
162 * `/videopress?v=<guid>` becomes the attachment's request, so the main
163 * query, the template context and the post blocks all see the video.
164 * An unknown GUID 404s.
165 *
166 * @param WP $wp The request.
167 * @return void
168 */
169 public static function route_video_page( $wp ) {
170 $path = trim( (string) $wp->request, '/' );
171 if ( self::ENDPOINT !== $path && empty( $wp->query_vars[ self::QUERY_VAR ] ) ) {
172 return;
173 }
174 $guid = isset( $_GET['v'] ) ? sanitize_text_field( wp_unslash( $_GET['v'] ) ) : ( $wp->query_vars['v'] ?? '' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- public URL.
175 $id = self::attachment_id( $guid );
176 if ( ! $id ) {
177 $wp->query_vars = array( 'error' => '404' );
178 return;
179 }
180 $wp->query_vars = array(
181 'attachment_id' => $id,
182 'post_type' => 'attachment',
183 self::QUERY_VAR => 1,
184 'v' => $guid,
185 );
186 }
187
188 /**
189 * Video pages render the theme's `videopress-video` block template.
190 *
191 * @param string[] $templates Template hierarchy.
192 * @return string[]
193 */
194 public static function template_hierarchy( $templates ) {
195 if ( get_query_var( self::QUERY_VAR ) ) {
196 array_unshift( $templates, self::TEMPLATE . '.php' );
197 }
198 return $templates;
199 }
200
201 /**
202 * VideoPress attachments link to their channel page.
203 *
204 * @param string $link Attachment permalink.
205 * @param int $post_id The attachment.
206 * @return string
207 */
208 public static function attachment_link( $link, $post_id ) {
209 $url = self::video_url( $post_id );
210 return '' === $url ? $link : $url;
211 }
212
213 /**
214 * Playlist entries that open a video link to its channel page when the
215 * video belongs to this site.
216 *
217 * @param string $url The entry URL ( videopress.com by default ).
218 * @param string $guid The video GUID.
219 * @return string
220 */
221 public static function playlist_entry_url( $url, $guid ) {
222 $id = self::attachment_id( $guid );
223 return $id ? self::video_url( $id ) : $url;
224 }
225
226 /**
227 * The video a block refers to: the Query Loop entry when it is a
228 * VideoPress attachment, else the queried attachment.
229 *
230 * @param int $context_post_id The block's postId context, if any.
231 * @return WP_Post|null
232 */
233 public static function current_video( $context_post_id = 0 ) {
234 $candidates = array( (int) $context_post_id, (int) get_the_ID() );
235 $queried = get_queried_object();
236 if ( $queried instanceof WP_Post ) {
237 $candidates[] = (int) $queried->ID;
238 }
239 foreach ( $candidates as $id ) {
240 $post = $id ? get_post( $id ) : null;
241 if ( $post instanceof WP_Post && '' !== self::guid( $post ) ) {
242 return $post;
243 }
244 }
245 return null;
246 }
247
248 /**
249 * A video block with `useQueriedVideo` plays the current video. Its title
250 * and poster are read from VideoPress at render time, not from the blog.
251 *
252 * @param array $parsed_block The block being rendered.
253 * @return array
254 */
255 public static function queried_video_block( $parsed_block ) {
256 if ( ! isset( $parsed_block['blockName'] ) || 'videopress/video' !== $parsed_block['blockName'] || empty( $parsed_block['attrs']['useQueriedVideo'] ) ) {
257 return $parsed_block;
258 }
259 $post = self::current_video();
260 $guid = $post instanceof WP_Post ? self::guid( $post ) : '';
261 if ( ! $post instanceof WP_Post || '' === $guid ) {
262 $parsed_block['attrs']['guid'] = '';
263 return $parsed_block;
264 }
265 $details = function_exists( 'videopress_get_video_details' ) ? videopress_get_video_details( $guid ) : null;
266 $details = is_object( $details ) && ! is_wp_error( $details ) ? $details : null;
267
268 $parsed_block['attrs']['guid'] = $guid;
269 $parsed_block['attrs']['id'] = $post->ID;
270 $parsed_block['attrs']['src'] = 'https://videopress.com/v/' . $guid;
271 $parsed_block['attrs']['title'] = $details && ! empty( $details->title ) ? (string) $details->title : get_the_title( $post );
272 if ( $details && ! empty( $details->poster ) ) {
273 $parsed_block['attrs']['poster'] = (string) $details->poster;
274 }
275 return $parsed_block;
276 }
277 }
278