From 485ed1f4ddddaa47333ff262bc632ba94228175b Mon Sep 17 00:00:00 2001 From: Dimitrios Pantazis Date: Mon, 9 Feb 2026 22:01:06 +0200 Subject: [PATCH 01/20] feat: add methods to specify parent page and go back to List URL --- src/Admin/Provider.php | 133 ++++++++++++++++++++++++++++++++++++++++- src/views/single.php | 13 ++++ 2 files changed, 145 insertions(+), 1 deletion(-) diff --git a/src/Admin/Provider.php b/src/Admin/Provider.php index 5fd0ab6..553e4f3 100644 --- a/src/Admin/Provider.php +++ b/src/Admin/Provider.php @@ -32,6 +32,38 @@ class Provider { */ private static bool $registered = false; + /** + * The parent page slug for sidebar highlighting. + * + * When set, the admin sidebar will highlight this menu item + * while viewing the single migration page. + * + * @since 0.0.1 + * + * @var ?string + */ + protected static ?string $parent_page = null; + + /** + * Stores the original parent file during hijacking. + * + * @since 0.0.1 + * + * @var ?string + */ + protected static ?string $stored_parent_file = null; + + /** + * The URL of the migrations list page. + * + * When set, the single migration page will display a back link. + * + * @since 0.0.1 + * + * @var ?string + */ + protected static ?string $list_url = null; + /** * Register the admin provider. * @@ -48,6 +80,8 @@ public function register(): void { add_action( 'admin_menu', [ $this, 'register_hidden_page' ] ); add_filter( 'set-screen-option', [ $this, 'save_screen_option' ], 10, 3 ); + add_filter( 'submenu_file', [ $this, 'hijack_current_parent_file' ] ); + add_action( 'adminmenu', [ $this, 'restore_current_parent_file' ] ); } /** @@ -179,6 +213,100 @@ public static function get_single_url( string $migration_id ): string { ); } + /** + * Set the parent page slug for sidebar highlighting. + * + * When set, viewing the single migration page will highlight + * the specified menu item in the admin sidebar. + * + * @since 0.0.1 + * + * @param string $parent_page The parent menu slug (e.g. 'edit.php?post_type=page'). + * + * @return void + */ + public static function set_parent_page( string $parent_page ): void { + self::$parent_page = $parent_page; + } + + /** + * Set the URL for the migrations list page. + * + * When set, the single migration page will display a link + * back to the list page. + * + * @since 0.0.1 + * + * @param string $url The URL of the migrations list page. + * + * @return void + */ + public static function set_list_url( string $url ): void { + self::$list_url = $url; + } + + /** + * Get the URL for the migrations list page. + * + * @since 0.0.1 + * + * @return string|null The list page URL, or null if not set. + */ + public static function get_list_url(): ?string { + return self::$list_url; + } + + /** + * Hijack the current parent file for sidebar highlighting. + * + * When a parent page is configured and we are on the single + * migration page, this overrides `$parent_file` so the correct + * admin menu item is highlighted in the sidebar. + * + * @since 0.0.1 + * + * @param string $submenu_file The submenu file. + * + * @return string The submenu file (unchanged). + */ + public function hijack_current_parent_file( $submenu_file ) { + if ( ! self::$parent_page ) { + return $submenu_file; + } + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $current_page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; + + if ( $current_page !== self::get_single_page_slug() ) { + return $submenu_file; + } + + global $parent_file; + + self::$stored_parent_file = $parent_file; + + $parent_file = self::$parent_page; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + + return $submenu_file; + } + + /** + * Restore the original parent file after the admin menu renders. + * + * @since 0.0.1 + * + * @return void + */ + public function restore_current_parent_file(): void { + if ( ! isset( self::$stored_parent_file ) ) { + return; + } + + global $parent_file; + + $parent_file = self::$stored_parent_file; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + } + /** * Reset registration status. Primarily for testing. * @@ -187,6 +315,9 @@ public static function get_single_url( string $migration_id ): string { * @return void */ public static function reset(): void { - self::$registered = false; + self::$registered = false; + self::$parent_page = null; + self::$stored_parent_file = null; + self::$list_url = null; } } diff --git a/src/views/single.php b/src/views/single.php index abcb189..c2697ab 100644 --- a/src/views/single.php +++ b/src/views/single.php @@ -14,6 +14,7 @@ defined( 'ABSPATH' ) || exit; +use StellarWP\Migrations\Admin\Provider as Admin_Provider; use StellarWP\Migrations\Config; use StellarWP\Migrations\Contracts\Migration; @@ -29,6 +30,18 @@ $template = Config::get_template_engine(); ?> + +
+ + + +
+
+ +

From 1e2eb478f76d822325c358c37285fdb6e83ab073 Mon Sep 17 00:00:00 2001 From: Dimitrios Pantazis Date: Mon, 9 Feb 2026 22:02:19 +0200 Subject: [PATCH 02/20] tests: added test coverage --- tests/wpunit/Admin/Provider_Test.php | 230 ++++++++++++++++++ tests/wpunit/Admin/UI_Test.php | 39 +++ ...ck_link_when_list_url_set__0.snapshot.html | 127 ++++++++++ 3 files changed, 396 insertions(+) create mode 100644 tests/wpunit/Admin/Provider_Test.php create mode 100644 tests/wpunit/Admin/__snapshots__/UI_Test__it_should_render_single_with_back_link_when_list_url_set__0.snapshot.html diff --git a/tests/wpunit/Admin/Provider_Test.php b/tests/wpunit/Admin/Provider_Test.php new file mode 100644 index 0000000..2a7ae6e --- /dev/null +++ b/tests/wpunit/Admin/Provider_Test.php @@ -0,0 +1,230 @@ +provider = new Provider(); + } + + /** + * Tear down after each test. + * + * @after + * + * @return void + */ + public function tear_down_provider(): void { + Provider::reset(); + unset( $_GET['page'] ); + } + + /** + * @test + */ + public function it_should_not_hijack_parent_file_when_no_parent_page_set(): void { + global $parent_file; + + $original_parent_file = $parent_file; + $submenu_file = 'some-submenu-file.php'; + + $result = $this->provider->hijack_current_parent_file( $submenu_file ); + + $this->assertSame( $submenu_file, $result, 'The submenu_file should be returned unchanged.' ); + $this->assertSame( $original_parent_file, $parent_file, 'The global $parent_file should not be modified.' ); + } + + /** + * @test + */ + public function it_should_not_hijack_parent_file_when_not_on_single_page(): void { + global $parent_file; + + Provider::set_parent_page( 'edit.php?post_type=page' ); + + $original_parent_file = $parent_file; + $submenu_file = 'some-submenu-file.php'; + + // Simulate being on a different admin page. + $_GET['page'] = 'some-other-page'; + + $result = $this->provider->hijack_current_parent_file( $submenu_file ); + + $this->assertSame( $submenu_file, $result, 'The submenu_file should be returned unchanged.' ); + $this->assertSame( $original_parent_file, $parent_file, 'The global $parent_file should not be modified when not on the single page.' ); + } + + /** + * @test + */ + public function it_should_hijack_parent_file_when_on_single_page(): void { + global $parent_file; + + $target_parent = 'edit.php?post_type=page'; + Provider::set_parent_page( $target_parent ); + + $parent_file = 'options-general.php'; + $submenu_file = 'some-submenu-file.php'; + + // Simulate being on the single migration page. + $_GET['page'] = 'stellarwp-migrations-foobar-single'; + + $result = $this->provider->hijack_current_parent_file( $submenu_file ); + + $this->assertSame( $submenu_file, $result, 'The submenu_file return value should remain unchanged.' ); + $this->assertSame( $target_parent, $parent_file, 'The global $parent_file should be overridden to the configured parent page.' ); + } + + /** + * @test + */ + public function it_should_restore_parent_file_after_hijack(): void { + global $parent_file; + + $original_parent_file = 'options-general.php'; + $target_parent = 'edit.php?post_type=page'; + + $parent_file = $original_parent_file; + Provider::set_parent_page( $target_parent ); + + // Simulate being on the single migration page and trigger hijack. + $_GET['page'] = 'stellarwp-migrations-foobar-single'; + $this->provider->hijack_current_parent_file( 'submenu.php' ); + + // Verify hijack occurred. + $this->assertSame( $target_parent, $parent_file, 'The parent_file should be hijacked at this point.' ); + + // Restore the parent file. + $this->provider->restore_current_parent_file(); + + $this->assertSame( $original_parent_file, $parent_file, 'The global $parent_file should be restored to its original value.' ); + } + + /** + * @test + */ + public function it_should_not_restore_when_no_hijack_occurred(): void { + global $parent_file; + + $parent_file = 'options-general.php'; + + // Call restore without any prior hijack. + $this->provider->restore_current_parent_file(); + + $this->assertSame( 'options-general.php', $parent_file, 'The global $parent_file should remain unchanged when no hijack occurred.' ); + } + + /** + * @test + */ + public function it_should_return_null_when_list_url_not_set(): void { + $this->assertNull( Provider::get_list_url(), 'The list URL should be null by default.' ); + } + + /** + * @test + */ + public function it_should_return_configured_list_url(): void { + $url = 'https://example.com/wp-admin/admin.php?page=my-migrations'; + + Provider::set_list_url( $url ); + + $this->assertSame( $url, Provider::get_list_url(), 'The list URL should match the configured value.' ); + } + + /** + * @test + */ + public function it_should_reset_parent_page_and_list_url(): void { + global $parent_file; + + // Set up state. + Provider::set_parent_page( 'edit.php?post_type=page' ); + Provider::set_list_url( 'https://example.com/wp-admin/admin.php?page=my-migrations' ); + + // Trigger a hijack to populate stored_parent_file. + $parent_file = 'options-general.php'; + $_GET['page'] = 'stellarwp-migrations-foobar-single'; + $this->provider->hijack_current_parent_file( 'submenu.php' ); + + // Reset all state. + Provider::reset(); + + $this->assertNull( Provider::get_list_url(), 'The list URL should be null after reset.' ); + + // After reset, hijack should be a no-op (parent_page is cleared). + $parent_file = 'some-file.php'; + $result = $this->provider->hijack_current_parent_file( 'submenu.php' ); + + $this->assertSame( 'some-file.php', $parent_file, 'The parent_file should not be hijacked after reset.' ); + + // After reset, restore should be a no-op (stored_parent_file is cleared). + $this->provider->restore_current_parent_file(); + + $this->assertSame( 'some-file.php', $parent_file, 'The parent_file should remain unchanged after restore when reset was called.' ); + } + + /** + * @test + */ + public function it_should_register_submenu_file_filter(): void { + $this->provider->register(); + + $this->assertIsInt( + has_filter( 'submenu_file', [ $this->provider, 'hijack_current_parent_file' ] ), + 'The submenu_file filter should be registered.' + ); + } + + /** + * @test + */ + public function it_should_register_adminmenu_action(): void { + $this->provider->register(); + + $this->assertIsInt( + has_action( 'adminmenu', [ $this->provider, 'restore_current_parent_file' ] ), + 'The adminmenu action should be registered.' + ); + } +} diff --git a/tests/wpunit/Admin/UI_Test.php b/tests/wpunit/Admin/UI_Test.php index a2a1a88..5b34257 100644 --- a/tests/wpunit/Admin/UI_Test.php +++ b/tests/wpunit/Admin/UI_Test.php @@ -14,6 +14,7 @@ namespace StellarWP\Migrations\Tests\Admin; use lucatume\WPBrowser\TestCase\WPTestCase; +use StellarWP\Migrations\Admin\Provider as Admin_Provider; use StellarWP\Migrations\Admin\UI; use StellarWP\Migrations\Config; use StellarWP\Migrations\Enums\Status; @@ -87,6 +88,7 @@ public function tear_down_ui(): void { Multi_Batch_Migration::reset(); Not_Applicable_Migration::reset(); Tagged_Migration::reset(); + Admin_Provider::reset(); tests_migrations_clear_calls_data(); } @@ -490,4 +492,41 @@ public function it_should_trigger_doing_it_wrong_with_correct_message(): void { // The setExpectedIncorrectUsage will handle the assertion. } + + /** + * @test + */ + public function it_should_render_single_with_back_link_when_list_url_set(): void { + $this->set_admin_user(); + $this->register_test_migrations(); + + Admin_Provider::set_list_url( 'http://wordpress.test/wp-admin/admin.php?page=test-migrations' ); + + ob_start(); + $this->ui->render_single( 'tests_simple_migration' ); + $output = ob_get_clean(); + + // Verify the back link container and link are present with the correct URL and text. + $this->assertStringContainsString( 'stellarwp-migration-single__back', $output ); + $this->assertStringContainsString( 'stellarwp-migration-single__back-link', $output ); + $this->assertStringContainsString( 'http://wordpress.test/wp-admin/admin.php?page=test-migrations', $output ); + $this->assertStringContainsString( 'Migrations', $output ); + + $this->assertMatchesHtmlSnapshot( $output ); + } + + /** + * @test + */ + public function it_should_render_single_without_back_link_when_list_url_not_set(): void { + $this->set_admin_user(); + $this->register_test_migrations(); + + // No list URL is set (default after reset), so the back link should not appear. + ob_start(); + $this->ui->render_single( 'tests_simple_migration' ); + $output = ob_get_clean(); + + $this->assertStringNotContainsString( 'stellarwp-migration-single__back', $output ); + } } diff --git a/tests/wpunit/Admin/__snapshots__/UI_Test__it_should_render_single_with_back_link_when_list_url_set__0.snapshot.html b/tests/wpunit/Admin/__snapshots__/UI_Test__it_should_render_single_with_back_link_when_list_url_set__0.snapshot.html new file mode 100644 index 0000000..f681d1c --- /dev/null +++ b/tests/wpunit/Admin/__snapshots__/UI_Test__it_should_render_single_with_back_link_when_list_url_set__0.snapshot.html @@ -0,0 +1,127 @@ + + +
+
+

Simple Migration

+

This is a simple migration that runs a single batch.

+
+ +
+

Status

+
+
+
+ + Pending + + + 0 / 1 +
+ +
+
+
+
+
+
+
+ +
+ + +
+
+ +
+ +
+ + Not yet started +
+ + +
+
+ +
+

Configuration

+
+
+
+ Total Items + 1 +
+ +
+ Batch Size + 1 +
+ +
+ Total Batches + 1 +
+ +
+ Retry Attempts + 0 +
+ +
+ Applicable + + Yes +
+ +
+ Can Run + + Yes +
+ +
+
+
+ +
+

Logs

+
+
+

No executions yet. Run the migration to see logs.

+
+
+
+
From cbe6142f3af7cfbaa2375057f1241c9e1b3baedb Mon Sep 17 00:00:00 2001 From: Dimitrios Pantazis Date: Mon, 9 Feb 2026 22:02:30 +0200 Subject: [PATCH 03/20] docs: updated docs with included changes --- docs/admin-ui.md | 71 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/docs/admin-ui.md b/docs/admin-ui.md index f05df7e..85dfc03 100644 --- a/docs/admin-ui.md +++ b/docs/admin-ui.md @@ -10,6 +10,7 @@ The Admin UI is built with a template engine abstraction, allowing consumers to src/ ├── Admin/ │ ├── Assets.php # CSS/JS asset registration and enqueuing +│ ├── Provider.php # Hidden page registration, sidebar highlighting, back link │ └── UI.php # Main UI class with render methods ├── Contracts/ │ └── Template_Engine.php # Interface for template rendering @@ -366,6 +367,76 @@ $ui->render_list(); This ensures that when users filter migrations, they stay on the same settings page and section. +## Admin Provider + +The `Admin\Provider` class registers the hidden single migration page and provides static configuration methods for sidebar highlighting and back navigation. + +### `Provider::set_parent_page( string $parent_page )` + +Configures which admin sidebar menu item should be highlighted when viewing the single migration detail page. + +By default, the single migration page is a hidden submenu page (no parent), so no sidebar item is highlighted. When you set a parent page, the library temporarily overrides the WordPress `$parent_file` global so that the specified menu item appears active. + +```php +use StellarWP\Migrations\Admin\Provider; + +// Highlight the "Migrations" menu item when viewing a single migration. +Provider::set_parent_page( 'my-plugin-migrations' ); +``` + +This uses the `submenu_file` filter and `adminmenu` action internally, following the same pattern used by WordPress core and other plugins for sidebar highlighting of hidden pages. + +**Call this before the `admin_menu` hook fires** (e.g., during plugin initialization or service provider registration). + +--- + +### `Provider::set_list_url( string $url )` + +Sets the URL for the migrations list page. When configured, the single migration detail page will display a "Migrations" back link above the migration content, separated by a soft horizontal rule. + +```php +use StellarWP\Migrations\Admin\Provider; + +Provider::set_list_url( admin_url( 'admin.php?page=my-plugin-migrations' ) ); +``` + +**Rendered output (when set):** + +```html + +``` + +When not set, no back link is rendered. + +--- + +### `Provider::get_list_url()` + +Returns the configured list page URL, or `null` if not set. + +```php +use StellarWP\Migrations\Admin\Provider; + +$url = Provider::get_list_url(); // string|null +``` + +--- + +### Complete Provider Configuration Example + +```php +use StellarWP\Migrations\Admin\Provider; + +// During plugin initialization, configure the admin provider. +Provider::set_parent_page( 'my-plugin-migrations' ); +Provider::set_list_url( admin_url( 'admin.php?page=my-plugin-migrations' ) ); +``` + ## Custom Integration Example Here's a complete example of integrating the Admin UI with a custom template engine: From 74bf00948dd32b05bccf3d8a8d57f73b4610047d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 9 Feb 2026 20:03:40 +0000 Subject: [PATCH 04/20] chore: autopublish 2026-02-09T20:03:40Z --- docs/admin-ui.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/admin-ui.md b/docs/admin-ui.md index 85dfc03..05679b7 100644 --- a/docs/admin-ui.md +++ b/docs/admin-ui.md @@ -404,10 +404,10 @@ Provider::set_list_url( admin_url( 'admin.php?page=my-plugin-migrations' ) ); ```html ``` From c3aa16c2f794d286f6dcb42ea3743568e328de40 Mon Sep 17 00:00:00 2001 From: Dimitrios Pantazis Date: Mon, 9 Feb 2026 22:16:33 +0200 Subject: [PATCH 05/20] fix: overwritten global --- src/Admin/Provider.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Admin/Provider.php b/src/Admin/Provider.php index 553e4f3..74c8a1b 100644 --- a/src/Admin/Provider.php +++ b/src/Admin/Provider.php @@ -281,11 +281,11 @@ public function hijack_current_parent_file( $submenu_file ) { return $submenu_file; } - global $parent_file; + global $plugin_page; - self::$stored_parent_file = $parent_file; + self::$stored_parent_file = $plugin_page; - $parent_file = self::$parent_page; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + $plugin_page = self::$parent_page; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited return $submenu_file; } @@ -302,9 +302,9 @@ public function restore_current_parent_file(): void { return; } - global $parent_file; + global $plugin_page; - $parent_file = self::$stored_parent_file; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + $plugin_page = self::$stored_parent_file; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited } /** From f189f301f7b66cb62315bd1b714623abd7a84126 Mon Sep 17 00:00:00 2001 From: Dimitrios Pantazis Date: Mon, 9 Feb 2026 22:23:28 +0200 Subject: [PATCH 06/20] feat: update refs from parent_file to plugin page --- docs/admin-ui.md | 2 +- src/Admin/Provider.php | 2 +- tests/wpunit/Admin/Provider_Test.php | 46 ++++++++++++++-------------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/docs/admin-ui.md b/docs/admin-ui.md index 05679b7..5234299 100644 --- a/docs/admin-ui.md +++ b/docs/admin-ui.md @@ -375,7 +375,7 @@ The `Admin\Provider` class registers the hidden single migration page and provid Configures which admin sidebar menu item should be highlighted when viewing the single migration detail page. -By default, the single migration page is a hidden submenu page (no parent), so no sidebar item is highlighted. When you set a parent page, the library temporarily overrides the WordPress `$parent_file` global so that the specified menu item appears active. +By default, the single migration page is a hidden submenu page (no parent), so no sidebar item is highlighted. When you set a parent page, the library temporarily overrides the WordPress `$plugin_page` global so that the specified menu item appears active. ```php use StellarWP\Migrations\Admin\Provider; diff --git a/src/Admin/Provider.php b/src/Admin/Provider.php index 74c8a1b..b199f07 100644 --- a/src/Admin/Provider.php +++ b/src/Admin/Provider.php @@ -260,7 +260,7 @@ public static function get_list_url(): ?string { * Hijack the current parent file for sidebar highlighting. * * When a parent page is configured and we are on the single - * migration page, this overrides `$parent_file` so the correct + * migration page, this overrides `$plugin_page` so the correct * admin menu item is highlighted in the sidebar. * * @since 0.0.1 diff --git a/tests/wpunit/Admin/Provider_Test.php b/tests/wpunit/Admin/Provider_Test.php index 2a7ae6e..8267616 100644 --- a/tests/wpunit/Admin/Provider_Test.php +++ b/tests/wpunit/Admin/Provider_Test.php @@ -63,26 +63,26 @@ public function tear_down_provider(): void { * @test */ public function it_should_not_hijack_parent_file_when_no_parent_page_set(): void { - global $parent_file; + global $plugin_page; - $original_parent_file = $parent_file; + $original_plugin_page = $plugin_page; $submenu_file = 'some-submenu-file.php'; $result = $this->provider->hijack_current_parent_file( $submenu_file ); $this->assertSame( $submenu_file, $result, 'The submenu_file should be returned unchanged.' ); - $this->assertSame( $original_parent_file, $parent_file, 'The global $parent_file should not be modified.' ); + $this->assertSame( $original_plugin_page, $plugin_page, 'The global $plugin_page should not be modified.' ); } /** * @test */ public function it_should_not_hijack_parent_file_when_not_on_single_page(): void { - global $parent_file; + global $plugin_page; Provider::set_parent_page( 'edit.php?post_type=page' ); - $original_parent_file = $parent_file; + $original_plugin_page = $plugin_page; $submenu_file = 'some-submenu-file.php'; // Simulate being on a different admin page. @@ -91,19 +91,19 @@ public function it_should_not_hijack_parent_file_when_not_on_single_page(): void $result = $this->provider->hijack_current_parent_file( $submenu_file ); $this->assertSame( $submenu_file, $result, 'The submenu_file should be returned unchanged.' ); - $this->assertSame( $original_parent_file, $parent_file, 'The global $parent_file should not be modified when not on the single page.' ); + $this->assertSame( $original_plugin_page, $plugin_page, 'The global $plugin_page should not be modified when not on the single page.' ); } /** * @test */ public function it_should_hijack_parent_file_when_on_single_page(): void { - global $parent_file; + global $plugin_page; $target_parent = 'edit.php?post_type=page'; Provider::set_parent_page( $target_parent ); - $parent_file = 'options-general.php'; + $plugin_page = 'options-general.php'; $submenu_file = 'some-submenu-file.php'; // Simulate being on the single migration page. @@ -112,19 +112,19 @@ public function it_should_hijack_parent_file_when_on_single_page(): void { $result = $this->provider->hijack_current_parent_file( $submenu_file ); $this->assertSame( $submenu_file, $result, 'The submenu_file return value should remain unchanged.' ); - $this->assertSame( $target_parent, $parent_file, 'The global $parent_file should be overridden to the configured parent page.' ); + $this->assertSame( $target_parent, $plugin_page, 'The global $plugin_page should be overridden to the configured parent page.' ); } /** * @test */ public function it_should_restore_parent_file_after_hijack(): void { - global $parent_file; + global $plugin_page; - $original_parent_file = 'options-general.php'; + $original_plugin_page = 'options-general.php'; $target_parent = 'edit.php?post_type=page'; - $parent_file = $original_parent_file; + $plugin_page = $original_plugin_page; Provider::set_parent_page( $target_parent ); // Simulate being on the single migration page and trigger hijack. @@ -132,26 +132,26 @@ public function it_should_restore_parent_file_after_hijack(): void { $this->provider->hijack_current_parent_file( 'submenu.php' ); // Verify hijack occurred. - $this->assertSame( $target_parent, $parent_file, 'The parent_file should be hijacked at this point.' ); + $this->assertSame( $target_parent, $plugin_page, 'The plugin_page should be hijacked at this point.' ); - // Restore the parent file. + // Restore the plugin page. $this->provider->restore_current_parent_file(); - $this->assertSame( $original_parent_file, $parent_file, 'The global $parent_file should be restored to its original value.' ); + $this->assertSame( $original_plugin_page, $plugin_page, 'The global $plugin_page should be restored to its original value.' ); } /** * @test */ public function it_should_not_restore_when_no_hijack_occurred(): void { - global $parent_file; + global $plugin_page; - $parent_file = 'options-general.php'; + $plugin_page = 'options-general.php'; // Call restore without any prior hijack. $this->provider->restore_current_parent_file(); - $this->assertSame( 'options-general.php', $parent_file, 'The global $parent_file should remain unchanged when no hijack occurred.' ); + $this->assertSame( 'options-general.php', $plugin_page, 'The global $plugin_page should remain unchanged when no hijack occurred.' ); } /** @@ -176,14 +176,14 @@ public function it_should_return_configured_list_url(): void { * @test */ public function it_should_reset_parent_page_and_list_url(): void { - global $parent_file; + global $plugin_page; // Set up state. Provider::set_parent_page( 'edit.php?post_type=page' ); Provider::set_list_url( 'https://example.com/wp-admin/admin.php?page=my-migrations' ); // Trigger a hijack to populate stored_parent_file. - $parent_file = 'options-general.php'; + $plugin_page = 'options-general.php'; $_GET['page'] = 'stellarwp-migrations-foobar-single'; $this->provider->hijack_current_parent_file( 'submenu.php' ); @@ -193,15 +193,15 @@ public function it_should_reset_parent_page_and_list_url(): void { $this->assertNull( Provider::get_list_url(), 'The list URL should be null after reset.' ); // After reset, hijack should be a no-op (parent_page is cleared). - $parent_file = 'some-file.php'; + $plugin_page = 'some-file.php'; $result = $this->provider->hijack_current_parent_file( 'submenu.php' ); - $this->assertSame( 'some-file.php', $parent_file, 'The parent_file should not be hijacked after reset.' ); + $this->assertSame( 'some-file.php', $plugin_page, 'The plugin_page should not be hijacked after reset.' ); // After reset, restore should be a no-op (stored_parent_file is cleared). $this->provider->restore_current_parent_file(); - $this->assertSame( 'some-file.php', $parent_file, 'The parent_file should remain unchanged after restore when reset was called.' ); + $this->assertSame( 'some-file.php', $plugin_page, 'The plugin_page should remain unchanged after restore when reset was called.' ); } /** From c74499659a8ee2880c8e654f25b3c4c146e8e2f3 Mon Sep 17 00:00:00 2001 From: Dimitrios Pantazis Date: Mon, 9 Feb 2026 22:49:01 +0200 Subject: [PATCH 07/20] feat: improve style of back to list URL --- src/Admin/Provider.php | 41 ++++++ src/views/single.php | 13 -- tests/wpunit/Admin/Provider_Test.php | 56 ++++++++ tests/wpunit/Admin/UI_Test.php | 38 ------ ...ck_link_when_list_url_set__0.snapshot.html | 127 ------------------ 5 files changed, 97 insertions(+), 178 deletions(-) delete mode 100644 tests/wpunit/Admin/__snapshots__/UI_Test__it_should_render_single_with_back_link_when_list_url_set__0.snapshot.html diff --git a/src/Admin/Provider.php b/src/Admin/Provider.php index b199f07..d3c3f8a 100644 --- a/src/Admin/Provider.php +++ b/src/Admin/Provider.php @@ -104,6 +104,7 @@ public function register_hidden_page(): void { if ( $single_page_hook && is_string( $single_page_hook ) ) { add_action( 'load-' . $single_page_hook, [ $this, 'add_screen_options' ] ); add_action( 'load-' . $single_page_hook, [ $this, 'set_page_title' ] ); + add_action( 'load-' . $single_page_hook, [ $this, 'maybe_add_back_link' ] ); } } @@ -143,6 +144,46 @@ public function set_page_title(): void { } } + /** + * Conditionally add the back link to the single migration page. + * + * When a list URL is configured, hooks into `all_admin_notices` to + * render the back link after admin notices at full width. + * + * @since 0.0.1 + * + * @return void + */ + public function maybe_add_back_link(): void { + if ( ! self::$list_url ) { + return; + } + + add_action( 'all_admin_notices', [ $this, 'render_back_link' ], 999 ); + } + + /** + * Render the back link to the migrations list page. + * + * Outputs the link and a full-width horizontal rule separator + * after admin notices and before the page content. + * + * @since 0.0.1 + * + * @return void + */ + public function render_back_link(): void { + if ( ! self::$list_url ) { + return; + } + + printf( + '', + esc_url( self::$list_url ), + esc_html__( 'Migrations', 'stellarwp-migrations' ) + ); + } + /** * Save the screen option value. * diff --git a/src/views/single.php b/src/views/single.php index c2697ab..abcb189 100644 --- a/src/views/single.php +++ b/src/views/single.php @@ -14,7 +14,6 @@ defined( 'ABSPATH' ) || exit; -use StellarWP\Migrations\Admin\Provider as Admin_Provider; use StellarWP\Migrations\Config; use StellarWP\Migrations\Contracts\Migration; @@ -30,18 +29,6 @@ $template = Config::get_template_engine(); ?> - -
- - - -
-
- -

diff --git a/tests/wpunit/Admin/Provider_Test.php b/tests/wpunit/Admin/Provider_Test.php index 8267616..bf826be 100644 --- a/tests/wpunit/Admin/Provider_Test.php +++ b/tests/wpunit/Admin/Provider_Test.php @@ -227,4 +227,60 @@ public function it_should_register_adminmenu_action(): void { 'The adminmenu action should be registered.' ); } + + /** + * @test + */ + public function it_should_add_back_link_action_when_list_url_set(): void { + Provider::set_list_url( 'http://wordpress.test/wp-admin/admin.php?page=test-migrations' ); + + $this->provider->maybe_add_back_link(); + + $this->assertIsInt( + has_action( 'all_admin_notices', [ $this->provider, 'render_back_link' ] ), + 'The all_admin_notices action should be registered when list URL is set.' + ); + } + + /** + * @test + */ + public function it_should_not_add_back_link_action_when_list_url_not_set(): void { + $this->provider->maybe_add_back_link(); + + $this->assertFalse( + has_action( 'all_admin_notices', [ $this->provider, 'render_back_link' ] ), + 'The all_admin_notices action should not be registered when no list URL is set.' + ); + } + + /** + * @test + */ + public function it_should_render_back_link_html(): void { + $url = 'http://wordpress.test/wp-admin/admin.php?page=test-migrations'; + Provider::set_list_url( $url ); + + ob_start(); + $this->provider->render_back_link(); + $output = ob_get_clean(); + + $this->assertStringContainsString( 'stellarwp-migration-single__back', $output ); + $this->assertStringContainsString( 'stellarwp-migration-single__back-link', $output ); + $this->assertStringContainsString( $url, $output ); + $this->assertStringContainsString( 'Migrations', $output ); + $this->assertStringContainsString( 'dashicons-arrow-left-alt2', $output ); + $this->assertStringContainsString( '
provider->render_back_link(); + $output = ob_get_clean(); + + $this->assertEmpty( $output, 'No output should be rendered when list URL is not set.' ); + } } diff --git a/tests/wpunit/Admin/UI_Test.php b/tests/wpunit/Admin/UI_Test.php index 5b34257..0f3bd97 100644 --- a/tests/wpunit/Admin/UI_Test.php +++ b/tests/wpunit/Admin/UI_Test.php @@ -14,7 +14,6 @@ namespace StellarWP\Migrations\Tests\Admin; use lucatume\WPBrowser\TestCase\WPTestCase; -use StellarWP\Migrations\Admin\Provider as Admin_Provider; use StellarWP\Migrations\Admin\UI; use StellarWP\Migrations\Config; use StellarWP\Migrations\Enums\Status; @@ -88,7 +87,6 @@ public function tear_down_ui(): void { Multi_Batch_Migration::reset(); Not_Applicable_Migration::reset(); Tagged_Migration::reset(); - Admin_Provider::reset(); tests_migrations_clear_calls_data(); } @@ -493,40 +491,4 @@ public function it_should_trigger_doing_it_wrong_with_correct_message(): void { // The setExpectedIncorrectUsage will handle the assertion. } - /** - * @test - */ - public function it_should_render_single_with_back_link_when_list_url_set(): void { - $this->set_admin_user(); - $this->register_test_migrations(); - - Admin_Provider::set_list_url( 'http://wordpress.test/wp-admin/admin.php?page=test-migrations' ); - - ob_start(); - $this->ui->render_single( 'tests_simple_migration' ); - $output = ob_get_clean(); - - // Verify the back link container and link are present with the correct URL and text. - $this->assertStringContainsString( 'stellarwp-migration-single__back', $output ); - $this->assertStringContainsString( 'stellarwp-migration-single__back-link', $output ); - $this->assertStringContainsString( 'http://wordpress.test/wp-admin/admin.php?page=test-migrations', $output ); - $this->assertStringContainsString( 'Migrations', $output ); - - $this->assertMatchesHtmlSnapshot( $output ); - } - - /** - * @test - */ - public function it_should_render_single_without_back_link_when_list_url_not_set(): void { - $this->set_admin_user(); - $this->register_test_migrations(); - - // No list URL is set (default after reset), so the back link should not appear. - ob_start(); - $this->ui->render_single( 'tests_simple_migration' ); - $output = ob_get_clean(); - - $this->assertStringNotContainsString( 'stellarwp-migration-single__back', $output ); - } } diff --git a/tests/wpunit/Admin/__snapshots__/UI_Test__it_should_render_single_with_back_link_when_list_url_set__0.snapshot.html b/tests/wpunit/Admin/__snapshots__/UI_Test__it_should_render_single_with_back_link_when_list_url_set__0.snapshot.html deleted file mode 100644 index f681d1c..0000000 --- a/tests/wpunit/Admin/__snapshots__/UI_Test__it_should_render_single_with_back_link_when_list_url_set__0.snapshot.html +++ /dev/null @@ -1,127 +0,0 @@ - - -
-
-

Simple Migration

-

This is a simple migration that runs a single batch.

-
- -
-

Status

-
-
-
- - Pending - - - 0 / 1 -
- -
-
-
-
-
-
-
- -
- - -
-
- -
- -
- - Not yet started -
- - -
-
- -
-

Configuration

-
-
-
- Total Items - 1 -
- -
- Batch Size - 1 -
- -
- Total Batches - 1 -
- -
- Retry Attempts - 0 -
- -
- Applicable - - Yes -
- -
- Can Run - - Yes -
- -
-
-
- -
-

Logs

-
-
-

No executions yet. Run the migration to see logs.

-
-
-
-
From 905aede46d30db2defc202b48a5419d4c9fede8b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 9 Feb 2026 20:49:44 +0000 Subject: [PATCH 08/20] chore: autopublish 2026-02-09T20:49:44Z --- tests/wpunit/Admin/UI_Test.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/wpunit/Admin/UI_Test.php b/tests/wpunit/Admin/UI_Test.php index 0f3bd97..a2a1a88 100644 --- a/tests/wpunit/Admin/UI_Test.php +++ b/tests/wpunit/Admin/UI_Test.php @@ -490,5 +490,4 @@ public function it_should_trigger_doing_it_wrong_with_correct_message(): void { // The setExpectedIncorrectUsage will handle the assertion. } - } From a8d206759cc3c85c5bae3cdb42679611665aa6a5 Mon Sep 17 00:00:00 2001 From: Dimitrios Pantazis Date: Mon, 9 Feb 2026 22:56:18 +0200 Subject: [PATCH 09/20] refactor: include the link within the template --- src/Admin/Provider.php | 42 +----------------------------------------- src/Admin/UI.php | 1 + src/views/single.php | 8 ++++++++ 3 files changed, 10 insertions(+), 41 deletions(-) diff --git a/src/Admin/Provider.php b/src/Admin/Provider.php index d3c3f8a..7dac33b 100644 --- a/src/Admin/Provider.php +++ b/src/Admin/Provider.php @@ -104,7 +104,6 @@ public function register_hidden_page(): void { if ( $single_page_hook && is_string( $single_page_hook ) ) { add_action( 'load-' . $single_page_hook, [ $this, 'add_screen_options' ] ); add_action( 'load-' . $single_page_hook, [ $this, 'set_page_title' ] ); - add_action( 'load-' . $single_page_hook, [ $this, 'maybe_add_back_link' ] ); } } @@ -144,46 +143,6 @@ public function set_page_title(): void { } } - /** - * Conditionally add the back link to the single migration page. - * - * When a list URL is configured, hooks into `all_admin_notices` to - * render the back link after admin notices at full width. - * - * @since 0.0.1 - * - * @return void - */ - public function maybe_add_back_link(): void { - if ( ! self::$list_url ) { - return; - } - - add_action( 'all_admin_notices', [ $this, 'render_back_link' ], 999 ); - } - - /** - * Render the back link to the migrations list page. - * - * Outputs the link and a full-width horizontal rule separator - * after admin notices and before the page content. - * - * @since 0.0.1 - * - * @return void - */ - public function render_back_link(): void { - if ( ! self::$list_url ) { - return; - } - - printf( - '', - esc_url( self::$list_url ), - esc_html__( 'Migrations', 'stellarwp-migrations' ) - ); - } - /** * Save the screen option value. * @@ -222,6 +181,7 @@ public function render_single_page(): void { return; } + Config::get_container()->get( UI::class )->render_single( $migration_id ); } diff --git a/src/Admin/UI.php b/src/Admin/UI.php index 7bea3f6..a041de7 100644 --- a/src/Admin/UI.php +++ b/src/Admin/UI.php @@ -147,6 +147,7 @@ public function render_single( string $migration_id ): void { 'migration' => $migration, 'executions' => $executions, 'rest_base_url' => rest_url( REST_Provider::get_namespace() ), + 'list_url' => Provider::get_list_url(), ] ); } diff --git a/src/views/single.php b/src/views/single.php index abcb189..99ef2cd 100644 --- a/src/views/single.php +++ b/src/views/single.php @@ -30,6 +30,14 @@ $template = Config::get_template_engine(); ?>
+ +
+ + + +
+
+

From d9a6ec6d637a4d6b6cb4797061fd49ab7b2c1496 Mon Sep 17 00:00:00 2001 From: Dimitrios Pantazis Date: Mon, 9 Feb 2026 23:05:01 +0200 Subject: [PATCH 10/20] refactor: change the class for the hr divider --- src/views/single.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/single.php b/src/views/single.php index 99ef2cd..a235195 100644 --- a/src/views/single.php +++ b/src/views/single.php @@ -35,7 +35,7 @@ -
+
From f46d1f6ec222d9379ae7d9fa70cf605d97296f36 Mon Sep 17 00:00:00 2001 From: Dimitrios Pantazis Date: Mon, 9 Feb 2026 23:11:58 +0200 Subject: [PATCH 11/20] feat: update tests and style the back to page link --- assets/css/admin.css | 31 +++++ docs/admin-ui.md | 10 +- src/Admin/Provider.php | 1 - tests/wpunit/Admin/Provider_Test.php | 55 -------- tests/wpunit/Admin/UI_Test.php | 38 ++++++ ...ck_link_when_list_url_set__0.snapshot.html | 126 ++++++++++++++++++ 6 files changed, 200 insertions(+), 61 deletions(-) create mode 100644 tests/wpunit/Admin/__snapshots__/UI_Test__it_should_render_single_with_back_link_when_list_url_set__0.snapshot.html diff --git a/assets/css/admin.css b/assets/css/admin.css index ca0046b..034facd 100644 --- a/assets/css/admin.css +++ b/assets/css/admin.css @@ -503,6 +503,37 @@ max-width: 1200px; } +/* Back Link */ +.stellarwp-migration-single__back { + margin-bottom: 16px; +} + +.stellarwp-migration-single__back-link { + align-items: center; + color: #50575e; + display: inline-flex; + font-size: 13px; + gap: 2px; + text-decoration: none; +} + +.stellarwp-migration-single__back-link:hover, +.stellarwp-migration-single__back-link:focus { + color: #2271b1; +} + +.stellarwp-migration-single__back-link .dashicons { + font-size: 16px; + height: 16px; + width: 16px; +} + +.stellarwp-migrations-back-link-divider { + border: none; + border-top: 1px solid #c3c4c7; + margin: 12px 0 0; +} + /* Header */ .stellarwp-migration-single__header { margin-bottom: 24px; diff --git a/docs/admin-ui.md b/docs/admin-ui.md index 5234299..7982c19 100644 --- a/docs/admin-ui.md +++ b/docs/admin-ui.md @@ -392,7 +392,7 @@ This uses the `submenu_file` filter and `adminmenu` action internally, following ### `Provider::set_list_url( string $url )` -Sets the URL for the migrations list page. When configured, the single migration detail page will display a "Migrations" back link above the migration content, separated by a soft horizontal rule. +Sets the URL for the migrations list page. When configured, the single migration detail page will display a "Migrations" back link above the migration content, separated by a full-width horizontal rule. The URL is passed as a `list_url` template variable to the `single` template via `UI::render_single()`. ```php use StellarWP\Migrations\Admin\Provider; @@ -404,10 +404,10 @@ Provider::set_list_url( admin_url( 'admin.php?page=my-plugin-migrations' ) ); ```html ``` diff --git a/src/Admin/Provider.php b/src/Admin/Provider.php index 7dac33b..b199f07 100644 --- a/src/Admin/Provider.php +++ b/src/Admin/Provider.php @@ -181,7 +181,6 @@ public function render_single_page(): void { return; } - Config::get_container()->get( UI::class )->render_single( $migration_id ); } diff --git a/tests/wpunit/Admin/Provider_Test.php b/tests/wpunit/Admin/Provider_Test.php index bf826be..116d7f0 100644 --- a/tests/wpunit/Admin/Provider_Test.php +++ b/tests/wpunit/Admin/Provider_Test.php @@ -228,59 +228,4 @@ public function it_should_register_adminmenu_action(): void { ); } - /** - * @test - */ - public function it_should_add_back_link_action_when_list_url_set(): void { - Provider::set_list_url( 'http://wordpress.test/wp-admin/admin.php?page=test-migrations' ); - - $this->provider->maybe_add_back_link(); - - $this->assertIsInt( - has_action( 'all_admin_notices', [ $this->provider, 'render_back_link' ] ), - 'The all_admin_notices action should be registered when list URL is set.' - ); - } - - /** - * @test - */ - public function it_should_not_add_back_link_action_when_list_url_not_set(): void { - $this->provider->maybe_add_back_link(); - - $this->assertFalse( - has_action( 'all_admin_notices', [ $this->provider, 'render_back_link' ] ), - 'The all_admin_notices action should not be registered when no list URL is set.' - ); - } - - /** - * @test - */ - public function it_should_render_back_link_html(): void { - $url = 'http://wordpress.test/wp-admin/admin.php?page=test-migrations'; - Provider::set_list_url( $url ); - - ob_start(); - $this->provider->render_back_link(); - $output = ob_get_clean(); - - $this->assertStringContainsString( 'stellarwp-migration-single__back', $output ); - $this->assertStringContainsString( 'stellarwp-migration-single__back-link', $output ); - $this->assertStringContainsString( $url, $output ); - $this->assertStringContainsString( 'Migrations', $output ); - $this->assertStringContainsString( 'dashicons-arrow-left-alt2', $output ); - $this->assertStringContainsString( '
provider->render_back_link(); - $output = ob_get_clean(); - - $this->assertEmpty( $output, 'No output should be rendered when list URL is not set.' ); - } } diff --git a/tests/wpunit/Admin/UI_Test.php b/tests/wpunit/Admin/UI_Test.php index a2a1a88..74444b8 100644 --- a/tests/wpunit/Admin/UI_Test.php +++ b/tests/wpunit/Admin/UI_Test.php @@ -14,6 +14,7 @@ namespace StellarWP\Migrations\Tests\Admin; use lucatume\WPBrowser\TestCase\WPTestCase; +use StellarWP\Migrations\Admin\Provider as Admin_Provider; use StellarWP\Migrations\Admin\UI; use StellarWP\Migrations\Config; use StellarWP\Migrations\Enums\Status; @@ -87,6 +88,7 @@ public function tear_down_ui(): void { Multi_Batch_Migration::reset(); Not_Applicable_Migration::reset(); Tagged_Migration::reset(); + Admin_Provider::reset(); tests_migrations_clear_calls_data(); } @@ -490,4 +492,40 @@ public function it_should_trigger_doing_it_wrong_with_correct_message(): void { // The setExpectedIncorrectUsage will handle the assertion. } + + /** + * @test + */ + public function it_should_render_single_with_back_link_when_list_url_set(): void { + $this->set_admin_user(); + $this->register_test_migrations(); + + Admin_Provider::set_list_url( 'http://wordpress.test/wp-admin/admin.php?page=test-migrations' ); + + ob_start(); + $this->ui->render_single( 'tests_simple_migration' ); + $output = ob_get_clean(); + + $this->assertStringContainsString( 'stellarwp-migration-single__back', $output ); + $this->assertStringContainsString( 'stellarwp-migration-single__back-link', $output ); + $this->assertStringContainsString( 'http://wordpress.test/wp-admin/admin.php?page=test-migrations', $output ); + $this->assertStringContainsString( 'Migrations', $output ); + $this->assertStringContainsString( 'stellarwp-migrations-back-link-divider', $output ); + + $this->assertMatchesHtmlSnapshot( $output ); + } + + /** + * @test + */ + public function it_should_render_single_without_back_link_when_list_url_not_set(): void { + $this->set_admin_user(); + $this->register_test_migrations(); + + ob_start(); + $this->ui->render_single( 'tests_simple_migration' ); + $output = ob_get_clean(); + + $this->assertStringNotContainsString( 'stellarwp-migration-single__back', $output ); + } } diff --git a/tests/wpunit/Admin/__snapshots__/UI_Test__it_should_render_single_with_back_link_when_list_url_set__0.snapshot.html b/tests/wpunit/Admin/__snapshots__/UI_Test__it_should_render_single_with_back_link_when_list_url_set__0.snapshot.html new file mode 100644 index 0000000..8df03f2 --- /dev/null +++ b/tests/wpunit/Admin/__snapshots__/UI_Test__it_should_render_single_with_back_link_when_list_url_set__0.snapshot.html @@ -0,0 +1,126 @@ +
+ +
+

Simple Migration

+

This is a simple migration that runs a single batch.

+
+ +
+

Status

+
+
+
+ + Pending + + + 0 / 1 +
+ +
+
+
+
+
+
+
+ +
+ + +
+
+ +
+ +
+ + Not yet started +
+ + +
+
+ +
+

Configuration

+
+
+
+ Total Items + 1 +
+ +
+ Batch Size + 1 +
+ +
+ Total Batches + 1 +
+ +
+ Retry Attempts + 0 +
+ +
+ Applicable + + Yes +
+ +
+ Can Run + + Yes +
+ +
+
+
+ +
+

Logs

+
+
+

No executions yet. Run the migration to see logs.

+
+
+
+
From 4b968f006b9e0dfed48ebe46f80fc2b5ac58507b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 9 Feb 2026 21:12:18 +0000 Subject: [PATCH 12/20] chore: autopublish 2026-02-09T21:12:18Z --- docs/admin-ui.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/admin-ui.md b/docs/admin-ui.md index 7982c19..aa66ec3 100644 --- a/docs/admin-ui.md +++ b/docs/admin-ui.md @@ -404,10 +404,10 @@ Provider::set_list_url( admin_url( 'admin.php?page=my-plugin-migrations' ) ); ```html ``` From dd3f1e5fff2b05b8edecac523f86bf667b4d94b8 Mon Sep 17 00:00:00 2001 From: Dimitrios Pantazis Date: Mon, 9 Feb 2026 23:13:28 +0200 Subject: [PATCH 13/20] chore: fix static analysis --- src/Admin/Provider.php | 4 ++-- src/views/single.php | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Admin/Provider.php b/src/Admin/Provider.php index b199f07..84206f1 100644 --- a/src/Admin/Provider.php +++ b/src/Admin/Provider.php @@ -275,7 +275,7 @@ public function hijack_current_parent_file( $submenu_file ) { } // phpcs:ignore WordPress.Security.NonceVerification.Recommended - $current_page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; + $current_page = isset( $_GET['page'] ) ? sanitize_text_field( (string) wp_unslash( $_GET['page'] ) ) : ''; if ( $current_page !== self::get_single_page_slug() ) { return $submenu_file; @@ -283,7 +283,7 @@ public function hijack_current_parent_file( $submenu_file ) { global $plugin_page; - self::$stored_parent_file = $plugin_page; + self::$stored_parent_file = is_string( $plugin_page ) ? $plugin_page : null; $plugin_page = self::$parent_page; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited diff --git a/src/views/single.php b/src/views/single.php index a235195..c177801 100644 --- a/src/views/single.php +++ b/src/views/single.php @@ -26,11 +26,12 @@ $description = $migration->get_description(); $rest_base_url ??= ''; $executions ??= []; +$list_url ??= null; $template = Config::get_template_engine(); ?>
- +
From 40a04004310cf763fc4e9d52eaaf9f7ad2dd836e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 9 Feb 2026 21:14:13 +0000 Subject: [PATCH 14/20] chore: autopublish 2026-02-09T21:14:13Z --- tests/wpunit/Admin/Provider_Test.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/wpunit/Admin/Provider_Test.php b/tests/wpunit/Admin/Provider_Test.php index 116d7f0..8267616 100644 --- a/tests/wpunit/Admin/Provider_Test.php +++ b/tests/wpunit/Admin/Provider_Test.php @@ -227,5 +227,4 @@ public function it_should_register_adminmenu_action(): void { 'The adminmenu action should be registered.' ); } - } From 4e612dd1ea3b828d79c6650928a272592f2c304f Mon Sep 17 00:00:00 2001 From: Dimitrios Pantazis Date: Mon, 9 Feb 2026 23:16:17 +0200 Subject: [PATCH 15/20] chore: fix phpstan issues --- src/Admin/Provider.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Admin/Provider.php b/src/Admin/Provider.php index 84206f1..98b2c29 100644 --- a/src/Admin/Provider.php +++ b/src/Admin/Provider.php @@ -275,7 +275,8 @@ public function hijack_current_parent_file( $submenu_file ) { } // phpcs:ignore WordPress.Security.NonceVerification.Recommended - $current_page = isset( $_GET['page'] ) ? sanitize_text_field( (string) wp_unslash( $_GET['page'] ) ) : ''; + $raw_page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? wp_unslash( $_GET['page'] ) : ''; + $current_page = sanitize_text_field( $raw_page ); if ( $current_page !== self::get_single_page_slug() ) { return $submenu_file; From 793f31d2c61adc70c65c34c6e888c63ca1d8ff93 Mon Sep 17 00:00:00 2001 From: Dimitrios Pantazis Date: Mon, 9 Feb 2026 23:18:05 +0200 Subject: [PATCH 16/20] chore: ignore word `adminmenu` from spell checker --- .cspell.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.cspell.json b/.cspell.json index 8f617e3..79b973a 100644 --- a/.cspell.json +++ b/.cspell.json @@ -22,6 +22,7 @@ "words": [ "absint", "abspath", + "adminmenu", "bunx", "chromedriver", "codecept", From b8aff2cede95bb899d0b94d72da153d9123572a2 Mon Sep 17 00:00:00 2001 From: Dimitrios Pantazis Date: Mon, 9 Feb 2026 23:21:47 +0200 Subject: [PATCH 17/20] chore: fix phpcs issues --- phpcs.xml | 4 ++++ src/Admin/Provider.php | 3 +-- src/views/single.php | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/phpcs.xml b/phpcs.xml index aca7be8..43bb472 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -70,6 +70,10 @@ */tests/* + + */tests/* + + */tests/* diff --git a/src/Admin/Provider.php b/src/Admin/Provider.php index 98b2c29..f979222 100644 --- a/src/Admin/Provider.php +++ b/src/Admin/Provider.php @@ -275,8 +275,7 @@ public function hijack_current_parent_file( $submenu_file ) { } // phpcs:ignore WordPress.Security.NonceVerification.Recommended - $raw_page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? wp_unslash( $_GET['page'] ) : ''; - $current_page = sanitize_text_field( $raw_page ); + $current_page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? wp_unslash( sanitize_text_field( $_GET['page'] ) ) : ''; if ( $current_page !== self::get_single_page_slug() ) { return $submenu_file; diff --git a/src/views/single.php b/src/views/single.php index c177801..8f26c87 100644 --- a/src/views/single.php +++ b/src/views/single.php @@ -10,6 +10,7 @@ * @var StellarWP\Migrations\Contracts\Migration $migration The migration object. * @var list> $executions List of execution records. * @var string $rest_base_url REST API base URL. + * @var string|null $list_url List URL. */ defined( 'ABSPATH' ) || exit; From 64d6baadb9bc45187fc8c05cef1499097e2b32fe Mon Sep 17 00:00:00 2001 From: Dimitrios Pantazis Date: Tue, 10 Feb 2026 00:15:35 +0200 Subject: [PATCH 18/20] fix: amend screen reader attribute --- src/views/single.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/single.php b/src/views/single.php index 8f26c87..2ed7e53 100644 --- a/src/views/single.php +++ b/src/views/single.php @@ -34,7 +34,7 @@
- + From 6e97535d7f595f6dcc6ac95ff7c772bb6117c4a6 Mon Sep 17 00:00:00 2001 From: Dimitrios Pantazis Date: Tue, 10 Feb 2026 01:07:30 +0200 Subject: [PATCH 19/20] chore: fix phpcs --- src/views/single.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/single.php b/src/views/single.php index 2ed7e53..ffb0640 100644 --- a/src/views/single.php +++ b/src/views/single.php @@ -34,7 +34,7 @@
- + From 5157d21c86202964a432281b872c149f12c0e6cf Mon Sep 17 00:00:00 2001 From: Dimitrios Pantazis Date: Tue, 10 Feb 2026 01:10:19 +0200 Subject: [PATCH 20/20] tests: update snapshot --- ...der_single_with_back_link_when_list_url_set__0.snapshot.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/wpunit/Admin/__snapshots__/UI_Test__it_should_render_single_with_back_link_when_list_url_set__0.snapshot.html b/tests/wpunit/Admin/__snapshots__/UI_Test__it_should_render_single_with_back_link_when_list_url_set__0.snapshot.html index 8df03f2..9a96357 100644 --- a/tests/wpunit/Admin/__snapshots__/UI_Test__it_should_render_single_with_back_link_when_list_url_set__0.snapshot.html +++ b/tests/wpunit/Admin/__snapshots__/UI_Test__it_should_render_single_with_back_link_when_list_url_set__0.snapshot.html @@ -1,6 +1,6 @@