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