From e3f09416523cbf67b12907bf24a89d78ddc432dc Mon Sep 17 00:00:00 2001 From: Colin Murphy Date: Fri, 13 Jun 2025 18:46:13 +0100 Subject: [PATCH 01/35] Added more Admin unit tests. Removed some unusued classes. --- plugins/hwp-previews/TESTING.md | 19 +- .../src/Admin/Settings/Menu/Menu_Page.php | 28 +-- .../Post_Slug_Repository_Interface.php | 16 -- .../src/Post/Slug/Post_Slug_Repository.php | 39 --- .../Preview_Parameter_Builder_Interface.php | 25 -- .../Admin/Settings/Menu/MenuPageTest.php | 230 ++++++++++++++++++ 6 files changed, 242 insertions(+), 115 deletions(-) delete mode 100644 plugins/hwp-previews/src/Post/Slug/Contracts/Post_Slug_Repository_Interface.php delete mode 100644 plugins/hwp-previews/src/Post/Slug/Post_Slug_Repository.php delete mode 100644 plugins/hwp-previews/src/Preview/Parameter/Contracts/Preview_Parameter_Builder_Interface.php create mode 100644 plugins/hwp-previews/tests/wpunit/Admin/Settings/Menu/MenuPageTest.php diff --git a/plugins/hwp-previews/TESTING.md b/plugins/hwp-previews/TESTING.md index c855772e..384c1bee 100644 --- a/plugins/hwp-previews/TESTING.md +++ b/plugins/hwp-previews/TESTING.md @@ -33,6 +33,7 @@ The plugin provides scripts to set up a local WordPress environment for testing, 1. **Copy and configure environment variables:** ```bash + @TODO cp .env.dist .env # Edit .env as needed for your local setup ``` @@ -54,26 +55,20 @@ The plugin provides scripts to set up a local WordPress environment for testing, ## Running Tests -### Unit Tests - -Run unit tests (no WordPress loaded): - -```bash -composer run test:unit -# or -vendor/bin/codecept run unit -``` +@TODO ### WPUnit (WordPress-aware Unit/Integration) Tests Run WPUnit tests (WordPress loaded): ```bash -composer run test:wpunit -# or -vendor/bin/codecept run wpunit +sh bin/local/run-unit-tests.sh coverage ``` +> [!IMPORTANT] +> You can also add coverage e.g. `sh bin/local/run-unit-tests.sh coverage --coverage-html` and the output will be saved in [tests/_output/coverage/dashboard.html](tests/_output/coverage/dashboard.html) + + ### Functional Tests Run functional tests (simulate web requests): diff --git a/plugins/hwp-previews/src/Admin/Settings/Menu/Menu_Page.php b/plugins/hwp-previews/src/Admin/Settings/Menu/Menu_Page.php index d23fe0f9..08075a5c 100644 --- a/plugins/hwp-previews/src/Admin/Settings/Menu/Menu_Page.php +++ b/plugins/hwp-previews/src/Admin/Settings/Menu/Menu_Page.php @@ -41,13 +41,6 @@ class Menu_Page { */ protected array $args; - /** - * The name of a Dashicons helper class to use a font icon. - * - * @var string - */ - protected string $icon; - /** * Constructor. * @@ -56,22 +49,19 @@ class Menu_Page { * @param string $menu_slug The slug name to refer to this menu by. Should be unique for this menu and only include lowercase alphanumeric, dashes, and underscores characters to be compatible with sanitize_key(). * @param string $template The template that will be included in the callback. * @param array> $args The args array for the template. - * @param string $icon The name of a Dashicons helper class to use a font icon. */ public function __construct( string $page_title, string $menu_title, string $menu_slug, string $template, - array $args = [], - string $icon = 'dashicons-admin-generic' + array $args = [] ) { $this->page_title = $page_title; $this->menu_title = $menu_title; $this->menu_slug = $menu_slug; $this->template = $template; $this->args = $args; - $this->icon = $icon; } /** @@ -84,8 +74,7 @@ public function register_page(): void { $this->menu_title, 'manage_options', $this->menu_slug, - [ $this, 'registration_callback' ], - 999 + [ $this, 'registration_callback' ] ); } @@ -101,18 +90,11 @@ public function registration_callback(): void { return; } - $this->set_query_vars(); - - // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable -- $this->template is validated and defined within the class - include_once $this->template; - } - - /** - * Sets the query vars for the template. - */ - protected function set_query_vars(): void { foreach ( $this->args as $query_var => $args ) { set_query_var( $query_var, $args ); } + + // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable -- $this->template is validated and defined within the class + include_once $this->template; } } diff --git a/plugins/hwp-previews/src/Post/Slug/Contracts/Post_Slug_Repository_Interface.php b/plugins/hwp-previews/src/Post/Slug/Contracts/Post_Slug_Repository_Interface.php deleted file mode 100644 index 3c056635..00000000 --- a/plugins/hwp-previews/src/Post/Slug/Contracts/Post_Slug_Repository_Interface.php +++ /dev/null @@ -1,16 +0,0 @@ -get_var( // phpcs:ignore WordPress.DB - $wpdb->prepare( - // @phpstan-ignore-next-line - "SELECT post_name FROM {$wpdb->posts} WHERE post_name = %s AND post_type = %s AND ID != %d LIMIT 1", - $slug, - $post_type, - $post_id - ) - ); - } -} diff --git a/plugins/hwp-previews/src/Preview/Parameter/Contracts/Preview_Parameter_Builder_Interface.php b/plugins/hwp-previews/src/Preview/Parameter/Contracts/Preview_Parameter_Builder_Interface.php deleted file mode 100644 index 6cc37ccf..00000000 --- a/plugins/hwp-previews/src/Preview/Parameter/Contracts/Preview_Parameter_Builder_Interface.php +++ /dev/null @@ -1,25 +0,0 @@ - - */ - public function build_preview_args( WP_Post $post, string $page_uri, string $token ): array; -} diff --git a/plugins/hwp-previews/tests/wpunit/Admin/Settings/Menu/MenuPageTest.php b/plugins/hwp-previews/tests/wpunit/Admin/Settings/Menu/MenuPageTest.php new file mode 100644 index 00000000..20378693 --- /dev/null +++ b/plugins/hwp-previews/tests/wpunit/Admin/Settings/Menu/MenuPageTest.php @@ -0,0 +1,230 @@ + [ + 'tabs' => [ + 'post' => 'Posts', + 'page' => 'Pages' + ], + 'current_tab' => 'post', + 'params' => '' + ], + ]; + + protected Menu_Page $menu_page; + + public function setUp(): void { + parent::setUp(); + + $this->current_user_id = get_current_user_id(); + + // Create an administrator user for testing. + $this->admin_user_id = $this->factory()->user->create( [ + 'role' => 'administrator' + ] ); + wp_set_current_user( $this->admin_user_id ); + + // Create a temporary template file for testing + $this->template_file = sys_get_temp_dir() . '/test-template-' . uniqid() . '.php'; + file_put_contents( $this->template_file, '' ); + + $this->menu_page = new Menu_Page( + $this->page_title, + $this->menu_title, + $this->menu_slug, + $this->template_file, + $this->args + ); + } + + public function tearDown(): void { + // Clean up the temporary template file + if ( file_exists( $this->template_file ) ) { + unlink( $this->template_file ); + } + + // Clean up the user + wp_delete_user( $this->admin_user_id ); + wp_set_current_user( $this->current_user_id ); + + parent::tearDown(); + } + + public function test_constructor_sets_properties_correctly() { + + $page = $this->menu_page; + + $reflection = new \ReflectionClass( $page ); + + $page_title_prop = $reflection->getProperty( 'page_title' ); + $page_title_prop->setAccessible( true ); + $this->assertEquals( $this->page_title, $page_title_prop->getValue( $page ) ); + + $menu_title_prop = $reflection->getProperty( 'menu_title' ); + $menu_title_prop->setAccessible( true ); + $this->assertEquals( $this->menu_title, $menu_title_prop->getValue( $page ) ); + + $menu_slug_prop = $reflection->getProperty( 'menu_slug' ); + $menu_slug_prop->setAccessible( true ); + $this->assertEquals( $this->menu_slug, $menu_slug_prop->getValue( $page ) ); + + $template_prop = $reflection->getProperty( 'template' ); + $template_prop->setAccessible( true ); + $this->assertEquals( $this->template_file, $template_prop->getValue( $page ) ); + + $args_prop = $reflection->getProperty( 'args' ); + $args_prop->setAccessible( true ); + $this->assertEquals( $this->args, $args_prop->getValue( $page ) ); + } + + public function test_constructor_with_empty_args() { + $page = new Menu_Page( + $this->page_title, + $this->menu_title, + $this->menu_slug, + $this->template_file, + ); + + $reflection = new \ReflectionClass( $page ); + $args_prop = $reflection->getProperty( 'args' ); + $args_prop->setAccessible( true ); + $this->assertEquals( [], $args_prop->getValue( $page ) ); + } + + public function test_register_page_adds_submenu_correctly() { + global $submenu, $_registered_pages, $_parent_pages; + + $page = $this->menu_page; + + // Verify the current user has the required capability + $this->assertTrue( current_user_can( 'manage_options' ) ); + + // Capture the state before registration + $submenu_before = $submenu; + + $page->register_page(); + + // Verify that the submenu was modified + $this->assertNotEquals( $submenu_before, $submenu ); + + // Check that the submenu item was added to options-general.php + $this->assertArrayHasKey( 'options-general.php', $submenu ); + + // Find the added submenu item + $found_item = null; + foreach ( $submenu['options-general.php'] as $item ) { + if ( $item[2] === $this->menu_slug ) { + $found_item = $item; + break; + } + } + + $this->assertNotNull( $found_item, 'Should find the added submenu item' ); + $this->assertEquals( $this->menu_title, $found_item[0], 'Menu title should match' ); + $this->assertEquals( $this->menu_slug, $found_item[2], 'Menu slug should match' ); + $this->assertEquals( $this->page_title, $found_item[3], 'Page title should match' ); + $this->assertEquals( 'manage_options', $found_item[1], 'Capability should be manage_options' ); + + // Verify page was registered and parent relationship was set + $expected_hookname = get_plugin_page_hookname( $this->menu_slug, 'options-general.php' ); + $this->assertArrayHasKey( $expected_hookname, $_registered_pages ); + $this->assertEquals( 'options-general.php', $_parent_pages[$this->menu_slug] ); + } + + public function test_register_page_without_manage_options_capability() { + global $submenu, $_wp_submenu_nopriv; + + // Create a subscriber user (no manage_options capability) + $subscriber_id = $this->factory()->user->create( [ + 'role' => 'subscriber' + ] ); + + wp_set_current_user( $subscriber_id ); + + $page = $this->menu_page; + + // Verify the current user doesn't have the required capability + $this->assertFalse( current_user_can( 'manage_options' ) ); + + // Capture the state before registration + $submenu_before = $submenu; + + $page->register_page(); + + // When user lacks capability, the submenu should not be modified + // but the page should be marked as no-privilege + $this->assertEquals( $submenu_before, $submenu, 'Submenu should not be modified when user lacks capability' ); + $this->assertTrue( $_wp_submenu_nopriv['options-general.php'][$this->menu_slug], 'Should mark page as no-privilege' ); + + // Clean up + wp_delete_user( $subscriber_id ); + wp_set_current_user( $this->admin_user_id ); + } + + public function test_registration_callback_with_nonexistent_template() { + $page = new Menu_Page( + $this->page_title, + $this->menu_title, + $this->menu_slug, + '/path/to/nonexistent/template.php' + ); + + // Capture output + ob_start(); + $page->registration_callback(); + $output = ob_get_clean(); + + $this->assertStringContainsString( 'notice notice-error', $output ); + $this->assertStringContainsString( 'The HWP Previews Settings template does not exist.', $output ); + } + + public function test_registration_callback_sets_query_vars() { + $args = [ + 'test_var_1' => [ 'key1' => 'value1', 'key2' => 'value2' ], + 'test_var_2' => [ 'key3' => 'value3' ] + ]; + + $page = new Menu_Page( + $this->page_title, + $this->menu_title, + $this->menu_slug, + $this->template_file, + $args + ); + + // Clear any existing query vars + global $wp_query; + if ( isset( $wp_query->query_vars['test_var_1'] ) ) { + unset( $wp_query->query_vars['test_var_1'] ); + } + if ( isset( $wp_query->query_vars['test_var_2'] ) ) { + unset( $wp_query->query_vars['test_var_2'] ); + } + + $page->registration_callback(); + + // Verify query vars were set + $this->assertEquals( [ 'key1' => 'value1', 'key2' => 'value2' ], get_query_var( 'test_var_1' ) ); + $this->assertEquals( [ 'key3' => 'value3' ], get_query_var( 'test_var_2' ) ); + } +} From 6d329470ac8b79cc60ab2aea96499cd13a2faee5 Mon Sep 17 00:00:00 2001 From: Colin Murphy Date: Mon, 16 Jun 2025 19:29:16 +0100 Subject: [PATCH 02/35] Working on refactoring plugin. Adding settings tests. Moving templates under "Templates". Refactoring iframe functionality. --- plugins/hwp-previews/TESTING.md | 21 +- .../hwp-previews/bin/local/run-e2e-tests.sh | 23 ++ plugins/hwp-previews/codeception.dist.yml | 4 + plugins/hwp-previews/composer.json | 10 +- plugins/hwp-previews/hwp-previews.php | 5 - .../Settings/Fields/Field/URL_Input_Field.php | 15 +- .../hwp-previews/src/Admin/Settings_Page.php | 8 +- .../hwp-previews/src/Hooks/Preview_Hooks.php | 216 ++++++-------- .../src/Integration/Faust_Integration.php | 2 +- .../src/Preview/Link/Preview_Link_Service.php | 14 +- .../Post_Parent_Manager_Interface.php | 2 +- .../Post/Parent/Post_Parent_Manager.php | 16 +- .../Post_Statuses_Config_Interface.php | 2 +- .../Post/Status/Post_Statuses_Config.php | 4 +- .../Post_Type_Inspector_Interface.php | 2 +- .../Contracts/Post_Types_Config_Interface.php | 2 +- .../Post/Type/Post_Type_Inspector.php | 4 +- .../Post/Type/Post_Types_Config.php | 10 +- .../Post/Type/Post_Types_Config_Registry.php | 6 +- .../Preview_Template_Resolver_Interface.php | 17 -- .../Template/Preview_Template_Resolver.php | 65 ---- .../src/Preview/Template_Resolver.php | 97 ++++++ .../src/Preview/URL_Generator.php | 10 + .../admin.php} | 0 .../hwp-preview.php => Templates/iframe.php} | 4 +- .../hwp-previews/tests/wpunit.suite.dist.yml | 2 +- .../Fields/Field/TextURLFieldTest.php | 69 +++++ .../tests/wpunit/Admin/SettingsPageTest.php | 279 ++++++++++++++++++ 28 files changed, 641 insertions(+), 268 deletions(-) create mode 100755 plugins/hwp-previews/bin/local/run-e2e-tests.sh rename plugins/hwp-previews/src/{ => Preview}/Post/Parent/Contracts/Post_Parent_Manager_Interface.php (85%) rename plugins/hwp-previews/src/{ => Preview}/Post/Parent/Post_Parent_Manager.php (65%) rename plugins/hwp-previews/src/{ => Preview}/Post/Status/Contracts/Post_Statuses_Config_Interface.php (92%) rename plugins/hwp-previews/src/{ => Preview}/Post/Status/Post_Statuses_Config.php (89%) rename plugins/hwp-previews/src/{ => Preview}/Post/Type/Contracts/Post_Type_Inspector_Interface.php (90%) rename plugins/hwp-previews/src/{ => Preview}/Post/Type/Contracts/Post_Types_Config_Interface.php (95%) rename plugins/hwp-previews/src/{ => Preview}/Post/Type/Post_Type_Inspector.php (91%) rename plugins/hwp-previews/src/{ => Preview}/Post/Type/Post_Types_Config.php (86%) rename plugins/hwp-previews/src/{ => Preview}/Post/Type/Post_Types_Config_Registry.php (80%) delete mode 100644 plugins/hwp-previews/src/Preview/Template/Contracts/Preview_Template_Resolver_Interface.php delete mode 100644 plugins/hwp-previews/src/Preview/Template/Preview_Template_Resolver.php create mode 100644 plugins/hwp-previews/src/Preview/Template_Resolver.php create mode 100644 plugins/hwp-previews/src/Preview/URL_Generator.php rename plugins/hwp-previews/src/{Admin/Settings/Templates/settings-page-main.php => Templates/admin.php} (100%) rename plugins/hwp-previews/src/{Admin/Settings/Templates/hwp-preview.php => Templates/iframe.php} (81%) create mode 100644 plugins/hwp-previews/tests/wpunit/Admin/SettingsPageTest.php diff --git a/plugins/hwp-previews/TESTING.md b/plugins/hwp-previews/TESTING.md index 384c1bee..73cac54b 100644 --- a/plugins/hwp-previews/TESTING.md +++ b/plugins/hwp-previews/TESTING.md @@ -55,7 +55,10 @@ The plugin provides scripts to set up a local WordPress environment for testing, ## Running Tests -@TODO +Currently the plugin has the following suite of tests + +1. WP Unit Tests - (Unit and Integration Tests) +2. E2E Tests - Playright tests ### WPUnit (WordPress-aware Unit/Integration) Tests @@ -69,24 +72,12 @@ sh bin/local/run-unit-tests.sh coverage > You can also add coverage e.g. `sh bin/local/run-unit-tests.sh coverage --coverage-html` and the output will be saved in [tests/_output/coverage/dashboard.html](tests/_output/coverage/dashboard.html) -### Functional Tests - -Run functional tests (simulate web requests): - -```bash -composer run test:functional -# or -vendor/bin/codecept run functional -``` - -### Acceptance Tests +### E2WTests Run browser-based acceptance tests: ```bash -composer run test:acceptance -# or -vendor/bin/codecept run acceptance +sh bin/local/run-e2e-tests.sh coverage ``` ### All Tests diff --git a/plugins/hwp-previews/bin/local/run-e2e-tests.sh b/plugins/hwp-previews/bin/local/run-e2e-tests.sh new file mode 100755 index 00000000..f0e260a8 --- /dev/null +++ b/plugins/hwp-previews/bin/local/run-e2e-tests.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +# Run Playwright E2E tests for GitHub Actions + +set -e + +# Install dependencies if needed +npm ci + +# Install composer dependencies +composer install + +# Install Playwright browsers +npx playwright install --with-deps + +# Start wp-env +npm run wp-env start + +# Run Playwright tests +npm run test:e2e + +# Stop wp-env +npm run wp-env stop diff --git a/plugins/hwp-previews/codeception.dist.yml b/plugins/hwp-previews/codeception.dist.yml index cf0411f5..87269997 100644 --- a/plugins/hwp-previews/codeception.dist.yml +++ b/plugins/hwp-previews/codeception.dist.yml @@ -31,6 +31,9 @@ extensions: - lucatume\WPBrowser\Command\MonkeyCachePath - lucatume\WPBrowser\Command\RunAll - lucatume\WPBrowser\Command\RunOriginal +exclude: + - src/Templates + - src/Templates/* coverage: enabled: true remote: false @@ -48,6 +51,7 @@ coverage: - /tests/* - /vendor-prefixed/* - /vendor/* + - /src/Templates/* show_only_summary: false modules: config: diff --git a/plugins/hwp-previews/composer.json b/plugins/hwp-previews/composer.json index 005b1c76..394069a4 100644 --- a/plugins/hwp-previews/composer.json +++ b/plugins/hwp-previews/composer.json @@ -128,7 +128,15 @@ ], "php:psalm": "psalm", "php:psalm:info": "psalm --show-info=true", - "php:psalm:fix": "psalm --alter" + "php:psalm:fix": "psalm --alter", + "test": [ + "sh bin/local/run-unit-tests.sh", + "sh bin/local/run-e2e-tests.sh" + ], + "test:unit": "sh bin/local/run-unit-tests.sh", + "test:unit:coverage": "sh bin/local/run-unit-tests.sh coverage", + "test:unit:coverage-html": "sh bin/local/run-unit-tests.sh coverage --coverage-html", + "test:e2e": "sh bin/local/run-e2e-tests.sh" }, "support": { "docs": "https://github.com/wpengine/hwptoolkit/tree/main/docs", diff --git a/plugins/hwp-previews/hwp-previews.php b/plugins/hwp-previews/hwp-previews.php index 1b332cb3..91c94357 100644 --- a/plugins/hwp-previews/hwp-previews.php +++ b/plugins/hwp-previews/hwp-previews.php @@ -84,11 +84,6 @@ function hwp_previews_constants(): void { if ( ! defined( 'HWP_PREVIEWS_TEXT_DOMAIN' ) ) { define( 'HWP_PREVIEWS_TEXT_DOMAIN', 'hwp-previews' ); } - - // Plugin Template Directory. - if ( ! defined( 'HWP_PREVIEWS_TEMPLATE_DIR' ) ) { - define( 'HWP_PREVIEWS_TEMPLATE_DIR', trailingslashit( HWP_PREVIEWS_PLUGIN_DIR ) . '/src/Admin/Settings/Templates/' ); - } } // phpcs:enable Generic.Metrics.CyclomaticComplexity.TooHigh diff --git a/plugins/hwp-previews/src/Admin/Settings/Fields/Field/URL_Input_Field.php b/plugins/hwp-previews/src/Admin/Settings/Fields/Field/URL_Input_Field.php index f9b15b89..ef700e9e 100644 --- a/plugins/hwp-previews/src/Admin/Settings/Fields/Field/URL_Input_Field.php +++ b/plugins/hwp-previews/src/Admin/Settings/Fields/Field/URL_Input_Field.php @@ -43,7 +43,16 @@ public function get_input_type(): string { * @param string $value */ private function fix_url( string $value ): string { - // Remove HTML tags, trim, encode spaces, add protocol. + + + if ( '' === $value ) { + return ''; + } + + // Remove #is', '', $value ); + + // Remove HTML tags except curly braces, trim, encode spaces, add protocol. $value = preg_replace( '/<(?!\{)[^>]+>/', '', $value ); $value = trim( str_replace( ' ', '%20', (string) $value ) ); @@ -55,7 +64,7 @@ private function fix_url( string $value ): string { if ( $has_prootocol ) { return $value; } - $protocol = is_ssl() ? 'https://' : 'http://'; - return $protocol . ltrim( $value, '/' ); + $protocol = is_ssl() ? 'https://' : 'http://'; + return $protocol . ltrim( $value, '/' ); } } diff --git a/plugins/hwp-previews/src/Admin/Settings_Page.php b/plugins/hwp-previews/src/Admin/Settings_Page.php index 1d30c54d..67165c6a 100644 --- a/plugins/hwp-previews/src/Admin/Settings_Page.php +++ b/plugins/hwp-previews/src/Admin/Settings_Page.php @@ -7,9 +7,9 @@ use HWP\Previews\Admin\Settings\Fields\Settings_Field_Collection; use HWP\Previews\Admin\Settings\Menu\Menu_Page; use HWP\Previews\Admin\Settings\Settings_Form_Manager; -use HWP\Previews\Post\Type\Contracts\Post_Types_Config_Interface; -use HWP\Previews\Post\Type\Post_Types_Config_Registry; use HWP\Previews\Preview\Parameter\Preview_Parameter_Registry; +use HWP\Previews\Preview\Post\Type\Contracts\Post_Types_Config_Interface; +use HWP\Previews\Preview\Post\Type\Post_Types_Config_Registry; class Settings_Page { /** @@ -23,7 +23,7 @@ class Settings_Page { protected Preview_Parameter_Registry $parameters; /** - * @var \HWP\Previews\Post\Type\Contracts\Post_Types_Config_Interface The post types available for previews. + * @var \HWP\Previews\Preview\Post\Type\Contracts\Post_Types_Config_Interface The post types available for previews. */ protected Post_Types_Config_Interface $types_config; @@ -80,7 +80,7 @@ public function register_settings_pages(): void { __( 'HWP Previews Settings', 'hwp-previews' ), 'HWP Previews', self::PLUGIN_MENU_SLUG, - trailingslashit( HWP_PREVIEWS_TEMPLATE_DIR ) . 'settings-page-main.php', + trailingslashit(HWP_PREVIEWS_PLUGIN_DIR) . 'src/Templates/admin.php', [ 'hwp_previews_main_page_config' => [ 'tabs' => $post_types, diff --git a/plugins/hwp-previews/src/Hooks/Preview_Hooks.php b/plugins/hwp-previews/src/Hooks/Preview_Hooks.php index 9b9b7938..8c70156b 100644 --- a/plugins/hwp-previews/src/Hooks/Preview_Hooks.php +++ b/plugins/hwp-previews/src/Hooks/Preview_Hooks.php @@ -1,19 +1,19 @@ settings_helper = Settings_Helper::get_instance(); + + $this->types_config = apply_filters( + 'hwp_previews_hooks_post_type_config', + Post_Types_Config_Registry::get_post_type_config() + ); + + // Initialize the post types and statuses configurations. + $this->statuses_config = apply_filters( + 'hwp_previews_hooks_post_status_config', + ( new Post_Statuses_Config() )->set_post_statuses( $this->get_post_statuses() ) + ); + + // Initialize the preview link service. + $this->link_service = apply_filters( + 'hwp_previews_hooks_preview_link_service', + new Preview_Link_Service( + $this->types_config, + $this->statuses_config, + new Preview_Link_Placeholder_Resolver( Preview_Parameter_Registry::get_instance() ) + ) + ); } /** * Registers the hooks for the preview functionality. */ - public static function add_hook_actions(): void { - if ( null === self::$types_config ) { - return; - } + public function setup(): void { // Enable post statuses as parent for the post types specified in the post-types config. - add_filter( 'page_attributes_dropdown_pages_args', [ self::class, 'enable_post_statuses_as_parent' ], 10, 1 ); - add_filter( 'quick_edit_dropdown_pages_args', [ self::class, 'enable_post_statuses_as_parent' ], 10, 1 ); + add_filter( 'page_attributes_dropdown_pages_args', [ $this, 'enable_post_statuses_as_parent' ], 10, 1 ); + add_filter( 'quick_edit_dropdown_pages_args', [ $this, 'enable_post_statuses_as_parent' ], 10, 1 ); - foreach ( self::$types_config->get_post_types() as $post_type ) { - if ( ! self::$types_config->gutenberg_editor_enabled( $post_type ) ) { + foreach ( $this->types_config->get_post_types() as $post_type ) { + if ( ! $this->types_config->gutenberg_editor_enabled( $post_type ) ) { continue; } // @TODO - Add unit tests for this filter. - add_filter( 'rest_' . $post_type . '_query', [ self::class, 'enable_post_statuses_as_parent' ], 10, 1 ); + add_filter( 'rest_' . $post_type . '_query', [ $this, 'enable_post_statuses_as_parent' ], 10, 1 ); } // iframe preview functionality. - add_filter( 'template_include', [ self::class, 'add_iframe_preview_template' ], 10, 1 ); + add_filter( 'template_include', [ $this, 'add_iframe_preview_template' ], 10, 1 ); // Preview link functionality. Extra priority to ensure it runs after the Faust preview link filter. - add_filter( 'preview_post_link', [ self::class, 'update_preview_post_link' ], 1001, 2 ); + add_filter( 'preview_post_link', [ $this, 'update_preview_post_link' ], 1001, 2 ); /** * Hack Function that changes the preview link for draft articles, * this must be removed when properly fixed https://github.com/WordPress/gutenberg/issues/13998. */ - foreach ( self::$types_config->get_public_post_types() as $key => $label ) { - add_filter( 'rest_prepare_' . $key, [ self::class, 'filter_rest_prepare_link' ], 10, 2 ); + foreach ( $this->types_config->get_public_post_types() as $key => $label ) { + add_filter( 'rest_prepare_' . $key, [ $this, 'filter_rest_prepare_link' ], 10, 2 ); } } - /** - * Initializes the class properties. - */ - public static function init_class_properties(): void { - - self::$settings_helper = Settings_Helper::get_instance(); - - self::$types_config = apply_filters( - 'hwp_previews_hooks_post_type_config', - Post_Types_Config_Registry::get_post_type_config() - ); - - // Initialize the post types and statuses configurations. - self::$statuses_config = apply_filters( - 'hwp_previews_hooks_post_status_config', - ( new Post_Statuses_Config() )->set_post_statuses( self::get_post_statuses() ) - ); - - // Initialize the preview link service. - self::$link_service = apply_filters( - 'hwp_previews_hooks_preview_link_service', - new Preview_Link_Service( - // @phpstan-ignore-next-line - self::$types_config, - // @phpstan-ignore-next-line - self::$statuses_config, - new Preview_Link_Placeholder_Resolver( Preview_Parameter_Registry::get_instance() ) - ) - ); - } - /** * Gets a list of available post statuses for the preview functionality.. * * @return array */ - public static function get_post_statuses(): array { + public function get_post_statuses(): array { $post_statuses = [ 'publish', 'future', @@ -150,13 +145,9 @@ public static function get_post_statuses(): array { * * @link https://developer.wordpress.org/reference/hooks/quick_edit_dropdown_pages_args/ */ - public static function enable_post_statuses_as_parent( array $args ): array { - - if ( null === self::$settings_helper || null === self::$types_config || null === self::$statuses_config ) { - return $args; - } + public function enable_post_statuses_as_parent( array $args ): array { - $post_parent_manager = new Post_Parent_Manager( self::$types_config, self::$statuses_config ); + $post_parent_manager = new Post_Parent_Manager( $this->types_config, $this->statuses_config ); if ( empty( $args['post_type'] ) ) { return $args; @@ -165,7 +156,7 @@ public static function enable_post_statuses_as_parent( array $args ): array { $post_type = (string) $args['post_type']; // Check if the correspondent setting is enabled. - if ( ! self::$settings_helper->post_statuses_as_parent( $post_type ) ) { + if ( ! $this->settings_helper->post_statuses_as_parent( $post_type ) ) { return $args; } @@ -180,16 +171,13 @@ public static function enable_post_statuses_as_parent( array $args ): array { /** * Replace the preview link in the REST response. */ - public static function filter_rest_prepare_link( WP_REST_Response $response, WP_Post $post ): WP_REST_Response { - if ( null === self::$settings_helper ) { - return $response; - } + public function filter_rest_prepare_link( WP_REST_Response $response, WP_Post $post ): WP_REST_Response { - if ( self::$settings_helper->in_iframe( $post->post_type ) ) { + if ( $this->settings_helper->in_iframe( $post->post_type ) ) { return $response; } - $preview_url = self::generate_preview_url( $post ); + $preview_url = $this->generate_preview_url( $post ); if ( ! empty( $preview_url ) ) { $response->data['link'] = $preview_url; } @@ -200,47 +188,29 @@ public static function filter_rest_prepare_link( WP_REST_Response $response, WP_ /** * Enable preview functionality in iframe. */ - public static function add_iframe_preview_template( string $template ): string { + public function add_iframe_preview_template( string $template ): string { - // Bail out if class not initialized or settings helper and link service are not set. - if ( null === self::$settings_helper || null === self::$link_service || null === self::$types_config || null === self::$statuses_config ) { - return $template; - } + $template_resolver = new Template_Resolver(); - if ( ! is_preview() ) { - return $template; - } - $post = get_post(); - if ( ! $post instanceof WP_Post ) { + if ( ! $template_resolver->is_allowed() ) { return $template; } - // Check if the correspondent setting is enabled. - if ( ! self::$settings_helper->in_iframe( $post->post_type ) ) { - return $template; - } - - $template_resolver = new Preview_Template_Resolver( self::$types_config, self::$statuses_config ); - - - /** - * The filter 'hwp_previews_template_path' allows to change the template directory path. - */ - $template_dir_path = (string) apply_filters( - 'hwp_previews_template_path', - trailingslashit( HWP_PREVIEWS_TEMPLATE_DIR ) . 'hwp-preview.php', - ); - - $preview_template = $template_resolver->resolve_template_path( $post, $template_dir_path ); - - if ( empty( $preview_template ) ) { + $iframe_template = $template_resolver->get_iframe_template(); + if ( empty( $iframe_template ) ) { return $template; } + // @TODO remove + // @TODO how do we get this URL with Preview_URL_Generator? + $post = $template_resolver->get_post(); set_query_var( $template_resolver::HWP_PREVIEWS_IFRAME_PREVIEW_URL, self::generate_preview_url( $post ) ); - return $preview_template; + // @TODO - Add back in. + // $template_resolver->set_query_variable( $iframe_template ); + + return $iframe_template; } /** @@ -248,21 +218,16 @@ public static function add_iframe_preview_template( string $template ): string { * * @link https://developer.wordpress.org/reference/hooks/preview_post_link/ */ - public static function update_preview_post_link( string $preview_link, WP_Post $post ): string { - - // Bail out if class not initialized or settings helper and link service are not set. - if ( null === self::$settings_helper || null === self::$link_service ) { - return $preview_link; - } + public function update_preview_post_link( string $preview_link, WP_Post $post ): string { // @TODO - Need to do more testing and add e2e tests for this filter. // If iframe option is enabled, we need to resolve preview on the template redirect level. - if ( self::$settings_helper->in_iframe( $post->post_type ) ) { + if ( $this->settings_helper->in_iframe( $post->post_type ) ) { return $preview_link; } - $url = self::generate_preview_url( $post ); + $url = $this->generate_preview_url( $post ); if ( empty( $url ) ) { return $preview_link; } @@ -277,18 +242,25 @@ public static function update_preview_post_link( string $preview_link, WP_Post $ * * @return string The generated preview URL. */ - public static function generate_preview_url( WP_Post $post ): string { - if ( null === self::$settings_helper ) { - return ''; - } + public function generate_preview_url( WP_Post $post ): string { // Check if the correspondent setting is enabled. - $url = self::$settings_helper->url_template( $post->post_type ); + $url = $this->settings_helper->url_template( $post->post_type ); - if ( empty( $url ) || null === self::$link_service ) { + if ( empty( $url ) ) { return ''; } - return self::$link_service->generate_preview_post_link( $url, $post ); + return $this->link_service->generate_preview_post_link( $url, $post ); + } + + /** + * Initialize the hooks for the preview functionality. + */ + public static function init(): void { + if ( ! isset( self::$instance ) || ! ( is_a( self::$instance, self::class ) ) ) { + self::$instance = new self(); + self::$instance->setup(); + } } } diff --git a/plugins/hwp-previews/src/Integration/Faust_Integration.php b/plugins/hwp-previews/src/Integration/Faust_Integration.php index 70c70ecc..a4c187a7 100644 --- a/plugins/hwp-previews/src/Integration/Faust_Integration.php +++ b/plugins/hwp-previews/src/Integration/Faust_Integration.php @@ -4,8 +4,8 @@ namespace HWP\Previews\Integration; -use HWP\Previews\Post\Type\Post_Types_Config_Registry; use HWP\Previews\Preview\Helper\Settings_Group; +use HWP\Previews\Preview\Post\Type\Post_Types_Config_Registry; class Faust_Integration { /** diff --git a/plugins/hwp-previews/src/Preview/Link/Preview_Link_Service.php b/plugins/hwp-previews/src/Preview/Link/Preview_Link_Service.php index db1cbfd2..fa19193d 100644 --- a/plugins/hwp-previews/src/Preview/Link/Preview_Link_Service.php +++ b/plugins/hwp-previews/src/Preview/Link/Preview_Link_Service.php @@ -4,8 +4,8 @@ namespace HWP\Previews\Preview\Link; -use HWP\Previews\Post\Status\Contracts\Post_Statuses_Config_Interface; -use HWP\Previews\Post\Type\Contracts\Post_Types_Config_Interface; +use HWP\Previews\Preview\Post\Status\Contracts\Post_Statuses_Config_Interface; +use HWP\Previews\Preview\Post\Type\Contracts\Post_Types_Config_Interface; use WP_Post; /** @@ -15,14 +15,14 @@ class Preview_Link_Service { /** * Post types config. * - * @var \HWP\Previews\Post\Type\Contracts\Post_Types_Config_Interface + * @var \HWP\Previews\Preview\Post\Type\Contracts\Post_Types_Config_Interface */ private Post_Types_Config_Interface $types; /** * Post statuses config. * - * @var \HWP\Previews\Post\Status\Contracts\Post_Statuses_Config_Interface + * @var \HWP\Previews\Preview\Post\Status\Contracts\Post_Statuses_Config_Interface */ private Post_Statuses_Config_Interface $statuses; @@ -36,9 +36,9 @@ class Preview_Link_Service { /** * Constructor. * - * @param \HWP\Previews\Post\Type\Contracts\Post_Types_Config_Interface $types Post types config. - * @param \HWP\Previews\Post\Status\Contracts\Post_Statuses_Config_Interface $statuses Post statuses config. - * @param \HWP\Previews\Preview\Link\Preview_Link_Placeholder_Resolver $resolver Preview link resolver. + * @param \HWP\Previews\Preview\Post\Type\Contracts\Post_Types_Config_Interface $types Post types config. + * @param \HWP\Previews\Preview\Post\Status\Contracts\Post_Statuses_Config_Interface $statuses Post statuses config. + * @param \HWP\Previews\Preview\Link\Preview_Link_Placeholder_Resolver $resolver Preview link resolver. */ public function __construct( Post_Types_Config_Interface $types, diff --git a/plugins/hwp-previews/src/Post/Parent/Contracts/Post_Parent_Manager_Interface.php b/plugins/hwp-previews/src/Preview/Post/Parent/Contracts/Post_Parent_Manager_Interface.php similarity index 85% rename from plugins/hwp-previews/src/Post/Parent/Contracts/Post_Parent_Manager_Interface.php rename to plugins/hwp-previews/src/Preview/Post/Parent/Contracts/Post_Parent_Manager_Interface.php index 31557ee1..094b7b2e 100644 --- a/plugins/hwp-previews/src/Post/Parent/Contracts/Post_Parent_Manager_Interface.php +++ b/plugins/hwp-previews/src/Preview/Post/Parent/Contracts/Post_Parent_Manager_Interface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace HWP\Previews\Post\Parent\Contracts; +namespace HWP\Previews\Preview\Post\Parent\Contracts; interface Post_Parent_Manager_Interface { /** diff --git a/plugins/hwp-previews/src/Post/Parent/Post_Parent_Manager.php b/plugins/hwp-previews/src/Preview/Post/Parent/Post_Parent_Manager.php similarity index 65% rename from plugins/hwp-previews/src/Post/Parent/Post_Parent_Manager.php rename to plugins/hwp-previews/src/Preview/Post/Parent/Post_Parent_Manager.php index 93d95134..0aaf834b 100644 --- a/plugins/hwp-previews/src/Post/Parent/Post_Parent_Manager.php +++ b/plugins/hwp-previews/src/Preview/Post/Parent/Post_Parent_Manager.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace HWP\Previews\Post\Parent; +namespace HWP\Previews\Preview\Post\Parent; -use HWP\Previews\Post\Parent\Contracts\Post_Parent_Manager_Interface; -use HWP\Previews\Post\Status\Contracts\Post_Statuses_Config_Interface; -use HWP\Previews\Post\Type\Contracts\Post_Types_Config_Interface; +use HWP\Previews\Preview\Post\Parent\Contracts\Post_Parent_Manager_Interface; +use HWP\Previews\Preview\Post\Status\Contracts\Post_Statuses_Config_Interface; +use HWP\Previews\Preview\Post\Type\Contracts\Post_Types_Config_Interface; /** * Class Post_Parent_Manager. @@ -24,22 +24,22 @@ class Post_Parent_Manager implements Post_Parent_Manager_Interface { /** * Post types configuration. * - * @var \HWP\Previews\Post\Type\Contracts\Post_Types_Config_Interface + * @var \HWP\Previews\Preview\Post\Type\Contracts\Post_Types_Config_Interface */ private Post_Types_Config_Interface $post_types; /** * Post statuses configuration. * - * @var \HWP\Previews\Post\Status\Contracts\Post_Statuses_Config_Interface + * @var \HWP\Previews\Preview\Post\Status\Contracts\Post_Statuses_Config_Interface */ private Post_Statuses_Config_Interface $post_statuses; /** * Post_Parent_Manager constructor. * - * @param \HWP\Previews\Post\Type\Contracts\Post_Types_Config_Interface $post_types Post types configuration. - * @param \HWP\Previews\Post\Status\Contracts\Post_Statuses_Config_Interface $post_statuses Post statuses configuration. + * @param \HWP\Previews\Preview\Post\Type\Contracts\Post_Types_Config_Interface $post_types Post types configuration. + * @param \HWP\Previews\Preview\Post\Status\Contracts\Post_Statuses_Config_Interface $post_statuses Post statuses configuration. */ public function __construct( Post_Types_Config_Interface $post_types, Post_Statuses_Config_Interface $post_statuses ) { $this->post_types = $post_types; diff --git a/plugins/hwp-previews/src/Post/Status/Contracts/Post_Statuses_Config_Interface.php b/plugins/hwp-previews/src/Preview/Post/Status/Contracts/Post_Statuses_Config_Interface.php similarity index 92% rename from plugins/hwp-previews/src/Post/Status/Contracts/Post_Statuses_Config_Interface.php rename to plugins/hwp-previews/src/Preview/Post/Status/Contracts/Post_Statuses_Config_Interface.php index 1af9b622..a0003fbe 100644 --- a/plugins/hwp-previews/src/Post/Status/Contracts/Post_Statuses_Config_Interface.php +++ b/plugins/hwp-previews/src/Preview/Post/Status/Contracts/Post_Statuses_Config_Interface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace HWP\Previews\Post\Status\Contracts; +namespace HWP\Previews\Preview\Post\Status\Contracts; interface Post_Statuses_Config_Interface { /** diff --git a/plugins/hwp-previews/src/Post/Status/Post_Statuses_Config.php b/plugins/hwp-previews/src/Preview/Post/Status/Post_Statuses_Config.php similarity index 89% rename from plugins/hwp-previews/src/Post/Status/Post_Statuses_Config.php rename to plugins/hwp-previews/src/Preview/Post/Status/Post_Statuses_Config.php index a5a25f58..d0a1d4ff 100644 --- a/plugins/hwp-previews/src/Post/Status/Post_Statuses_Config.php +++ b/plugins/hwp-previews/src/Preview/Post/Status/Post_Statuses_Config.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace HWP\Previews\Post\Status; +namespace HWP\Previews\Preview\Post\Status; -use HWP\Previews\Post\Status\Contracts\Post_Statuses_Config_Interface; +use HWP\Previews\Preview\Post\Status\Contracts\Post_Statuses_Config_Interface; /** * Class Post_Statuses_Config. diff --git a/plugins/hwp-previews/src/Post/Type/Contracts/Post_Type_Inspector_Interface.php b/plugins/hwp-previews/src/Preview/Post/Type/Contracts/Post_Type_Inspector_Interface.php similarity index 90% rename from plugins/hwp-previews/src/Post/Type/Contracts/Post_Type_Inspector_Interface.php rename to plugins/hwp-previews/src/Preview/Post/Type/Contracts/Post_Type_Inspector_Interface.php index eb21865e..7bae44ae 100644 --- a/plugins/hwp-previews/src/Post/Type/Contracts/Post_Type_Inspector_Interface.php +++ b/plugins/hwp-previews/src/Preview/Post/Type/Contracts/Post_Type_Inspector_Interface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace HWP\Previews\Post\Type\Contracts; +namespace HWP\Previews\Preview\Post\Type\Contracts; use WP_Post_Type; diff --git a/plugins/hwp-previews/src/Post/Type/Contracts/Post_Types_Config_Interface.php b/plugins/hwp-previews/src/Preview/Post/Type/Contracts/Post_Types_Config_Interface.php similarity index 95% rename from plugins/hwp-previews/src/Post/Type/Contracts/Post_Types_Config_Interface.php rename to plugins/hwp-previews/src/Preview/Post/Type/Contracts/Post_Types_Config_Interface.php index 141035c2..43bd3957 100644 --- a/plugins/hwp-previews/src/Post/Type/Contracts/Post_Types_Config_Interface.php +++ b/plugins/hwp-previews/src/Preview/Post/Type/Contracts/Post_Types_Config_Interface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace HWP\Previews\Post\Type\Contracts; +namespace HWP\Previews\Preview\Post\Type\Contracts; interface Post_Types_Config_Interface { /** diff --git a/plugins/hwp-previews/src/Post/Type/Post_Type_Inspector.php b/plugins/hwp-previews/src/Preview/Post/Type/Post_Type_Inspector.php similarity index 91% rename from plugins/hwp-previews/src/Post/Type/Post_Type_Inspector.php rename to plugins/hwp-previews/src/Preview/Post/Type/Post_Type_Inspector.php index 422210f7..bb688210 100644 --- a/plugins/hwp-previews/src/Post/Type/Post_Type_Inspector.php +++ b/plugins/hwp-previews/src/Preview/Post/Type/Post_Type_Inspector.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace HWP\Previews\Post\Type; +namespace HWP\Previews\Preview\Post\Type; -use HWP\Previews\Post\Type\Contracts\Post_Type_Inspector_Interface; +use HWP\Previews\Preview\Post\Type\Contracts\Post_Type_Inspector_Interface; use WP_Post_Type; class Post_Type_Inspector implements Post_Type_Inspector_Interface { diff --git a/plugins/hwp-previews/src/Post/Type/Post_Types_Config.php b/plugins/hwp-previews/src/Preview/Post/Type/Post_Types_Config.php similarity index 86% rename from plugins/hwp-previews/src/Post/Type/Post_Types_Config.php rename to plugins/hwp-previews/src/Preview/Post/Type/Post_Types_Config.php index 33ad2fc0..b890c67f 100644 --- a/plugins/hwp-previews/src/Post/Type/Post_Types_Config.php +++ b/plugins/hwp-previews/src/Preview/Post/Type/Post_Types_Config.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace HWP\Previews\Post\Type; +namespace HWP\Previews\Preview\Post\Type; -use HWP\Previews\Post\Type\Contracts\Post_Type_Inspector_Interface; -use HWP\Previews\Post\Type\Contracts\Post_Types_Config_Interface; +use HWP\Previews\Preview\Post\Type\Contracts\Post_Type_Inspector_Interface; +use HWP\Previews\Preview\Post\Type\Contracts\Post_Types_Config_Interface; use WP_Post_Type; /** @@ -22,14 +22,14 @@ class Post_Types_Config implements Post_Types_Config_Interface { /** * Post type inspector. * - * @var \HWP\Previews\Post\Type\Contracts\Post_Type_Inspector_Interface + * @var \HWP\Previews\Preview\Post\Type\Contracts\Post_Type_Inspector_Interface */ private Post_Type_Inspector_Interface $inspector; /** * Class constructor. * - * @param \HWP\Previews\Post\Type\Contracts\Post_Type_Inspector_Interface $inspector Post Type inspector. + * @param \HWP\Previews\Preview\Post\Type\Contracts\Post_Type_Inspector_Interface $inspector Post Type inspector. */ public function __construct( Post_Type_Inspector_Interface $inspector ) { $this->inspector = $inspector; diff --git a/plugins/hwp-previews/src/Post/Type/Post_Types_Config_Registry.php b/plugins/hwp-previews/src/Preview/Post/Type/Post_Types_Config_Registry.php similarity index 80% rename from plugins/hwp-previews/src/Post/Type/Post_Types_Config_Registry.php rename to plugins/hwp-previews/src/Preview/Post/Type/Post_Types_Config_Registry.php index c5e5d2c7..0efa0906 100644 --- a/plugins/hwp-previews/src/Post/Type/Post_Types_Config_Registry.php +++ b/plugins/hwp-previews/src/Preview/Post/Type/Post_Types_Config_Registry.php @@ -2,10 +2,10 @@ declare(strict_types=1); -namespace HWP\Previews\Post\Type; +namespace HWP\Previews\Preview\Post\Type; -use HWP\Previews\Post\Type\Contracts\Post_Types_Config_Interface; use HWP\Previews\Preview\Helper\Settings_Helper; +use HWP\Previews\Preview\Post\Type\Contracts\Post_Types_Config_Interface; /** * Class Post_Types_Config_Registry. @@ -14,7 +14,7 @@ class Post_Types_Config_Registry { /** * The instance of the post types config. * - * @var \HWP\Previews\Post\Type\Contracts\Post_Types_Config_Interface|null + * @var \HWP\Previews\Preview\Post\Type\Contracts\Post_Types_Config_Interface|null */ protected static ?Post_Types_Config_Interface $instance = null; diff --git a/plugins/hwp-previews/src/Preview/Template/Contracts/Preview_Template_Resolver_Interface.php b/plugins/hwp-previews/src/Preview/Template/Contracts/Preview_Template_Resolver_Interface.php deleted file mode 100644 index 24973aec..00000000 --- a/plugins/hwp-previews/src/Preview/Template/Contracts/Preview_Template_Resolver_Interface.php +++ /dev/null @@ -1,17 +0,0 @@ -types = $types; - $this->statuses = $statuses; - } - - /** - * Resolves the template path for the preview. - * - * @param \WP_Post $post The post object. - * @param string $template_path The template path. - * - * @return string The resolved template path. - */ - public function resolve_template_path( WP_Post $post, string $template_path ): string { - if ( - empty( $template_path ) || - ! $this->types->is_post_type_applicable( $post->post_type ) || - ! $this->statuses->is_post_status_applicable( $post->post_status ) || - ! is_preview() - ) { - return ''; - } - - return file_exists( $template_path ) ? $template_path : ''; - } -} diff --git a/plugins/hwp-previews/src/Preview/Template_Resolver.php b/plugins/hwp-previews/src/Preview/Template_Resolver.php new file mode 100644 index 00000000..cf66aa4c --- /dev/null +++ b/plugins/hwp-previews/src/Preview/Template_Resolver.php @@ -0,0 +1,97 @@ +post = $post; + } + } + + public function is_allowed(): bool { + + if ( ! is_preview() ) { + return false; + } + + if ( ! $this->post instanceof WP_Post ) { + return false; + } + + // @TODO check + // if ( + // empty( $template_path ) || + // ! $this->types->is_post_type_applicable( $post->post_type ) || + // ! $this->statuses->is_post_status_applicable( $post->post_status ) || + // ! is_preview() + // ) { + // return ''; + // } + + $settings_helper = Settings_Helper::get_instance(); + if ( ! $settings_helper->in_iframe( $this->post->post_type ) ) { + return false; + } + + + return is_preview(); + } + + public function get_post(): ?WP_Post { + return $this->post; + } + + public function get_iframe_template(): string { + + /** + * The filter 'hwp_previews_template_path' allows to change the template directory path. + */ + $template_dir_path = (string) apply_filters( + 'hwp_previews_template_path', + trailingslashit( HWP_PREVIEWS_PLUGIN_DIR ) . 'src/Templates/iframe.php', + ); + + if ( ! file_exists( $template_dir_path ) ) { + return ''; + } + + return $template_dir_path; + } + + /** + * Set the query variable that contains the preview URL for the iframe. + * + * @param string $template_url + */ + public function set_query_variable( string $template_url ): void { + set_query_var( self::HWP_PREVIEWS_IFRAME_PREVIEW_URL, $template_url ); + } + + /** + * Get the query variable that contains the preview URL for the iframe. + */ + public static function get_query_variable(): string { + return (string) get_query_var( self::HWP_PREVIEWS_IFRAME_PREVIEW_URL ); + } +} diff --git a/plugins/hwp-previews/src/Preview/URL_Generator.php b/plugins/hwp-previews/src/Preview/URL_Generator.php new file mode 100644 index 00000000..d57fe85c --- /dev/null +++ b/plugins/hwp-previews/src/Preview/URL_Generator.php @@ -0,0 +1,10 @@ + diff --git a/plugins/hwp-previews/tests/wpunit.suite.dist.yml b/plugins/hwp-previews/tests/wpunit.suite.dist.yml index 5bcc7311..98ee4533 100644 --- a/plugins/hwp-previews/tests/wpunit.suite.dist.yml +++ b/plugins/hwp-previews/tests/wpunit.suite.dist.yml @@ -5,4 +5,4 @@ actor: WpunitTester modules: enabled: - lucatume\WPBrowser\Module\WPLoader -bootstrap: bootstrap.php \ No newline at end of file +bootstrap: bootstrap.php diff --git a/plugins/hwp-previews/tests/wpunit/Admin/Settings/Fields/Field/TextURLFieldTest.php b/plugins/hwp-previews/tests/wpunit/Admin/Settings/Fields/Field/TextURLFieldTest.php index fa28133c..c9abc7cc 100644 --- a/plugins/hwp-previews/tests/wpunit/Admin/Settings/Fields/Field/TextURLFieldTest.php +++ b/plugins/hwp-previews/tests/wpunit/Admin/Settings/Fields/Field/TextURLFieldTest.php @@ -173,4 +173,73 @@ public function test_get_setting_value_returns_value_when_set() { delete_option( $settings_key ); $this->assertEmpty( $field->get_setting_value( $settings_key, $post_type ) ); } + + public function test_fix_url_removes_html_and_scripts() { + $field = $this->field; + + // Simulate fix_url as a public/protected method for testing. + // If fix_url is private, use Reflection to access it. + $reflection = new \ReflectionClass($field); + $method = $reflection->getMethod('fix_url'); + $method->setAccessible(true); + + // Remove script tags + $input = 'https://example.com/?preview=true'; + $expected = 'https://example.com/?preview=true'; + $this->assertEquals($expected, $method->invoke($field, $input)); + + // Remove HTML tags + $input = 'Boldhttps://example.com/'; + $expected = 'http://Boldhttps://example.com/'; + $this->assertEquals($expected, $method->invoke($field, $input)); + + // Remove image tags + $input = 'test'; + $expected = ''; + $this->assertEquals($expected, $method->invoke($field, $input)); + } + + public function test_fix_url_handles_encoded_urls() { + $field = $this->field; + $reflection = new \ReflectionClass($field); + $method = $reflection->getMethod('fix_url'); + $method->setAccessible(true); + + $input = 'https://example.com/?q=%3Cscript%3Ealert(1)%3C%2Fscript%3E'; + $expected = 'https://example.com/?q=%3Cscript%3Ealert(1)%3C%2Fscript%3E'; + $this->assertEquals($expected, $method->invoke($field, $input)); + } + + public function test_fix_url_preserves_placeholders() { + $field = $this->field; + $reflection = new \ReflectionClass($field); + $method = $reflection->getMethod('fix_url'); + $method->setAccessible(true); + + $input = 'https://example.com/{slug}?preview=true&post_id={ID}'; + $expected = 'https://example.com/{slug}?preview=true&post_id={ID}'; + $this->assertEquals($expected, $method->invoke($field, $input)); + } + + public function test_fix_url_handles_empty_and_invalid_input() { + $field = $this->field; + $reflection = new \ReflectionClass($field); + $method = $reflection->getMethod('fix_url'); + $method->setAccessible(true); + + $this->assertEquals('', $method->invoke($field, '')); + $this->assertEquals('', $method->invoke($field, '
')); + } + + public function test_fix_url_handles_relative_urls() { + $field = $this->field; + $reflection = new \ReflectionClass($field); + $method = $reflection->getMethod('fix_url'); + $method->setAccessible(true); + + $input = '/relative/path?foo=bar'; + $protocol = is_ssl() ? 'https://' : 'http://'; + $expected = $protocol . 'relative/path?foo=bar'; + $this->assertEquals($expected, $method->invoke($field, $input)); + } } diff --git a/plugins/hwp-previews/tests/wpunit/Admin/SettingsPageTest.php b/plugins/hwp-previews/tests/wpunit/Admin/SettingsPageTest.php new file mode 100644 index 00000000..be6db733 --- /dev/null +++ b/plugins/hwp-previews/tests/wpunit/Admin/SettingsPageTest.php @@ -0,0 +1,279 @@ +admin_user_id = $this->factory()->user->create([ + 'role' => 'administrator' + ]); + wp_set_current_user($this->admin_user_id); + + // Reset the singleton instance + $reflection = new \ReflectionClass(Settings_Page::class); + $instance_property = $reflection->getProperty('instance'); + $instance_property->setAccessible(true); + $instance_property->setValue(null, null); + + // Mock dependencies + $this->setupMocks(); + } + + public function tearDown(): void + { + // Clean up user + wp_delete_user($this->admin_user_id); + wp_set_current_user(0); + + // Reset singleton + $reflection = new \ReflectionClass(Settings_Page::class); + $instance_property = $reflection->getProperty('instance'); + $instance_property->setAccessible(true); + $instance_property->setValue(null, null); + + // Clear $_GET + $_GET = []; + + parent::tearDown(); + } + + private function setupMocks(): void + { + // Mock Preview_Parameter_Registry + $this->mock_parameter_registry = $this->createMock(Preview_Parameter_Registry::class); + $this->mock_parameter_registry->method('get_descriptions') + ->willReturn([ + 'param1' => 'Description 1', + 'param2' => 'Description 2' + ]); + + // Mock Post_Types_Config_Interface + $this->mock_post_types_config = $this->createMock(Post_Types_Config_Interface::class); + $this->mock_post_types_config->method('get_public_post_types') + ->willReturn([ + 'post' => 'Posts', + 'page' => 'Pages', + 'product' => 'Products' + ]); + } + + public function test_singleton_init_creates_instance() + { + // Use reflection to mock static method calls + $this->mockStaticDependencies(); + + $instance1 = Settings_Page::init(); + $instance2 = Settings_Page::init(); + + $this->assertInstanceOf(Settings_Page::class, $instance1); + $this->assertSame($instance1, $instance2, 'Should return the same instance (singleton)'); + } + + public function test_singleton_init_fires_action() + { + $this->mockStaticDependencies(); + + $action_fired = false; + add_action('hwp_previews_init', function($instance) use (&$action_fired) { + $action_fired = true; + $this->assertInstanceOf(Settings_Page::class, $instance); + }); + + Settings_Page::init(); + + $this->assertTrue($action_fired, 'hwp_previews_init action should be fired'); + } + + public function test_constructor_initializes_dependencies() + { + $this->mockStaticDependencies(); + + $settings_page = new Settings_Page(); + + // Use reflection to check protected properties + $reflection = new \ReflectionClass($settings_page); + + $parameters_prop = $reflection->getProperty('parameters'); + $parameters_prop->setAccessible(true); + $this->assertInstanceOf(Preview_Parameter_Registry::class, $parameters_prop->getValue($settings_page)); + + $types_config_prop = $reflection->getProperty('types_config'); + $types_config_prop->setAccessible(true); + $this->assertInstanceOf(Post_Types_Config_Interface::class, $types_config_prop->getValue($settings_page)); + } + + public function test_register_settings_pages_adds_admin_menu_action() + { + $this->mockStaticDependencies(); + + $settings_page = new Settings_Page(); + + // Check that admin_menu action was added + $this->assertTrue(has_action('admin_menu') !== false, 'admin_menu action should be registered'); + } + + public function test_register_settings_fields_adds_admin_init_action() + { + $this->mockStaticDependencies(); + + $settings_page = new Settings_Page(); + + // Check that admin_init action was added + $this->assertTrue(has_action('admin_init') !== false, 'admin_init action should be registered'); + } + + public function test_load_scripts_styles_adds_admin_enqueue_scripts_action() + { + $this->mockStaticDependencies(); + + $settings_page = new Settings_Page(); + + // Check that admin_enqueue_scripts action was added + $this->assertTrue(has_action('admin_enqueue_scripts') !== false, 'admin_enqueue_scripts action should be registered'); + } + + public function test_get_current_tab_returns_first_tab_when_no_get_param() + { + $this->mockStaticDependencies(); + $settings_page = new Settings_Page(); + + $post_types = [ + 'post' => 'Posts', + 'page' => 'Pages', + 'product' => 'Products' + ]; + + $current_tab = $settings_page->get_current_tab($post_types); + + $this->assertEquals('post', $current_tab, 'Should return first post type key when no GET parameter'); + } + + public function test_get_current_tab_returns_sanitized_get_param() + { + $this->mockStaticDependencies(); + $settings_page = new Settings_Page(); + + $post_types = [ + 'post' => 'Posts', + 'page' => 'Pages', + 'product' => 'Products' + ]; + + $_GET['tab'] = 'page'; + $current_tab = $settings_page->get_current_tab($post_types); + + $this->assertEquals('page', $current_tab, 'Should return sanitized GET parameter value'); + } + + public function test_get_current_tab_with_custom_tab_param_name() + { + $this->mockStaticDependencies(); + $settings_page = new Settings_Page(); + + $post_types = [ + 'post' => 'Posts', + 'page' => 'Pages' + ]; + + $_GET['custom_tab'] = 'page'; + $current_tab = $settings_page->get_current_tab($post_types, 'custom_tab'); + + $this->assertEquals('page', $current_tab, 'Should use custom tab parameter name'); + } + + public function test_get_current_tab_sanitizes_malicious_input() + { + $this->mockStaticDependencies(); + $settings_page = new Settings_Page(); + + $post_types = [ + 'post' => 'Posts', + 'page' => 'Pages' + ]; + + $_GET['tab'] = ''; + $current_tab = $settings_page->get_current_tab($post_types); + + $this->assertEquals('scriptalertxssscript', $current_tab, 'Should sanitize malicious input using sanitize_key'); + } + + public function test_get_current_tab_returns_empty_string_for_empty_post_types() + { + $this->mockStaticDependencies(); + $settings_page = new Settings_Page(); + + $current_tab = $settings_page->get_current_tab([]); + + $this->assertEquals('', $current_tab, 'Should return empty string for empty post types array'); + } + + public function test_get_current_tab_handles_non_string_get_value() + { + $this->mockStaticDependencies(); + $settings_page = new Settings_Page(); + + $post_types = [ + 'post' => 'Posts', + 'page' => 'Pages' + ]; + + $_GET['tab'] = ['array_value']; + $current_tab = $settings_page->get_current_tab($post_types); + + $this->assertEquals('post', $current_tab, 'Should return first post type when GET value is not a string'); + } + + public function test_plugin_menu_slug_constant() + { + $this->assertEquals('hwp-previews', Settings_Page::PLUGIN_MENU_SLUG, 'Plugin menu slug should be correct'); + } + + private function mockStaticDependencies(): void + { + // Mock Preview_Parameter_Registry::get_instance() + if (!function_exists('HWP\Previews\wpunit\Admin\mock_preview_parameter_registry_get_instance')) { + function mock_preview_parameter_registry_get_instance() { + $mock = $this->createMock(Preview_Parameter_Registry::class); + $mock->method('get_descriptions')->willReturn([ + 'param1' => 'Description 1', + 'param2' => 'Description 2' + ]); + return $mock; + } + } + + // Mock Post_Types_Config_Registry::get_post_type_config() + if (!function_exists('HWP\Previews\wpunit\Admin\mock_post_types_config_registry_get_post_type_config')) { + function mock_post_types_config_registry_get_post_type_config() { + $mock = $this->createMock(Post_Types_Config_Interface::class); + $mock->method('get_public_post_types')->willReturn([ + 'post' => 'Posts', + 'page' => 'Pages', + 'product' => 'Products' + ]); + return $mock; + } + } + + // Override the static method calls in the constructor + // This would require modifying the original class or using a mocking framework + // For now, we'll assume the dependencies are properly injected + } +} From b420c0fd0c8a8f84db81eb8f8878a11fba423475 Mon Sep 17 00:00:00 2001 From: Colin Murphy Date: Mon, 16 Jun 2025 20:44:12 +0100 Subject: [PATCH 03/35] Refactor of Template Resolver and added test. --- .github/actions/codeception/action.yml | 30 -- plugins/hwp-previews/TESTING.md | 6 - plugins/hwp-previews/composer.json | 1 + .../Settings/Fields/Field/URL_Input_Field.php | 4 + .../hwp-previews/src/Admin/Settings_Page.php | 2 +- .../hwp-previews/src/Hooks/Preview_Hooks.php | 33 +- .../src/Preview/Template_Resolver.php | 78 ++-- .../src/Preview/URL_Generator.php | 10 - plugins/hwp-previews/src/Templates/iframe.php | 1 - .../tests/acceptance.suite.dist.yml | 23 -- .../hwp-previews/tests/acceptance/.gitkeep | 0 .../tests/functional.suite.dist.yml | 20 - .../hwp-previews/tests/functional/.gitkeep | 0 .../hwp-previews/tests/unit.suite.dist.yml | 8 - .../wpunit/Preview/TemplateResolverTest.php | 362 ++++++++++++++++++ 15 files changed, 438 insertions(+), 140 deletions(-) delete mode 100644 plugins/hwp-previews/src/Preview/URL_Generator.php delete mode 100644 plugins/hwp-previews/tests/acceptance.suite.dist.yml delete mode 100644 plugins/hwp-previews/tests/acceptance/.gitkeep delete mode 100644 plugins/hwp-previews/tests/functional.suite.dist.yml delete mode 100644 plugins/hwp-previews/tests/functional/.gitkeep delete mode 100644 plugins/hwp-previews/tests/unit.suite.dist.yml create mode 100644 plugins/hwp-previews/tests/wpunit/Preview/TemplateResolverTest.php diff --git a/.github/actions/codeception/action.yml b/.github/actions/codeception/action.yml index ff7ecc8d..f509b670 100644 --- a/.github/actions/codeception/action.yml +++ b/.github/actions/codeception/action.yml @@ -67,36 +67,6 @@ runs: WP_VERSION: ${{ inputs.wordpress }} PHP_VERSION: ${{ inputs.php }} - - name: Run Acceptance Tests w/ Docker - working-directory: ${{ inputs.working-directory }} - shell: bash - run: | - docker exec \ - --env DEBUG=${{ env.DEBUG }} \ - --env SKIP_TESTS_CLEANUP=${{ env.SKIP_TESTS_CLEANUP }} \ - --env SUITES=acceptance \ - $(docker compose ps -q wordpress) \ - bash -c "cd wp-content/plugins/$(basename $(echo ${{ inputs.working-directory }} | sed 's:/*$::')) && bin/run-codeception.sh" - env: - DEBUG: ${{ env.ACTIONS_STEP_DEBUG }} - SKIP_TESTS_CLEANUP: "true" - continue-on-error: true - - - name: Run Functional Tests w/ Docker - working-directory: ${{ inputs.working-directory }} - shell: bash - run: | - docker exec \ - --env DEBUG=${{ env.DEBUG }} \ - --env SKIP_TESTS_CLEANUP=${{ env.SKIP_TESTS_CLEANUP }} \ - --env SUITES=functional \ - $(docker compose ps -q wordpress) \ - bash -c "cd wp-content/plugins/$(basename ${{ inputs.working-directory }}) && bin/run-codeception.sh" - env: - DEBUG: ${{ env.ACTIONS_STEP_DEBUG }} - SKIP_TESTS_CLEANUP: "true" - continue-on-error: true - - name: Run WPUnit Tests w/ Docker working-directory: ${{ inputs.working-directory }} shell: bash diff --git a/plugins/hwp-previews/TESTING.md b/plugins/hwp-previews/TESTING.md index 73cac54b..3ac32f38 100644 --- a/plugins/hwp-previews/TESTING.md +++ b/plugins/hwp-previews/TESTING.md @@ -128,13 +128,7 @@ tests/ ├── _envs/ # Environment configs ├── _output/ # Test output (logs, coverage) ├── _support/ # Helper classes, modules -├── acceptance/ # Acceptance test cases -├── functional/ # Functional test cases -├── unit/ # Unit test cases ├── wpunit/ # WPUnit (WordPress-aware unit/integration) test cases -├── acceptance.suite.dist.yml -├── functional.suite.dist.yml -├── unit.suite.dist.yml ├── wpunit.suite.dist.yml └── wpunit/ └── bootstrap.php # Bootstrap for WPUnit tests diff --git a/plugins/hwp-previews/composer.json b/plugins/hwp-previews/composer.json index 394069a4..42eeafaf 100644 --- a/plugins/hwp-previews/composer.json +++ b/plugins/hwp-previews/composer.json @@ -129,6 +129,7 @@ "php:psalm": "psalm", "php:psalm:info": "psalm --show-info=true", "php:psalm:fix": "psalm --alter", + "qa": "sh bin/local/run-qa.sh", "test": [ "sh bin/local/run-unit-tests.sh", "sh bin/local/run-e2e-tests.sh" diff --git a/plugins/hwp-previews/src/Admin/Settings/Fields/Field/URL_Input_Field.php b/plugins/hwp-previews/src/Admin/Settings/Fields/Field/URL_Input_Field.php index ef700e9e..109ec11b 100644 --- a/plugins/hwp-previews/src/Admin/Settings/Fields/Field/URL_Input_Field.php +++ b/plugins/hwp-previews/src/Admin/Settings/Fields/Field/URL_Input_Field.php @@ -52,6 +52,10 @@ private function fix_url( string $value ): string { // Remove #is', '', $value ); + if ( ! is_string( $value ) || '' === trim( $value ) ) { + return ''; + } + // Remove HTML tags except curly braces, trim, encode spaces, add protocol. $value = preg_replace( '/<(?!\{)[^>]+>/', '', $value ); $value = trim( str_replace( ' ', '%20', (string) $value ) ); diff --git a/plugins/hwp-previews/src/Admin/Settings_Page.php b/plugins/hwp-previews/src/Admin/Settings_Page.php index 67165c6a..63ac1bd2 100644 --- a/plugins/hwp-previews/src/Admin/Settings_Page.php +++ b/plugins/hwp-previews/src/Admin/Settings_Page.php @@ -80,7 +80,7 @@ public function register_settings_pages(): void { __( 'HWP Previews Settings', 'hwp-previews' ), 'HWP Previews', self::PLUGIN_MENU_SLUG, - trailingslashit(HWP_PREVIEWS_PLUGIN_DIR) . 'src/Templates/admin.php', + trailingslashit( HWP_PREVIEWS_PLUGIN_DIR ) . 'src/Templates/admin.php', [ 'hwp_previews_main_page_config' => [ 'tabs' => $post_types, diff --git a/plugins/hwp-previews/src/Hooks/Preview_Hooks.php b/plugins/hwp-previews/src/Hooks/Preview_Hooks.php index 8c70156b..0b6a6d6e 100644 --- a/plugins/hwp-previews/src/Hooks/Preview_Hooks.php +++ b/plugins/hwp-previews/src/Hooks/Preview_Hooks.php @@ -1,6 +1,6 @@ settings_helper = Settings_Helper::get_instance(); $this->types_config = apply_filters( @@ -190,8 +193,25 @@ public function filter_rest_prepare_link( WP_REST_Response $response, WP_Post $p */ public function add_iframe_preview_template( string $template ): string { - $template_resolver = new Template_Resolver(); + if ( ! is_preview() ) { + return $template; + } + $post = get_post(); + if ( ! is_object( $post ) || ! ( $post instanceof WP_Post ) ) { + return $template; + } + + // @TODO - Refactor as should be part of the main preview settings. + $settings_helper = Settings_Helper::get_instance(); + if ( ! $settings_helper->in_iframe( $post->post_type ) ) { + return $template; + } + + // @TODO - Refactor as part of types and post statuses config refactor. + $post_types = $this->types_config->get_post_types(); + $post_statuses = $this->statuses_config->get_post_statuses(); + $template_resolver = new Template_Resolver( $post, $post_types, $post_statuses ); if ( ! $template_resolver->is_allowed() ) { return $template; @@ -202,13 +222,8 @@ public function add_iframe_preview_template( string $template ): string { return $template; } - // @TODO remove - // @TODO how do we get this URL with Preview_URL_Generator? - $post = $template_resolver->get_post(); - set_query_var( $template_resolver::HWP_PREVIEWS_IFRAME_PREVIEW_URL, self::generate_preview_url( $post ) ); - - // @TODO - Add back in. - // $template_resolver->set_query_variable( $iframe_template ); + // @TODO - Refactor this as part of URL generation refactor. + $template_resolver->set_query_variable( self::generate_preview_url( $post ) ); return $iframe_template; } diff --git a/plugins/hwp-previews/src/Preview/Template_Resolver.php b/plugins/hwp-previews/src/Preview/Template_Resolver.php index cf66aa4c..aa1d0497 100644 --- a/plugins/hwp-previews/src/Preview/Template_Resolver.php +++ b/plugins/hwp-previews/src/Preview/Template_Resolver.php @@ -4,7 +4,6 @@ namespace HWP\Previews\Preview; -use HWP\Previews\Preview\Helper\Settings_Helper; use WP_Post; class Template_Resolver { @@ -18,54 +17,69 @@ class Template_Resolver { /** * The current post object. * - * @var \WP_Post|null + * @var \WP_Post */ - protected ?WP_Post $post = null; + protected WP_Post $post; - public function __construct() { - $post = get_post(); - if ( $post instanceof WP_Post ) { - $this->post = $post; - } + /** + * Allowed post types for preview links. + * + * @var array + */ + private array $post_types = []; + + /** + * Allowed post statuses for preview links. + * + * @var array + */ + private array $post_statuses = []; + + /** + * Constructor. + * + * @param \WP_Post $post The post object. + * @param array $post_types Post types that are applicable for preview links. + * @param array $post_statuses Post statuses that are applicable for preview links. + */ + public function __construct( WP_Post $post, array $post_types = [], array $post_statuses = [] ) { + $this->post = $post; + $this->post_types = $post_types; + $this->post_statuses = $post_statuses; } + /** + * Check if the current post is allowed for preview in an iframe. + * + * @return bool True if the post is allowed, false otherwise. + */ public function is_allowed(): bool { - if ( ! is_preview() ) { + $post_type = $this->post->post_type; + $post_types = $this->post_types; + if ( ! in_array( $post_type, $post_types, true ) ) { return false; } - if ( ! $this->post instanceof WP_Post ) { - return false; - } + $post_status = $this->post->post_status; + $post_statuses = $this->post_statuses; - // @TODO check - // if ( - // empty( $template_path ) || - // ! $this->types->is_post_type_applicable( $post->post_type ) || - // ! $this->statuses->is_post_status_applicable( $post->post_status ) || - // ! is_preview() - // ) { - // return ''; - // } - - $settings_helper = Settings_Helper::get_instance(); - if ( ! $settings_helper->in_iframe( $this->post->post_type ) ) { + if ( ! in_array( $post_status, $post_statuses, true ) ) { return false; } - - return is_preview(); + return true; } - public function get_post(): ?WP_Post { - return $this->post; - } - - public function get_iframe_template(): string { + /** + * Get the path to the iframe template. + * + * @return string|null The path to the iframe template, or null if it does not exist. + */ + public function get_iframe_template(): ?string { /** - * The filter 'hwp_previews_template_path' allows to change the template directory path. + * The filter 'hwp_previews_template_path' allows changing the template file path. */ $template_dir_path = (string) apply_filters( 'hwp_previews_template_path', diff --git a/plugins/hwp-previews/src/Preview/URL_Generator.php b/plugins/hwp-previews/src/Preview/URL_Generator.php deleted file mode 100644 index d57fe85c..00000000 --- a/plugins/hwp-previews/src/Preview/URL_Generator.php +++ /dev/null @@ -1,10 +0,0 @@ - diff --git a/plugins/hwp-previews/tests/acceptance.suite.dist.yml b/plugins/hwp-previews/tests/acceptance.suite.dist.yml deleted file mode 100644 index acda1956..00000000 --- a/plugins/hwp-previews/tests/acceptance.suite.dist.yml +++ /dev/null @@ -1,23 +0,0 @@ -# Codeception Test Suite Configuration -# -# Suite for acceptance tests. -# Perform tests in browser using the WPWebDriver or WPBrowser. -# Use WPDb to set up your initial database fixture. -# If you need both WPWebDriver and WPBrowser tests - create a separate suite. - -actor: AcceptanceTester -modules: - enabled: - - Asserts - - lucatume\WPBrowser\Module\WPBrowser - - lucatume\WPBrowser\Module\WPDb - - lucatume\WPBrowser\Module\WPFilesystem - - lucatume\WPBrowser\Module\WPLoader - - REST - config: - lucatume\WPBrowser\Module\WPLoader: - loadOnly: true - plugins: - - hwp-previews/hwp-previews.php - activatePlugins: - - hwp-previews/hwp-previews.php diff --git a/plugins/hwp-previews/tests/acceptance/.gitkeep b/plugins/hwp-previews/tests/acceptance/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/plugins/hwp-previews/tests/functional.suite.dist.yml b/plugins/hwp-previews/tests/functional.suite.dist.yml deleted file mode 100644 index b27e03a2..00000000 --- a/plugins/hwp-previews/tests/functional.suite.dist.yml +++ /dev/null @@ -1,20 +0,0 @@ -# Codeception Test Suite Configuration -# -# Suite for functional tests -# Emulate web requests and make WordPress process them - -actor: FunctionalTester -modules: - enabled: - - Asserts - - lucatume\WPBrowser\Module\WPBrowser - - lucatume\WPBrowser\Module\WPDb - - lucatume\WPBrowser\Module\WPLoader - - REST - config: - lucatume\WPBrowser\Module\WPLoader: - loadOnly: true - plugins: - - hwp-previews/hwp-previews.php - activatePlugins: - - hwp-previews/hwp-previews.php diff --git a/plugins/hwp-previews/tests/functional/.gitkeep b/plugins/hwp-previews/tests/functional/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/plugins/hwp-previews/tests/unit.suite.dist.yml b/plugins/hwp-previews/tests/unit.suite.dist.yml deleted file mode 100644 index 07a7166d..00000000 --- a/plugins/hwp-previews/tests/unit.suite.dist.yml +++ /dev/null @@ -1,8 +0,0 @@ -# Codeception Test Suite Configuration -# -# Suite for unit (internal) tests. - -class_name: UnitTester -modules: - enabled: - - Asserts \ No newline at end of file diff --git a/plugins/hwp-previews/tests/wpunit/Preview/TemplateResolverTest.php b/plugins/hwp-previews/tests/wpunit/Preview/TemplateResolverTest.php new file mode 100644 index 00000000..327df3c9 --- /dev/null +++ b/plugins/hwp-previews/tests/wpunit/Preview/TemplateResolverTest.php @@ -0,0 +1,362 @@ +factory()->post->create([ + 'post_title' => 'Test Post', + 'post_type' => 'post', + 'post_status' => 'publish' + ]); + + $this->test_post = get_post($post_id); + } + + + /** + * Clean up after each test. + */ + public function tearDown(): void { + // Clean up query vars + set_query_var(Template_Resolver::HWP_PREVIEWS_IFRAME_PREVIEW_URL, ''); + + // Remove any filters that might have been added + remove_all_filters('hwp_previews_template_path'); + + parent::tearDown(); + } + + /** + * Test constructor sets properties correctly. + */ + public function test_constructor_sets_properties_correctly(): void { + $post_types = ['post', 'page']; + $post_statuses = ['publish', 'draft']; + + $resolver = new Template_Resolver($this->test_post, $post_types, $post_statuses); + + // Use reflection to access private properties for testing + $reflection = new \ReflectionClass($resolver); + + $post_property = $reflection->getProperty('post'); + $post_property->setAccessible(true); + $this->assertEquals($this->test_post, $post_property->getValue($resolver)); + + $post_types_property = $reflection->getProperty('post_types'); + $post_types_property->setAccessible(true); + $this->assertEquals($post_types, $post_types_property->getValue($resolver)); + + $post_statuses_property = $reflection->getProperty('post_statuses'); + $post_statuses_property->setAccessible(true); + $this->assertEquals($post_statuses, $post_statuses_property->getValue($resolver)); + } + + /** + * Test constructor with default empty arrays. + */ + public function test_constructor_with_default_arrays(): void { + $resolver = new Template_Resolver($this->test_post); + + $reflection = new \ReflectionClass($resolver); + + $post_types_property = $reflection->getProperty('post_types'); + $post_types_property->setAccessible(true); + $this->assertEquals([], $post_types_property->getValue($resolver)); + + $post_statuses_property = $reflection->getProperty('post_statuses'); + $post_statuses_property->setAccessible(true); + $this->assertEquals([], $post_statuses_property->getValue($resolver)); + } + + /** + * Test is_allowed returns true when post type and status are allowed. + */ + public function test_is_allowed_returns_true_when_post_is_allowed(): void { + $post_types = ['post', 'page']; + $post_statuses = ['publish', 'draft']; + + $resolver = new Template_Resolver($this->test_post, $post_types, $post_statuses); + + $this->assertTrue($resolver->is_allowed()); + } + + /** + * Test is_allowed returns false when post type is not allowed. + */ + public function test_is_allowed_returns_false_when_post_type_not_allowed(): void { + $post_types = ['page', 'custom_post_type']; // 'post' not included + $post_statuses = ['publish', 'draft']; + + $resolver = new Template_Resolver($this->test_post, $post_types, $post_statuses); + + $this->assertFalse($resolver->is_allowed()); + } + + /** + * Test is_allowed returns false when post status is not allowed. + */ + public function test_is_allowed_returns_false_when_post_status_not_allowed(): void { + $post_types = ['post', 'page']; + $post_statuses = ['draft', 'private']; // 'publish' not included + + $resolver = new Template_Resolver($this->test_post, $post_types, $post_statuses); + + $this->assertFalse($resolver->is_allowed()); + } + + /** + * Test is_allowed returns false when both arrays are empty. + */ + public function test_is_allowed_returns_false_when_arrays_are_empty(): void { + $resolver = new Template_Resolver($this->test_post, [], []); + + $this->assertFalse($resolver->is_allowed()); + } + + /** + * Test is_allowed with custom post type and status. + */ + public function test_is_allowed_with_custom_post_type_and_status(): void { + // Create a custom post with specific type and status + $custom_post_id = $this->factory->post->create([ + 'post_type' => 'custom_type', + 'post_status' => 'custom_status' + ]); + $custom_post = get_post($custom_post_id); + + $post_types = ['custom_type']; + $post_statuses = ['custom_status']; + + $resolver = new Template_Resolver($custom_post, $post_types, $post_statuses); + + $this->assertTrue($resolver->is_allowed()); + } + + /** + * Test get_iframe_template returns template path when file exists. + */ + public function test_get_iframe_template_returns_path_when_file_exists(): void { + $resolver = new Template_Resolver($this->test_post); + + // Mock the filter to return a path to an existing file + $existing_file = tempnam(sys_get_temp_dir(), 'iframe_template'); + file_put_contents($existing_file, 'get_iframe_template(); + + $this->assertEquals($existing_file, $result); + + // Cleanup + unlink($existing_file); + remove_all_filters('hwp_previews_template_path'); + } + + /** + * Test get_iframe_template returns empty string when file does not exist. + */ + public function test_get_iframe_template_returns_empty_string_when_file_not_exists(): void { + $resolver = new Template_Resolver($this->test_post); + + // Mock the filter to return a non-existent file path + $non_existent_file = '/path/that/does/not/exist/iframe.php'; + + add_filter('hwp_previews_template_path', function() use ($non_existent_file) { + return $non_existent_file; + }); + + $result = $resolver->get_iframe_template(); + + $this->assertEquals('', $result); + + // Cleanup + remove_all_filters('hwp_previews_template_path'); + } + + /** + * Test get_iframe_template applies the hwp_previews_template_path filter. + */ + public function test_get_iframe_template_applies_filter(): void { + $resolver = new Template_Resolver($this->test_post); + + $custom_path = '/custom/template/path.php'; + $filter_called = false; + + add_filter('hwp_previews_template_path', function($path) use ($custom_path, &$filter_called) { + $filter_called = true; + return $custom_path; + }); + + // Call the method (will return empty string since file doesn't exist) + $resolver->get_iframe_template(); + + $this->assertTrue($filter_called); + + // Cleanup + remove_all_filters('hwp_previews_template_path'); + } + + /** + * Test set_query_variable sets the query variable correctly. + */ + public function test_set_query_variable_sets_query_var(): void { + $resolver = new Template_Resolver($this->test_post); + $test_url = 'https://example.com/preview'; + + $resolver->set_query_variable($test_url); + + $this->assertEquals($test_url, get_query_var(Template_Resolver::HWP_PREVIEWS_IFRAME_PREVIEW_URL)); + } + + /** + * Test set_query_variable with empty string. + */ + public function test_set_query_variable_with_empty_string(): void { + $resolver = new Template_Resolver($this->test_post); + + $resolver->set_query_variable(''); + + $this->assertEquals('', get_query_var(Template_Resolver::HWP_PREVIEWS_IFRAME_PREVIEW_URL)); + } + + /** + * Test get_query_variable returns the correct value. + */ + public function test_get_query_variable_returns_correct_value(): void { + $test_url = 'https://example.com/preview'; + + set_query_var(Template_Resolver::HWP_PREVIEWS_IFRAME_PREVIEW_URL, $test_url); + + $result = Template_Resolver::get_query_variable(); + + $this->assertEquals($test_url, $result); + } + + /** + * Test get_query_variable returns empty string when not set. + */ + public function test_get_query_variable_returns_empty_string_when_not_set(): void { + // Ensure the query var is not set + set_query_var(Template_Resolver::HWP_PREVIEWS_IFRAME_PREVIEW_URL, ''); + + $result = Template_Resolver::get_query_variable(); + + $this->assertEquals('', $result); + } + + /** + * Test get_query_variable is static and works without instance. + */ + public function test_get_query_variable_is_static(): void { + $test_url = 'https://example.com/static-test'; + + set_query_var(Template_Resolver::HWP_PREVIEWS_IFRAME_PREVIEW_URL, $test_url); + + // Call static method without creating an instance + $result = Template_Resolver::get_query_variable(); + + $this->assertEquals($test_url, $result); + } + + /** + * Test constant is defined correctly. + */ + public function test_constant_is_defined_correctly(): void { + $this->assertEquals('hwp_previews_iframe_preview_url', Template_Resolver::HWP_PREVIEWS_IFRAME_PREVIEW_URL); + } + + /** + * Test integration: set and get query variable using the same constant. + */ + public function test_set_and_get_query_variable_integration(): void { + $resolver = new Template_Resolver($this->test_post); + $test_url = 'https://example.com/integration-test'; + + $resolver->set_query_variable($test_url); + $retrieved_url = Template_Resolver::get_query_variable(); + + $this->assertEquals($test_url, $retrieved_url); + } + + /** + * Test is_allowed with different post objects. + */ + public function test_is_allowed_with_different_post_objects(): void { + // Create posts with different types and statuses + $page_id = $this->factory->post->create([ + 'post_type' => 'page', + 'post_status' => 'draft' + ]); + $page_post = get_post($page_id); + + $post_types = ['page']; + $post_statuses = ['draft']; + + $resolver = new Template_Resolver($page_post, $post_types, $post_statuses); + + $this->assertTrue($resolver->is_allowed()); + } + + /** + * Test type safety - ensure post_types array contains only strings. + */ + public function test_type_safety_post_types(): void { + $post_types = ['post', 'page', 'custom_type']; + + $resolver = new Template_Resolver($this->test_post, $post_types); + + $reflection = new \ReflectionClass($resolver); + $post_types_property = $reflection->getProperty('post_types'); + $post_types_property->setAccessible(true); + + $stored_types = $post_types_property->getValue($resolver); + + foreach ($stored_types as $type) { + $this->assertIsString($type); + } + } + + /** + * Test type safety - ensure post_statuses array contains only strings. + */ + public function test_type_safety_post_statuses(): void { + $post_statuses = ['publish', 'draft', 'private']; + + $resolver = new Template_Resolver($this->test_post, [], $post_statuses); + + $reflection = new \ReflectionClass($resolver); + $post_statuses_property = $reflection->getProperty('post_statuses'); + $post_statuses_property->setAccessible(true); + + $stored_statuses = $post_statuses_property->getValue($resolver); + + foreach ($stored_statuses as $status) { + $this->assertIsString($status); + } + } +} From 2de7fb1f615dd2063c2d5fa93adcf5747e5727cd Mon Sep 17 00:00:00 2001 From: Colin Murphy Date: Mon, 16 Jun 2025 20:47:15 +0100 Subject: [PATCH 04/35] Code Quality fix. --- plugins/hwp-previews/src/Preview/Template_Resolver.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/plugins/hwp-previews/src/Preview/Template_Resolver.php b/plugins/hwp-previews/src/Preview/Template_Resolver.php index aa1d0497..7a8d5572 100644 --- a/plugins/hwp-previews/src/Preview/Template_Resolver.php +++ b/plugins/hwp-previews/src/Preview/Template_Resolver.php @@ -64,11 +64,7 @@ public function is_allowed(): bool { $post_status = $this->post->post_status; $post_statuses = $this->post_statuses; - if ( ! in_array( $post_status, $post_statuses, true ) ) { - return false; - } - - return true; + return in_array( $post_status, $post_statuses, true ); } /** From f5c59668ebae53d2193ebda6a3da93e82f0fe23f Mon Sep 17 00:00:00 2001 From: Colin Murphy Date: Tue, 17 Jun 2025 18:49:46 +0100 Subject: [PATCH 05/35] Refactoring. Add some new services to consolidate some of the existing functionality. --- plugins/hwp-previews/ACTIONS_AND_FILTERS.md | 2 +- .../hwp-previews/src/Hooks/Preview_Hooks.php | 27 +- .../src/Preview/Helper/Settings_Helper.php | 11 +- .../Preview/Service/Post_Preview_Service.php | 88 +++++ .../Preview/Service/Post_Settings_Service.php | 63 +++ .../src/Preview/Service/Post_Type_Service.php | 96 +++++ .../Service/Template_Resolver_Service.php | 52 +++ .../src/Preview/Template_Resolver.php | 107 ------ plugins/hwp-previews/src/Templates/iframe.php | 2 +- .../Service/PostPreviewServiceTest.php | 96 +++++ .../Service/PostServiceSettingsTest.php | 198 ++++++++++ .../Preview/Service/PostTypeServiceTest.php | 209 ++++++++++ .../Service/TemplateResolverServiceTest.php | 174 +++++++++ .../wpunit/Preview/TemplateResolverTest.php | 362 ------------------ 14 files changed, 992 insertions(+), 495 deletions(-) create mode 100644 plugins/hwp-previews/src/Preview/Service/Post_Preview_Service.php create mode 100644 plugins/hwp-previews/src/Preview/Service/Post_Settings_Service.php create mode 100644 plugins/hwp-previews/src/Preview/Service/Post_Type_Service.php create mode 100644 plugins/hwp-previews/src/Preview/Service/Template_Resolver_Service.php delete mode 100644 plugins/hwp-previews/src/Preview/Template_Resolver.php create mode 100644 plugins/hwp-previews/tests/wpunit/Preview/Service/PostPreviewServiceTest.php create mode 100644 plugins/hwp-previews/tests/wpunit/Preview/Service/PostServiceSettingsTest.php create mode 100644 plugins/hwp-previews/tests/wpunit/Preview/Service/PostTypeServiceTest.php create mode 100644 plugins/hwp-previews/tests/wpunit/Preview/Service/TemplateResolverServiceTest.php delete mode 100644 plugins/hwp-previews/tests/wpunit/Preview/TemplateResolverTest.php diff --git a/plugins/hwp-previews/ACTIONS_AND_FILTERS.md b/plugins/hwp-previews/ACTIONS_AND_FILTERS.md index 4318bb03..66647764 100644 --- a/plugins/hwp-previews/ACTIONS_AND_FILTERS.md +++ b/plugins/hwp-previews/ACTIONS_AND_FILTERS.md @@ -12,6 +12,7 @@ - `hwp_previews_template_path` - To use our own template for iframe previews - `hwp_previews_core` - Register or unregister URL parameters, and adjust types/statuses - `hwp_previews_filter_available_post_types` - Filter to modify the available post types for Previews. +- `hwp_previews_filter_available_post_statuses` - Filter for post statuses for previews for Previews - `hwp_previews_settings_group_option_key` - Filter to modify the settings group option key. Default is HWP_PREVIEWS_SETTINGS_KEY - `hwp_previews_settings_group_settings_group` - Filter to modify the settings group name. Default is HWP_PREVIEWS_SETTINGS_GROUP - `hwp_previews_settings_group_settings_config` - Filter to modify the settings array. See `Settings_Group` @@ -20,7 +21,6 @@ - `hwp_previews_hooks_post_type_config` - Filter for post type config service for the Hook class - `hwp_previews_hooks_post_status_config` - Filter for post status config service for the Hook class - `hwp_previews_hooks_preview_link_service` - Filter for preview link service for the Hook class -- `hwp_previews_hooks_post_statuses` - Filter for post statuses for previews for the Hook Class - `hwp_previews_settings_fields` - Allows a user to register, modify, or remove settings fields for the settings page ## Usage Examples diff --git a/plugins/hwp-previews/src/Hooks/Preview_Hooks.php b/plugins/hwp-previews/src/Hooks/Preview_Hooks.php index 0b6a6d6e..d2f96205 100644 --- a/plugins/hwp-previews/src/Hooks/Preview_Hooks.php +++ b/plugins/hwp-previews/src/Hooks/Preview_Hooks.php @@ -13,7 +13,10 @@ use HWP\Previews\Preview\Post\Status\Post_Statuses_Config; use HWP\Previews\Preview\Post\Type\Contracts\Post_Types_Config_Interface; use HWP\Previews\Preview\Post\Type\Post_Types_Config_Registry; -use HWP\Previews\Preview\Template_Resolver; +use HWP\Previews\Preview\Service\Post_Preview_Service; +use HWP\Previews\Preview\Service\Post_Settings_Service; +use HWP\Previews\Preview\Service\Post_Type_Service; +use HWP\Previews\Preview\Service\Template_Resolver_Service; use WP_Post; use WP_REST_Response; @@ -60,7 +63,7 @@ class Preview_Hooks { */ public function __construct() { - // @TODO - Refactor to use a factory or service locator pattern and ananlyze what is is actually needed. + // @TODO - Refactor to use a factory or service locator pattern and analyze what is is actually needed. $this->settings_helper = Settings_Helper::get_instance(); @@ -125,6 +128,7 @@ public function setup(): void { * @return array */ public function get_post_statuses(): array { + // @TODO - Remove $post_statuses = [ 'publish', 'future', @@ -202,22 +206,17 @@ public function add_iframe_preview_template( string $template ): string { return $template; } - // @TODO - Refactor as should be part of the main preview settings. - $settings_helper = Settings_Helper::get_instance(); - if ( ! $settings_helper->in_iframe( $post->post_type ) ) { - return $template; - } - - // @TODO - Refactor as part of types and post statuses config refactor. - $post_types = $this->types_config->get_post_types(); - $post_statuses = $this->statuses_config->get_post_statuses(); - $template_resolver = new Template_Resolver( $post, $post_types, $post_statuses ); + // @TODO - Move main classes into properties + $post_preview_service = new Post_Preview_Service(); + $post_settings_service = new Post_Settings_Service(); + $post_type_service = new Post_Type_Service( $post, $post_preview_service, $post_settings_service ); - if ( ! $template_resolver->is_allowed() ) { + if ( ! $post_type_service->is_allowed_for_previews() || ! $post_type_service->is_iframe() ) { return $template; } - $iframe_template = $template_resolver->get_iframe_template(); + $template_resolver = new Template_Resolver_Service(); + $iframe_template = $template_resolver->get_iframe_template(); if ( empty( $iframe_template ) ) { return $template; } diff --git a/plugins/hwp-previews/src/Preview/Helper/Settings_Helper.php b/plugins/hwp-previews/src/Preview/Helper/Settings_Helper.php index b0d15590..b6d83913 100644 --- a/plugins/hwp-previews/src/Preview/Helper/Settings_Helper.php +++ b/plugins/hwp-previews/src/Preview/Helper/Settings_Helper.php @@ -44,16 +44,6 @@ public static function get_instance(): self { return self::$instance; } - /** - * Get the settings group. - * - * @return array - */ - public function get_settings_config(): array { - // @TODO: Remove this method when the settings group is no longer needed. - return $this->settings_group->get_settings_config(); - } - /** * Get all post types that are enabled in the settings. * @@ -99,6 +89,7 @@ public function post_statuses_as_parent( string $post_type, bool $default_value */ public function in_iframe( string $post_type, bool $default_value = false ): bool { + // @TODO remove as part of Post_Type_Service refactor. $key = $this->settings_group->get_settings_key_in_iframe(); return $this->settings_group->get_post_type_boolean_value( $key, $post_type, $default_value ); diff --git a/plugins/hwp-previews/src/Preview/Service/Post_Preview_Service.php b/plugins/hwp-previews/src/Preview/Service/Post_Preview_Service.php new file mode 100644 index 00000000..b491ff94 --- /dev/null +++ b/plugins/hwp-previews/src/Preview/Service/Post_Preview_Service.php @@ -0,0 +1,88 @@ + + */ + protected $post_types = []; + + /** + * The allowed post-statuses for previews. + * + * @var array + */ + protected $post_statuses = []; + + /** + * Constructor for the Post_Preview_Service class. + * + * Initializes the allowed post-types and statuses for previews. + */ + public function __construct() { + $this->set_post_types(); + $this->set_post_statuses(); + } + + /** + * @return array + */ + public function get_allowed_post_types(): array { + return $this->post_types; + } + + /** + * Get the post statuses. + * + * @return array + */ + public function get_post_statuses(): array { + return $this->post_statuses; + } + + /** + * Get the post types. + * + * @return array + */ + public function get_post_types(): array { + return $this->post_types; + } + + /** + * Sets the allowed post types for previews. + */ + protected function set_post_types(): void { + $post_types = get_post_types( [ 'public' => true ], 'objects' ); + + $result = []; + + foreach ( $post_types as $post_type ) { + $result[ $post_type->name ] = $post_type->label; + } + + $this->post_types = apply_filters( 'hwp_previews_filter_available_post_types', $result ); + } + + /** + * Sets the allowed post statuses for previews. + */ + protected function set_post_statuses(): void { + + $post_statuses = [ + 'publish', + 'future', + 'draft', + 'pending', + 'private', + 'auto-draft', + ]; + + $this->post_statuses = apply_filters( 'hwp_previews_filter_available_post_statuses', $post_statuses ); + } +} diff --git a/plugins/hwp-previews/src/Preview/Service/Post_Settings_Service.php b/plugins/hwp-previews/src/Preview/Service/Post_Settings_Service.php new file mode 100644 index 00000000..4834cc57 --- /dev/null +++ b/plugins/hwp-previews/src/Preview/Service/Post_Settings_Service.php @@ -0,0 +1,63 @@ + + */ + protected $settings_values = []; + + /** + * Initialize the settings service. + */ + public function __construct() { + $this->setup(); + } + + /** + * @param string $post_type + * + * @return array|null + */ + public function get_post_type_config( string $post_type ): ?array { + return $this->settings_values[ $post_type ] ?? null; + } + + /** + * The option key for the settings group. + */ + public function get_option_key(): string { + return (string) apply_filters( 'hwp_previews_settings_group_option_key', HWP_PREVIEWS_SETTINGS_KEY ); + } + + /** + * The settings group for the options. + */ + public function get_settings_group(): string { + return (string) apply_filters( 'hwp_previews_settings_group_settings_group', HWP_PREVIEWS_SETTINGS_GROUP ); + } + + /** + * Setup the settings values by retrieving them from the database or cache. + * This method is called in the constructor to ensure settings are available. + */ + protected function setup(): void { + $option_key = $this->get_option_key(); + $settings_group = $this->get_settings_group(); + + $value = wp_cache_get( $option_key, $settings_group ); + if ( is_array( $value ) ) { + $this->settings_values = $value; + + return; + } + + $this->settings_values = (array) get_option( $option_key, [] ); + wp_cache_set( $option_key, $value, $settings_group ); + } +} diff --git a/plugins/hwp-previews/src/Preview/Service/Post_Type_Service.php b/plugins/hwp-previews/src/Preview/Service/Post_Type_Service.php new file mode 100644 index 00000000..46b5ad00 --- /dev/null +++ b/plugins/hwp-previews/src/Preview/Service/Post_Type_Service.php @@ -0,0 +1,96 @@ +post = $post; + $this->post_preview_service = $post_preview_service; + $this->post_settings_service = $post_settings_service; + } + + /** + * Checks if the post type is allowed for previews. + */ + public function is_allowed_for_previews(): bool { + return $this->is_enabled() && $this->is_allowed_post_type() && $this->is_allowed_post_status(); + } + + /** + * Checks if the post-type is enabled for previews. + */ + public function is_enabled(): bool { + $config = $this->post_settings_service->get_post_type_config( $this->post->post_type ); + if ( ! is_array( $config ) ) { + return false; + } + + if ( ! isset( $config['enabled'] ) ) { + return false; + } + return (bool) $config['enabled']; + } + + /** + * Checks if the post-type is allowed for previews. + */ + public function is_allowed_post_type(): bool { + $post_type = $this->post->post_type; + $post_types = $this->post_preview_service->get_post_types(); + return array_key_exists( $post_type, $post_types ); + } + + /** + * Checks if the post-status is allowed for previews. + */ + public function is_allowed_post_status(): bool { + $post_status = $this->post->post_status; + $post_statuses = $this->post_preview_service->get_post_statuses(); + return in_array( $post_status, $post_statuses, true ); + } + + /** + * Checks if the post-type is allowed for iframe previews. + */ + public function is_iframe(): bool { + $config = $this->post_settings_service->get_post_type_config( $this->post->post_type ); + if ( ! is_array( $config ) ) { + return false; + } + + if ( ! isset( $config['in_iframe'] ) ) { + return false; + } + return (bool) $config['in_iframe']; + } +} diff --git a/plugins/hwp-previews/src/Preview/Service/Template_Resolver_Service.php b/plugins/hwp-previews/src/Preview/Service/Template_Resolver_Service.php new file mode 100644 index 00000000..b7e69081 --- /dev/null +++ b/plugins/hwp-previews/src/Preview/Service/Template_Resolver_Service.php @@ -0,0 +1,52 @@ + - */ - private array $post_types = []; - - /** - * Allowed post statuses for preview links. - * - * @var array - */ - private array $post_statuses = []; - - /** - * Constructor. - * - * @param \WP_Post $post The post object. - * @param array $post_types Post types that are applicable for preview links. - * @param array $post_statuses Post statuses that are applicable for preview links. - */ - public function __construct( WP_Post $post, array $post_types = [], array $post_statuses = [] ) { - $this->post = $post; - $this->post_types = $post_types; - $this->post_statuses = $post_statuses; - } - - /** - * Check if the current post is allowed for preview in an iframe. - * - * @return bool True if the post is allowed, false otherwise. - */ - public function is_allowed(): bool { - - $post_type = $this->post->post_type; - $post_types = $this->post_types; - if ( ! in_array( $post_type, $post_types, true ) ) { - return false; - } - - $post_status = $this->post->post_status; - $post_statuses = $this->post_statuses; - - return in_array( $post_status, $post_statuses, true ); - } - - /** - * Get the path to the iframe template. - * - * @return string|null The path to the iframe template, or null if it does not exist. - */ - public function get_iframe_template(): ?string { - - /** - * The filter 'hwp_previews_template_path' allows changing the template file path. - */ - $template_dir_path = (string) apply_filters( - 'hwp_previews_template_path', - trailingslashit( HWP_PREVIEWS_PLUGIN_DIR ) . 'src/Templates/iframe.php', - ); - - if ( ! file_exists( $template_dir_path ) ) { - return ''; - } - - return $template_dir_path; - } - - /** - * Set the query variable that contains the preview URL for the iframe. - * - * @param string $template_url - */ - public function set_query_variable( string $template_url ): void { - set_query_var( self::HWP_PREVIEWS_IFRAME_PREVIEW_URL, $template_url ); - } - - /** - * Get the query variable that contains the preview URL for the iframe. - */ - public static function get_query_variable(): string { - return (string) get_query_var( self::HWP_PREVIEWS_IFRAME_PREVIEW_URL ); - } -} diff --git a/plugins/hwp-previews/src/Templates/iframe.php b/plugins/hwp-previews/src/Templates/iframe.php index f24a62f0..b2ca177e 100644 --- a/plugins/hwp-previews/src/Templates/iframe.php +++ b/plugins/hwp-previews/src/Templates/iframe.php @@ -9,7 +9,7 @@ declare(strict_types=1); -$hwp_previews_url_template = \HWP\Previews\Preview\Template_Resolver::get_query_variable(); +$hwp_previews_url_template = \HWP\Previews\Preview\Service\Template_Resolver_Service::get_query_variable(); ?> diff --git a/plugins/hwp-previews/tests/wpunit/Preview/Service/PostPreviewServiceTest.php b/plugins/hwp-previews/tests/wpunit/Preview/Service/PostPreviewServiceTest.php new file mode 100644 index 00000000..09e9ed3f --- /dev/null +++ b/plugins/hwp-previews/tests/wpunit/Preview/Service/PostPreviewServiceTest.php @@ -0,0 +1,96 @@ +remove_all_filters(); + $this->service = new Post_Preview_Service(); + } + + public function tearDown(): void { + $this->remove_all_filters(); + parent::tearDown(); + } + + public function remove_all_filters() { + remove_all_filters( 'hwp_previews_filter_available_post_types' ); + remove_all_filters( 'hwp_previews_filter_available_post_statuses' ); + } + + public function test_get_allowed_post_types_returns_array(): void { + $result = $this->service->get_allowed_post_types(); + + $this->assertIsArray( $result ); + $this->assertNotEmpty( $result ); + } + + public function test_get_post_statuses_returns_default_statuses(): void { + $result = $this->service->get_post_statuses(); + + $expected = ['publish', 'future', 'draft', 'pending', 'private', 'auto-draft']; + $this->assertEquals($expected, $result); + } + + public function test_get_post_types_returns_same_as_get_allowed_post_types(): void { + $allowed_types = $this->service->get_allowed_post_types(); + $post_types = $this->service->get_post_types(); + + $this->assertEquals($allowed_types, $post_types); + } + + public function test_post_types_filter_is_applied(): void { + // Arrange + $custom_post_types = ['custom_post' => 'Custom Post Type']; + add_filter('hwp_previews_filter_available_post_types', function() use ($custom_post_types) { + return $custom_post_types; + }); + + // Act + $service = new Post_Preview_Service(); + $result = $service->get_post_types(); + + // Assert + $this->assertEquals($custom_post_types, $result); + } + + public function test_post_statuses_filter_is_applied(): void { + // Arrange + $custom_statuses = ['custom_status']; + add_filter('hwp_previews_filter_available_post_statuses', function() use ($custom_statuses) { + return $custom_statuses; + }); + + // Act + $service = new Post_Preview_Service(); + $result = $service->get_post_statuses(); + + // Assert + $this->assertEquals($custom_statuses, $result); + } + + public function test_constructor_initializes_post_types_and_statuses(): void { + // Act + $service = new Post_Preview_Service(); + + // Assert + $this->assertIsArray($service->get_post_types()); + $this->assertIsArray($service->get_post_statuses()); + + // Ensure post statuses are not empty (should have default values) + $this->assertNotEmpty($service->get_post_statuses()); + } +} diff --git a/plugins/hwp-previews/tests/wpunit/Preview/Service/PostServiceSettingsTest.php b/plugins/hwp-previews/tests/wpunit/Preview/Service/PostServiceSettingsTest.php new file mode 100644 index 00000000..fdc46265 --- /dev/null +++ b/plugins/hwp-previews/tests/wpunit/Preview/Service/PostServiceSettingsTest.php @@ -0,0 +1,198 @@ +remove_all_filters(); + $this->delete_option(); + + // Set up test filters so we don't overwrite the actual plugin settings. + add_filter( 'hwp_previews_settings_group_option_key', function () { + return $this->test_option_key; + } ); + add_filter( 'hwp_previews_settings_group_settings_group', function () { + return $this->test_settings_group; + } ); + } + + public function tearDown(): void { + $this->delete_option(); + $this->remove_all_filters(); + parent::tearDown(); + } + + public function remove_all_filters() { + remove_all_filters( 'hwp_previews_settings_group_option_key' ); + remove_all_filters( 'hwp_previews_settings_group_settings_group' ); + } + + public function delete_option() { + delete_option( $this->test_option_key ); + wp_cache_flush(); + } + + public function test_get_post_type_config_returns_config_when_exists(): void { + $test_config = [ + 'post' => [ 'enabled' => true, 'in_iframe' => false ], + 'page' => [ 'enabled' => false, 'in_iframe' => true ] + ]; + update_option( $this->test_option_key, $test_config ); + + $this->service = new Post_Settings_Service(); + + // Act + $result = $this->service->get_post_type_config( 'post' ); + + // Assert + $this->assertEquals( [ 'enabled' => true, 'in_iframe' => false ], $result ); + } + + public function test_get_post_type_config_returns_null_when_not_exists(): void { + // Arrange + $test_config = [ 'post' => [ 'enabled' => true ] ]; + update_option( $this->test_option_key, $test_config ); + + $this->service = new Post_Settings_Service(); + + // Act + $result = $this->service->get_post_type_config( 'nonexistent_post_type' ); + + // Assert + $this->assertNull( $result ); + } + + public function test_get_post_type_config_returns_null_when_no_settings(): void { + // Arrange - no settings saved + $this->service = new Post_Settings_Service(); + + // Act + $result = $this->service->get_post_type_config( 'post' ); + + // Assert + $this->assertNull( $result ); + } + + public function test_get_option_key_returns_filtered_value(): void { + // Arrange + $this->service = new Post_Settings_Service(); + + // Act + $result = $this->service->get_option_key(); + + // Assert + $this->assertEquals( $this->test_option_key, $result ); + } + + public function test_get_settings_group_returns_filtered_value(): void { + // Arrange + $this->service = new Post_Settings_Service(); + + // Act + $result = $this->service->get_settings_group(); + + // Assert + $this->assertEquals( $this->test_settings_group, $result ); + } + + public function test_constructor_loads_settings_from_cache_when_available(): void { + // Arrange + $cached_data = [ 'post' => [ 'enabled' => true, 'cached' => true ] ]; + wp_cache_set( $this->test_option_key, $cached_data, $this->test_settings_group ); + + // Different data in database to ensure cache is used + $db_data = [ 'post' => [ 'enabled' => false, 'cached' => false ] ]; + update_option( $this->test_option_key, $db_data ); + + // Act + $this->service = new Post_Settings_Service(); + $result = $this->service->get_post_type_config( 'post' ); + + // Assert + $this->assertEquals( [ 'enabled' => true, 'cached' => true ], $result ); + } + + public function test_constructor_loads_settings_from_database_when_cache_empty(): void { + // Arrange + $db_data = [ 'post' => [ 'enabled' => true, 'from_db' => true ] ]; + update_option( $this->test_option_key, $db_data ); + + // Ensure cache is empty + wp_cache_delete( $this->test_option_key, $this->test_settings_group ); + + // Act + $this->service = new Post_Settings_Service(); + $result = $this->service->get_post_type_config( 'post' ); + + // Assert + $this->assertEquals( [ 'enabled' => true, 'from_db' => true ], $result ); + } + + public function test_constructor_handles_non_array_cache_value(): void { + // Arrange + wp_cache_set( $this->test_option_key, 'not_an_array', $this->test_settings_group ); + + $db_data = [ 'post' => [ 'enabled' => true ] ]; + update_option( $this->test_option_key, $db_data ); + + // Act + $this->service = new Post_Settings_Service(); + $result = $this->service->get_post_type_config( 'post' ); + + // Assert + $this->assertEquals( [ 'enabled' => true ], $result ); + } + + public function test_constructor_handles_empty_database_option(): void { + // Arrange - ensure option doesn't exist + delete_option( $this->test_option_key ); + wp_cache_delete( $this->test_option_key, $this->test_settings_group ); + + // Act + $this->service = new Post_Settings_Service(); + $result = $this->service->get_post_type_config( 'post' ); + + // Assert + $this->assertNull( $result ); + } + + public function test_constructor_handles_non_array_database_option(): void { + // Arrange + update_option( $this->test_option_key, 'not_an_array' ); + wp_cache_delete( $this->test_option_key, $this->test_settings_group ); + + // Act + $this->service = new Post_Settings_Service(); + $result = $this->service->get_post_type_config( 'post' ); + + // Assert + $this->assertNull( $result ); + } +} diff --git a/plugins/hwp-previews/tests/wpunit/Preview/Service/PostTypeServiceTest.php b/plugins/hwp-previews/tests/wpunit/Preview/Service/PostTypeServiceTest.php new file mode 100644 index 00000000..1f79cfbc --- /dev/null +++ b/plugins/hwp-previews/tests/wpunit/Preview/Service/PostTypeServiceTest.php @@ -0,0 +1,209 @@ +post_id = $this->factory()->post->create( + [ + 'post_type' => 'post', + 'post_status' => 'publish', + 'post_title' => 'Test Post', + ] + ); + + $this->post = get_post( $this->post_id ); + + // Create mocks for dependencies + $this->post_preview_service_mock = $this->createMock( Post_Preview_Service::class ); + $this->post_settings_service_mock = $this->createMock( Post_Settings_Service::class ); + + $this->service = new Post_Type_Service( + $this->post, + $this->post_preview_service_mock, + $this->post_settings_service_mock + ); + } + + public function tearDown(): void { + remove_all_filters( 'hwp_previews_settings_group_option_key' ); + remove_all_filters( 'hwp_previews_settings_group_settings_group' ); + } + + public function test_is_allowed_for_previews_when_enabled_and_post_type_and_status_is_allowed(): void { + + $this->post_settings_service_mock + ->method( 'get_post_type_config' ) + ->willReturn( [ 'enabled' => true ] ); + + $this->post_preview_service_mock + ->method( 'get_post_types' ) + ->willReturn( [ 'post' => 'Posts' ] ); + + $this->post_preview_service_mock + ->method( 'get_post_statuses' ) + ->willReturn( [ 'publish', 'draft' ] ); + + $result = $this->service->is_allowed_for_previews(); + $this->assertTrue( $result ); + } + + public function test_is_not_allowed_for_previews_when_enabled_and_post_type_and_status_is_not_allowed(): void { + + $this->post_settings_service_mock + ->method( 'get_post_type_config' ) + ->willReturn( [ 'enabled' => true ] ); + + $this->post_preview_service_mock + ->method( 'get_post_types' ) + ->willReturn( [ 'post' => 'Posts' ] ); + + $this->post_preview_service_mock + ->method( 'get_post_statuses' ) + ->willReturn( [ 'publish', 'draft' ] ); + + $post = $this->post; + $post->post_type = 'draft'; + $draft_service = new Post_Type_Service( + $post, + $this->post_preview_service_mock, + $this->post_settings_service_mock + ); + + $result = $draft_service->is_allowed_for_previews(); + $this->assertFalse( $result ); + + + $post = $this->post; + $post->post_type = 'media'; + $custom_post_type_service = new Post_Type_Service( + $post, + $this->post_preview_service_mock, + $this->post_settings_service_mock + ); + + $result = $custom_post_type_service->is_allowed_for_previews(); + $this->assertFalse( $result ); + } + + public function test_is_allowed_for_previews_returns_false_when_not_enabled(): void { + $this->post_settings_service_mock + ->method( 'get_post_type_config' ) + ->willReturn( [ 'enabled' => false ] ); + + $result = $this->service->is_allowed_for_previews(); + + $this->assertFalse( $result ); + } + + + public function test_is_enabled_returns_false_when_config_not_array(): void { + // Arrange + $this->post_settings_service_mock + ->method( 'get_post_type_config' ) + ->willReturn( null ); + + // Act + $result = $this->service->is_enabled(); + + // Assert + $this->assertFalse( $result ); + } + + public function test_is_enabled_returns_false_when_enabled_key_missing(): void { + // Arrange + $this->post_settings_service_mock + ->method( 'get_post_type_config' ) + ->willReturn( [ 'other_setting' => true ] ); + + // Act + $result = $this->service->is_enabled(); + + // Assert + $this->assertFalse( $result ); + } + + public function test_is_allowed_post_type_returns_true_when_post_type_exists(): void { + // Arrange + $this->post_preview_service_mock + ->method( 'get_post_types' ) + ->willReturn( [ 'post' => 'Posts', 'page' => 'Pages' ] ); + + // Act + $result = $this->service->is_allowed_post_type(); + + // Assert + $this->assertTrue( $result ); + } + + public function test_is_allowed_post_type_returns_false_when_post_type_not_exists(): void { + // Arrange + $this->post_preview_service_mock + ->method( 'get_post_types' ) + ->willReturn( [ 'page' => 'Pages' ] ); + + // Act + $result = $this->service->is_allowed_post_type(); + + // Assert + $this->assertFalse( $result ); + } + + public function test_is_iframe_returns_true_when_enabled(): void { + $this->post_settings_service_mock + ->method( 'get_post_type_config' ) + ->willReturn( [ 'in_iframe' => true ] ); + + $result = $this->service->is_iframe(); + $this->assertTrue( $result ); + } + + public function test_is_iframe_returns_false_when_disabled(): void { + $this->post_settings_service_mock + ->method( 'get_post_type_config' ) + ->willReturn( [ 'in_iframe' => false ] ); + + $result = $this->service->is_iframe(); + $this->assertFalse( $result ); + } + + public function test_is_iframe_returns_false_when_config_not_array(): void { + $this->post_settings_service_mock + ->method( 'get_post_type_config' ) + ->willReturn( null ); + + $result = $this->service->is_iframe(); + $this->assertFalse( $result ); + } + + public function test_is_iframe_returns_false_when_iframe_key_missing(): void { + $this->post_settings_service_mock + ->method( 'get_post_type_config' ) + ->willReturn( [ 'enabled' => true ] ); + + $result = $this->service->is_iframe(); + $this->assertFalse( $result ); + } +} diff --git a/plugins/hwp-previews/tests/wpunit/Preview/Service/TemplateResolverServiceTest.php b/plugins/hwp-previews/tests/wpunit/Preview/Service/TemplateResolverServiceTest.php new file mode 100644 index 00000000..af452083 --- /dev/null +++ b/plugins/hwp-previews/tests/wpunit/Preview/Service/TemplateResolverServiceTest.php @@ -0,0 +1,174 @@ +get_iframe_template(); + + $this->assertEquals( $existing_file, $result ); + + // Cleanup + unlink( $existing_file ); + remove_all_filters( 'hwp_previews_template_path' ); + } + + /** + * Test get_iframe_template returns empty string when file does not exist. + */ + public function test_get_iframe_template_returns_empty_string_when_file_not_exists(): void { + $resolver = new Template_Resolver_Service(); + + // Mock the filter to return a non-existent file path + $non_existent_file = '/path/that/does/not/exist/iframe.php'; + + add_filter( 'hwp_previews_template_path', function () use ( $non_existent_file ) { + return $non_existent_file; + } ); + + $result = $resolver->get_iframe_template(); + + $this->assertEquals( '', $result ); + + // Cleanup + remove_all_filters( 'hwp_previews_template_path' ); + } + + /** + * Test get_iframe_template applies the hwp_previews_template_path filter. + */ + public function test_get_iframe_template_applies_filter(): void { + $resolver = new Template_Resolver_Service(); + + $custom_path = '/custom/template/path.php'; + $filter_called = false; + + add_filter( 'hwp_previews_template_path', function ( $path ) use ( $custom_path, &$filter_called ) { + $filter_called = true; + + return $custom_path; + } ); + + // Call the method (will return empty string since file doesn't exist) + $resolver->get_iframe_template(); + + $this->assertTrue( $filter_called ); + + // Cleanup + remove_all_filters( 'hwp_previews_template_path' ); + } + + /** + * Test set_query_variable sets the query variable correctly. + */ + public function test_set_query_variable_sets_query_var(): void { + $resolver = new Template_Resolver_Service(); + $test_url = 'https://example.com/preview'; + + $resolver->set_query_variable( $test_url ); + + $this->assertEquals( $test_url, get_query_var( Template_Resolver_Service::HWP_PREVIEWS_IFRAME_PREVIEW_URL ) ); + } + + /** + * Test set_query_variable with empty string. + */ + public function test_set_query_variable_with_empty_string(): void { + $resolver = new Template_Resolver_Service(); + + $resolver->set_query_variable( '' ); + + $this->assertEquals( '', get_query_var( Template_Resolver_Service::HWP_PREVIEWS_IFRAME_PREVIEW_URL ) ); + } + + /** + * Test get_query_variable returns the correct value. + */ + public function test_get_query_variable_returns_correct_value(): void { + $test_url = 'https://example.com/preview'; + + set_query_var( Template_Resolver_Service::HWP_PREVIEWS_IFRAME_PREVIEW_URL, $test_url ); + + $result = Template_Resolver_Service::get_query_variable(); + + $this->assertEquals( $test_url, $result ); + } + + /** + * Test get_query_variable returns empty string when not set. + */ + public function test_get_query_variable_returns_empty_string_when_not_set(): void { + // Ensure the query var is not set + set_query_var( Template_Resolver_Service::HWP_PREVIEWS_IFRAME_PREVIEW_URL, '' ); + + $result = Template_Resolver_Service::get_query_variable(); + + $this->assertEquals( '', $result ); + } + + /** + * Test get_query_variable is static and works without instance. + */ + public function test_get_query_variable_is_static(): void { + $test_url = 'https://example.com/static-test'; + + set_query_var( Template_Resolver_Service::HWP_PREVIEWS_IFRAME_PREVIEW_URL, $test_url ); + + // Call static method without creating an instance + $result = Template_Resolver_Service::get_query_variable(); + + $this->assertEquals( $test_url, $result ); + } + + /** + * Test constant is defined correctly. + */ + public function test_constant_is_defined_correctly(): void { + $this->assertEquals( 'hwp_previews_iframe_preview_url', Template_Resolver_Service::HWP_PREVIEWS_IFRAME_PREVIEW_URL ); + } + + /** + * Test integration: set and get query variable using the same constant. + */ + public function test_set_and_get_query_variable_integration(): void { + $resolver = new Template_Resolver_Service(); + $test_url = 'https://example.com/integration-test'; + + $resolver->set_query_variable( $test_url ); + $retrieved_url = Template_Resolver_Service::get_query_variable(); + + $this->assertEquals( $test_url, $retrieved_url ); + } +} diff --git a/plugins/hwp-previews/tests/wpunit/Preview/TemplateResolverTest.php b/plugins/hwp-previews/tests/wpunit/Preview/TemplateResolverTest.php deleted file mode 100644 index 327df3c9..00000000 --- a/plugins/hwp-previews/tests/wpunit/Preview/TemplateResolverTest.php +++ /dev/null @@ -1,362 +0,0 @@ -factory()->post->create([ - 'post_title' => 'Test Post', - 'post_type' => 'post', - 'post_status' => 'publish' - ]); - - $this->test_post = get_post($post_id); - } - - - /** - * Clean up after each test. - */ - public function tearDown(): void { - // Clean up query vars - set_query_var(Template_Resolver::HWP_PREVIEWS_IFRAME_PREVIEW_URL, ''); - - // Remove any filters that might have been added - remove_all_filters('hwp_previews_template_path'); - - parent::tearDown(); - } - - /** - * Test constructor sets properties correctly. - */ - public function test_constructor_sets_properties_correctly(): void { - $post_types = ['post', 'page']; - $post_statuses = ['publish', 'draft']; - - $resolver = new Template_Resolver($this->test_post, $post_types, $post_statuses); - - // Use reflection to access private properties for testing - $reflection = new \ReflectionClass($resolver); - - $post_property = $reflection->getProperty('post'); - $post_property->setAccessible(true); - $this->assertEquals($this->test_post, $post_property->getValue($resolver)); - - $post_types_property = $reflection->getProperty('post_types'); - $post_types_property->setAccessible(true); - $this->assertEquals($post_types, $post_types_property->getValue($resolver)); - - $post_statuses_property = $reflection->getProperty('post_statuses'); - $post_statuses_property->setAccessible(true); - $this->assertEquals($post_statuses, $post_statuses_property->getValue($resolver)); - } - - /** - * Test constructor with default empty arrays. - */ - public function test_constructor_with_default_arrays(): void { - $resolver = new Template_Resolver($this->test_post); - - $reflection = new \ReflectionClass($resolver); - - $post_types_property = $reflection->getProperty('post_types'); - $post_types_property->setAccessible(true); - $this->assertEquals([], $post_types_property->getValue($resolver)); - - $post_statuses_property = $reflection->getProperty('post_statuses'); - $post_statuses_property->setAccessible(true); - $this->assertEquals([], $post_statuses_property->getValue($resolver)); - } - - /** - * Test is_allowed returns true when post type and status are allowed. - */ - public function test_is_allowed_returns_true_when_post_is_allowed(): void { - $post_types = ['post', 'page']; - $post_statuses = ['publish', 'draft']; - - $resolver = new Template_Resolver($this->test_post, $post_types, $post_statuses); - - $this->assertTrue($resolver->is_allowed()); - } - - /** - * Test is_allowed returns false when post type is not allowed. - */ - public function test_is_allowed_returns_false_when_post_type_not_allowed(): void { - $post_types = ['page', 'custom_post_type']; // 'post' not included - $post_statuses = ['publish', 'draft']; - - $resolver = new Template_Resolver($this->test_post, $post_types, $post_statuses); - - $this->assertFalse($resolver->is_allowed()); - } - - /** - * Test is_allowed returns false when post status is not allowed. - */ - public function test_is_allowed_returns_false_when_post_status_not_allowed(): void { - $post_types = ['post', 'page']; - $post_statuses = ['draft', 'private']; // 'publish' not included - - $resolver = new Template_Resolver($this->test_post, $post_types, $post_statuses); - - $this->assertFalse($resolver->is_allowed()); - } - - /** - * Test is_allowed returns false when both arrays are empty. - */ - public function test_is_allowed_returns_false_when_arrays_are_empty(): void { - $resolver = new Template_Resolver($this->test_post, [], []); - - $this->assertFalse($resolver->is_allowed()); - } - - /** - * Test is_allowed with custom post type and status. - */ - public function test_is_allowed_with_custom_post_type_and_status(): void { - // Create a custom post with specific type and status - $custom_post_id = $this->factory->post->create([ - 'post_type' => 'custom_type', - 'post_status' => 'custom_status' - ]); - $custom_post = get_post($custom_post_id); - - $post_types = ['custom_type']; - $post_statuses = ['custom_status']; - - $resolver = new Template_Resolver($custom_post, $post_types, $post_statuses); - - $this->assertTrue($resolver->is_allowed()); - } - - /** - * Test get_iframe_template returns template path when file exists. - */ - public function test_get_iframe_template_returns_path_when_file_exists(): void { - $resolver = new Template_Resolver($this->test_post); - - // Mock the filter to return a path to an existing file - $existing_file = tempnam(sys_get_temp_dir(), 'iframe_template'); - file_put_contents($existing_file, 'get_iframe_template(); - - $this->assertEquals($existing_file, $result); - - // Cleanup - unlink($existing_file); - remove_all_filters('hwp_previews_template_path'); - } - - /** - * Test get_iframe_template returns empty string when file does not exist. - */ - public function test_get_iframe_template_returns_empty_string_when_file_not_exists(): void { - $resolver = new Template_Resolver($this->test_post); - - // Mock the filter to return a non-existent file path - $non_existent_file = '/path/that/does/not/exist/iframe.php'; - - add_filter('hwp_previews_template_path', function() use ($non_existent_file) { - return $non_existent_file; - }); - - $result = $resolver->get_iframe_template(); - - $this->assertEquals('', $result); - - // Cleanup - remove_all_filters('hwp_previews_template_path'); - } - - /** - * Test get_iframe_template applies the hwp_previews_template_path filter. - */ - public function test_get_iframe_template_applies_filter(): void { - $resolver = new Template_Resolver($this->test_post); - - $custom_path = '/custom/template/path.php'; - $filter_called = false; - - add_filter('hwp_previews_template_path', function($path) use ($custom_path, &$filter_called) { - $filter_called = true; - return $custom_path; - }); - - // Call the method (will return empty string since file doesn't exist) - $resolver->get_iframe_template(); - - $this->assertTrue($filter_called); - - // Cleanup - remove_all_filters('hwp_previews_template_path'); - } - - /** - * Test set_query_variable sets the query variable correctly. - */ - public function test_set_query_variable_sets_query_var(): void { - $resolver = new Template_Resolver($this->test_post); - $test_url = 'https://example.com/preview'; - - $resolver->set_query_variable($test_url); - - $this->assertEquals($test_url, get_query_var(Template_Resolver::HWP_PREVIEWS_IFRAME_PREVIEW_URL)); - } - - /** - * Test set_query_variable with empty string. - */ - public function test_set_query_variable_with_empty_string(): void { - $resolver = new Template_Resolver($this->test_post); - - $resolver->set_query_variable(''); - - $this->assertEquals('', get_query_var(Template_Resolver::HWP_PREVIEWS_IFRAME_PREVIEW_URL)); - } - - /** - * Test get_query_variable returns the correct value. - */ - public function test_get_query_variable_returns_correct_value(): void { - $test_url = 'https://example.com/preview'; - - set_query_var(Template_Resolver::HWP_PREVIEWS_IFRAME_PREVIEW_URL, $test_url); - - $result = Template_Resolver::get_query_variable(); - - $this->assertEquals($test_url, $result); - } - - /** - * Test get_query_variable returns empty string when not set. - */ - public function test_get_query_variable_returns_empty_string_when_not_set(): void { - // Ensure the query var is not set - set_query_var(Template_Resolver::HWP_PREVIEWS_IFRAME_PREVIEW_URL, ''); - - $result = Template_Resolver::get_query_variable(); - - $this->assertEquals('', $result); - } - - /** - * Test get_query_variable is static and works without instance. - */ - public function test_get_query_variable_is_static(): void { - $test_url = 'https://example.com/static-test'; - - set_query_var(Template_Resolver::HWP_PREVIEWS_IFRAME_PREVIEW_URL, $test_url); - - // Call static method without creating an instance - $result = Template_Resolver::get_query_variable(); - - $this->assertEquals($test_url, $result); - } - - /** - * Test constant is defined correctly. - */ - public function test_constant_is_defined_correctly(): void { - $this->assertEquals('hwp_previews_iframe_preview_url', Template_Resolver::HWP_PREVIEWS_IFRAME_PREVIEW_URL); - } - - /** - * Test integration: set and get query variable using the same constant. - */ - public function test_set_and_get_query_variable_integration(): void { - $resolver = new Template_Resolver($this->test_post); - $test_url = 'https://example.com/integration-test'; - - $resolver->set_query_variable($test_url); - $retrieved_url = Template_Resolver::get_query_variable(); - - $this->assertEquals($test_url, $retrieved_url); - } - - /** - * Test is_allowed with different post objects. - */ - public function test_is_allowed_with_different_post_objects(): void { - // Create posts with different types and statuses - $page_id = $this->factory->post->create([ - 'post_type' => 'page', - 'post_status' => 'draft' - ]); - $page_post = get_post($page_id); - - $post_types = ['page']; - $post_statuses = ['draft']; - - $resolver = new Template_Resolver($page_post, $post_types, $post_statuses); - - $this->assertTrue($resolver->is_allowed()); - } - - /** - * Test type safety - ensure post_types array contains only strings. - */ - public function test_type_safety_post_types(): void { - $post_types = ['post', 'page', 'custom_type']; - - $resolver = new Template_Resolver($this->test_post, $post_types); - - $reflection = new \ReflectionClass($resolver); - $post_types_property = $reflection->getProperty('post_types'); - $post_types_property->setAccessible(true); - - $stored_types = $post_types_property->getValue($resolver); - - foreach ($stored_types as $type) { - $this->assertIsString($type); - } - } - - /** - * Test type safety - ensure post_statuses array contains only strings. - */ - public function test_type_safety_post_statuses(): void { - $post_statuses = ['publish', 'draft', 'private']; - - $resolver = new Template_Resolver($this->test_post, [], $post_statuses); - - $reflection = new \ReflectionClass($resolver); - $post_statuses_property = $reflection->getProperty('post_statuses'); - $post_statuses_property->setAccessible(true); - - $stored_statuses = $post_statuses_property->getValue($resolver); - - foreach ($stored_statuses as $status) { - $this->assertIsString($status); - } - } -} From 6cef67472b9edb4512890c82587ff9defa40a7dd Mon Sep 17 00:00:00 2001 From: Colin Murphy Date: Tue, 17 Jun 2025 18:55:20 +0100 Subject: [PATCH 06/35] Added checks for codeception and phpcs to run tests or code quality --- .github/workflows/code-quality.yml | 8 ++++++++ .github/workflows/codeception.yml | 10 +++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 45afe165..02e93cea 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -26,6 +26,14 @@ jobs: plugin=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep '^plugins/' | head -1 | cut -d/ -f2) echo "slug=$plugin" >> $GITHUB_OUTPUT + # We should at least have a phpcs.xml file to run code quality checks + - name: Validate phpcs.xml + run: | + if [ ! -f "plugins/${{ steps.plugin.outputs.slug }}/phpcs.xml" ]; then + echo "Exiting as no phpcs.xml file found for /${{ steps.plugin.outputs.slug }}" + exit 1 + fi + - name: PHP Code Quality uses: ./.github/actions/code-quality with: diff --git a/.github/workflows/codeception.yml b/.github/workflows/codeception.yml index 577f8ec2..1a06952c 100644 --- a/.github/workflows/codeception.yml +++ b/.github/workflows/codeception.yml @@ -43,10 +43,18 @@ jobs: plugin=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | grep '^plugins/' | head -1 | cut -d/ -f2) echo "slug=$plugin" >> $GITHUB_OUTPUT + - name: Validate codeception.dist.yml + run: | + if [ ! -f "plugins/${{ steps.plugin.outputs.slug }}/codeception.dist.yml" ]; then + echo "Exiting as no codeception file found for this plugin - /${{ steps.plugin.outputs.slug }}" + exit 1 + fi + - name: Validate composer.json run: | if [ ! -f "plugins/${{ steps.plugin.outputs.slug }}/composer.json" ]; then - echo "Warning: composer.json missing in plugins/${{ steps.plugin.outputs.slug }}" + echo "Exiting as no composer file found for this plugin - ${{ steps.plugin.outputs.slug }}" + exit 1 fi - name: Run Codeception Tests From 030de0a93b0262d4fa44dcac636e16b9563b8727 Mon Sep 17 00:00:00 2001 From: Colin Murphy Date: Tue, 17 Jun 2025 20:34:48 +0100 Subject: [PATCH 07/35] Refactoring URL resolver functionality. --- .../hwp-previews/src/Hooks/Preview_Hooks.php | 93 ++++++++++++------- .../src/Preview/Helper/Settings_Group.php | 17 ---- .../src/Preview/Helper/Settings_Helper.php | 26 ------ .../src/Preview/Link/Preview_Link_Service.php | 70 -------------- .../Post_Preview_Service.php | 2 +- .../Post_Settings_Service.php | 2 +- .../{Service => Post}/Post_Type_Service.php | 35 +++++-- .../Template_Resolver_Service.php | 2 +- .../Preview_Url_Resolver_Service.php} | 32 ++++--- plugins/hwp-previews/src/Templates/iframe.php | 2 +- .../PostPreviewServiceTest.php | 4 +- .../PostServiceSettingsTest.php | 4 +- .../{Service => Post}/PostTypeServiceTest.php | 8 +- .../TemplateResolverServiceTest.php | 7 +- 14 files changed, 119 insertions(+), 185 deletions(-) delete mode 100644 plugins/hwp-previews/src/Preview/Link/Preview_Link_Service.php rename plugins/hwp-previews/src/Preview/{Service => Post}/Post_Preview_Service.php (97%) rename plugins/hwp-previews/src/Preview/{Service => Post}/Post_Settings_Service.php (97%) rename plugins/hwp-previews/src/Preview/{Service => Post}/Post_Type_Service.php (72%) rename plugins/hwp-previews/src/Preview/{Service => Template}/Template_Resolver_Service.php (96%) rename plugins/hwp-previews/src/Preview/{Link/Preview_Link_Placeholder_Resolver.php => Url/Preview_Url_Resolver_Service.php} (56%) rename plugins/hwp-previews/tests/wpunit/Preview/{Service => Post}/PostPreviewServiceTest.php (96%) rename plugins/hwp-previews/tests/wpunit/Preview/{Service => Post}/PostServiceSettingsTest.php (98%) rename plugins/hwp-previews/tests/wpunit/Preview/{Service => Post}/PostTypeServiceTest.php (96%) rename plugins/hwp-previews/tests/wpunit/Preview/{Service => Template}/TemplateResolverServiceTest.php (95%) diff --git a/plugins/hwp-previews/src/Hooks/Preview_Hooks.php b/plugins/hwp-previews/src/Hooks/Preview_Hooks.php index d2f96205..1526b9b2 100644 --- a/plugins/hwp-previews/src/Hooks/Preview_Hooks.php +++ b/plugins/hwp-previews/src/Hooks/Preview_Hooks.php @@ -5,18 +5,17 @@ namespace HWP\Previews\Hooks; use HWP\Previews\Preview\Helper\Settings_Helper; -use HWP\Previews\Preview\Link\Preview_Link_Placeholder_Resolver; -use HWP\Previews\Preview\Link\Preview_Link_Service; use HWP\Previews\Preview\Parameter\Preview_Parameter_Registry; use HWP\Previews\Preview\Post\Parent\Post_Parent_Manager; +use HWP\Previews\Preview\Post\Post_Preview_Service; +use HWP\Previews\Preview\Post\Post_Settings_Service; +use HWP\Previews\Preview\Post\Post_Type_Service; use HWP\Previews\Preview\Post\Status\Contracts\Post_Statuses_Config_Interface; use HWP\Previews\Preview\Post\Status\Post_Statuses_Config; use HWP\Previews\Preview\Post\Type\Contracts\Post_Types_Config_Interface; use HWP\Previews\Preview\Post\Type\Post_Types_Config_Registry; -use HWP\Previews\Preview\Service\Post_Preview_Service; -use HWP\Previews\Preview\Service\Post_Settings_Service; -use HWP\Previews\Preview\Service\Post_Type_Service; -use HWP\Previews\Preview\Service\Template_Resolver_Service; +use HWP\Previews\Preview\Template\Template_Resolver_Service; +use HWP\Previews\Preview\Url\Preview_Url_Resolver_Service; use WP_Post; use WP_REST_Response; @@ -43,11 +42,11 @@ class Preview_Hooks { protected Post_Statuses_Config_Interface $statuses_config; /** - * Preview link service class that handles the generation of preview links. + * Post-settings service that provides access to post-settings. * - * @var \HWP\Previews\Preview\Link\Preview_Link_Service + * @var \HWP\Previews\Preview\Post\Post_Preview_Service */ - protected Preview_Link_Service $link_service; + protected Post_Preview_Service $post_preview_service; /** * The instance of the Preview_Hooks class. @@ -56,6 +55,13 @@ class Preview_Hooks { */ protected static ?Preview_Hooks $instance = null; + /** + * Post-settings service that provides access to post-settings. + * + * @var \HWP\Previews\Preview\Post\Post_Settings_Service + */ + private Post_Settings_Service $post_settings_service; + /** * Constructor for the Preview_Hooks class. * @@ -78,15 +84,9 @@ public function __construct() { ( new Post_Statuses_Config() )->set_post_statuses( $this->get_post_statuses() ) ); - // Initialize the preview link service. - $this->link_service = apply_filters( - 'hwp_previews_hooks_preview_link_service', - new Preview_Link_Service( - $this->types_config, - $this->statuses_config, - new Preview_Link_Placeholder_Resolver( Preview_Parameter_Registry::get_instance() ) - ) - ); + + $this->post_preview_service = new Post_Preview_Service(); + $this->post_settings_service = new Post_Settings_Service(); } /** @@ -154,12 +154,12 @@ public function get_post_statuses(): array { */ public function enable_post_statuses_as_parent( array $args ): array { - $post_parent_manager = new Post_Parent_Manager( $this->types_config, $this->statuses_config ); - if ( empty( $args['post_type'] ) ) { return $args; } + $post_parent_manager = new Post_Parent_Manager( $this->types_config, $this->statuses_config ); + $post_type = (string) $args['post_type']; // Check if the correspondent setting is enabled. @@ -180,7 +180,13 @@ public function enable_post_statuses_as_parent( array $args ): array { */ public function filter_rest_prepare_link( WP_REST_Response $response, WP_Post $post ): WP_REST_Response { - if ( $this->settings_helper->in_iframe( $post->post_type ) ) { + $post_type_service = new Post_Type_Service( $post, $this->post_preview_service, $this->post_settings_service ); + + if ( ! $post_type_service->is_allowed_for_previews() ) { + return $response; + } + + if ( $post_type_service->is_iframe() ) { return $response; } @@ -202,27 +208,28 @@ public function add_iframe_preview_template( string $template ): string { } $post = get_post(); - if ( ! is_object( $post ) || ! ( $post instanceof WP_Post ) ) { + if ( ! ( $post instanceof WP_Post ) ) { return $template; } - // @TODO - Move main classes into properties - $post_preview_service = new Post_Preview_Service(); - $post_settings_service = new Post_Settings_Service(); - $post_type_service = new Post_Type_Service( $post, $post_preview_service, $post_settings_service ); + $post_type_service = new Post_Type_Service( $post, $this->post_preview_service, $this->post_settings_service ); - if ( ! $post_type_service->is_allowed_for_previews() || ! $post_type_service->is_iframe() ) { + if ( + ! $post_type_service->is_allowed_for_previews() || + ! $post_type_service->is_iframe() + ) { return $template; } $template_resolver = new Template_Resolver_Service(); $iframe_template = $template_resolver->get_iframe_template(); - if ( empty( $iframe_template ) ) { + $url = self::generate_preview_url( $post ); + + if ( empty( $iframe_template ) || empty( $url ) ) { return $template; } - // @TODO - Refactor this as part of URL generation refactor. - $template_resolver->set_query_variable( self::generate_preview_url( $post ) ); + $template_resolver->set_query_variable( $url ); return $iframe_template; } @@ -234,10 +241,14 @@ public function add_iframe_preview_template( string $template ): string { */ public function update_preview_post_link( string $preview_link, WP_Post $post ): string { - // @TODO - Need to do more testing and add e2e tests for this filter. + $post_type_service = new Post_Type_Service( $post, $this->post_preview_service, $this->post_settings_service ); + + if ( ! $post_type_service->is_allowed_for_previews() ) { + return $preview_link; + } - // If iframe option is enabled, we need to resolve preview on the template redirect level. - if ( $this->settings_helper->in_iframe( $post->post_type ) ) { + // If the iframe option is enabled, we need to resolve preview on the template redirect level. + if ( $post_type_service->is_iframe() ) { return $preview_link; } @@ -258,14 +269,24 @@ public function update_preview_post_link( string $preview_link, WP_Post $post ): */ public function generate_preview_url( WP_Post $post ): string { - // Check if the correspondent setting is enabled. - $url = $this->settings_helper->url_template( $post->post_type ); + $post_type_service = new Post_Type_Service( $post, $this->post_preview_service, $this->post_settings_service ); + if ( ! $post_type_service->is_allowed_for_previews() ) { + return ''; + } + + $url = $post_type_service->get_preview_url(); + if ( empty( $url ) ) { + return ''; + } + + $service = new Preview_Url_Resolver_Service( Preview_Parameter_Registry::get_instance() ); + $url = $service->resolve( $post, $url ); if ( empty( $url ) ) { return ''; } - return $this->link_service->generate_preview_post_link( $url, $post ); + return $url; } /** diff --git a/plugins/hwp-previews/src/Preview/Helper/Settings_Group.php b/plugins/hwp-previews/src/Preview/Helper/Settings_Group.php index 6e9b2e15..9f8eb69b 100644 --- a/plugins/hwp-previews/src/Preview/Helper/Settings_Group.php +++ b/plugins/hwp-previews/src/Preview/Helper/Settings_Group.php @@ -135,23 +135,6 @@ public function get_post_type_boolean_value( string $name, string $post_type, bo return $default_value; } - /** - * Gets a string value for a specific setting in a post type. - * - * @param string $name The setting name. - * @param string $post_type The post type slug. - * @param string $default_value The default value to return if the setting is not found. - */ - public function get_post_type_string_value( string $name, string $post_type, string $default_value = '' ): string { - $settings = $this->get_cached_settings(); - $type = $this->settings_config[ $name ] ?? null; - if ( 'string' === $type && isset( $settings[ $post_type ][ $name ] ) ) { - return (string) $settings[ $post_type ][ $name ]; - } - - return $default_value; - } - /** * Gets the option key for the settings group. */ diff --git a/plugins/hwp-previews/src/Preview/Helper/Settings_Helper.php b/plugins/hwp-previews/src/Preview/Helper/Settings_Helper.php index b6d83913..66ad43f8 100644 --- a/plugins/hwp-previews/src/Preview/Helper/Settings_Helper.php +++ b/plugins/hwp-previews/src/Preview/Helper/Settings_Helper.php @@ -80,30 +80,4 @@ public function post_statuses_as_parent( string $post_type, bool $default_value return $this->settings_group->get_post_type_boolean_value( $key, $post_type, $default_value ); } - - /** - * Show In iframe value for the given post type. - * - * @param string $post_type The post type to get the setting for. - * @param bool $default_value The default value to return if the setting is not set. - */ - public function in_iframe( string $post_type, bool $default_value = false ): bool { - - // @TODO remove as part of Post_Type_Service refactor. - $key = $this->settings_group->get_settings_key_in_iframe(); - - return $this->settings_group->get_post_type_boolean_value( $key, $post_type, $default_value ); - } - - /** - * URL template setting value for the given post type. - * - * @param string $post_type The post type to get the setting for. - * @param string $default_value The default value to return if the setting is not set. - */ - public function url_template( string $post_type, string $default_value = '' ): string { - $key = $this->settings_group->get_settings_key_preview_url(); - - return $this->settings_group->get_post_type_string_value( $key, $post_type, $default_value ); - } } diff --git a/plugins/hwp-previews/src/Preview/Link/Preview_Link_Service.php b/plugins/hwp-previews/src/Preview/Link/Preview_Link_Service.php deleted file mode 100644 index fa19193d..00000000 --- a/plugins/hwp-previews/src/Preview/Link/Preview_Link_Service.php +++ /dev/null @@ -1,70 +0,0 @@ -types = $types; - $this->statuses = $statuses; - $this->resolver = $resolver; - } - - /** - * Generate a preview post link. - * - * @param string $preview_url_template Preview URL template. - * @param \WP_Post $post The post object. - */ - public function generate_preview_post_link( string $preview_url_template, WP_Post $post ): string { - if ( - empty( $preview_url_template ) || - ! $this->types->is_post_type_applicable( $post->post_type ) || - ! $this->statuses->is_post_status_applicable( $post->post_status ) - ) { - return ''; - } - - return $this->resolver->resolve_placeholders( $preview_url_template, $post ); - } -} diff --git a/plugins/hwp-previews/src/Preview/Service/Post_Preview_Service.php b/plugins/hwp-previews/src/Preview/Post/Post_Preview_Service.php similarity index 97% rename from plugins/hwp-previews/src/Preview/Service/Post_Preview_Service.php rename to plugins/hwp-previews/src/Preview/Post/Post_Preview_Service.php index b491ff94..2b3d5903 100644 --- a/plugins/hwp-previews/src/Preview/Service/Post_Preview_Service.php +++ b/plugins/hwp-previews/src/Preview/Post/Post_Preview_Service.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace HWP\Previews\Preview\Service; +namespace HWP\Previews\Preview\Post; class Post_Preview_Service { /** diff --git a/plugins/hwp-previews/src/Preview/Service/Post_Settings_Service.php b/plugins/hwp-previews/src/Preview/Post/Post_Settings_Service.php similarity index 97% rename from plugins/hwp-previews/src/Preview/Service/Post_Settings_Service.php rename to plugins/hwp-previews/src/Preview/Post/Post_Settings_Service.php index 4834cc57..cdfda807 100644 --- a/plugins/hwp-previews/src/Preview/Service/Post_Settings_Service.php +++ b/plugins/hwp-previews/src/Preview/Post/Post_Settings_Service.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace HWP\Previews\Preview\Service; +namespace HWP\Previews\Preview\Post; class Post_Settings_Service { /** diff --git a/plugins/hwp-previews/src/Preview/Service/Post_Type_Service.php b/plugins/hwp-previews/src/Preview/Post/Post_Type_Service.php similarity index 72% rename from plugins/hwp-previews/src/Preview/Service/Post_Type_Service.php rename to plugins/hwp-previews/src/Preview/Post/Post_Type_Service.php index 46b5ad00..18499c9b 100644 --- a/plugins/hwp-previews/src/Preview/Service/Post_Type_Service.php +++ b/plugins/hwp-previews/src/Preview/Post/Post_Type_Service.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace HWP\Previews\Preview\Service; +namespace HWP\Previews\Preview\Post; use WP_Post; @@ -17,21 +17,21 @@ class Post_Type_Service { /** * Post-preview service that provide information about the post type for previews. * - * @var \HWP\Previews\Preview\Service\Post_Preview_Service + * @var \HWP\Previews\Preview\Post\Post_Preview_Service */ private Post_Preview_Service $post_preview_service; /** * Settings service to retrieve post type settings. * - * @var \HWP\Previews\Preview\Service\Post_Settings_Service + * @var \HWP\Previews\Preview\Post\Post_Settings_Service */ private Post_Settings_Service $post_settings_service; /** - * @param \WP_Post $post - * @param \HWP\Previews\Preview\Service\Post_Preview_Service $post_preview_service - * @param \HWP\Previews\Preview\Service\Post_Settings_Service $post_settings_service + * @param \WP_Post $post + * @param \HWP\Previews\Preview\Post\Post_Preview_Service $post_preview_service + * @param \HWP\Previews\Preview\Post\Post_Settings_Service $post_settings_service */ public function __construct( WP_Post $post, Post_Preview_Service $post_preview_service, Post_Settings_Service $post_settings_service ) { $this->post = $post; @@ -58,6 +58,7 @@ public function is_enabled(): bool { if ( ! isset( $config['enabled'] ) ) { return false; } + return (bool) $config['enabled']; } @@ -67,6 +68,7 @@ public function is_enabled(): bool { public function is_allowed_post_type(): bool { $post_type = $this->post->post_type; $post_types = $this->post_preview_service->get_post_types(); + return array_key_exists( $post_type, $post_types ); } @@ -76,6 +78,7 @@ public function is_allowed_post_type(): bool { public function is_allowed_post_status(): bool { $post_status = $this->post->post_status; $post_statuses = $this->post_preview_service->get_post_statuses(); + return in_array( $post_status, $post_statuses, true ); } @@ -88,9 +91,29 @@ public function is_iframe(): bool { return false; } + // @TODO - Key resolution. if ( ! isset( $config['in_iframe'] ) ) { return false; } + return (bool) $config['in_iframe']; } + + /** + * Retrieves the settings URL for the given post. + * + * @return string|null The settings URL or null if not found. + */ + public function get_preview_url(): ?string { + $config = $this->post_settings_service->get_post_type_config( $this->post->post_type ); + if ( ! is_array( $config ) ) { + return null; + } + + if ( ! isset( $config['preview_url'] ) ) { + return null; + } + + return (string) $config['preview_url']; + } } diff --git a/plugins/hwp-previews/src/Preview/Service/Template_Resolver_Service.php b/plugins/hwp-previews/src/Preview/Template/Template_Resolver_Service.php similarity index 96% rename from plugins/hwp-previews/src/Preview/Service/Template_Resolver_Service.php rename to plugins/hwp-previews/src/Preview/Template/Template_Resolver_Service.php index b7e69081..c266af4c 100644 --- a/plugins/hwp-previews/src/Preview/Service/Template_Resolver_Service.php +++ b/plugins/hwp-previews/src/Preview/Template/Template_Resolver_Service.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace HWP\Previews\Preview\Service; +namespace HWP\Previews\Preview\Template; class Template_Resolver_Service { /** diff --git a/plugins/hwp-previews/src/Preview/Link/Preview_Link_Placeholder_Resolver.php b/plugins/hwp-previews/src/Preview/Url/Preview_Url_Resolver_Service.php similarity index 56% rename from plugins/hwp-previews/src/Preview/Link/Preview_Link_Placeholder_Resolver.php rename to plugins/hwp-previews/src/Preview/Url/Preview_Url_Resolver_Service.php index e8bd7195..c4efd7d7 100644 --- a/plugins/hwp-previews/src/Preview/Link/Preview_Link_Placeholder_Resolver.php +++ b/plugins/hwp-previews/src/Preview/Url/Preview_Url_Resolver_Service.php @@ -2,13 +2,13 @@ declare(strict_types=1); -namespace HWP\Previews\Preview\Link; +namespace HWP\Previews\Preview\Url; use HWP\Previews\Preview\Parameter\Contracts\Preview_Parameter_Interface; use HWP\Previews\Preview\Parameter\Preview_Parameter_Registry; use WP_Post; -class Preview_Link_Placeholder_Resolver { +class Preview_Url_Resolver_Service { /** * Placeholder pattern in curly brackets - https://localhost:3000/preview={example}. * @@ -17,39 +17,41 @@ class Preview_Link_Placeholder_Resolver { public const PLACEHOLDER_REGEX = '/\{([A-Za-z0-9_]+)\}/'; /** - * Placeholder not found message. + * Placeholder fpr not found message. * * @var string */ public const PLACEHOLDER_NOT_FOUND = 'PARAMETER_NOT_FOUND'; /** - * Preview parameter registry. + * The registry of preview parameters. * * @var \HWP\Previews\Preview\Parameter\Preview_Parameter_Registry */ - private Preview_Parameter_Registry $registry; + protected Preview_Parameter_Registry $parameter_registry; /** * Constructor. * - * @param \HWP\Previews\Preview\Parameter\Preview_Parameter_Registry $registry Preview parameter registry. + * @param \HWP\Previews\Preview\Parameter\Preview_Parameter_Registry $parameter_registry The registry of preview parameters. */ - public function __construct(Preview_Parameter_Registry $registry) { - $this->registry = $registry; + public function __construct(Preview_Parameter_Registry $parameter_registry ) { + $this->parameter_registry = $parameter_registry; } /** - * Replace all {PLACEHOLDER} tokens in template string with urlencoded string values from callbacks. + * Resolves the parameters in the given URL using the post data. * - * @param string $template The string containing {KEY} placeholders. - * @param \WP_Post $post The post object to resolve the tokens against. + * @param \WP_Post $post The post object. + * @param string $url The URL containing placeholders to resolve. + * + * @return string|null The URL with resolved parameters or null if not found. */ - public function resolve_placeholders(string $template, WP_Post $post ): string { + public function resolve( WP_Post $post, string $url ): ?string { return (string) preg_replace_callback( self::PLACEHOLDER_REGEX, fn(array $matches): string => rawurlencode( $this->resolve_token( $matches[1], $post ) ), - $template + $url ); } @@ -59,8 +61,8 @@ public function resolve_placeholders(string $template, WP_Post $post ): string { * @param string $key The token key without braces. * @param \WP_Post $post Post object to resolve the token against. */ - private function resolve_token( string $key, WP_Post $post ): string { - $parameter = $this->registry->get( $key ); + public function resolve_token( string $key, WP_Post $post ): string { + $parameter = $this->parameter_registry->get( $key ); if ( ! $parameter instanceof Preview_Parameter_Interface ) { return self::PLACEHOLDER_NOT_FOUND; } diff --git a/plugins/hwp-previews/src/Templates/iframe.php b/plugins/hwp-previews/src/Templates/iframe.php index b2ca177e..1dd60fd1 100644 --- a/plugins/hwp-previews/src/Templates/iframe.php +++ b/plugins/hwp-previews/src/Templates/iframe.php @@ -9,7 +9,7 @@ declare(strict_types=1); -$hwp_previews_url_template = \HWP\Previews\Preview\Service\Template_Resolver_Service::get_query_variable(); +$hwp_previews_url_template = \HWP\Previews\Preview\Template\Template_Resolver_Service::get_query_variable(); ?> diff --git a/plugins/hwp-previews/tests/wpunit/Preview/Service/PostPreviewServiceTest.php b/plugins/hwp-previews/tests/wpunit/Preview/Post/PostPreviewServiceTest.php similarity index 96% rename from plugins/hwp-previews/tests/wpunit/Preview/Service/PostPreviewServiceTest.php rename to plugins/hwp-previews/tests/wpunit/Preview/Post/PostPreviewServiceTest.php index 09e9ed3f..05a0acb5 100644 --- a/plugins/hwp-previews/tests/wpunit/Preview/Service/PostPreviewServiceTest.php +++ b/plugins/hwp-previews/tests/wpunit/Preview/Post/PostPreviewServiceTest.php @@ -3,9 +3,9 @@ declare( strict_types=1 ); -namespace HWP\Previews\Tests\Unit\Preview\Service; +namespace HWP\Previews\Tests\Unit\Preview\Post; -use HWP\Previews\Preview\Service\Post_Preview_Service; +use HWP\Previews\Preview\Post\Post_Preview_Service; use lucatume\WPBrowser\TestCase\WPTestCase; /** diff --git a/plugins/hwp-previews/tests/wpunit/Preview/Service/PostServiceSettingsTest.php b/plugins/hwp-previews/tests/wpunit/Preview/Post/PostServiceSettingsTest.php similarity index 98% rename from plugins/hwp-previews/tests/wpunit/Preview/Service/PostServiceSettingsTest.php rename to plugins/hwp-previews/tests/wpunit/Preview/Post/PostServiceSettingsTest.php index fdc46265..599ca2fd 100644 --- a/plugins/hwp-previews/tests/wpunit/Preview/Service/PostServiceSettingsTest.php +++ b/plugins/hwp-previews/tests/wpunit/Preview/Post/PostServiceSettingsTest.php @@ -2,9 +2,9 @@ declare( strict_types=1 ); -namespace HWP\Previews\Tests\Unit\Preview\Service; +namespace HWP\Previews\Tests\Unit\Preview\Post; -use HWP\Previews\Preview\Service\Post_Settings_Service; +use HWP\Previews\Preview\Post\Post_Settings_Service; use lucatume\WPBrowser\TestCase\WPTestCase; /** diff --git a/plugins/hwp-previews/tests/wpunit/Preview/Service/PostTypeServiceTest.php b/plugins/hwp-previews/tests/wpunit/Preview/Post/PostTypeServiceTest.php similarity index 96% rename from plugins/hwp-previews/tests/wpunit/Preview/Service/PostTypeServiceTest.php rename to plugins/hwp-previews/tests/wpunit/Preview/Post/PostTypeServiceTest.php index 1f79cfbc..b42d9395 100644 --- a/plugins/hwp-previews/tests/wpunit/Preview/Service/PostTypeServiceTest.php +++ b/plugins/hwp-previews/tests/wpunit/Preview/Post/PostTypeServiceTest.php @@ -2,11 +2,11 @@ declare( strict_types=1 ); -namespace HWP\Previews\Tests\Unit\Preview\Service; +namespace HWP\Previews\Tests\Unit\Preview\Post; -use HWP\Previews\Preview\Service\Post_Preview_Service; -use HWP\Previews\Preview\Service\Post_Settings_Service; -use HWP\Previews\Preview\Service\Post_Type_Service; +use HWP\Previews\Preview\Post\Post_Preview_Service; +use HWP\Previews\Preview\Post\Post_Settings_Service; +use HWP\Previews\Preview\Post\Post_Type_Service; use lucatume\WPBrowser\TestCase\WPTestCase; use WP_Post; diff --git a/plugins/hwp-previews/tests/wpunit/Preview/Service/TemplateResolverServiceTest.php b/plugins/hwp-previews/tests/wpunit/Preview/Template/TemplateResolverServiceTest.php similarity index 95% rename from plugins/hwp-previews/tests/wpunit/Preview/Service/TemplateResolverServiceTest.php rename to plugins/hwp-previews/tests/wpunit/Preview/Template/TemplateResolverServiceTest.php index af452083..18835999 100644 --- a/plugins/hwp-previews/tests/wpunit/Preview/Service/TemplateResolverServiceTest.php +++ b/plugins/hwp-previews/tests/wpunit/Preview/Template/TemplateResolverServiceTest.php @@ -2,11 +2,12 @@ declare( strict_types=1 ); -namespace HWP\Previews\Tests\Unit\Preview\Service; +namespace HWP\Previews\Tests\Unit\Preview\Template; -use HWP\Previews\Preview\Service\Template_Resolver_Service; +use HWP\Previews\Preview\Template\Template_Resolver_Service; +use lucatume\WPBrowser\TestCase\WPTestCase; -class Template_Resolver_Service_Test extends \lucatume\WPBrowser\TestCase\WPTestCase { +class Template_Resolver_Service_Test extends WPTestCase { /** From c7b62069c335eea527117e75e5955a3467de48fe Mon Sep 17 00:00:00 2001 From: Colin Murphy Date: Wed, 18 Jun 2025 11:24:11 +0100 Subject: [PATCH 08/35] Refactored post parent functionality. Added constants for default fields. Added more tests. --- plugins/hwp-previews/ACTIONS_AND_FILTERS.md | 4 ++ .../Fields/Settings_Field_Collection.php | 38 +++++++++-- .../hwp-previews/src/Hooks/Preview_Hooks.php | 51 ++++++++------ .../src/Preview/Helper/Settings_Helper.php | 12 ---- .../Post_Parent_Manager_Interface.php | 16 ----- .../Post/Parent/Post_Parent_Manager.php | 66 ------------------- .../src/Preview/Post/Post_Preview_Service.php | 33 ++++++++++ .../src/Preview/Post/Post_Type_Service.php | 18 +++-- .../Preview/Post/PostPreviewServiceTest.php | 43 ++++++++---- .../Preview/Post/PostTypeServiceTest.php | 57 +++++++++------- 10 files changed, 179 insertions(+), 159 deletions(-) delete mode 100644 plugins/hwp-previews/src/Preview/Post/Parent/Contracts/Post_Parent_Manager_Interface.php delete mode 100644 plugins/hwp-previews/src/Preview/Post/Parent/Post_Parent_Manager.php diff --git a/plugins/hwp-previews/ACTIONS_AND_FILTERS.md b/plugins/hwp-previews/ACTIONS_AND_FILTERS.md index 66647764..14f602df 100644 --- a/plugins/hwp-previews/ACTIONS_AND_FILTERS.md +++ b/plugins/hwp-previews/ACTIONS_AND_FILTERS.md @@ -13,6 +13,7 @@ - `hwp_previews_core` - Register or unregister URL parameters, and adjust types/statuses - `hwp_previews_filter_available_post_types` - Filter to modify the available post types for Previews. - `hwp_previews_filter_available_post_statuses` - Filter for post statuses for previews for Previews +- `hwp_previews_filter_available_parent_post_statuses` - Filter for parent post statuses for Previews - `hwp_previews_settings_group_option_key` - Filter to modify the settings group option key. Default is HWP_PREVIEWS_SETTINGS_KEY - `hwp_previews_settings_group_settings_group` - Filter to modify the settings group name. Default is HWP_PREVIEWS_SETTINGS_GROUP - `hwp_previews_settings_group_settings_config` - Filter to modify the settings array. See `Settings_Group` @@ -25,6 +26,9 @@ ## Usage Examples +@TODO - Redo + + ### Filter: Post Types List Modify which post types appear in the settings UI: diff --git a/plugins/hwp-previews/src/Admin/Settings/Fields/Settings_Field_Collection.php b/plugins/hwp-previews/src/Admin/Settings/Fields/Settings_Field_Collection.php index c4a43357..338eaaa0 100644 --- a/plugins/hwp-previews/src/Admin/Settings/Fields/Settings_Field_Collection.php +++ b/plugins/hwp-previews/src/Admin/Settings/Fields/Settings_Field_Collection.php @@ -8,6 +8,36 @@ use HWP\Previews\Admin\Settings\Fields\Field\URL_Input_Field; class Settings_Field_Collection { + /** + * The ID of the field that enables or disables the previews. + * + * @var string + */ + public const ENABLED_FIELD_ID = 'enabled'; + + + /** + * The ID of the field that allows using post-statuses as parent. + * + * @var string + */ + public const POST_STATUSES_AS_PARENT_FIELD_ID = 'post_statuses_as_parent'; + + + /** + * The ID of the field that indicates whether the preview is rendered in an iframe. + * + * @var string + */ + public const IN_IFRAME_FIELD_ID = 'in_iframe'; + + /** + * The ID of the field that contains the preview URL. + * + * @var string + */ + public const PREVIEW_URL_FIELD_ID = 'preview_url'; + /** * Array of fields * @@ -66,7 +96,7 @@ protected function initialize_fields(): void { $this->add_field( new Checkbox_Field( - 'enabled', + self::ENABLED_FIELD_ID, false, __( 'Enable Previews', 'hwp-previews' ), __( 'Enable previews for post type.', 'hwp-previews' ) @@ -75,7 +105,7 @@ protected function initialize_fields(): void { $this->add_field( new Checkbox_Field( - 'post_statuses_as_parent', + self::POST_STATUSES_AS_PARENT_FIELD_ID, true, __( 'Allow all post statuses in parents option', 'hwp-previews' ), __( 'By default WordPress only allows published posts to be parents. This option allows posts of all statuses to be used as parent within hierarchical post types.', 'hwp-previews' ) @@ -84,7 +114,7 @@ protected function initialize_fields(): void { $this->add_field( new Checkbox_Field( - 'in_iframe', + self::IN_IFRAME_FIELD_ID, false, __( 'Use iframe to render previews', 'hwp-previews' ), __( 'With this option enabled, headless previews will be displayed inside an iframe on the preview page, without leaving WordPress.', 'hwp-previews' ) @@ -93,7 +123,7 @@ protected function initialize_fields(): void { $this->add_field( new URL_Input_Field( - 'preview_url', + self::PREVIEW_URL_FIELD_ID, false, __( 'Preview URL', 'hwp-previews' ), __( 'Construct your preview URL using the tags on the right. You can add any parameters needed to support headless previews.', 'hwp-previews' ), diff --git a/plugins/hwp-previews/src/Hooks/Preview_Hooks.php b/plugins/hwp-previews/src/Hooks/Preview_Hooks.php index 1526b9b2..e1292cbb 100644 --- a/plugins/hwp-previews/src/Hooks/Preview_Hooks.php +++ b/plugins/hwp-previews/src/Hooks/Preview_Hooks.php @@ -4,9 +4,8 @@ namespace HWP\Previews\Hooks; -use HWP\Previews\Preview\Helper\Settings_Helper; +use HWP\Previews\Admin\Settings\Fields\Settings_Field_Collection; use HWP\Previews\Preview\Parameter\Preview_Parameter_Registry; -use HWP\Previews\Preview\Post\Parent\Post_Parent_Manager; use HWP\Previews\Preview\Post\Post_Preview_Service; use HWP\Previews\Preview\Post\Post_Settings_Service; use HWP\Previews\Preview\Post\Post_Type_Service; @@ -20,13 +19,6 @@ use WP_REST_Response; class Preview_Hooks { - /** - * Settings helper instance that provides access to plugin settings. - * - * @var \HWP\Previews\Preview\Helper\Settings_Helper - */ - protected Settings_Helper $settings_helper; - /** * Post types configuration. * @@ -71,8 +63,6 @@ public function __construct() { // @TODO - Refactor to use a factory or service locator pattern and analyze what is is actually needed. - $this->settings_helper = Settings_Helper::get_instance(); - $this->types_config = apply_filters( 'hwp_previews_hooks_post_type_config', Post_Types_Config_Registry::get_post_type_config() @@ -153,26 +143,47 @@ public function get_post_statuses(): array { * @link https://developer.wordpress.org/reference/hooks/quick_edit_dropdown_pages_args/ */ public function enable_post_statuses_as_parent( array $args ): array { + if ( ! $this->should_enable_post_statuses_as_parent( $args ) ) { + return $args; + } - if ( empty( $args['post_type'] ) ) { + $parent_statuses = $this->post_preview_service->get_parent_post_statuses(); + $post_statuses = $this->post_preview_service->get_post_statuses(); + $post_statuses = array_intersect( $parent_statuses, $post_statuses ); + + if ( empty( $post_statuses ) ) { return $args; } - $post_parent_manager = new Post_Parent_Manager( $this->types_config, $this->statuses_config ); + $args['post_status'] = $post_statuses; + + return $args; + } + + /** + * Whether post-statuses should be enabled as parent for the given post-type. + * + * @param array $args + */ + public function should_enable_post_statuses_as_parent( array $args ): bool { + if ( empty( $args['post_type'] ) ) { + return false; + } $post_type = (string) $args['post_type']; - // Check if the correspondent setting is enabled. - if ( ! $this->settings_helper->post_statuses_as_parent( $post_type ) ) { - return $args; + if ( ! is_post_type_hierarchical( $post_type ) ) { + return false; } - $post_statuses = $post_parent_manager->get_post_statuses_as_parent( $post_type ); - if ( ! empty( $post_statuses ) ) { - $args['post_status'] = $post_statuses; + $config = $this->post_settings_service->get_post_type_config( $post_type ); + if ( ! is_array( $config ) || empty( $config ) ) { + return false; } - return $args; + $field_id = Settings_Field_Collection::POST_STATUSES_AS_PARENT_FIELD_ID; + + return isset( $config[ $field_id ] ) && (bool) $config[ $field_id ]; } /** diff --git a/plugins/hwp-previews/src/Preview/Helper/Settings_Helper.php b/plugins/hwp-previews/src/Preview/Helper/Settings_Helper.php index 66ad43f8..9e0e6b93 100644 --- a/plugins/hwp-previews/src/Preview/Helper/Settings_Helper.php +++ b/plugins/hwp-previews/src/Preview/Helper/Settings_Helper.php @@ -68,16 +68,4 @@ public function post_types_enabled( array $default_value = [] ): array { } return $default_value; } - - /** - * Get Post Statuses as Parent setting value for the given post type. - * - * @param string $post_type The post type to get the setting for. - * @param bool $default_value The default value to return if the setting is not set. - */ - public function post_statuses_as_parent( string $post_type, bool $default_value = false ): bool { - $key = $this->settings_group->get_settings_key_post_statuses_as_parent(); - - return $this->settings_group->get_post_type_boolean_value( $key, $post_type, $default_value ); - } } diff --git a/plugins/hwp-previews/src/Preview/Post/Parent/Contracts/Post_Parent_Manager_Interface.php b/plugins/hwp-previews/src/Preview/Post/Parent/Contracts/Post_Parent_Manager_Interface.php deleted file mode 100644 index 094b7b2e..00000000 --- a/plugins/hwp-previews/src/Preview/Post/Parent/Contracts/Post_Parent_Manager_Interface.php +++ /dev/null @@ -1,16 +0,0 @@ - - */ - public function get_post_statuses_as_parent( string $post_type ): array; -} diff --git a/plugins/hwp-previews/src/Preview/Post/Parent/Post_Parent_Manager.php b/plugins/hwp-previews/src/Preview/Post/Parent/Post_Parent_Manager.php deleted file mode 100644 index 0aaf834b..00000000 --- a/plugins/hwp-previews/src/Preview/Post/Parent/Post_Parent_Manager.php +++ /dev/null @@ -1,66 +0,0 @@ - - */ - public const POST_STATUSES = [ 'publish', 'future', 'draft', 'pending', 'private' ]; - - /** - * Post types configuration. - * - * @var \HWP\Previews\Preview\Post\Type\Contracts\Post_Types_Config_Interface - */ - private Post_Types_Config_Interface $post_types; - - /** - * Post statuses configuration. - * - * @var \HWP\Previews\Preview\Post\Status\Contracts\Post_Statuses_Config_Interface - */ - private Post_Statuses_Config_Interface $post_statuses; - - /** - * Post_Parent_Manager constructor. - * - * @param \HWP\Previews\Preview\Post\Type\Contracts\Post_Types_Config_Interface $post_types Post types configuration. - * @param \HWP\Previews\Preview\Post\Status\Contracts\Post_Statuses_Config_Interface $post_statuses Post statuses configuration. - */ - public function __construct( Post_Types_Config_Interface $post_types, Post_Statuses_Config_Interface $post_statuses ) { - $this->post_types = $post_types; - $this->post_statuses = $post_statuses; - } - - /** - * Get the post statuses that can be used as parent for a given post type. - * - * @param string $post_type Post Type slug. - * - * @return array - */ - public function get_post_statuses_as_parent( string $post_type ): array { - if ( - ! $this->post_types->is_post_type_applicable( $post_type ) || - ! $this->post_types->is_hierarchical( $post_type ) - ) { - return []; - } - - return array_intersect( self::POST_STATUSES, $this->post_statuses->get_post_statuses() ); - } -} diff --git a/plugins/hwp-previews/src/Preview/Post/Post_Preview_Service.php b/plugins/hwp-previews/src/Preview/Post/Post_Preview_Service.php index 2b3d5903..c39c39a9 100644 --- a/plugins/hwp-previews/src/Preview/Post/Post_Preview_Service.php +++ b/plugins/hwp-previews/src/Preview/Post/Post_Preview_Service.php @@ -19,6 +19,13 @@ class Post_Preview_Service { */ protected $post_statuses = []; + /** + * The allowed post-statuses for parent post-types (hierarchical). + * + * @var array + */ + protected $parent_post_statuses = []; + /** * Constructor for the Post_Preview_Service class. * @@ -27,6 +34,7 @@ class Post_Preview_Service { public function __construct() { $this->set_post_types(); $this->set_post_statuses(); + $this->set_post_parent_statuses(); } /** @@ -54,6 +62,15 @@ public function get_post_types(): array { return $this->post_types; } + /** + * Get the parent post statuses + * + * @return array + */ + public function get_parent_post_statuses(): array { + return $this->parent_post_statuses; + } + /** * Sets the allowed post types for previews. */ @@ -85,4 +102,20 @@ protected function set_post_statuses(): void { $this->post_statuses = apply_filters( 'hwp_previews_filter_available_post_statuses', $post_statuses ); } + + /** + * Sets the allowed post statuses for parent post-types (hierarchael). + */ + protected function set_post_parent_statuses(): void { + + $post_statuses = [ + 'publish', + 'future', + 'draft', + 'pending', + 'private' + ]; + + $this->parent_post_statuses = apply_filters( 'hwp_previews_filter_available_parent_post_statuses', $post_statuses ); + } } diff --git a/plugins/hwp-previews/src/Preview/Post/Post_Type_Service.php b/plugins/hwp-previews/src/Preview/Post/Post_Type_Service.php index 18499c9b..31c5a1a1 100644 --- a/plugins/hwp-previews/src/Preview/Post/Post_Type_Service.php +++ b/plugins/hwp-previews/src/Preview/Post/Post_Type_Service.php @@ -4,6 +4,7 @@ namespace HWP\Previews\Preview\Post; +use HWP\Previews\Admin\Settings\Fields\Settings_Field_Collection; use WP_Post; class Post_Type_Service { @@ -55,11 +56,13 @@ public function is_enabled(): bool { return false; } - if ( ! isset( $config['enabled'] ) ) { + $field_id = Settings_Field_Collection::ENABLED_FIELD_ID; + + if ( ! isset( $config[ $field_id ] ) ) { return false; } - return (bool) $config['enabled']; + return (bool) $config[ $field_id ]; } /** @@ -91,12 +94,12 @@ public function is_iframe(): bool { return false; } - // @TODO - Key resolution. - if ( ! isset( $config['in_iframe'] ) ) { + $field_id = Settings_Field_Collection::IN_IFRAME_FIELD_ID; + if ( ! isset( $config[ $field_id ] ) ) { return false; } - return (bool) $config['in_iframe']; + return (bool) $config[ $field_id ]; } /** @@ -110,10 +113,11 @@ public function get_preview_url(): ?string { return null; } - if ( ! isset( $config['preview_url'] ) ) { + $field_id = Settings_Field_Collection::PREVIEW_URL_FIELD_ID; + if ( ! isset( $config[ $field_id ] ) ) { return null; } - return (string) $config['preview_url']; + return (string) $config[ $field_id ]; } } diff --git a/plugins/hwp-previews/tests/wpunit/Preview/Post/PostPreviewServiceTest.php b/plugins/hwp-previews/tests/wpunit/Preview/Post/PostPreviewServiceTest.php index 05a0acb5..a023de95 100644 --- a/plugins/hwp-previews/tests/wpunit/Preview/Post/PostPreviewServiceTest.php +++ b/plugins/hwp-previews/tests/wpunit/Preview/Post/PostPreviewServiceTest.php @@ -41,7 +41,27 @@ public function test_get_allowed_post_types_returns_array(): void { public function test_get_post_statuses_returns_default_statuses(): void { $result = $this->service->get_post_statuses(); - $expected = ['publish', 'future', 'draft', 'pending', 'private', 'auto-draft']; + $expected = [ + 'publish', + 'future', + 'draft', + 'pending', + 'private', + 'auto-draft', + ]; + $this->assertEquals($expected, $result); + } + + public function test_get_parent_post_statuses_returns_default_statuses(): void { + $result = $this->service->get_parent_post_statuses(); + + $expected = [ + 'publish', + 'future', + 'draft', + 'pending', + 'private' + ]; $this->assertEquals($expected, $result); } @@ -53,44 +73,43 @@ public function test_get_post_types_returns_same_as_get_allowed_post_types(): vo } public function test_post_types_filter_is_applied(): void { - // Arrange $custom_post_types = ['custom_post' => 'Custom Post Type']; add_filter('hwp_previews_filter_available_post_types', function() use ($custom_post_types) { return $custom_post_types; }); - // Act $service = new Post_Preview_Service(); $result = $service->get_post_types(); - - // Assert $this->assertEquals($custom_post_types, $result); } public function test_post_statuses_filter_is_applied(): void { - // Arrange $custom_statuses = ['custom_status']; add_filter('hwp_previews_filter_available_post_statuses', function() use ($custom_statuses) { return $custom_statuses; }); - // Act $service = new Post_Preview_Service(); $result = $service->get_post_statuses(); + $this->assertEquals($custom_statuses, $result); + } - // Assert + public function test_parent_post_statuses_filter_is_applied(): void { + $custom_statuses = ['custom_status']; + add_filter('hwp_previews_filter_available_parent_post_statuses', function() use ($custom_statuses) { + return $custom_statuses; + }); + + $service = new Post_Preview_Service(); + $result = $service->get_parent_post_statuses(); $this->assertEquals($custom_statuses, $result); } public function test_constructor_initializes_post_types_and_statuses(): void { - // Act $service = new Post_Preview_Service(); - // Assert $this->assertIsArray($service->get_post_types()); $this->assertIsArray($service->get_post_statuses()); - - // Ensure post statuses are not empty (should have default values) $this->assertNotEmpty($service->get_post_statuses()); } } diff --git a/plugins/hwp-previews/tests/wpunit/Preview/Post/PostTypeServiceTest.php b/plugins/hwp-previews/tests/wpunit/Preview/Post/PostTypeServiceTest.php index b42d9395..f6a89afc 100644 --- a/plugins/hwp-previews/tests/wpunit/Preview/Post/PostTypeServiceTest.php +++ b/plugins/hwp-previews/tests/wpunit/Preview/Post/PostTypeServiceTest.php @@ -4,6 +4,7 @@ namespace HWP\Previews\Tests\Unit\Preview\Post; +use HWP\Previews\Admin\Settings\Fields\Settings_Field_Collection; use HWP\Previews\Preview\Post\Post_Preview_Service; use HWP\Previews\Preview\Post\Post_Settings_Service; use HWP\Previews\Preview\Post\Post_Type_Service; @@ -56,7 +57,7 @@ public function test_is_allowed_for_previews_when_enabled_and_post_type_and_stat $this->post_settings_service_mock ->method( 'get_post_type_config' ) - ->willReturn( [ 'enabled' => true ] ); + ->willReturn( [ Settings_Field_Collection::ENABLED_FIELD_ID => true ] ); $this->post_preview_service_mock ->method( 'get_post_types' ) @@ -74,7 +75,7 @@ public function test_is_not_allowed_for_previews_when_enabled_and_post_type_and_ $this->post_settings_service_mock ->method( 'get_post_type_config' ) - ->willReturn( [ 'enabled' => true ] ); + ->willReturn( [ Settings_Field_Collection::ENABLED_FIELD_ID => true ] ); $this->post_preview_service_mock ->method( 'get_post_types' ) @@ -111,7 +112,7 @@ public function test_is_not_allowed_for_previews_when_enabled_and_post_type_and_ public function test_is_allowed_for_previews_returns_false_when_not_enabled(): void { $this->post_settings_service_mock ->method( 'get_post_type_config' ) - ->willReturn( [ 'enabled' => false ] ); + ->willReturn( [ Settings_Field_Collection::ENABLED_FIELD_ID => false ] ); $result = $this->service->is_allowed_for_previews(); @@ -120,61 +121,45 @@ public function test_is_allowed_for_previews_returns_false_when_not_enabled(): v public function test_is_enabled_returns_false_when_config_not_array(): void { - // Arrange $this->post_settings_service_mock ->method( 'get_post_type_config' ) ->willReturn( null ); - // Act $result = $this->service->is_enabled(); - - // Assert $this->assertFalse( $result ); } public function test_is_enabled_returns_false_when_enabled_key_missing(): void { - // Arrange $this->post_settings_service_mock ->method( 'get_post_type_config' ) ->willReturn( [ 'other_setting' => true ] ); - // Act $result = $this->service->is_enabled(); - - // Assert $this->assertFalse( $result ); } public function test_is_allowed_post_type_returns_true_when_post_type_exists(): void { - // Arrange $this->post_preview_service_mock ->method( 'get_post_types' ) ->willReturn( [ 'post' => 'Posts', 'page' => 'Pages' ] ); - // Act $result = $this->service->is_allowed_post_type(); - - // Assert $this->assertTrue( $result ); } public function test_is_allowed_post_type_returns_false_when_post_type_not_exists(): void { - // Arrange $this->post_preview_service_mock ->method( 'get_post_types' ) ->willReturn( [ 'page' => 'Pages' ] ); - // Act $result = $this->service->is_allowed_post_type(); - - // Assert $this->assertFalse( $result ); } public function test_is_iframe_returns_true_when_enabled(): void { $this->post_settings_service_mock ->method( 'get_post_type_config' ) - ->willReturn( [ 'in_iframe' => true ] ); + ->willReturn( [ Settings_Field_Collection::IN_IFRAME_FIELD_ID => true ] ); $result = $this->service->is_iframe(); $this->assertTrue( $result ); @@ -183,7 +168,7 @@ public function test_is_iframe_returns_true_when_enabled(): void { public function test_is_iframe_returns_false_when_disabled(): void { $this->post_settings_service_mock ->method( 'get_post_type_config' ) - ->willReturn( [ 'in_iframe' => false ] ); + ->willReturn( [ Settings_Field_Collection::IN_IFRAME_FIELD_ID => false ] ); $result = $this->service->is_iframe(); $this->assertFalse( $result ); @@ -201,9 +186,37 @@ public function test_is_iframe_returns_false_when_config_not_array(): void { public function test_is_iframe_returns_false_when_iframe_key_missing(): void { $this->post_settings_service_mock ->method( 'get_post_type_config' ) - ->willReturn( [ 'enabled' => true ] ); + ->willReturn( [ Settings_Field_Collection::ENABLED_FIELD_ID => true ] ); $result = $this->service->is_iframe(); $this->assertFalse( $result ); } + + public function test_get_preview_url_returns_url_when_set(): void { + $expected_url = 'https://example.com/preview'; + $this->post_settings_service_mock + ->method('get_post_type_config') + ->willReturn([ Settings_Field_Collection::PREVIEW_URL_FIELD_ID => $expected_url ]); + + $result = $this->service->get_preview_url(); + $this->assertSame($expected_url, $result); + } + + public function test_get_preview_url_returns_null_when_config_not_array(): void { + $this->post_settings_service_mock + ->method('get_post_type_config') + ->willReturn(null); + + $result = $this->service->get_preview_url(); + $this->assertNull($result); + } + + public function test_get_preview_url_returns_null_when_field_missing(): void { + $this->post_settings_service_mock + ->method('get_post_type_config') + ->willReturn([ Settings_Field_Collection::ENABLED_FIELD_ID => true ]); + + $result = $this->service->get_preview_url(); + $this->assertNull($result); + } } From 595af07aa98af4acfd6c6adaea5efc746312fefc Mon Sep 17 00:00:00 2001 From: Colin Murphy Date: Wed, 18 Jun 2025 11:30:47 +0100 Subject: [PATCH 09/35] Fixed linting issues. --- .../hwp-previews/src/Hooks/Preview_Hooks.php | 35 ------------------- .../src/Preview/Post/Post_Preview_Service.php | 2 +- 2 files changed, 1 insertion(+), 36 deletions(-) diff --git a/plugins/hwp-previews/src/Hooks/Preview_Hooks.php b/plugins/hwp-previews/src/Hooks/Preview_Hooks.php index e1292cbb..2efab92a 100644 --- a/plugins/hwp-previews/src/Hooks/Preview_Hooks.php +++ b/plugins/hwp-previews/src/Hooks/Preview_Hooks.php @@ -9,8 +9,6 @@ use HWP\Previews\Preview\Post\Post_Preview_Service; use HWP\Previews\Preview\Post\Post_Settings_Service; use HWP\Previews\Preview\Post\Post_Type_Service; -use HWP\Previews\Preview\Post\Status\Contracts\Post_Statuses_Config_Interface; -use HWP\Previews\Preview\Post\Status\Post_Statuses_Config; use HWP\Previews\Preview\Post\Type\Contracts\Post_Types_Config_Interface; use HWP\Previews\Preview\Post\Type\Post_Types_Config_Registry; use HWP\Previews\Preview\Template\Template_Resolver_Service; @@ -26,13 +24,6 @@ class Preview_Hooks { */ protected Post_Types_Config_Interface $types_config; - /** - * Post statuses configuration. - * - * @var \HWP\Previews\Preview\Post\Status\Contracts\Post_Statuses_Config_Interface - */ - protected Post_Statuses_Config_Interface $statuses_config; - /** * Post-settings service that provides access to post-settings. * @@ -68,13 +59,6 @@ public function __construct() { Post_Types_Config_Registry::get_post_type_config() ); - // Initialize the post types and statuses configurations. - $this->statuses_config = apply_filters( - 'hwp_previews_hooks_post_status_config', - ( new Post_Statuses_Config() )->set_post_statuses( $this->get_post_statuses() ) - ); - - $this->post_preview_service = new Post_Preview_Service(); $this->post_settings_service = new Post_Settings_Service(); } @@ -112,25 +96,6 @@ public function setup(): void { } } - /** - * Gets a list of available post statuses for the preview functionality.. - * - * @return array - */ - public function get_post_statuses(): array { - // @TODO - Remove - $post_statuses = [ - 'publish', - 'future', - 'draft', - 'pending', - 'private', - 'auto-draft', - ]; - - return apply_filters( 'hwp_previews_hooks_post_statuses', $post_statuses ); - } - /** * Enable post statuses as parent for the post types specified in the post types config. * diff --git a/plugins/hwp-previews/src/Preview/Post/Post_Preview_Service.php b/plugins/hwp-previews/src/Preview/Post/Post_Preview_Service.php index c39c39a9..05325e2c 100644 --- a/plugins/hwp-previews/src/Preview/Post/Post_Preview_Service.php +++ b/plugins/hwp-previews/src/Preview/Post/Post_Preview_Service.php @@ -113,7 +113,7 @@ protected function set_post_parent_statuses(): void { 'future', 'draft', 'pending', - 'private' + 'private', ]; $this->parent_post_statuses = apply_filters( 'hwp_previews_filter_available_parent_post_statuses', $post_statuses ); From 0f3b0bf5c0c0c8735407cdd7d0e7e1d7eff82426 Mon Sep 17 00:00:00 2001 From: Colin Murphy Date: Wed, 18 Jun 2025 18:11:25 +0100 Subject: [PATCH 10/35] Refactored checking if classic editor is available. Added tests. --- .../hwp-previews/src/Admin/Settings_Page.php | 21 +- .../hwp-previews/src/Hooks/Preview_Hooks.php | 34 +-- .../src/Preview/Post/Post_Editor_Service.php | 61 ++++ .../Post_Statuses_Config_Interface.php | 28 -- .../Post/Status/Post_Statuses_Config.php | 50 ---- .../tests/wpunit/Admin/SettingsPageTest.php | 279 ------------------ .../Preview/Post/PostEditorServiceTest.php | 123 ++++++++ ...gsTest.php => PostSettingsServiceTest.php} | 39 +-- 8 files changed, 208 insertions(+), 427 deletions(-) create mode 100644 plugins/hwp-previews/src/Preview/Post/Post_Editor_Service.php delete mode 100644 plugins/hwp-previews/src/Preview/Post/Status/Contracts/Post_Statuses_Config_Interface.php delete mode 100644 plugins/hwp-previews/src/Preview/Post/Status/Post_Statuses_Config.php delete mode 100644 plugins/hwp-previews/tests/wpunit/Admin/SettingsPageTest.php create mode 100644 plugins/hwp-previews/tests/wpunit/Preview/Post/PostEditorServiceTest.php rename plugins/hwp-previews/tests/wpunit/Preview/Post/{PostServiceSettingsTest.php => PostSettingsServiceTest.php} (91%) diff --git a/plugins/hwp-previews/src/Admin/Settings_Page.php b/plugins/hwp-previews/src/Admin/Settings_Page.php index 63ac1bd2..4a8b5e29 100644 --- a/plugins/hwp-previews/src/Admin/Settings_Page.php +++ b/plugins/hwp-previews/src/Admin/Settings_Page.php @@ -8,8 +8,7 @@ use HWP\Previews\Admin\Settings\Menu\Menu_Page; use HWP\Previews\Admin\Settings\Settings_Form_Manager; use HWP\Previews\Preview\Parameter\Preview_Parameter_Registry; -use HWP\Previews\Preview\Post\Type\Contracts\Post_Types_Config_Interface; -use HWP\Previews\Preview\Post\Type\Post_Types_Config_Registry; +use HWP\Previews\Preview\Post\Post_Preview_Service; class Settings_Page { /** @@ -23,9 +22,11 @@ class Settings_Page { protected Preview_Parameter_Registry $parameters; /** - * @var \HWP\Previews\Preview\Post\Type\Contracts\Post_Types_Config_Interface The post types available for previews. + * Post-preview service to get post types and statuses for the settings page. + * + * @var \HWP\Previews\Preview\Post\Post_Preview_Service */ - protected Post_Types_Config_Interface $types_config; + protected Post_Preview_Service $post_preview_service; /** * The instance of the plugin. @@ -40,8 +41,8 @@ class Settings_Page { * Initializes the settings page, registers settings fields, and loads scripts and styles. */ public function __construct() { - $this->parameters = Preview_Parameter_Registry::get_instance(); - $this->types_config = Post_Types_Config_Registry::get_post_type_config(); + $this->parameters = Preview_Parameter_Registry::get_instance(); + $this->post_preview_service = new Post_Preview_Service(); $this->register_settings_pages(); $this->register_settings_fields(); @@ -72,9 +73,9 @@ public static function init(): Settings_Page { public function register_settings_pages(): void { add_action( 'admin_menu', function (): void { - // Note: We didn't initalize in the constructor because we need to ensure - // the post types are registered before we can use them. - $post_types = $this->types_config->get_public_post_types(); + // Note: We didn't initalise in the constructor because we need to ensure + // the post-types are registered before we can use them. + $post_types = $this->post_preview_service->get_post_types(); $page = new Menu_Page( __( 'HWP Previews Settings', 'hwp-previews' ), @@ -100,7 +101,7 @@ public function register_settings_pages(): void { public function register_settings_fields(): void { add_action( 'admin_init', function (): void { $settings_manager = new Settings_Form_Manager( - $this->types_config->get_public_post_types(), + $this->post_preview_service->get_post_types(), new Settings_Field_Collection() ); $settings_manager->render_form(); diff --git a/plugins/hwp-previews/src/Hooks/Preview_Hooks.php b/plugins/hwp-previews/src/Hooks/Preview_Hooks.php index 2efab92a..75eaee67 100644 --- a/plugins/hwp-previews/src/Hooks/Preview_Hooks.php +++ b/plugins/hwp-previews/src/Hooks/Preview_Hooks.php @@ -6,24 +6,16 @@ use HWP\Previews\Admin\Settings\Fields\Settings_Field_Collection; use HWP\Previews\Preview\Parameter\Preview_Parameter_Registry; +use HWP\Previews\Preview\Post\Post_Editor_Service; use HWP\Previews\Preview\Post\Post_Preview_Service; use HWP\Previews\Preview\Post\Post_Settings_Service; use HWP\Previews\Preview\Post\Post_Type_Service; -use HWP\Previews\Preview\Post\Type\Contracts\Post_Types_Config_Interface; -use HWP\Previews\Preview\Post\Type\Post_Types_Config_Registry; use HWP\Previews\Preview\Template\Template_Resolver_Service; use HWP\Previews\Preview\Url\Preview_Url_Resolver_Service; use WP_Post; use WP_REST_Response; class Preview_Hooks { - /** - * Post types configuration. - * - * @var \HWP\Previews\Preview\Post\Type\Contracts\Post_Types_Config_Interface - */ - protected Post_Types_Config_Interface $types_config; - /** * Post-settings service that provides access to post-settings. * @@ -51,14 +43,6 @@ class Preview_Hooks { * Initializes the settings helper, post types and statuses configurations, and the preview link service. */ public function __construct() { - - // @TODO - Refactor to use a factory or service locator pattern and analyze what is is actually needed. - - $this->types_config = apply_filters( - 'hwp_previews_hooks_post_type_config', - Post_Types_Config_Registry::get_post_type_config() - ); - $this->post_preview_service = new Post_Preview_Service(); $this->post_settings_service = new Post_Settings_Service(); } @@ -72,11 +56,15 @@ public function setup(): void { add_filter( 'page_attributes_dropdown_pages_args', [ $this, 'enable_post_statuses_as_parent' ], 10, 1 ); add_filter( 'quick_edit_dropdown_pages_args', [ $this, 'enable_post_statuses_as_parent' ], 10, 1 ); - foreach ( $this->types_config->get_post_types() as $post_type ) { - if ( ! $this->types_config->gutenberg_editor_enabled( $post_type ) ) { + $post_editor_service = new Post_Editor_Service(); + $post_types = $this->post_preview_service->get_post_types(); + + // Enable post-statuses as parent for the post-types specified in the post-types config. + foreach ( $post_types as $post_type => $label ) { + if ( ! $post_editor_service->gutenberg_editor_enabled( $post_type ) ) { continue; } - // @TODO - Add unit tests for this filter. + add_filter( 'rest_' . $post_type . '_query', [ $this, 'enable_post_statuses_as_parent' ], 10, 1 ); } @@ -91,13 +79,13 @@ public function setup(): void { * Hack Function that changes the preview link for draft articles, * this must be removed when properly fixed https://github.com/WordPress/gutenberg/issues/13998. */ - foreach ( $this->types_config->get_public_post_types() as $key => $label ) { - add_filter( 'rest_prepare_' . $key, [ $this, 'filter_rest_prepare_link' ], 10, 2 ); + foreach ( $post_types as $post_type => $label ) { + add_filter( 'rest_prepare_' . $post_type, [ $this, 'filter_rest_prepare_link' ], 10, 2 ); } } /** - * Enable post statuses as parent for the post types specified in the post types config. + * Enable post-statuses as parent for the post types specified in the post types config. * * @param array $args The arguments for the dropdown pages. * diff --git a/plugins/hwp-previews/src/Preview/Post/Post_Editor_Service.php b/plugins/hwp-previews/src/Preview/Post/Post_Editor_Service.php new file mode 100644 index 00000000..bc8f0d28 --- /dev/null +++ b/plugins/hwp-previews/src/Preview/Post/Post_Editor_Service.php @@ -0,0 +1,61 @@ +is_gutenberg_supported( $post_type_object ) && + ! $this->is_classic_editor_forced( $post_type ); + } + + /** + * Checks if the post type supports Gutenberg. + * + * @param \WP_Post_Type $post_type Post Type object. + */ + public function is_gutenberg_supported( WP_Post_Type $post_type ): bool { + + if (empty($post_type->show_in_rest)) { + return false; + } + + if (!post_type_supports($post_type->name, 'editor')) { + return false; + } + + return $post_type->show_in_rest; + } + + /** + * Checks if the post type is supported by Classic Editor. + */ + public function is_classic_editor_forced(string $post_type): bool { + if ( + ! function_exists( 'is_plugin_active' ) || + ! is_plugin_active( 'classic-editor/classic-editor.php' ) + ) { + return false; + } + + // If this post type is listed in Classic Editor settings, Gutenberg is disabled. + $settings = (array) get_option( 'classic-editor-settings', [] ); + + return ! empty( $settings['post_types'] ) && + is_array( $settings['post_types'] ) && + in_array( $post_type, $settings['post_types'], true ); + } +} diff --git a/plugins/hwp-previews/src/Preview/Post/Status/Contracts/Post_Statuses_Config_Interface.php b/plugins/hwp-previews/src/Preview/Post/Status/Contracts/Post_Statuses_Config_Interface.php deleted file mode 100644 index a0003fbe..00000000 --- a/plugins/hwp-previews/src/Preview/Post/Status/Contracts/Post_Statuses_Config_Interface.php +++ /dev/null @@ -1,28 +0,0 @@ - $post_statuses The post statuses to set. - */ - public function set_post_statuses( array $post_statuses ): self; - - /** - * Get the post statuses that are applicable for the plugin. - * - * @return array - */ - public function get_post_statuses(): array; - - /** - * Check if a given post status is applicable for the plugin. - * - * @param string $post_status Post status slug. - */ - public function is_post_status_applicable( string $post_status ): bool; -} diff --git a/plugins/hwp-previews/src/Preview/Post/Status/Post_Statuses_Config.php b/plugins/hwp-previews/src/Preview/Post/Status/Post_Statuses_Config.php deleted file mode 100644 index d0a1d4ff..00000000 --- a/plugins/hwp-previews/src/Preview/Post/Status/Post_Statuses_Config.php +++ /dev/null @@ -1,50 +0,0 @@ - - */ - private array $post_statuses = []; - - /** - * Sets the post statuses that are applicable for the plugin. - * - * @param array $post_statuses Post statuses that are applicable for the plugin. - * - * @return $this - */ - public function set_post_statuses( array $post_statuses ): self { - $this->post_statuses = $post_statuses; - - return $this; - } - - /** - * Get the post statuses that are applicable for the plugin. - * - * @return array Post statuses. - */ - public function get_post_statuses(): array { - return $this->post_statuses; - } - - /** - * Verifies if the post status is applicable according to the configuration. - * - * @param string $post_status Post status to check. - */ - public function is_post_status_applicable( string $post_status ): bool { - return in_array( $post_status, $this->post_statuses, true ); - } -} diff --git a/plugins/hwp-previews/tests/wpunit/Admin/SettingsPageTest.php b/plugins/hwp-previews/tests/wpunit/Admin/SettingsPageTest.php deleted file mode 100644 index be6db733..00000000 --- a/plugins/hwp-previews/tests/wpunit/Admin/SettingsPageTest.php +++ /dev/null @@ -1,279 +0,0 @@ -admin_user_id = $this->factory()->user->create([ - 'role' => 'administrator' - ]); - wp_set_current_user($this->admin_user_id); - - // Reset the singleton instance - $reflection = new \ReflectionClass(Settings_Page::class); - $instance_property = $reflection->getProperty('instance'); - $instance_property->setAccessible(true); - $instance_property->setValue(null, null); - - // Mock dependencies - $this->setupMocks(); - } - - public function tearDown(): void - { - // Clean up user - wp_delete_user($this->admin_user_id); - wp_set_current_user(0); - - // Reset singleton - $reflection = new \ReflectionClass(Settings_Page::class); - $instance_property = $reflection->getProperty('instance'); - $instance_property->setAccessible(true); - $instance_property->setValue(null, null); - - // Clear $_GET - $_GET = []; - - parent::tearDown(); - } - - private function setupMocks(): void - { - // Mock Preview_Parameter_Registry - $this->mock_parameter_registry = $this->createMock(Preview_Parameter_Registry::class); - $this->mock_parameter_registry->method('get_descriptions') - ->willReturn([ - 'param1' => 'Description 1', - 'param2' => 'Description 2' - ]); - - // Mock Post_Types_Config_Interface - $this->mock_post_types_config = $this->createMock(Post_Types_Config_Interface::class); - $this->mock_post_types_config->method('get_public_post_types') - ->willReturn([ - 'post' => 'Posts', - 'page' => 'Pages', - 'product' => 'Products' - ]); - } - - public function test_singleton_init_creates_instance() - { - // Use reflection to mock static method calls - $this->mockStaticDependencies(); - - $instance1 = Settings_Page::init(); - $instance2 = Settings_Page::init(); - - $this->assertInstanceOf(Settings_Page::class, $instance1); - $this->assertSame($instance1, $instance2, 'Should return the same instance (singleton)'); - } - - public function test_singleton_init_fires_action() - { - $this->mockStaticDependencies(); - - $action_fired = false; - add_action('hwp_previews_init', function($instance) use (&$action_fired) { - $action_fired = true; - $this->assertInstanceOf(Settings_Page::class, $instance); - }); - - Settings_Page::init(); - - $this->assertTrue($action_fired, 'hwp_previews_init action should be fired'); - } - - public function test_constructor_initializes_dependencies() - { - $this->mockStaticDependencies(); - - $settings_page = new Settings_Page(); - - // Use reflection to check protected properties - $reflection = new \ReflectionClass($settings_page); - - $parameters_prop = $reflection->getProperty('parameters'); - $parameters_prop->setAccessible(true); - $this->assertInstanceOf(Preview_Parameter_Registry::class, $parameters_prop->getValue($settings_page)); - - $types_config_prop = $reflection->getProperty('types_config'); - $types_config_prop->setAccessible(true); - $this->assertInstanceOf(Post_Types_Config_Interface::class, $types_config_prop->getValue($settings_page)); - } - - public function test_register_settings_pages_adds_admin_menu_action() - { - $this->mockStaticDependencies(); - - $settings_page = new Settings_Page(); - - // Check that admin_menu action was added - $this->assertTrue(has_action('admin_menu') !== false, 'admin_menu action should be registered'); - } - - public function test_register_settings_fields_adds_admin_init_action() - { - $this->mockStaticDependencies(); - - $settings_page = new Settings_Page(); - - // Check that admin_init action was added - $this->assertTrue(has_action('admin_init') !== false, 'admin_init action should be registered'); - } - - public function test_load_scripts_styles_adds_admin_enqueue_scripts_action() - { - $this->mockStaticDependencies(); - - $settings_page = new Settings_Page(); - - // Check that admin_enqueue_scripts action was added - $this->assertTrue(has_action('admin_enqueue_scripts') !== false, 'admin_enqueue_scripts action should be registered'); - } - - public function test_get_current_tab_returns_first_tab_when_no_get_param() - { - $this->mockStaticDependencies(); - $settings_page = new Settings_Page(); - - $post_types = [ - 'post' => 'Posts', - 'page' => 'Pages', - 'product' => 'Products' - ]; - - $current_tab = $settings_page->get_current_tab($post_types); - - $this->assertEquals('post', $current_tab, 'Should return first post type key when no GET parameter'); - } - - public function test_get_current_tab_returns_sanitized_get_param() - { - $this->mockStaticDependencies(); - $settings_page = new Settings_Page(); - - $post_types = [ - 'post' => 'Posts', - 'page' => 'Pages', - 'product' => 'Products' - ]; - - $_GET['tab'] = 'page'; - $current_tab = $settings_page->get_current_tab($post_types); - - $this->assertEquals('page', $current_tab, 'Should return sanitized GET parameter value'); - } - - public function test_get_current_tab_with_custom_tab_param_name() - { - $this->mockStaticDependencies(); - $settings_page = new Settings_Page(); - - $post_types = [ - 'post' => 'Posts', - 'page' => 'Pages' - ]; - - $_GET['custom_tab'] = 'page'; - $current_tab = $settings_page->get_current_tab($post_types, 'custom_tab'); - - $this->assertEquals('page', $current_tab, 'Should use custom tab parameter name'); - } - - public function test_get_current_tab_sanitizes_malicious_input() - { - $this->mockStaticDependencies(); - $settings_page = new Settings_Page(); - - $post_types = [ - 'post' => 'Posts', - 'page' => 'Pages' - ]; - - $_GET['tab'] = ''; - $current_tab = $settings_page->get_current_tab($post_types); - - $this->assertEquals('scriptalertxssscript', $current_tab, 'Should sanitize malicious input using sanitize_key'); - } - - public function test_get_current_tab_returns_empty_string_for_empty_post_types() - { - $this->mockStaticDependencies(); - $settings_page = new Settings_Page(); - - $current_tab = $settings_page->get_current_tab([]); - - $this->assertEquals('', $current_tab, 'Should return empty string for empty post types array'); - } - - public function test_get_current_tab_handles_non_string_get_value() - { - $this->mockStaticDependencies(); - $settings_page = new Settings_Page(); - - $post_types = [ - 'post' => 'Posts', - 'page' => 'Pages' - ]; - - $_GET['tab'] = ['array_value']; - $current_tab = $settings_page->get_current_tab($post_types); - - $this->assertEquals('post', $current_tab, 'Should return first post type when GET value is not a string'); - } - - public function test_plugin_menu_slug_constant() - { - $this->assertEquals('hwp-previews', Settings_Page::PLUGIN_MENU_SLUG, 'Plugin menu slug should be correct'); - } - - private function mockStaticDependencies(): void - { - // Mock Preview_Parameter_Registry::get_instance() - if (!function_exists('HWP\Previews\wpunit\Admin\mock_preview_parameter_registry_get_instance')) { - function mock_preview_parameter_registry_get_instance() { - $mock = $this->createMock(Preview_Parameter_Registry::class); - $mock->method('get_descriptions')->willReturn([ - 'param1' => 'Description 1', - 'param2' => 'Description 2' - ]); - return $mock; - } - } - - // Mock Post_Types_Config_Registry::get_post_type_config() - if (!function_exists('HWP\Previews\wpunit\Admin\mock_post_types_config_registry_get_post_type_config')) { - function mock_post_types_config_registry_get_post_type_config() { - $mock = $this->createMock(Post_Types_Config_Interface::class); - $mock->method('get_public_post_types')->willReturn([ - 'post' => 'Posts', - 'page' => 'Pages', - 'product' => 'Products' - ]); - return $mock; - } - } - - // Override the static method calls in the constructor - // This would require modifying the original class or using a mocking framework - // For now, we'll assume the dependencies are properly injected - } -} diff --git a/plugins/hwp-previews/tests/wpunit/Preview/Post/PostEditorServiceTest.php b/plugins/hwp-previews/tests/wpunit/Preview/Post/PostEditorServiceTest.php new file mode 100644 index 00000000..c042072c --- /dev/null +++ b/plugins/hwp-previews/tests/wpunit/Preview/Post/PostEditorServiceTest.php @@ -0,0 +1,123 @@ +service = new Post_Editor_Service(); + + $this->original_settings = get_option( 'classic-editor-settings', [] ); + $this->clean_up_filter_options(); + } + + public function tearDown(): void { + $this->clean_up_filter_options(); + update_option( 'classic-editor-settings', $this->original_settings ); + parent::tearDown(); + } + + public function clean_up_filter_options() { + remove_all_filters( 'pre_option_classic-editor-settings' ); + delete_option( 'classic-editor-settings' ); + } + + + public function test_gutenberg_editor_enabled_returns_true_when_conditions_met(): void { + + $post_type = 'events'; + register_post_type( $post_type, [ + 'label' => 'Events', + 'description' => 'Custom post type for events', + 'public' => true, + 'show_in_rest' => true, // Gutenberg supported + 'supports' => array( 'title', 'editor', 'author', 'thumbnail' ), + ] ); + + $result = $this->service->gutenberg_editor_enabled( $post_type ); + + $this->assertTrue( $result ); + unregister_post_type( $post_type ); + } + + public function test_gutenberg_editor_enabled_returns_false_when_gutenberg_not_supported(): void { + $post_type = 'events_no_gutenberg'; + register_post_type($post_type, [ + 'label' => 'Events', + 'description' => 'Custom post type for events', + 'public' => true, + 'show_in_rest' => false, // Gutenberg not supported + 'supports' => ['title', 'editor'] + ]); + + $result = $this->service->gutenberg_editor_enabled($post_type); + $this->assertFalse($result); + + unregister_post_type($post_type); + } + + public function test_gutenberg_editor_enabled_returns_false_when_post_type_not_exists(): void { + $result = $this->service->gutenberg_editor_enabled( 'nonexistent_post_type' ); + + $this->assertFalse( $result ); + } + + + public function test_gutenberg_editor_enabled_returns_false_when_classic_editor_forced(): void { + $post_type = 'events_classic'; + register_post_type($post_type, [ + 'public' => true, + 'show_in_rest' => true, + 'supports' => ['title', 'editor'] + ]); + + // Mock classic editor being active and configured + tests_add_filter( 'pre_option_active_plugins', function( $plugins ) { + $plugins[] = 'classic-editor/classic-editor.php'; + return $plugins; + } ); + + + // Set classic editor settings to force this post type + update_option('classic-editor-settings', [ + 'post_types' => [$post_type] + ]); + + + $result = $this->service->gutenberg_editor_enabled($post_type); + + $this->assertFalse($result); + unregister_post_type($post_type); + } + + + public function test_is_gutenberg_supported_returns_false_when_editor_not_supported(): void { + $post_type = 'no_editor_support'; + register_post_type($post_type, [ + 'public' => true, + 'show_in_rest' => true, + 'supports' => ['title'] // No 'editor' support + ]); + + $result = $this->service->gutenberg_editor_enabled($post_type); + + $this->assertFalse($result); + unregister_post_type($post_type); + } + + + +} diff --git a/plugins/hwp-previews/tests/wpunit/Preview/Post/PostServiceSettingsTest.php b/plugins/hwp-previews/tests/wpunit/Preview/Post/PostSettingsServiceTest.php similarity index 91% rename from plugins/hwp-previews/tests/wpunit/Preview/Post/PostServiceSettingsTest.php rename to plugins/hwp-previews/tests/wpunit/Preview/Post/PostSettingsServiceTest.php index 599ca2fd..61f9356b 100644 --- a/plugins/hwp-previews/tests/wpunit/Preview/Post/PostServiceSettingsTest.php +++ b/plugins/hwp-previews/tests/wpunit/Preview/Post/PostSettingsServiceTest.php @@ -67,132 +67,97 @@ public function test_get_post_type_config_returns_config_when_exists(): void { update_option( $this->test_option_key, $test_config ); $this->service = new Post_Settings_Service(); - - // Act $result = $this->service->get_post_type_config( 'post' ); - // Assert + $this->assertEquals( [ 'enabled' => true, 'in_iframe' => false ], $result ); } public function test_get_post_type_config_returns_null_when_not_exists(): void { - // Arrange + $test_config = [ 'post' => [ 'enabled' => true ] ]; update_option( $this->test_option_key, $test_config ); $this->service = new Post_Settings_Service(); - - // Act $result = $this->service->get_post_type_config( 'nonexistent_post_type' ); - // Assert $this->assertNull( $result ); } public function test_get_post_type_config_returns_null_when_no_settings(): void { - // Arrange - no settings saved $this->service = new Post_Settings_Service(); - - // Act $result = $this->service->get_post_type_config( 'post' ); - // Assert $this->assertNull( $result ); } public function test_get_option_key_returns_filtered_value(): void { - // Arrange $this->service = new Post_Settings_Service(); - - // Act $result = $this->service->get_option_key(); - // Assert $this->assertEquals( $this->test_option_key, $result ); } public function test_get_settings_group_returns_filtered_value(): void { - // Arrange $this->service = new Post_Settings_Service(); - - // Act $result = $this->service->get_settings_group(); - // Assert $this->assertEquals( $this->test_settings_group, $result ); } public function test_constructor_loads_settings_from_cache_when_available(): void { - // Arrange $cached_data = [ 'post' => [ 'enabled' => true, 'cached' => true ] ]; wp_cache_set( $this->test_option_key, $cached_data, $this->test_settings_group ); - // Different data in database to ensure cache is used $db_data = [ 'post' => [ 'enabled' => false, 'cached' => false ] ]; update_option( $this->test_option_key, $db_data ); - // Act $this->service = new Post_Settings_Service(); $result = $this->service->get_post_type_config( 'post' ); - // Assert $this->assertEquals( [ 'enabled' => true, 'cached' => true ], $result ); } public function test_constructor_loads_settings_from_database_when_cache_empty(): void { - // Arrange $db_data = [ 'post' => [ 'enabled' => true, 'from_db' => true ] ]; update_option( $this->test_option_key, $db_data ); - // Ensure cache is empty wp_cache_delete( $this->test_option_key, $this->test_settings_group ); - // Act $this->service = new Post_Settings_Service(); $result = $this->service->get_post_type_config( 'post' ); - // Assert $this->assertEquals( [ 'enabled' => true, 'from_db' => true ], $result ); } public function test_constructor_handles_non_array_cache_value(): void { - // Arrange wp_cache_set( $this->test_option_key, 'not_an_array', $this->test_settings_group ); - $db_data = [ 'post' => [ 'enabled' => true ] ]; update_option( $this->test_option_key, $db_data ); - // Act $this->service = new Post_Settings_Service(); $result = $this->service->get_post_type_config( 'post' ); - // Assert $this->assertEquals( [ 'enabled' => true ], $result ); } public function test_constructor_handles_empty_database_option(): void { - // Arrange - ensure option doesn't exist delete_option( $this->test_option_key ); wp_cache_delete( $this->test_option_key, $this->test_settings_group ); - // Act $this->service = new Post_Settings_Service(); $result = $this->service->get_post_type_config( 'post' ); - // Assert $this->assertNull( $result ); } public function test_constructor_handles_non_array_database_option(): void { - // Arrange update_option( $this->test_option_key, 'not_an_array' ); wp_cache_delete( $this->test_option_key, $this->test_settings_group ); - // Act $this->service = new Post_Settings_Service(); $result = $this->service->get_post_type_config( 'post' ); - // Assert $this->assertNull( $result ); } } From c4c92704ba8199b5aeeec673e452fdf9c33ed06d Mon Sep 17 00:00:00 2001 From: Colin Murphy Date: Wed, 18 Jun 2025 20:18:36 +0100 Subject: [PATCH 11/35] Removed some unused classes already refactored. Updated Faust Integration and added tests for unit as we already have e2e tests --- plugins/hwp-previews/ACTIONS_AND_FILTERS.md | 1 - plugins/hwp-previews/composer.json | 6 +- .../src/Integration/Faust_Integration.php | 120 +++++++------ .../src/Preview/Helper/Settings_Group.php | 159 ------------------ .../src/Preview/Helper/Settings_Helper.php | 71 -------- .../src/Preview/Post/Post_Editor_Service.php | 4 +- .../Preview/Post/Post_Settings_Service.php | 9 + .../Post_Type_Inspector_Interface.php | 23 --- .../Contracts/Post_Types_Config_Interface.php | 49 ------ .../Preview/Post/Type/Post_Type_Inspector.php | 49 ------ .../Preview/Post/Type/Post_Types_Config.php | 109 ------------ .../Post/Type/Post_Types_Config_Registry.php | 38 ----- .../Integration/FaustIntegrationTest.php | 60 +++++++ 13 files changed, 148 insertions(+), 550 deletions(-) delete mode 100644 plugins/hwp-previews/src/Preview/Helper/Settings_Group.php delete mode 100644 plugins/hwp-previews/src/Preview/Helper/Settings_Helper.php delete mode 100644 plugins/hwp-previews/src/Preview/Post/Type/Contracts/Post_Type_Inspector_Interface.php delete mode 100644 plugins/hwp-previews/src/Preview/Post/Type/Contracts/Post_Types_Config_Interface.php delete mode 100644 plugins/hwp-previews/src/Preview/Post/Type/Post_Type_Inspector.php delete mode 100644 plugins/hwp-previews/src/Preview/Post/Type/Post_Types_Config.php delete mode 100644 plugins/hwp-previews/src/Preview/Post/Type/Post_Types_Config_Registry.php create mode 100644 plugins/hwp-previews/tests/wpunit/Integration/FaustIntegrationTest.php diff --git a/plugins/hwp-previews/ACTIONS_AND_FILTERS.md b/plugins/hwp-previews/ACTIONS_AND_FILTERS.md index 44b3cfcb..1f66fd27 100644 --- a/plugins/hwp-previews/ACTIONS_AND_FILTERS.md +++ b/plugins/hwp-previews/ACTIONS_AND_FILTERS.md @@ -19,7 +19,6 @@ - `hwp_previews_settings_group_settings_config` - Filter to modify the settings array. See `Settings_Group` - `hwp_previews_settings_group_cache_groups` - Filter to modify cache groups for `Settings_Group` - `hwp_previews_get_post_types_config` - Filter for generating the instance of `Post_Types_Config_Interface` -- `hwp_previews_hooks_post_type_config` - Filter for post type config service for the Hook class - `hwp_previews_hooks_post_status_config` - Filter for post status config service for the Hook class - `hwp_previews_hooks_preview_link_service` - Filter for preview link service for the Hook class - `hwp_previews_settings_fields` - Allows a user to register, modify, or remove settings fields for the settings page diff --git a/plugins/hwp-previews/composer.json b/plugins/hwp-previews/composer.json index 42eeafaf..cd53a436 100644 --- a/plugins/hwp-previews/composer.json +++ b/plugins/hwp-previews/composer.json @@ -36,6 +36,7 @@ "johnpbloch/wordpress-core": "^6.8", "lucatume/wp-browser": "^3.5", "mockery/mockery": "^1.5", + "pcov/clobber": "*", "phpcompatibility/php-compatibility": "dev-develop as 9.99.99", "phpcompatibility/phpcompatibility-wp": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", @@ -89,7 +90,10 @@ ".env.dist", "c3.php", "codeception.dist.yml", - "tests" + "tests", + "artifacts", + "package.json", + "package-lock.json" ] }, "autoload": { diff --git a/plugins/hwp-previews/src/Integration/Faust_Integration.php b/plugins/hwp-previews/src/Integration/Faust_Integration.php index a4c187a7..dfb6f03f 100644 --- a/plugins/hwp-previews/src/Integration/Faust_Integration.php +++ b/plugins/hwp-previews/src/Integration/Faust_Integration.php @@ -1,11 +1,12 @@ faust_enabled = $this->is_faust_enabled(); + + if ( ! $this->get_faust_enabled() ) { + return; + } + + $this->configure_faust(); } /** - * Configure Faust settings and remove conflicting filters. + * Checks if Faust is enabled. */ - public static function configure_faust(): void { - if ( self::$faust_enabled ) { - self::set_default_faust_settings(); + public function is_faust_enabled(): bool { + if ( ! function_exists( 'is_plugin_active' ) ) { + return false; + } - // Remove FaustWP post preview link filter to avoid conflicts with our custom preview link generation. - remove_filter( 'preview_post_link', 'WPE\FaustWP\Replacement\post_preview_link', 1000 ); + return is_plugin_active( 'faustwp/faustwp.php' ); + } - self::display_faust_admin_notice(); - } + /** + * Get the Faust enabled status. + */ + public function get_faust_enabled(): bool { + return $this->faust_enabled; } /** - * Checks if Faust is enabled. + * Initialize the hooks for the preview functionality. */ - public static function is_faust_enabled(): bool { - if ( function_exists( 'is_plugin_active' ) ) { - return is_plugin_active( 'faustwp/faustwp.php' ); + public static function init(): Faust_Integration { + if ( ! isset( self::$instance ) || ! ( is_a( self::$instance, self::class ) ) ) { + self::$instance = new self(); } - return false; + return self::$instance; + } + + /** + * Configure Faust settings and remove conflicting filters. + */ + protected function configure_faust(): void { + $this->set_default_faust_settings(); + + // Remove FaustWP post preview link filter to avoid conflicts with our custom preview link generation. + remove_filter( 'preview_post_link', 'WPE\FaustWP\Replacement\post_preview_link', 1000 ); + + $this->display_faust_admin_notice(); } /** * Returns the Faust frontend URL from settings or a default value. */ - public static function get_faust_frontend_url(): string { + public function get_faust_frontend_url(): string { $default_value = 'http://localhost:3000'; - if ( self::$faust_enabled && function_exists( '\WPE\FaustWP\Settings\faustwp_get_setting' ) ) { + if ( $this->get_faust_enabled() && function_exists( '\WPE\FaustWP\Settings\faustwp_get_setting' ) ) { $frontend_uri = \WPE\FaustWP\Settings\faustwp_get_setting( 'frontend_uri', '' ); if ( ! empty( $frontend_uri ) ) { @@ -74,34 +100,32 @@ public static function get_faust_frontend_url(): string { /** * Get default preview URL for Faust. */ - public static function get_faust_preview_url(): string { + public function get_faust_preview_url(): string { return self::get_faust_frontend_url() . '/preview?p={ID}&preview=true&previewPathname=p{ID}&typeName={type}'; } /** * Sets default Faust settings if there are no existing settings. */ - public static function set_default_faust_settings(): void { - $settings_group = Settings_Group::get_instance(); - $types_config = apply_filters( - 'hwp_previews_hooks_post_type_config', - Post_Types_Config_Registry::get_post_type_config() - ); - + public function set_default_faust_settings(): void { - $plugin_settings = $settings_group->get_cached_settings(); + $settings_helper = new Post_Settings_Service(); + $plugin_settings = $settings_helper->get_settings_values(); + // If already configured, do not overwrite. if ( ! empty( $plugin_settings ) ) { return; } - $setting_preview_key = $settings_group->get_settings_key_preview_url(); - $setting_enabled_key = $settings_group->get_settings_key_enabled(); + $post_preview_service = new Post_Preview_Service(); + $post_types = $post_preview_service->get_post_types(); - $default_settings = []; + $setting_preview_key = Settings_Field_Collection::PREVIEW_URL_FIELD_ID; + $setting_enabled_key = Settings_Field_Collection::ENABLED_FIELD_ID; - foreach ( $types_config->get_public_post_types() as $key => $label ) { - $default_settings[ $key ] = [ + $default_settings = []; + foreach ( $post_types as $type => $label ) { + $default_settings[ $type ] = [ $setting_enabled_key => true, $setting_preview_key => self::get_faust_preview_url(), ]; @@ -113,15 +137,15 @@ public static function set_default_faust_settings(): void { /** * Dismiss the Faust admin notice. */ - public static function dismiss_faust_admin_notice(): void { + public function dismiss_faust_admin_notice(): void { update_user_meta( get_current_user_id(), self::FAUST_NOTICE_KEY, 1 ); } /** * Register admin notice to inform users about Faust integration. */ - public static function register_faust_admin_notice(): void { - add_action( 'admin_notices', static function (): void { + public function register_faust_admin_notice(): void { + add_action( 'admin_notices', function (): void { $screen = get_current_screen(); // Exit if not this plugin's settings page. @@ -137,10 +161,10 @@ public static function register_faust_admin_notice(): void { get_faust_enabled() || (bool) $is_dismissed ) { return; } self::register_faust_admin_notice(); // Register the AJAX action for dismissing the notice. - add_action( 'wp_ajax_' . self::FAUST_NOTICE_KEY, static function (): void { + add_action( 'wp_ajax_' . self::FAUST_NOTICE_KEY, function (): void { // Exit if the action is not set or does not match the expected key. if ( ! isset( $_POST['action'] ) || esc_attr( self::FAUST_NOTICE_KEY ) !== $_POST['action'] ) { return; diff --git a/plugins/hwp-previews/src/Preview/Helper/Settings_Group.php b/plugins/hwp-previews/src/Preview/Helper/Settings_Group.php deleted file mode 100644 index 9f8eb69b..00000000 --- a/plugins/hwp-previews/src/Preview/Helper/Settings_Group.php +++ /dev/null @@ -1,159 +0,0 @@ - - */ - protected array $settings_config = []; - - /** - * The settings helper instance. - * - * @var \HWP\Previews\Preview\Helper\Settings_Group|null - */ - protected static $instance = null; - - /** - * Class initializer. - */ - public function __construct() { - $this->option_key = $this->get_option_key(); - $this->settings_group = $this->get_settings_group(); - $this->settings_config = $this->get_settings_config(); - $this->set_cache_group(); - } - - /** - * Get Singleton instance of the Settings_Group class. - */ - public static function get_instance(): self { - - $instance = self::$instance; - - if ( $instance instanceof self ) { - return $instance; - } - - self::$instance = new self(); - - return self::$instance; - } - - /** - * Setting key for the enabled status. - */ - public function get_settings_key_enabled(): string { - return 'enabled'; - } - - /** - * Setting key for post-statuses as parent. - */ - public function get_settings_key_post_statuses_as_parent(): string { - return 'post_statuses_as_parent'; - } - - /** - * Setting key for the preview URL. - */ - public function get_settings_key_preview_url(): string { - return 'preview_url'; - } - - /** - * Setting key for is the preview in an iframe. - */ - public function get_settings_key_in_iframe(): string { - return 'in_iframe'; - } - - /** - * Gets the settings configuration for the settings group. - * - * @return array The settings configuration. - */ - public function get_settings_config(): array { - return apply_filters( 'hwp_previews_settings_group_settings_config', [ - $this->get_settings_key_enabled() => 'bool', - $this->get_settings_key_post_statuses_as_parent() => 'bool', - $this->get_settings_key_preview_url() => 'string', - $this->get_settings_key_in_iframe() => 'bool', - ] ); - } - - /** - * Gets settings from the cache or database. - * - * @return array - */ - public function get_cached_settings(): array { - - $value = wp_cache_get( $this->option_key, $this->settings_group ); - if ( is_array( $value ) ) { - return $value; - } - - $value = (array) get_option( $this->option_key, [] ); - wp_cache_set( $this->option_key, $value, $this->settings_group ); - - return $value; - } - - /** - * Gets a boolean value for a specific setting in a post type. - * - * @param string $name The setting name. - * @param string $post_type The post type slug. - * @param bool $default_value The default value to return if the setting is not found. - */ - public function get_post_type_boolean_value( string $name, string $post_type, bool $default_value = false ): bool { - $settings = $this->get_cached_settings(); - $type = $this->settings_config[ $name ] ?? null; - if ( 'bool' === $type && isset( $settings[ $post_type ][ $name ] ) ) { - return (bool) $settings[ $post_type ][ $name ]; - } - - return $default_value; - } - - /** - * Gets the option key for the settings group. - */ - protected function get_option_key(): string { - return apply_filters( 'hwp_previews_settings_group_option_key', HWP_PREVIEWS_SETTINGS_KEY ); - } - - /** - * Gets the settings group name. - */ - protected function get_settings_group(): string { - return apply_filters( 'hwp_previews_settings_group_settings_group', HWP_PREVIEWS_SETTINGS_GROUP ); - } - - /** - * Sets the cache group for the settings. - */ - protected function set_cache_group(): void { - $groups = apply_filters( 'hwp_previews_settings_group_cache_groups', [ $this->settings_group ] ); - wp_cache_add_non_persistent_groups( $groups ); - } -} diff --git a/plugins/hwp-previews/src/Preview/Helper/Settings_Helper.php b/plugins/hwp-previews/src/Preview/Helper/Settings_Helper.php deleted file mode 100644 index 9e0e6b93..00000000 --- a/plugins/hwp-previews/src/Preview/Helper/Settings_Helper.php +++ /dev/null @@ -1,71 +0,0 @@ -settings_group = $settings_group; - } - - /** - * Get an instance of the Settings_Helper class. - * - * @return \HWP\Previews\Preview\Helper\Settings_Helper - */ - public static function get_instance(): self { - - $instance = self::$instance; - - if ( $instance instanceof self ) { - return $instance; - } - - self::$instance = new self( Settings_Group::get_instance() ); - - return self::$instance; - } - - /** - * Get all post types that are enabled in the settings. - * - * @param array $default_value Default post types to return if none are enabled. - * - * @return array - */ - public function post_types_enabled( array $default_value = [] ): array { - $settings = $this->settings_group->get_cached_settings(); - - $enabled_key = $this->settings_group->get_settings_key_enabled(); - - $enabled_post_types = []; - foreach ( $settings as $key => $item ) { - if ( (bool) ( $item[ $enabled_key ] ?? false ) ) { - $enabled_post_types[] = $key; - } - } - - if ( ! empty( $enabled_post_types ) ) { - return $enabled_post_types; - } - return $default_value; - } -} diff --git a/plugins/hwp-previews/src/Preview/Post/Post_Editor_Service.php b/plugins/hwp-previews/src/Preview/Post/Post_Editor_Service.php index bc8f0d28..e10c7f03 100644 --- a/plugins/hwp-previews/src/Preview/Post/Post_Editor_Service.php +++ b/plugins/hwp-previews/src/Preview/Post/Post_Editor_Service.php @@ -29,11 +29,11 @@ public function gutenberg_editor_enabled(string $post_type): bool { */ public function is_gutenberg_supported( WP_Post_Type $post_type ): bool { - if (empty($post_type->show_in_rest)) { + if ( empty( $post_type->show_in_rest ) ) { return false; } - if (!post_type_supports($post_type->name, 'editor')) { + if ( ! post_type_supports( $post_type->name, 'editor' ) ) { return false; } diff --git a/plugins/hwp-previews/src/Preview/Post/Post_Settings_Service.php b/plugins/hwp-previews/src/Preview/Post/Post_Settings_Service.php index cdfda807..0897acf6 100644 --- a/plugins/hwp-previews/src/Preview/Post/Post_Settings_Service.php +++ b/plugins/hwp-previews/src/Preview/Post/Post_Settings_Service.php @@ -19,6 +19,15 @@ public function __construct() { $this->setup(); } + /** + * Get the settings values. + * + * @return array + */ + public function get_settings_values(): array { + return $this->settings_values; + } + /** * @param string $post_type * diff --git a/plugins/hwp-previews/src/Preview/Post/Type/Contracts/Post_Type_Inspector_Interface.php b/plugins/hwp-previews/src/Preview/Post/Type/Contracts/Post_Type_Inspector_Interface.php deleted file mode 100644 index 7bae44ae..00000000 --- a/plugins/hwp-previews/src/Preview/Post/Type/Contracts/Post_Type_Inspector_Interface.php +++ /dev/null @@ -1,23 +0,0 @@ - $post_types The post type to check. - */ - public function set_post_types( array $post_types ): self; - - /** - * Get the post types that are applicable for previews. - * - * @return array Post types that are applicable for previews. - */ - public function get_post_types(): array; - - /** - * Check if a post type is applicable for previews. - * - * @param string $post_type The post type to check. - */ - public function is_post_type_applicable( string $post_type ): bool; - - /** - * Check if a post type is hierarchical. - * - * @param string $post_type The post type. - */ - public function is_hierarchical( string $post_type ): bool; - - /** - * Check if a post type supports Gutenberg. - * - * @param string $post_type Post Type slug. - */ - public function gutenberg_editor_enabled( string $post_type ): bool; - - /** - * Gets all publicly available post types as key value array, where key is a post type slug and value is a label. - * - * @return array - */ - public function get_public_post_types(): array; -} diff --git a/plugins/hwp-previews/src/Preview/Post/Type/Post_Type_Inspector.php b/plugins/hwp-previews/src/Preview/Post/Type/Post_Type_Inspector.php deleted file mode 100644 index bb688210..00000000 --- a/plugins/hwp-previews/src/Preview/Post/Type/Post_Type_Inspector.php +++ /dev/null @@ -1,49 +0,0 @@ -show_in_rest ) || - empty( $post_type->supports ) || - ! is_array( $post_type->supports ) || - ! in_array( 'editor', $post_type->supports, true ) - ) { - return false; - } - - return $post_type->show_in_rest; - } - - /** - * Checks if the post type is supported by Classic Editor. - * - * @param string $post_type Post Type slug. - */ - public function is_classic_editor_forced( string $post_type ): bool { - if ( - ! function_exists( 'is_plugin_active' ) || - ! is_plugin_active( 'classic-editor/classic-editor.php' ) - ) { - return false; - } - - // If this post type is listed in Classic Editor settings, Gutenberg is disabled. - $settings = (array) get_option( 'classic-editor-settings', [] ); - - return ! empty( $settings['post_types'] ) && - is_array( $settings['post_types'] ) && - in_array( $post_type, $settings['post_types'], true ); - } -} diff --git a/plugins/hwp-previews/src/Preview/Post/Type/Post_Types_Config.php b/plugins/hwp-previews/src/Preview/Post/Type/Post_Types_Config.php deleted file mode 100644 index b890c67f..00000000 --- a/plugins/hwp-previews/src/Preview/Post/Type/Post_Types_Config.php +++ /dev/null @@ -1,109 +0,0 @@ - - */ - private array $post_types = []; - - /** - * Post type inspector. - * - * @var \HWP\Previews\Preview\Post\Type\Contracts\Post_Type_Inspector_Interface - */ - private Post_Type_Inspector_Interface $inspector; - - /** - * Class constructor. - * - * @param \HWP\Previews\Preview\Post\Type\Contracts\Post_Type_Inspector_Interface $inspector Post Type inspector. - */ - public function __construct( Post_Type_Inspector_Interface $inspector ) { - $this->inspector = $inspector; - } - - /** - * Sets the post types that are applicable for preview links. - * - * @param array $post_types Post types that are applicable for preview links. - * - * @return $this - */ - public function set_post_types( array $post_types ): self { - $this->post_types = $post_types; - - return $this; - } - - /** - * Get the post types that are applicable for preview links. - * - * @return array - */ - public function get_post_types(): array { - return $this->post_types; - } - - /** - * Check if the post type is applicable for preview links. - * - * @param string $post_type Post Type slug. - */ - public function is_post_type_applicable( string $post_type ): bool { - return in_array( $post_type, $this->post_types, true ) && post_type_exists( $post_type ); - } - - /** - * Check if the post type is hierarchical. - * - * @param string $post_type Post Type slug. - */ - public function is_hierarchical( string $post_type ): bool { - return is_post_type_hierarchical( $post_type ); - } - - /** - * Check if the post type supports Gutenberg editor and if the classic editor is not being forced. - * - * @param string $post_type Post Type slug. - */ - public function gutenberg_editor_enabled( string $post_type ): bool { - $post_type_object = get_post_type_object( $post_type ); - if ( ! $post_type_object instanceof WP_Post_Type ) { - return false; - } - - return $this->inspector->is_gutenberg_supported( $post_type_object ) && - ! $this->inspector->is_classic_editor_forced( $post_type ); - } - - /** - * Gets all publicly available post types as key value array, where key is a post type slug and value is a label. - * - * @return array - */ - public function get_public_post_types(): array { - $post_types = get_post_types( [ 'public' => true ], 'objects' ); - - $result = []; - - foreach ( $post_types as $post_type ) { - $result[ $post_type->name ] = $post_type->label; - } - - return apply_filters( 'hwp_previews_filter_available_post_types', $result ); - } -} diff --git a/plugins/hwp-previews/src/Preview/Post/Type/Post_Types_Config_Registry.php b/plugins/hwp-previews/src/Preview/Post/Type/Post_Types_Config_Registry.php deleted file mode 100644 index 0efa0906..00000000 --- a/plugins/hwp-previews/src/Preview/Post/Type/Post_Types_Config_Registry.php +++ /dev/null @@ -1,38 +0,0 @@ -set_post_types( Settings_Helper::get_instance()->post_types_enabled() ); - $instance = apply_filters( 'hwp_previews_get_post_types_config', $instance ); - self::$instance = $instance; - - return $instance; - } -} diff --git a/plugins/hwp-previews/tests/wpunit/Integration/FaustIntegrationTest.php b/plugins/hwp-previews/tests/wpunit/Integration/FaustIntegrationTest.php new file mode 100644 index 00000000..18540bf3 --- /dev/null +++ b/plugins/hwp-previews/tests/wpunit/Integration/FaustIntegrationTest.php @@ -0,0 +1,60 @@ +getProperty( 'instance' ); + $instance_property->setAccessible( true ); + $instance_property->setValue( null, null ); + } + + /** + * Test that init() returns a singleton instance + */ + public function test_init_returns_singleton_instance(): void { + $instance1 = Faust_Integration::init(); + $instance2 = Faust_Integration::init(); + + $this->assertInstanceOf( Faust_Integration::class, $instance1 ); + $this->assertSame( $instance1, $instance2, 'init() should return the same singleton instance' ); + } + +// public function test_is_faust_enabled_asserts_false() { +// +// } + + public function test_is_faust_enabled_asserts_true() { + + // Mock FaustWP exists + tests_add_filter( 'pre_option_active_plugins', function ( $plugins ) { + $plugins[] = 'faustwp/faustwp.php'; + + return $plugins; + } ); + + $instance = Faust_Integration::init(); + $this->assertTrue( $instance->is_faust_enabled() ); + } + + public function test_is_faust_enabled_asserts_false() { + + $instance = Faust_Integration::init(); + $this->assertFalse( $instance->is_faust_enabled() ); + } +} From d86ad043bf393f99f63065205327fba681e38774 Mon Sep 17 00:00:00 2001 From: Colin Murphy Date: Wed, 18 Jun 2025 20:19:09 +0100 Subject: [PATCH 12/35] Updated package. --- plugins/hwp-previews/composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/hwp-previews/composer.json b/plugins/hwp-previews/composer.json index cd53a436..c142b179 100644 --- a/plugins/hwp-previews/composer.json +++ b/plugins/hwp-previews/composer.json @@ -36,7 +36,6 @@ "johnpbloch/wordpress-core": "^6.8", "lucatume/wp-browser": "^3.5", "mockery/mockery": "^1.5", - "pcov/clobber": "*", "phpcompatibility/php-compatibility": "dev-develop as 9.99.99", "phpcompatibility/phpcompatibility-wp": "^2.0", "phpstan/phpstan-strict-rules": "^2.0", From baaf821b022eb36e9870d7587ee2a90ebb73d44f Mon Sep 17 00:00:00 2001 From: Colin Murphy Date: Wed, 18 Jun 2025 20:26:57 +0100 Subject: [PATCH 13/35] Fixed issues after refactoring Faust Integration. e2e and code quality caught the issues. --- .../src/Integration/Faust_Integration.php | 78 ++++++++++--------- plugins/hwp-previews/src/Templates/admin.php | 13 ++-- 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/plugins/hwp-previews/src/Integration/Faust_Integration.php b/plugins/hwp-previews/src/Integration/Faust_Integration.php index dfb6f03f..5897608e 100644 --- a/plugins/hwp-previews/src/Integration/Faust_Integration.php +++ b/plugins/hwp-previews/src/Integration/Faust_Integration.php @@ -1,6 +1,6 @@ faust_enabled = $this->is_faust_enabled(); @@ -39,6 +43,17 @@ public function __construct() { $this->configure_faust(); } + /** + * Initialize the hooks for the preview functionality. + */ + public static function init(): Faust_Integration { + if ( ! isset( self::$instance ) || ! ( is_a( self::$instance, self::class ) ) ) { + self::$instance = new self(); + } + + return self::$instance; + } + /** * Checks if Faust is enabled. */ @@ -57,29 +72,6 @@ public function get_faust_enabled(): bool { return $this->faust_enabled; } - /** - * Initialize the hooks for the preview functionality. - */ - public static function init(): Faust_Integration { - if ( ! isset( self::$instance ) || ! ( is_a( self::$instance, self::class ) ) ) { - self::$instance = new self(); - } - - return self::$instance; - } - - /** - * Configure Faust settings and remove conflicting filters. - */ - protected function configure_faust(): void { - $this->set_default_faust_settings(); - - // Remove FaustWP post preview link filter to avoid conflicts with our custom preview link generation. - remove_filter( 'preview_post_link', 'WPE\FaustWP\Replacement\post_preview_link', 1000 ); - - $this->display_faust_admin_notice(); - } - /** * Returns the Faust frontend URL from settings or a default value. */ @@ -134,18 +126,11 @@ public function set_default_faust_settings(): void { update_option( HWP_PREVIEWS_SETTINGS_KEY, $default_settings ); } - /** - * Dismiss the Faust admin notice. - */ - public function dismiss_faust_admin_notice(): void { - update_user_meta( get_current_user_id(), self::FAUST_NOTICE_KEY, 1 ); - } - /** * Register admin notice to inform users about Faust integration. */ public function register_faust_admin_notice(): void { - add_action( 'admin_notices', function (): void { + add_action( 'admin_notices', static function (): void { $screen = get_current_screen(); // Exit if not this plugin's settings page. @@ -181,6 +166,25 @@ public function register_faust_admin_notice(): void { }, 10, 0 ); } + /** + * Dismiss the Faust admin notice. + */ + public static function dismiss_faust_admin_notice(): void { + update_user_meta( get_current_user_id(), self::FAUST_NOTICE_KEY, 1 ); + } + + /** + * Configure Faust settings and remove conflicting filters. + */ + protected function configure_faust(): void { + $this->set_default_faust_settings(); + + // Remove FaustWP post preview link filter to avoid conflicts with our custom preview link generation. + remove_filter( 'preview_post_link', 'WPE\FaustWP\Replacement\post_preview_link', 1000 ); + + $this->display_faust_admin_notice(); + } + /** * If Faust is enabled, show an admin notice about the migration on the settings page. */ @@ -195,7 +199,7 @@ protected function display_faust_admin_notice(): void { self::register_faust_admin_notice(); // Register the AJAX action for dismissing the notice. - add_action( 'wp_ajax_' . self::FAUST_NOTICE_KEY, function (): void { + add_action( 'wp_ajax_' . self::FAUST_NOTICE_KEY, static function (): void { // Exit if the action is not set or does not match the expected key. if ( ! isset( $_POST['action'] ) || esc_attr( self::FAUST_NOTICE_KEY ) !== $_POST['action'] ) { return; diff --git a/plugins/hwp-previews/src/Templates/admin.php b/plugins/hwp-previews/src/Templates/admin.php index fbf8a2e8..b1559e2d 100644 --- a/plugins/hwp-previews/src/Templates/admin.php +++ b/plugins/hwp-previews/src/Templates/admin.php @@ -4,10 +4,11 @@ use HWP\Previews\Integration\Faust_Integration; -$hwp_previews_tabs_config = (array) get_query_var( 'hwp_previews_main_page_config' ); -$hwp_previews_current_tab = (string) ( $hwp_previews_tabs_config['current_tab'] ?? '' ); -$hwp_previews_tabs = (array) ( $hwp_previews_tabs_config['tabs'] ?? [] ); -$hwp_previews_params = (array) ( $hwp_previews_tabs_config['params'] ?? [] ); +$hwp_previews_tabs_config = (array) get_query_var( 'hwp_previews_main_page_config' ); +$hwp_previews_current_tab = (string) ( $hwp_previews_tabs_config['current_tab'] ?? '' ); +$hwp_previews_tabs = (array) ( $hwp_previews_tabs_config['tabs'] ?? [] ); +$hwp_previews_params = (array) ( $hwp_previews_tabs_config['params'] ?? [] ); +$hwp_previews_faust_integration = Faust_Integration::init(); ?> @@ -61,11 +62,11 @@

- +

HWP Toolkit on GitHub

From 8d8e67f5b31611f6cc30f3f5ea9b865ce79c80d5 Mon Sep 17 00:00:00 2001 From: Huseyn Aghayev Date: Wed, 25 Jun 2025 10:10:47 +0200 Subject: [PATCH 35/35] Update plugins/hwp-previews/README.md --- plugins/hwp-previews/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/hwp-previews/README.md b/plugins/hwp-previews/README.md index 5d8a6384..aa05c0a3 100644 --- a/plugins/hwp-previews/README.md +++ b/plugins/hwp-previews/README.md @@ -156,7 +156,7 @@ This out-of-the-box configuration allows your existing preview workflow to conti --- -### Actions & Filters +## Actions & Filters See the [Actions & Filters documentation](ACTIONS_AND_FILTERS.md) for a comprehensive list of available hooks and how to use them.