From 8c73f25d93d709e23faf69a429895fd7c465add0 Mon Sep 17 00:00:00 2001 From: Mykhailo Los Date: Tue, 2 Sep 2025 22:07:10 +0300 Subject: [PATCH 01/22] Adjust plugin code. Split to classes, add factory and hooks --- composer.json | 2 +- composer.lock | 32 ++- src/Core.php | 386 +++++------------------------ src/Providers/Dailymotion.php | 10 +- src/Providers/Provider.php | 9 +- src/Providers/Provider_Factory.php | 144 +++++++++++ src/Providers/Vimeo.php | 4 +- src/Providers/YouTube.php | 6 +- src/Util/Assets.php | 38 +++ src/Util/Block_Filter.php | 88 +++++++ src/Util/Facade_Builder.php | 206 +++++++++++++++ src/Util/Thumbnail_Service.php | 64 +++++ src/Util/Url_Parser.php | 38 +++ tribe-embed.php | 4 +- 14 files changed, 671 insertions(+), 360 deletions(-) create mode 100644 src/Providers/Provider_Factory.php create mode 100644 src/Util/Assets.php create mode 100644 src/Util/Block_Filter.php create mode 100644 src/Util/Facade_Builder.php create mode 100644 src/Util/Thumbnail_Service.php create mode 100644 src/Util/Url_Parser.php diff --git a/composer.json b/composer.json index 4f12f90..0eaa5f6 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "phpunit/phpunit": "^9.0", "assertwell/wp-core-test-framework": "^0.2.0", "phpstan/phpstan": "^1.10", - "php-stubs/wordpress-stubs": "^5.9", + "php-stubs/wordpress-stubs": "^6.6", "szepeviktor/phpstan-wordpress": "^1.1", "phpstan/extension-installer": "^1.1", "php-stubs/acf-pro-stubs": "^5.12", diff --git a/composer.lock b/composer.lock index 03178ea..ce13826 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "57248f8129bae7499b7a240a82c99292", + "content-hash": "412d2fb427af068bf60a5bbc69e2b468", "packages": [], "packages-dev": [ { @@ -564,29 +564,35 @@ }, { "name": "php-stubs/wordpress-stubs", - "version": "v5.9.6", + "version": "v6.8.2", "source": { "type": "git", "url": "https://github.com/php-stubs/wordpress-stubs.git", - "reference": "6a18d938d0aef39d091505a4a35b025fb6c10098" + "reference": "9c8e22e437463197c1ec0d5eaa9ddd4a0eb6d7f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/6a18d938d0aef39d091505a4a35b025fb6c10098", - "reference": "6a18d938d0aef39d091505a4a35b025fb6c10098", + "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/9c8e22e437463197c1ec0d5eaa9ddd4a0eb6d7f8", + "reference": "9c8e22e437463197c1ec0d5eaa9ddd4a0eb6d7f8", "shasum": "" }, + "conflict": { + "phpdocumentor/reflection-docblock": "5.6.1" + }, "require-dev": { - "nikic/php-parser": "< 4.12.0", - "php": "~7.3 || ~8.0", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "nikic/php-parser": "^5.5", + "php": "^7.4 || ^8.0", "php-stubs/generator": "^0.8.3", - "phpdocumentor/reflection-docblock": "^5.3", - "phpstan/phpstan": "^1.10.12", - "phpunit/phpunit": "^9.5" + "phpdocumentor/reflection-docblock": "^5.4.1", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^9.5", + "szepeviktor/phpcs-psr-12-neutron-hybrid-ruleset": "^1.1.1", + "wp-coding-standards/wpcs": "3.1.0 as 2.3.0" }, "suggest": { "paragonie/sodium_compat": "Pure PHP implementation of libsodium", - "symfony/polyfill-php73": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "symfony/polyfill-php80": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "szepeviktor/phpstan-wordpress": "WordPress extensions for PHPStan" }, "type": "library", @@ -603,9 +609,9 @@ ], "support": { "issues": "https://github.com/php-stubs/wordpress-stubs/issues", - "source": "https://github.com/php-stubs/wordpress-stubs/tree/v5.9.6" + "source": "https://github.com/php-stubs/wordpress-stubs/tree/v6.8.2" }, - "time": "2023-05-18T04:34:27+00:00" + "time": "2025-07-16T06:41:00+00:00" }, { "name": "phpcompatibility/php-compatibility", diff --git a/src/Core.php b/src/Core.php index f7dfef4..247a0bf 100644 --- a/src/Core.php +++ b/src/Core.php @@ -2,327 +2,102 @@ namespace Tribe\Tribe_Embed; -use Tribe\Tribe_Embed\Providers\Dailymotion; -use Tribe\Tribe_Embed\Providers\Vimeo; -use Tribe\Tribe_Embed\Providers\YouTube; -use WP_Block; - +use Tribe\Tribe_Embed\Providers\Provider_Factory; +use Tribe\Tribe_Embed\Util\Assets; +use Tribe\Tribe_Embed\Util\Block_Filter; +use Tribe\Tribe_Embed\Util\Facade_Builder; +use Tribe\Tribe_Embed\Util\Thumbnail_Service; +use Tribe\Tribe_Embed\Util\Url_Parser; + +/** + * Builds and shares single service instances. + * Delegates hook registration to Block_Filter. + */ final class Core { - public const VERSION = '1.0.3'; + public const VERSION = '1.1'; public const PLUGIN_NAME = 'tribe-embed'; - private static self $instance; + private Provider_Factory|null $factory = null; + private Url_Parser|null $url_parser = null; + private Thumbnail_Service|null $thumbs = null; + private Facade_Builder|null $facade = null; + private Block_Filter|null $block_filter = null; + + /** @var self|null Singleton instance */ + private static ?self $instance = null; private function __construct() { define( 'TRIBE_MP_PATH', trailingslashit( plugin_dir_path( dirname( __FILE__ ) ) ) ); - define( 'TRIBE_MP_URL', plugin_dir_url( TRIBE_MP_PATH . self::PLUGIN_NAME ) ); - define( 'TRIBE_MP_VERSION', self::VERSION ); } + /** Get Core singleton */ public static function instance(): self { - if ( ! isset( self::$instance ) ) { + if ( ! self::$instance instanceof self ) { self::$instance = new self(); } return self::$instance; } - public function init( string $file ): void { - add_action( 'admin_enqueue_scripts', [ $this, 'register_admin_scripts' ] ); - add_action( 'wp_enqueue_scripts', [ $this, 'register_public_scripts' ] ); + /** Register WP hooks via Block_Filter */ + public function register_hooks(): void { + $assets = new Assets( self::PLUGIN_NAME, self::VERSION ); - add_filter( 'render_block_core/embed', [ $this, 'filter_embed_block' ], 10, 3 ); + add_action( 'admin_enqueue_scripts', [ $assets, 'register_admin_scripts' ] ); + add_action( 'wp_enqueue_scripts', [ $assets, 'register_public_scripts' ] ); - add_action( 'video_thumbnail_markup', [ $this, 'open_markup_figure_element' ], 10, 4 ); - add_action( 'video_thumbnail_markup', [ $this, 'add_video_play_button' ], 20, 4 ); - add_action( 'video_thumbnail_markup', [ $this, 'add_video_thumbnail_markup' ], 30, 4 ); - add_action( 'video_thumbnail_markup', [ $this, 'close_markup_figure_element' ], 40, 4 ); - add_action( 'video_thumbnail_markup', [ $this, 'add_original_embed_template' ], 50, 4 ); - } - - /** - * Registers the admin scripts - */ - public function register_admin_scripts(): void { - $asset_file = include TRIBE_MP_PATH . 'dist/editor.asset.php'; - wp_enqueue_script( self::PLUGIN_NAME . '-admin', TRIBE_MP_URL . 'dist/editor.js', $asset_file['dependencies'], $asset_file['version'] ); - wp_enqueue_style( self::PLUGIN_NAME . '-admin', TRIBE_MP_URL . 'dist/editor.css', $asset_file['version'] ); - } - - /** - * Registers the public scripts - */ - public function register_public_scripts(): void { - $asset_file = include TRIBE_MP_PATH . 'dist/index.asset.php'; - wp_enqueue_script( self::PLUGIN_NAME . '-public', TRIBE_MP_URL . 'dist/index.js', $asset_file['dependencies'], $asset_file['version'] ); - wp_enqueue_style( self::PLUGIN_NAME . '-public', TRIBE_MP_URL . 'dist/style-index.css', $asset_file['version'] ); + $this->block_filter()->register_hooks(); } - /** - * Filters the code embed block output for improved performance on Youtube videos. - * - * @param string $block_content The block content. - * @param array $block The full block, including name and attributes. - * @param \Tribe\Tribe_Embed\WP_Block $instance The block instance. - * - * @return string $block_content The block content. - */ - public function filter_embed_block( string $block_content, array $block, WP_Block $instance ): string { - - // if the provider slug name is empty. - if ( empty( $block['attrs']['providerNameSlug'] ) ) { - return $block_content; - } - - // if for some reason there is no embed URL. - if ( empty( $block['attrs']['url'] ) ) { - return $block_content; - } - - // setup some base variables and get the video url - $provider = null; - $thumbnail_data = []; - $parsed_video_url = parse_url( $block['attrs']['url'] ); - - // Only continue for allowed providers - if ( ! $this->is_allowed_host( $parsed_video_url['host'] ) ) { - return $block_content; + /** Get Provider_Factory */ + public function provider_factory(): Provider_Factory { + if ( ! $this->factory instanceof Provider_Factory ) { + $this->factory = new Provider_Factory(); } - // switch based on the host. - switch ( $parsed_video_url['host'] ) { - // for youtube urls - case in_array( $parsed_video_url['host'], YouTube::ALLOWED_HOSTS ): - $provider = new YouTube( $parsed_video_url ); - break; - - // for vimeo urls. - case in_array( $parsed_video_url['host'], Vimeo::ALLOWED_HOSTS ): - $provider = new Vimeo( $parsed_video_url ); - break; - - // for dailymotion urls. - case in_array( $parsed_video_url['host'], Dailymotion::ALLOWED_HOSTS ): - $provider = new Dailymotion( $parsed_video_url ); - break; - - default: - /** - * Returns Custom Provider class object - * - * @var mixed|null $provider Provider object - * @var array $video_url_data Video url parsed with parse_url - * @var array $block The full block, including name and attributes. - */ - $provider = apply_filters( 'tribe-embeds_video_provider', null, $parsed_video_url, $block ); - break; - } - - // Bail if empty/wrong provider is provided - if ( empty( $provider ) ) { - return $block_content; - } - - // get thumbnail data. - $video_id = $provider->get_video_id(); - $thumbnail_data = $provider->get_thumbnail_data(); - - // if we don't have any video thumbnails. - if ( count( $thumbnail_data ) === 0 ) { - return $block_content; - } - - // create an array of classes to add to the placeholder image wrapper. - $wrapper_classes = [ - 'wp-block-image', - 'tribe-embed', - 'is--' . $block['attrs']['providerNameSlug'], - ]; - - // if we have classNames on the embed block. - if ( ! empty( $block['attrs']['className'] ) ) { - // explode the className string into array. - $class_names = explode( ' ', $block['attrs']['className'] ); - - // merge the class names into the figures classes array. - $wrapper_classes = array_merge( $wrapper_classes, $class_names ); - } - - // if the embed block has an alignment. - if ( ! empty( $block['attrs']['align'] ) ) { - // add the alignment class to the figure classes. - $wrapper_classes[] = 'align' . $block['attrs']['align']; - } - - // allow the classes to be filtered. - $wrapper_classes = apply_filters( '', $wrapper_classes, $block, $video_id, $thumbnail_data ); - - // buffer the output as we need to return not echo. - ob_start(); - - // output the registered "block" styles for the thubmnail. - wp_print_styles( 'tribe-embeds-styles' ); - - /** - * Fires and action to which the new block markup is added too. - * - * @hooked open_markup_figure_element - 10 - * @hooked add_video_play_button - 20 - * @hooked add_video_thumbnail_markup - 30 - * @hooked hd_bvce_close_markup_figure_element - 40 - * @hooked add_original_embed_template - 50 - */ - do_action( 'video_thumbnail_markup', $block, $video_id, $thumbnail_data, $wrapper_classes ); - - // return the new block markup. - return ob_get_clean(); - } - - /** - * Creates a escaping function to allowed certain HTML for embed content. - * Needed for when echoing the innerblock HTML. - * - * @param array An array of HTML elements allowed. - */ - public function allowed_innerblock_html(): array { - /** - * Return the allowed html - * These are the elements in the rendered embed block for supported videos. - * This also includes everything you can add to an embed caption. - * Therefore we need to allow these to keep the same structure. - */ - return [ - 'iframe' => [ - 'src' => true, - 'height' => true, - 'width' => true, - 'frameborder' => true, - 'allowfullscreen' => true, - ], - 'figure' => [ - 'class' => true, - ], - 'figcaption' => [ - 'class' => true, - ], - 'div' => [ - 'class' => true, - ], - 'a' => [ - 'class' => true, - 'href' => true, - 'data-type' => true, - ], - 'strong' => [], - 'em' => [], - 'sub' => [], - 'sup' => [], - 's' => [], - 'kbd' => [], - 'img' => [ - 'class' => true, - 'style' => true, - 'src' => true, - 'alt' => true, - ], - 'code' => [], - 'mark' => [ - 'style' => true, - 'class' => true, - ], - ]; - } - - /** - * Adds the opening figure element to the thumbnail markup. - * - * @param array $block The block array. - * @param string $video_id The ID of the embedded video. - * @param array $thumbnail_data The URL of the video thumbnail. - * @param array $wrapper_classes An array of CSS classes to add to the wrapper. - */ - public function open_markup_figure_element( array $block, string $video_id, array $thumbnail_data, array $wrapper_classes ): void { - - ?> -
-
- factory; } - /** - * Adds the play button div to the markup. - * - * @param array $block The block array. - * @param string $video_id The ID of the embedded video. - * @param array $thumbnail_data The URL of the video thumbnail. - * @param array $wrapper_classes An array of CSS classes to add to the wrapper. - */ - public function add_video_play_button( array $block, string $video_id, array $thumbnail_data, array $wrapper_classes ): void { + /** Get Url_Parser */ + public function url_parser(): Url_Parser { + if ( ! $this->url_parser instanceof Url_Parser ) { + $this->url_parser = new Url_Parser(); + } - ?> - - url_parser; } - /** - * Adds the video thumbnail markup output. - * - * @param array $block The block array. - * @param string $video_id The ID of the embedded video. - * @param array $thumbnail_data The URL of the video thumbnail. - * @param array $wrapper_classes An array of CSS classes to add to the wrapper. - */ - public function add_video_thumbnail_markup( array $block, string $video_id, array $thumbnail_data, array $wrapper_classes ): void { - - $max_res_image = end( $thumbnail_data ); - $srcset = []; - $sizes = [ '(max-width: ' . $max_res_image['width'] . 'px) 100vw', $max_res_image['width'] . 'px' ]; - - foreach ( $thumbnail_data as $data ) { - $srcset[] = $data['url'] . ' ' . $data['width'] . 'w'; + /** Get Thumbnail_Service */ + public function thumbnail_service(): Thumbnail_Service { + if ( ! $this->thumbs instanceof Thumbnail_Service ) { + $this->thumbs = new Thumbnail_Service( $this->provider_factory() ); } - ?> - height= - class="tribe-embed__thumbnail" alt="" src="" - srcset="" sizes="" /> - thumbs; } - /** - * Adds the closing figure element to the thumbnail markup. - * - * @param array $block The block array. - * @param string $video_id The ID of the embedded video. - * @param array $thumbnail_data The URL of the video thumbnail. - * @param array $wrapper_classes An array of CSS classes to add to the wrapper. - */ - public function close_markup_figure_element( array $block, string $video_id, array $thumbnail_data, array $wrapper_classes ): void { + /** Get Facade_Builder */ + public function facade_builder(): Facade_Builder { + if ( ! $this->facade instanceof Facade_Builder ) { + $this->facade = new Facade_Builder(); + } - ?> -
-
- facade; } - /** - * Adds the original block markup to the template element. - * This is used when the item is cloned when the thumbnail is clicked. - * - * @param array $block The block array. - * @param string $video_id The ID of the embedded video. - * @param array $thumbnail_data The URL of the video thumbnail. - * @param array $wrapper_classes An array of CSS classes to add to the wrapper. - */ - public function add_original_embed_template( array $block, string $video_id, array $thumbnail_data, array $wrapper_classes ): void { + /** Get Block_Filter */ + public function block_filter(): Block_Filter { + if ( ! $this->block_filter instanceof Block_Filter ) { + $this->block_filter = new Block_Filter( + $this->url_parser(), + $this->provider_factory(), + $this->thumbnail_service(), + $this->facade_builder() + ); + } - ?> - - block_filter; } /** @@ -339,43 +114,4 @@ public static function deactivate(): void { return; } - /** - * @param string $host - * @param array $allowed_hosts - */ - private function is_allowed_host( string $host, array $allowed_hosts = [] ): bool { - // Use default list of allowed hosts if nothing has been specified - if ( empty( $allowed_hosts ) ) { - $allowed_hosts = array_merge( YouTube::ALLOWED_HOSTS, Vimeo::ALLOWED_HOSTS, Dailymotion::ALLOWED_HOSTS ); - } - - /** - * Allows to inject custom provider hosts - * @var array $allowed_hosts List of allowed hosts - * @var string $host Current video hostname - */ - $allowed_hosts = apply_filters( 'tribe-embeds_allowed_provider_hosts', $allowed_hosts, $host ); - - if ( in_array( $host, $allowed_hosts ) ) { - return true; - } - - /** - * Check if requested host is in the list of allowed ones. This is a wildcard check with regex - * and it is specifically handles problem with provider dynamic urls - * - * Example: - * Wistia video embed: https://tri-4.wistia.com/medias/7c1s0ftfl3?embedType=web_component&seo=true&videoWidth=960 - * The `tri-4` part may vary and be different for different videos or/and accounts - */ - $allowed_hosts = array_filter( $allowed_hosts, static function ( $allowed ) use ( $host ) { - return (bool) preg_match( "/$allowed/i", $host ); - } ); - - return ! empty( $allowed_hosts ); - } - - private function __clone() { - } - } diff --git a/src/Providers/Dailymotion.php b/src/Providers/Dailymotion.php index b13b0db..90e5338 100644 --- a/src/Providers/Dailymotion.php +++ b/src/Providers/Dailymotion.php @@ -48,7 +48,7 @@ public function get_thumbnail_data(): array { // if the request to the image errors or returns anything other than a http 200 response code. if ( ( is_wp_error( $video_details )) && ( 200 !== wp_remote_retrieve_response_code( $video_details ) ) ) { - return ''; + return []; } // grab the body of the response. @@ -59,7 +59,7 @@ public function get_thumbnail_data(): array { ); if ( $response_body === null ) { - return ''; + return []; } // get the image url from the json. @@ -96,8 +96,6 @@ protected function set_video_id(): string { // remove the preceeding slash. return str_replace( '/video/', '', $this->video_url['path'] ); - - break; case 'dai.ly': // if we have a path. if ( empty( $this->video_url['path'] ) ) { @@ -106,9 +104,9 @@ protected function set_video_id(): string { // remove the preceeding slash. return str_replace( '/', '', $this->video_url['path'] ); - - break; } + + return ''; } } diff --git a/src/Providers/Provider.php b/src/Providers/Provider.php index 96b1f1d..b5f5099 100644 --- a/src/Providers/Provider.php +++ b/src/Providers/Provider.php @@ -8,6 +8,7 @@ abstract class Provider { public const IMAGE_SIZES = []; public const ALLOWED_HOSTS = []; + // phpcs:ignore SlevomatCodingStandard.TypeHints.PropertyTypeHint.MissingTraversableTypeHintSpecification protected array $video_url; protected string $video_id; @@ -22,14 +23,6 @@ public function __construct( array $video_url = [] ) { $this->video_id = $this->set_video_id(); } - public static function instance( array $video_url ): self { - if ( ! isset( self::$instance ) ) { - self::$instance = new self( $video_url ); - } - - return self::$instance; - } - public function get_video_id(): string { return $this->video_id ?? ''; } diff --git a/src/Providers/Provider_Factory.php b/src/Providers/Provider_Factory.php new file mode 100644 index 0000000..1c6ee0a --- /dev/null +++ b/src/Providers/Provider_Factory.php @@ -0,0 +1,144 @@ + */ + private array $provider_classes; + + /** + * @param array $provider_classes Optional override list. + */ + public function __construct( array $provider_classes = [] ) { + $defaults = [ + '\\Tribe\\Tribe_Embed\\Providers\\YouTube', + '\\Tribe\\Tribe_Embed\\Providers\\Vimeo', + '\\Tribe\\Tribe_Embed\\Providers\\Dailymotion', + ]; + + /** Allow external override of provider class list */ + $filtered = apply_filters( 'tribe_embeds_provider_classes', $provider_classes ?: $defaults ); + + $this->provider_classes = array_values( array_filter( + array_map( 'strval', (array) $filtered ), + static function ( string $class ): bool { + return class_exists( $class ); + } + ) ); + } + + /** + * Resolve provider instance or null. + * + * @param array $video_url_data Parsed URL parts incl. 'host' + * @param array $block Gutenberg block array + */ + public function resolve( array $video_url_data, array $block ): ?object { + /** Allow short-circuit with a ready-made provider instance */ + $maybe = apply_filters( 'tribe_embeds_video_provider', null, $video_url_data, $block ); + if ( is_object( $maybe ) ) { + return $maybe; + } + + /** Match by per-provider allowed hosts */ + $host = strtolower( (string) ($video_url_data['host'] ?? '') ); + + if ( $host !== '' ) { + foreach ( $this->provider_classes as $class ) { + if ( in_array( $host, $this->allowed_hosts_for( $class ), true ) ) { + return $this->instantiate( $class, $video_url_data ); + } + } + } + + return null; + } + + /** + * Get allowed hosts for a provider (constant + filters). + * + * Filters: + * - tribe_embeds_allowed_provider_hosts_{slug} + * - tribe_embeds_allowed_provider_hosts + * + * @return array Lowercased hostnames. + */ + public function allowed_hosts_for( string $provider_class ): array { + $base = []; + + if ( defined( $provider_class . '::ALLOWED_HOSTS' ) ) { + /** @var array $base */ + $base = (array) $provider_class::ALLOWED_HOSTS; + } + + $slug = $this->provider_slug( $provider_class ); + + $by_provider = apply_filters( 'tribe_embeds_allowed_provider_hosts_' . $slug, $base, $provider_class ); + $global = apply_filters( 'tribe_embeds_allowed_provider_hosts', $by_provider, $provider_class ); + + return array_values( array_unique( array_map( 'strtolower', array_filter( (array) $global, 'is_string' ) ) ) ); + } + + /** + * Get image sizes for a provider (constant + filters). + * + * Filters: + * - tribe_embeds_image_sizes_{slug} + * - tribe_embeds_image_sizes + * + * @return array + */ + public function image_sizes_for( string $provider_class ): array { + $base = []; + + if ( defined( $provider_class . '::IMAGE_SIZES' ) ) { + /** @var array $base */ + $base = (array) $provider_class::IMAGE_SIZES; + } + + $slug = $this->provider_slug( $provider_class ); + + $by_provider = apply_filters( 'tribe_embeds_image_sizes_' . $slug, $base, $provider_class ); + $global = apply_filters( 'tribe_embeds_image_sizes', $by_provider, $provider_class ); + + return (array) $global; + } + + /** + * Slug used in filter names. + * Uses class SLUG constant if present; otherwise derived from class name. + */ + public function provider_slug( string $provider_class ): string { + if ( defined( $provider_class . '::SLUG' ) ) { + $slug = (string) $provider_class::SLUG; + if ( $slug !== '' ) { + return sanitize_key( $slug ); + } + } + + $base = strtolower( trim( (string) strrchr( '\\' . ltrim( $provider_class, '\\' ), '\\' ), '\\' ) ); + $base = preg_replace( '/_provider$|provider$/', '', $base ); + + return sanitize_key( $base ?: 'provider' ); + } + + /** + * Instantiate provider safely. + */ + private function instantiate( string $class, array $video_url_data ): ?object { + try { + return new $class( $video_url_data ); + } catch ( \Throwable $e ) { + try { + return new $class(); + } catch ( \Throwable ) { + return null; + } + } + } + +} diff --git a/src/Providers/Vimeo.php b/src/Providers/Vimeo.php index f33533e..e773820 100644 --- a/src/Providers/Vimeo.php +++ b/src/Providers/Vimeo.php @@ -90,9 +90,9 @@ protected function set_video_id(): string { // remove the preceeding slash. return str_replace( '/', '', $this->video_url['path'] ); - - break; } + + return ''; } } diff --git a/src/Providers/YouTube.php b/src/Providers/YouTube.php index 687d276..be4be7f 100644 --- a/src/Providers/YouTube.php +++ b/src/Providers/YouTube.php @@ -83,8 +83,6 @@ protected function set_video_id(): string { // set the video id to the v query arg. return $video_url_query_args['v']; - break; - // for youtube short urls. case 'youtu.be': // if we have a path. @@ -94,9 +92,9 @@ protected function set_video_id(): string { // remove the preceeding slash. return str_replace( '/', '', $this->video_url['path'] ); - - break; } + + return ''; } } diff --git a/src/Util/Assets.php b/src/Util/Assets.php new file mode 100644 index 0000000..d7e6f12 --- /dev/null +++ b/src/Util/Assets.php @@ -0,0 +1,38 @@ +plugin_name = $plugin_name; + $this->version = $version; + + define( 'TRIBE_MP_URL', plugin_dir_url( TRIBE_MP_PATH . $plugin_name ) ); + define( 'TRIBE_MP_VERSION', $version ); + } + + /** + * Registers the admin scripts + */ + public function register_admin_scripts(): void { + $asset_file = include TRIBE_MP_PATH . 'dist/editor.asset.php'; + wp_enqueue_script( $this->plugin_name . '-admin', TRIBE_MP_URL . 'dist/editor.js', $asset_file['dependencies'], $asset_file['version'] ); + wp_enqueue_style( $this->plugin_name . '-admin', TRIBE_MP_URL . 'dist/editor.css', $asset_file['version'] ); + } + + /** + * Registers the public scripts + */ + public function register_public_scripts(): void { + $asset_file = include TRIBE_MP_PATH . 'dist/index.asset.php'; + wp_enqueue_script( $this->plugin_name . '-public', TRIBE_MP_URL . 'dist/index.js', $asset_file['dependencies'], $asset_file['version'] ); + wp_enqueue_style( $this->plugin_name . '-public', TRIBE_MP_URL . 'dist/style-index.css', $asset_file['version'] ); + } + +} diff --git a/src/Util/Block_Filter.php b/src/Util/Block_Filter.php new file mode 100644 index 0000000..a3a6d7c --- /dev/null +++ b/src/Util/Block_Filter.php @@ -0,0 +1,88 @@ +url_parser = $url_parser; + $this->factory = $factory; + $this->thumbs = $thumbs; + $this->facade = $facade; + } + + /** Attach filter once */ + public function register_hooks(): void { + if ( $this->hooks_added ) { + return; + } + add_filter( 'render_block', [ $this, 'filter_render_block' ], 10, 2 ); + $this->hooks_added = true; + } + + /** + * Filter callback for render_block + * - Only processes core/embed blocks + * - Provider may render fully or fallback to facade + * + * @param string $html Original block HTML + * @param array $block Block data + */ + public function filter_render_block( string $html, array $block ): string { + if ( ! isset( $block['blockName'] ) || $block['blockName'] !== 'core/embed' ) { + return $html; + } + + $attrs = (array) ($block['attrs'] ?? []); + $url = isset( $attrs['url'] ) && is_string( $attrs['url'] ) ? $attrs['url'] : ''; + if ( $url === '' ) { + return $html; + } + + $video_url_data = $this->url_parser->parse_url( $url ); + if ( $video_url_data === null ) { + return $html; + } + + // Fallback: use thumbnails and facade + $result = $this->thumbs->resolve_thumb( $video_url_data, $block ); + if ( $result === null ) { + return $html; + } + + // buffer the output as we need to return not echo. + ob_start(); + + // output the registered "block" styles for the thubmnail. + wp_print_styles( 'tribe-embeds-styles' ); + + $facade_html = $this->facade->build( $result['thumb'], $block, $result['video_id'] ); + + /** + * Fires and action to which the new block markup is added too. + */ + echo apply_filters( 'tribe_embeds_facade_html', $facade_html, $result['provider'], $block, $html ); + + return ob_get_clean(); + } + +} diff --git a/src/Util/Facade_Builder.php b/src/Util/Facade_Builder.php new file mode 100644 index 0000000..ed92c54 --- /dev/null +++ b/src/Util/Facade_Builder.php @@ -0,0 +1,206 @@ + for video embeds. + */ +final class Facade_Builder { + + /** + * Build facade image HTML. + * + * @param array $thumb + * @param array $block + * @param string $video_id + */ + public function build( array $thumb, array $block, string $video_id ): string { + $wrapper_classes = $this->get_wrapper_classes( $block, $video_id, $thumb ); + + $content = $this->open_markup_figure_element( $block, $video_id, $thumb, $wrapper_classes ); + $content .= $this->add_video_play_button( $block, $video_id, $thumb, $wrapper_classes ); + $content .= $this->add_video_thumbnail_markup( $block, $video_id, $thumb, $wrapper_classes ); + $content .= $this->close_markup_figure_element( $block, $video_id, $thumb, $wrapper_classes ); + $content .= $this->add_original_embed_template( $block, $video_id, $thumb, $wrapper_classes ); + + return $content; + } + + /** + * Get block wrapper classes + * + * @param array $block + * @param string $video_id + * @param array $thumbnail_data + */ + public function get_wrapper_classes( array $block, string $video_id, array $thumbnail_data ): array { + $wrapper_classes = [ + 'wp-block-image', + 'tribe-embed', + 'is--' . $block['attrs']['providerNameSlug'], + ]; + + // if we have classNames on the embed block. + if ( ! empty( $block['attrs']['className'] ) ) { + // explode the className string into array. + $class_names = explode( ' ', $block['attrs']['className'] ); + + // merge the class names into the figures classes array. + $wrapper_classes = array_merge( $wrapper_classes, $class_names ); + } + + // if the embed block has an alignment. + if ( ! empty( $block['attrs']['align'] ) ) { + // add the alignment class to the figure classes. + $wrapper_classes[] = 'align' . $block['attrs']['align']; + } + + // allow the classes to be filtered. + return apply_filters( 'tribe_embeds_video_wrapper_classes', $wrapper_classes, $block, $video_id, $thumbnail_data ); + } + + /** + * Adds the play button div to the markup. + * + * @param array $block + * @param string $video_id + * @param array $thumbnail_data + * @param array $wrapper_classes + */ + public function add_video_play_button( array $block, string $video_id, array $thumbnail_data, array $wrapper_classes ): string { + $button = sprintf( '', esc_html__( 'Play Video', 'tribe' ) ); + + return apply_filters( 'tribe_embeds_video_button_html', $button, $block, $video_id, $thumbnail_data, $wrapper_classes ); + } + + /** + * Adds the video thumbnail markup output. + * + * @param array $block The block array. + * @param string $video_id The ID of the embedded video. + * @param array $thumbnail_data The URL of the video thumbnail. + * @param array $wrapper_classes An array of CSS classes to add to the wrapper. + */ + public function add_video_thumbnail_markup( array $block, string $video_id, array $thumbnail_data, array $wrapper_classes ): string { + + $max_res_image = end( $thumbnail_data ); + $srcset = []; + $sizes = [ '(max-width: ' . $max_res_image['width'] . 'px) 100vw', $max_res_image['width'] . 'px' ]; + + foreach ( $thumbnail_data as $data ) { + $srcset[] = $data['url'] . ' ' . $data['width'] . 'w'; + } + + $image_tag = sprintf( + '', + $max_res_image['width'], + $max_res_image['height'], + $max_res_image['url'], + implode( ',', $srcset ), + implode( ',', $sizes ) + ); + + return apply_filters( 'tribe_embeds_video_thumb_markup', $image_tag, $block, $video_id, $thumbnail_data, $wrapper_classes ); + } + + /** + * Adds the closing figure element to the thumbnail markup. + * + * @param array $block The block array. + * @param string $video_id The ID of the embedded video. + * @param array $thumbnail_data The URL of the video thumbnail. + * @param array $wrapper_classes An array of CSS classes to add to the wrapper. + */ + public function close_markup_figure_element( array $block, string $video_id, array $thumbnail_data, array $wrapper_classes ): string { + $html = ''; + + return apply_filters( 'tribe_embeds_video_thumb_close_markup', $html, $block, $video_id, $thumbnail_data, $wrapper_classes ); + } + + /** + * Adds the opening figure element to the thumbnail markup. + * + * @param array $block The block array. + * @param string $video_id The ID of the embedded video. + * @param array $thumbnail_data The URL of the video thumbnail. + * @param array $wrapper_classes An array of CSS classes to add to the wrapper. + */ + public function open_markup_figure_element( array $block, string $video_id, array $thumbnail_data, array $wrapper_classes ): string { + $html = sprintf( + '
', + esc_attr( implode( ' ', $wrapper_classes ) ), + esc_attr( $video_id ) + ); + + return apply_filters( 'tribe_embeds_video_thumb_open_markup', $html, $block, $video_id, $thumbnail_data, $wrapper_classes ); + } + + /** + * Creates a escaping function to allowed certain HTML for embed content. + * Needed for when echoing the innerblock HTML. + */ + public function allowed_innerblock_html(): array { + /** + * Return the allowed html + * These are the elements in the rendered embed block for supported videos. + * This also includes everything you can add to an embed caption. + * Therefore, we need to allow these to keep the same structure. + */ + return [ + 'iframe' => [ + 'src' => true, + 'height' => true, + 'width' => true, + 'frameborder' => true, + 'allowfullscreen' => true, + ], + 'figure' => [ + 'class' => true, + ], + 'figcaption' => [ + 'class' => true, + ], + 'div' => [ + 'class' => true, + ], + 'a' => [ + 'class' => true, + 'href' => true, + 'data-type' => true, + ], + 'strong' => [], + 'em' => [], + 'sub' => [], + 'sup' => [], + 's' => [], + 'kbd' => [], + 'img' => [ + 'class' => true, + 'style' => true, + 'src' => true, + 'alt' => true, + ], + 'code' => [], + 'mark' => [ + 'style' => true, + 'class' => true, + ], + ]; + } + + /** + * Adds the original block markup to the template element. + * This is used when the item is cloned when the thumbnail is clicked. + * + * @param array $block The block array. + * @param string $video_id The ID of the embedded video. + * @param array $thumbnail_data The URL of the video thumbnail. + * @param array $wrapper_classes An array of CSS classes to add to the wrapper. + */ + public function add_original_embed_template( array $block, string $video_id, array $thumbnail_data, array $wrapper_classes ): string { + $html = sprintf( '', esc_attr( $video_id ), wp_kses( $block['innerHTML'], $this->allowed_innerblock_html() ) ); + + return apply_filters( 'tribe_embeds_video_embed_template', $html, $block, $video_id, $thumbnail_data, $wrapper_classes ); + } + +} diff --git a/src/Util/Thumbnail_Service.php b/src/Util/Thumbnail_Service.php new file mode 100644 index 0000000..f4b488c --- /dev/null +++ b/src/Util/Thumbnail_Service.php @@ -0,0 +1,64 @@ +factory = $factory; + } + + /** + * Get provider, video id, and thumbnails. + * + * @return array{provider:object, video_id:string, thumb:array}|null + */ + public function resolve_thumb( array $video_url_data, array $block ): ?array { + $provider = $this->factory->resolve( $video_url_data, $block ); + if ( ! is_object( $provider ) ) { + return null; + } + + $video_id = $provider->get_video_id(); + + if ( ! $video_id ) { + return null; + } + + $image_sizes = $this->factory->image_sizes_for( $provider::class ); + + $thumb = $this->safe_get_thumbnail_data( $provider, $image_sizes ); + if ( empty( $thumb ) ) { + return null; + } + + return [ + 'provider' => $provider, + 'video_id' => $video_id, + 'thumb' => $thumb, + ]; + } + + /** Call provider get_thumbnail_data and filter results */ + private function safe_get_thumbnail_data( object $provider, array $image_sizes ): array { + if ( ! method_exists( $provider, 'get_thumbnail_data' ) ) { + return apply_filters( 'tribe_embeds_thumbnail_data', [], $provider, $image_sizes ); + } + + try { + $data = $provider->get_thumbnail_data( $image_sizes ); + } catch ( \Throwable $e ) { + $data = []; + } + + return apply_filters( 'tribe_embeds_thumbnail_data', is_array( $data ) ? $data : [], $provider, $image_sizes ); + } + +} diff --git a/src/Util/Url_Parser.php b/src/Util/Url_Parser.php new file mode 100644 index 0000000..48af4cf --- /dev/null +++ b/src/Util/Url_Parser.php @@ -0,0 +1,38 @@ +|null + */ + public function parse_url( string $url ): ?array { + $url = trim( $url ); + if ( $url === '' ) { + return null; + } + + if ( str_starts_with( $url, '//' ) ) { + $url = 'https:' . $url; + } elseif ( ! preg_match( '#^https?://#i', $url ) ) { + $url = 'https://' . ltrim( $url, '/' ); + } + + $parts = parse_url( $url ); + if ( ! is_array( $parts ) || empty( $parts['host'] ) ) { + return null; + } + + $parts['host'] = strtolower( (string) $parts['host'] ); + $parts['url'] = $url; + + return $parts; + } + +} diff --git a/tribe-embed.php b/tribe-embed.php index 1662222..0ceb6b1 100644 --- a/tribe-embed.php +++ b/tribe-embed.php @@ -24,7 +24,9 @@ register_deactivation_hook( __FILE__, [ Core::class, 'deactivate' ] ); add_action( 'plugins_loaded', static function (): void { - tribe_embed_core()->init( __file__ ); + $core = tribe_embed_core(); + + $core->register_hooks(); } ); function tribe_embed_core(): Core { From 7ae2c743ea14aa6fe940f8e47dbceecc1ae90e05 Mon Sep 17 00:00:00 2001 From: MlKilderkin Date: Wed, 3 Sep 2025 09:41:59 +0300 Subject: [PATCH 02/22] Fix typo src/Util/Facade_Builder.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Util/Facade_Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Util/Facade_Builder.php b/src/Util/Facade_Builder.php index ed92c54..b1bb58a 100644 --- a/src/Util/Facade_Builder.php +++ b/src/Util/Facade_Builder.php @@ -198,7 +198,7 @@ public function allowed_innerblock_html(): array { * @param array $wrapper_classes An array of CSS classes to add to the wrapper. */ public function add_original_embed_template( array $block, string $video_id, array $thumbnail_data, array $wrapper_classes ): string { - $html = sprintf( '', esc_attr( $video_id ), wp_kses( $block['innerHTML'], $this->allowed_innerblock_html() ) ); + $html = sprintf( '', esc_attr( $video_id ), wp_kses( $block['innerHTML'], $this->allowed_innerblock_html() ) ); return apply_filters( 'tribe_embeds_video_embed_template', $html, $block, $video_id, $thumbnail_data, $wrapper_classes ); } From 490d7c467cada2b34793d0e5e7844dab7fcea913 Mon Sep 17 00:00:00 2001 From: MlKilderkin Date: Wed, 3 Sep 2025 09:42:28 +0300 Subject: [PATCH 03/22] Fix typo in src/Util/Block_Filter.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Util/Block_Filter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Util/Block_Filter.php b/src/Util/Block_Filter.php index a3a6d7c..dcc3579 100644 --- a/src/Util/Block_Filter.php +++ b/src/Util/Block_Filter.php @@ -72,7 +72,7 @@ public function filter_render_block( string $html, array $block ): string { // buffer the output as we need to return not echo. ob_start(); - // output the registered "block" styles for the thubmnail. + // output the registered "block" styles for the thumbnail. wp_print_styles( 'tribe-embeds-styles' ); $facade_html = $this->facade->build( $result['thumb'], $block, $result['video_id'] ); From beca8cdb98ac4b085f6899f2eec8565679667ea3 Mon Sep 17 00:00:00 2001 From: Mykhailo Los Date: Tue, 23 Sep 2025 16:38:05 +0300 Subject: [PATCH 04/22] Add Wistia provider --- readme.md | 65 ++++++++++++---------- src/Providers/Wistia.php | 114 +++++++++++++++++++++++++++++++++++++++ src/Util/Assets.php | 4 +- 3 files changed, 152 insertions(+), 31 deletions(-) create mode 100644 src/Providers/Wistia.php diff --git a/readme.md b/readme.md index 682ee27..6c2f48d 100644 --- a/readme.md +++ b/readme.md @@ -33,38 +33,45 @@ If you need to rebuild the lando environment you will need to delete the `./dev/ This repo is setup to use the [WP CLI dist-archive](https://developer.wordpress.org/cli/commands/dist-archive/) command. To build the zip file for the make sure you have the dist-archive command package installed and run `wp dist-archive .` form the root folder. The zip file will be created one folder back form the root folder. -### Providers +## Hooks (filters & actions) -Each provider represents separate service(YouTube, Vimeo, etc). In order to provide ability extend list of providers use `tribe-embeds_video_provider` hook. -For proper use add new class in your theme or plugin. Each provider should extend `Tribe\Tribe_Embed\Provider` class -Usage example: +These are the **public** extension points intended for themes/plugins to customize behavior. Names and arguments are considered part of the API. + +### Filters + +#### `tribe-embeds_video_provider` +Choose/override the Provider instance for a given embed URL. + +- **Signature:** `apply_filters( 'tribe-embeds_video_provider', $provider, $video_url_data, $block )` +- **Args:** + - `$provider` — default or previously resolved provider instance (or `null`) + - `$video_url_data` — result of `parse_url()` for the video URL + - `$block` — full Gutenberg block array (name, attributes, innerBlocks, etc.) +- **Return:** A `Provider` instance or `null` to skip. +- **Example:** ```php -class TestProvider extends \Tribe\Tribe_Embed\Provider { -.... -} - -function is_allowed_provider(): bool { -... -} - -/** - * @var mixed|null $provider - * @var array $video_url_data Video url parsed with parse_url - * @var array $block The full block, including name and attributes. - */ -add_filter( 'tribe-embeds_video_provider', function( $provider, $video_url_data, $block ) { - if ( is_allowed_provider( $video_url_data['host'] ) ) { - return $provider; - } - return ( new TestProvider( $video_url_data ) ); +add_filter( 'tribe-embeds_video_provider', function ( $provider, $video_url_data, $block ) { + // Force our custom provider for a specific host or path + if ( isset( $video_url_data['host'] ) && $video_url_data['host'] === 'videos.example.com' ) { + return new \Tribe\Tribe_Embed\Providers\Example_Provider( $video_url_data ); + } + return $provider; }, 10, 3 ); ``` -A list of allowed providers can be updated via `tribe-embeds_allowed_provider_hosts` hook + +#### `tribe-embeds_allowed_provider_hosts` + +Expand or restrict the whitelist of hostnames that can be handled by built-in or custom providers. + +- **Signature:** `apply_filters( 'tribe-embeds_allowed_provider_hosts', $allowed_hosts, $host )` +- **Args:** + - `$allowed_hosts` — array of allowed host strings (e.g. ['youtube.com', 'youtu.be', 'vimeo.com', 'dailymotion.com']) + - `$host` — the currently detected host +- **Return:** Modified array of allowed hosts. +- **Example:** ```php -/** - * Allows to inject custom provider hosts - * @var array $allowed_hosts List of allowed hosts - * @var string $host Current video hostname - */ -$allowed_hosts = apply_filters( 'tribe-embeds_allowed_provider_hosts', $allowed_hosts, $host ); +add_filter( 'tribe-embeds_allowed_provider_hosts', function ( array $hosts ) { + $hosts[] = 'videos.example.com'; + return $hosts; +}, 10 ); ``` diff --git a/src/Providers/Wistia.php b/src/Providers/Wistia.php new file mode 100644 index 0000000..43d9391 --- /dev/null +++ b/src/Providers/Wistia.php @@ -0,0 +1,114 @@ +get_video_id() || ! defined( 'WISTIA_API_KEY' ) ) { + return []; + } + + // get the URL from the transient. + $image_data = get_transient( 'tribe-embed_' . $this->get_video_id() ); + + if ( false === $image_data ) { + $image_data = []; + + $wistia_api_secret = get_option( 'options_' . WISTIA_API_KEY, '' ); + + if ( empty( $wistia_api_secret ) ) { + return []; + } + + $video_details = wp_remote_get( self::BASE_URL . $this->get_video_id() . '.json', [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $wistia_api_secret, + 'accept' => 'application/json', + ], + ] ); + + // if the request to the image errors or returns anything other than a http 200 response code. + if ( ( is_wp_error( $video_details )) && ( 200 !== wp_remote_retrieve_response_code( $video_details ) ) ) { + return []; + } + + // grab the body of the response. + $response_body = json_decode( + wp_remote_retrieve_body( + $video_details + ) + ); + + if ( $response_body === null ) { + return []; + } + + foreach ( self::IMAGE_SIZES as $resolution ) { + if ( empty( $response_body->thumbnail ) || empty( $response_body->thumbnail->url ) ) { + continue; + } + + $image_url = strtok( $response_body->thumbnail->url, '?' ); + switch ( $resolution ) { + case 'thumbnail_640_url': + $image_url .= '?image_crop_resized=640x360'; + break; + case 'thumbnail_320_url': + $image_url .= '?image_crop_resized=320x260'; + break; + } + + + $image_size = getimagesize( $image_url ); + $width = $image_size[0]; + $height = $image_size[1]; + // set the image data + $image_data[ $resolution ] = [ + 'url' => $image_url, + 'width' => $width, + 'height' => $height, + ]; + } + + // set the transient, storing the image url. + set_transient( 'tribe-embed_' . $this->get_video_id(), $image_data, DAY_IN_SECONDS ); + } + + // Prevent edge case when `$image_data` may have boolean value + if ( ! is_array( $image_data ) || empty( $image_data ) ) { + $image_data = []; + } + + // return the url. + return apply_filters( 'tribe-embed_wistia_video_thumbnail_url', $image_data, $this->get_video_id() ); + } + + protected function set_video_id(): string { + if ( empty( $this->video_url['path'] ) ) { + return ''; + } + + // remove the preceeding slash. + return str_replace( '/medias/', '', $this->video_url['path'] ); + } + +} diff --git a/src/Util/Assets.php b/src/Util/Assets.php index d7e6f12..24a8c27 100644 --- a/src/Util/Assets.php +++ b/src/Util/Assets.php @@ -21,7 +21,7 @@ public function __construct( string $plugin_name, string $version ) { * Registers the admin scripts */ public function register_admin_scripts(): void { - $asset_file = include TRIBE_MP_PATH . 'dist/editor.asset.php'; + $asset_file = include TRIBE_MP_PATH . 'dist/editor.asset.php'; wp_enqueue_script( $this->plugin_name . '-admin', TRIBE_MP_URL . 'dist/editor.js', $asset_file['dependencies'], $asset_file['version'] ); wp_enqueue_style( $this->plugin_name . '-admin', TRIBE_MP_URL . 'dist/editor.css', $asset_file['version'] ); } @@ -30,7 +30,7 @@ public function register_admin_scripts(): void { * Registers the public scripts */ public function register_public_scripts(): void { - $asset_file = include TRIBE_MP_PATH . 'dist/index.asset.php'; + $asset_file = include TRIBE_MP_PATH . 'dist/index.asset.php'; wp_enqueue_script( $this->plugin_name . '-public', TRIBE_MP_URL . 'dist/index.js', $asset_file['dependencies'], $asset_file['version'] ); wp_enqueue_style( $this->plugin_name . '-public', TRIBE_MP_URL . 'dist/style-index.css', $asset_file['version'] ); } From 218cd87a9a51dfd5f60f26d706b44892513321c8 Mon Sep 17 00:00:00 2001 From: Mykhailo Los Date: Tue, 23 Sep 2025 17:47:17 +0300 Subject: [PATCH 05/22] Update Readme --- readme.md | 86 ++++++++++++++++++++++++++++++ src/Providers/Provider_Factory.php | 1 + 2 files changed, 87 insertions(+) diff --git a/readme.md b/readme.md index 6c2f48d..90c9a9b 100644 --- a/readme.md +++ b/readme.md @@ -75,3 +75,89 @@ add_filter( 'tribe-embeds_allowed_provider_hosts', function ( array $hosts ) { return $hosts; }, 10 ); ``` + +#### `tribe_embeds_video_provider` + +Allow short-circuit with a ready-made provider instance + +- **Signature:** `apply_filters( 'tribe_embeds_video_provider', null, $video_url_data, $block )` +- **Args:** + - `null|` — if object provided resolves immediately and return provided object + - `$video_url_data` — embed video ulr + - `$block` — current embed block data +- **Return:** `null` or provided provider class +- **Example:** +```php +add_filter( 'tribe_embeds_video_provider', function ( $obj, $video_url_data, $block ) { + // Note: $video_url_data has parsed video url. Provider accepts url string + $provider = new CustomProvider( $video_url ); + + return $provider; +}, 10 ); +``` + +#### `tribe_embeds_allowed_provider_hosts_` + +Adjust allowed hosts for provider + +- **Signature:** `apply_filters( 'tribe_embeds_allowed_provider_hosts_' . $slug, $base, $provider_class );` +- **Args:** + - `$base` — list of allowed hosts + - `$provider_class` — current provider class + +#### `tribe_embeds_allowed_provider_hosts` + +Adjust allowed hosts for provider + +- **Signature:** `apply_filters( 'tribe_embeds_allowed_provider_hosts', $by_provider, $provider_class );` +- **Args:** + - `$by_provider` — List of hosts returned from `tribe_embeds_allowed_provider_hosts_` + - `$provider_class` — current provider class + +#### `tribe_embeds_image_sizes_` + +Get image sizes for a provider + +- **Signature:** `apply_filters( 'tribe_embeds_image_sizes_' . $slug, $base, $provider_class );` +- **Args:** + - `$base` — list of image sizes + - `$provider_class` — current provider class + +#### `tribe_embeds_image_sizes` + +Get image sizes for a provider + +- **Signature:** `apply_filters( 'tribe_embeds_image_sizes', $by_provider, $provider_class );` +- **Args:** + - `$by_provider` — list of image sizes `tribe_embeds_image_sizes_` + - `$provider_class` — current provider class + +#### `tribe_embeds_provider_classes` + +Allow external override of provider class list + +- **Signature:** `apply_filters( 'tribe_embeds_provider_classes', $provider_classes ?: $defaults );` +- **Args:** + - `$provider_classes` — list of existing providers classes + +#### `tribe-embed__video_thumbnail_url` + +Allows to adjust image data for each provider. Use slug instead of `` e.g `tribe-embed_wistia_video_thumbnail_url` + +- **Signature:** `apply_filters( 'tribe-embed_wistia_video_thumbnail_url', $image_data, $video_id )` +- **Args:** + - `$image_data` — Thumbnail image data + - `$video_id` — current video id +- **Return:** Video thumbnail image data. + +#### `tribe_embeds_facade_html` + +Fires and action to which the new block markup is added too. + +- **Signature:** `apply_filters( 'tribe_embeds_facade_html', $facade_html, $provider, $block, $html )` +- **Args:** + - `$facade_html` — Resulting html + - `$provider` — provider class + - `$block` — current block + - `$html` — original block html +- **Return:** Embed block markup diff --git a/src/Providers/Provider_Factory.php b/src/Providers/Provider_Factory.php index 1c6ee0a..1aa5c56 100644 --- a/src/Providers/Provider_Factory.php +++ b/src/Providers/Provider_Factory.php @@ -18,6 +18,7 @@ public function __construct( array $provider_classes = [] ) { '\\Tribe\\Tribe_Embed\\Providers\\YouTube', '\\Tribe\\Tribe_Embed\\Providers\\Vimeo', '\\Tribe\\Tribe_Embed\\Providers\\Dailymotion', + '\\Tribe\\Tribe_Embed\\Providers\\Wistia', ]; /** Allow external override of provider class list */ From dd23899f7824fe29f546bb23acdf000d74465c45 Mon Sep 17 00:00:00 2001 From: Mykhailo Los Date: Wed, 1 Oct 2025 17:13:29 +0300 Subject: [PATCH 06/22] Resolve CR --- src/Providers/Wistia.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Providers/Wistia.php b/src/Providers/Wistia.php index 43d9391..313c137 100644 --- a/src/Providers/Wistia.php +++ b/src/Providers/Wistia.php @@ -47,7 +47,7 @@ public function get_thumbnail_data(): array { ] ); // if the request to the image errors or returns anything other than a http 200 response code. - if ( ( is_wp_error( $video_details )) && ( 200 !== wp_remote_retrieve_response_code( $video_details ) ) ) { + if ( ( is_wp_error( $video_details ) ) || ( 200 !== wp_remote_retrieve_response_code( $video_details ) ) ) { return []; } @@ -70,10 +70,14 @@ public function get_thumbnail_data(): array { $image_url = strtok( $response_body->thumbnail->url, '?' ); switch ( $resolution ) { case 'thumbnail_640_url': - $image_url .= '?image_crop_resized=640x360'; + $image_url = add_query_arg( [ + 'image_crop_resized' => '640x360', + ], $image_url ); break; case 'thumbnail_320_url': - $image_url .= '?image_crop_resized=320x260'; + $image_url = add_query_arg( [ + 'image_crop_resized' => '320x260', + ], $image_url ); break; } From 266e08e2e028bea6e99c83d7037dd06beecb2817 Mon Sep 17 00:00:00 2001 From: Mykhailo Los Date: Wed, 15 Oct 2025 21:02:16 +0300 Subject: [PATCH 07/22] Update deprecate Vimeo API --- readme.md | 5 ++ src/Admin/Settings_Page.php | 81 +++++++++++++++++++++++++++ src/Core.php | 7 +++ src/Providers/Vimeo.php | 108 ++++++++++++++++++++++++------------ 4 files changed, 167 insertions(+), 34 deletions(-) create mode 100644 src/Admin/Settings_Page.php diff --git a/readme.md b/readme.md index 682ee27..774e145 100644 --- a/readme.md +++ b/readme.md @@ -68,3 +68,8 @@ A list of allowed providers can be updated via `tribe-embeds_allowed_provider_ho */ $allowed_hosts = apply_filters( 'tribe-embeds_allowed_provider_hosts', $allowed_hosts, $host ); ``` + +### Vimeo + +In order to make Vimeo provider thumbs work correctly create an access token https://help.vimeo.com/hc/en-us/articles/12427789081745-How-to-generate-a-personal-access-token +Use Tribe Embeds Settings page to store token via DB or set `VIMEO_ACCESS_TOKEN` in you wp-config.php diff --git a/src/Admin/Settings_Page.php b/src/Admin/Settings_Page.php new file mode 100644 index 0000000..8508b09 --- /dev/null +++ b/src/Admin/Settings_Page.php @@ -0,0 +1,81 @@ + 'sanitize_text_field' ] + ); + } + + /** + * Render the settings page. + */ + public function render_page(): void { + ?> +
+

+ +
+ + + + + + +
+ + + +
+ +
+
+ get_option( 'tribe_embeds_vimeo_access_token', '' ), + ]; + } + +} diff --git a/src/Core.php b/src/Core.php index f7dfef4..8525b1d 100644 --- a/src/Core.php +++ b/src/Core.php @@ -2,6 +2,7 @@ namespace Tribe\Tribe_Embed; +use Tribe\Tribe_Embed\Admin\Settings_Page; use Tribe\Tribe_Embed\Providers\Dailymotion; use Tribe\Tribe_Embed\Providers\Vimeo; use Tribe\Tribe_Embed\Providers\YouTube; @@ -39,6 +40,12 @@ public function init( string $file ): void { add_action( 'video_thumbnail_markup', [ $this, 'add_video_thumbnail_markup' ], 30, 4 ); add_action( 'video_thumbnail_markup', [ $this, 'close_markup_figure_element' ], 40, 4 ); add_action( 'video_thumbnail_markup', [ $this, 'add_original_embed_template' ], 50, 4 ); + + add_action( 'init', [ $this, 'register_settings' ] ); + } + + public function register_settings(): void { + ( new Settings_Page() ); } /** diff --git a/src/Providers/Vimeo.php b/src/Providers/Vimeo.php index f33533e..39e3808 100644 --- a/src/Providers/Vimeo.php +++ b/src/Providers/Vimeo.php @@ -2,15 +2,11 @@ namespace Tribe\Tribe_Embed\Providers; -final class Vimeo extends Provider { +use Tribe\Tribe_Embed\Admin\Settings_Page; - public const BASE_URL = 'https://vimeo.com/api/v2/video/'; +final class Vimeo extends Provider { - public const IMAGE_SIZES = [ - 'thumbnail_small', - 'thumbnail_medium', - 'thumbnail_large', - ]; + public const BASE_URL = 'https://api.vimeo.com/videos/%s/pictures'; public const ALLOWED_HOSTS = [ 'www.vimeo.com', @@ -34,37 +30,22 @@ public function get_thumbnail_data(): array { if ( false === $image_data ) { $image_data = []; - // get the video details from the api. - $video_details = wp_remote_get( - self::BASE_URL . esc_attr( $this->get_video_id() ) . '.json' - ); - - // if the request to the hi res image errors or returns anything other than a http 200 response code. - if ( ( is_wp_error( $video_details )) && ( 200 !== wp_remote_retrieve_response_code( $video_details ) ) ) { - return []; - } + $response_body = $this->get_video_pictures(); - // grab the body of the response. - $response_body = json_decode( - wp_remote_retrieve_body( - $video_details - ) - ); - - if ( $response_body === null ) { + if ( empty( $response_body ) || empty( $response_body['data'] ) || empty( $response_body['data'][0]['sizes'] ) ) { return []; } - foreach ( self::IMAGE_SIZES as $resolution ) { + foreach ( $response_body['data'][0]['sizes'] as $resolution ) { // get the image url from the json. - $image_url = $response_body[0]->$resolution; + $image_url = $resolution['link']; + $width = $resolution['width']; + $height = $resolution['height']; - $image_size = getimagesize( $image_url ); - $width = $image_size[0]; - $height = $image_size[1]; + $resolution_name = sprintf( 'thumbnail_%s_%s', $width, $height ); // set the image data - $image_data[ $resolution ] = [ + $image_data[ $resolution_name ] = [ 'url' => $image_url, 'width' => $width, 'height' => $height, @@ -79,20 +60,79 @@ public function get_thumbnail_data(): array { return apply_filters( 'tribe-embed_vimeo_video_thumbnail_url', $image_data, $this->get_video_id() ); } + protected function get_video_pictures(): array { + $token = $this->get_token(); + + if ( empty( $token ) ) { + return []; + } + + // get the video details from the api. + $video_details = wp_remote_get( + sprintf( self::BASE_URL, $this->get_video_id() ), + [ + 'headers' => [ + 'Authorization' => sprintf( 'Bearer %s', $token ), + 'Accept' => 'application/json', + ], + ] + ); + + // if the request to the hi res image errors or returns anything other than a http 200 response code. + if ( ( is_wp_error( $video_details )) && ( 200 !== wp_remote_retrieve_response_code( $video_details ) ) ) { + return []; + } + + // grab the body of the response. + $response_body = json_decode( + wp_remote_retrieve_body( + $video_details + ), + true + ); + + if ( $response_body === null ) { + return []; + } + + return $response_body; + } + protected function set_video_id(): string { switch ( $this->video_url['host'] ) { case 'vimeo.com': case 'www.vimeo.com': - // if we have a path. - if ( $this->video_url['path'] === '' ) { - return $this->video_url['path']; + if ( empty( $this->video_url['path'] ) ) { + return ''; + } + + $maybe_find_correct_id = explode( '/', trim( $this->video_url['path'] ) ); + + if ( is_array( $maybe_find_correct_id ) && isset( $maybe_find_correct_id[2] ) ) { + // urls like https://vimeo.com/1083696811/fd0767701e + return $maybe_find_correct_id[1]; } // remove the preceeding slash. return str_replace( '/', '', $this->video_url['path'] ); - break; + default: + return ''; } } + protected function get_token(): string { + if ( defined( 'VIMEO_ACCESS_TOKEN' ) && ! empty( VIMEO_ACCESS_TOKEN ) ) { + return VIMEO_ACCESS_TOKEN; + } + + $settings = Settings_Page::get_stored_settings(); + + if ( ! empty( $settings[ Settings_Page::VIMEO_TOKEN ] ) ) { + return $settings[ Settings_Page::VIMEO_TOKEN ]; + } + + return ''; + } + } From 50761ecd3cb165b7ceb477b955a25b95843c7b43 Mon Sep 17 00:00:00 2001 From: Mykhailo Los Date: Wed, 15 Oct 2025 21:04:03 +0300 Subject: [PATCH 08/22] Bump version --- src/Core.php | 2 +- tribe-embed.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Core.php b/src/Core.php index 8525b1d..e111767 100644 --- a/src/Core.php +++ b/src/Core.php @@ -10,7 +10,7 @@ final class Core { - public const VERSION = '1.0.3'; + public const VERSION = '1.1.1'; public const PLUGIN_NAME = 'tribe-embed'; private static self $instance; diff --git a/tribe-embed.php b/tribe-embed.php index 1662222..d82d888 100644 --- a/tribe-embed.php +++ b/tribe-embed.php @@ -4,7 +4,7 @@ * Plugin Name: Tribe Embed * Plugin URI: https://github.com/moderntribe/tribe-embed * Description: A Tribe Embed Plugin. - * Version: 1.1.0 + * Version: 1.1.1 * Requires at least: 6.3 * Requires PHP: 8.0 * Author: Modern Tribe From 5d1f5235f19720148630fc8d4da114bd8771c90d Mon Sep 17 00:00:00 2001 From: Mykhailo Los Date: Thu, 16 Oct 2025 18:29:36 +0300 Subject: [PATCH 09/22] Adjust Wistia settings --- src/Admin/Settings_Page.php | 25 +++++++++++- src/Admin/Support_Providers.php | 30 +++++++++++++++ src/Core.php | 5 ++- src/Providers/Provider_Factory.php | 25 ++++++++++-- src/Providers/Wistia.php | 34 ++++++++++------- src/Util/Block_Filter.php | 3 +- src/Util/Facade_Builder.php | 61 +++++++++++++++++++++--------- 7 files changed, 145 insertions(+), 38 deletions(-) create mode 100644 src/Admin/Support_Providers.php diff --git a/src/Admin/Settings_Page.php b/src/Admin/Settings_Page.php index 8508b09..8c4a870 100644 --- a/src/Admin/Settings_Page.php +++ b/src/Admin/Settings_Page.php @@ -4,7 +4,8 @@ class Settings_Page { - public const VIMEO_TOKEN = 'vimeo_access_token'; + public const VIMEO_TOKEN = 'vimeo_access_token'; + public const WISTIA_TOKEN = 'wistia_api_key'; public function __construct() { add_action( 'admin_menu', [ $this, 'add_menu' ] ); @@ -33,6 +34,12 @@ public function register_settings(): void { 'tribe_embeds_vimeo_access_token', [ 'sanitize_callback' => 'sanitize_text_field' ] ); + + register_setting( + 'tribe_embeds_settings', + 'tribe_embeds_wistia_api_key', + [ 'sanitize_callback' => 'sanitize_text_field' ] + ); } /** @@ -62,6 +69,19 @@ class="regular-text" /> + + + + + + + + @@ -74,7 +94,8 @@ class="regular-text" */ public static function get_stored_settings(): array { return [ - self::VIMEO_TOKEN => get_option( 'tribe_embeds_vimeo_access_token', '' ), + self::VIMEO_TOKEN => get_option( 'tribe_embeds_vimeo_access_token', '' ), + self::WISTIA_TOKEN => get_option( 'tribe_embeds_wistia_api_key', '' ), ]; } diff --git a/src/Admin/Support_Providers.php b/src/Admin/Support_Providers.php new file mode 100644 index 0000000..f6bc7fe --- /dev/null +++ b/src/Admin/Support_Providers.php @@ -0,0 +1,30 @@ +add_wistia_support(); + }, 10, 0 ); + add_filter( 'oembed_providers', function( $providers ) { + // Match subdomains and optional query strings + $providers['#https?://[^\.]+\.wistia\.com/medias/[a-zA-Z0-9]+(?:\?.*)?$#i'] = [ + 'https://fast.wistia.com/oembed', + true, // regex + ]; + return $providers; + }); + add_filter( 'the_content', [ $GLOBALS['wp_embed'], 'autoembed' ], 8 ); + } + + protected function add_wistia_support(): void { + wp_oembed_add_provider( + '#https?://[^\.]+\.wistia\.com/medias/[a-zA-Z0-9]+(?:\?.*)?$#i', + 'https://fast.wistia.com/oembed', + true + ); + } + +} diff --git a/src/Core.php b/src/Core.php index feb445d..616bbdb 100644 --- a/src/Core.php +++ b/src/Core.php @@ -3,6 +3,7 @@ namespace Tribe\Tribe_Embed; use Tribe\Tribe_Embed\Admin\Settings_Page; +use Tribe\Tribe_Embed\Admin\Support_Providers; use Tribe\Tribe_Embed\Providers\Provider_Factory; use Tribe\Tribe_Embed\Util\Assets; use Tribe\Tribe_Embed\Util\Block_Filter; @@ -43,13 +44,15 @@ public static function instance(): self { /** Register WP hooks via Block_Filter */ public function register_hooks(): void { - $assets = new Assets( self::PLUGIN_NAME, self::VERSION ); + $assets = new Assets( self::PLUGIN_NAME, self::VERSION ); + $providers_support = new Support_Providers(); add_action( 'admin_enqueue_scripts', [ $assets, 'register_admin_scripts' ] ); add_action( 'wp_enqueue_scripts', [ $assets, 'register_public_scripts' ] ); add_action( 'init', [ $this, 'register_settings' ] ); $this->block_filter()->register_hooks(); + $providers_support->register(); } public function register_settings(): void { diff --git a/src/Providers/Provider_Factory.php b/src/Providers/Provider_Factory.php index 1aa5c56..3ae33d7 100644 --- a/src/Providers/Provider_Factory.php +++ b/src/Providers/Provider_Factory.php @@ -50,7 +50,7 @@ public function resolve( array $video_url_data, array $block ): ?object { if ( $host !== '' ) { foreach ( $this->provider_classes as $class ) { - if ( in_array( $host, $this->allowed_hosts_for( $class ), true ) ) { + if ( in_array( $host, $this->allowed_hosts_for( $class ), true ) || $this->is_allowed_hosts_by_regex( $class, $host ) ) { return $this->instantiate( $class, $video_url_data ); } } @@ -69,6 +69,26 @@ public function resolve( array $video_url_data, array $block ): ?object { * @return array Lowercased hostnames. */ public function allowed_hosts_for( string $provider_class ): array { + $global = $this->get_allowed_rules( $provider_class ); + + return array_values( array_unique( array_map( 'strtolower', array_filter( $global, 'is_string' ) ) ) ); + } + + public function is_allowed_hosts_by_regex( string $provider_class, string $host ): bool { + $global = $this->get_allowed_rules( $provider_class ); + + foreach ( $global as $pattern ) { + if ( ! preg_match( '/' . $pattern . '/i', $host ) ) { + continue; + } + + return true; + } + + return false; + } + + public function get_allowed_rules( string $provider_class ): array { $base = []; if ( defined( $provider_class . '::ALLOWED_HOSTS' ) ) { @@ -79,9 +99,8 @@ public function allowed_hosts_for( string $provider_class ): array { $slug = $this->provider_slug( $provider_class ); $by_provider = apply_filters( 'tribe_embeds_allowed_provider_hosts_' . $slug, $base, $provider_class ); - $global = apply_filters( 'tribe_embeds_allowed_provider_hosts', $by_provider, $provider_class ); - return array_values( array_unique( array_map( 'strtolower', array_filter( (array) $global, 'is_string' ) ) ) ); + return apply_filters( 'tribe_embeds_allowed_provider_hosts', $by_provider, $provider_class ); } /** diff --git a/src/Providers/Wistia.php b/src/Providers/Wistia.php index 313c137..a119de6 100644 --- a/src/Providers/Wistia.php +++ b/src/Providers/Wistia.php @@ -2,16 +2,15 @@ namespace Tribe\Tribe_Embed\Providers; +use Tribe\Tribe_Embed\Admin\Settings_Page; + class Wistia extends Provider { public const BASE_URL = 'https://api.wistia.com/v1/medias/'; public const ALLOWED_HOSTS = [ - 'fast.wistia.com', - 'wistia.com', - 'wistia.com', - 'www.wistia.com', - 'wi.st', + '(^|\.)wistia\.com$', + '(^|\.)wi\.st$', ]; // Example: https://embed-ssl.wistia.com/deliveries/be29ff2d1c1e783bda383f30d4ec027152fcc6be.jpg?image_crop_resized=200x1200 @@ -22,8 +21,9 @@ class Wistia extends Provider { ]; public function get_thumbnail_data(): array { + $token = $this->get_token(); // if we have no video id. - if ( '' === $this->get_video_id() || ! defined( 'WISTIA_API_KEY' ) ) { + if ( '' === $this->get_video_id() || empty( $token ) ) { return []; } @@ -33,15 +33,9 @@ public function get_thumbnail_data(): array { if ( false === $image_data ) { $image_data = []; - $wistia_api_secret = get_option( 'options_' . WISTIA_API_KEY, '' ); - - if ( empty( $wistia_api_secret ) ) { - return []; - } - $video_details = wp_remote_get( self::BASE_URL . $this->get_video_id() . '.json', [ 'headers' => [ - 'Authorization' => 'Bearer ' . $wistia_api_secret, + 'Authorization' => 'Bearer ' . $token, 'accept' => 'application/json', ], ] ); @@ -115,4 +109,18 @@ protected function set_video_id(): string { return str_replace( '/medias/', '', $this->video_url['path'] ); } + protected function get_token(): string { + if ( defined( 'WISTIA_API_KEY' ) && ! empty( WISTIA_API_KEY ) ) { + return WISTIA_API_KEY; + } + + $settings = Settings_Page::get_stored_settings(); + + if ( ! empty( $settings[ Settings_Page::WISTIA_TOKEN ] ) ) { + return (string) $settings[ Settings_Page::WISTIA_TOKEN ]; + } + + return ''; + } + } diff --git a/src/Util/Block_Filter.php b/src/Util/Block_Filter.php index dcc3579..fb8bbcd 100644 --- a/src/Util/Block_Filter.php +++ b/src/Util/Block_Filter.php @@ -65,6 +65,7 @@ public function filter_render_block( string $html, array $block ): string { // Fallback: use thumbnails and facade $result = $this->thumbs->resolve_thumb( $video_url_data, $block ); + if ( $result === null ) { return $html; } @@ -75,7 +76,7 @@ public function filter_render_block( string $html, array $block ): string { // output the registered "block" styles for the thumbnail. wp_print_styles( 'tribe-embeds-styles' ); - $facade_html = $this->facade->build( $result['thumb'], $block, $result['video_id'] ); + $facade_html = $this->facade->build( $result['thumb'], $block, $result['video_id'], $result['provider'] ); /** * Fires and action to which the new block markup is added too. diff --git a/src/Util/Facade_Builder.php b/src/Util/Facade_Builder.php index b1bb58a..d79b045 100644 --- a/src/Util/Facade_Builder.php +++ b/src/Util/Facade_Builder.php @@ -2,6 +2,9 @@ namespace Tribe\Tribe_Embed\Util; +use Tribe\Tribe_Embed\Providers\Provider; +use Tribe\Tribe_Embed\Providers\Wistia; + /** * Builds HTML facade for video embeds. */ @@ -14,14 +17,21 @@ final class Facade_Builder { * @param array $block * @param string $video_id */ - public function build( array $thumb, array $block, string $video_id ): string { + public function build( array $thumb, array $block, string $video_id, Provider $provider ): string { $wrapper_classes = $this->get_wrapper_classes( $block, $video_id, $thumb ); $content = $this->open_markup_figure_element( $block, $video_id, $thumb, $wrapper_classes ); $content .= $this->add_video_play_button( $block, $video_id, $thumb, $wrapper_classes ); $content .= $this->add_video_thumbnail_markup( $block, $video_id, $thumb, $wrapper_classes ); + + if ( is_a( $provider, Wistia::class ) ) { + $content .= $this->add_raw_original_embed( $block, $video_id, $thumb, $wrapper_classes, $provider ); + $content .= $this->close_markup_figure_element( $block, $video_id, $thumb, $wrapper_classes ); + + return $content; + } $content .= $this->close_markup_figure_element( $block, $video_id, $thumb, $wrapper_classes ); - $content .= $this->add_original_embed_template( $block, $video_id, $thumb, $wrapper_classes ); + $content .= $this->add_original_embed_template( $block, $video_id, $thumb, $wrapper_classes, $provider ); return $content; } @@ -147,41 +157,52 @@ public function allowed_innerblock_html(): array { * Therefore, we need to allow these to keep the same structure. */ return [ - 'iframe' => [ + 'iframe' => [ 'src' => true, 'height' => true, 'width' => true, 'frameborder' => true, 'allowfullscreen' => true, ], - 'figure' => [ + 'style' => [], + 'wistia-player' => [ + 'media-id' => true, + 'dnt' => true, + 'aspect' => true, + ], + 'script' => [ + 'src' => true, + 'type' => true, + 'async' => true, + ], + 'figure' => [ 'class' => true, ], - 'figcaption' => [ + 'figcaption' => [ 'class' => true, ], - 'div' => [ + 'div' => [ 'class' => true, ], - 'a' => [ + 'a' => [ 'class' => true, 'href' => true, 'data-type' => true, ], - 'strong' => [], - 'em' => [], - 'sub' => [], - 'sup' => [], - 's' => [], - 'kbd' => [], - 'img' => [ + 'strong' => [], + 'em' => [], + 'sub' => [], + 'sup' => [], + 's' => [], + 'kbd' => [], + 'img' => [ 'class' => true, 'style' => true, 'src' => true, 'alt' => true, ], - 'code' => [], - 'mark' => [ + 'code' => [], + 'mark' => [ 'style' => true, 'class' => true, ], @@ -197,10 +218,14 @@ public function allowed_innerblock_html(): array { * @param array $thumbnail_data The URL of the video thumbnail. * @param array $wrapper_classes An array of CSS classes to add to the wrapper. */ - public function add_original_embed_template( array $block, string $video_id, array $thumbnail_data, array $wrapper_classes ): string { + public function add_original_embed_template( array $block, string $video_id, array $thumbnail_data, array $wrapper_classes, Provider $provider ): string { $html = sprintf( '', esc_attr( $video_id ), wp_kses( $block['innerHTML'], $this->allowed_innerblock_html() ) ); - return apply_filters( 'tribe_embeds_video_embed_template', $html, $block, $video_id, $thumbnail_data, $wrapper_classes ); + return apply_filters( 'tribe_embeds_video_embed_template', $html, $block, $video_id, $thumbnail_data, $wrapper_classes, $provider ); + } + + public function add_raw_original_embed( array $block, string $video_id, array $thumbnail_data, array $wrapper_classes, Provider $provider ): string { + return apply_filters( 'tribe_embeds_video_embed_template', wp_kses( $block['innerHTML'], $this->allowed_innerblock_html() ), $block, $video_id, $thumbnail_data, $wrapper_classes, $provider ); } } From 9d61acd92bbbd376cc21099dccd17a3bbb342de3 Mon Sep 17 00:00:00 2001 From: Mykhailo Los Date: Thu, 20 Nov 2025 10:53:17 +0200 Subject: [PATCH 10/22] Add support for YT shorts --- src/Providers/YouTube.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Providers/YouTube.php b/src/Providers/YouTube.php index be4be7f..5b43512 100644 --- a/src/Providers/YouTube.php +++ b/src/Providers/YouTube.php @@ -72,6 +72,22 @@ protected function set_video_id(): string { // for standard youtube URLs case 'www.youtube.com': case 'youtube.com': + // parse the query part of the URL into its arguments. + if ( ! empty( $this->video_url['path'] ) && str_starts_with( $this->video_url['path'], '/shorts/' ) ) { + // Extract video ID from /shorts/VIDEO_ID path + $path_parts = explode( '/', trim( $this->video_url['path'], '/' ) ); + if ( count( $path_parts ) >= 2 && $path_parts[0] === 'shorts' ) { + return $path_parts[1]; + } + + return ''; + } + + // Handle standard YouTube URLs with query parameters + if ( empty( $this->video_url['query'] ) ) { + return ''; + } + // parse the query part of the URL into its arguments. parse_str( $this->video_url['query'], $video_url_query_args ); @@ -83,7 +99,6 @@ protected function set_video_id(): string { // set the video id to the v query arg. return $video_url_query_args['v']; - // for youtube short urls. case 'youtu.be': // if we have a path. if ( empty( $this->video_url['path'] ) ) { From b4325c0ad4cd9e30a92aaa629b9fd1a1480dc518 Mon Sep 17 00:00:00 2001 From: Mykhailo Los Date: Thu, 20 Nov 2025 10:59:49 +0200 Subject: [PATCH 11/22] Fix issue with missing provider name --- src/Util/Facade_Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Util/Facade_Builder.php b/src/Util/Facade_Builder.php index d79b045..ed81f13 100644 --- a/src/Util/Facade_Builder.php +++ b/src/Util/Facade_Builder.php @@ -47,7 +47,7 @@ public function get_wrapper_classes( array $block, string $video_id, array $thum $wrapper_classes = [ 'wp-block-image', 'tribe-embed', - 'is--' . $block['attrs']['providerNameSlug'], + 'is--' . ( $block['attrs']['providerNameSlug'] ?? 'default' ), ]; // if we have classNames on the embed block. From 656dd543fe59eacc59031b4229a5f0315de28e3e Mon Sep 17 00:00:00 2001 From: Mykhailo Los Date: Thu, 20 Nov 2025 11:02:33 +0200 Subject: [PATCH 12/22] Fix issue with missing provider name --- src/Util/Facade_Builder.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Util/Facade_Builder.php b/src/Util/Facade_Builder.php index ed81f13..c59a332 100644 --- a/src/Util/Facade_Builder.php +++ b/src/Util/Facade_Builder.php @@ -101,10 +101,15 @@ public function add_video_thumbnail_markup( array $block, string $video_id, arra $srcset[] = $data['url'] . ' ' . $data['width'] . 'w'; } + $classes = apply_filters( 'tribe_embeds_video_thumb_classes', [ + 'tribe-embed__thumbnail', + ], $block, $video_id, $thumbnail_data, $wrapper_classes ); + $image_tag = sprintf( - '', + '', $max_res_image['width'], $max_res_image['height'], + implode( ' ', $classes ), $max_res_image['url'], implode( ',', $srcset ), implode( ',', $sizes ) From 94386cdb4c3a2e06b97ff7648c9197b6e5dec41f Mon Sep 17 00:00:00 2001 From: Mykhailo Los Date: Fri, 21 Nov 2025 20:20:08 +0200 Subject: [PATCH 13/22] Update Wistia API --- src/Providers/Wistia.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Providers/Wistia.php b/src/Providers/Wistia.php index a119de6..74c36d3 100644 --- a/src/Providers/Wistia.php +++ b/src/Providers/Wistia.php @@ -6,7 +6,7 @@ class Wistia extends Provider { - public const BASE_URL = 'https://api.wistia.com/v1/medias/'; + public const BASE_URL = 'https://api.wistia.com/v1/medias?type=Video&hashed_id='; public const ALLOWED_HOSTS = [ '(^|\.)wistia\.com$', @@ -30,13 +30,14 @@ public function get_thumbnail_data(): array { // get the URL from the transient. $image_data = get_transient( 'tribe-embed_' . $this->get_video_id() ); - if ( false === $image_data ) { + if ( empty( $image_data ) ) { $image_data = []; - $video_details = wp_remote_get( self::BASE_URL . $this->get_video_id() . '.json', [ + $video_details = wp_remote_get(self::BASE_URL . $this->get_video_id(), [ 'headers' => [ - 'Authorization' => 'Bearer ' . $token, + 'authorization' => 'Bearer ' . $token, 'accept' => 'application/json', + 'content-type' => 'application/json', ], ] ); @@ -57,11 +58,11 @@ public function get_thumbnail_data(): array { } foreach ( self::IMAGE_SIZES as $resolution ) { - if ( empty( $response_body->thumbnail ) || empty( $response_body->thumbnail->url ) ) { + if ( empty( $response_body[0]->thumbnail ) || empty( $response_body[0]->thumbnail->url ) ) { continue; } - $image_url = strtok( $response_body->thumbnail->url, '?' ); + $image_url = strtok( $response_body[0]->thumbnail->url, '?' ); switch ( $resolution ) { case 'thumbnail_640_url': $image_url = add_query_arg( [ From d8d7e0817d50a4403325c9a6cf341affdd173ecd Mon Sep 17 00:00:00 2001 From: Mykhailo Los Date: Wed, 4 Mar 2026 18:28:37 +0200 Subject: [PATCH 14/22] Make twicks and updates --- src/Admin/Credentials.php | 31 +++++++++++++++++++++++++++++++ src/Admin/Support_Providers.php | 30 +++++++++++++----------------- src/Core.php | 4 ---- src/Providers/Dailymotion.php | 5 +++-- src/Providers/Provider.php | 15 +++++++++++++-- src/Providers/Vimeo.php | 20 +++++--------------- src/Providers/Wistia.php | 22 ++++++++-------------- src/Providers/YouTube.php | 2 +- src/Util/Assets.php | 11 ++++------- src/Util/Block_Filter.php | 16 ++++++---------- src/Util/Facade_Builder.php | 5 ++--- src/Util/Thumbnail_Service.php | 2 +- tribe-embed.php | 12 +++++++++++- 13 files changed, 98 insertions(+), 77 deletions(-) create mode 100644 src/Admin/Credentials.php diff --git a/src/Admin/Credentials.php b/src/Admin/Credentials.php new file mode 100644 index 0000000..a51c4dc --- /dev/null +++ b/src/Admin/Credentials.php @@ -0,0 +1,31 @@ +add_wistia_support(); - }, 10, 0 ); - add_filter( 'oembed_providers', function( $providers ) { - // Match subdomains and optional query strings - $providers['#https?://[^\.]+\.wistia\.com/medias/[a-zA-Z0-9]+(?:\?.*)?$#i'] = [ - 'https://fast.wistia.com/oembed', - true, // regex - ]; - return $providers; - }); + add_filter( 'oembed_providers', [ $this, 'add_wistia_oembed' ], 10, 1 ); add_filter( 'the_content', [ $GLOBALS['wp_embed'], 'autoembed' ], 8 ); } - protected function add_wistia_support(): void { - wp_oembed_add_provider( - '#https?://[^\.]+\.wistia\.com/medias/[a-zA-Z0-9]+(?:\?.*)?$#i', - 'https://fast.wistia.com/oembed', - true - ); + /** + * @param array $providers + * + * @return array + */ + public function add_wistia_oembed( array $providers ): array { + $providers[ self::WISTIA_REGEX ] = [ self::WISTIA_OEMBED, true ]; + + return $providers; } } diff --git a/src/Core.php b/src/Core.php index 616bbdb..19b4471 100644 --- a/src/Core.php +++ b/src/Core.php @@ -29,10 +29,6 @@ final class Core { /** @var self|null Singleton instance */ private static ?self $instance = null; - private function __construct() { - define( 'TRIBE_MP_PATH', trailingslashit( plugin_dir_path( dirname( __FILE__ ) ) ) ); - } - /** Get Core singleton */ public static function instance(): self { if ( ! self::$instance instanceof self ) { diff --git a/src/Providers/Dailymotion.php b/src/Providers/Dailymotion.php index 90e5338..c3f9b7a 100644 --- a/src/Providers/Dailymotion.php +++ b/src/Providers/Dailymotion.php @@ -26,7 +26,7 @@ final class Dailymotion extends Provider { ]; /** - * Return the vimeo video thumbnail urls. + * Return the Dailymotion video thumbnail URLs. */ public function get_thumbnail_data(): array { @@ -47,7 +47,7 @@ public function get_thumbnail_data(): array { $video_details = wp_remote_get( self::BASE_URL . $this->get_video_id() . '?fields=' . $resolution ); // if the request to the image errors or returns anything other than a http 200 response code. - if ( ( is_wp_error( $video_details )) && ( 200 !== wp_remote_retrieve_response_code( $video_details ) ) ) { + if ( is_wp_error( $video_details ) || 200 !== wp_remote_retrieve_response_code( $video_details ) ) { return []; } @@ -96,6 +96,7 @@ protected function set_video_id(): string { // remove the preceeding slash. return str_replace( '/video/', '', $this->video_url['path'] ); + case 'dai.ly': // if we have a path. if ( empty( $this->video_url['path'] ) ) { diff --git a/src/Providers/Provider.php b/src/Providers/Provider.php index b5f5099..4ce9dc3 100644 --- a/src/Providers/Provider.php +++ b/src/Providers/Provider.php @@ -12,8 +12,11 @@ abstract class Provider { protected array $video_url; protected string $video_id; - protected static self $instance; - + /** + * Return thumbnail data for the video. + * + * @return array + */ abstract public function get_thumbnail_data(): array; abstract protected function set_video_id(): string; @@ -27,4 +30,12 @@ public function get_video_id(): string { return $this->video_id ?? ''; } + /** + * Whether this provider uses inline embed (raw HTML inside facade) instead of a template. + * When true, Facade_Builder will not wrap the embed in a template element. + */ + public function uses_inline_embed(): bool { + return false; + } + } diff --git a/src/Providers/Vimeo.php b/src/Providers/Vimeo.php index adb2145..0e59ac1 100644 --- a/src/Providers/Vimeo.php +++ b/src/Providers/Vimeo.php @@ -2,7 +2,7 @@ namespace Tribe\Tribe_Embed\Providers; -use Tribe\Tribe_Embed\Admin\Settings_Page; +use Tribe\Tribe_Embed\Admin\Credentials; final class Vimeo extends Provider { @@ -15,7 +15,7 @@ final class Vimeo extends Provider { ]; /** - * Return the vimeo video thumbnail urls. + * Return the Vimeo video thumbnail URLs. */ public function get_thumbnail_data(): array { @@ -79,8 +79,8 @@ protected function get_video_pictures(): array { ] ); - // if the request to the hi res image errors or returns anything other than a http 200 response code. - if ( ( is_wp_error( $video_details )) && ( 200 !== wp_remote_retrieve_response_code( $video_details ) ) ) { + // if the request to the hi-res image errors or returns anything other than a http 200 response code. + if ( is_wp_error( $video_details ) || 200 !== wp_remote_retrieve_response_code( $video_details ) ) { return []; } @@ -126,17 +126,7 @@ protected function set_video_id(): string { } protected function get_token(): string { - if ( defined( 'VIMEO_ACCESS_TOKEN' ) && ! empty( VIMEO_ACCESS_TOKEN ) ) { - return VIMEO_ACCESS_TOKEN; - } - - $settings = Settings_Page::get_stored_settings(); - - if ( ! empty( $settings[ Settings_Page::VIMEO_TOKEN ] ) ) { - return $settings[ Settings_Page::VIMEO_TOKEN ]; - } - - return ''; + return Credentials::get_vimeo_token(); } } diff --git a/src/Providers/Wistia.php b/src/Providers/Wistia.php index 74c36d3..4bc83bd 100644 --- a/src/Providers/Wistia.php +++ b/src/Providers/Wistia.php @@ -2,9 +2,9 @@ namespace Tribe\Tribe_Embed\Providers; -use Tribe\Tribe_Embed\Admin\Settings_Page; +use Tribe\Tribe_Embed\Admin\Credentials; -class Wistia extends Provider { +final class Wistia extends Provider { public const BASE_URL = 'https://api.wistia.com/v1/medias?type=Video&hashed_id='; @@ -37,7 +37,7 @@ public function get_thumbnail_data(): array { 'headers' => [ 'authorization' => 'Bearer ' . $token, 'accept' => 'application/json', - 'content-type' => 'application/json', + 'content-type' => 'application/json', ], ] ); @@ -101,6 +101,10 @@ public function get_thumbnail_data(): array { return apply_filters( 'tribe-embed_wistia_video_thumbnail_url', $image_data, $this->get_video_id() ); } + public function uses_inline_embed(): bool { + return true; + } + protected function set_video_id(): string { if ( empty( $this->video_url['path'] ) ) { return ''; @@ -111,17 +115,7 @@ protected function set_video_id(): string { } protected function get_token(): string { - if ( defined( 'WISTIA_API_KEY' ) && ! empty( WISTIA_API_KEY ) ) { - return WISTIA_API_KEY; - } - - $settings = Settings_Page::get_stored_settings(); - - if ( ! empty( $settings[ Settings_Page::WISTIA_TOKEN ] ) ) { - return (string) $settings[ Settings_Page::WISTIA_TOKEN ]; - } - - return ''; + return Credentials::get_wistia_token(); } } diff --git a/src/Providers/YouTube.php b/src/Providers/YouTube.php index 5b43512..f2ea9f3 100644 --- a/src/Providers/YouTube.php +++ b/src/Providers/YouTube.php @@ -20,7 +20,7 @@ final class YouTube extends Provider { ]; /** - * Accepts a video id and returns an array of thumbnail data + * Accepts a video id and returns an array of thumbnail data. */ public function get_thumbnail_data(): array { diff --git a/src/Util/Assets.php b/src/Util/Assets.php index 24a8c27..0b2ee02 100644 --- a/src/Util/Assets.php +++ b/src/Util/Assets.php @@ -12,9 +12,6 @@ final class Assets { public function __construct( string $plugin_name, string $version ) { $this->plugin_name = $plugin_name; $this->version = $version; - - define( 'TRIBE_MP_URL', plugin_dir_url( TRIBE_MP_PATH . $plugin_name ) ); - define( 'TRIBE_MP_VERSION', $version ); } /** @@ -22,8 +19,8 @@ public function __construct( string $plugin_name, string $version ) { */ public function register_admin_scripts(): void { $asset_file = include TRIBE_MP_PATH . 'dist/editor.asset.php'; - wp_enqueue_script( $this->plugin_name . '-admin', TRIBE_MP_URL . 'dist/editor.js', $asset_file['dependencies'], $asset_file['version'] ); - wp_enqueue_style( $this->plugin_name . '-admin', TRIBE_MP_URL . 'dist/editor.css', $asset_file['version'] ); + wp_enqueue_script( $this->plugin_name . '-admin', TRIBE_MP_URL . 'dist/editor.js', $asset_file['dependencies'], (string) $asset_file['version'] ); + wp_enqueue_style( $this->plugin_name . '-admin', TRIBE_MP_URL . 'dist/editor.css', [], (string) $asset_file['version'] ); } /** @@ -31,8 +28,8 @@ public function register_admin_scripts(): void { */ public function register_public_scripts(): void { $asset_file = include TRIBE_MP_PATH . 'dist/index.asset.php'; - wp_enqueue_script( $this->plugin_name . '-public', TRIBE_MP_URL . 'dist/index.js', $asset_file['dependencies'], $asset_file['version'] ); - wp_enqueue_style( $this->plugin_name . '-public', TRIBE_MP_URL . 'dist/style-index.css', $asset_file['version'] ); + wp_enqueue_script( $this->plugin_name . '-public', TRIBE_MP_URL . 'dist/index.js', $asset_file['dependencies'], (string) $asset_file['version'] ); + wp_enqueue_style( $this->plugin_name . '-public', TRIBE_MP_URL . 'dist/style-index.css', [], (string) $asset_file['version'] ); } } diff --git a/src/Util/Block_Filter.php b/src/Util/Block_Filter.php index fb8bbcd..1517552 100644 --- a/src/Util/Block_Filter.php +++ b/src/Util/Block_Filter.php @@ -70,18 +70,14 @@ public function filter_render_block( string $html, array $block ): string { return $html; } - // buffer the output as we need to return not echo. - ob_start(); - - // output the registered "block" styles for the thumbnail. - wp_print_styles( 'tribe-embeds-styles' ); - $facade_html = $this->facade->build( $result['thumb'], $block, $result['video_id'], $result['provider'] ); - /** - * Fires and action to which the new block markup is added too. - */ - echo apply_filters( 'tribe_embeds_facade_html', $facade_html, $result['provider'], $block, $html ); + /** Filter the facade HTML before output. */ + $facade_html = (string) apply_filters( 'tribe_embeds_facade_html', $facade_html, $result['provider'], $block, $html ); + + ob_start(); + wp_print_styles( 'tribe-embeds-styles' ); + echo $facade_html; return ob_get_clean(); } diff --git a/src/Util/Facade_Builder.php b/src/Util/Facade_Builder.php index c59a332..a6871f9 100644 --- a/src/Util/Facade_Builder.php +++ b/src/Util/Facade_Builder.php @@ -3,7 +3,6 @@ namespace Tribe\Tribe_Embed\Util; use Tribe\Tribe_Embed\Providers\Provider; -use Tribe\Tribe_Embed\Providers\Wistia; /** * Builds HTML facade for video embeds. @@ -24,7 +23,7 @@ public function build( array $thumb, array $block, string $video_id, Provider $p $content .= $this->add_video_play_button( $block, $video_id, $thumb, $wrapper_classes ); $content .= $this->add_video_thumbnail_markup( $block, $video_id, $thumb, $wrapper_classes ); - if ( is_a( $provider, Wistia::class ) ) { + if ( $provider->uses_inline_embed() ) { $content .= $this->add_raw_original_embed( $block, $video_id, $thumb, $wrapper_classes, $provider ); $content .= $this->close_markup_figure_element( $block, $video_id, $thumb, $wrapper_classes ); @@ -78,7 +77,7 @@ public function get_wrapper_classes( array $block, string $video_id, array $thum * @param array $wrapper_classes */ public function add_video_play_button( array $block, string $video_id, array $thumbnail_data, array $wrapper_classes ): string { - $button = sprintf( '', esc_html__( 'Play Video', 'tribe' ) ); + $button = sprintf( '', esc_html__( 'Play Video', 'tribe-embeds' ) ); return apply_filters( 'tribe_embeds_video_button_html', $button, $block, $video_id, $thumbnail_data, $wrapper_classes ); } diff --git a/src/Util/Thumbnail_Service.php b/src/Util/Thumbnail_Service.php index f4b488c..c6ae3f2 100644 --- a/src/Util/Thumbnail_Service.php +++ b/src/Util/Thumbnail_Service.php @@ -53,7 +53,7 @@ private function safe_get_thumbnail_data( object $provider, array $image_sizes ) } try { - $data = $provider->get_thumbnail_data( $image_sizes ); + $data = $provider->get_thumbnail_data(); } catch ( \Throwable $e ) { $data = []; } diff --git a/tribe-embed.php b/tribe-embed.php index b44e322..dbc0e6c 100644 --- a/tribe-embed.php +++ b/tribe-embed.php @@ -11,7 +11,7 @@ * Author URI: https://github.com/moderntribe * License: GPL v2 or later * License URI: https://www.gnu.org/licenses/gpl-2.0.html - * Text Domain: tribe + * Text Domain: tribe-embeds * Domain Path: /languages * Update URI: false */ @@ -20,6 +20,16 @@ include dirname( __FILE__ ) . '/vendor/autoload.php'; +if ( ! defined( 'TRIBE_MP_PATH' ) ) { + define( 'TRIBE_MP_PATH', trailingslashit( plugin_dir_path( __FILE__ ) ) ); +} +if ( ! defined( 'TRIBE_MP_URL' ) ) { + define( 'TRIBE_MP_URL', plugin_dir_url( __FILE__ ) ); +} +if ( ! defined( 'TRIBE_MP_VERSION' ) ) { + define( 'TRIBE_MP_VERSION', Core::VERSION ); +} + register_activation_hook( __FILE__, [ Core::class, 'activate' ] ); register_deactivation_hook( __FILE__, [ Core::class, 'deactivate' ] ); From eb39aec543e6b70b28c036fb5eca0b13179f804a Mon Sep 17 00:00:00 2001 From: Mykhailo Los Date: Wed, 4 Mar 2026 18:58:13 +0200 Subject: [PATCH 15/22] Force wistia autplay on click and change tempalte creation --- assets/js/embed.js | 63 ++++++++++++++++++++++++++++++++++++---- src/Providers/Wistia.php | 2 +- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/assets/js/embed.js b/assets/js/embed.js index 8b7d0ae..9c2e732 100644 --- a/assets/js/embed.js +++ b/assets/js/embed.js @@ -37,6 +37,28 @@ function createCaptionEl( caption ) { return captionEl; } +/** + * Configure and trigger play for (Aurora web component). + * Uses the element's autoplay attribute and api-ready event; window._wq does not apply. + * See https://docs.wistia.com/docs/player-attributes-and-properties#autoplay + * and https://docs.wistia.com/docs/player-events + * + * @param {Element} playerEl The DOM element (in the document). + */ +function playWistiaPlayer( playerEl ) { + if ( ! playerEl || playerEl.tagName !== 'WISTIA-PLAYER' ) return; + playerEl.setAttribute( 'autoplay', '' ); + playerEl.addEventListener( + 'api-ready', + () => { + if ( typeof playerEl.play === 'function' ) { + playerEl.play(); + } + }, + { once: true } + ); +} + /** * Setup event handlers * @@ -53,6 +75,10 @@ function setupEventHandlers( embed, template ) { if ( clickEls.length === 0 ) return; + const embedContent = template.content.children[ 0 ]; + const isWistia = + embedContent && embedContent.querySelector( 'wistia-player' ); + // loop through each click event - play button and thumbnail. clickEls.forEach( ( clickEl ) => { // when the element is clicked. @@ -63,6 +89,14 @@ function setupEventHandlers( embed, template ) { // grab just the first child of the template - this is the figure block element which wraps the iframe. const content = contentOuter.children[ 0 ]; + // Wistia: set autoplay on in the clone so it plays once injected (web component uses attributes, not _wq). + if ( isWistia ) { + const playerInClone = content.querySelector( 'wistia-player' ); + if ( playerInClone ) { + playerInClone.setAttribute( 'autoplay', '' ); + } + } + // add the iframe embed content before the embed wrapper. embed.before( content ); @@ -71,6 +105,14 @@ function setupEventHandlers( embed, template ) { // remove the template item which holds the iframe. template.remove(); + + // Wistia: ensure play when API is ready (handles async script load / custom element upgrade). + if ( isWistia ) { + const playerEl = content.querySelector( 'wistia-player' ); + if ( playerEl ) { + playWistiaPlayer( playerEl ); + } + } } ); } ); } @@ -81,15 +123,24 @@ function setupEventHandlers( embed, template ) { function updateEmbeds() { embedBlocks.forEach( ( embed ) => { // get the associated template element which holds the embed code. - // it is the next element after the wrapper. + // it is the next element after the wrapper (only used for providers that use template, e.g. YouTube and Wistia). const template = embed.nextElementSibling; + if ( ! template || template.tagName !== 'TEMPLATE' ) { + return; + } - const iframe = template.content.children[ 0 ].querySelector( 'iframe' ); - setIframeAttributes( iframe ); + const embedContent = template.content.children[ 0 ]; + if ( ! embedContent ) { + return; + } + + const iframe = embedContent.querySelector( 'iframe' ); + if ( iframe ) { + setIframeAttributes( iframe ); + } - // get the first child of the figure and add after tumbnail if present - const caption = - template.content.children[ 0 ].querySelector( 'figcaption' ); + // get the first child of the figure and add after thumbnail if present + const caption = embedContent.querySelector( 'figcaption' ); if ( caption ) { const captionEl = createCaptionEl( caption ); diff --git a/src/Providers/Wistia.php b/src/Providers/Wistia.php index 4bc83bd..3238f0d 100644 --- a/src/Providers/Wistia.php +++ b/src/Providers/Wistia.php @@ -102,7 +102,7 @@ public function get_thumbnail_data(): array { } public function uses_inline_embed(): bool { - return true; + return false; } protected function set_video_id(): string { From 0ad3f57e54c641396268ed176140eda47f657f05 Mon Sep 17 00:00:00 2001 From: Mykhailo Los Date: Tue, 10 Mar 2026 16:28:39 +0200 Subject: [PATCH 16/22] Bump php version --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 79528a0..183f290 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: uses: shivammathur/setup-php@2.22.0 with: tools: composer, wp - php-version: 8.0 + php-version: 8.3 - name: Checkout Repo uses: actions/checkout@v3 From 63299fff16b2c7c18684790e903c8bdedffc9287 Mon Sep 17 00:00:00 2001 From: Mykhailo Los Date: Tue, 10 Mar 2026 16:30:03 +0200 Subject: [PATCH 17/22] Bump php version --- .lando.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.lando.yml b/.lando.yml index 4aac18a..6334ebf 100644 --- a/.lando.yml +++ b/.lando.yml @@ -9,7 +9,7 @@ excludes: - .github config: - php: '8.0' + php: '8.3' via: nginx database: mysql webroot: dev/public From 634688f9cd1a3d02afd3d14db1bf6010c758f76e Mon Sep 17 00:00:00 2001 From: Mykhailo Los Date: Tue, 10 Mar 2026 16:31:55 +0200 Subject: [PATCH 18/22] Allow manual run for action --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 183f290..5dd47c2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,6 +1,7 @@ name: 'Release' on: + workflow_dispatch: release: types: [published] From 1c60776c80d1244ecebcdbe07deffa5420ed6671 Mon Sep 17 00:00:00 2001 From: Mykhailo Los Date: Tue, 10 Mar 2026 16:41:47 +0200 Subject: [PATCH 19/22] Fix version mismatch --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5dd47c2..77798af 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,7 +32,7 @@ jobs: composer install --no-dev composer dump-autoload - name: NPM Setup - uses: actions/setup-node@v3 + uses: actions/setup-node@v5 with: node-version-file: "${{ env.BUILD_FOLDER }}/.nvmrc" cache: 'npm' @@ -48,7 +48,7 @@ jobs: - name: Configure WP-CLI dist-archive-command run: | cd ${{ env.BUILD_FOLDER }} - wp package install wp-cli/dist-archive-command + wp package install wp-cli/dist-archive-command:^3.1 - name: Build Plugin Zip run: | wp dist-archive ${{ env.BUILD_FOLDER }} --plugin-dirname=${{ env.PLUGIN_SLUG }} From 5d868f59fd2e87f57c1845bacbfd497cc7ba931e Mon Sep 17 00:00:00 2001 From: Mykhailo Los Date: Wed, 8 Apr 2026 19:46:00 +0300 Subject: [PATCH 20/22] Address notes from review --- readme.md | 28 ++++------------------------ src/Core.php | 2 +- src/Providers/Dailymotion.php | 2 +- src/Providers/Vimeo.php | 2 +- src/Providers/Wistia.php | 8 ++------ src/Providers/YouTube.php | 2 +- tribe-embed.php | 2 +- 7 files changed, 11 insertions(+), 35 deletions(-) diff --git a/readme.md b/readme.md index 7ec6a55..f111b0e 100644 --- a/readme.md +++ b/readme.md @@ -44,26 +44,6 @@ These are the **public** extension points intended for themes/plugins to customi ### Filters -#### `tribe-embeds_video_provider` -Choose/override the Provider instance for a given embed URL. - -- **Signature:** `apply_filters( 'tribe-embeds_video_provider', $provider, $video_url_data, $block )` -- **Args:** - - `$provider` — default or previously resolved provider instance (or `null`) - - `$video_url_data` — result of `parse_url()` for the video URL - - `$block` — full Gutenberg block array (name, attributes, innerBlocks, etc.) -- **Return:** A `Provider` instance or `null` to skip. -- **Example:** -```php -add_filter( 'tribe-embeds_video_provider', function ( $provider, $video_url_data, $block ) { - // Force our custom provider for a specific host or path - if ( isset( $video_url_data['host'] ) && $video_url_data['host'] === 'videos.example.com' ) { - return new \Tribe\Tribe_Embed\Providers\Example_Provider( $video_url_data ); - } - return $provider; -}, 10, 3 ); -``` - #### `tribe-embeds_allowed_provider_hosts` Expand or restrict the whitelist of hostnames that can be handled by built-in or custom providers. @@ -145,11 +125,11 @@ Allow external override of provider class list - **Args:** - `$provider_classes` — list of existing providers classes -#### `tribe-embed__video_thumbnail_url` +#### `tribe_embed__video_thumbnail_url` -Allows to adjust image data for each provider. Use slug instead of `` e.g `tribe-embed_wistia_video_thumbnail_url` +Allows adjusting image data for each provider. Use slug instead of `` e.g `tribe_embed_wistia_video_thumbnail_url` -- **Signature:** `apply_filters( 'tribe-embed_wistia_video_thumbnail_url', $image_data, $video_id )` +- **Signature:** `apply_filters( 'tribe_embed_wistia_video_thumbnail_url', $image_data, $video_id )` - **Args:** - `$image_data` — Thumbnail image data - `$video_id` — current video id @@ -157,7 +137,7 @@ Allows to adjust image data for each provider. Use slug instead of `get_video_id() ); + return apply_filters( 'tribe_embed_dailymotion_video_thumbnail_url', $image_data, $this->get_video_id() ); } protected function set_video_id(): string { diff --git a/src/Providers/Vimeo.php b/src/Providers/Vimeo.php index 0e59ac1..af503a2 100644 --- a/src/Providers/Vimeo.php +++ b/src/Providers/Vimeo.php @@ -58,7 +58,7 @@ public function get_thumbnail_data(): array { } // return the url. - return apply_filters( 'tribe-embed_vimeo_video_thumbnail_url', $image_data, $this->get_video_id() ); + return apply_filters( 'tribe_embed_vimeo_video_thumbnail_url', $image_data, $this->get_video_id() ); } protected function get_video_pictures(): array { diff --git a/src/Providers/Wistia.php b/src/Providers/Wistia.php index 3238f0d..dc59c4e 100644 --- a/src/Providers/Wistia.php +++ b/src/Providers/Wistia.php @@ -70,6 +70,7 @@ public function get_thumbnail_data(): array { ], $image_url ); break; case 'thumbnail_320_url': + default: $image_url = add_query_arg( [ 'image_crop_resized' => '320x260', ], $image_url ); @@ -92,13 +93,8 @@ public function get_thumbnail_data(): array { set_transient( 'tribe-embed_' . $this->get_video_id(), $image_data, DAY_IN_SECONDS ); } - // Prevent edge case when `$image_data` may have boolean value - if ( ! is_array( $image_data ) || empty( $image_data ) ) { - $image_data = []; - } - // return the url. - return apply_filters( 'tribe-embed_wistia_video_thumbnail_url', $image_data, $this->get_video_id() ); + return apply_filters( 'tribe_embed_wistia_video_thumbnail_url', $image_data, $this->get_video_id() ); } public function uses_inline_embed(): bool { diff --git a/src/Providers/YouTube.php b/src/Providers/YouTube.php index f2ea9f3..8b7ab28 100644 --- a/src/Providers/YouTube.php +++ b/src/Providers/YouTube.php @@ -63,7 +63,7 @@ public function get_thumbnail_data(): array { } // return the thumbnail urls. - return apply_filters( 'tribe-embed_youtube_video_thumbnail_data', $image_data, $this->get_video_id() ); + return apply_filters( 'tribe_embed_youtube_video_thumbnail_data', $image_data, $this->get_video_id() ); } protected function set_video_id(): string { diff --git a/tribe-embed.php b/tribe-embed.php index dbc0e6c..18abb84 100644 --- a/tribe-embed.php +++ b/tribe-embed.php @@ -4,7 +4,7 @@ * Plugin Name: Tribe Embed * Plugin URI: https://github.com/moderntribe/tribe-embed * Description: A Tribe Embed Plugin. - * Version: 1.1.1 + * Version: 2.0.0 * Requires at least: 6.3 * Requires PHP: 8.0 * Author: Modern Tribe From 7ac3cbd5d9675136e1da7e61174a9a1e111fe625 Mon Sep 17 00:00:00 2001 From: Mykhailo Los Date: Wed, 8 Apr 2026 19:48:40 +0300 Subject: [PATCH 21/22] Address notes from review --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index f111b0e..3d4bd4e 100644 --- a/readme.md +++ b/readme.md @@ -114,7 +114,7 @@ Get image sizes for a provider - **Signature:** `apply_filters( 'tribe_embeds_image_sizes', $by_provider, $provider_class );` - **Args:** - - `$by_provider` — list of image sizes `tribe_embeds_image_sizes_` + - `$by_provider` — list of image sizes `tribe_embeds_image_sizes` - `$provider_class` — current provider class #### `tribe_embeds_provider_classes` From 59a0a358632490c482b2356b9a393654b1514c99 Mon Sep 17 00:00:00 2001 From: Mykhailo Los Date: Tue, 21 Apr 2026 13:22:50 +0300 Subject: [PATCH 22/22] Change settings page name --- src/Admin/Settings_Page.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Admin/Settings_Page.php b/src/Admin/Settings_Page.php index 8c4a870..6038359 100644 --- a/src/Admin/Settings_Page.php +++ b/src/Admin/Settings_Page.php @@ -48,7 +48,7 @@ public function register_settings(): void { public function render_page(): void { ?>
-

+