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.
404 lines
10 KiB
PHTML
404 lines
10 KiB
PHTML
7 months ago
|
<?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;
|
||
|
}
|
||
|
}
|