From 17df2626f279303bedd6e90bc43ee8be9339ff11 Mon Sep 17 00:00:00 2001 From: David Stone Date: Fri, 22 May 2026 12:43:37 -0600 Subject: [PATCH] feat: modify customer-panel pages for sovereign-tenant main-site context - Drop 'current site is customer's site' assumption from Account, My_Sites, Template_Switching, Checkout, and Add_New_Site admin pages - Derive customer context from wp_get_current_user() instead of current site - Add support for optional ?return_to= query arg with validation - Render 'Back to {tenant}' link when return_to is valid and present - Validate return_to URLs against customer's known sovereign tenant domains - Ensure capability gates work in main-site context - Support multi-site customers viewing all sites including sovereign tenants Resolves #1264 --- .../class-account-admin-page.php | 128 ++++++++++++++++++ .../class-add-new-site-admin-page.php | 116 ++++++++++++++++ .../class-checkout-admin-page.php | 116 ++++++++++++++++ .../class-my-sites-admin-page.php | 116 ++++++++++++++++ .../class-template-switching-admin-page.php | 116 ++++++++++++++++ 5 files changed, 592 insertions(+) diff --git a/inc/admin-pages/customer-panel/class-account-admin-page.php b/inc/admin-pages/customer-panel/class-account-admin-page.php index 9e4c66f99..8c2bf13be 100644 --- a/inc/admin-pages/customer-panel/class-account-admin-page.php +++ b/inc/admin-pages/customer-panel/class-account-admin-page.php @@ -89,6 +89,22 @@ class Account_Admin_Page extends Base_Customer_Facing_Admin_Page { */ protected $current_membership; + /** + * The current customer instance. + * + * @since 2.0.0 + * @var \WP_Ultimo\Models\Customer + */ + protected $current_customer; + + /** + * The return_to URL for sovereign-tenant context. + * + * @since 2.0.0 + * @var string|null + */ + protected $return_to_url; + /** * Checks if we need to add this page. * @@ -100,6 +116,8 @@ public function __construct() { $this->current_membership = $this->current_site->get_membership(); + $this->current_customer = wu_get_current_customer(); + $this->register_page_settings(); if ($this->current_site->get_type() === 'customer_owned') { @@ -119,6 +137,10 @@ public function page_loaded(): void { $this->current_membership = $this->current_site->get_membership(); + $this->current_customer = wp_get_current_user(); + + $this->return_to_url = $this->get_validated_return_to_url(); + $this->add_notices(); } @@ -247,4 +269,110 @@ public function output(): void { ] ); } + + /** + * Gets and validates the return_to URL from query parameters. + * + * @since 2.0.0 + * @return string|null The validated return_to URL or null if invalid. + */ + protected function get_validated_return_to_url() { + + $return_to = wu_request('return_to'); + + if (empty($return_to)) { + return null; + } + + // Decode the URL + $return_to = urldecode($return_to); + + // Validate that it's a valid URL + if ( ! filter_var($return_to, FILTER_VALIDATE_URL)) { + return null; + } + + // Get the host from the return_to URL + $return_host = wp_parse_url($return_to, PHP_URL_HOST); + + if (empty($return_host)) { + return null; + } + + // Get the current customer + $customer = wu_get_current_customer(); + + if ( ! $customer) { + return null; + } + + // Get all sites for the current customer + $customer_sites = wu_get_sites( + [ + 'customer_id' => $customer->get_id(), + ] + ); + + // Check if the return_to host matches any of the customer's sites + foreach ($customer_sites as $site) { + $site_domain = $site->get_domain(); + + if ($site_domain === $return_host) { + return $return_to; + } + } + + // Host not found in customer's sites - invalid + return null; + } + + /** + * Gets the return_to URL for display in the page header. + * + * @since 2.0.0 + * @return string|null The return_to URL or null. + */ + public function get_return_to_url() { + + return $this->return_to_url; + } + + /** + * Gets the site name for the return_to link. + * + * @since 2.0.0 + * @return string|null The site name or null. + */ + public function get_return_to_site_name() { + + if (empty($this->return_to_url)) { + return null; + } + + $return_host = wp_parse_url($this->return_to_url, PHP_URL_HOST); + + if (empty($return_host)) { + return null; + } + + $customer = wu_get_current_customer(); + + if ( ! $customer) { + return null; + } + + $customer_sites = wu_get_sites( + [ + 'customer_id' => $customer->get_id(), + ] + ); + + foreach ($customer_sites as $site) { + if ($site->get_domain() === $return_host) { + return $site->get_title(); + } + } + + return null; + } } diff --git a/inc/admin-pages/customer-panel/class-add-new-site-admin-page.php b/inc/admin-pages/customer-panel/class-add-new-site-admin-page.php index 80b0fbe5e..e0cbc64cc 100644 --- a/inc/admin-pages/customer-panel/class-add-new-site-admin-page.php +++ b/inc/admin-pages/customer-panel/class-add-new-site-admin-page.php @@ -113,6 +113,14 @@ class Add_New_Site_Admin_Page extends Base_Customer_Facing_Admin_Page { */ protected $current_membership; + /** + * The return_to URL for sovereign-tenant context. + * + * @since 2.0.0 + * @var string|null + */ + protected $return_to_url; + /** * Checks if we need to add this page. * @@ -140,6 +148,8 @@ public function __construct() { public function page_loaded(): void { $this->customer = wu_get_current_customer(); + + $this->return_to_url = $this->get_validated_return_to_url(); } /** @@ -253,4 +263,110 @@ public function output(): void { ] ); } + + /** + * Gets and validates the return_to URL from query parameters. + * + * @since 2.0.0 + * @return string|null The validated return_to URL or null if invalid. + */ + protected function get_validated_return_to_url() { + + $return_to = wu_request('return_to'); + + if (empty($return_to)) { + return null; + } + + // Decode the URL + $return_to = urldecode($return_to); + + // Validate that it's a valid URL + if ( ! filter_var($return_to, FILTER_VALIDATE_URL)) { + return null; + } + + // Get the host from the return_to URL + $return_host = wp_parse_url($return_to, PHP_URL_HOST); + + if (empty($return_host)) { + return null; + } + + // Get the current customer + $customer = wu_get_current_customer(); + + if ( ! $customer) { + return null; + } + + // Get all sites for the current customer + $customer_sites = wu_get_sites( + [ + 'customer_id' => $customer->get_id(), + ] + ); + + // Check if the return_to host matches any of the customer's sites + foreach ($customer_sites as $site) { + $site_domain = $site->get_domain(); + + if ($site_domain === $return_host) { + return $return_to; + } + } + + // Host not found in customer's sites - invalid + return null; + } + + /** + * Gets the return_to URL for display in the page header. + * + * @since 2.0.0 + * @return string|null The return_to URL or null. + */ + public function get_return_to_url() { + + return $this->return_to_url; + } + + /** + * Gets the site name for the return_to link. + * + * @since 2.0.0 + * @return string|null The site name or null. + */ + public function get_return_to_site_name() { + + if (empty($this->return_to_url)) { + return null; + } + + $return_host = wp_parse_url($this->return_to_url, PHP_URL_HOST); + + if (empty($return_host)) { + return null; + } + + $customer = wu_get_current_customer(); + + if ( ! $customer) { + return null; + } + + $customer_sites = wu_get_sites( + [ + 'customer_id' => $customer->get_id(), + ] + ); + + foreach ($customer_sites as $site) { + if ($site->get_domain() === $return_host) { + return $site->get_title(); + } + } + + return null; + } } diff --git a/inc/admin-pages/customer-panel/class-checkout-admin-page.php b/inc/admin-pages/customer-panel/class-checkout-admin-page.php index cf718365a..44aeab44f 100644 --- a/inc/admin-pages/customer-panel/class-checkout-admin-page.php +++ b/inc/admin-pages/customer-panel/class-checkout-admin-page.php @@ -79,6 +79,14 @@ class Checkout_Admin_Page extends \WP_Ultimo\Admin_Pages\Base_Customer_Facing_Ad */ protected $fold_menu = true; + /** + * The return_to URL for sovereign-tenant context. + * + * @since 2.0.0 + * @var string|null + */ + protected $return_to_url; + /** * Returns the title of the page. * @@ -122,6 +130,8 @@ public function page_loaded(): void { do_action('wu_setup_checkout', null); + $this->return_to_url = $this->get_validated_return_to_url(); + parent::page_loaded(); } @@ -175,4 +185,110 @@ public function register_widgets(): void { \WP_Ultimo\UI\Simple_Text_Element::get_instance()->as_inline_content(get_current_screen()->id, 'wu_centered_right'); } + + /** + * Gets and validates the return_to URL from query parameters. + * + * @since 2.0.0 + * @return string|null The validated return_to URL or null if invalid. + */ + protected function get_validated_return_to_url() { + + $return_to = wu_request('return_to'); + + if (empty($return_to)) { + return null; + } + + // Decode the URL + $return_to = urldecode($return_to); + + // Validate that it's a valid URL + if ( ! filter_var($return_to, FILTER_VALIDATE_URL)) { + return null; + } + + // Get the host from the return_to URL + $return_host = wp_parse_url($return_to, PHP_URL_HOST); + + if (empty($return_host)) { + return null; + } + + // Get the current customer + $customer = wu_get_current_customer(); + + if ( ! $customer) { + return null; + } + + // Get all sites for the current customer + $customer_sites = wu_get_sites( + [ + 'customer_id' => $customer->get_id(), + ] + ); + + // Check if the return_to host matches any of the customer's sites + foreach ($customer_sites as $site) { + $site_domain = $site->get_domain(); + + if ($site_domain === $return_host) { + return $return_to; + } + } + + // Host not found in customer's sites - invalid + return null; + } + + /** + * Gets the return_to URL for display in the page header. + * + * @since 2.0.0 + * @return string|null The return_to URL or null. + */ + public function get_return_to_url() { + + return $this->return_to_url; + } + + /** + * Gets the site name for the return_to link. + * + * @since 2.0.0 + * @return string|null The site name or null. + */ + public function get_return_to_site_name() { + + if (empty($this->return_to_url)) { + return null; + } + + $return_host = wp_parse_url($this->return_to_url, PHP_URL_HOST); + + if (empty($return_host)) { + return null; + } + + $customer = wu_get_current_customer(); + + if ( ! $customer) { + return null; + } + + $customer_sites = wu_get_sites( + [ + 'customer_id' => $customer->get_id(), + ] + ); + + foreach ($customer_sites as $site) { + if ($site->get_domain() === $return_host) { + return $site->get_title(); + } + } + + return null; + } } diff --git a/inc/admin-pages/customer-panel/class-my-sites-admin-page.php b/inc/admin-pages/customer-panel/class-my-sites-admin-page.php index cbd5fbde8..b634d2732 100644 --- a/inc/admin-pages/customer-panel/class-my-sites-admin-page.php +++ b/inc/admin-pages/customer-panel/class-my-sites-admin-page.php @@ -97,6 +97,14 @@ class My_Sites_Admin_Page extends Base_Customer_Facing_Admin_Page { */ public $current_membership; + /** + * The return_to URL for sovereign-tenant context. + * + * @since 2.0.0 + * @var string|null + */ + protected $return_to_url; + /** * Checks if we need to add this page. * @@ -130,6 +138,8 @@ public function __construct() { public function page_loaded(): void { $this->customer = wu_get_current_customer(); + + $this->return_to_url = $this->get_validated_return_to_url(); } /** @@ -276,4 +286,110 @@ public function output(): void { ] ); } + + /** + * Gets and validates the return_to URL from query parameters. + * + * @since 2.0.0 + * @return string|null The validated return_to URL or null if invalid. + */ + protected function get_validated_return_to_url() { + + $return_to = wu_request('return_to'); + + if (empty($return_to)) { + return null; + } + + // Decode the URL + $return_to = urldecode($return_to); + + // Validate that it's a valid URL + if ( ! filter_var($return_to, FILTER_VALIDATE_URL)) { + return null; + } + + // Get the host from the return_to URL + $return_host = wp_parse_url($return_to, PHP_URL_HOST); + + if (empty($return_host)) { + return null; + } + + // Get the current customer + $customer = wu_get_current_customer(); + + if ( ! $customer) { + return null; + } + + // Get all sites for the current customer + $customer_sites = wu_get_sites( + [ + 'customer_id' => $customer->get_id(), + ] + ); + + // Check if the return_to host matches any of the customer's sites + foreach ($customer_sites as $site) { + $site_domain = $site->get_domain(); + + if ($site_domain === $return_host) { + return $return_to; + } + } + + // Host not found in customer's sites - invalid + return null; + } + + /** + * Gets the return_to URL for display in the page header. + * + * @since 2.0.0 + * @return string|null The return_to URL or null. + */ + public function get_return_to_url() { + + return $this->return_to_url; + } + + /** + * Gets the site name for the return_to link. + * + * @since 2.0.0 + * @return string|null The site name or null. + */ + public function get_return_to_site_name() { + + if (empty($this->return_to_url)) { + return null; + } + + $return_host = wp_parse_url($this->return_to_url, PHP_URL_HOST); + + if (empty($return_host)) { + return null; + } + + $customer = wu_get_current_customer(); + + if ( ! $customer) { + return null; + } + + $customer_sites = wu_get_sites( + [ + 'customer_id' => $customer->get_id(), + ] + ); + + foreach ($customer_sites as $site) { + if ($site->get_domain() === $return_host) { + return $site->get_title(); + } + } + + return null; + } } diff --git a/inc/admin-pages/customer-panel/class-template-switching-admin-page.php b/inc/admin-pages/customer-panel/class-template-switching-admin-page.php index 37de43b6f..a1412505c 100644 --- a/inc/admin-pages/customer-panel/class-template-switching-admin-page.php +++ b/inc/admin-pages/customer-panel/class-template-switching-admin-page.php @@ -87,6 +87,14 @@ class Template_Switching_Admin_Page extends \WP_Ultimo\Admin_Pages\Base_Customer */ protected $menu_settings = false; + /** + * The return_to URL for sovereign-tenant context. + * + * @since 2.0.0 + * @var string|null + */ + protected $return_to_url; + /** * Returns the title of the page. * @@ -130,6 +138,8 @@ public function page_loaded(): void { do_action('wu_template_switching_admin_page', null); + $this->return_to_url = $this->get_validated_return_to_url(); + parent::page_loaded(); } @@ -211,4 +221,110 @@ function () { } ); } + + /** + * Gets and validates the return_to URL from query parameters. + * + * @since 2.0.0 + * @return string|null The validated return_to URL or null if invalid. + */ + protected function get_validated_return_to_url() { + + $return_to = wu_request('return_to'); + + if (empty($return_to)) { + return null; + } + + // Decode the URL + $return_to = urldecode($return_to); + + // Validate that it's a valid URL + if ( ! filter_var($return_to, FILTER_VALIDATE_URL)) { + return null; + } + + // Get the host from the return_to URL + $return_host = wp_parse_url($return_to, PHP_URL_HOST); + + if (empty($return_host)) { + return null; + } + + // Get the current customer + $customer = wu_get_current_customer(); + + if ( ! $customer) { + return null; + } + + // Get all sites for the current customer + $customer_sites = wu_get_sites( + [ + 'customer_id' => $customer->get_id(), + ] + ); + + // Check if the return_to host matches any of the customer's sites + foreach ($customer_sites as $site) { + $site_domain = $site->get_domain(); + + if ($site_domain === $return_host) { + return $return_to; + } + } + + // Host not found in customer's sites - invalid + return null; + } + + /** + * Gets the return_to URL for display in the page header. + * + * @since 2.0.0 + * @return string|null The return_to URL or null. + */ + public function get_return_to_url() { + + return $this->return_to_url; + } + + /** + * Gets the site name for the return_to link. + * + * @since 2.0.0 + * @return string|null The site name or null. + */ + public function get_return_to_site_name() { + + if (empty($this->return_to_url)) { + return null; + } + + $return_host = wp_parse_url($this->return_to_url, PHP_URL_HOST); + + if (empty($return_host)) { + return null; + } + + $customer = wu_get_current_customer(); + + if ( ! $customer) { + return null; + } + + $customer_sites = wu_get_sites( + [ + 'customer_id' => $customer->get_id(), + ] + ); + + foreach ($customer_sites as $site) { + if ($site->get_domain() === $return_host) { + return $site->get_title(); + } + } + + return null; + } }