From 5f5a37f0d08532c10e482a35758ea33012f10ef1 Mon Sep 17 00:00:00 2001 From: Colin Murphy Date: Thu, 11 Sep 2025 19:52:45 +0100 Subject: [PATCH 1/4] Restrict settings page to admins with manage_options Updated Settings_Page::init() to require both is_admin() and current_user_can('manage_options'). Added and updated unit tests to verify correct behavior for users with and without the required capability. --- .../hwp-previews/src/Admin/Settings_Page.php | 2 +- .../tests/wpunit/Admin/SettingsPageTest.php | 44 ++++++++++++++++++- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/plugins/hwp-previews/src/Admin/Settings_Page.php b/plugins/hwp-previews/src/Admin/Settings_Page.php index 18948f61..31ca69b6 100644 --- a/plugins/hwp-previews/src/Admin/Settings_Page.php +++ b/plugins/hwp-previews/src/Admin/Settings_Page.php @@ -58,7 +58,7 @@ protected function __construct() { * Initializes the settings page. */ public static function init(): ?Settings_Page { - if ( ! is_admin() ) { + if ( ! is_admin() || ! current_user_can( 'manage_options' ) ) { return null; } if ( ! isset( self::$instance ) || ! ( is_a( self::$instance, self::class ) ) ) { diff --git a/plugins/hwp-previews/tests/wpunit/Admin/SettingsPageTest.php b/plugins/hwp-previews/tests/wpunit/Admin/SettingsPageTest.php index eccf0474..bccbcc2c 100644 --- a/plugins/hwp-previews/tests/wpunit/Admin/SettingsPageTest.php +++ b/plugins/hwp-previews/tests/wpunit/Admin/SettingsPageTest.php @@ -21,6 +21,12 @@ public function in_admin( $context = null ) { return $context === 'user'; } }; + + // Reset the instance before each test + $reflection = new ReflectionClass( Settings_Page::class ); + $instanceProperty = $reflection->getProperty( 'instance' ); + $instanceProperty->setAccessible( true ); + $instanceProperty->setValue( null ); } public function tearDown() : void { @@ -29,13 +35,40 @@ public function tearDown() : void { parent::tearDown(); } + public function test_init_returns_null_if_not_admin() { + // Unset the admin screen mock + unset( $GLOBALS['current_screen'] ); + + $instance = Settings_Page::init(); + $this->assertNull( $instance, 'Settings_Page::init() should return null if not in admin.' ); + } + + public function test_init_returns_null_for_user_without_manage_options_cap() { + $user_id = self::factory()->user->create( [ 'role' => 'subscriber' ] ); + wp_set_current_user( $user_id ); + + $instance = Settings_Page::init(); + $this->assertNull( $instance, 'Settings_Page::init() should return null for users without manage_options capability.' ); + } + + public function test_init_returns_instance_for_user_with_manage_options_cap() { + $user_id = self::factory()->user->create( [ 'role' => 'administrator' ] ); + wp_set_current_user( $user_id ); + + $instance = Settings_Page::init(); + $this->assertInstanceOf( Settings_Page::class, $instance, 'Settings_Page::init() should return an instance for users with manage_options capability.' ); + } + public function test_settings_page_instance() { $reflection = new ReflectionClass( Settings_Page::class ); $instanceProperty = $reflection->getProperty( 'instance' ); $instanceProperty->setAccessible( true ); - $instanceProperty->setValue( null ); $this->assertNull( $instanceProperty->getValue() ); + + // To pass the capability check + $user_id = self::factory()->user->create( [ 'role' => 'administrator' ] ); + wp_set_current_user( $user_id ); $instance = Settings_Page::init(); $this->assertInstanceOf( Settings_Page::class, $instanceProperty->getValue() ); @@ -44,7 +77,11 @@ public function test_settings_page_instance() { public function test_get_current_tab() { $_GET['attachment'] = 'attachment'; - $settings_page = Settings_Page::init(); + + // To pass the capability check + $user_id = self::factory()->user->create( [ 'role' => 'administrator' ] ); + wp_set_current_user( $user_id ); + $settings_page = Settings_Page::init(); $post_preview_service = new Post_Preview_Service(); $post_types = $post_preview_service->get_post_types(); @@ -61,6 +98,9 @@ public function test_get_current_tab() { } public function test_register_hooks() { + // To pass the capability check + $user_id = self::factory()->user->create( [ 'role' => 'administrator' ] ); + wp_set_current_user( $user_id ); $settings_page = Settings_Page::init(); $this->assertNull( $settings_page->register_settings_page() ); $this->assertNull( $settings_page->register_settings_fields() ); From 4afde985e623be3d4298889f4deed80d52013e62 Mon Sep 17 00:00:00 2001 From: Colin Murphy Date: Thu, 11 Sep 2025 20:25:16 +0100 Subject: [PATCH 2/4] Update README for clarity and accuracy Improved grammar, clarified feature descriptions, corrected file tree formatting, and updated example links for better guidance. Enhanced instructions and configuration details for easier understanding and setup. --- plugins/hwp-previews/README.md | 39 ++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/plugins/hwp-previews/README.md b/plugins/hwp-previews/README.md index 4a4adcb3..93d1c6bb 100644 --- a/plugins/hwp-previews/README.md +++ b/plugins/hwp-previews/README.md @@ -9,7 +9,7 @@ ----- [![Version](https://img.shields.io/github/v/release/wpengine/hwptoolkit?include_prereleases&label=previews&filter=%40wpengine%2Fhwp-previews-wordpress-plugin-*)](https://github.com/wpengine/hwptoolkit/releases) -[![License](https://img.shields.io/badge/license-GPLv2%2B-green)]() +[![License](https://img.shields.io/badge/license-GPLv2%2B-green)](https://www.gnu.org/licenses/gpl-2.0.html) ![GitHub forks](https://img.shields.io/github/forks/wpengine/hwptoolkit?style=social) ![GitHub stars](https://img.shields.io/github/stars/wpengine/hwptoolkit?style=social) [![Testing Integration](https://img.shields.io/github/check-runs/wpengine/hwptoolkit/main?checkName=hwp-previews%20codeception%20tests&label=Automated%20Tests)](https://github.com/wpengine/hwptoolkit/actions) @@ -21,7 +21,7 @@ > [!CAUTION] -> This plugin is currently in an beta state. It's still under active development, so you may encounter bugs or incomplete features. Updates will be rolled out regularly. Use with caution and provide feedback if possible. You can create an issue at [https://github.com/wpengine/hwptoolkit/issues](https://github.com/wpengine/hwptoolkit/issues) +> This plugin is currently in a beta state. It's still under active development, so you may encounter bugs or incomplete features. Updates will be rolled out regularly. Use with caution and provide feedback if possible. You can create an issue at [https://github.com/wpengine/hwptoolkit/issues](https://github.com/wpengine/hwptoolkit/issues) --- @@ -40,7 +40,7 @@ HWP Previews is a robust and extensible WordPress plugin that centralizes all preview configurations into a user-friendly settings interface. It empowers site administrators and developers to tailor preview behaviors for each public post type independently, facilitating seamless headless or decoupled workflows. -With HWP Previews, you can define dynamic URL templates, enforce unique slugs for drafts, allow all post statuses be used as parent and extend functionality through flexible hooks and filters, ensuring a consistent and conflict-free preview experience across diverse environments. +With HWP Previews, you can define dynamic URL templates, allow posts of all statuses to be used as parents, and extend functionality through flexible hooks and filters, ensuring a consistent and conflict-free preview experience across diverse environments. @@ -53,7 +53,7 @@ With HWP Previews, you can define dynamic URL templates, enforce unique slugs fo - **Enable/Disable Previews**: Turn preview functionality on or off for each public post type (including custom types). - **Custom URL Templates**: Define preview URLs using placeholder tokens for dynamic content. -- **Parent Status**: Allow posts of **all** statuses to be used as parent within hierarchical post types. +- **Parent Status**: Allow posts of **all** statuses to be used as parents within hierarchical post types. - **Highly Customizable**: Extend core behavior with a comprehensive set of actions and filters. - **Faust Compatibility**: The plugin is compatible with [Faust.js](https://faustjs.org/) and the [FaustWP plugin](https://github.com/wpengine/faustjs/tree/canary/plugins/faustwp). @@ -64,9 +64,9 @@ This guide will help you set up your first headless preview link for the "Posts" 1. **Activate the Plugin:** Ensure "HWP Previews" is installed and activated. 2. **Navigate to Settings:** Go to **Settings > HWP Previews** in your WordPress admin dashboard. -3. **Enable for Posts:** On the "Posts" tab check the "Enable HWP Previews" box. If you have Faust installed this option will be enabled by default. Find more information about Faust integration below. +3. **Enable for Posts:** On the "Posts" tab, check the "Enable HWP Previews" box. If you have Faust installed, this option will be enabled by default. Find more information about Faust integration below. 4. **Set the Preview URL:** In the "Preview URL Template" field for Posts, enter the URL for your front-end application's preview endpoint. Use parameters to add dynamic information that you want to access. -5. **Save and Test:** Save changes and go to any post, make a change, and click the "Preview" button. You should be redirected to the URL you just configured. +5. **Save and Test:** Save changes, go to any post, make a change, and click the "Preview" button. You should be redirected to the URL you just configured. --- @@ -81,15 +81,16 @@ hwp-previews/ │ ├── Preview/ # Preview URL logic, template resolver, helpers │ ├── Plugin.php # Main plugin class (entry point) │ └── Autoload.php # PSR-4 autoloader +├── examples/ # Example front-end integrations for WP GraphQL and REST ├── tests/ # All test suites -│ ├── wpunit/ # WPBrowser/Codeception unit -├── [hwp-previews.php] -├── [activation.php] -├── [composer.json] -├── [deactivation.php] -├── [ACTIONS_AND_FILTERS.md] -├── [TESTING.md] -├── [README.md] +│ ├── wpunit/ # WPBrowser/Codeception unit +├── hwp-previews.php +├── activation.php +├── composer.json +├── deactivation.php +├── ACTIONS_AND_FILTERS.md +├── TESTING.md +├── README.md ``` ## Configuration @@ -101,12 +102,12 @@ HWP Previews configuration located at **Settings > HWP Previews** page in your W For each public post type, you can configure: - **Enable HWP Previews:** This is the master switch for the post type. If disabled, WordPress will revert to its default preview behavior for these posts. -- **Allow All Statuses as Parent:** This option is only available for Pages type. By default, WordPress only allows published posts to be parents. Enable this to build parent-child relationships using draft or pending posts. +- **Allow All Statuses as Parent:** This option is only available for hierarchical post types like Pages. By default, WordPress only allows published posts to be parents. Enable this to build parent-child relationships using draft or pending posts. - **Load Previews in Iframe:** When enabled, the preview will be displayed directly within the WordPress editor in a sandboxed `