PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.2-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.2-a.1
16.2-a.3 16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / jetpack_vendor / automattic / jetpack-videopress / src / class-access-control.php
jetpack / jetpack_vendor / automattic / jetpack-videopress / src Last commit date
js 2 months ago tus 1 month ago videopress-divi 2 months ago videopress-divi-5 2 months ago class-access-control.php 6 days ago class-admin-ui.php 2 weeks ago class-ajax.php 1 month ago class-attachment-handler.php 1 month ago class-block-editor-content.php 1 month ago class-block-editor-extensions.php 2 weeks ago class-block-replacement.php 9 months ago class-caption-tracks.php 1 month ago class-data.php 1 month ago class-divi.php 2 months ago class-initial-state.php 2 weeks ago class-initializer.php 6 days ago class-jwt-token-bridge.php 2 years ago class-module-control.php 3 years ago class-options.php 2 years ago class-package-version.php 6 days ago class-plan.php 2 years ago class-rest-controller.php 1 month ago class-site.php 3 years ago class-stats.php 1 year ago class-status.php 9 months ago class-upload-exception.php 3 years ago class-uploader-rest-endpoints.php 2 weeks ago class-uploader.php 1 month ago class-utils.php 3 months ago class-video-block-email-renderer.php 2 months ago class-videopress-rest-api-v1-features.php 6 months ago class-videopress-rest-api-v1-settings.php 1 month ago class-videopress-rest-api-v1-site.php 2 weeks ago class-videopress-rest-api-v1-stats.php 3 years ago class-videopresstoken.php 1 month ago class-wpcom-rest-api-v2-attachment-field-videopress.php 9 months ago class-wpcom-rest-api-v2-attachment-videopress-data.php 1 month ago class-wpcom-rest-api-v2-endpoint-videopress-caption-tracks.php 1 month ago class-wpcom-rest-api-v2-endpoint-videopress.php 2 weeks ago class-xmlrpc.php 1 month ago utility-functions.php 4 weeks ago
class-access-control.php
647 lines
1 <?php
2 /**
3 * VideoPress Access Control.
4 *
5 * @package automattic/jetpack-videopress
6 */
7
8 namespace Automattic\Jetpack\VideoPress;
9
10 use Automattic\Jetpack\Extensions\Premium_Content\Subscription_Service\Abstract_Token_Subscription_Service;
11 use Automattic\Jetpack\Modules;
12 use VIDEOPRESS_PRIVACY;
13 use WP_Post;
14
15 /**
16 * VideoPress video access control utilities.
17 *
18 * Note: this is also being used on WordPress.com.
19 * Use IS_WPCOM checks for functionality that is specific to WPCOM/Jetpack.
20 */
21 class Access_Control {
22
23 /**
24 * Singleton Access_Control instance.
25 *
26 * @var Access_Control
27 **/
28 private static $instance = null;
29
30 /**
31 * Guid to subscription plan, store, for when used inline on a page.
32 *
33 * @var array
34 */
35 private $guids_to_subscriptions = array();
36
37 /**
38 * Set that this guid is controlled by a subscription.
39 *
40 * @param string $guid The guid to set.
41 * @param string|int $subscription_id The subscription to set.
42 *
43 * @return Access_Control
44 */
45 public function set_guid_subscription( $guid, $subscription_id ) {
46 $this->guids_to_subscriptions[ $guid ] = $subscription_id;
47 return $this;
48 }
49
50 /**
51 * Get the subscription for a guid.
52 *
53 * @param string $guid The guid to get.
54 *
55 * @return string|int|false
56 */
57 public function get_subscription_plan_id( $guid ) {
58 return $this->guids_to_subscriptions[ $guid ] ?? false;
59 }
60
61 /**
62 * Get the singleton instance.
63 *
64 * @return self
65 */
66 public static function instance() {
67 if ( null === self::$instance ) {
68 self::$instance = new self();
69 }
70
71 return self::$instance;
72 }
73
74 /**
75 * Determines if Jetpack Memberships are available.
76 *
77 * @return bool
78 */
79 private function jetpack_memberships_available() {
80 return class_exists( '\Jetpack_Memberships' );
81 }
82
83 /**
84 * Determines if Jetpack Subscriptions are available.
85 *
86 * @return bool
87 */
88 private function jetpack_subscriptions_available() {
89 $is_module_active = ( new Modules() )->is_active( 'subscriptions' );
90 if ( ! $is_module_active ) {
91 return false;
92 }
93
94 if ( function_exists( '\Automattic\Jetpack\Extensions\Premium_Content\subscription_service' ) ) {
95 return true;
96 }
97
98 if ( ! defined( 'JETPACK__PLUGIN_DIR' ) ) {
99 return false;
100 }
101
102 $subscription_service_file_path = JETPACK__PLUGIN_DIR . 'extensions/blocks/premium-content/_inc/subscription-service/include.php';
103 if ( ! file_exists( $subscription_service_file_path ) ) {
104 return false;
105 }
106
107 require_once $subscription_service_file_path;
108
109 return function_exists( '\Automattic\Jetpack\Extensions\Premium_Content\subscription_service' );
110 }
111
112 /**
113 * Check default user access. By default, subscribers or higher can view videos.
114 *
115 * @param WP_Post $post_to_check The post to check.
116 *
117 * @return bool
118 **/
119 private function get_default_user_capability_for_post( $post_to_check ) {
120 if ( ! isset( $post_to_check->ID ) ) {
121 return false;
122 }
123
124 $default_auth = current_user_can( 'read_post', $post_to_check->ID );
125
126 return $default_auth;
127 }
128
129 /**
130 * Determines if the current user can access restricted content and builds the restriction_details array.
131 *
132 * @param string $guid the video guid.
133 * @param int $embedded_post_id the post id.
134 * @param int $selected_plan_id the selected plan id if applicable.
135 *
136 * @return array
137 */
138 private function build_restriction_details( $guid, $embedded_post_id, $selected_plan_id ) {
139 $post_to_check = get_post( $embedded_post_id );
140
141 if ( empty( $post_to_check ) ) {
142 $restriction_details = $this->default_video_restriction_details( false );
143 return $this->filter_video_restriction_details( $restriction_details, $guid, $embedded_post_id, $selected_plan_id );
144 }
145
146 $default_auth = $this->get_default_user_capability_for_post( $post_to_check );
147 $restriction_details = $this->default_video_restriction_details( $default_auth );
148
149 if ( $this->jetpack_memberships_available() ) {
150 $post_access_level = \Jetpack_Memberships::get_post_access_level( $embedded_post_id );
151 if ( 'everybody' !== $post_access_level ) {
152 $memberships_can_view_post = \Jetpack_Memberships::user_can_view_post( $embedded_post_id );
153 $restriction_details = $this->get_subscriber_only_restriction_details( $default_auth );
154 $restriction_details['can_access'] = $memberships_can_view_post;
155 }
156 }
157
158 return $this->check_block_level_access(
159 $restriction_details,
160 $guid,
161 $embedded_post_id,
162 $selected_plan_id
163 );
164 }
165
166 /**
167 * Determines if the current user can access restricted block content and updates the restriction_details array.
168 *
169 * @param array $restriction_details the restriction details array.
170 * @param string $guid the video guid.
171 * @param int $embedded_post_id the post id.
172 * @param int $selected_plan_id the selected plan id if applicable.
173 *
174 * @return array
175 */
176 private function check_block_level_access( $restriction_details, $guid, $embedded_post_id, $selected_plan_id ) {
177 if ( $this->jetpack_subscriptions_available() && $selected_plan_id > 0 ) {
178 $restriction_details = $this->get_subscriber_only_restriction_details( $restriction_details['can_access'] );
179 $paywall = \Automattic\Jetpack\Extensions\Premium_Content\subscription_service();
180
181 // Only paid subscribers should be granted access to the premium content.
182 $access_level = '';
183 if ( class_exists( Abstract_Token_Subscription_Service::class ) ) {
184 $access_level = Abstract_Token_Subscription_Service::POST_ACCESS_LEVEL_PAID_SUBSCRIBERS;
185 }
186
187 $can_view = $paywall->visitor_can_view_content( array( $selected_plan_id ), $access_level );
188 $restriction_details['can_access'] = $can_view || current_user_can( 'edit_post', $embedded_post_id ); // Editors can always view the content.
189 }
190
191 return $this->filter_video_restriction_details(
192 $restriction_details,
193 $guid,
194 $embedded_post_id,
195 $selected_plan_id
196 );
197 }
198
199 /**
200 * Returns the default restriction_details for a video.
201 *
202 * @param bool $default_can_access The default auth.
203 *
204 * @return array
205 **/
206 private function get_subscriber_only_restriction_details( $default_can_access = false ) {
207 return array(
208 'provider' => 'jetpack_memberships',
209 'title' => __( 'This video is subscriber-only', 'jetpack-videopress-pkg' ),
210 'unauthorized_message' => __( 'You need to be subscribed to view this video', 'jetpack-videopress-pkg' ),
211 'can_access' => $default_can_access,
212 );
213 }
214
215 /**
216 * Filters restriction details.
217 *
218 * @param array $video_restriction_details The restriction details.
219 * @param string $guid The video guid.
220 * @param int $embedded_post_id The post id.
221 * @param int $selected_plan_id The selected plan id if applicable.
222 *
223 * @return array
224 */
225 private function filter_video_restriction_details( $video_restriction_details, $guid, $embedded_post_id, $selected_plan_id ) {
226 /**
227 * Filters the video restriction details.
228 *
229 * @param array $video_restriction_details The restriction details.
230 * @param string $guid The video guid.
231 * @param int $embedded_post_id The post id.
232 * @param int $selected_plan_id The selected plan id if applicable.
233 *
234 * @return array
235 */
236 return (array) apply_filters( 'videopress_video_restriction_details', $video_restriction_details, $guid, $embedded_post_id, $selected_plan_id );
237 }
238
239 /**
240 * Returns the default restriction_details for a video.
241 *
242 * @param bool $default_can_access The default auth.
243 *
244 * @return array
245 **/
246 private function default_video_restriction_details( $default_can_access = false ) {
247 $restriction_details = array(
248 'version' => '1',
249 'provider' => 'auth',
250 'title' => __( 'Unauthorized', 'jetpack-videopress-pkg' ),
251 'unauthorized_message' => __( 'Unauthorized', 'jetpack-videopress-pkg' ),
252 'can_access' => $default_can_access,
253 );
254
255 return $restriction_details;
256 }
257
258 /**
259 * Determines whether a given post actually embeds a given VideoPress GUID.
260 *
261 * Used to prevent the embedded post id — which arrives from request input — from being
262 * treated as an authorization context when it has no relationship to the requested video.
263 * Matching the attachment id itself is not treated as proof of embedding: attachment ids
264 * are enumerable via the media REST route and would otherwise provide a second path around
265 * this check whenever the attachment has no parent and falls back to the `read` capability.
266 *
267 * @param int $embedded_post_id The post id claimed as the embedding context.
268 * @param string $guid The video guid.
269 *
270 * @return bool
271 */
272 private function post_embeds_videopress_guid( $embedded_post_id, $guid ) {
273 $post = get_post( $embedded_post_id );
274 if ( ! $post instanceof WP_Post || empty( $post->post_content ) ) {
275 return false;
276 }
277
278 // If the guid is nowhere in the content, neither the block nor the shortcode scan can match.
279 if ( false === strpos( $post->post_content, $guid ) ) {
280 return false;
281 }
282
283 if ( $this->post_content_has_videopress_block( $post->post_content, $guid ) ) {
284 return true;
285 }
286
287 if ( $this->post_content_has_videopress_shortcode( $post->post_content, $guid ) ) {
288 return true;
289 }
290
291 return $this->post_content_has_videopress_url( $post->post_content, $guid );
292 }
293
294 /**
295 * Walk parsed blocks (including inner blocks) looking for a videopress/video block
296 * whose guid attribute matches.
297 *
298 * @param string $post_content The post content to scan.
299 * @param string $guid The video guid to match.
300 *
301 * @return bool
302 */
303 private function post_content_has_videopress_block( $post_content, $guid ) {
304 if ( false === strpos( $post_content, 'wp:videopress/video' ) ) {
305 return false;
306 }
307
308 return $this->blocks_contain_videopress_guid( parse_blocks( $post_content ), $guid );
309 }
310
311 /**
312 * Recursively scans a parsed block tree for a videopress/video block whose guid attribute matches.
313 *
314 * @param array $blocks Parsed blocks (as returned by parse_blocks() or an innerBlocks array).
315 * @param string $guid The video guid to match.
316 *
317 * @return bool
318 */
319 private function blocks_contain_videopress_guid( $blocks, $guid ) {
320 foreach ( $blocks as $block ) {
321 if (
322 isset( $block['blockName'] ) && 'videopress/video' === $block['blockName']
323 && isset( $block['attrs']['guid'] ) && $block['attrs']['guid'] === $guid
324 ) {
325 return true;
326 }
327
328 if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] )
329 && $this->blocks_contain_videopress_guid( $block['innerBlocks'], $guid )
330 ) {
331 return true;
332 }
333 }
334
335 return false;
336 }
337
338 /**
339 * Detect a [videopress GUID] or [wpvideo GUID] shortcode whose first positional
340 * argument matches the given guid.
341 *
342 * @param string $post_content The post content to scan.
343 * @param string $guid The video guid to match.
344 *
345 * @return bool
346 */
347 private function post_content_has_videopress_shortcode( $post_content, $guid ) {
348 if ( false === stripos( $post_content, '[videopress' ) && false === stripos( $post_content, '[wpvideo' ) ) {
349 return false;
350 }
351
352 $pattern = get_shortcode_regex( array( 'videopress', 'wpvideo' ) );
353 $count = preg_match_all( '/' . $pattern . '/', $post_content, $matches, PREG_SET_ORDER );
354 if ( false === $count || 0 === $count ) {
355 return false;
356 }
357
358 foreach ( $matches as $match ) {
359 $atts = shortcode_parse_atts( $match[3] );
360 if ( ! is_array( $atts ) ) {
361 continue;
362 }
363
364 // Only the positional argument identifies the video; named attributes must not satisfy the binding check.
365 if ( isset( $atts[0] ) && is_string( $atts[0] ) && $atts[0] === $guid ) {
366 return true;
367 }
368 }
369
370 return false;
371 }
372
373 /**
374 * Detect a canonical VideoPress URL referencing the given guid. Covers oEmbed
375 * inserts, core/embed blocks, core/video blocks, and core [video] shortcodes
376 * whose src/mp4 attributes resolve to a VideoPress URL.
377 *
378 * @param string $post_content The post content to scan.
379 * @param string $guid The video guid to match.
380 *
381 * @return bool
382 */
383 private function post_content_has_videopress_url( $post_content, $guid ) {
384 if ( ! preg_match_all( '#https?://[^\s"\'<>)]+#i', $post_content, $matches ) ) {
385 return false;
386 }
387
388 foreach ( $matches[0] as $url ) {
389 $extracted_guid = Utils::extract_videopress_guid_from_url( $url );
390 if ( $extracted_guid === $guid ) {
391 return true;
392 }
393 }
394
395 return false;
396 }
397
398 /**
399 * Build and cache the list of VideoPress GUIDs present in a post.
400 *
401 * Scans the post content for VideoPress blocks (including those in synced patterns),
402 * shortcodes, and URLs, then caches the GUID list in a transient for fast lookup
403 * during authorization checks.
404 *
405 * @param int $post_id The post ID to scan.
406 * @return array Array of VideoPress GUIDs found in the post.
407 */
408 public static function build_and_cache_post_guids( $post_id ) {
409 if ( empty( $post_id ) ) {
410 return array();
411 }
412
413 $post_id = absint( $post_id );
414 $transient_key = "videopress_guids_{$post_id}";
415
416 // Check if already cached.
417 $cached_guids = get_transient( $transient_key );
418 if ( false !== $cached_guids ) {
419 return (array) $cached_guids;
420 }
421
422 $post = get_post( $post_id );
423 if ( ! $post instanceof WP_Post || empty( $post->post_content ) ) {
424 set_transient( $transient_key, array(), 12 * HOUR_IN_SECONDS );
425 return array();
426 }
427
428 $guids = array();
429
430 // Scan for VideoPress blocks (including those in synced patterns).
431 $guids = array_merge( $guids, self::collect_guids_from_blocks( parse_blocks( $post->post_content ) ) );
432
433 // Scan for VideoPress shortcodes and URLs (legacy embedding methods).
434 if ( preg_match_all( '#https?://[^\s"\'<>)]+#i', $post->post_content, $matches ) ) {
435 foreach ( $matches[0] as $url ) {
436 $guid = Utils::extract_videopress_guid_from_url( $url );
437 if ( $guid ) {
438 $guids[] = $guid;
439 }
440 }
441 }
442
443 // Scan for [videopress] and [wpvideo] shortcodes.
444 $pattern = get_shortcode_regex( array( 'videopress', 'wpvideo' ) );
445 $count = preg_match_all( '/' . $pattern . '/', $post->post_content, $matches, PREG_SET_ORDER );
446 if ( false !== $count && $count > 0 ) {
447 foreach ( $matches as $match ) {
448 $atts = shortcode_parse_atts( $match[3] );
449 if ( is_array( $atts ) && isset( $atts[0] ) && is_string( $atts[0] ) ) {
450 $guids[] = $atts[0];
451 }
452 }
453 }
454
455 // Cache for 12 hours and return unique GUIDs.
456 $unique_guids = array_unique( array_filter( $guids ) );
457 set_transient( $transient_key, $unique_guids, 12 * HOUR_IN_SECONDS );
458
459 return $unique_guids;
460 }
461
462 /**
463 * Recursively collect VideoPress GUIDs from parsed blocks.
464 *
465 * Handles both direct VideoPress blocks and those within synced patterns,
466 * which are automatically expanded by WordPress when parse_blocks() is called.
467 *
468 * @param array $blocks Array of parsed blocks.
469 * @return array Array of VideoPress GUIDs found.
470 */
471 private static function collect_guids_from_blocks( $blocks ) {
472 $guids = array();
473
474 foreach ( $blocks as $block ) {
475 // Check if this is a VideoPress block with a GUID.
476 if (
477 isset( $block['blockName'] ) && 'videopress/video' === $block['blockName']
478 && isset( $block['attrs']['guid'] ) && is_string( $block['attrs']['guid'] )
479 ) {
480 $guids[] = $block['attrs']['guid'];
481 }
482
483 // Recursively check inner blocks (including synced patterns which have been expanded).
484 if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
485 $guids = array_merge( $guids, self::collect_guids_from_blocks( $block['innerBlocks'] ) );
486 }
487 }
488
489 return $guids;
490 }
491
492 /**
493 * Check if a post contains a VideoPress GUID, using cached GUID list for fast lookup.
494 *
495 * Falls back to detailed scanning only if cache misses. This is much faster than
496 * the previous implementation which always scanned post_content.
497 *
498 * @param int $embedded_post_id The post id to check.
499 * @param string $guid The video guid to find.
500 *
501 * @return bool
502 */
503 private function post_embeds_videopress_guid_cached( $embedded_post_id, $guid ) {
504 if ( empty( $embedded_post_id ) || empty( $guid ) ) {
505 return false;
506 }
507
508 // Try fast lookup from cache.
509 $transient_key = "videopress_guids_{$embedded_post_id}";
510 $cached_guids = get_transient( $transient_key );
511
512 if ( false !== $cached_guids ) {
513 return in_array( $guid, (array) $cached_guids, true );
514 }
515
516 // Cache miss: build and cache the GUID list, then check.
517 $guids = self::build_and_cache_post_guids( $embedded_post_id );
518 return in_array( $guid, $guids, true );
519 }
520
521 /**
522 * Determines if the current user can view the provided video. Only ever gets fired if site-wide private videos are enabled.
523 *
524 * Filterable for 3rd party plugins.
525 *
526 * @param string $guid The video id being checked.
527 * @param int $embedded_post_id The post id the video is embedded in or 0.
528 * @param int $selected_plan_id The plan id the earn block this video is embedded in has.
529 */
530 public function is_current_user_authed_for_video( $guid, $embedded_post_id, $selected_plan_id = 0 ) {
531 if ( current_user_can( 'upload_files' ) ) {
532 return $this->filter_is_current_user_authed_for_video( true, $guid, $embedded_post_id );
533 }
534
535 $attachment = false;
536 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
537 $video_info = video_get_info_by_guid( $guid );
538 if ( ! empty( $video_info ) ) {
539 $attachment = get_blog_post( $video_info->blog_id, $video_info->post_id );
540 }
541 } else {
542 $attachment = videopress_get_post_by_guid( $guid );
543 }
544
545 if ( ! $attachment ) {
546 return false;
547 }
548
549 $video_info = video_get_info_by_blogpostid( get_current_blog_id(), $attachment->ID );
550 if ( null === $video_info->guid ) {
551 return false;
552 }
553
554 /*
555 * Default missing privacy_setting to SITE_DEFAULT to avoid an
556 * undefined-property warning and make the site-level fallback explicit.
557 */
558 $privacy_setting = $video_info->privacy_setting ?? VIDEOPRESS_PRIVACY::SITE_DEFAULT;
559
560 $embedded_post_id = (int) $embedded_post_id;
561 if (
562 $embedded_post_id
563 && VIDEOPRESS_PRIVACY::IS_PUBLIC !== $privacy_setting
564 && ! $this->post_embeds_videopress_guid_cached( $embedded_post_id, $guid )
565 ) {
566 $embedded_post_id = 0;
567 }
568
569 $is_user_authed = false;
570
571 // Determine if video is public, private or use site default.
572 switch ( $privacy_setting ) {
573 case VIDEOPRESS_PRIVACY::IS_PUBLIC:
574 $is_user_authed = true;
575 break;
576 case VIDEOPRESS_PRIVACY::IS_PRIVATE:
577 $restriction_details = $this->build_restriction_details( $guid, $embedded_post_id, $selected_plan_id );
578 $is_user_authed = $restriction_details['can_access'];
579 break;
580 case VIDEOPRESS_PRIVACY::SITE_DEFAULT:
581 default:
582 $is_videopress_private_for_site = Data::get_videopress_videos_private_for_site();
583 $is_user_authed = true;
584 if ( $is_videopress_private_for_site ) {
585 $restriction_details = $this->build_restriction_details( $guid, $embedded_post_id, $selected_plan_id );
586 $is_user_authed = $restriction_details['can_access'];
587 }
588 }
589
590 /**
591 * Overrides video view authorization for current user.
592 *
593 * Example of making all videos public:
594 *
595 * function jp_example_override_video_auth( $is_user_authed, $guid ) {
596 * return true
597 * };
598 * add_filter( 'videopress_is_current_user_authed_for_video', 'jp_example_override_video_auth', 10, 2 );
599 *
600 * @param bool $is_user_authed The current user authorization state.
601 * @param string $guid The video's unique identifier.
602 * @param int|null $embedded_post_id The post the video is embedded..
603 *
604 * @return bool
605 */
606 return $this->filter_is_current_user_authed_for_video( $is_user_authed, $guid, $embedded_post_id );
607 }
608
609 /**
610 * Overrides video view authorization for current user.
611 *
612 * @param bool $is_user_authed The current user authorization state.
613 * @param string $guid The video's unique identifier.
614 * @param int|null $embedded_post_id The post the video is embedded..
615 *
616 * @return bool
617 */
618 private function filter_is_current_user_authed_for_video( $is_user_authed, $guid, $embedded_post_id ) {
619 /**
620 * Overrides video view authorization for current user.
621 *
622 * Example of making all videos public:
623 *
624 * function jp_example_override_video_auth( $is_user_authed, $guid ) {
625 * return true
626 * };
627 * add_filter( 'videopress_is_current_user_authed_for_video', 'jp_example_override_video_auth', 10, 2 );
628 *
629 * @param bool $is_user_authed The current user authorization state.
630 * @param string $guid The video's unique identifier.
631 * @param int|null $embedded_post_id The post the video is embedded..
632 *
633 * @return bool
634 */
635 return (bool) apply_filters( 'videopress_is_current_user_authed_for_video', $is_user_authed, $guid, $embedded_post_id );
636 }
637
638 /**
639 * Returns the proper blog id depending on Jetpack or WP.com
640 *
641 * @return int the blog id
642 */
643 public function get_videopress_blog_id() {
644 return \Jetpack_Options::get_option( 'id' );
645 }
646 }
647