Admin redirects for HPOS URLs (#35463)
* Redirect COT/HPOS admin requests to the corresponding CPT screen, if COT is not authoritative. * Tidy handling of query parameters. * Linting fixes.
This commit is contained in:
parent
c561d7941d
commit
925432aebe
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: minor
|
||||||
|
Type: add
|
||||||
|
|
||||||
|
When custom order tables are not authoritative, admin UI requests will be redirected to the matching legacy order screen as appropriate.
|
|
@ -6,6 +6,7 @@
|
||||||
* @version 2.5.0
|
* @version 2.5.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Internal\Admin\Orders\COTRedirectionController;
|
||||||
use Automattic\WooCommerce\Internal\Admin\Orders\PageController as Custom_Orders_PageController;
|
use Automattic\WooCommerce\Internal\Admin\Orders\PageController as Custom_Orders_PageController;
|
||||||
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
|
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
|
||||||
use Automattic\WooCommerce\Admin\Features\Features;
|
use Automattic\WooCommerce\Admin\Features\Features;
|
||||||
|
@ -316,6 +317,8 @@ class WC_Admin_Menus {
|
||||||
if ( wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled() ) {
|
if ( wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled() ) {
|
||||||
$this->orders_page_controller = new Custom_Orders_PageController();
|
$this->orders_page_controller = new Custom_Orders_PageController();
|
||||||
$this->orders_page_controller->setup();
|
$this->orders_page_controller->setup();
|
||||||
|
} else {
|
||||||
|
wc_get_container()->get( COTRedirectionController::class )->setup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Automattic\WooCommerce\Internal\Admin\Orders;
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When Custom Order Tables are not the default order store (ie, posts are authoritative), we should take care of
|
||||||
|
* redirecting requests for the order editor and order admin list table to the equivalent posts-table screens.
|
||||||
|
*
|
||||||
|
* If the redirect logic is problematic, it can be unhooked using code like the following example:
|
||||||
|
*
|
||||||
|
* remove_action(
|
||||||
|
* 'admin_page_access_denied',
|
||||||
|
* array( wc_get_container()->get( COTRedirectionController::class ), 'handle_hpos_admin_requests' )
|
||||||
|
* );
|
||||||
|
*/
|
||||||
|
class COTRedirectionController {
|
||||||
|
use AccessiblePrivateMethods;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add hooks needed to perform our magic.
|
||||||
|
*/
|
||||||
|
public function setup(): void {
|
||||||
|
// Only take action in cases where access to the admin screen would otherwise be denied.
|
||||||
|
self::add_action( 'admin_page_access_denied', array( $this, 'handle_hpos_admin_requests' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen for denied admin requests and, if they appear to relate to HPOS admin screens, potentially
|
||||||
|
* redirect the user to the equivalent CPT-driven screens.
|
||||||
|
*
|
||||||
|
* @param array|null $query_params The query parameters to use when determining the redirect. If not provided, the $_GET superglobal will be used.
|
||||||
|
*/
|
||||||
|
private function handle_hpos_admin_requests( $query_params = null ) {
|
||||||
|
$query_params = is_array( $query_params ) ? $query_params : $_GET;
|
||||||
|
|
||||||
|
if ( ! isset( $query_params['page'] ) || 'wc-orders' !== $query_params['page'] ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$params = wp_unslash( $query_params );
|
||||||
|
$action = $params['action'] ?? '';
|
||||||
|
unset( $params['page'] );
|
||||||
|
|
||||||
|
if ( 'edit' === $action && isset( $params['id'] ) ) {
|
||||||
|
$params['post'] = $params['id'];
|
||||||
|
unset( $params['id'] );
|
||||||
|
$new_url = add_query_arg( $params, get_admin_url( null, 'post.php' ) );
|
||||||
|
} elseif ( 'new' === $action ) {
|
||||||
|
unset( $params['action'] );
|
||||||
|
$params['post_type'] = 'shop_order';
|
||||||
|
$new_url = add_query_arg( $params, get_admin_url( null, 'post-new.php' ) );
|
||||||
|
} else {
|
||||||
|
// If nonce parameters are present and valid, rebuild them for the CPT admin list table.
|
||||||
|
if ( isset( $params['_wpnonce'] ) && check_admin_referer( 'bulk-orders' ) ) {
|
||||||
|
$params['_wp_http_referer'] = get_admin_url( null, 'edit.php?post_type=shop_order' );
|
||||||
|
$params['_wpnonce'] = wp_create_nonce( 'bulk-posts' );
|
||||||
|
}
|
||||||
|
|
||||||
|
// If an `order` array parameter is present, rename as `post`.
|
||||||
|
if ( isset( $params['order'] ) && is_array( $params['order'] ) ) {
|
||||||
|
$params['post'] = $params['order'];
|
||||||
|
unset( $params['order'] );
|
||||||
|
}
|
||||||
|
|
||||||
|
$params['post_type'] = 'shop_order';
|
||||||
|
$new_url = add_query_arg( $params, get_admin_url( null, 'edit.php' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! empty( $new_url ) && wp_safe_redirect( $new_url, 301 ) ) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
|
namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Internal\Admin\Orders\COTRedirectionController;
|
||||||
use Automattic\WooCommerce\Internal\Admin\Orders\Edit;
|
use Automattic\WooCommerce\Internal\Admin\Orders\Edit;
|
||||||
use Automattic\WooCommerce\Internal\Admin\Orders\ListTable;
|
use Automattic\WooCommerce\Internal\Admin\Orders\ListTable;
|
||||||
use Automattic\WooCommerce\Internal\Admin\Orders\PageController;
|
use Automattic\WooCommerce\Internal\Admin\Orders\PageController;
|
||||||
|
@ -21,6 +22,7 @@ class OrderAdminServiceProvider extends AbstractServiceProvider {
|
||||||
* @var string[]
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
protected $provides = array(
|
protected $provides = array(
|
||||||
|
COTRedirectionController::class,
|
||||||
PageController::class,
|
PageController::class,
|
||||||
Edit::class,
|
Edit::class,
|
||||||
ListTable::class,
|
ListTable::class,
|
||||||
|
@ -32,6 +34,7 @@ class OrderAdminServiceProvider extends AbstractServiceProvider {
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function register() {
|
public function register() {
|
||||||
|
$this->share( COTRedirectionController::class );
|
||||||
$this->share( PageController::class );
|
$this->share( PageController::class );
|
||||||
$this->share( Edit::class )->addArgument( PageController::class );
|
$this->share( Edit::class )->addArgument( PageController::class );
|
||||||
$this->share( ListTable::class )->addArgument( PageController::class );
|
$this->share( ListTable::class )->addArgument( PageController::class );
|
||||||
|
|
|
@ -0,0 +1,181 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Internal\Admin\Orders\COTRedirectionController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes our redirection logic covering HPOS admin screens when Custom Order Tables are not authoritative.
|
||||||
|
*/
|
||||||
|
class COTRedirectionControllerTest extends WC_Unit_Test_Case {
|
||||||
|
/**
|
||||||
|
* @var COTRedirectionController
|
||||||
|
*/
|
||||||
|
private $sut;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the URL of the last attempted redirect.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $redirected_to = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup our SUT and start listening for redirects.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setUp(): void {
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->sut = new COTRedirectionController();
|
||||||
|
$this->sut->setup();
|
||||||
|
$this->redirected_to = '';
|
||||||
|
|
||||||
|
add_filter( 'wp_redirect', array( $this, 'watch_and_anull_redirects' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove our redirect listener.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function tearDown(): void {
|
||||||
|
parent::tearDown();
|
||||||
|
remove_filter( 'wp_redirect', array( $this, 'watch_and_anull_redirects' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Captures the attempted redirect location, and stops the redirect from taking place.
|
||||||
|
*
|
||||||
|
* @param string $url Redirect location.
|
||||||
|
*
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
public function watch_and_anull_redirects( string $url ) {
|
||||||
|
$this->redirected_to = $url;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supplies the URL of the last attempted redirect, then resets ready for the next test.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function get_redirect_attempt(): string {
|
||||||
|
$return = $this->redirected_to;
|
||||||
|
$this->redirected_to = '';
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that redirects only occur in relation to HPOS admin screen requests.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function test_redirects_only_impact_hpos_admin_requests() {
|
||||||
|
$this->sut->handle_hpos_admin_requests( array( 'page' => 'wc-orders' ) );
|
||||||
|
$this->assertNotEmpty( $this->get_redirect_attempt(), 'A redirect was attempted in relation to an HPOS admin request.' );
|
||||||
|
|
||||||
|
$this->sut->handle_hpos_admin_requests( array( 'page' => 'foo' ) );
|
||||||
|
$this->assertEmpty( $this->get_redirect_attempt(), 'A redirect was not attempted in relation to a non-HPOS admin request.' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test order editor redirects work (in relation to creating new orders).
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function test_redirects_to_the_new_order_screen(): void {
|
||||||
|
$this->sut->handle_hpos_admin_requests(
|
||||||
|
array(
|
||||||
|
'action' => 'new',
|
||||||
|
'page' => 'wc-orders',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertStringContainsString(
|
||||||
|
'/wp-admin/post-new.php?post_type=shop_order',
|
||||||
|
$this->get_redirect_attempt(),
|
||||||
|
'Attempts to access the new order page (HPOS) are successfully redirected to the new order page (CPT).'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test order editor redirects work (in relation to existing orders).
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function test_redirects_to_the_order_editor_screen(): void {
|
||||||
|
$this->sut->handle_hpos_admin_requests(
|
||||||
|
array(
|
||||||
|
'action' => 'edit',
|
||||||
|
'id' => 12345,
|
||||||
|
'page' => 'wc-orders',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$redirect_url = $this->get_redirect_attempt();
|
||||||
|
$redirect_base = wp_parse_url( $redirect_url, PHP_URL_PATH );
|
||||||
|
parse_str( wp_parse_url( $redirect_url, PHP_URL_QUERY ), $redirect_query );
|
||||||
|
|
||||||
|
$this->assertStringContainsString(
|
||||||
|
'/post.php',
|
||||||
|
$redirect_base,
|
||||||
|
'Confirm order editor redirects go to the expected WordPress admin controller.'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
'12345',
|
||||||
|
$redirect_query['post'],
|
||||||
|
'Confirm order editor redirects maintain the correct order ID.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests order list table redirects work.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function test_redirects_to_the_order_admin_list_screen(): void {
|
||||||
|
$this->sut->handle_hpos_admin_requests(
|
||||||
|
array(
|
||||||
|
'arbitrary' => '3pd-integration',
|
||||||
|
'order' => array(
|
||||||
|
123,
|
||||||
|
456,
|
||||||
|
),
|
||||||
|
'page' => 'wc-orders',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$redirect_url = $this->get_redirect_attempt();
|
||||||
|
$redirect_base = wp_parse_url( $redirect_url, PHP_URL_PATH );
|
||||||
|
parse_str( wp_parse_url( $redirect_url, PHP_URL_QUERY ), $redirect_query );
|
||||||
|
|
||||||
|
$this->assertStringContainsString(
|
||||||
|
'/edit.php',
|
||||||
|
$redirect_base,
|
||||||
|
'Confirm order list table redirects go to the expected WordPress admin controller.'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
array(
|
||||||
|
'123',
|
||||||
|
'456',
|
||||||
|
),
|
||||||
|
$redirect_query['post'],
|
||||||
|
'Confirm order list table redirects maintain a list of order IDs for bulk action requests (if one was passed).'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
'shop_order',
|
||||||
|
$redirect_query['post_type'],
|
||||||
|
'Confirm order list table redirects reference the correct custom post type.'
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
'3pd-integration',
|
||||||
|
$redirect_query['arbitrary'],
|
||||||
|
'Confirm that arbitrary query parameters are also passed across via order list table redirects.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue