Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
42 changes: 42 additions & 0 deletions includes/Settings/Settings_Page.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use WordPress\AI\Experiments\Experiment_Category;
use WordPress\AI\Features\Feature_Category;
use WordPress\AI\Features\Registry;

use function WordPress\AI\has_ai_credentials;
use function WordPress\AI\has_valid_ai_credentials;

Expand All @@ -27,6 +28,16 @@
*/
class Settings_Page {

/**
* Legacy settings page slug.
* TODO: either once [0.6.0 is less than 10% of installs](https://wordpress.org/plugins/ai/advanced/) or we're in October 2026 let's remove this section in case other plugin(s) are attempting to use the `ai` page.
*
Comment thread
dkotter marked this conversation as resolved.
* @since x.x.x
*
* @var string
*/
private const LEGACY_PAGE_SLUG = 'ai';

/**
* The settings page slug.
*
Expand All @@ -45,6 +56,9 @@ class Settings_Page {
* @return void
*/
public static function init( Registry $registry ): void {
add_action( 'admin_init', array( self::class, 'maybe_redirect_legacy_page' ), 1 );
add_action( 'admin_page_access_denied', array( self::class, 'maybe_redirect_legacy_page' ) );

if ( function_exists( 'ai_ai_wp_admin_render_page' ) ) {
add_action(
'admin_menu',
Expand Down Expand Up @@ -92,6 +106,34 @@ static function () {
}
}

/**
* Redirects legacy settings page slug to the current settings route.
*
* @since x.x.x
*/
public static function maybe_redirect_legacy_page(): void {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading query param for admin page detection only, no data processing.
if ( ! isset( $_GET['page'] ) ) {
return;
}

// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading query param for admin page detection only, no data processing.
$page = sanitize_key( wp_unslash( (string) $_GET['page'] ) );
if ( self::LEGACY_PAGE_SLUG !== $page ) {
return;
}

$redirect_url = add_query_arg(
'page',
self::PAGE_SLUG,
admin_url( 'options-general.php' )
);

if ( wp_safe_redirect( $redirect_url, 301, 'WordPress AI plugin' ) ) {
exit;
}
}

/**
* Gets feature group metadata for the settings UI.
*
Expand Down
41 changes: 41 additions & 0 deletions tests/Integration/Includes/Settings/Settings_PageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ public function setUp(): void {
public function tearDown(): void {
remove_all_filters( 'wpai_settings_feature_groups' );
remove_all_filters( 'wpai_settings_feature_metadata' );
remove_all_filters( 'wp_redirect' );
$_GET = array();
parent::tearDown();
}

Expand Down Expand Up @@ -486,4 +488,43 @@ public function test_feature_without_settings_has_empty_settings_fields() {
$this->assertArrayHasKey( 'settingsFields', $feature );
$this->assertSame( array(), $feature['settingsFields'], 'Feature without custom settings should have empty settingsFields' );
}

/**
* Test that init registers an admin redirect hook for the legacy settings slug.
*/
public function test_init_registers_legacy_settings_redirect_hook() {
Settings_Page::init( $this->registry );

$this->assertTrue(
has_action( 'admin_init', array( Settings_Page::class, 'maybe_redirect_legacy_page' ) ) !== false
);
$this->assertTrue(
has_action( 'admin_page_access_denied', array( Settings_Page::class, 'maybe_redirect_legacy_page' ) ) !== false
);
}

/**
* Test that the legacy settings slug redirects to the new slug.
*/
public function test_legacy_settings_slug_redirects_to_new_slug() {
$captured_location = null;
$captured_status = null;

add_filter(
'wp_redirect',
static function ( $location, $status ) use ( &$captured_location, &$captured_status ) {
$captured_location = $location;
$captured_status = $status;
return false;
},
10,
2
);

$_GET['page'] = 'ai';
Settings_Page::maybe_redirect_legacy_page();

$this->assertSame( admin_url( 'options-general.php?page=ai-wp-admin' ), $captured_location );
$this->assertSame( 301, $captured_status );
}
}
Loading