PluginProbe ʕ •ᴥ•ʔ
Presto Player / 4.3.3
Presto Player v4.3.3
4.3.3 4.3.2 4.3.1 4.3.0 4.2.4 4.2.3 4.2.2 4.2.0 4.2.1 trunk 1.10.0 1.10.1 1.10.2 1.11.0 1.12.0 1.13.0 1.14.0 1.14.1 1.5.10 1.5.11 1.5.12 1.5.13 1.5.14 1.5.15 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.10 1.6.11 1.6.12 1.6.13 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.8.0 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.11 2.0.12 2.0.13 2.0.14 2.0.15 2.0.16 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.2.0 2.2.1 2.2.2 2.2.3 2.2.3-beta1 2.3.0 2.3.1 2.3.2 2.3.3 3.0.0 3.0.0-beta1 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.1.0 3.1.1 3.1.2 3.1.3 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4
presto-player / inc / Services / VideoPostType.php
presto-player / inc / Services Last commit date
API 2 months ago Blocks 5 days ago License 2 weeks ago AdminNotice.php 1 month ago AdminNotices.php 1 month ago AjaxActions.php 1 month ago Blocks.php 2 years ago Compatibility.php 10 months ago Learn.php 2 months ago Menu.php 5 days ago Migrations.php 2 years ago NpsSurvey.php 6 months ago Onboarding.php 1 month ago Player.php 5 days ago PluginInstaller.php 2 months ago PreloadService.php 1 year ago ProCompatibility.php 5 days ago ReusableVideos.php 1 month ago RewriteRulesManager.php 2 years ago Scripts.php 1 month ago Settings.php 2 months ago Shortcodes.php 2 months ago Streamer.php 1 month ago Translation.php 5 days ago Usage.php 1 month ago VideoPostType.php 5 days ago
VideoPostType.php
836 lines
1 <?php
2 /**
3 * Video Post Type Service
4 *
5 * @package PrestoPlayer
6 * @since 1.0.0
7 */
8
9 namespace PrestoPlayer\Services;
10
11 use PrestoPlayer\Models\ReusableVideo;
12 use PrestoPlayer\Models\Video;
13
14 /**
15 * Class VideoPostType
16 *
17 * Handles the registration and management of the custom post type for video blocks
18 * in the Presto Player plugin. This includes setting up custom columns, managing
19 * post type UI, and handling various actions and filters related to video posts.
20 */
21 class VideoPostType {
22
23 /**
24 * The custom post type identifier for video blocks.
25 *
26 * @var string
27 */
28 protected $post_type = 'pp_video_block';
29
30 /**
31 * Register all hooks and filters for the video post type.
32 *
33 * @return void
34 */
35 public function register() {
36 global $wp_version;
37
38 add_action( 'init', array( $this, 'init' ) );
39 add_action( 'init', array( $this, 'registerMetaSettings' ) );
40 add_filter( 'is_protected_meta', array( $this, 'protectInstantVideoMeta' ), 10, 3 );
41
42 if ( version_compare( $wp_version, '5.8', '>=' ) ) {
43 add_filter( 'allowed_block_types_all', array( $this, 'allowedTypes' ), 10, 2 );
44 } else {
45 add_filter( 'allowed_block_types', array( $this, 'allowedTypesDeprecated' ), 10, 2 );
46 }
47
48 add_filter( 'enter_title_here', array( $this, 'videoTitle' ) );
49
50 // post type ui.
51 add_filter( "manage_{$this->post_type}_posts_columns", array( $this, 'postTypeColumns' ), 1 );
52 add_action( "manage_{$this->post_type}_posts_custom_column", array( $this, 'postTypeColumnContent' ), 10, 2 );
53
54 // filter by tags.
55 add_action( 'restrict_manage_posts', array( $this, 'tagFilter' ) );
56 add_action( 'parse_query', array( $this, 'tagQuery' ) );
57
58 // force gutenberg here.
59 add_action( 'use_block_editor_for_post', array( $this, 'forceGutenberg' ), 999, 2 );
60
61 // limit media hub posts.
62 add_filter( 'pre_get_posts', array( $this, 'limitMediaHubPosts' ) );
63
64 // redirect to 404 if instant video page not published.
65 add_action( 'template_redirect', array( $this, 'maybeRedirectTo404' ) );
66
67 // filter the single template.
68 add_filter( 'single_template', array( $this, 'singleTemplate' ) );
69
70 // script for instant video page dropdown on editor toolbar.
71 add_action( 'admin_enqueue_scripts', array( $this, 'enqueueEditorToolbarScript' ) );
72
73 add_filter( 'post_thumbnail_id', array( $this, 'attachPoster' ), 10, 2 );
74
75 add_filter( 'the_title', array( $this, 'filterVideoTitle' ), 10, 2 );
76
77 add_filter( 'rest_prepare_' . $this->post_type, array( $this, 'addTitleField' ), 10, 3 );
78
79 add_action( 'transition_post_status', array( $this, 'set_post_title' ), 10, 3 );
80
81 add_filter( 'posts_where', array( $this, 'exclude_disabled_media_from_search' ), 10, 2 );
82
83 // Redirect legacy CPT list table to the new dashboard Media Hub tab.
84 // Gutenberg's editor "Back" arrow natively links to edit.php?post_type=<cpt>,
85 // so this redirect is still load-bearing even though no UI in this plugin
86 // links to the legacy URL directly.
87 add_action( 'load-edit.php', array( $this, 'redirectLegacyListToDashboard' ) );
88 }
89
90 /**
91 * Redirect legacy edit.php?post_type=pp_video_block requests to the new
92 * dashboard Media Hub tab. Catches Gutenberg's editor back-arrow, the
93 * admin bar's "All Posts" link, and any direct bookmarks.
94 *
95 * @return void
96 */
97 public function redirectLegacyListToDashboard() {
98 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
99 $post_type = isset( $_GET['post_type'] ) ? sanitize_key( wp_unslash( $_GET['post_type'] ) ) : '';
100 if ( $this->post_type !== $post_type ) {
101 return;
102 }
103
104 $target = admin_url( 'admin.php?page=presto-dashboard&tab=MediaHub' );
105
106 /**
107 * Filter whether to redirect the legacy CPT list table to the new dashboard.
108 *
109 * Return false to short-circuit the redirect — useful for integrators that
110 * still rely on the native list UI, or for internal tests that need to
111 * assert on the legacy screen.
112 *
113 * @param bool $should_redirect Whether to perform the redirect. Default true.
114 * @param string $target The resolved redirect URL.
115 */
116 if ( ! apply_filters( 'presto_player_redirect_legacy_media_list', true, $target ) ) {
117 return;
118 }
119
120 wp_safe_redirect( $target );
121 exit;
122 }
123
124 /**
125 * Filter the WHERE clause to exclude pp_video_block posts with instant video page disabled.
126 *
127 * @param string $where The WHERE clause of the query.
128 * @param \WP_Query $query The WP_Query instance.
129 * @return string Modified WHERE clause.
130 */
131 public function exclude_disabled_media_from_search( $where, $query ) {
132 global $wpdb;
133
134 // Only apply to main search queries on the frontend.
135 if ( ! is_admin() && $query->is_main_query() && $query->is_search() ) {
136 // Exclude pp_video_block posts that don't have instant video pages enabled.
137 $where .= $wpdb->prepare(
138 " AND (
139 {$wpdb->posts}.post_type != %s
140 OR EXISTS (
141 SELECT 1 FROM {$wpdb->postmeta}
142 WHERE {$wpdb->postmeta}.post_id = {$wpdb->posts}.ID
143 AND {$wpdb->postmeta}.meta_key = %s
144 AND {$wpdb->postmeta}.meta_value = %s
145 )
146 )",
147 $this->post_type,
148 'presto_player_instant_video_pages_enabled',
149 '1'
150 );
151 }
152
153 return $where;
154 }
155
156 /**
157 * Limit media hub posts by author if cannot edit others posts
158 *
159 * @param \WP_Query $query The WP_Query instance.
160 * @return \WP_Query
161 */
162 public function limitMediaHubPosts( $query ) {
163 global $pagenow, $typenow;
164
165 if ( 'edit.php' != $pagenow || ! $query->is_admin || $this->post_type !== $typenow ) {
166 return $query;
167 }
168
169 if ( ! current_user_can( 'edit_others_posts' ) ) {
170 $query->set( 'author', get_current_user_id() );
171 }
172
173 return $query;
174 }
175
176 /**
177 * Force gutenberg in case of classic editor.
178 *
179 * @param bool $use Whether to use the block editor.
180 * @param object $post The post object.
181 *
182 * @return bool Whether to use the block editor.
183 */
184 public function forceGutenberg( $use, $post ) {
185 if ( $this->post_type === $post->post_type ) {
186 return true;
187 }
188
189 return $use;
190 }
191
192 /**
193 * Columns on all posts page.
194 *
195 * @param array $defaults The default columns.
196 * @return array The columns.
197 */
198 public function postTypeColumns( $defaults ) {
199 $columns = array_merge(
200 $defaults,
201 array(
202 'poster' => esc_html__( 'Poster', 'presto-player' ),
203 'title' => $defaults['title'],
204 'shortcode' => esc_html__( 'Shortcode', 'presto-player' ),
205 )
206 );
207
208 $v = $columns['taxonomy-pp_video_tag'];
209 unset( $columns['taxonomy-pp_video_tag'] );
210 $columns['taxonomy-pp_video_tag'] = $v;
211
212 $v = $columns['poster'];
213 unset( $columns['poster'] );
214 $columns['poster'] = $v;
215
216 $v = $columns['date'];
217 unset( $columns['date'] );
218 $columns['date'] = $v;
219
220 // return re-arranged columns.
221 return array(
222 'cb' => $columns['cb'],
223 'title' => $columns['title'],
224 'poster' => $columns['poster'],
225 'video_tags' => $columns['taxonomy-pp_video_tag'],
226 'shortcode' => $columns['shortcode'],
227 'date' => $columns['date'],
228 );
229 }
230
231 /**
232 * Renders column content for custom post types.
233 *
234 * @param string $column_name The name of the column to render content for.
235 * @param int $post_ID The ID of the post for which to render the column content.
236 */
237 public function postTypeColumnContent( $column_name, $post_ID ) {
238 $output = '';
239 switch ( $column_name ) {
240 case 'shortcode':
241 $output = $this->renderShortcodeColumn( $post_ID );
242 break;
243 case 'video_tags':
244 $output = $this->renderVideoTagsColumn( $post_ID );
245 break;
246 case 'poster':
247 $output = $this->renderTitleWithPosterColumn( $post_ID );
248 break;
249 }
250 echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is pre-escaped HTML from helper methods above.
251 }
252
253 /**
254 * Renders the shortcode column content.
255 *
256 * @param int $post_ID The ID of the post for which to render the shortcode.
257 */
258 public function renderShortcodeColumn( $post_ID ) {
259 ob_start();
260 ?>
261 <code>[presto_player id=<?php echo (int) $post_ID; ?>]</code>
262 <?php
263 return ob_get_clean();
264 }
265
266 /**
267 * Renders the video tags column content.
268 *
269 * @param int $post_ID The ID of the post for which to render the video tags.
270 * @return string buffer output string.
271 */
272 public function renderVideoTagsColumn( $post_ID ) {
273 ob_start();
274 $tags = get_the_terms( $post_ID, 'pp_video_tag' );
275 if ( is_array( $tags ) ) {
276 foreach ( $tags as $key => $tag ) {
277 // Escape tag name to prevent XSS, use esc_url and absint for defense-in-depth.
278 $tags[ $key ] = '<a href="' . esc_url( '?post_type=pp_video_block&pp_video_tag=' . absint( $tag->term_id ) ) . '">' . esc_html( $tag->name ) . '</a>';
279 }
280 // Each tag link is already individually escaped above.
281 echo wp_kses_post( implode( ', ', $tags ) );
282 }
283 return ob_get_clean();
284 }
285
286 /**
287 * Renders the title with poster column content.
288 *
289 * @param int $post_ID The ID of the post for which to render the title with poster.
290 * @return string buffer output string.
291 */
292 public function renderTitleWithPosterColumn( $post_ID ) {
293 ob_start();
294 $thumbnail = get_the_post_thumbnail(
295 $post_ID,
296 '',
297 array(
298 'style' => 'width: 75px; height: auto; aspect-ratio: 16/9; object-fit: cover; border-radius: 2px;',
299 )
300 );
301 ?>
302 <div class='pp-container' style="display: flex; justify-content: flex-start;">
303 <?php if ( '' !== $thumbnail ) : ?>
304 <div class='pp-container__media-icon pp-container__media-icon--image'><?php echo $thumbnail; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is pre-escaped HTML from get_the_post_thumbnail(). ?></div>
305 <?php else : ?>
306 <div class='pp-container__media-icon pp-container__media-icon--image' style="
307 width: 75px;
308 aspect-ratio: 16/9;
309 box-sizing: border-box;
310 background-color: #b8b8b8;
311 border-radius: 2px;
312 display: flex;
313 justify-content: center;
314 align-items: center;">
315 <?php
316 $svg_content = file_get_contents( PRESTO_PLAYER_PLUGIN_DIR . '/img/icon-white.svg' );
317
318 // Define allowed SVG elements and attributes.
319 $svg_args = array(
320 'svg' => array(
321 'width' => true,
322 'height' => true,
323 'viewbox' => true,
324 'fill' => true,
325 'xmlns' => true,
326 'style' => true,
327 ),
328 'path' => array(
329 'd' => true,
330 'fill' => true,
331 'fill-opacity' => true,
332 ),
333 );
334
335 // Merge the default allowed HTML tags with the SVG arguments.
336 $allowed_tags = array_merge( wp_kses_allowed_html( 'post' ), $svg_args );
337
338 // Initialize the WP_HTML_Tag_Processor.
339 $processor = new \WP_HTML_Tag_Processor( $svg_content );
340
341 // Find the SVG tag and set the width and height attributes.
342 if ( $processor->next_tag( 'svg' ) ) {
343 $processor->set_attribute( 'width', '20px' );
344 $processor->set_attribute( 'height', 'auto' );
345 }
346
347 echo wp_kses( $processor->get_updated_html(), $allowed_tags );
348 ?>
349 </div>
350 <?php endif; ?>
351 </div>
352 <?php
353 return ob_get_clean();
354 }
355
356 /**
357 * Modifies the default title placeholder text
358 *
359 * @param string $title The title placeholder text.
360 * @return string Modified title text
361 */
362 public function videoTitle( $title ) {
363 $screen = get_current_screen();
364 if ( $this->post_type == $screen->post_type ) {
365 $title = __( 'Enter a title...', 'presto-player' );
366 }
367 return $title;
368 }
369
370 /**
371 * Allowed block types.
372 *
373 * @param array $allowed_block_types Array of allowed block types.
374 * @param object $block_editor_content Block editor context.
375 * @return array
376 */
377 public function allowedTypes( $allowed_block_types, $block_editor_content ) {
378 if ( ! empty( $block_editor_content->post->post_type ) ) {
379 if ( $block_editor_content->post->post_type === $this->post_type ) {
380 return array(
381 'presto-player/reusable',
382 'presto-player/self-hosted',
383 'presto-player/youtube',
384 'presto-player/vimeo',
385 'presto-player/bunny',
386 'presto-player/audio',
387 );
388 }
389 }
390
391 return $allowed_block_types;
392 }
393
394 /**
395 * Filter allowed block types for deprecated WordPress versions.
396 *
397 * @param array $allowed_block_types Array of allowed block types.
398 * @param \WP_Post $post Post being loaded.
399 * @return array Filtered block types.
400 */
401 public function allowedTypesDeprecated( $allowed_block_types, $post ) {
402 if ( $post->post_type !== $this->post_type ) {
403 return $allowed_block_types;
404 }
405
406 return array(
407 'presto-player/reusable',
408 'presto-player/self-hosted',
409 'presto-player/youtube',
410 'presto-player/vimeo',
411 'presto-player/bunny',
412 'presto-player/audio',
413 );
414 }
415
416 /**
417 * Register post type
418 *
419 * @return void
420 */
421 public function init() {
422 register_taxonomy(
423 'pp_video_tag',
424 $this->post_type,
425 array(
426 'labels' => array(
427 'name' => _x( 'Media Tags', 'post type general name', 'presto-player' ),
428 'singular_name' => _x( 'Media Tag', 'post type singular name', 'presto-player' ),
429 'search_items' => _x( 'Search Media Tags', 'admin menu', 'presto-player' ),
430 'popular_items' => _x( 'Popular Media Tags', 'add new on admin bar', 'presto-player' ),
431 ),
432 'label' => __( 'Tag', 'presto-player' ),
433 'public' => false,
434 'show_ui' => true,
435 'show_in_rest' => true,
436 'show_admin_column' => true,
437 )
438 );
439
440 register_post_type(
441 $this->post_type,
442 array(
443 'labels' => array(
444 'name' => _x( 'Media Hub', 'post type general name', 'presto-player' ),
445 'singular_name' => _x( 'Media', 'post type singular name', 'presto-player' ),
446 'menu_name' => _x( 'Media', 'admin menu', 'presto-player' ),
447 'name_admin_bar' => _x( 'Presto Media', 'add new on admin bar', 'presto-player' ),
448 'add_new' => _x( 'Add New', 'Media', 'presto-player' ),
449 'add_new_item' => __( 'Add New Media', 'presto-player' ),
450 'new_item' => __( 'New Media', 'presto-player' ),
451 'edit_item' => __( 'Edit Media', 'presto-player' ),
452 'view_item' => __( 'View Media', 'presto-player' ),
453 'all_items' => __( 'Media Hub', 'presto-player' ),
454 'search_items' => __( 'Search Media', 'presto-player' ),
455 'not_found' => __( 'No Media found.', 'presto-player' ),
456 'not_found_in_trash' => __( 'No Media found in Trash.', 'presto-player' ),
457 'filter_items_list' => __( 'Filter Media list', 'presto-player' ),
458 'items_list_navigation' => __( 'Media list navigation', 'presto-player' ),
459 'items_list' => __( 'Media list', 'presto-player' ),
460 'item_published' => __( 'Media published.', 'presto-player' ),
461 'item_published_privately' => __( 'Media published privately.', 'presto-player' ),
462 'item_reverted_to_draft' => __( 'Media reverted to draft.', 'presto-player' ),
463 'item_scheduled' => __( 'Media scheduled.', 'presto-player' ),
464 'item_updated' => __( 'Media updated.', 'presto-player' ),
465 ),
466 'public' => true,
467 'show_ui' => true,
468 'show_in_menu' => false,
469 'show_in_admin_bar' => true,
470 'rewrite' => array(
471 'slug' => 'media',
472 'with_front' => false,
473 ),
474 'show_in_rest' => true,
475 'rest_base' => 'presto-videos',
476 'rest_controller_class' => 'WP_REST_Posts_Controller',
477 'map_meta_cap' => true,
478 'supports' => array(
479 'title',
480 'editor',
481 'custom-fields',
482 ),
483 'taxonomies' => array( 'pp_video_tag' ),
484 'template' => array(
485 array( 'presto-player/reusable-edit' ),
486 ),
487 'template_lock' => 'all',
488 )
489 );
490 }
491
492 /**
493 * Adds a tag filter dropdown
494 *
495 * @return void
496 */
497 public function tagFilter() {
498 global $typenow;
499
500 $post_type = 'pp_video_block';
501 $taxonomy = 'pp_video_tag';
502
503 if ( $typenow !== $post_type ) {
504 return;
505 }
506
507 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin list-table filter; core admin filters use GET without a nonce.
508 $selected = isset( $_GET[ $taxonomy ] ) ? absint( $_GET[ $taxonomy ] ) : '';
509 $info_taxonomy = get_taxonomy( $taxonomy );
510
511 ob_start();
512 wp_dropdown_categories(
513 array(
514 /* translators: %s: taxonomy label */
515 'show_option_all' => sprintf( __( 'Show all %s', 'presto-player' ), $info_taxonomy->label ),
516 'taxonomy' => $taxonomy,
517 'name' => $taxonomy,
518 'orderby' => 'name',
519 'selected' => $selected,
520 'show_count' => true,
521 'hide_empty' => true,
522 )
523 );
524 $dropdown = ob_get_clean();
525
526 $allowed_html = array(
527 'select' => array(
528 'name' => true,
529 'id' => true,
530 'class' => true,
531 ),
532 'option' => array(
533 'class' => true,
534 'value' => true,
535 'selected' => true,
536 ),
537 );
538 echo wp_kses( $dropdown, $allowed_html );
539 }
540
541 /**
542 * Modify admin query for tag.
543 *
544 * @param \WP_Query $query Query being modified.
545 * @return void
546 */
547 public function tagQuery( $query ) {
548 global $pagenow;
549
550 $post_type = $this->post_type;
551 $taxonomy = 'pp_video_tag';
552
553 $q_vars = &$query->query_vars;
554 if ( 'edit.php' === $pagenow && isset( $q_vars['post_type'] ) && $q_vars['post_type'] === $post_type && isset( $q_vars[ $taxonomy ] ) && is_numeric( $q_vars[ $taxonomy ] ) && 0 !== (int) $q_vars[ $taxonomy ] ) {
555 $term = get_term_by( 'id', $q_vars[ $taxonomy ], $taxonomy );
556 $q_vars[ $taxonomy ] = $term->slug;
557 }
558 }
559
560 /**
561 * Retrieves the video title from a media hub block for a given post ID.
562 *
563 * @param int $post_id The ID of the post from which to retrieve the video title.
564 *
565 * @return string|null The video title if found, null otherwise.
566 */
567 public function getVideoTitleFromBlock( $post_id ) {
568 $block = $this->getMediaHubBlock( get_post( $post_id ) );
569 if ( isset( $block['attrs']['id'] ) ) {
570 $video = new Video( $block['attrs']['id'] );
571 $attachment_id = $video->getAttachmentID();
572 if ( ! empty( $attachment_id ) ) {
573 $attachment_title = $video->getAttachmentPostTitle( $attachment_id );
574 if ( ! empty( $attachment_title ) ) {
575 return $attachment_title;
576 }
577 }
578 $video_title = $video->getTitle();
579 if ( ! empty( $video_title ) ) {
580 return $video_title;
581 }
582 }
583 return null;
584 }
585
586 /**
587 * Get the the title fallback.
588 *
589 * @param string $title The title.
590 * @param int $post_id The ID of the post.
591 *
592 * @return string The title fallback.
593 */
594 public function getTitleFallback( $title, $post_id ) {
595 if ( ! isset( $post_id ) ) {
596 return $title;
597 }
598 // Include the translated block name in title if available.
599 $block = ( new ReusableVideo( $post_id ) )->getAttributes();
600 if ( $block ) {
601 if ( ! empty( $block['name'] ) ) {
602 $title = ( $block['name'] ?? '' ) . ' #' . $post_id;
603 }
604 }
605 return $title;
606 }
607
608 /**
609 * Register the meta settings for the video block
610 *
611 * @return void
612 */
613 public function registerMetaSettings() {
614 register_post_meta(
615 $this->post_type,
616 'presto_player_instant_video_pages_enabled',
617 array(
618 'single' => true,
619 'type' => 'boolean',
620 'description' => 'Enable Instant Video Pages',
621 'show_in_rest' => true,
622 'auth_callback' => function ( $allowed, $meta_key, $post_id, $user_id ) {
623 return user_can( $user_id, 'edit_post', $post_id );
624 },
625 )
626 );
627 }
628
629 /**
630 * Hide the instant video page meta from the Custom Fields metabox.
631 *
632 * If a user has the editor's "Custom fields" panel switched on, the metabox
633 * renders this key as an editable row and posts its stale value back right
634 * after Gutenberg's REST save — silently reverting the toggle.
635 *
636 * @param bool $protected Whether the key is protected.
637 * @param string $meta_key The meta key.
638 * @param string $meta_type The object type.
639 * @return bool
640 */
641 public function protectInstantVideoMeta( $protected, $meta_key, $meta_type ) {
642 if ( 'post' === $meta_type && 'presto_player_instant_video_pages_enabled' === $meta_key ) {
643 return true;
644 }
645 return $protected;
646 }
647
648 /**
649 * Redirect to 404 if the instant video page is disabled
650 *
651 * @return void
652 */
653 public function maybeRedirectTo404() {
654 global $post;
655 if ( ! isset( $post ) ) {
656 return;
657 }
658 if ( $this->post_type !== $post->post_type ) {
659 return;
660 }
661 // If this is a search request, don't redirect to 404.
662 if ( is_search() ) {
663 return;
664 }
665 if ( current_user_can( 'edit_post', $post->ID ) ) {
666 return;
667 }
668 $media_hub_video = new ReusableVideo( $post->ID );
669 if ( ! empty( $media_hub_video ) && empty( $media_hub_video->instantVideoPageEnabled() ) ) {
670 global $wp_query;
671 $wp_query->set_404();
672 status_header( 404 );
673 get_template_part( 404 );
674 exit();
675 }
676 }
677
678 /**
679 * Attach the poster image URL to the video post.
680 *
681 * @param int $id The attachment ID.
682 * @param WP_Post $post The post object.
683 *
684 * @return int The attachment ID.
685 */
686 public function attachPoster( $id, $post ) {
687 if ( $this->post_type !== $post->post_type ) {
688 return $id;
689 }
690 $block = $this->getMediaHubBlock( $post );
691 $poster = ( ! empty( $block ) ) && isset( $block['attrs']['poster'] ) ? $block['attrs']['poster'] : '';
692 // No URL → nothing to resolve. Skips a wasted attachment_url_to_postid()
693 // DB query on every get_post_thumbnail_id() call for posts whose block
694 // doesn't define a poster (most YouTube / Vimeo / audio sources).
695 if ( '' === $poster ) {
696 return $id;
697 }
698 $attachment_id = attachment_url_to_postid( $poster );
699 return $attachment_id ? $attachment_id : $id;
700 }
701
702 /**
703 * Get the single template for the video block
704 *
705 * @param string $template The template file.
706 *
707 * @return string The template file
708 */
709 public function singleTemplate( $template ) {
710 global $post;
711
712 if ( $this->post_type !== $post->post_type ) {
713 return $template;
714 }
715
716 $theme_template = locate_template( array( 'single-presto-media.php' ) );
717 if ( $theme_template ) {
718 return $theme_template;
719 }
720
721 return PRESTO_PLAYER_PLUGIN_DIR . 'templates/single-presto-media.php';
722 }
723
724 /**
725 * Get the media hub block.
726 *
727 * @param WP_Post $post Post object.
728 *
729 * @return array|bool The media hub block array or false if block not found.
730 */
731 public function getMediaHubBlock( $post ) {
732 $blocks = parse_blocks( $post->post_content );
733 $first_block = wp_get_first_block( $blocks, 'presto-player/reusable-edit' );
734 return isset( $first_block['innerBlocks'][0] ) ? $first_block['innerBlocks'][0] : false;
735 }
736
737 /**
738 * Register the editor toolbar script for
739 * instant video page dropdown on the editor toolbar.
740 *
741 * @return void
742 */
743 public function enqueueEditorToolbarScript() {
744 global $post_type;
745
746 if ( $this->post_type !== $post_type ) {
747 return;
748 }
749
750 $assets = include trailingslashit( PRESTO_PLAYER_PLUGIN_DIR ) . 'dist/toolbar.asset.php';
751 wp_enqueue_script(
752 'presto-player/toolbar/admin',
753 trailingslashit( PRESTO_PLAYER_PLUGIN_URL ) . 'dist/toolbar.js',
754 array_merge( array( 'jquery', 'regenerator-runtime' ), $assets['dependencies'] ?? array() ),
755 $assets['version'],
756 true
757 );
758 wp_enqueue_style(
759 'presto-player/toolbar/admin',
760 trailingslashit( PRESTO_PLAYER_PLUGIN_URL ) . 'dist/toolbar.css',
761 array(),
762 $assets['version']
763 );
764 }
765
766 /**
767 * Modify title for response.
768 *
769 * @param WP_REST_Response $response Response object.
770 * @param WP_Post $post Post object.
771 * @param WP_REST_Request $request Request object.
772 *
773 * @return WP_REST_Response Response object with title property modified.
774 */
775 public function addTitleField( $response, $post, $request ) {
776 if ( $post->post_type !== $this->post_type ) {
777 return $response;
778 }
779 if ( ! isset( $response->data['title'] ) ) {
780 return $response;
781 }
782 if ( ! is_array( $response->data['title'] ) ) {
783 return $response;
784 }
785 $response->data['title'] = get_the_title( $post->ID );
786 return $response;
787 }
788
789 /**
790 * Update the post title to the video title on publish.
791 *
792 * @param string $new_status new status of the post.
793 * @param string $old_status old status of the post.
794 * @param WP_Post $post Post object.
795 *
796 * @return void
797 */
798 public function set_post_title( $new_status, $old_status, $post ) {
799 if ( 'pp_video_block' !== $post->post_type ) {
800 return;
801 }
802 if ( ! empty( $post->post_title ) ) {
803 return;
804 }
805 $post_title = $this->getVideoTitleFromBlock( $post->ID );
806 if ( empty( $post_title ) ) {
807 return;
808 }
809 wp_update_post(
810 array(
811 'ID' => $post->ID,
812 'post_title' => $post_title,
813 )
814 );
815 }
816
817 /**
818 * Filters video post title to use the video's title
819 *
820 * @param string $title The current title of the post.
821 * @param int $post_id The ID of the post.
822 *
823 * @return string The filtered title, either the original title or the video's title if the original is empty.
824 */
825 public function filterVideoTitle( $title, $post_id = 0 ) {
826 if ( get_post_type( $post_id ) !== $this->post_type ) {
827 return $title;
828 }
829 if ( ! empty( $title ) ) {
830 return $title;
831 }
832 $video_title = $this->getVideoTitleFromBlock( $post_id );
833 return $video_title ?? $this->getTitleFallback( $title, $post_id );
834 }
835 }
836