From 5a943323161c163ca6d57868feac4afd828e2f1f Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Mon, 22 Jul 2024 16:46:40 -0400 Subject: [PATCH 01/12] WIP: issue-7 From 1a29121884fe116a00171d6650ebd0e10c19c2aa Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Mon, 22 Jul 2024 18:08:57 -0400 Subject: [PATCH 02/12] WIP build JW Player 7 adapter --- README.md | 95 +++-------------------- src/adapters/class-jw-player-7-for-wp.php | 27 +++++++ src/autoload.php | 34 ++++++++ src/class-sync-manager.php | 90 +++++++++++++++++++++ src/class-wp-video-sync.php | 15 ---- src/interfaces/adapter.php | 24 ++++++ wp-video-sync.php | 2 +- 7 files changed, 185 insertions(+), 102 deletions(-) create mode 100644 src/adapters/class-jw-player-7-for-wp.php create mode 100644 src/autoload.php create mode 100644 src/class-sync-manager.php delete mode 100644 src/class-wp-video-sync.php create mode 100644 src/interfaces/adapter.php diff --git a/README.md b/README.md index d2a7bbc..4014e19 100644 --- a/README.md +++ b/README.md @@ -6,22 +6,21 @@ Tags: alleyinteractive, wp-video-sync Stable tag: 0.0.0 -Requires at least: 5.9 +Requires at least: 6.6 -Tested up to: 6.1 +Tested up to: 6.6 -Requires PHP: 8.1 +Requires PHP: 8.2 License: GPL v2 or later -[![Coding Standards](https://github.com/alleyinteractive/wp-video-sync/actions/workflows/coding-standards.yml/badge.svg)](https://github.com/alleyinteractive/wp-video-sync/actions/workflows/coding-standards.yml) -[![Testing Suite](https://github.com/alleyinteractive/wp-video-sync/actions/workflows/unit-test.yml/badge.svg)](https://github.com/alleyinteractive/wp-video-sync/actions/workflows/unit-test.yml) +[![Testing Suite](https://github.com/alleyinteractive/wp-video-sync/actions/workflows/all-pr-tests.yml/badge.svg)](https://github.com/alleyinteractive/wp-video-sync/actions/workflows/all-pr-tests.yml) Sync videos from a hosting provider to WordPress. ## Installation -You can install the package via composer: +You can install the package via Composer: ```bash composer require alleyinteractive/wp-video-sync @@ -32,90 +31,14 @@ composer require alleyinteractive/wp-video-sync Activate the plugin in WordPress and use it like so: ```php -$plugin = Alley\WP\WP_Video_Sync\WP_Video_Sync\WP_Video_Sync(); -$plugin->perform_magic(); +$video_sync = new Alley\WP\WP_Video_Sync\Sync_Manager()->with_jw_player_7_for_wp(); ``` - -## Testing -Run `npm run test` to run Jest tests against JavaScript files. Run -`npm run test:watch` to keep the test runner open and watching for changes. - -Run `npm run lint` to run ESLint against all JavaScript files. Linting will also -happen when running development or production builds. - -Run `composer test` to run tests against PHPUnit and the PHP code in the plugin. - -### The `entries` directory and entry points - -All directories created in the `entries` directory can serve as entry points and will be compiled with [@wordpress/scripts](https://github.com/WordPress/gutenberg/blob/trunk/packages/scripts/README.md#scripts) into the `build` directory with an accompanied `index.asset.php` asset map. - -#### Scaffolding an entry point - -To generate a new entry point, run the following command: - -```sh -npm run create-entry -``` - -To generate a new slotfill, run the following command: - -```sh -npm run create-slotfill -``` - -The command will prompt the user through several options for creating an entry or slotfill. The entries are scaffolded with the `@alleyinteractive/create-entry` script. Run the help command to see all the options: - -```sh -npx @alleyinteractive/create-entry --help -``` -[Visit the package README](https://www.npmjs.com/package/@alleyinteractive/create-entry) for more information. - -#### Enqueuing Entry Points - -You can also include an `index.php` file in the entry point directory for enqueueing or registering a script. This file will then be moved to the build directory and will be auto-loaded with the `load_scripts()` function in the `functions.php` file. Alternatively, if a script is to be enqueued elsewhere there are helper functions in the `src/assets.php` file for getting the assets. - -### Scaffold a dynamic block with `create-block` - -Use the `create-block` command to create custom blocks with [@alleyinteractive/create-block](https://github.com/alleyinteractive/alley-scripts/tree/main/packages/create-block) script and follow the prompts to generate all the block assets in the `blocks/` directory. -Block registration, script creation, etc will be scaffolded from the `create-block` script. Run `npm run build` to compile and build the custom block. Blocks are enqueued using the `load_scripts()` function in `src/assets.php`. - -### Updating WP Dependencies - -Update the [WordPress dependency packages](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/#packages-update) used in the project to their latest version. - -To update `@wordpress` dependencies to their latest version use the packages-update command: - -```sh -npx wp-scripts packages-update -``` - -This script provides the following custom options: - -- `--dist-tag` – allows specifying a custom dist-tag when updating npm packages. Defaults to `latest`. This is especially useful when using [`@wordpress/dependency-extraction-webpack-plugin`](https://www.npmjs.com/package/@wordpress/dependency-extraction-webpack-plugin). It lets installing the npm dependencies at versions used by the given WordPress major version for local testing, etc. Example: - -```sh -npx wp-scripts packages-update --dist-tag=wp-WPVERSION` -``` - -Where `WPVERSION` is the version of WordPress you are targeting. The version -must include both the major and minor version (e.g., `6.1`). For example: - -```sh -npx wp-scripts packages-update --dist-tag=wp-6.1` -``` +As of now, the plugin only supports JW Player 7 for WordPress (both the free and premium versions). Other adapters may be added in the future. ## Releasing the Plugin -The plugin uses a [built release workflow](./.github/workflows/built-release.yml) -to compile and tag releases. Whenever a new version is detected in the root -`composer.json` file or in the plugin's headers, the workflow will automatically -build the plugin and tag it with a new version. The built tag will contain all -the required front-end assets the plugin may require. This works well for -publishing to WordPress.org or for submodule-ing. - -When you are ready to release a new version of the plugin, you can run -`npm run release` to start the process of setting up a new release. +New versions of this plugin will be created as releases in GitHub once ready. ## Changelog @@ -132,4 +55,4 @@ with us](https://alley.co/careers/). ## License -The GNU General Public License (GPL) license. Please see [License File](LICENSE) for more information. \ No newline at end of file +The GNU General Public License (GPL) license. Please see [License File](LICENSE) for more information. diff --git a/src/adapters/class-jw-player-7-for-wp.php b/src/adapters/class-jw-player-7-for-wp.php new file mode 100644 index 0000000..5706205 --- /dev/null +++ b/src/adapters/class-jw-player-7-for-wp.php @@ -0,0 +1,27 @@ +frequency = $frequency; + return $this; + } + + /** + * Loads the JW Player 7 for WP adapter. + * + * @return $this For chaining configuration. + */ + public function with_jw_player_7_for_wp(): self { + $this->adapter = new Adapters\JW_Player_7_For_WP(); + return $this; + } + + /** + * Schedules the sync cron job if it's not already scheduled. + */ + public function maybe_schedule_sync(): void { + if ( ! wp_next_scheduled( self::CRON_HOOK ) ) { + wp_schedule_event( time(), $this->frequency, self::CRON_HOOK ); + } + } + + /** + * Syncs videos from the provider. + */ + public function sync_videos(): void { + $last_sync = get_option( self::LAST_SYNC_OPTION, DateTime::createFromFormat( DATE_W3C, '1970-01-01T00:00:00Z' ) ); + $videos = $this->adapter->get_videos( $last_sync ); + foreach ( $videos as $video ) { + // Do something with the video. + } + } +} diff --git a/src/class-wp-video-sync.php b/src/class-wp-video-sync.php deleted file mode 100644 index 8d3dd64..0000000 --- a/src/class-wp-video-sync.php +++ /dev/null @@ -1,15 +0,0 @@ - Date: Mon, 22 Jul 2024 22:11:41 -0400 Subject: [PATCH 03/12] Setup initial unit test --- src/adapters/class-jw-player-7-for-wp.php | 6 +- src/autoload.php | 2 +- src/class-sync-manager.php | 77 ++++++++++++------- src/interfaces/adapter.php | 6 +- ...eatureTest.php => JWPlayerAdapterTest.php} | 8 +- tests/Unit/ExampleUnitTest.php | 24 ------ tests/Unit/SyncManagerTest.php | 28 +++++++ tests/bootstrap.php | 2 + 8 files changed, 90 insertions(+), 63 deletions(-) rename tests/Feature/{ExampleFeatureTest.php => JWPlayerAdapterTest.php} (66%) delete mode 100644 tests/Unit/ExampleUnitTest.php create mode 100644 tests/Unit/SyncManagerTest.php diff --git a/src/adapters/class-jw-player-7-for-wp.php b/src/adapters/class-jw-player-7-for-wp.php index 5706205..044a6e4 100644 --- a/src/adapters/class-jw-player-7-for-wp.php +++ b/src/adapters/class-jw-player-7-for-wp.php @@ -8,7 +8,7 @@ namespace Alley\WP\WP_Video_Sync\Adapters; use Alley\WP\WP_Video_Sync\Interfaces\Adapter; -use DateTime; +use DateTimeImmutable; /** * JW Player 7 for WP Adapter. Supports both the free and premium versions of the plugin. @@ -17,11 +17,11 @@ class JW_Player_7_For_WP implements Adapter { /** * Fetches videos from JW Player that were modified after the provided DateTime. * - * @param DateTime $updated_after Return videos modified after this date. + * @param DateTimeImmutable $updated_after Return videos modified after this date. * * @return array An array of video data. */ - public function get_videos( DateTime $updated_after ): array { + public function get_videos( DateTimeImmutable $updated_after ): array { return []; } } diff --git a/src/autoload.php b/src/autoload.php index a6c6d30..7516215 100644 --- a/src/autoload.php +++ b/src/autoload.php @@ -12,7 +12,7 @@ * * @param string $class Class name. */ -function autoload( string $class ) { +function autoload( string $class ): void { // Only autoload classes for this namespace. $class = ltrim( $class, '\\' ); if ( strpos( $class, __NAMESPACE__ . '\\' ) !== 0 ) { diff --git a/src/class-sync-manager.php b/src/class-sync-manager.php index 8fe40a8..f848b72 100644 --- a/src/class-sync-manager.php +++ b/src/class-sync-manager.php @@ -8,7 +8,8 @@ namespace Alley\WP\WP_Video_Sync; use Alley\WP\WP_Video_Sync\Interfaces\Adapter; -use DateTime; +use DateTimeImmutable; +use Error; /** * Sync manager. Manages the synchronization of videos from a provider. @@ -39,33 +40,17 @@ class Sync_Manager { public string $frequency = 'hourly'; /** - * Constructor. Sets up actions and filters. - */ - public function __construct() { - add_action( 'admin_init', [ $this, 'maybe_schedule_sync' ] ); - add_action( self::CRON_HOOK, [ $this, 'sync_videos' ] ); - } - - /** - * Allows the sync frequency to be set. Can be any valid value for wp_schedule_event(). + * Factory method that creates a new instance of this class, hooks it, and returns it. * - * @param string $frequency The frequency with which to sync videos. - * - * @return $this For chaining configuration. + * @return self */ - public function with_frequency( string $frequency ): self { - $this->frequency = $frequency; - return $this; - } + public static function init(): self { + $instance = new self(); - /** - * Loads the JW Player 7 for WP adapter. - * - * @return $this For chaining configuration. - */ - public function with_jw_player_7_for_wp(): self { - $this->adapter = new Adapters\JW_Player_7_For_WP(); - return $this; + add_action( 'admin_init', [ $instance, 'maybe_schedule_sync' ] ); + add_action( self::CRON_HOOK, [ $instance, 'sync_videos' ] ); + + return $instance; } /** @@ -79,12 +64,50 @@ public function maybe_schedule_sync(): void { /** * Syncs videos from the provider. + * + * @throws Error If unable to parse the last sync as a DateTimeImmutable object. */ public function sync_videos(): void { - $last_sync = get_option( self::LAST_SYNC_OPTION, DateTime::createFromFormat( DATE_W3C, '1970-01-01T00:00:00Z' ) ); - $videos = $this->adapter->get_videos( $last_sync ); + // Try to get the option value so we can parse it as a date. If it doesn't exist, default to the Unix epoch. + $option_value = get_option( self::LAST_SYNC_OPTION, '1970-01-01T00:00:00Z' ); + if ( ! is_string( $option_value ) ) { + throw new Error( esc_html__( 'The value saved to the options table for the last sync time is not a string.', 'wp-video-sync' ) ); + } + + // Try to parse the last sync time into a DateTimeImmutable object and fail if we can't. + $last_sync = DateTimeImmutable::createFromFormat( DATE_W3C, $option_value ); + if ( ! $last_sync instanceof DateTimeImmutable ) { + throw new Error( esc_html__( 'The last sync time could not be parsed into a DateTimeImmutable object.', 'wp-video-sync' ) ); + } + + // Get a batch of videos and loop over them and process each. + $videos = $this->adapter->get_videos( $last_sync ); foreach ( $videos as $video ) { // Do something with the video. } } + + /** + * Allows the adapter to be set. Can be used with an adapter that ships with this plugin or any custom adapter that implements the Adapter interface. + * + * @param Adapter $adapter The adapter to load. + * + * @return $this For chaining configuration. + */ + public function with_adapter( Adapter $adapter ): self { + $this->adapter = $adapter; + return $this; + } + + /** + * Allows the sync frequency to be set. Can be any valid value for wp_schedule_event(). + * + * @param string $frequency The frequency with which to sync videos. + * + * @return $this For chaining configuration. + */ + public function with_frequency( string $frequency ): self { + $this->frequency = $frequency; + return $this; + } } diff --git a/src/interfaces/adapter.php b/src/interfaces/adapter.php index b8697ac..8443777 100644 --- a/src/interfaces/adapter.php +++ b/src/interfaces/adapter.php @@ -7,7 +7,7 @@ namespace Alley\WP\WP_Video_Sync\Interfaces; -use DateTime; +use DateTimeImmutable; /** * Defines an interface that all adapters must implement. @@ -16,9 +16,9 @@ interface Adapter { /** * Fetches videos from the provider that were modified after the provided DateTime. * - * @param DateTime $updated_after Return videos modified after this date. + * @param DateTimeImmutable $updated_after Return videos modified after this date. * * @return array An array of video data. Specific shape will be determined by the adapter. */ - public function get_videos( DateTime $updated_after ): array; + public function get_videos( DateTimeImmutable $updated_after ): array; } diff --git a/tests/Feature/ExampleFeatureTest.php b/tests/Feature/JWPlayerAdapterTest.php similarity index 66% rename from tests/Feature/ExampleFeatureTest.php rename to tests/Feature/JWPlayerAdapterTest.php index 550f1f8..68f7730 100644 --- a/tests/Feature/ExampleFeatureTest.php +++ b/tests/Feature/JWPlayerAdapterTest.php @@ -1,6 +1,6 @@ assertTrue( true ); - } -} diff --git a/tests/Unit/SyncManagerTest.php b/tests/Unit/SyncManagerTest.php new file mode 100644 index 0000000..23250bc --- /dev/null +++ b/tests/Unit/SyncManagerTest.php @@ -0,0 +1,28 @@ +with_adapter( new JW_Player_7_For_WP() ) + ->with_frequency( 'daily' ); + $this->assertInstanceOf( JW_Player_7_For_WP::class, $sync_manager->adapter ); + $this->assertEquals( 'daily', $sync_manager->frequency ); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 9fc102a..0047d04 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -7,6 +7,8 @@ * @package wp-video-sync */ +require_once __DIR__ . '/../vendor/autoload.php'; + /** * Visit {@see https://mantle.alley.com/testing/test-framework.html} to learn more. */ From fe629f7c1d765ab54205026c74b725df303b3a26 Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Tue, 23 Jul 2024 00:08:51 -0400 Subject: [PATCH 04/12] Implement support for JW Player 7 for WP Premium --- src/adapters/class-jw-player-7-for-wp.php | 17 ++++++- src/class-sync-manager.php | 38 ++++++++++++++-- src/interfaces/adapter.php | 3 +- tests/Feature/JWPlayerAdapterTest.php | 53 ++++++++++++++++++++-- tests/Fixtures/jw-player-api-v2-media.json | 36 +++++++++++++++ tests/Mocks/JWPPP_Dashboard_API.php | 25 ++++++++++ tests/bootstrap.php | 6 +-- 7 files changed, 164 insertions(+), 14 deletions(-) create mode 100644 tests/Fixtures/jw-player-api-v2-media.json create mode 100644 tests/Mocks/JWPPP_Dashboard_API.php diff --git a/src/adapters/class-jw-player-7-for-wp.php b/src/adapters/class-jw-player-7-for-wp.php index 044a6e4..1caf43e 100644 --- a/src/adapters/class-jw-player-7-for-wp.php +++ b/src/adapters/class-jw-player-7-for-wp.php @@ -9,6 +9,7 @@ use Alley\WP\WP_Video_Sync\Interfaces\Adapter; use DateTimeImmutable; +use stdClass; /** * JW Player 7 for WP Adapter. Supports both the free and premium versions of the plugin. @@ -19,9 +20,23 @@ class JW_Player_7_For_WP implements Adapter { * * @param DateTimeImmutable $updated_after Return videos modified after this date. * - * @return array An array of video data. + * @return stdClass[] An array of video data. */ public function get_videos( DateTimeImmutable $updated_after ): array { + // Check if the JW Player 7 for WP Premium plugin is active. + if ( class_exists( 'JWPPP_Dashboard_Api' ) ) { + $api = new \JWPPP_Dashboard_Api(); + $result = $api->call( + sprintf( + 'media/?q=last_modified:[%s TO *]&page=1&page_length=100&sort=last_modified:asc', + $updated_after->format( 'Y-m-d' ) + ) + ); + return $result->media ?? []; + } + + // TODO: Check if the free version of the plugin is active. + return []; } } diff --git a/src/class-sync-manager.php b/src/class-sync-manager.php index f848b72..9b1a3a3 100644 --- a/src/class-sync-manager.php +++ b/src/class-sync-manager.php @@ -32,6 +32,13 @@ class Sync_Manager { */ public Adapter $adapter; + /** + * A callback to run for each result when the sync runs. + * + * @var callable + */ + public $callback; + /** * The frequency with which to sync videos. Can be any valid value for wp_schedule_event(). * @@ -68,22 +75,35 @@ public function maybe_schedule_sync(): void { * @throws Error If unable to parse the last sync as a DateTimeImmutable object. */ public function sync_videos(): void { + // If there isn't a valid callback, bail. + if ( ! is_callable( $this->callback ) ) { + throw new Error( esc_html__( 'WP Video Sync: Unable to execute provided callback.', 'wp-video-sync' ) ); + } + // Try to get the option value so we can parse it as a date. If it doesn't exist, default to the Unix epoch. $option_value = get_option( self::LAST_SYNC_OPTION, '1970-01-01T00:00:00Z' ); if ( ! is_string( $option_value ) ) { - throw new Error( esc_html__( 'The value saved to the options table for the last sync time is not a string.', 'wp-video-sync' ) ); + throw new Error( esc_html__( 'WP Video Sync: The value saved to the options table for the last sync time is not a string.', 'wp-video-sync' ) ); } // Try to parse the last sync time into a DateTimeImmutable object and fail if we can't. $last_sync = DateTimeImmutable::createFromFormat( DATE_W3C, $option_value ); if ( ! $last_sync instanceof DateTimeImmutable ) { - throw new Error( esc_html__( 'The last sync time could not be parsed into a DateTimeImmutable object.', 'wp-video-sync' ) ); + throw new Error( esc_html__( 'WP Video Sync: The last sync time could not be parsed into a DateTimeImmutable object.', 'wp-video-sync' ) ); } // Get a batch of videos and loop over them and process each. $videos = $this->adapter->get_videos( $last_sync ); foreach ( $videos as $video ) { - // Do something with the video. + call_user_func_array( $this->callback, [ $video ] ); + } + + // Try to update the last sync time to the last modified time of the last video that was processed. + if ( isset( $video->last_modified ) && is_string( $video->last_modified ) ) { + $check = DateTimeImmutable::createFromFormat( DATE_W3C, $video->last_modified ); + if ( $check instanceof DateTimeImmutable ) { + update_option( self::LAST_SYNC_OPTION, $video->last_modified ); + } } } @@ -99,6 +119,18 @@ public function with_adapter( Adapter $adapter ): self { return $this; } + /** + * Allows a callback to be set that will be called when the sync runs. + * + * @param callable $callback The callback to run when the sync runs. + * + * @return $this For chaining configuration. + */ + public function with_callback( callable $callback ): self { + $this->callback = $callback; + return $this; + } + /** * Allows the sync frequency to be set. Can be any valid value for wp_schedule_event(). * diff --git a/src/interfaces/adapter.php b/src/interfaces/adapter.php index 8443777..cef37ec 100644 --- a/src/interfaces/adapter.php +++ b/src/interfaces/adapter.php @@ -8,6 +8,7 @@ namespace Alley\WP\WP_Video_Sync\Interfaces; use DateTimeImmutable; +use stdClass; /** * Defines an interface that all adapters must implement. @@ -18,7 +19,7 @@ interface Adapter { * * @param DateTimeImmutable $updated_after Return videos modified after this date. * - * @return array An array of video data. Specific shape will be determined by the adapter. + * @return stdClass[] An array of video data. Specific shape will be determined by the adapter. */ public function get_videos( DateTimeImmutable $updated_after ): array; } diff --git a/tests/Feature/JWPlayerAdapterTest.php b/tests/Feature/JWPlayerAdapterTest.php index 68f7730..ab87d39 100644 --- a/tests/Feature/JWPlayerAdapterTest.php +++ b/tests/Feature/JWPlayerAdapterTest.php @@ -7,17 +7,62 @@ namespace Alley\WP\WP_Video_Sync\Tests\Feature; +use Alley\WP\WP_Video_Sync\Adapters\JW_Player_7_For_WP; +use Alley\WP\WP_Video_Sync\Sync_Manager; use Alley\WP\WP_Video_Sync\Tests\TestCase; +use DateTimeImmutable; +use WP_Query; /** * A test suite for the JW Player adapter. */ class JWPlayerAdapterTest extends TestCase { /** - * An example test for the example feature. In practice, this should be updated to test an aspect of the feature. + * Tests the behavior of the adapter with the JW Player 7 for WP Premium plugin. */ - public function test_example() { - $this->assertTrue( true ); - $this->assertNotEmpty( home_url() ); + public function test_jw_player_7_for_wp_premium() { + // Fake the class that's used to make the API call. + require_once __DIR__ . '/../Mocks/JWPPP_Dashboard_API.php'; + + // Fake the API response for the first page of data. + $this->fake_request( 'https://api.jwplayer.com/v2/sites/example-api-key/media/*' ) + ->with_response_code( 200 ) + ->with_body( file_get_contents( __DIR__ . '/../Fixtures/jw-player-api-v2-media.json' ) ); + + // Create an instance of the adapter. + $sync_manager = Sync_Manager::init() + ->with_adapter( new JW_Player_7_For_WP() ) + ->with_callback( fn ( $video ) => self::factory()->post->create( + [ + 'post_title' => $video->metadata->title, + 'post_date' => DateTimeImmutable::createFromFormat( DATE_W3C, $video->created )->format( 'Y-m-d H:i:s' ), + 'post_modified' => DateTimeImmutable::createFromFormat( DATE_W3C, $video->last_modified )->format( 'Y-m-d H:i:s' ), + 'meta_input' => [ + 'jwplayer_id' => $video->id, + ], + ] + ) ); + + // Run the sync. + $sync_manager->sync_videos(); + + // Confirm that the sync was successful. + $video_query = new WP_Query( + [ + 'name' => 'example-video', + 'post_status' => 'publish', + 'post_type' => 'post', + ] + ); + $this->assertEquals( 1, $video_query->post_count ); + $this->assertEquals( 'Example Video', $video_query->posts[0]->post_title ); + $this->assertEquals( '2024-01-01 12:00:00', $video_query->posts[0]->post_date ); + $this->assertEquals( '2024-01-01 12:00:00', $video_query->posts[0]->post_date_gmt ); + $this->assertEquals( '2024-01-01 13:00:00', $video_query->posts[0]->post_modified ); + $this->assertEquals( '2024-01-01 13:00:00', $video_query->posts[0]->post_modified_gmt ); + $this->assertEquals( 'ABCD1234', get_post_meta( $video_query->posts[0]->ID, 'jwplayer_id', true ) ); + + // Ensure that the sync time was updated. + $this->assertEquals( '2024-01-01T13:00:00+00:00', get_option( Sync_Manager::LAST_SYNC_OPTION ) ); } } diff --git a/tests/Fixtures/jw-player-api-v2-media.json b/tests/Fixtures/jw-player-api-v2-media.json new file mode 100644 index 0000000..c7971e9 --- /dev/null +++ b/tests/Fixtures/jw-player-api-v2-media.json @@ -0,0 +1,36 @@ +{ + "media": [ + { + "created": "2024-01-01T12:00:00+00:00", + "duration": 1234.56789, + "error_message": null, + "external_id": null, + "hosting_type": "hosted", + "id": "ABCD1234", + "last_modified": "2024-01-01T13:00:00+00:00", + "media_type": "video", + "metadata": { + "author": null, + "category": null, + "custom_params": {}, + "description": null, + "external_id": null, + "language": null, + "permalink": null, + "protection_rule_key": null, + "publish_end_date": null, + "publish_start_date": "2024-01-01T12:00:00+00:00", + "tags": [], + "title": "Example Video" + }, + "mime_type": null, + "relationships": {}, + "schema": null, + "source_url": null, + "status": "ready", + "trim_in_point": null, + "trim_out_point": null, + "type": "media" + } + ] +} diff --git a/tests/Mocks/JWPPP_Dashboard_API.php b/tests/Mocks/JWPPP_Dashboard_API.php new file mode 100644 index 0000000..ab13187 --- /dev/null +++ b/tests/Mocks/JWPPP_Dashboard_API.php @@ -0,0 +1,25 @@ +maybe_rsync_plugin() From 8243688cba098849be6576df0e5d411515e49829 Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Tue, 23 Jul 2024 00:14:09 -0400 Subject: [PATCH 05/12] Update comments to indicate support is for both free and premium --- src/adapters/class-jw-player-7-for-wp.php | 4 +--- tests/Feature/JWPlayerAdapterTest.php | 4 ++-- tests/Mocks/JWPPP_Dashboard_API.php | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/adapters/class-jw-player-7-for-wp.php b/src/adapters/class-jw-player-7-for-wp.php index 1caf43e..7efeb1f 100644 --- a/src/adapters/class-jw-player-7-for-wp.php +++ b/src/adapters/class-jw-player-7-for-wp.php @@ -23,7 +23,7 @@ class JW_Player_7_For_WP implements Adapter { * @return stdClass[] An array of video data. */ public function get_videos( DateTimeImmutable $updated_after ): array { - // Check if the JW Player 7 for WP Premium plugin is active. + // Check if the JW Player 7 for WP plugin is active (free or premium). if ( class_exists( 'JWPPP_Dashboard_Api' ) ) { $api = new \JWPPP_Dashboard_Api(); $result = $api->call( @@ -35,8 +35,6 @@ public function get_videos( DateTimeImmutable $updated_after ): array { return $result->media ?? []; } - // TODO: Check if the free version of the plugin is active. - return []; } } diff --git a/tests/Feature/JWPlayerAdapterTest.php b/tests/Feature/JWPlayerAdapterTest.php index ab87d39..bf0ea56 100644 --- a/tests/Feature/JWPlayerAdapterTest.php +++ b/tests/Feature/JWPlayerAdapterTest.php @@ -18,9 +18,9 @@ */ class JWPlayerAdapterTest extends TestCase { /** - * Tests the behavior of the adapter with the JW Player 7 for WP Premium plugin. + * Tests the behavior of the adapter with the JW Player 7 for WP plugin (free and premium). */ - public function test_jw_player_7_for_wp_premium() { + public function test_jw_player_7_for_wp() { // Fake the class that's used to make the API call. require_once __DIR__ . '/../Mocks/JWPPP_Dashboard_API.php'; diff --git a/tests/Mocks/JWPPP_Dashboard_API.php b/tests/Mocks/JWPPP_Dashboard_API.php index ab13187..a797a44 100644 --- a/tests/Mocks/JWPPP_Dashboard_API.php +++ b/tests/Mocks/JWPPP_Dashboard_API.php @@ -1,6 +1,6 @@ Date: Tue, 23 Jul 2024 00:40:00 -0400 Subject: [PATCH 06/12] Make the adapter responsible for keeping track of the last modified date --- README.md | 41 +++++++++++++++++++++-- src/adapters/class-jw-player-7-for-wp.php | 28 +++++++++++++++- src/class-sync-manager.php | 8 ++--- src/interfaces/adapter.php | 7 ++++ 4 files changed, 76 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4014e19..4bf76d4 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Contributors: alleyinteractive Tags: alleyinteractive, wp-video-sync -Stable tag: 0.0.0 +Stable tag: 0.1.0 Requires at least: 6.6 @@ -18,6 +18,10 @@ License: GPL v2 or later Sync videos from a hosting provider to WordPress. +Runs a scheduled task to sync videos from a supported video hosting provider to WordPress in batches based on the last modified date of the video. Implementers are responsible for installing and configuring a compatible plugin, choosing it as an adapter, and defining the callback that will be run for each video, which will be responsible for performing any post creations or updates in WordPress. + +This plugin is a great way to sync videos uploaded to a hosting provider (such as JW Player) to WordPress, such that the video itself remains on the hosting provider, but the video can be displayed in WordPress using a player block or shortcode, appears at its own unique URL, and can be included in search results. + ## Installation You can install the package via Composer: @@ -31,11 +35,44 @@ composer require alleyinteractive/wp-video-sync Activate the plugin in WordPress and use it like so: ```php -$video_sync = new Alley\WP\WP_Video_Sync\Sync_Manager()->with_jw_player_7_for_wp(); +use Alley\WP\WP_Video_Sync\Adapters\JW_Player_7_For_WP; +use Alley\WP\WP_Video_Sync\Sync_Manager; +use DateTimeImmutable; +use WP_Query; + +$sync_manager = Sync_Manager::init() + ->with_adapter( new JW_Player_7_For_WP() ) + ->with_frequency( 'hourly' ) + ->with_callback( + function ( $video ) { + $existing_video = new WP_Query( [ 'meta_key' => 'jwplayer_id', 'meta_value' => $video->id ] ); + $existing_id = $existing_video->posts[0]->ID ?? 0; + wp_insert_post( + [ + 'ID' => $existing_id, + 'post_title' => $video->metadata->title, + 'post_date' => DateTimeImmutable::createFromFormat( DATE_W3C, $video->created )->format( 'Y-m-d H:i:s' ), + 'post_modified' => DateTimeImmutable::createFromFormat( DATE_W3C, $video->last_modified )->format( 'Y-m-d H:i:s' ), + 'meta_input' => [ + 'jwplayer_id' => $video->id, + ], + ] + ); + } + ); ``` +This will configure the plugin to import a batch of 100 videos every hour from JW Player, sorted by least to most recently updated, starting with the date and time of the last video that was updated. If videos have already been imported (as identified by the postmeta value saved for the unique video ID) they will be updated rather than created. New videos will be created. The example code above uses the `post` post type for this purpose, but the code could easily be adapted to use a custom post type. Additionally, the post content could be set to include a Gutenberg block or a shortcode for a player. + +### Supported Adapters + As of now, the plugin only supports JW Player 7 for WordPress (both the free and premium versions). Other adapters may be added in the future. +#### JW Player 7 for WordPress + +- Requires the [JW Player 7 for WordPress](https://wordpress.org/plugins/jw-player-7-for-wp/) plugin to be installed, activated, and properly configured with access credentials. Also supports the premium version. +- The video object in the callback is a `stdClass` with the properties described in the `media` object under response code `200` in [the JW Player API documentation for the media list endpoint](https://docs.jwplayer.com/platform/reference/get_v2-sites-site-id-media). + ## Releasing the Plugin New versions of this plugin will be created as releases in GitHub once ready. diff --git a/src/adapters/class-jw-player-7-for-wp.php b/src/adapters/class-jw-player-7-for-wp.php index 7efeb1f..a3deea1 100644 --- a/src/adapters/class-jw-player-7-for-wp.php +++ b/src/adapters/class-jw-player-7-for-wp.php @@ -15,6 +15,22 @@ * JW Player 7 for WP Adapter. Supports both the free and premium versions of the plugin. */ class JW_Player_7_For_WP implements Adapter { + /** + * The date of the last modification to the last batch of videos. + * + * @var DateTimeImmutable + */ + private DateTimeImmutable $last_modified_date; + + /** + * Fetches the date of the last modification to the last batch of videos. + * + * @return ?DateTimeImmutable + */ + public function get_last_modified_date(): ?DateTimeImmutable { + return $this->last_modified_date; + } + /** * Fetches videos from JW Player that were modified after the provided DateTime. * @@ -32,7 +48,17 @@ public function get_videos( DateTimeImmutable $updated_after ): array { $updated_after->format( 'Y-m-d' ) ) ); - return $result->media ?? []; + $videos = $result->media ?? []; + + // Attempt to set the last modified date. + if ( isset( $videos[ count( $videos ) - 1 ]->last_modified ) ) { + $last_modified_date = DateTimeImmutable::createFromFormat( DATE_W3C, $videos[ count( $videos ) - 1 ]->last_modified ); + if ( $last_modified_date instanceof DateTimeImmutable ) { + $this->last_modified_date = $last_modified_date; + } + } + + return $videos; } return []; diff --git a/src/class-sync-manager.php b/src/class-sync-manager.php index 9b1a3a3..c5e5a72 100644 --- a/src/class-sync-manager.php +++ b/src/class-sync-manager.php @@ -99,11 +99,9 @@ public function sync_videos(): void { } // Try to update the last sync time to the last modified time of the last video that was processed. - if ( isset( $video->last_modified ) && is_string( $video->last_modified ) ) { - $check = DateTimeImmutable::createFromFormat( DATE_W3C, $video->last_modified ); - if ( $check instanceof DateTimeImmutable ) { - update_option( self::LAST_SYNC_OPTION, $video->last_modified ); - } + $next_last_modified = $this->adapter->get_last_modified_date(); + if ( $next_last_modified instanceof DateTimeImmutable ) { + update_option( self::LAST_SYNC_OPTION, $next_last_modified->format( DATE_W3C ) ); } } diff --git a/src/interfaces/adapter.php b/src/interfaces/adapter.php index cef37ec..edcecde 100644 --- a/src/interfaces/adapter.php +++ b/src/interfaces/adapter.php @@ -14,6 +14,13 @@ * Defines an interface that all adapters must implement. */ interface Adapter { + /** + * Fetches the date of the last modification to the last batch of videos. + * + * @return ?DateTimeImmutable + */ + public function get_last_modified_date(): ?DateTimeImmutable; + /** * Fetches videos from the provider that were modified after the provided DateTime. * From 1dcd4b81c2eee2e99ce779dd8e020f35319f14ad Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Tue, 23 Jul 2024 00:40:37 -0400 Subject: [PATCH 07/12] Set version to 0.1.0 in plugin main file --- wp-video-sync.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wp-video-sync.php b/wp-video-sync.php index 7790a40..821065e 100644 --- a/wp-video-sync.php +++ b/wp-video-sync.php @@ -3,7 +3,7 @@ * Plugin Name: WP Video Sync * Plugin URI: https://github.com/alleyinteractive/wp-video-sync * Description: Sync videos from a hosting provider to WordPress - * Version: 0.0.0 + * Version: 0.1.0 * Author: Alley * Author URI: https://github.com/alleyinteractive/wp-video-sync * Requires at least: 6.6 From ec47f9c8f6bf50afa84d9eafcd43388bbc0d0368 Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Tue, 23 Jul 2024 09:18:03 -0400 Subject: [PATCH 08/12] Code review feedback, relax version constraints --- .phpcs.xml | 2 +- README.md | 2 +- src/adapters/class-jw-player-7-for-wp.php | 4 ++-- src/class-sync-manager.php | 9 +++++++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.phpcs.xml b/.phpcs.xml index d8870e5..eadd241 100644 --- a/.phpcs.xml +++ b/.phpcs.xml @@ -28,7 +28,7 @@ vendor/ - + diff --git a/README.md b/README.md index 4bf76d4..f6bbb87 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Tags: alleyinteractive, wp-video-sync Stable tag: 0.1.0 -Requires at least: 6.6 +Requires at least: 6.0 Tested up to: 6.6 diff --git a/src/adapters/class-jw-player-7-for-wp.php b/src/adapters/class-jw-player-7-for-wp.php index a3deea1..92db2bc 100644 --- a/src/adapters/class-jw-player-7-for-wp.php +++ b/src/adapters/class-jw-player-7-for-wp.php @@ -18,9 +18,9 @@ class JW_Player_7_For_WP implements Adapter { /** * The date of the last modification to the last batch of videos. * - * @var DateTimeImmutable + * @var ?DateTimeImmutable */ - private DateTimeImmutable $last_modified_date; + private ?DateTimeImmutable $last_modified_date; /** * Fetches the date of the last modification to the last batch of videos. diff --git a/src/class-sync-manager.php b/src/class-sync-manager.php index c5e5a72..35ee1b8 100644 --- a/src/class-sync-manager.php +++ b/src/class-sync-manager.php @@ -28,9 +28,9 @@ class Sync_Manager { /** * The adapter to use for fetching videos. * - * @var Adapter + * @var ?Adapter */ - public Adapter $adapter; + public ?Adapter $adapter; /** * A callback to run for each result when the sync runs. @@ -75,6 +75,11 @@ public function maybe_schedule_sync(): void { * @throws Error If unable to parse the last sync as a DateTimeImmutable object. */ public function sync_videos(): void { + // If there isn't a valid adapter, bail. + if ( ! $this->adapter instanceof Adapter ) { + throw new Error( esc_html__( 'WP Video Sync: Unable to sync videos without a valid adapter.', 'wp-video-sync' ) ); + } + // If there isn't a valid callback, bail. if ( ! is_callable( $this->callback ) ) { throw new Error( esc_html__( 'WP Video Sync: Unable to execute provided callback.', 'wp-video-sync' ) ); From 643ef730a73c400aa1e7cd7ff887712849ac0593 Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Tue, 23 Jul 2024 10:08:32 -0400 Subject: [PATCH 09/12] Define WP_IMPORTING before bulk importing videos --- src/class-sync-manager.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/class-sync-manager.php b/src/class-sync-manager.php index 35ee1b8..1a87622 100644 --- a/src/class-sync-manager.php +++ b/src/class-sync-manager.php @@ -75,6 +75,8 @@ public function maybe_schedule_sync(): void { * @throws Error If unable to parse the last sync as a DateTimeImmutable object. */ public function sync_videos(): void { + define( 'WP_IMPORTING', true ); // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound + // If there isn't a valid adapter, bail. if ( ! $this->adapter instanceof Adapter ) { throw new Error( esc_html__( 'WP Video Sync: Unable to sync videos without a valid adapter.', 'wp-video-sync' ) ); From bf82c7b2407a529df7e203e79d9f79a692d498b3 Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Tue, 23 Jul 2024 10:36:42 -0400 Subject: [PATCH 10/12] Don't autoload the option, allow batch size to be configured --- src/adapters/class-jw-player-7-for-wp.php | 8 +++++--- src/class-sync-manager.php | 23 +++++++++++++++++++++-- src/interfaces/adapter.php | 3 ++- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/adapters/class-jw-player-7-for-wp.php b/src/adapters/class-jw-player-7-for-wp.php index 92db2bc..b3957e3 100644 --- a/src/adapters/class-jw-player-7-for-wp.php +++ b/src/adapters/class-jw-player-7-for-wp.php @@ -35,17 +35,19 @@ public function get_last_modified_date(): ?DateTimeImmutable { * Fetches videos from JW Player that were modified after the provided DateTime. * * @param DateTimeImmutable $updated_after Return videos modified after this date. + * @param int $batch_size The number of videos to fetch in each batch. * * @return stdClass[] An array of video data. */ - public function get_videos( DateTimeImmutable $updated_after ): array { + public function get_videos( DateTimeImmutable $updated_after, int $batch_size ): array { // Check if the JW Player 7 for WP plugin is active (free or premium). if ( class_exists( 'JWPPP_Dashboard_Api' ) ) { $api = new \JWPPP_Dashboard_Api(); $result = $api->call( sprintf( - 'media/?q=last_modified:[%s TO *]&page=1&page_length=100&sort=last_modified:asc', - $updated_after->format( 'Y-m-d' ) + 'media/?q=last_modified:[%s TO *]&page=1&page_length=%d&sort=last_modified:asc', + $updated_after->format( 'Y-m-d' ), + $batch_size ) ); $videos = $result->media ?? []; diff --git a/src/class-sync-manager.php b/src/class-sync-manager.php index 1a87622..f80aef4 100644 --- a/src/class-sync-manager.php +++ b/src/class-sync-manager.php @@ -32,6 +32,13 @@ class Sync_Manager { */ public ?Adapter $adapter; + /** + * The number of videos to process in each batch. + * + * @var int + */ + public int $batch_size = 1000; + /** * A callback to run for each result when the sync runs. * @@ -100,7 +107,7 @@ public function sync_videos(): void { } // Get a batch of videos and loop over them and process each. - $videos = $this->adapter->get_videos( $last_sync ); + $videos = $this->adapter->get_videos( $last_sync, $this->batch_size ); foreach ( $videos as $video ) { call_user_func_array( $this->callback, [ $video ] ); } @@ -108,7 +115,7 @@ public function sync_videos(): void { // Try to update the last sync time to the last modified time of the last video that was processed. $next_last_modified = $this->adapter->get_last_modified_date(); if ( $next_last_modified instanceof DateTimeImmutable ) { - update_option( self::LAST_SYNC_OPTION, $next_last_modified->format( DATE_W3C ) ); + update_option( self::LAST_SYNC_OPTION, $next_last_modified->format( DATE_W3C ), false ); } } @@ -124,6 +131,18 @@ public function with_adapter( Adapter $adapter ): self { return $this; } + /** + * Allows the batch size to be configured. + * + * @param int $batch_size The number of videos to process in each batch. + * + * @return $this For chaining configuration. + */ + public function with_batch_size( int $batch_size ): self { + $this->batch_size = $batch_size; + return $this; + } + /** * Allows a callback to be set that will be called when the sync runs. * diff --git a/src/interfaces/adapter.php b/src/interfaces/adapter.php index edcecde..c660628 100644 --- a/src/interfaces/adapter.php +++ b/src/interfaces/adapter.php @@ -25,8 +25,9 @@ public function get_last_modified_date(): ?DateTimeImmutable; * Fetches videos from the provider that were modified after the provided DateTime. * * @param DateTimeImmutable $updated_after Return videos modified after this date. + * @param int $batch_size The number of videos to fetch in each batch. * * @return stdClass[] An array of video data. Specific shape will be determined by the adapter. */ - public function get_videos( DateTimeImmutable $updated_after ): array; + public function get_videos( DateTimeImmutable $updated_after, int $batch_size ): array; } From 0b8117c5c3a42b103571abc511abe5a10c0f5ab1 Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Tue, 23 Jul 2024 10:47:43 -0400 Subject: [PATCH 11/12] Update readme with more robust example of usage --- README.md | 67 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index f6bbb87..b3626aa 100644 --- a/README.md +++ b/README.md @@ -37,32 +37,57 @@ Activate the plugin in WordPress and use it like so: ```php use Alley\WP\WP_Video_Sync\Adapters\JW_Player_7_For_WP; use Alley\WP\WP_Video_Sync\Sync_Manager; +use DateInterval; use DateTimeImmutable; use WP_Query; -$sync_manager = Sync_Manager::init() - ->with_adapter( new JW_Player_7_For_WP() ) - ->with_frequency( 'hourly' ) - ->with_callback( - function ( $video ) { - $existing_video = new WP_Query( [ 'meta_key' => 'jwplayer_id', 'meta_value' => $video->id ] ); - $existing_id = $existing_video->posts[0]->ID ?? 0; - wp_insert_post( - [ - 'ID' => $existing_id, - 'post_title' => $video->metadata->title, - 'post_date' => DateTimeImmutable::createFromFormat( DATE_W3C, $video->created )->format( 'Y-m-d H:i:s' ), - 'post_modified' => DateTimeImmutable::createFromFormat( DATE_W3C, $video->last_modified )->format( 'Y-m-d H:i:s' ), - 'meta_input' => [ - 'jwplayer_id' => $video->id, - ], - ] - ); - } - ); +add_action( 'plugins_loaded', function () => { + $sync_manager = Sync_Manager::init() + ->with_adapter( new JW_Player_7_For_WP() ) + ->with_frequency( 'hourly' ) + ->with_batch_size( 1000 ) + ->with_callback( + function ( $video ) { + $existing_video = new WP_Query( [ 'meta_key' => '_jwppp-video-url-1', 'meta_value' => $video->id ] ); + $existing_id = $existing_video->posts[0]->ID ?? 0; + $duration = ''; + try { + if ( ! empty( $video->metadata->duration ) ) { + $duration = ( new DateTimeImmutable() ) + ->add( new DateInterval( sprintf( 'PT%dS', (int) $video->metadata->duration ) ) ) + ->diff( new DateTimeImmutable() )->format( 'H:i:s' ); + } + } catch ( Exception $e ) { + $duration = ''; + } + wp_insert_post( + [ + 'ID' => $existing_id, + 'post_type' => 'post', + 'post_status' => 'publish', + 'post_title' => $video->metadata->title, + 'post_content' => $video->metadata->description ?? '', + 'post_date' => DateTimeImmutable::createFromFormat( DATE_W3C, $video->created )->format( 'Y-m-d H:i:s' ), + 'post_modified' => DateTimeImmutable::createFromFormat( DATE_W3C, $video->last_modified )->format( 'Y-m-d H:i:s' ), + 'meta_input' => [ + '_jwppp-video-url-1' => $video->id, + '_jwppp-cloud-playlist-1' => 'no', + '_jwppp-sources-number-1' => 1, + '_jwppp-video-title-1' => $video->metadata->title, + '_jwppp-video-description-1' => $video->metadata->description ?? '', + '_jwppp-activate-media-type-1' => 0, + '_jwppp-playlist-carousel-1' => 0, + '_jwppp-video-duration-1' => $duration, + '_jwppp-video-tags-1' => $video->metadata->tags ?? '', + ], + ] + ); + } + ); +} ); ``` -This will configure the plugin to import a batch of 100 videos every hour from JW Player, sorted by least to most recently updated, starting with the date and time of the last video that was updated. If videos have already been imported (as identified by the postmeta value saved for the unique video ID) they will be updated rather than created. New videos will be created. The example code above uses the `post` post type for this purpose, but the code could easily be adapted to use a custom post type. Additionally, the post content could be set to include a Gutenberg block or a shortcode for a player. +This will configure the plugin to import a batch of 1000 videos every hour from JW Player, sorted by least to most recently updated, starting with the date and time of the last video that was updated. If videos have already been imported (as identified by the postmeta value saved for the unique video ID) they will be updated rather than created. New videos will be created. The example code above uses the `post` post type for this purpose, but the code could easily be adapted to use a custom post type. Additionally, the post content could be set to include a Gutenberg block or a shortcode for a player. ### Supported Adapters From 13896fed35f19c1ff715b895179bf0a8a9a96ce9 Mon Sep 17 00:00:00 2001 From: Kevin Fodness <2650828+kevinfodness@users.noreply.github.com> Date: Tue, 23 Jul 2024 10:50:06 -0400 Subject: [PATCH 12/12] Relax version constraints in the other place I forgot earlier --- wp-video-sync.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wp-video-sync.php b/wp-video-sync.php index 821065e..77d17f3 100644 --- a/wp-video-sync.php +++ b/wp-video-sync.php @@ -6,7 +6,7 @@ * Version: 0.1.0 * Author: Alley * Author URI: https://github.com/alleyinteractive/wp-video-sync - * Requires at least: 6.6 + * Requires at least: 6.0 * Tested up to: 6.6 * * Text Domain: wp-video-sync