Commit realizado el 12:13:52 08-04-2024
This commit is contained in:
117
wp-content/plugins/web-stories/includes/Media/Base_Color.php
Normal file
117
wp-content/plugins/web-stories/includes/Media/Base_Color.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Image_Size
|
||||
*
|
||||
* @link https://github.com/googleforcreators/web-stories-wp
|
||||
*
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright 2021 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\Media;
|
||||
|
||||
use Google\Web_Stories\Infrastructure\HasMeta;
|
||||
use Google\Web_Stories\Infrastructure\PluginUninstallAware;
|
||||
use Google\Web_Stories\Service_Base;
|
||||
|
||||
/**
|
||||
* Class Base_Color
|
||||
*/
|
||||
class Base_Color extends Service_Base implements HasMeta, PluginUninstallAware {
|
||||
|
||||
/**
|
||||
* The base color meta key.
|
||||
*/
|
||||
public const BASE_COLOR_POST_META_KEY = 'web_stories_base_color';
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @since 1.15.0
|
||||
*/
|
||||
public function register(): void {
|
||||
$this->register_meta();
|
||||
|
||||
add_filter( 'wp_prepare_attachment_for_js', [ $this, 'wp_prepare_attachment_for_js' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register meta
|
||||
*
|
||||
* @since 1.15.0
|
||||
*/
|
||||
public function register_meta(): void {
|
||||
register_meta(
|
||||
'post',
|
||||
self::BASE_COLOR_POST_META_KEY,
|
||||
[
|
||||
'type' => 'string',
|
||||
'description' => __( 'Attachment base color', 'web-stories' ),
|
||||
'show_in_rest' => [
|
||||
'schema' => [
|
||||
'type' => 'string',
|
||||
'format' => 'hex-color',
|
||||
],
|
||||
],
|
||||
'single' => true,
|
||||
'object_subtype' => 'attachment',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the attachment data prepared for JavaScript.
|
||||
*
|
||||
* @since 1.15.0
|
||||
*
|
||||
* @param array|mixed $response Array of prepared attachment data.
|
||||
* @return array|mixed $response;
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @phpstan-return ($response is array<T> ? array<T> : mixed)
|
||||
*/
|
||||
public function wp_prepare_attachment_for_js( $response ) {
|
||||
if ( ! \is_array( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attachment ID.
|
||||
*
|
||||
* @var int $post_id
|
||||
*/
|
||||
$post_id = $response['id'];
|
||||
|
||||
$response[ self::BASE_COLOR_POST_META_KEY ] = get_post_meta( $post_id, self::BASE_COLOR_POST_META_KEY, true );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on plugin uninstall.
|
||||
*
|
||||
* @since 1.26.0
|
||||
*/
|
||||
public function on_plugin_uninstall(): void {
|
||||
delete_post_meta_by_key( self::BASE_COLOR_POST_META_KEY );
|
||||
}
|
||||
}
|
116
wp-content/plugins/web-stories/includes/Media/Blurhash.php
Normal file
116
wp-content/plugins/web-stories/includes/Media/Blurhash.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Blurhash
|
||||
*
|
||||
* @link https://github.com/googleforcreators/web-stories-wp
|
||||
*
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright 2021 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\Media;
|
||||
|
||||
use Google\Web_Stories\Infrastructure\HasMeta;
|
||||
use Google\Web_Stories\Infrastructure\PluginUninstallAware;
|
||||
use Google\Web_Stories\Service_Base;
|
||||
|
||||
/**
|
||||
* Class Blurhash
|
||||
*/
|
||||
class Blurhash extends Service_Base implements HasMeta, PluginUninstallAware {
|
||||
|
||||
/**
|
||||
* The blurhash meta key.
|
||||
*/
|
||||
public const BLURHASH_POST_META_KEY = 'web_stories_blurhash';
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @since 1.16.0
|
||||
*/
|
||||
public function register(): void {
|
||||
$this->register_meta();
|
||||
|
||||
add_filter( 'wp_prepare_attachment_for_js', [ $this, 'wp_prepare_attachment_for_js' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register meta
|
||||
*
|
||||
* @since 1.16.0
|
||||
*/
|
||||
public function register_meta(): void {
|
||||
register_meta(
|
||||
'post',
|
||||
self::BLURHASH_POST_META_KEY,
|
||||
[
|
||||
'type' => 'string',
|
||||
'description' => __( 'Attachment BlurHash', 'web-stories' ),
|
||||
'show_in_rest' => [
|
||||
'schema' => [
|
||||
'type' => 'string',
|
||||
],
|
||||
],
|
||||
'single' => true,
|
||||
'object_subtype' => 'attachment',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the attachment data prepared for JavaScript.
|
||||
*
|
||||
* @since 1.16.0
|
||||
*
|
||||
* @param array|mixed $response Array of prepared attachment data.
|
||||
* @return array|mixed Response data.
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @phpstan-return ($response is array<T> ? array<T> : mixed)
|
||||
*/
|
||||
public function wp_prepare_attachment_for_js( $response ) {
|
||||
if ( ! \is_array( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Post ID.
|
||||
*
|
||||
* @var int $post_id
|
||||
*/
|
||||
$post_id = $response['id'];
|
||||
|
||||
$response[ self::BLURHASH_POST_META_KEY ] = get_post_meta( $post_id, self::BLURHASH_POST_META_KEY, true );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on plugin uninstall.
|
||||
*
|
||||
* @since 1.26.0
|
||||
*/
|
||||
public function on_plugin_uninstall(): void {
|
||||
delete_post_meta_by_key( self::BLURHASH_POST_META_KEY );
|
||||
}
|
||||
}
|
95
wp-content/plugins/web-stories/includes/Media/Cropping.php
Normal file
95
wp-content/plugins/web-stories/includes/Media/Cropping.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Cropping
|
||||
*
|
||||
* @link https://github.com/googleforcreators/web-stories-wp
|
||||
*
|
||||
* @copyright 2022 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright 2022 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\Media;
|
||||
|
||||
use Google\Web_Stories\Infrastructure\HasMeta;
|
||||
use Google\Web_Stories\Infrastructure\PluginUninstallAware;
|
||||
use Google\Web_Stories\Service_Base;
|
||||
|
||||
/**
|
||||
* Class Cropping
|
||||
*/
|
||||
class Cropping extends Service_Base implements HasMeta, PluginUninstallAware {
|
||||
|
||||
/**
|
||||
* The cropped video id post meta key.
|
||||
*/
|
||||
public const CROPPED_ID_POST_META_KEY = 'web_stories_cropped_origin_id';
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @since 1.26.0
|
||||
*/
|
||||
public function register(): void {
|
||||
$this->register_meta();
|
||||
add_action( 'delete_attachment', [ $this, 'delete_video' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register meta
|
||||
*
|
||||
* @since 1.26.0
|
||||
*/
|
||||
public function register_meta(): void {
|
||||
register_meta(
|
||||
'post',
|
||||
self::CROPPED_ID_POST_META_KEY,
|
||||
[
|
||||
'sanitize_callback' => 'absint',
|
||||
'type' => 'integer',
|
||||
'description' => __( 'Parent ID if this is a cropped attachment', 'web-stories' ),
|
||||
'show_in_rest' => true,
|
||||
'default' => 0,
|
||||
'single' => true,
|
||||
'object_subtype' => 'attachment',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes associated meta data when a video is deleted.
|
||||
*
|
||||
* @since 1.26.0
|
||||
*
|
||||
* @param int $attachment_id ID of the attachment to be deleted.
|
||||
*/
|
||||
public function delete_video( int $attachment_id ): void {
|
||||
delete_metadata( 'post', 0, self::CROPPED_ID_POST_META_KEY, $attachment_id, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on plugin uninstall.
|
||||
*
|
||||
* @since 1.26.0
|
||||
*/
|
||||
public function on_plugin_uninstall(): void {
|
||||
delete_post_meta_by_key( self::CROPPED_ID_POST_META_KEY );
|
||||
}
|
||||
}
|
173
wp-content/plugins/web-stories/includes/Media/Image_Sizes.php
Normal file
173
wp-content/plugins/web-stories/includes/Media/Image_Sizes.php
Normal file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Image_Size
|
||||
*
|
||||
* @link https://github.com/googleforcreators/web-stories-wp
|
||||
*
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright 2021 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\Media;
|
||||
|
||||
use Google\Web_Stories\Service_Base;
|
||||
use WP_Post;
|
||||
|
||||
/**
|
||||
* Class Image_Sizes
|
||||
*/
|
||||
class Image_Sizes extends Service_Base {
|
||||
/**
|
||||
* The image size for the poster-portrait-src.
|
||||
*/
|
||||
public const POSTER_PORTRAIT_IMAGE_SIZE = 'web-stories-poster-portrait';
|
||||
|
||||
/**
|
||||
* The image dimensions for the poster-portrait-src.
|
||||
*/
|
||||
public const POSTER_PORTRAIT_IMAGE_DIMENSIONS = [ 640, 853 ];
|
||||
|
||||
/**
|
||||
* Name of size used in media library.
|
||||
*/
|
||||
public const STORY_THUMBNAIL_IMAGE_SIZE = 'web-stories-thumbnail';
|
||||
|
||||
/**
|
||||
* The image dimensions for media library thumbnails.
|
||||
*/
|
||||
public const STORY_THUMBNAIL_IMAGE_DIMENSIONS = [ 150, 9999 ];
|
||||
|
||||
/**
|
||||
* The image size for the publisher logo.
|
||||
*/
|
||||
public const PUBLISHER_LOGO_IMAGE_SIZE = 'web-stories-publisher-logo';
|
||||
|
||||
/**
|
||||
* The image dimensions for the publisher logo.
|
||||
*/
|
||||
public const PUBLISHER_LOGO_IMAGE_DIMENSIONS = [ 96, 96 ];
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function register(): void {
|
||||
$this->add_image_sizes();
|
||||
add_filter( 'wp_prepare_attachment_for_js', [ $this, 'wp_prepare_attachment_for_js' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the attachment data prepared for JavaScript.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param array|mixed $response Array of prepared attachment data.
|
||||
* @param WP_Post $attachment Attachment object.
|
||||
* @return array|mixed $response;
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @phpstan-return ($response is array<T> ? array<T> : mixed)
|
||||
*/
|
||||
public function wp_prepare_attachment_for_js( $response, WP_Post $attachment ) {
|
||||
if ( ! \is_array( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
// See https://github.com/WordPress/wordpress-develop/blob/d28766f8f2ecf2be02c2520cdf0cc3b51deb9e1b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php#L753-L791 .
|
||||
$response['media_details'] = wp_get_attachment_metadata( $attachment->ID );
|
||||
|
||||
// Ensure empty details is an empty object.
|
||||
if ( empty( $response['media_details'] ) ) {
|
||||
$response['media_details'] = [];
|
||||
} elseif ( ! empty( $response['media_details']['sizes'] ) ) {
|
||||
foreach ( $response['media_details']['sizes'] as $size => &$size_data ) {
|
||||
|
||||
if ( isset( $size_data['mime-type'] ) ) {
|
||||
$size_data['mime_type'] = $size_data['mime-type'];
|
||||
unset( $size_data['mime-type'] );
|
||||
}
|
||||
|
||||
// Use the same method image_downsize() does.
|
||||
$image = wp_get_attachment_image_src( $attachment->ID, $size );
|
||||
if ( ! $image ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[ $image_src ] = $image;
|
||||
$size_data['source_url'] = $image_src;
|
||||
}
|
||||
|
||||
$img_src = wp_get_attachment_image_src( $attachment->ID, 'full' );
|
||||
|
||||
if ( $img_src ) {
|
||||
[ $src, $width, $height ] = $img_src;
|
||||
|
||||
$response['media_details']['sizes']['full'] = [
|
||||
'file' => wp_basename( $src ),
|
||||
'width' => $width,
|
||||
'height' => $height,
|
||||
'mime_type' => $attachment->post_mime_type,
|
||||
'source_url' => $src,
|
||||
];
|
||||
}
|
||||
} else {
|
||||
$response['media_details']['sizes'] = [];
|
||||
}
|
||||
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add image sizes.
|
||||
*
|
||||
* @since 1.10.0
|
||||
*
|
||||
* @link https://amp.dev/documentation/components/amp-story/#poster-guidelines-for-poster-portrait-src-poster-landscape-src-and-poster-square-src.
|
||||
*/
|
||||
protected function add_image_sizes(): void {
|
||||
// Used for amp-story[poster-portrait-src]: The story poster in portrait format (3x4 aspect ratio).
|
||||
add_image_size(
|
||||
self::POSTER_PORTRAIT_IMAGE_SIZE,
|
||||
self::POSTER_PORTRAIT_IMAGE_DIMENSIONS[0],
|
||||
self::POSTER_PORTRAIT_IMAGE_DIMENSIONS[1],
|
||||
true
|
||||
);
|
||||
|
||||
// As per https://amp.dev/documentation/components/amp-story/#publisher-logo-src-guidelines.
|
||||
add_image_size(
|
||||
self::PUBLISHER_LOGO_IMAGE_SIZE,
|
||||
self::PUBLISHER_LOGO_IMAGE_DIMENSIONS[0],
|
||||
self::PUBLISHER_LOGO_IMAGE_DIMENSIONS[1],
|
||||
true
|
||||
);
|
||||
|
||||
// Used in the editor.
|
||||
add_image_size(
|
||||
self::STORY_THUMBNAIL_IMAGE_SIZE,
|
||||
self::STORY_THUMBNAIL_IMAGE_DIMENSIONS[0],
|
||||
self::STORY_THUMBNAIL_IMAGE_DIMENSIONS[1],
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,403 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Media_Source_Taxonomy
|
||||
*
|
||||
* @link https://github.com/googleforcreators/web-stories-wp
|
||||
*
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright 2021 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\Media;
|
||||
|
||||
use Google\Web_Stories\Context;
|
||||
use Google\Web_Stories\REST_API\Stories_Terms_Controller;
|
||||
use Google\Web_Stories\Taxonomy\Taxonomy_Base;
|
||||
use ReflectionClass;
|
||||
use WP_Post;
|
||||
use WP_Query;
|
||||
use WP_Site;
|
||||
|
||||
/**
|
||||
* Class Media_Source_Taxonomy
|
||||
*
|
||||
* @phpstan-import-type TaxonomyArgs from \Google\Web_Stories\Taxonomy\Taxonomy_Base
|
||||
*/
|
||||
class Media_Source_Taxonomy extends Taxonomy_Base {
|
||||
public const TERM_EDITOR = 'editor';
|
||||
public const TERM_POSTER_GENERATION = 'poster-generation';
|
||||
public const TERM_SOURCE_VIDEO = 'source-video';
|
||||
public const TERM_SOURCE_IMAGE = 'source-image';
|
||||
public const TERM_VIDEO_OPTIMIZATION = 'video-optimization';
|
||||
public const TERM_PAGE_TEMPLATE = 'page-template';
|
||||
public const TERM_GIF_CONVERSION = 'gif-conversion';
|
||||
public const TERM_RECORDING = 'recording';
|
||||
|
||||
/**
|
||||
* Media Source key.
|
||||
*/
|
||||
public const MEDIA_SOURCE_KEY = 'web_stories_media_source';
|
||||
|
||||
/**
|
||||
* Context instance.
|
||||
*/
|
||||
private Context $context;
|
||||
|
||||
/**
|
||||
* Single constructor.
|
||||
*
|
||||
* @param Context $context Context instance.
|
||||
*/
|
||||
public function __construct( Context $context ) {
|
||||
$this->context = $context;
|
||||
$this->taxonomy_slug = 'web_story_media_source';
|
||||
$this->taxonomy_post_type = 'attachment';
|
||||
}
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @since 1.10.0
|
||||
*/
|
||||
public function register(): void {
|
||||
$this->register_taxonomy();
|
||||
|
||||
add_action( 'rest_api_init', [ $this, 'rest_api_init' ] );
|
||||
add_filter( 'wp_prepare_attachment_for_js', [ $this, 'wp_prepare_attachment_for_js' ] );
|
||||
|
||||
// Hide video posters from Media grid view.
|
||||
add_filter( 'ajax_query_attachments_args', [ $this, 'filter_ajax_query_attachments_args' ], PHP_INT_MAX );
|
||||
// Hide video posters from Media list view.
|
||||
add_action( 'pre_get_posts', [ $this, 'filter_generated_media_attachments' ], PHP_INT_MAX );
|
||||
// Hide video posters from web-stories/v1/media REST API requests.
|
||||
add_filter( 'web_stories_rest_attachment_query', [ $this, 'filter_rest_generated_media_attachments' ], PHP_INT_MAX );
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on site initialization.
|
||||
*
|
||||
* @since 1.29.0
|
||||
*
|
||||
* @param WP_Site $site The site being initialized.
|
||||
*/
|
||||
public function on_site_initialization( WP_Site $site ): void {
|
||||
parent::on_site_initialization( $site );
|
||||
|
||||
$this->add_missing_terms();
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on plugin activation.
|
||||
*
|
||||
* @since 1.29.0
|
||||
*
|
||||
* @param bool $network_wide Whether the activation was done network-wide.
|
||||
*/
|
||||
public function on_plugin_activation( bool $network_wide ): void {
|
||||
parent::on_plugin_activation( $network_wide );
|
||||
|
||||
$this->add_missing_terms();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all defined media source term names.
|
||||
*
|
||||
* @since 1.29.0
|
||||
*
|
||||
* @return string[] Media sources
|
||||
*/
|
||||
public function get_all_terms(): array {
|
||||
$consts = ( new ReflectionClass( $this ) )->getConstants();
|
||||
|
||||
/**
|
||||
* List of terms.
|
||||
*
|
||||
* @var string[] $terms
|
||||
*/
|
||||
$terms = array_values(
|
||||
array_filter(
|
||||
$consts,
|
||||
static fn( $key ) => str_starts_with( $key, 'TERM_' ),
|
||||
ARRAY_FILTER_USE_KEY
|
||||
)
|
||||
);
|
||||
|
||||
return $terms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional REST API fields upon API initialization.
|
||||
*
|
||||
* @since 1.10.0
|
||||
*/
|
||||
public function rest_api_init(): void {
|
||||
// Custom field, as built in term update require term id and not slug.
|
||||
register_rest_field(
|
||||
$this->taxonomy_post_type,
|
||||
self::MEDIA_SOURCE_KEY,
|
||||
[
|
||||
|
||||
'get_callback' => [ $this, 'get_callback_media_source' ],
|
||||
'schema' => [
|
||||
'description' => __( 'Media source.', 'web-stories' ),
|
||||
'type' => 'string',
|
||||
'enum' => $this->get_all_terms(),
|
||||
'context' => [ 'view', 'edit', 'embed' ],
|
||||
],
|
||||
'update_callback' => [ $this, 'update_callback_media_source' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the attachment data prepared for JavaScript.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param array|mixed $response Array of prepared attachment data.
|
||||
* @return array|mixed $response Filtered attachment data.
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @phpstan-return ($response is array<T> ? array<T> : mixed)
|
||||
*/
|
||||
public function wp_prepare_attachment_for_js( $response ) {
|
||||
if ( ! \is_array( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response[ self::MEDIA_SOURCE_KEY ] = $this->get_callback_media_source( $response );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force media attachment as string instead of the default array.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param array<string, mixed> $prepared Prepared data before response.
|
||||
*/
|
||||
public function get_callback_media_source( array $prepared ): string {
|
||||
/**
|
||||
* Taxonomy ID.
|
||||
*
|
||||
* @var int $id
|
||||
*/
|
||||
$id = $prepared['id'];
|
||||
|
||||
$terms = get_the_terms( $id, $this->taxonomy_slug );
|
||||
if ( \is_array( $terms ) && ! empty( $terms ) ) {
|
||||
return array_shift( $terms )->slug;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Update rest field callback.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param string $value Value to update.
|
||||
* @param WP_Post $post Object to update on.
|
||||
* @return true|\WP_Error
|
||||
*/
|
||||
public function update_callback_media_source( string $value, WP_Post $post ) {
|
||||
$check = wp_set_object_terms( $post->ID, $value, $this->taxonomy_slug );
|
||||
if ( is_wp_error( $check ) ) {
|
||||
return $check;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the attachment query args to hide generated video poster images.
|
||||
*
|
||||
* Reduces unnecessary noise in the Media grid view.
|
||||
*
|
||||
* @since 1.10.0
|
||||
*
|
||||
* @param array<string, mixed>|mixed $args Query args.
|
||||
* @return array<string, mixed>|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 ) ) {
|
||||
return $args;
|
||||
}
|
||||
|
||||
$args['tax_query'] = $this->get_exclude_tax_query( $args ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the current query to hide generated video poster images and source video.
|
||||
*
|
||||
* Reduces unnecessary noise in the Media list view.
|
||||
*
|
||||
* @since 1.10.0
|
||||
*
|
||||
* @param WP_Query $query WP_Query instance, passed by reference.
|
||||
*/
|
||||
public function filter_generated_media_attachments( WP_Query $query ): void {
|
||||
if ( is_admin() && $query->is_main_query() && $this->context->is_upload_screen() ) {
|
||||
$tax_query = $query->get( 'tax_query' );
|
||||
|
||||
$query->set( 'tax_query', $this->get_exclude_tax_query( [ 'tax_query' => $tax_query ] ) ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the current query to hide generated video poster images.
|
||||
*
|
||||
* Reduces unnecessary noise in media REST API requests.
|
||||
*
|
||||
* @since 1.10.0
|
||||
*
|
||||
* @param array<string, mixed>|mixed $args Query args.
|
||||
* @return array<string, mixed>|mixed Filtered query args.
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @phpstan-return ($args is array<T> ? array<T> : mixed)
|
||||
*/
|
||||
public function filter_rest_generated_media_attachments( $args ) {
|
||||
if ( ! \is_array( $args ) ) {
|
||||
return $args;
|
||||
}
|
||||
|
||||
$args['tax_query'] = $this->get_exclude_tax_query( $args ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds missing terms to the taxonomy.
|
||||
*
|
||||
* @since 1.29.0
|
||||
*/
|
||||
private function add_missing_terms(): void {
|
||||
$existing_terms = get_terms(
|
||||
[
|
||||
'taxonomy' => $this->get_taxonomy_slug(),
|
||||
'hide_empty' => false,
|
||||
'fields' => 'slugs',
|
||||
]
|
||||
);
|
||||
|
||||
if ( is_wp_error( $existing_terms ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$missing_terms = array_diff( $this->get_all_terms(), $existing_terms );
|
||||
|
||||
foreach ( $missing_terms as $term ) {
|
||||
wp_insert_term( $term, $this->get_taxonomy_slug() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Taxonomy args.
|
||||
*
|
||||
* @since 1.12.0
|
||||
*
|
||||
* @return array<string,mixed> Taxonomy args.
|
||||
*
|
||||
* @phpstan-return TaxonomyArgs
|
||||
*/
|
||||
protected function taxonomy_args(): array {
|
||||
return [
|
||||
'label' => __( 'Source', 'web-stories' ),
|
||||
'public' => false,
|
||||
'rewrite' => false,
|
||||
'hierarchical' => false,
|
||||
'show_in_rest' => true,
|
||||
'rest_namespace' => self::REST_NAMESPACE,
|
||||
'rest_controller_class' => Stories_Terms_Controller::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tax query needed to exclude generated video poster images and source videos.
|
||||
*
|
||||
* @param array<string, mixed> $args Existing WP_Query args.
|
||||
* @return array<int|string, mixed> Tax query arg.
|
||||
*/
|
||||
private function get_exclude_tax_query( array $args ): array {
|
||||
/**
|
||||
* Tax query.
|
||||
*
|
||||
* @var array<int|string, mixed> $tax_query
|
||||
*/
|
||||
$tax_query = ! empty( $args['tax_query'] ) ? $args['tax_query'] : [];
|
||||
|
||||
/**
|
||||
* Filter whether generated attachments should be hidden in the media library.
|
||||
*
|
||||
* @since 1.16.0
|
||||
*
|
||||
* @param bool $enabled Whether the taxonomy check should be applied.
|
||||
* @param array $args Existing WP_Query args.
|
||||
*/
|
||||
$enabled = apply_filters( 'web_stories_hide_auto_generated_attachments', true, $args );
|
||||
if ( true !== $enabled ) {
|
||||
return $tax_query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge with existing tax query if needed,
|
||||
* in a nested way so WordPress will run them
|
||||
* with an 'AND' relation. Example:
|
||||
*
|
||||
* [
|
||||
* 'relation' => 'AND', // implicit.
|
||||
* [ this query ],
|
||||
* [ [ any ], [ existing ], [ tax queries] ]
|
||||
* ]
|
||||
*/
|
||||
$new_tax_query = [
|
||||
'relation' => 'AND',
|
||||
[
|
||||
'taxonomy' => $this->taxonomy_slug,
|
||||
'field' => 'slug',
|
||||
'terms' => [
|
||||
self::TERM_POSTER_GENERATION,
|
||||
self::TERM_SOURCE_VIDEO,
|
||||
self::TERM_SOURCE_IMAGE,
|
||||
self::TERM_PAGE_TEMPLATE,
|
||||
],
|
||||
'operator' => 'NOT IN',
|
||||
],
|
||||
];
|
||||
|
||||
if ( ! empty( $tax_query ) ) {
|
||||
$new_tax_query[] = [ $tax_query ];
|
||||
}
|
||||
|
||||
return $new_tax_query;
|
||||
}
|
||||
}
|
420
wp-content/plugins/web-stories/includes/Media/SVG.php
Normal file
420
wp-content/plugins/web-stories/includes/Media/SVG.php
Normal file
@@ -0,0 +1,420 @@
|
||||
<?php
|
||||
/**
|
||||
* Class SVG.
|
||||
*
|
||||
* @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\Media;
|
||||
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use Google\Web_Stories\Experiments;
|
||||
use Google\Web_Stories\Service_Base;
|
||||
use Google\Web_Stories_Dependencies\enshrined\svgSanitize\Sanitizer;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Class SVG
|
||||
*
|
||||
* @since 1.3.0
|
||||
*/
|
||||
class SVG extends Service_Base {
|
||||
/**
|
||||
* File extension.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public const EXT = 'svg';
|
||||
|
||||
/**
|
||||
* Mime type.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public const MIME_TYPE = 'image/svg+xml';
|
||||
|
||||
/**
|
||||
* Cached list of SVG files and their contents.
|
||||
* Speeds up access during the same request.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $svgs = [];
|
||||
|
||||
/**
|
||||
* Experiments instance.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*
|
||||
* @var Experiments Experiments instance.
|
||||
*/
|
||||
private Experiments $experiments;
|
||||
|
||||
/**
|
||||
* SVG constructor.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*
|
||||
* @param Experiments $experiments Experiments instance.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( Experiments $experiments ) {
|
||||
$this->experiments = $experiments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register filters and actions.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public function register(): void {
|
||||
if ( ! $this->experiments->is_experiment_enabled( 'enableSVG' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_filter( 'web_stories_allowed_mime_types', [ $this, 'web_stories_allowed_mime_types' ] );
|
||||
|
||||
// Check if svg uploads, already enabled.
|
||||
if ( $this->svg_already_enabled() ) {
|
||||
add_filter( 'mime_types', [ $this, 'mime_types_add_svg' ] );
|
||||
return;
|
||||
}
|
||||
|
||||
add_filter( 'upload_mimes', [ $this, 'upload_mimes_add_svg' ] ); // phpcs:ignore WordPressVIPMinimum.Hooks.RestrictedHooks.upload_mimes
|
||||
add_filter( 'mime_types', [ $this, 'mime_types_add_svg' ] );
|
||||
add_filter( 'wp_handle_upload_prefilter', [ $this, 'wp_handle_upload' ] );
|
||||
add_filter( 'wp_generate_attachment_metadata', [ $this, 'wp_generate_attachment_metadata' ], 10, 3 );
|
||||
add_filter( 'wp_check_filetype_and_ext', [ $this, 'wp_check_filetype_and_ext' ], 10, 5 );
|
||||
add_filter( 'site_option_upload_filetypes', [ $this, 'filter_list_of_allowed_filetypes' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable SVG upload.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*
|
||||
* @param array<string, string> $mime_types Mime types keyed by the file extension regex corresponding to those types.
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function upload_mimes_add_svg( array $mime_types ): array {
|
||||
// allow SVG file upload.
|
||||
$mime_types['svg'] = self::MIME_TYPE;
|
||||
$mime_types['svgz'] = self::MIME_TYPE;
|
||||
|
||||
return $mime_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds SVG to list of mime types and file extensions
|
||||
*
|
||||
* @since 1.3.0
|
||||
*
|
||||
* @param string[] $mime_types Mime types keyed by the file extension regex
|
||||
* corresponding to those types.
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function mime_types_add_svg( array $mime_types ): array {
|
||||
// allow SVG files.
|
||||
$mime_types['svg'] = self::MIME_TYPE;
|
||||
|
||||
return array_unique( $mime_types );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add SVG to allowed mime types.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*
|
||||
* @param array<string, string[]> $mime_types Associative array of allowed mime types per media type (image, audio, video).
|
||||
* @return array<string, string[]>
|
||||
*/
|
||||
public function web_stories_allowed_mime_types( array $mime_types ): array {
|
||||
$mime_types['vector'][] = self::MIME_TYPE;
|
||||
|
||||
return $mime_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add svg file type to allow file in multisite.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*
|
||||
* @param string $value List of allowed file types.
|
||||
* @return string List of allowed file types.
|
||||
*/
|
||||
public function filter_list_of_allowed_filetypes( string $value ): string {
|
||||
$filetypes = explode( ' ', $value );
|
||||
if ( ! \in_array( self::EXT, $filetypes, true ) ) {
|
||||
$filetypes[] = self::EXT;
|
||||
$value = implode( ' ', $filetypes );
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook into metadata generation and get height and width for SVG file.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*
|
||||
* @param array<string,mixed> $metadata An array of attachment meta data.
|
||||
* @param int $attachment_id Current attachment ID.
|
||||
* @param string $context Additional context. Can be 'create' when metadata
|
||||
* was initially created for new attachment.
|
||||
* @return array<string,mixed> Filtered metadata.
|
||||
*/
|
||||
public function wp_generate_attachment_metadata( array $metadata, int $attachment_id, string $context ): array {
|
||||
if ( 'create' !== $context ) {
|
||||
return $metadata;
|
||||
}
|
||||
$attachment = get_post( $attachment_id );
|
||||
$mime_type = get_post_mime_type( $attachment );
|
||||
|
||||
if ( self::MIME_TYPE !== $mime_type ) {
|
||||
return $metadata;
|
||||
}
|
||||
$file = get_attached_file( $attachment_id );
|
||||
if ( false === $file ) {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
$size = $this->get_svg_size( $file );
|
||||
// Check if image size failed to generate and return if so.
|
||||
if ( is_wp_error( $size ) ) {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
return [
|
||||
'width' => (int) $size['width'],
|
||||
'height' => (int) $size['height'],
|
||||
'file' => _wp_relative_upload_path( $file ),
|
||||
'filesize' => (int) filesize( $file ),
|
||||
'sizes' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook into upload and error if size could not be generated.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*
|
||||
* @param array $upload {
|
||||
* Array of upload data.
|
||||
*
|
||||
* @type string $file Filename of the newly-uploaded file.
|
||||
* @type string $url URL of the newly-uploaded file.
|
||||
* @type string $type Mime type of the newly-uploaded file.
|
||||
* @type string $tmp_name Temporary file name.
|
||||
* }
|
||||
* @return string[]
|
||||
*
|
||||
* @phpstan-param array{file: string, url: string, type: string, tmp_name: string} $upload
|
||||
*/
|
||||
public function wp_handle_upload( array $upload ): array {
|
||||
if ( self::MIME_TYPE !== $upload['type'] ) {
|
||||
return $upload;
|
||||
}
|
||||
|
||||
$sanitized = $this->sanitize( $upload['tmp_name'] );
|
||||
if ( is_wp_error( $sanitized ) ) {
|
||||
return [ 'error' => $sanitized->get_error_message() ];
|
||||
}
|
||||
|
||||
$size = $this->get_svg_size( $upload['tmp_name'] );
|
||||
if ( is_wp_error( $size ) ) {
|
||||
return [ 'error' => $size->get_error_message() ];
|
||||
}
|
||||
|
||||
return $upload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Work around for incorrect mime type.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*
|
||||
* @param array $wp_check_filetype_and_ext {
|
||||
* Values for the extension, mime type, and corrected filename.
|
||||
*
|
||||
* @type string|false $ext File extension, or false if the file doesn't match a mime type.
|
||||
* @type string|false $type File mime type, or false if the file doesn't match a mime type.
|
||||
* @type string|false $proper_filename File name with its correct extension, or false if it cannot be
|
||||
* determined.
|
||||
* }
|
||||
* @param string $file Full path to the file.
|
||||
* @param string $filename The name of the file (may differ from $file due to
|
||||
* $file being in a tmp directory).
|
||||
* @param string[]|null|false $mimes Array of mime types keyed by their file extension regex.
|
||||
* @param string|bool $real_mime The actual mime type or false if the type cannot be determined.
|
||||
* @return array{ext?: string, type?: string, proper_filename?: bool}
|
||||
*
|
||||
* @phpstan-param array{ext?: string, type?: string, proper_filename?: bool} $wp_check_filetype_and_ext
|
||||
*/
|
||||
public function wp_check_filetype_and_ext( array $wp_check_filetype_and_ext, string $file, string $filename, $mimes, $real_mime ): array {
|
||||
if ( 'image/svg' === $real_mime ) {
|
||||
$wp_check_filetype_and_ext = [
|
||||
'ext' => self::EXT,
|
||||
'type' => self::MIME_TYPE,
|
||||
'proper_filename' => false,
|
||||
];
|
||||
}
|
||||
|
||||
return $wp_check_filetype_and_ext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to check if svg uploads are already enabled.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*/
|
||||
private function svg_already_enabled(): bool {
|
||||
$allowed_mime_types = get_allowed_mime_types();
|
||||
$mime_types = array_values( $allowed_mime_types );
|
||||
|
||||
return \in_array( self::MIME_TYPE, $mime_types, true );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get SVG image size.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.NPathComplexity)
|
||||
*
|
||||
* @since 1.3.0
|
||||
*
|
||||
* @param string $file Path to SVG file.
|
||||
* @return array|WP_Error
|
||||
*
|
||||
* @phpstan-return array{width: int, height: int}|WP_Error
|
||||
*/
|
||||
protected function get_svg_size( string $file ) {
|
||||
$svg = $this->get_svg_data( $file );
|
||||
$xml = $this->get_xml( $svg );
|
||||
|
||||
if ( false === $xml ) {
|
||||
return new \WP_Error( 'invalid_xml_svg', __( 'Invalid XML in SVG.', 'web-stories' ) );
|
||||
}
|
||||
|
||||
$width = (int) $xml->getAttribute( 'width' );
|
||||
$height = (int) $xml->getAttribute( 'height' );
|
||||
|
||||
// If height and width are not set, try the viewport attribute.
|
||||
if ( ! $width || ! $height ) {
|
||||
$view_box = $xml->getAttribute( 'viewBox' );
|
||||
if ( empty( $view_box ) ) {
|
||||
$view_box = $xml->getAttribute( 'viewbox' );
|
||||
}
|
||||
$pieces = explode( ' ', $view_box );
|
||||
if ( 4 === \count( $pieces ) ) {
|
||||
[, , $width, $height] = $pieces;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $width || ! $height ) {
|
||||
return new \WP_Error( 'invalid_svg_size', __( 'Unable to generate SVG image size.', 'web-stories' ) );
|
||||
}
|
||||
|
||||
return array_map( 'absint', compact( 'width', 'height' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the SVG
|
||||
*
|
||||
* @since 1.3.0
|
||||
*
|
||||
* @param string $file File path.
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
protected function sanitize( string $file ) {
|
||||
$dirty = $this->get_svg_data( $file );
|
||||
$sanitizer = new Sanitizer();
|
||||
$clean = $sanitizer->sanitize( $dirty );
|
||||
|
||||
if ( empty( $clean ) ) {
|
||||
return new \WP_Error( 'invalid_xml_svg', __( 'Invalid XML in SVG.', 'web-stories' ) );
|
||||
}
|
||||
|
||||
$errors = $sanitizer->getXmlIssues();
|
||||
if ( \count( $errors ) > 1 ) {
|
||||
return new \WP_Error( 'insecure_svg_file', __( "Sorry, this file couldn't be sanitized so for security reasons wasn't uploaded.", 'web-stories' ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get xml document.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*
|
||||
* @param string $svg String of xml.
|
||||
* @return DOMElement|false
|
||||
*/
|
||||
protected function get_xml( string $svg ) {
|
||||
$dom = new DOMDocument();
|
||||
$dom->preserveWhiteSpace = false;
|
||||
$dom->strictErrorChecking = false;
|
||||
|
||||
$errors = libxml_use_internal_errors( true );
|
||||
$loaded = $dom->loadXML( $svg );
|
||||
if ( ! $loaded ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$node = $dom->getElementsByTagName( 'svg' )->item( 0 );
|
||||
|
||||
libxml_clear_errors();
|
||||
libxml_use_internal_errors( $errors );
|
||||
|
||||
if ( ! $node ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SVG data.
|
||||
*
|
||||
* @since 1.3.0
|
||||
*
|
||||
* @param string $file File path.
|
||||
* @return string File contents.
|
||||
*/
|
||||
protected function get_svg_data( string $file ): string {
|
||||
$key = md5( $file );
|
||||
if ( ! isset( $this->svgs[ $key ] ) ) {
|
||||
if ( is_readable( $file ) ) {
|
||||
$this->svgs[ $key ] = (string) file_get_contents( $file ); // phpcs:ignore WordPressVIPMinimum.Performance.FetchingRemoteData.FileGetContentsUnknown
|
||||
} else {
|
||||
$this->svgs[ $key ] = '';
|
||||
}
|
||||
}
|
||||
|
||||
return $this->svgs[ $key ];
|
||||
}
|
||||
}
|
115
wp-content/plugins/web-stories/includes/Media/Types.php
Normal file
115
wp-content/plugins/web-stories/includes/Media/Types.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Types
|
||||
*
|
||||
* @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\Media;
|
||||
|
||||
/**
|
||||
* Class Types
|
||||
*/
|
||||
class Types {
|
||||
/**
|
||||
* Returns a list of allowed file types.
|
||||
*
|
||||
* @since 1.5.0
|
||||
*
|
||||
* @param string[] $mime_types Array of mime types.
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_file_type_exts( array $mime_types = [] ): array {
|
||||
$allowed_file_types = [];
|
||||
$all_mime_types = get_allowed_mime_types();
|
||||
|
||||
foreach ( $all_mime_types as $ext => $mime ) {
|
||||
if ( \in_array( $mime, $mime_types, true ) ) {
|
||||
array_push( $allowed_file_types, ...explode( '|', $ext ) );
|
||||
}
|
||||
}
|
||||
sort( $allowed_file_types );
|
||||
|
||||
return $allowed_file_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of allowed mime types per media type (image, audio, video).
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return array<string, string[]> List of allowed mime types.
|
||||
*/
|
||||
public function get_allowed_mime_types(): array {
|
||||
$default_allowed_mime_types = [
|
||||
'image' => [
|
||||
'image/webp',
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/jpg',
|
||||
'image/gif',
|
||||
],
|
||||
'audio' => [
|
||||
'audio/mpeg',
|
||||
'audio/aac',
|
||||
'audio/wav',
|
||||
'audio/ogg',
|
||||
],
|
||||
'caption' => [ 'text/vtt' ],
|
||||
'vector' => [],
|
||||
'video' => [
|
||||
'video/mp4',
|
||||
'video/webm',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Filter list of allowed mime types.
|
||||
*
|
||||
* This can be used to add additionally supported formats, for example by plugins
|
||||
* that do video transcoding.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param array<string, string[]> $default_allowed_mime_types Associative array of allowed mime types per media type (image, audio, video).
|
||||
*/
|
||||
$allowed_mime_types = apply_filters( 'web_stories_allowed_mime_types', $default_allowed_mime_types );
|
||||
|
||||
/**
|
||||
* Media type.
|
||||
*
|
||||
* @var string $media_type
|
||||
*/
|
||||
foreach ( array_keys( $default_allowed_mime_types ) as $media_type ) {
|
||||
if ( ! \is_array( $allowed_mime_types[ $media_type ] ) || empty( $allowed_mime_types[ $media_type ] ) ) {
|
||||
$allowed_mime_types[ $media_type ] = $default_allowed_mime_types[ $media_type ];
|
||||
}
|
||||
|
||||
// Only add currently supported mime types.
|
||||
$allowed_mime_types[ $media_type ] = array_values( array_intersect( $allowed_mime_types[ $media_type ], get_allowed_mime_types() ) );
|
||||
}
|
||||
|
||||
return $allowed_mime_types;
|
||||
}
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Captions
|
||||
*
|
||||
* @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\Media\Video;
|
||||
|
||||
use Google\Web_Stories\Service_Base;
|
||||
|
||||
/**
|
||||
* Class Captions
|
||||
*/
|
||||
class Captions extends Service_Base {
|
||||
/**
|
||||
* Initializes the File_Type logic.
|
||||
*
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public function register(): void {
|
||||
add_filter( 'site_option_upload_filetypes', [ $this, 'filter_list_of_allowed_filetypes' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add VTT file type to allow file in multisite.
|
||||
*
|
||||
* @param string|mixed $value List of allowed file types.
|
||||
* @return string|mixed List of allowed file types.
|
||||
*/
|
||||
public function filter_list_of_allowed_filetypes( $value ) {
|
||||
if ( ! \is_string( $value ) ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$filetypes = explode( ' ', $value );
|
||||
if ( ! \in_array( 'vtt', $filetypes, true ) ) {
|
||||
$filetypes[] = 'vtt';
|
||||
$value = implode( ' ', $filetypes );
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
111
wp-content/plugins/web-stories/includes/Media/Video/Is_Gif.php
Normal file
111
wp-content/plugins/web-stories/includes/Media/Video/Is_Gif.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Is_Gif
|
||||
*
|
||||
* @link https://github.com/googleforcreators/web-stories-wp
|
||||
*
|
||||
* @copyright 2022 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright 2022 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\Media\Video;
|
||||
|
||||
use Google\Web_Stories\Infrastructure\HasMeta;
|
||||
use Google\Web_Stories\Infrastructure\PluginUninstallAware;
|
||||
use Google\Web_Stories\Service_Base;
|
||||
|
||||
/**
|
||||
* Class Is_Gif
|
||||
*/
|
||||
class Is_Gif extends Service_Base implements HasMeta, PluginUninstallAware {
|
||||
/**
|
||||
* The post meta key.
|
||||
*/
|
||||
public const IS_GIF_POST_META_KEY = 'web_stories_is_gif';
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @since 1.23.0
|
||||
*/
|
||||
public function register(): void {
|
||||
$this->register_meta();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register post meta
|
||||
*
|
||||
* @since 1.23.0
|
||||
*/
|
||||
public function register_meta(): void {
|
||||
register_post_meta(
|
||||
'attachment',
|
||||
self::IS_GIF_POST_META_KEY,
|
||||
[
|
||||
'sanitize_callback' => 'rest_sanitize_boolean',
|
||||
'type' => 'boolean',
|
||||
'description' => __( 'Whether the video is to be considered a GIF', 'web-stories' ),
|
||||
'show_in_rest' => true,
|
||||
'default' => false,
|
||||
'single' => true,
|
||||
'object_subtype' => 'attachment',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the attachment data prepared for JavaScript.
|
||||
*
|
||||
* @since 1.23.0
|
||||
*
|
||||
* @param array|mixed $response Array of prepared attachment data.
|
||||
* @return array|mixed Response data.
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @phpstan-return ($response is array<T> ? array<T> : mixed)
|
||||
*/
|
||||
public function wp_prepare_attachment_for_js( $response ) {
|
||||
if ( ! \is_array( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Post ID.
|
||||
*
|
||||
* @var int $post_id
|
||||
*/
|
||||
$post_id = $response['id'];
|
||||
|
||||
$response[ self::IS_GIF_POST_META_KEY ] = get_post_meta( $post_id, self::IS_GIF_POST_META_KEY, true );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on plugin uninstall.
|
||||
*
|
||||
* @since 1.26.0
|
||||
*/
|
||||
public function on_plugin_uninstall(): void {
|
||||
delete_post_meta_by_key( self::IS_GIF_POST_META_KEY );
|
||||
}
|
||||
}
|
231
wp-content/plugins/web-stories/includes/Media/Video/Muting.php
Normal file
231
wp-content/plugins/web-stories/includes/Media/Video/Muting.php
Normal file
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Muting
|
||||
*
|
||||
* @link https://github.com/googleforcreators/web-stories-wp
|
||||
*
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright 2021 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\Media\Video;
|
||||
|
||||
use Google\Web_Stories\Infrastructure\HasMeta;
|
||||
use Google\Web_Stories\Infrastructure\PluginUninstallAware;
|
||||
use Google\Web_Stories\Service_Base;
|
||||
use WP_Error;
|
||||
use WP_Post;
|
||||
|
||||
/**
|
||||
* Class Muting
|
||||
*/
|
||||
class Muting extends Service_Base implements HasMeta, PluginUninstallAware {
|
||||
|
||||
/**
|
||||
* Is muted.
|
||||
*/
|
||||
public const IS_MUTED_POST_META_KEY = 'web_stories_is_muted';
|
||||
|
||||
/**
|
||||
* The muted video id post meta key.
|
||||
*/
|
||||
public const MUTED_ID_POST_META_KEY = 'web_stories_muted_id';
|
||||
|
||||
/**
|
||||
* Is muted.
|
||||
*/
|
||||
public const IS_MUTED_REST_API_KEY = 'web_stories_is_muted';
|
||||
|
||||
/**
|
||||
* Register.
|
||||
*
|
||||
* @since 1.10.0
|
||||
*/
|
||||
public function register(): void {
|
||||
$this->register_meta();
|
||||
|
||||
add_action( 'delete_attachment', [ $this, 'delete_video' ] );
|
||||
add_action( 'rest_api_init', [ $this, 'rest_api_init' ] );
|
||||
add_filter( 'wp_prepare_attachment_for_js', [ $this, 'wp_prepare_attachment_for_js' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register meta for attachment post type.
|
||||
*
|
||||
* @since 1.10.0
|
||||
*/
|
||||
public function register_meta(): void {
|
||||
register_meta(
|
||||
'post',
|
||||
self::IS_MUTED_POST_META_KEY,
|
||||
[
|
||||
'type' => 'boolean',
|
||||
'description' => __( 'Whether the video is muted', 'web-stories' ),
|
||||
'default' => false,
|
||||
'single' => true,
|
||||
'object_subtype' => 'attachment',
|
||||
]
|
||||
);
|
||||
|
||||
register_meta(
|
||||
'post',
|
||||
self::MUTED_ID_POST_META_KEY,
|
||||
[
|
||||
'sanitize_callback' => 'absint',
|
||||
'type' => 'integer',
|
||||
'description' => __( 'ID of muted video.', 'web-stories' ),
|
||||
'show_in_rest' => true,
|
||||
'default' => 0,
|
||||
'single' => true,
|
||||
'object_subtype' => 'attachment',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional REST API fields upon API initialization.
|
||||
*
|
||||
* @since 1.10.0
|
||||
*/
|
||||
public function rest_api_init(): void {
|
||||
register_rest_field(
|
||||
'attachment',
|
||||
self::IS_MUTED_REST_API_KEY,
|
||||
[
|
||||
'get_callback' => [ $this, 'get_callback_is_muted' ],
|
||||
'schema' => [
|
||||
'type' => [ 'boolean', 'null' ],
|
||||
'description' => __( 'Whether the video is muted', 'web-stories' ),
|
||||
'default' => null,
|
||||
'context' => [ 'view', 'edit', 'embed' ],
|
||||
'arg_options' => [
|
||||
'sanitize_callback' => 'rest_sanitize_boolean',
|
||||
],
|
||||
],
|
||||
'update_callback' => [ $this, 'update_callback_is_muted' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the attachment data prepared for JavaScript.
|
||||
*
|
||||
* @since 1.10.0
|
||||
*
|
||||
* @param array|mixed $response Array of prepared attachment data.
|
||||
* @return array|mixed Response data.
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @phpstan-return ($response is array<T> ? array<T> : mixed)
|
||||
*/
|
||||
public function wp_prepare_attachment_for_js( $response ) {
|
||||
if ( ! \is_array( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
if ( 'video' === $response['type'] ) {
|
||||
$response[ self::IS_MUTED_REST_API_KEY ] = $this->get_callback_is_muted( $response );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attachment's post meta.
|
||||
*
|
||||
* @since 1.10.0
|
||||
*
|
||||
* @param array<string, mixed> $prepared Array of data to add to.
|
||||
*/
|
||||
public function get_callback_is_muted( array $prepared ): ?bool {
|
||||
/**
|
||||
* Attachment ID.
|
||||
*
|
||||
* @var int $id
|
||||
*/
|
||||
$id = $prepared['id'];
|
||||
|
||||
/**
|
||||
* Muted value.
|
||||
*
|
||||
* @var bool|null $value
|
||||
*/
|
||||
$value = get_metadata_raw( 'post', $id, self::IS_MUTED_POST_META_KEY, true );
|
||||
|
||||
if ( null === $value ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
return rest_sanitize_boolean( $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the attachment's post meta.
|
||||
*
|
||||
* @since 1.10.0
|
||||
*
|
||||
* @param mixed $value Value to updated.
|
||||
* @param WP_Post $post Post object to be updated.
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public function update_callback_is_muted( $value, WP_Post $post ) {
|
||||
$object_id = $post->ID;
|
||||
$name = self::IS_MUTED_REST_API_KEY;
|
||||
$meta_key = self::IS_MUTED_POST_META_KEY;
|
||||
|
||||
if ( ! current_user_can( 'edit_post_meta', $object_id, $meta_key ) ) {
|
||||
return new \WP_Error(
|
||||
'rest_cannot_update',
|
||||
/* translators: %s: Custom field key.**/
|
||||
sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.', 'web-stories' ), $name ),
|
||||
[
|
||||
'key' => $name,
|
||||
'status' => rest_authorization_required_code(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
update_post_meta( $object_id, $meta_key, $value );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes associated meta data when a video is deleted.
|
||||
*
|
||||
* @since 1.26.0
|
||||
*
|
||||
* @param int $attachment_id ID of the attachment to be deleted.
|
||||
*/
|
||||
public function delete_video( int $attachment_id ): void {
|
||||
delete_metadata( 'post', 0, self::MUTED_ID_POST_META_KEY, $attachment_id, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on plugin uninstall.
|
||||
*
|
||||
* @since 1.26.0
|
||||
*/
|
||||
public function on_plugin_uninstall(): void {
|
||||
delete_post_meta_by_key( self::MUTED_ID_POST_META_KEY );
|
||||
delete_post_meta_by_key( self::IS_MUTED_POST_META_KEY );
|
||||
}
|
||||
}
|
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Optimization
|
||||
*
|
||||
* @link https://github.com/googleforcreators/web-stories-wp
|
||||
*
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright 2021 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\Media\Video;
|
||||
|
||||
use Google\Web_Stories\Infrastructure\HasMeta;
|
||||
use Google\Web_Stories\Infrastructure\PluginUninstallAware;
|
||||
use Google\Web_Stories\Service_Base;
|
||||
|
||||
/**
|
||||
* Class Optimization
|
||||
*/
|
||||
class Optimization extends Service_Base implements HasMeta, PluginUninstallAware {
|
||||
|
||||
/**
|
||||
* The optimized video id post meta key.
|
||||
*/
|
||||
public const OPTIMIZED_ID_POST_META_KEY = 'web_stories_optimized_id';
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @since 1.10.0
|
||||
*/
|
||||
public function register(): void {
|
||||
$this->register_meta();
|
||||
add_action( 'delete_attachment', [ $this, 'delete_video' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register meta
|
||||
*
|
||||
* @since 1.15.0
|
||||
*/
|
||||
public function register_meta(): void {
|
||||
register_meta(
|
||||
'post',
|
||||
self::OPTIMIZED_ID_POST_META_KEY,
|
||||
[
|
||||
'sanitize_callback' => 'absint',
|
||||
'type' => 'integer',
|
||||
'description' => __( 'ID of optimized video.', 'web-stories' ),
|
||||
'show_in_rest' => true,
|
||||
'default' => 0,
|
||||
'single' => true,
|
||||
'object_subtype' => 'attachment',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes associated meta data when a video is deleted.
|
||||
*
|
||||
* @since 1.26.0
|
||||
*
|
||||
* @param int $attachment_id ID of the attachment to be deleted.
|
||||
*/
|
||||
public function delete_video( int $attachment_id ): void {
|
||||
delete_metadata( 'post', 0, self::OPTIMIZED_ID_POST_META_KEY, $attachment_id, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on plugin uninstall.
|
||||
*
|
||||
* @since 1.26.0
|
||||
*/
|
||||
public function on_plugin_uninstall(): void {
|
||||
delete_post_meta_by_key( self::OPTIMIZED_ID_POST_META_KEY );
|
||||
}
|
||||
}
|
279
wp-content/plugins/web-stories/includes/Media/Video/Poster.php
Normal file
279
wp-content/plugins/web-stories/includes/Media/Video/Poster.php
Normal file
@@ -0,0 +1,279 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Poster
|
||||
*
|
||||
* @link https://github.com/googleforcreators/web-stories-wp
|
||||
*
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright 2021 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\Media\Video;
|
||||
|
||||
use Google\Web_Stories\Infrastructure\HasMeta;
|
||||
use Google\Web_Stories\Infrastructure\PluginUninstallAware;
|
||||
use Google\Web_Stories\Media\Media_Source_Taxonomy;
|
||||
use Google\Web_Stories\Service_Base;
|
||||
use WP_Post;
|
||||
|
||||
/**
|
||||
* Class Poster
|
||||
*/
|
||||
class Poster extends Service_Base implements HasMeta, PluginUninstallAware {
|
||||
/**
|
||||
* The poster post meta key.
|
||||
*/
|
||||
public const POSTER_POST_META_KEY = 'web_stories_is_poster';
|
||||
|
||||
/**
|
||||
* The poster id post meta key.
|
||||
*/
|
||||
public const POSTER_ID_POST_META_KEY = 'web_stories_poster_id';
|
||||
|
||||
/**
|
||||
* Media_Source_Taxonomy instance.
|
||||
*
|
||||
* @var Media_Source_Taxonomy Experiments instance.
|
||||
*/
|
||||
protected Media_Source_Taxonomy $media_source_taxonomy;
|
||||
|
||||
/**
|
||||
* Poster constructor.
|
||||
*
|
||||
* @since 1.12.0
|
||||
*
|
||||
* @param Media_Source_Taxonomy $media_source_taxonomy Media_Source_Taxonomy instance.
|
||||
*/
|
||||
public function __construct( Media_Source_Taxonomy $media_source_taxonomy ) {
|
||||
$this->media_source_taxonomy = $media_source_taxonomy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @since 1.10.0
|
||||
*/
|
||||
public function register(): void {
|
||||
$this->register_meta();
|
||||
add_action( 'rest_api_init', [ $this, 'rest_api_init' ] );
|
||||
add_action( 'delete_attachment', [ $this, 'delete_video_poster' ] );
|
||||
add_filter( 'wp_prepare_attachment_for_js', [ $this, 'wp_prepare_attachment_for_js' ], 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register meta for attachment post type.
|
||||
*
|
||||
* @since 1.10.0
|
||||
*/
|
||||
public function register_meta(): void {
|
||||
register_meta(
|
||||
'post',
|
||||
self::POSTER_ID_POST_META_KEY,
|
||||
[
|
||||
'sanitize_callback' => 'absint',
|
||||
'type' => 'integer',
|
||||
'description' => __( 'Attachment id of generated poster image.', 'web-stories' ),
|
||||
'show_in_rest' => true,
|
||||
'default' => 0,
|
||||
'single' => true,
|
||||
'object_subtype' => 'attachment',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers additional REST API fields upon API initialization.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function rest_api_init(): void {
|
||||
register_rest_field(
|
||||
'attachment',
|
||||
'featured_media',
|
||||
[
|
||||
'schema' => [
|
||||
'description' => __( 'The ID of the featured media for the object.', 'web-stories' ),
|
||||
'type' => 'integer',
|
||||
'context' => [ 'view', 'edit', 'embed' ],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
register_rest_field(
|
||||
'attachment',
|
||||
'featured_media_src',
|
||||
[
|
||||
'get_callback' => [ $this, 'get_callback_featured_media_src' ],
|
||||
'schema' => [
|
||||
'description' => __( 'URL, width and height.', 'web-stories' ),
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'src' => [
|
||||
'type' => 'string',
|
||||
'format' => 'uri',
|
||||
],
|
||||
'width' => [
|
||||
'type' => 'integer',
|
||||
],
|
||||
'height' => [
|
||||
'type' => 'integer',
|
||||
],
|
||||
'generated' => [
|
||||
'type' => 'boolean',
|
||||
],
|
||||
],
|
||||
'context' => [ 'view', 'edit', 'embed' ],
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attachment source for featured media.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param array<string, mixed> $prepared Prepared data before response.
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function get_callback_featured_media_src( array $prepared ): array {
|
||||
/**
|
||||
* Featured media ID.
|
||||
*
|
||||
* @var int|null $id
|
||||
*/
|
||||
$id = $prepared['featured_media'] ?? null;
|
||||
$image = [];
|
||||
if ( $id ) {
|
||||
$image = $this->get_thumbnail_data( $id );
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the attachment data prepared for JavaScript.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param array<string, mixed>|mixed $response Array of prepared attachment data.
|
||||
* @param WP_Post $attachment Attachment object.
|
||||
* @return array<string, mixed>|mixed $response
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @phpstan-return ($response is array<T> ? array<T> : mixed)
|
||||
*/
|
||||
public function wp_prepare_attachment_for_js( $response, WP_Post $attachment ) {
|
||||
if ( ! \is_array( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
if ( 'video' === $response['type'] ) {
|
||||
$thumbnail_id = (int) get_post_thumbnail_id( $attachment );
|
||||
$image = '';
|
||||
if ( 0 !== $thumbnail_id ) {
|
||||
$image = $this->get_thumbnail_data( $thumbnail_id );
|
||||
}
|
||||
|
||||
$response['featured_media'] = $thumbnail_id;
|
||||
$response['featured_media_src'] = $image;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get poster image data.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param int $thumbnail_id Attachment ID.
|
||||
* @return array{src?: string, width?: int, height?: int, generated?: bool}
|
||||
*/
|
||||
public function get_thumbnail_data( int $thumbnail_id ): array {
|
||||
$img_src = wp_get_attachment_image_src( $thumbnail_id, 'full' );
|
||||
|
||||
if ( ! $img_src ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
[ $src, $width, $height ] = $img_src;
|
||||
$generated = $this->is_poster( $thumbnail_id );
|
||||
return compact( 'src', 'width', 'height', 'generated' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes associated poster image when a video is deleted.
|
||||
*
|
||||
* This prevents the poster image from becoming an orphan because it is not
|
||||
* displayed anywhere in WordPress or the story editor.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param int $attachment_id ID of the attachment to be deleted.
|
||||
*/
|
||||
public function delete_video_poster( int $attachment_id ): void {
|
||||
/**
|
||||
* Post ID.
|
||||
*
|
||||
* @var int|string $post_id
|
||||
*/
|
||||
$post_id = get_post_meta( $attachment_id, self::POSTER_ID_POST_META_KEY, true );
|
||||
|
||||
if ( empty( $post_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Used in favor of slow meta queries.
|
||||
$is_poster = $this->is_poster( (int) $post_id );
|
||||
if ( $is_poster ) {
|
||||
wp_delete_attachment( (int) $post_id, true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on plugin uninstall.
|
||||
*
|
||||
* @since 1.26.0
|
||||
*/
|
||||
public function on_plugin_uninstall(): void {
|
||||
delete_post_meta_by_key( self::POSTER_ID_POST_META_KEY );
|
||||
delete_post_meta_by_key( self::POSTER_POST_META_KEY );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper util to check if attachment is a poster.
|
||||
*
|
||||
* @since 1.2.1
|
||||
*
|
||||
* @param int $post_id Attachment ID.
|
||||
*/
|
||||
protected function is_poster( int $post_id ): bool {
|
||||
$terms = get_the_terms( $post_id, $this->media_source_taxonomy->get_taxonomy_slug() );
|
||||
if ( \is_array( $terms ) && ! empty( $terms ) ) {
|
||||
$slugs = wp_list_pluck( $terms, 'slug' );
|
||||
|
||||
return \in_array( $this->media_source_taxonomy::TERM_POSTER_GENERATION, $slugs, true );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
138
wp-content/plugins/web-stories/includes/Media/Video/Trimming.php
Normal file
138
wp-content/plugins/web-stories/includes/Media/Video/Trimming.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
/**
|
||||
* Class Trimming
|
||||
*
|
||||
* @link https://github.com/googleforcreators/web-stories-wp
|
||||
*
|
||||
* @copyright 2021 Google LLC
|
||||
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Copyright 2021 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\Media\Video;
|
||||
|
||||
use Google\Web_Stories\Infrastructure\HasMeta;
|
||||
use Google\Web_Stories\Infrastructure\PluginUninstallAware;
|
||||
use Google\Web_Stories\Service_Base;
|
||||
|
||||
/**
|
||||
* Class Trimming
|
||||
*/
|
||||
class Trimming extends Service_Base implements HasMeta, PluginUninstallAware {
|
||||
|
||||
/**
|
||||
* The trim video post meta key.
|
||||
*/
|
||||
public const TRIM_POST_META_KEY = 'web_stories_trim_data';
|
||||
|
||||
/**
|
||||
* Is trim.
|
||||
*/
|
||||
public const TRIM_DATA_KEY = 'trim_data';
|
||||
|
||||
/**
|
||||
* Register.
|
||||
*
|
||||
* @since 1.12.0
|
||||
*/
|
||||
public function register(): void {
|
||||
$this->register_meta();
|
||||
|
||||
add_filter( 'wp_prepare_attachment_for_js', [ $this, 'wp_prepare_attachment_for_js' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register meta for attachment post type.
|
||||
*
|
||||
* @since 1.12.0
|
||||
*/
|
||||
public function register_meta(): void {
|
||||
register_meta(
|
||||
'post',
|
||||
self::TRIM_POST_META_KEY,
|
||||
[
|
||||
'type' => 'object',
|
||||
'description' => __( 'Video trim data.', 'web-stories' ),
|
||||
'show_in_rest' => [
|
||||
'schema' => [
|
||||
'properties' => [
|
||||
'original' => [
|
||||
'description' => __( 'Original attachment id', 'web-stories' ),
|
||||
'type' => 'integer',
|
||||
],
|
||||
'start' => [
|
||||
'description' => __( 'Start time.', 'web-stories' ),
|
||||
'type' => 'string',
|
||||
],
|
||||
'end' => [
|
||||
'description' => __( 'End time.', 'web-stories' ),
|
||||
'type' => 'string',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'default' => [
|
||||
'original' => 0,
|
||||
],
|
||||
'single' => true,
|
||||
'object_subtype' => 'attachment',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the attachment data prepared for JavaScript.
|
||||
*
|
||||
* @since 1.12.0
|
||||
*
|
||||
* @param array|mixed $response Array of prepared attachment data.
|
||||
* @return array|mixed Response data.
|
||||
*
|
||||
* @template T
|
||||
*
|
||||
* @phpstan-return ($response is array<T> ? array<T> : mixed)
|
||||
*/
|
||||
public function wp_prepare_attachment_for_js( $response ) {
|
||||
if ( ! \is_array( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
if ( 'video' === $response['type'] ) {
|
||||
/**
|
||||
* Post ID.
|
||||
*
|
||||
* @var int $post_id
|
||||
*/
|
||||
$post_id = $response['id'];
|
||||
|
||||
$response[ self::TRIM_DATA_KEY ] = get_post_meta( $post_id, self::TRIM_POST_META_KEY, true );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Act on plugin uninstall.
|
||||
*
|
||||
* @since 1.26.0
|
||||
*/
|
||||
public function on_plugin_uninstall(): void {
|
||||
delete_post_meta_by_key( self::TRIM_POST_META_KEY );
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user