Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
485ed1f
feat: add methods to specify parent page and go back to List URL
dpanta94 Feb 9, 2026
1e2eb47
tests: added test coverage
dpanta94 Feb 9, 2026
cbe6142
docs: updated docs with included changes
dpanta94 Feb 9, 2026
4cf2faf
Merge cbe6142f3af7cfbaa2375057f1241c9e1b3baedb into 4211ab8bb3af5d7b4…
dpanta94 Feb 9, 2026
74bf009
chore: autopublish 2026-02-09T20:03:40Z
github-actions[bot] Feb 9, 2026
c3aa16c
fix: overwritten global
dpanta94 Feb 9, 2026
35fb57b
Merge remote-tracking branch 'origin/feat/parent-page-method' into fe…
dpanta94 Feb 9, 2026
f189f30
feat: update refs from parent_file to plugin page
dpanta94 Feb 9, 2026
c744996
feat: improve style of back to list URL
dpanta94 Feb 9, 2026
8c8ec48
Merge c74499659a8ee2880c8e654f25b3c4c146e8e2f3 into 4211ab8bb3af5d7b4…
dpanta94 Feb 9, 2026
905aede
chore: autopublish 2026-02-09T20:49:44Z
github-actions[bot] Feb 9, 2026
a8d2067
refactor: include the link within the template
dpanta94 Feb 9, 2026
d9a6ec6
refactor: change the class for the hr divider
dpanta94 Feb 9, 2026
f46d1f6
feat: update tests and style the back to page link
dpanta94 Feb 9, 2026
c6b75f0
Merge f46d1f6ec222d9379ae7d9fa70cf605d97296f36 into 4211ab8bb3af5d7b4…
dpanta94 Feb 9, 2026
4b968f0
chore: autopublish 2026-02-09T21:12:18Z
github-actions[bot] Feb 9, 2026
dd3f1e5
chore: fix static analysis
dpanta94 Feb 9, 2026
bdee411
Merge dd3f1e5fff2b05b8edecac523f86bf667b4d94b8 into 4211ab8bb3af5d7b4…
dpanta94 Feb 9, 2026
40a0400
chore: autopublish 2026-02-09T21:14:13Z
github-actions[bot] Feb 9, 2026
4e612dd
chore: fix phpstan issues
dpanta94 Feb 9, 2026
793f31d
chore: ignore word `adminmenu` from spell checker
dpanta94 Feb 9, 2026
b8aff2c
chore: fix phpcs issues
dpanta94 Feb 9, 2026
64d6baa
fix: amend screen reader attribute
dpanta94 Feb 9, 2026
47e5ed9
Merge remote-tracking branch 'origin/main' into feat/parent-page-method
dpanta94 Feb 9, 2026
6e97535
chore: fix phpcs
dpanta94 Feb 9, 2026
5157d21
tests: update snapshot
dpanta94 Feb 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"words": [
"absint",
"abspath",
"adminmenu",
"bunx",
"chromedriver",
"codecept",
Expand Down
31 changes: 31 additions & 0 deletions assets/css/admin.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
71 changes: 71 additions & 0 deletions docs/admin-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 `$plugin_page` 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 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;

Provider::set_list_url( admin_url( 'admin.php?page=my-plugin-migrations' ) );
```

**Rendered output (when set):**

```html
<div class="stellarwp-migration-single__back">
<a href="..." class="stellarwp-migration-single__back-link">
<span class="dashicons dashicons-arrow-left-alt2"></span> Migrations
</a>
<hr class="stellarwp-migrations-back-link-divider" />
</div>
```

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:
Expand Down
4 changes: 4 additions & 0 deletions phpcs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@
<exclude-pattern>*/tests/*</exclude-pattern>
</rule>

<rule ref="WordPress.WP.GlobalVariablesOverride.Prohibited">
<exclude-pattern>*/tests/*</exclude-pattern>
</rule>

<rule ref="WordPress.DB.SlowDBQuery.slow_db_query_meta_value">
<exclude-pattern>*/tests/*</exclude-pattern>
</rule>
Expand Down
133 changes: 132 additions & 1 deletion src/Admin/Provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,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.
*
Expand All @@ -49,6 +81,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' ] );
add_action( 'admin_post_' . Log_Download_Handler::get_action_name(), [ Log_Download_Handler::class, 'download' ] );
}

Expand Down Expand Up @@ -181,6 +215,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 `$plugin_page` 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'] ) && is_string( $_GET['page'] ) ? wp_unslash( sanitize_text_field( $_GET['page'] ) ) : '';

if ( $current_page !== self::get_single_page_slug() ) {
return $submenu_file;
}

global $plugin_page;

self::$stored_parent_file = is_string( $plugin_page ) ? $plugin_page : null;

$plugin_page = 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 $plugin_page;

$plugin_page = self::$stored_parent_file; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
}

/**
* Reset registration status. Primarily for testing.
*
Expand All @@ -189,6 +317,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;
}
}
1 change: 1 addition & 0 deletions src/Admin/UI.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
]
);
}
Expand Down
10 changes: 10 additions & 0 deletions src/views/single.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* @var StellarWP\Migrations\Contracts\Migration $migration The migration object.
* @var list<array<string,mixed>> $executions List of execution records.
* @var string $rest_base_url REST API base URL.
* @var string|null $list_url List URL.
*/

defined( 'ABSPATH' ) || exit;
Expand All @@ -26,10 +27,19 @@
$description = $migration->get_description();
$rest_base_url ??= '';
$executions ??= [];
$list_url ??= null;

$template = Config::get_template_engine();
?>
<div class="wrap stellarwp-migration-single" data-rest-url="<?php echo esc_url( $rest_base_url ); ?>" data-migration-id="<?php echo esc_attr( $migration_id ); ?>">
<?php if ( is_string( $list_url ) ) : ?>
<div class="stellarwp-migration-single__back">
<a href="<?php echo esc_url( $list_url ); ?>" class="stellarwp-migration-single__back-link" aria-label="<?php esc_attr_e( 'Back to Migrations', 'stellarwp-migrations' ); ?>">
<span class="dashicons dashicons-arrow-left-alt2"></span> <?php esc_html_e( 'Migrations', 'stellarwp-migrations' ); ?>
Comment thread
dpanta94 marked this conversation as resolved.
Resolved
</a>
<hr class="stellarwp-migrations-back-link-divider" />
</div>
<?php endif; ?>
<header class="stellarwp-migration-single__header">
<h1 class="stellarwp-migration-single__label"><?php echo esc_html( $migration_label ); ?></h1>
<p class="stellarwp-migration-single__description"><?php echo esc_html( $description ); ?></p>
Expand Down
Loading
Loading