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 ? array : 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 $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|mixed $args Query args. * @return array|mixed Filtered query args. * * @template T * * @phpstan-return ($args is array ? array : 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|mixed $args Query args. * @return array|mixed Filtered query args. * * @template T * * @phpstan-return ($args is array ? array : 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 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 $args Existing WP_Query args. * @return array Tax query arg. */ private function get_exclude_tax_query( array $args ): array { /** * Tax query. * * @var array $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; } }