You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
397 lines
12 KiB
PHTML
397 lines
12 KiB
PHTML
8 months ago
|
<?php
|
||
|
/**
|
||
|
* Class Jetpack
|
||
|
*
|
||
|
* @link https://github.com/googleforcreators/web-stories-wp
|
||
|
*
|
||
|
* @copyright 2020 Google LLC
|
||
|
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Copyright 2020 Google LLC
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
declare(strict_types = 1);
|
||
|
|
||
|
namespace Google\Web_Stories\Integrations;
|
||
|
|
||
|
use Google\Web_Stories\Context;
|
||
|
use Google\Web_Stories\Media\Media_Source_Taxonomy;
|
||
|
use Google\Web_Stories\Service_Base;
|
||
|
use Google\Web_Stories\Story_Post_Type;
|
||
|
use WP_Post;
|
||
|
use WP_REST_Response;
|
||
|
|
||
|
/**
|
||
|
* Class Jetpack.
|
||
|
*
|
||
|
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
|
||
|
*
|
||
|
* @phpstan-type AttachmentData array{
|
||
|
* media_details?: array{
|
||
|
* length?: int,
|
||
|
* length_formatted?: string
|
||
|
* },
|
||
|
* url?: string,
|
||
|
* featured_media_src?: string
|
||
|
* }
|
||
|
*
|
||
|
* @phpstan-type EnhancedAttachmentMetadata array{
|
||
|
* width: int,
|
||
|
* height: int,
|
||
|
* file: string,
|
||
|
* sizes: mixed,
|
||
|
* image_meta: mixed,
|
||
|
* videopress?: array{
|
||
|
* duration: int,
|
||
|
* poster: string,
|
||
|
* width: int,
|
||
|
* height: int,
|
||
|
* file_url_base?: array{
|
||
|
* https: string
|
||
|
* },
|
||
|
* files?: array{
|
||
|
* hd?: array{
|
||
|
* mp4?: string
|
||
|
* }
|
||
|
* }
|
||
|
* }
|
||
|
* }
|
||
|
*/
|
||
|
class Jetpack extends Service_Base {
|
||
|
|
||
|
/**
|
||
|
* VideoPress Mime type.
|
||
|
*
|
||
|
* @since 1.7.2
|
||
|
*/
|
||
|
public const VIDEOPRESS_MIME_TYPE = 'video/videopress';
|
||
|
|
||
|
/**
|
||
|
* VideoPress poster meta key.
|
||
|
*
|
||
|
* @since 1.7.2
|
||
|
*/
|
||
|
public const VIDEOPRESS_POSTER_META_KEY = 'videopress_poster_image';
|
||
|
|
||
|
/**
|
||
|
* Media_Source_Taxonomy instance.
|
||
|
*
|
||
|
* @var Media_Source_Taxonomy Experiments instance.
|
||
|
*/
|
||
|
protected Media_Source_Taxonomy $media_source_taxonomy;
|
||
|
|
||
|
/**
|
||
|
* Context instance.
|
||
|
*
|
||
|
* @var Context Context instance.
|
||
|
*/
|
||
|
private Context $context;
|
||
|
|
||
|
/**
|
||
|
* Jetpack constructor.
|
||
|
*
|
||
|
* @since 1.12.0
|
||
|
*
|
||
|
* @param Media_Source_Taxonomy $media_source_taxonomy Media_Source_Taxonomy instance.
|
||
|
* @param Context $context Context instance.
|
||
|
*/
|
||
|
public function __construct( Media_Source_Taxonomy $media_source_taxonomy, Context $context ) {
|
||
|
$this->media_source_taxonomy = $media_source_taxonomy;
|
||
|
$this->context = $context;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initializes all hooks.
|
||
|
*
|
||
|
* @since 1.2.0
|
||
|
*/
|
||
|
public function register(): void {
|
||
|
// See https://github.com/Automattic/jetpack/blob/4b85be883b3c584c64eeb2fb0f3fcc15dabe2d30/modules/custom-post-types/portfolios.php#L80.
|
||
|
if ( \defined( 'IS_WPCOM' ) && IS_WPCOM ) {
|
||
|
add_filter( 'wpcom_sitemap_post_types', [ $this, 'add_to_jetpack_sitemap' ] );
|
||
|
} else {
|
||
|
add_filter( 'jetpack_sitemap_post_types', [ $this, 'add_to_jetpack_sitemap' ] );
|
||
|
}
|
||
|
|
||
|
add_filter( 'jetpack_is_amp_request', [ $this, 'force_amp_request' ] );
|
||
|
add_filter( 'web_stories_allowed_mime_types', [ $this, 'add_videopress' ] );
|
||
|
add_filter( 'web_stories_rest_prepare_attachment', [ $this, 'filter_rest_api_response' ], 10, 2 );
|
||
|
add_filter( 'ajax_query_attachments_args', [ $this, 'filter_ajax_query_attachments_args' ] );
|
||
|
add_action( 'added_post_meta', [ $this, 'add_term' ], 10, 3 );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Adds the web-story post type to Jetpack / WordPress.com sitemaps.
|
||
|
*
|
||
|
* @since 1.2.0
|
||
|
*
|
||
|
* @see https://github.com/Automattic/jetpack/blob/4b85be883b3c584c64eeb2fb0f3fcc15dabe2d30/modules/custom-post-types/portfolios.php#L80
|
||
|
*
|
||
|
* @param array|mixed $post_types Array of post types.
|
||
|
* @return array|mixed Modified list of post types.
|
||
|
*
|
||
|
* @template T
|
||
|
*
|
||
|
* @phpstan-return ($post_types is array<T> ? array<T> : mixed)
|
||
|
*/
|
||
|
public function add_to_jetpack_sitemap( $post_types ) {
|
||
|
if ( ! \is_array( $post_types ) ) {
|
||
|
return $post_types;
|
||
|
}
|
||
|
$post_types[] = Story_Post_Type::POST_TYPE_SLUG;
|
||
|
|
||
|
return $post_types;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add VideoPress to allowed mime types.
|
||
|
*
|
||
|
* If the site does not support VideoPress, this will be filtered out.
|
||
|
*
|
||
|
* @since 1.7.2
|
||
|
*
|
||
|
* @param array{video?: string[]}|mixed $mime_types Associative array of allowed mime types per media type (image, audio, video).
|
||
|
* @return array{video?: string[]}|mixed
|
||
|
*
|
||
|
* @template T
|
||
|
*
|
||
|
* @phpstan-return ($mime_types is array<T> ? array<T> : mixed)
|
||
|
*/
|
||
|
public function add_videopress( $mime_types ) {
|
||
|
if ( ! \is_array( $mime_types ) ) {
|
||
|
return $mime_types;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Mime types config.
|
||
|
*
|
||
|
* @var array{video?: string[]} $mime_types
|
||
|
*/
|
||
|
$mime_types['video'][] = self::VIDEOPRESS_MIME_TYPE;
|
||
|
|
||
|
return $mime_types;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Filter ajax query attachments args when accessed from the Web Stories editor.
|
||
|
*
|
||
|
* Only filters the response if the mime type matches exactly what Web Stories is looking for.
|
||
|
*
|
||
|
* @since 1.7.2
|
||
|
*
|
||
|
* @param array|mixed $args Query args.
|
||
|
* @return array|mixed Filtered query args.
|
||
|
*
|
||
|
* @template T
|
||
|
*
|
||
|
* @phpstan-return ($args is array<T> ? array<T> : mixed)
|
||
|
*/
|
||
|
public function filter_ajax_query_attachments_args( $args ) {
|
||
|
if ( ! \is_array( $args ) || ! isset( $args['post_mime_type'] ) || ! \is_array( $args['post_mime_type'] ) ) {
|
||
|
return $args;
|
||
|
}
|
||
|
|
||
|
if ( \in_array( self::VIDEOPRESS_MIME_TYPE, $args['post_mime_type'], true ) ) {
|
||
|
add_filter( 'wp_prepare_attachment_for_js', [ $this, 'filter_admin_ajax_response' ], 15, 2 );
|
||
|
}
|
||
|
|
||
|
return $args;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Filter admin ajax responses for VideoPress videos.
|
||
|
*
|
||
|
* Changes the video/videopress type back to mp4
|
||
|
* and ensures MP4 source URLs are returned.
|
||
|
*
|
||
|
* @since 1.7.2
|
||
|
*
|
||
|
* @param array|mixed $data Array of prepared attachment data. @see wp_prepare_attachment_for_js().
|
||
|
* @param WP_Post $attachment Attachment object.
|
||
|
* @return array|mixed
|
||
|
*
|
||
|
* @phpstan-param AttachmentData $data
|
||
|
* @phpstan-return AttachmentData|mixed
|
||
|
*
|
||
|
* @template T
|
||
|
*
|
||
|
* @phpstan-return ($data is array<T> ? array<T> : mixed)
|
||
|
*/
|
||
|
public function filter_admin_ajax_response( $data, WP_Post $attachment ) {
|
||
|
if ( self::VIDEOPRESS_MIME_TYPE !== $attachment->post_mime_type ) {
|
||
|
return $data;
|
||
|
}
|
||
|
|
||
|
if ( ! \is_array( $data ) ) {
|
||
|
return $data;
|
||
|
}
|
||
|
|
||
|
// Reset mime type back to mp4, as this is the correct value.
|
||
|
$data['mime'] = 'video/mp4';
|
||
|
$data['subtype'] = 'mp4';
|
||
|
|
||
|
// Mark video as optimized.
|
||
|
$data[ $this->media_source_taxonomy::MEDIA_SOURCE_KEY ] = 'video-optimization';
|
||
|
|
||
|
/**
|
||
|
* Jetpack adds an additional field to regular attachment metadata.
|
||
|
*
|
||
|
* @var array $metadata
|
||
|
* @phpstan-var EnhancedAttachmentMetadata|false $metadata
|
||
|
*/
|
||
|
$metadata = wp_get_attachment_metadata( $attachment->ID );
|
||
|
|
||
|
if ( $metadata && isset( $metadata['videopress']['duration'], $data['media_details'] ) && \is_array( $data['media_details'] ) ) {
|
||
|
$data['media_details']['length_formatted'] = $this->format_milliseconds( $metadata['videopress']['duration'] );
|
||
|
$data['media_details']['length'] = (int) floor( $metadata['videopress']['duration'] / 1000 );
|
||
|
}
|
||
|
|
||
|
if ( $metadata && isset( $data['url'], $metadata['videopress']['file_url_base']['https'], $metadata['videopress']['files']['hd']['mp4'] ) ) {
|
||
|
$data['url'] = $metadata['videopress']['file_url_base']['https'] . $metadata['videopress']['files']['hd']['mp4'];
|
||
|
}
|
||
|
|
||
|
// Get the correct poster with matching dimensions from VideoPress.
|
||
|
if ( $metadata && isset( $data['featured_media_src'], $metadata['videopress']['poster'], $metadata['videopress']['width'], $metadata['videopress']['height'] ) ) {
|
||
|
$data['featured_media_src'] = [
|
||
|
'src' => $metadata['videopress']['poster'],
|
||
|
'width' => $metadata['videopress']['width'],
|
||
|
'height' => $metadata['videopress']['height'],
|
||
|
'generated' => true,
|
||
|
];
|
||
|
}
|
||
|
|
||
|
return $data;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Filter REST API responses for VideoPress videos.
|
||
|
*
|
||
|
* Changes the video/videopress type back to mp4
|
||
|
* and ensures MP4 source URLs are returned.
|
||
|
*
|
||
|
* @since 1.7.2
|
||
|
*
|
||
|
* @param WP_REST_Response $response The response object.
|
||
|
* @param WP_Post $post The original attachment post.
|
||
|
*/
|
||
|
public function filter_rest_api_response( WP_REST_Response $response, WP_Post $post ): WP_REST_Response {
|
||
|
if ( self::VIDEOPRESS_MIME_TYPE !== $post->post_mime_type ) {
|
||
|
return $response;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Response data.
|
||
|
*
|
||
|
* @var array<string, string|array<string, int|string>|bool> $data
|
||
|
*/
|
||
|
$data = $response->get_data();
|
||
|
|
||
|
// Reset mime type back to mp4, as this is the correct value.
|
||
|
$data['mime_type'] = 'video/mp4';
|
||
|
|
||
|
// Mark video as optimized.
|
||
|
$data[ $this->media_source_taxonomy::MEDIA_SOURCE_KEY ] = 'video-optimization';
|
||
|
|
||
|
/**
|
||
|
* Jetpack adds an additional field to regular attachment metadata.
|
||
|
*
|
||
|
* @var EnhancedAttachmentMetadata|false $metadata
|
||
|
*/
|
||
|
$metadata = wp_get_attachment_metadata( $post->ID );
|
||
|
|
||
|
if ( $metadata && isset( $metadata['videopress']['duration'], $data['media_details'] ) && \is_array( $data['media_details'] ) ) {
|
||
|
$data['media_details']['length_formatted'] = $this->format_milliseconds( $metadata['videopress']['duration'] );
|
||
|
$data['media_details']['length'] = (int) floor( $metadata['videopress']['duration'] / 1000 );
|
||
|
}
|
||
|
|
||
|
if ( $metadata && isset( $data['source_url'], $metadata['videopress']['file_url_base']['https'], $metadata['videopress']['files']['hd']['mp4'] ) ) {
|
||
|
$data['source_url'] = $metadata['videopress']['file_url_base']['https'] . $metadata['videopress']['files']['hd']['mp4'];
|
||
|
}
|
||
|
|
||
|
// Get the correct poster with matching dimensions from VideoPress.
|
||
|
if ( $metadata && isset( $data['featured_media_src'], $metadata['videopress']['poster'], $metadata['videopress']['width'], $metadata['videopress']['height'] ) ) {
|
||
|
$data['featured_media_src'] = [
|
||
|
'src' => $metadata['videopress']['poster'],
|
||
|
'width' => $metadata['videopress']['width'],
|
||
|
'height' => $metadata['videopress']['height'],
|
||
|
'generated' => true,
|
||
|
];
|
||
|
}
|
||
|
|
||
|
$response->set_data( $data );
|
||
|
|
||
|
return $response;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Hook into added_post_meta.
|
||
|
*
|
||
|
* @since 1.7.2
|
||
|
*
|
||
|
* @param int $mid The meta ID after successful update.
|
||
|
* @param int $object_id ID of the object metadata is for.
|
||
|
* @param string $meta_key Metadata key.
|
||
|
*/
|
||
|
public function add_term( int $mid, int $object_id, string $meta_key ): void {
|
||
|
if ( self::VIDEOPRESS_POSTER_META_KEY !== $meta_key ) {
|
||
|
return;
|
||
|
}
|
||
|
if ( 'attachment' !== get_post_type( $object_id ) ) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
wp_set_object_terms( (int) $object_id, $this->media_source_taxonomy::TERM_POSTER_GENERATION, $this->media_source_taxonomy->get_taxonomy_slug() );
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Force Jetpack to see Web Stories as AMP.
|
||
|
*
|
||
|
* @since 1.2.0
|
||
|
*
|
||
|
* @param bool $is_amp_request Is the request supposed to return valid AMP content.
|
||
|
* @return bool Whether the current request is an AMP request.
|
||
|
*/
|
||
|
public function force_amp_request( bool $is_amp_request ): bool {
|
||
|
if ( ! $this->context->is_web_story() ) {
|
||
|
return (bool) $is_amp_request;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Format milliseconds into seconds.
|
||
|
*
|
||
|
* @since 1.7.2
|
||
|
*
|
||
|
* @param int $milliseconds Milliseconds to converted to minutes and seconds.
|
||
|
*/
|
||
|
protected function format_milliseconds( int $milliseconds ): string {
|
||
|
$seconds = floor( $milliseconds / 1000 );
|
||
|
|
||
|
if ( $seconds >= 1 ) {
|
||
|
$minutes = floor( $seconds / 60 );
|
||
|
$seconds %= 60;
|
||
|
} else {
|
||
|
$seconds = 0;
|
||
|
$minutes = 0;
|
||
|
}
|
||
|
|
||
|
return sprintf( '%d:%02u', $minutes, $seconds );
|
||
|
}
|
||
|
}
|